From 467af2c2c0ef3cbaf1ff6c192e5aed9fe5009089 Mon Sep 17 00:00:00 2001 From: Lobsterdog Contributors Date: Tue, 12 May 2026 18:58:39 -0600 Subject: [PATCH 1/2] ci-ofvhg: add kotlin-sdk as first fork-mode repo - Add kotlin-sdk repo entry to cistern.yaml with delivery_mode=fork, upstream_remote, and feature-fork aqueduct reference - Add feature-fork aqueduct workflow definition inline in cistern.yaml and as standalone aqueduct/feature-fork.yaml - Add TestAqueductConfig_KotlinSdkRepo_ParsesInForkMode test verifying fork-mode config parsing and validation - Update doctor test to generate cataractae for all aqueducts in the embedded config (not just default) --- aqueduct/aqueduct.yaml | 104 +---------------------------- aqueduct/feature-fork.yaml | 101 ++++++++++++++++++++++++++++ cmd/ct/assets/cistern.yaml | 101 ++++++++++++++++++++++++++++ cmd/ct/doctor_test.go | 23 ++++--- internal/aqueduct/workflow_test.go | 71 ++++++++++++++++++++ 5 files changed, 287 insertions(+), 113 deletions(-) create mode 100644 aqueduct/feature-fork.yaml diff --git a/aqueduct/aqueduct.yaml b/aqueduct/aqueduct.yaml index 794b4103..e7c9b2b2 100644 --- a/aqueduct/aqueduct.yaml +++ b/aqueduct/aqueduct.yaml @@ -94,106 +94,4 @@ cataractae: timeout_minutes: 60 on_pass: done on_recirculate: implement - on_pool: pooled - -# Fork-mode variant: for external repos where you push to a fork and open a PR -# against upstream instead of merging directly. Use this workflow with a repo -# config that sets delivery_mode: fork and upstream_remote: . -# -# name: feature-fork -# -# cataractae: -# - name: architect -# type: agent -# identity: architect -# context: full_codebase -# skills: -# - name: cistern-signaling -# - name: cistern-git -# - name: cistern-diff-reader -# timeout_minutes: 20 -# on_pass: implement -# on_recirculate: architect -# on_fail: pooled -# on_pool: pooled -# -# - name: implement -# type: agent -# identity: implementer -# context: full_codebase -# skills: -# - name: cistern-signaling -# - name: cistern-git -# - name: cistern-test-runner -# timeout_minutes: 30 -# on_pass: review -# on_fail: pooled -# on_pool: pooled -# -# - name: review -# type: agent -# identity: reviewer -# context: full_codebase -# skills: -# - name: cistern-signaling -# - name: cistern-git -# - name: cistern-diff-reader -# timeout_minutes: 20 -# on_pass: qa -# on_fail: implement -# on_recirculate: implement -# on_pool: pooled -# -# - name: qa -# type: agent -# identity: qa -# context: full_codebase -# skills: -# - name: cistern-signaling -# - name: cistern-git -# - name: cistern-test-runner -# - name: cistern-diff-reader -# timeout_minutes: 20 -# on_pass: security-review -# on_fail: implement -# on_recirculate: implement -# on_pool: pooled -# -# - name: security-review -# type: agent -# identity: security -# context: full_codebase -# skills: -# - name: cistern-signaling -# - name: cistern-diff-reader -# timeout_minutes: 15 -# on_pass: docs -# on_fail: implement -# on_recirculate: implement -# on_pool: pooled -# -# - name: docs -# type: agent -# identity: docs_writer -# context: full_codebase -# skills: -# - name: cistern-signaling -# - name: cistern-git -# - name: cistern-diff-reader -# timeout_minutes: 20 -# on_pass: fork-delivery -# on_fail: implement -# on_recirculate: implement -# on_pool: pooled -# -# - name: fork-delivery -# type: agent -# identity: fork-delivery -# skills: -# - name: cistern-signaling -# - name: cistern-github -# - name: cistern-git -# timeout_minutes: 60 -# on_pass: done -# on_recirculate: implement -# on_pool: pooled \ No newline at end of file + on_pool: pooled \ No newline at end of file diff --git a/aqueduct/feature-fork.yaml b/aqueduct/feature-fork.yaml new file mode 100644 index 00000000..7f5bb46c --- /dev/null +++ b/aqueduct/feature-fork.yaml @@ -0,0 +1,101 @@ +# Fork-mode variant: for external repos where you push to a fork and open a PR +# against upstream instead of merging directly. Use this workflow with a repo +# config that sets delivery_mode: fork and upstream_remote: . + +name: feature-fork + +cataractae: + - name: architect + type: agent + identity: architect + context: full_codebase + skills: + - name: cistern-signaling + - name: cistern-git + - name: cistern-diff-reader + timeout_minutes: 20 + on_pass: implement + on_recirculate: architect + on_fail: pooled + on_pool: pooled + + - name: implement + type: agent + identity: implementer + context: full_codebase + skills: + - name: cistern-signaling + - name: cistern-git + - name: cistern-test-runner + timeout_minutes: 30 + on_pass: review + on_fail: pooled + on_pool: pooled + + - name: review + type: agent + identity: reviewer + context: full_codebase + skills: + - name: cistern-signaling + - name: cistern-git + - name: cistern-diff-reader + timeout_minutes: 20 + on_pass: qa + on_fail: implement + on_recirculate: implement + on_pool: pooled + + - name: qa + type: agent + identity: qa + context: full_codebase + skills: + - name: cistern-signaling + - name: cistern-git + - name: cistern-test-runner + - name: cistern-diff-reader + timeout_minutes: 20 + on_pass: security-review + on_fail: implement + on_recirculate: implement + on_pool: pooled + + - name: security-review + type: agent + identity: security + context: full_codebase + skills: + - name: cistern-signaling + - name: cistern-diff-reader + timeout_minutes: 15 + on_pass: docs + on_fail: implement + on_recirculate: implement + on_pool: pooled + + - name: docs + type: agent + identity: docs_writer + context: full_codebase + skills: + - name: cistern-signaling + - name: cistern-git + - name: cistern-diff-reader + timeout_minutes: 20 + on_pass: fork-delivery + on_fail: implement + on_recirculate: implement + on_pool: pooled + + - name: fork-delivery + type: agent + identity: fork-delivery + skills: + - name: cistern-signaling + - name: cistern-github + - name: cistern-git + timeout_minutes: 60 + on_pass: done + on_recirculate: implement + on_pool: pooled \ No newline at end of file diff --git a/cmd/ct/assets/cistern.yaml b/cmd/ct/assets/cistern.yaml index 998573f9..f7af2513 100644 --- a/cmd/ct/assets/cistern.yaml +++ b/cmd/ct/assets/cistern.yaml @@ -19,6 +19,99 @@ aqueducts: type: automated on_pass: done + # Fork-mode variant: for external repos where you push to a fork and open a PR + # against upstream instead of merging directly. + - name: feature-fork + cataractae: + - name: architect + type: agent + identity: architect + context: full_codebase + skills: + - name: cistern-signaling + - name: cistern-git + - name: cistern-diff-reader + timeout_minutes: 20 + on_pass: implement + on_recirculate: architect + on_fail: pooled + on_pool: pooled + - name: implement + type: agent + identity: implementer + context: full_codebase + skills: + - name: cistern-signaling + - name: cistern-git + - name: cistern-test-runner + timeout_minutes: 30 + on_pass: review + on_fail: pooled + on_pool: pooled + - name: review + type: agent + identity: reviewer + context: full_codebase + skills: + - name: cistern-signaling + - name: cistern-git + - name: cistern-diff-reader + timeout_minutes: 20 + on_pass: qa + on_fail: implement + on_recirculate: implement + on_pool: pooled + - name: qa + type: agent + identity: qa + context: full_codebase + skills: + - name: cistern-signaling + - name: cistern-git + - name: cistern-test-runner + - name: cistern-diff-reader + timeout_minutes: 20 + on_pass: security-review + on_fail: implement + on_recirculate: implement + on_pool: pooled + - name: security-review + type: agent + identity: security + context: full_codebase + skills: + - name: cistern-signaling + - name: cistern-diff-reader + timeout_minutes: 15 + on_pass: docs + on_fail: implement + on_recirculate: implement + on_pool: pooled + - name: docs + type: agent + identity: docs_writer + context: full_codebase + skills: + - name: cistern-signaling + - name: cistern-git + - name: cistern-diff-reader + timeout_minutes: 20 + on_pass: fork-delivery + on_fail: implement + on_recirculate: implement + on_pool: pooled + - name: fork-delivery + type: agent + identity: fork-delivery + skills: + - name: cistern-signaling + - name: cistern-github + - name: cistern-git + timeout_minutes: 60 + on_pass: done + on_recirculate: implement + on_pool: pooled + repos: - name: ScaledTest url: https://github.com/example/ScaledTest @@ -39,6 +132,14 @@ repos: - marcia prefix: ct + - name: kotlin-sdk + url: https://github.com/MichielDean/kotlin-sdk + aqueduct: feature-fork + cataractae: 1 + prefix: ks + delivery_mode: fork + upstream_remote: https://github.com/modelcontextprotocol/kotlin-sdk + handoff_token_threshold: 100000 # heartbeat_interval controls how often the Castellarius scans for orphaned or diff --git a/cmd/ct/doctor_test.go b/cmd/ct/doctor_test.go index d0214e39..d11cd124 100644 --- a/cmd/ct/doctor_test.go +++ b/cmd/ct/doctor_test.go @@ -2201,16 +2201,19 @@ func TestRunDoctorExtendedChecks_DefaultWorkflow_InstallerStubs_Passes(t *testin } // Generate AGENTS.md files for all identities, mirroring what ct init does. - w, err := cfg.ResolveAqueduct("default") - if err != nil { - t.Fatalf("resolve aqueduct: %v", err) - } - if err := initCataractaeDir(w, cataractaeDir); err != nil { - t.Fatalf("init cataractae dir: %v", err) - } - preset, _ := cfg.ResolveProvider("") - if _, err := aqueduct.GenerateCataractaeFiles(w, cataractaeDir, preset.InstrFile()); err != nil { - t.Fatalf("generate AGENTS.md files: %v", err) + // Must cover every aqueduct defined in the config since doctor checks all repos. + for _, aquedef := range cfg.Aqueducts { + w, err := cfg.ResolveAqueduct(aquedef.Name) + if err != nil { + t.Fatalf("resolve aqueduct %s: %v", aquedef.Name, err) + } + if err := initCataractaeDir(w, cataractaeDir); err != nil { + t.Fatalf("init cataractae dir for %s: %v", aquedef.Name, err) + } + preset, _ := cfg.ResolveProvider("") + if _, err := aqueduct.GenerateCataractaeFiles(w, cataractaeDir, preset.InstrFile()); err != nil { + t.Fatalf("generate AGENTS.md files for %s: %v", aquedef.Name, err) + } } // installerStubs mirrors _install_skill_stubs in tests/installer/run-tests.sh. diff --git a/internal/aqueduct/workflow_test.go b/internal/aqueduct/workflow_test.go index 911ba8fe..8a90d66a 100644 --- a/internal/aqueduct/workflow_test.go +++ b/internal/aqueduct/workflow_test.go @@ -767,6 +767,77 @@ trackers: } } +// TestAqueductConfig_KotlinSdkRepo_ParsesInForkMode verifies that a cistern.yaml +// containing the kotlin-sdk repo entry with fork delivery mode parses correctly +// and produces a valid AqueductConfig. +func TestAqueductConfig_KotlinSdkRepo_ParsesInForkMode(t *testing.T) { + yaml := ` +aqueducts: + - name: default + cataractae: + - name: implement + type: agent + on_pass: done + - name: feature-fork + cataractae: + - name: architect + type: agent + on_pass: implement + - name: implement + type: agent + on_pass: review + - name: review + type: agent + on_pass: fork-delivery + - name: fork-delivery + type: agent + on_pass: done +repos: + - name: cistern + aqueduct: default + cataractae: 1 + prefix: ct + - name: kotlin-sdk + url: https://github.com/MichielDean/kotlin-sdk + aqueduct: feature-fork + cataractae: 1 + prefix: ks + delivery_mode: fork + upstream_remote: https://github.com/modelcontextprotocol/kotlin-sdk +` + tmpDir := t.TempDir() + cfgPath := filepath.Join(tmpDir, "cistern.yaml") + if err := os.WriteFile(cfgPath, []byte(yaml), 0o644); err != nil { + t.Fatal(err) + } + + cfg, err := ParseAqueductConfig(cfgPath) + if err != nil { + t.Fatalf("ParseAqueductConfig: %v", err) + } + + // Find the kotlin-sdk repo (last entry). + last := cfg.Repos[len(cfg.Repos)-1] + if last.Name != "kotlin-sdk" { + t.Errorf("last repo Name = %q, want %q", last.Name, "kotlin-sdk") + } + if last.DeliveryMode != DeliveryModeFork { + t.Errorf("kotlin-sdk DeliveryMode = %q, want %q", last.DeliveryMode, DeliveryModeFork) + } + if last.UpstreamRemote != "https://github.com/modelcontextprotocol/kotlin-sdk" { + t.Errorf("kotlin-sdk UpstreamRemote = %q, want upstream URL", last.UpstreamRemote) + } + if last.URL != "https://github.com/MichielDean/kotlin-sdk" { + t.Errorf("kotlin-sdk URL = %q, want fork URL", last.URL) + } + if last.Aqueduct != "feature-fork" { + t.Errorf("kotlin-sdk Aqueduct = %q, want %q", last.Aqueduct, "feature-fork") + } + if last.Prefix != "ks" { + t.Errorf("kotlin-sdk Prefix = %q, want %q", last.Prefix, "ks") + } +} + // TestAqueductConfig_Trackers_OmittedWhenAbsent verifies that the Trackers field // is nil when the trackers: key is absent from the config. func TestAqueductConfig_Trackers_OmittedWhenAbsent(t *testing.T) { From f2103d39a055945126ce0ef301621add28412153 Mon Sep 17 00:00:00 2001 From: Lobsterdog Contributors Date: Tue, 12 May 2026 19:19:27 -0600 Subject: [PATCH 2/2] ci-ofvhg: add CHANGELOG entry for kotlin-sdk fork-mode repo and feature-fork aqueduct --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d8442bc..0e42e7c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ ## Unreleased +### Add kotlin-sdk as first external fork-mode repo (ci-ofvhg) + +Add modelcontextprotocol/kotlin-sdk as a repo in cistern.yaml with `delivery_mode: fork`, `upstream_remote: https://github.com/modelcontextprotocol/kotlin-sdk`, and `fork: https://github.com/MichielDean/kotlin-sdk`. Uses 1 cataractae (sufficient for samples/docs contributions). Also promotes the fork-mode aqueduct from a commented-out template in `aqueduct.yaml` to a standalone `aqueduct/feature-fork.yaml` workflow file, and adds the `feature-fork` aqueduct definition to the embedded `cistern.yaml` config. + +**Key changes:** +- New `aqueduct/feature-fork.yaml` — standalone aqueduct definition for fork-mode repos (previously commented-out in aqueduct.yaml) +- `cmd/ct/assets/cistern.yaml` — added `feature-fork` aqueduct definition and `kotlin-sdk` repo entry with `delivery_mode: fork` +- `aqueduct/aqueduct.yaml` — removed commented-out fork-mode template (moved to standalone file) +- `cmd/ct/doctor_test.go` — generalized doctor test to iterate all aqueducts instead of only `default` +- `internal/aqueduct/workflow_test.go` — new test validating kotlin-sdk repo config parses in fork mode (all 6 fields) + ### Inject external repo contributing guidelines as cataractae context (ci-agrc5) For fork-mode repos, Cistern now automatically extracts contributing guidelines from the upstream repository and injects them into CONTEXT.md for all cataractae. This ensures that agents working on external repos follow the upstream project's own conventions (coding style, test commands, commit message format, etc.) instead of Cistern defaults when they conflict.