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. 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) {