diff --git a/.gitignore b/.gitignore index d950c20a..197de150 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,8 @@ openspec/plan/* !openspec/plan/migrate-multica-runtime-model/** !openspec/plan/role-artifact-smoke-main/ !openspec/plan/role-artifact-smoke-main/** +!openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/ +!openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/** # multiagent-safety:START .omx/ diff --git a/openspec/changes/agent-claude-submodule-aware-gx-2026-05-07-18-46/proposal.md b/openspec/changes/agent-claude-submodule-aware-gx-2026-05-07-18-46/proposal.md new file mode 100644 index 00000000..eac4699a --- /dev/null +++ b/openspec/changes/agent-claude-submodule-aware-gx-2026-05-07-18-46/proposal.md @@ -0,0 +1,82 @@ +# Submodule-aware gitguardex: detect, claim, commit, PR, merge nested repos + +## Why + +`.gitmodules` lists 5 submodules in this repo (`examples/conductor`, +`examples/dmux`, `examples/hive`, `examples/skills_for_claude`, +`vscode-material-icon-theme`). `git submodule status` shows three of +them as uninitialized-but-disk-modified (`-` prefix in `git submodule +status` plus `m` in `git status`). That state is the smoking gun: a +prior agent edited files inside submodule paths, the parent recorded +nothing, and the changes are now stranded on disk with no commit +path. + +Today `scripts/agent-branch-start.sh`, `scripts/agent-branch-finish.sh`, +`scripts/agent-file-locks.py`, and `scripts/codex-agent.sh` contain +**zero** `submodule` references — the entire Guardex lifecycle +ignores nested repos. When Codex/Claude works inside a submodule +path, edits never reach the submodule's remote and the parent's +gitlink is never bumped. + +This change makes the gx lifecycle submodule-aware: detect at +`branch start`, claim per-(repo,path), and run a per-submodule +commit→push→PR→merge cycle on `branch finish`, then atomically bump +all parent gitlinks in one parent commit. + +## What Changes + +- **Detection** (`gx branch start` and a new `gx submodules status`): + parse `.gitmodules`, run `git submodule status`, classify each + submodule as `clean | dirty | uninitialized | missing-remote`. +- **Auto-init** (`gx branch start`, default on): run `git submodule + update --init --recursive` inside the new worktree before tier + scaffold so agent edits land in real submodule trees, not stranded + paths. Opt-out via `GUARDEX_SUBMODULE_INIT=0`. +- **Lock keying** (`scripts/agent-file-locks.py`): claim records key + on `(submodule_root, relative_path)` instead of bare absolute path. + Parent-repo and submodule paths share no lock namespace. +- **Per-submodule write flow** (`gx branch finish`): for each dirty + submodule, create `agent//` inside the submodule, + commit, push, open a PR via `gh -R /`, and wait for + merge. Configurable via `GUARDEX_SUBMODULE_MODE`: + - `off` — skip entirely (legacy behavior). + - `sync-only` — detect drift, refuse, surface BLOCKED. + - `commit-only` — commit + push to a topic branch, no PR. + - `full-pr` (default) — full PR cycle. +- **Atomic gitlink bump**: parent does NOT bump submodule SHAs + incrementally. Only after every submodule PR reaches `MERGED` does + the parent stage all gitlink updates in a single commit + (`chore(submodules): bump gitlinks for `). On any submodule + failure, parent stages no bumps and finish exits BLOCKED with + rollback instructions. +- **Multi-org token preflight**: at `branch start`, gx probes + `GET https://api.github.com/repos//` with the active + token for every submodule whose URL host is github.com. Missing + write permission anywhere fails fast with a remediation message. +- **Per-submodule gate-skip**: `openspec validate --specs` is run + only against submodules that own an `openspec/` directory; absent + `openspec/` records a deliberate skip in the finish report. +- **New capability spec** (`openspec/specs/gitguardex-submodules/`) + to make these behaviors testable. + +## Impact + +- **Affected specs** — adds `gitguardex-submodules` capability; + `gitguardex-branch-lifecycle` (if extracted) gains a §I link to + the new capability. +- **Affected code** — `scripts/agent-branch-start.sh`, + `scripts/agent-branch-finish.sh`, `scripts/agent-file-locks.py`, + `scripts/codex-agent.sh`, plus a new `scripts/agent-submodules.py` + helper. New tests under `test/agent-submodules.test.*`. +- **Affected docs** — `AGENTS.md` Multi-Agent Execution Contract + gains a "Submodules" subsection; `README.md` "How it works" gains + a one-paragraph submodule note. +- **Migration** — first run on a repo with dirty submodules will + refuse `branch finish` until the operator chooses a mode. This is + intentional: silently committing a stranded `m examples/hive` + state into a submodule's `main` would amount to undisclosed + history rewrites for that downstream repo. +- **Risk** — full-PR mode multiplies merge surface (5× repos × 5× + protected-branch dance × 5× wait loops). Mitigations: the mode + switch above; a single shared `--wait-for-merge` poller across + all PRs; fail-fast preflight. diff --git a/openspec/changes/agent-claude-submodule-aware-gx-2026-05-07-18-46/specs/gitguardex-submodules/spec.md b/openspec/changes/agent-claude-submodule-aware-gx-2026-05-07-18-46/specs/gitguardex-submodules/spec.md new file mode 100644 index 00000000..b74f197d --- /dev/null +++ b/openspec/changes/agent-claude-submodule-aware-gx-2026-05-07-18-46/specs/gitguardex-submodules/spec.md @@ -0,0 +1,132 @@ +## ADDED Requirements + +### Requirement: gx auto-recognizes submodules at branch start +`gx branch start` SHALL parse `.gitmodules` (when present), run `git +submodule status` inside the new worktree, classify every submodule +as `clean | dirty | uninitialized | missing-remote`, persist the +classification at +`.omc/agent-worktrees//.guardex/submodules.json`, and run `git +submodule update --init --recursive` inside the worktree unless +`GUARDEX_SUBMODULE_INIT=0` is set in the environment. + +#### Scenario: Clean repo with five configured submodules +- **WHEN** `gx branch start "" ""` runs in a worktree + whose `.gitmodules` lists five submodules and `git submodule + status` reports all five as initialized +- **THEN** `submodules.json` contains exactly five entries +- **AND** every entry has `state: "clean"` and a non-null + `parent_gitlink_sha` +- **AND** the worktree's submodule paths each contain a populated + `.git` file or directory (init succeeded). + +#### Scenario: Uninitialized submodule with on-disk modifications +- **WHEN** `gx branch start` runs in a repo where `git submodule + status` reports `examples/hive` with a `-` prefix and `git status` + reports `m examples/hive` +- **THEN** the manifest entry for `examples/hive` records + `state: "dirty"` and `was_uninitialized: true` +- **AND** finish-time refusal is set on the entry so a future `gx + branch finish` cannot silently push stranded edits. + +#### Scenario: Operator opts out of init +- **WHEN** `GUARDEX_SUBMODULE_INIT=0 gx branch start ...` runs +- **THEN** `git submodule update --init` is NOT invoked +- **AND** the manifest still records `state: "uninitialized"` for + every uninitialized submodule +- **AND** the start log prints `submodule init: skipped + (GUARDEX_SUBMODULE_INIT=0)`. + +### Requirement: File locks key on (submodule_root, relative_path) +`scripts/agent-file-locks.py` SHALL key claim records on the tuple +`(submodule_root, relative_path)` rather than the bare absolute or +parent-relative path. The same relative path inside the parent repo +and inside a submodule SHALL NOT collide. + +#### Scenario: Same filename in parent and submodule +- **WHEN** branch `agent/claude/foo` claims + `examples/hive/src/index.js` and branch `agent/codex/bar` claims + `src/index.js` in the parent +- **THEN** both claims succeed +- **AND** the locks file records two distinct entries with + `submodule_root` values `"examples/hive"` and `""` respectively. + +#### Scenario: Cross-branch collision inside the same submodule +- **WHEN** branch `agent/claude/foo` already holds + `examples/hive/src/index.js` and branch `agent/codex/bar` attempts + to claim the same path +- **THEN** the second claim fails with exit code `2` +- **AND** the failure message identifies the holder branch and the + submodule root. + +#### Scenario: Legacy lock entries remain readable +- **WHEN** the locks file already contains an entry written by the + pre-tuple format (bare path string) +- **THEN** `agent-file-locks.py status` lists it without crashing +- **AND** any new claim involving that path is rewritten to the + tuple format on first contact. + +### Requirement: gx finish atomically bumps parent gitlinks +`gx branch finish` SHALL stage parent-repo gitlink updates for all +dirty submodules in **exactly one** parent commit, and SHALL stage +that commit only after every submodule PR has reached merge state +`MERGED`. On any submodule failure the parent SHALL NOT receive a +gitlink bump for any submodule, and finish SHALL exit with status +`BLOCKED` and a recovery hint. + +#### Scenario: All submodule PRs merge +- **WHEN** `gx branch finish ... --via-pr --wait-for-merge` runs + with three dirty submodules and all three child PRs reach + `MERGED` +- **THEN** the parent's last commit before the parent PR is opened + has subject `chore(submodules): bump gitlinks for ` +- **AND** the commit's diff updates exactly three gitlink entries +- **AND** no parent commit was pushed earlier than the final bump. + +#### Scenario: One submodule PR fails to merge +- **WHEN** two child PRs reach `MERGED` and the third is closed + without merge +- **THEN** finish exits non-zero with a `BLOCKED:` line that + includes the failed submodule path and its PR URL +- **AND** `git -C diff --name-only HEAD` lists no + submodule path bumps +- **AND** the manifest records `parent_bump: "skipped"` with a + `reason` field. + +#### Scenario: `commit-only` mode skips PR step +- **WHEN** `GUARDEX_SUBMODULE_MODE=commit-only gx branch finish ...` + runs with one dirty submodule +- **THEN** the submodule's topic branch is pushed but no `gh pr + create` is issued +- **AND** the parent's gitlink bump targets the pushed topic + branch's HEAD SHA +- **AND** the finish report records `mode: "commit-only"`. + +### Requirement: gx preflights cross-org token write permission +`gx branch start` SHALL probe write permission on every github.com +submodule remote using the active `GITHUB_TOKEN`, and SHALL fail +fast if any submodule is unreachable or its token-derived +permission level is below `push`. Non-github.com remotes SHALL be +recorded as `permission: "unverified"` without failing start. + +#### Scenario: Token has push on every submodule remote +- **WHEN** `gx branch start` runs with five github.com submodules + and `GET /repos//` returns + `"permissions": {"push": true}` for each +- **THEN** start completes +- **AND** the manifest's `preflight` field is `"ok"` for every + submodule. + +#### Scenario: Token lacks push on one submodule +- **WHEN** start runs and `repos//` for one submodule + returns `"permissions": {"push": false, "pull": true}` +- **THEN** start exits non-zero with a message naming the + unreachable repo and the remediation + (`gh auth refresh -s repo` or token rotation) +- **AND** no worktree is left dirty (start cleans up the partial + scaffold). + +#### Scenario: Self-hosted git remote +- **WHEN** a submodule URL points to `git.internal.example.com` +- **THEN** the manifest entry records `permission: "unverified"` + and `host: "git.internal.example.com"` +- **AND** start does not block on that submodule. diff --git a/openspec/changes/agent-claude-submodule-aware-gx-2026-05-07-18-46/tasks.md b/openspec/changes/agent-claude-submodule-aware-gx-2026-05-07-18-46/tasks.md new file mode 100644 index 00000000..2bceb738 --- /dev/null +++ b/openspec/changes/agent-claude-submodule-aware-gx-2026-05-07-18-46/tasks.md @@ -0,0 +1,57 @@ +# Tasks + +## 1. Spec +- [x] 1.1 Capture proposal in `proposal.md` (root cause + 5 invariants). +- [x] 1.2 Capture spec delta in `specs/gitguardex-submodules/spec.md` + (4 ADDED requirements with BDD scenarios). +- [ ] 1.3 Run `openspec validate --specs` and attach output to the + finish handoff. + +## 2. Tests +- [ ] 2.1 Add `test/agent-submodules-detect.test.js` covering + `parseGitmodules`, `classifySubmodule` (clean / dirty / + uninitialized / missing-remote), and `manifestForFinish`. +- [ ] 2.2 Add `test/agent-submodules-locks.test.py` covering + `(submodule_root, path)` keying — same relative path inside + parent and submodule must NOT collide. +- [ ] 2.3 Add `test/agent-submodules-finish.test.js` covering the + atomic gitlink bump (one parent commit only after all child + PRs hit `MERGED`; partial-failure leaves parent untouched). +- [ ] 2.4 Add `test/agent-submodules-preflight.test.js` covering the + cross-org token probe (mocks `api.github.com/repos/...` + returning 404/401/403/200 → expected failure modes). + +## 3. Implementation +- [ ] 3.1 Add `scripts/agent-submodules.py` with `parse_gitmodules`, + `submodule_status`, `classify`, `manifest_for_branch`, + `preflight_token`, `init_in_worktree`. +- [ ] 3.2 Extend `scripts/agent-branch-start.sh`: after worktree + creation, run `init_in_worktree` (skipped under + `GUARDEX_SUBMODULE_INIT=0`); record manifest in + `.omc/agent-worktrees//.guardex/submodules.json`. +- [ ] 3.3 Extend `scripts/agent-file-locks.py`: replace bare-path + keying with `(submodule_root, relative_path)`; back-compat + read of legacy lock entries. +- [ ] 3.4 Extend `scripts/agent-branch-finish.sh`: read manifest, + loop submodules in `GUARDEX_SUBMODULE_MODE`, share one + merge-wait poller, stage atomic gitlink bump only after all + child PRs hit `MERGED`. +- [ ] 3.5 Add `gx submodules status` and `gx submodules preflight` + subcommands. +- [ ] 3.6 Update `AGENTS.md` Multi-Agent Execution Contract with a + "Submodules" subsection; update README "How it works". + +## 4. Verification +- [ ] 4.1 `openspec validate --specs` → green. +- [ ] 4.2 `npm test` → green (or recorded skip with reason). +- [ ] 4.3 Live walkthrough on this repo: clean state → edit a file + in `examples/hive` → `gx branch finish --via-pr` + --wait-for-merge` → confirm child PR opens in `NagyVikt/hive`, + merges, and parent commit lists exactly the bumped gitlinks. + +## 5. Cleanup +- [ ] 5.1 Commit changes on the agent branch. +- [ ] 5.2 Push branch and open a PR. +- [ ] 5.3 Run `gx branch finish ... --base main --via-pr + --wait-for-merge --cleanup`. +- [ ] 5.4 Record PR URL and `MERGED` evidence in handoff note. diff --git a/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/README.md b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/README.md new file mode 100644 index 00000000..6f4b9a06 --- /dev/null +++ b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/README.md @@ -0,0 +1,21 @@ +# Plan Workspace: agent-claude-submodule-aware-gx-2026-05-07-18-46 + +Durable pre-implementation planning workspace. + +Use this command to update checkpoints: + +```bash +/opsx:checkpoint agent-claude-submodule-aware-gx-2026-05-07-18-46 +``` + +Roles (each has its own `tasks.md`): + +- `planner/` — owns spec + open-questions +- `architect/` — owns manifest schema + failure catalog +- `critic/` — stress-tests design + executor diff +- `executor/` — implements (tests first, then code) +- `writer/` — keeps AGENTS.md, README, and context.md in sync +- `verifier/` — proves the change works against this repo before archive + +See `summary.md` for the high-level intent and `open-questions.md` +for unresolved tradeoffs. diff --git a/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/architect/tasks.md b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/architect/tasks.md new file mode 100644 index 00000000..86542719 --- /dev/null +++ b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/architect/tasks.md @@ -0,0 +1,65 @@ +# architect tasks — submodule-aware gx + +> **Role goal**: turn the spec into an implementable shape. Pick +> data structures, decide where new code lives, choose the +> dependency graph between scripts, and document the failure +> modes the executor must handle. + +## Scope boundary + +In scope: +- shape of `scripts/agent-submodules.py` (functions, return types, + invariants) +- shape of `.guardex/submodules.json` (manifest schema) +- shape of the new lock-record tuple in `agent-file-locks.py` +- shape of `gx submodules` subcommand routing +- failure mode catalog (what happens on each failure → which + recovery path) + +Out of scope: +- writing implementation code (executor) +- writing tests (executor authors, critic reviews) + +## 1. Spec +- [ ] 1.1 Enumerate every state transition for a submodule across + its lifecycle: `unknown → uninitialized → init → clean → + dirty → committed → pushed → pr-open → pr-merged → + gitlink-bumped`. Identify which step is allowed to fail + silently. +- [ ] 1.2 Author the manifest schema. Required keys: `name`, + `path`, `url`, `branch`, `state`, `was_uninitialized`, + `parent_gitlink_sha`, `child_branch`, `child_pr_url`, + `child_pr_state`, `permission`, `host`, `mode`, + `parent_bump`, `reason`. + +## 2. Tests +- [ ] 2.1 Confirm the failure catalog has a corresponding negative + test in `tasks.md §2`. Add any missing entries. + +## 3. Implementation +- [ ] 3.1 Choose: should `agent-submodules.py` shell out to `git` + or use `dulwich`/`pygit2`? Record reasoning. (Default: + shell out to `git` via `subprocess.run`; matches existing + script style.) +- [ ] 3.2 Choose the locking strategy when an agent edits across + parent + submodule in the same task: separate claims, or + one combined claim with a `repo` segment per file? Record + reasoning. (Default: separate claim per `(repo, path)`.) +- [ ] 3.3 Author `failure-modes.md` (sibling of this file) listing + every failure → user-facing message → recovery command. +- [ ] 3.4 Approve or reject the proposed `GUARDEX_SUBMODULE_MODE` + and `GUARDEX_SUBMODULE_INIT` env-var contract. + +## 4. Checkpoints +- [ ] 4.1 Publish architect checkpoint after the manifest schema + and failure catalog land. + +## Handoff fields + +``` +branch=agent/claude/submodule-aware-gx-2026-05-07-18-46 +task=architect +blocker= +next=critic | executor +evidence=openspec/plan//architect/failure-modes.md, manifest schema in proposal.md +``` diff --git a/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/checkpoints.md b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/checkpoints.md new file mode 100644 index 00000000..f13da2b1 --- /dev/null +++ b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/checkpoints.md @@ -0,0 +1,6 @@ +# Plan Checkpoints: submodule-aware gitguardex + +Chronological checkpoint log for all roles. + +- 2026-05-07T18:46:00+02:00 | role=planner | scope=openspec/changes//proposal.md,specs/gitguardex-submodules/spec.md | action=Drafted proposal with five §V invariants (auto-init, tuple lock keying, atomic gitlink bump, cross-org preflight, per-submodule openspec gate-skip) and 4 ADDED requirements with negative scenarios. +- 2026-05-07T18:46:00+02:00 | role=planner | scope=openspec/plan//{summary,checkpoints,open-questions,README,planner,architect,critic,executor,writer,verifier} | action=Scaffolded role tasks files; default GUARDEX_SUBMODULE_MODE=full-pr; default GUARDEX_SUBMODULE_INIT=on; gx submodules subcommand limited to status+preflight in this PR. diff --git a/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/critic/tasks.md b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/critic/tasks.md new file mode 100644 index 00000000..7a357da4 --- /dev/null +++ b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/critic/tasks.md @@ -0,0 +1,55 @@ +# critic tasks — submodule-aware gx + +> **Role goal**: stress-test the design and the executor's diff. +> Find the case the spec did not cover. Reject merge until every +> §V invariant has a test that would fail without the change. + +## Scope boundary + +In scope: +- review of `proposal.md`, spec deltas, executor diff, tests +- adding negative scenarios that the planner missed +- requesting changes via comments on the executor's commits + +Out of scope: +- editing scripts or specs directly (suggest, do not patch) +- ownership of merge — that is the verifier + +## 1. Spec +- [ ] 1.1 Re-read the spec and list every invariant the executor + could violate without breaking a test. Add tests until + that list is empty. +- [ ] 1.2 Confirm the spec uses MUST/SHALL language consistently; + flag any soft-language drift (`should`, `tries to`). + +## 2. Tests +- [ ] 2.1 Verify the partial-failure scenario (one submodule PR + merges, one fails) is exercised end-to-end, not just at the + unit level. +- [ ] 2.2 Verify the legacy lock entry migration is tested with a + real fixture file, not a generated one. +- [ ] 2.3 Add a test for the cross-org case: two submodules in + different orgs, token has push on one but not the other. + +## 3. Implementation +- [ ] 3.1 Review the executor's diff for: unclaimed file edits, + `--no-verify` usage, force-push attempts, missing + `submodule_root` in lock writes, missing rollback when + atomic bump aborts. +- [ ] 3.2 Reject any change that bumps a parent gitlink before + confirming child PR `MERGED`. This is the load-bearing + invariant. + +## 4. Checkpoints +- [ ] 4.1 Publish critic checkpoint with `accepted` or `changes + requested` and the explicit blocker list. + +## Handoff fields + +``` +branch=agent/claude/submodule-aware-gx-2026-05-07-18-46 +task=critic +blocker= +next=executor (revise) | verifier (advance) +evidence=PR review URL, list of missing scenarios +``` diff --git a/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/executor/tasks.md b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/executor/tasks.md new file mode 100644 index 00000000..90694b35 --- /dev/null +++ b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/executor/tasks.md @@ -0,0 +1,75 @@ +# executor tasks — submodule-aware gx + +> **Role goal**: implement the spec exactly. Author tests first, +> then code. Claim every file before edit. Do not expand scope. + +## Scope boundary + +In scope: +- `scripts/agent-submodules.py` (new) +- `scripts/agent-branch-start.sh` (extend; do not rewrite) +- `scripts/agent-branch-finish.sh` (extend; do not rewrite) +- `scripts/agent-file-locks.py` (extend lock-record format) +- `scripts/codex-agent.sh` (only if it surfaces submodule state) +- new tests under `test/agent-submodules-*` +- minimal CLI surface in `bin/gx` + +Out of scope: +- frontend / cockpit changes +- Rust runtime +- README / AGENTS.md prose (writer) +- merge gate logic for parent PR (already exists) + +## 1. Spec +- [ ] 1.1 Re-read `specs/gitguardex-submodules/spec.md` and the + proposal before any edits. +- [ ] 1.2 Claim every file you intend to touch: + `gx locks claim --branch `. Note the + claim is per `(submodule_root, relative_path)` after the + lock-format change ships, so claim with the new format + last (chicken-and-egg: bootstrap with the bare-path format + once, then migrate). + +## 2. Tests +- [ ] 2.1 Write `test/agent-submodules-detect.test.js` first; + confirm it fails before implementing detection. +- [ ] 2.2 Write `test/agent-submodules-locks.test.py`; confirm it + fails before implementing tuple keying. +- [ ] 2.3 Write `test/agent-submodules-finish.test.js`; uses a + throwaway fixture repo with two fixture submodules. +- [ ] 2.4 Write `test/agent-submodules-preflight.test.js`; mocks + `api.github.com/repos/...` via a stub HTTP client. + +## 3. Implementation +- [ ] 3.1 Implement `agent-submodules.py` (`parse_gitmodules`, + `submodule_status`, `classify`, `manifest_for_branch`, + `preflight_token`, `init_in_worktree`). +- [ ] 3.2 Wire `init_in_worktree` into `agent-branch-start.sh` + after worktree creation; persist manifest to + `.guardex/submodules.json` inside the worktree. +- [ ] 3.3 Migrate `agent-file-locks.py` to tuple keys; preserve + legacy-entry read path. +- [ ] 3.4 Wire the per-submodule write loop and atomic gitlink + bump into `agent-branch-finish.sh`. Reuse the existing + `--wait-for-merge` poller; do not duplicate it. +- [ ] 3.5 Add `gx submodules status` and `gx submodules + preflight` to `bin/gx`. +- [ ] 3.6 Run the full test suite: `npm test` (Node tests) and + `python3 -m pytest test/` (if pytest is installed) or + `python3 -m unittest discover test/` as a fallback. + +## 4. Checkpoints +- [ ] 4.1 Checkpoint after detection + manifest land. +- [ ] 4.2 Checkpoint after lock migration lands. +- [ ] 4.3 Checkpoint after atomic gitlink bump lands and the + fixture E2E test passes. + +## Handoff fields + +``` +branch=agent/claude/submodule-aware-gx-2026-05-07-18-46 +task=executor +blocker= +next=critic | verifier +evidence=, +``` diff --git a/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/open-questions.md b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/open-questions.md new file mode 100644 index 00000000..cd10279b --- /dev/null +++ b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/open-questions.md @@ -0,0 +1,68 @@ +# Open Questions — submodule-aware gitguardex + +Unresolved tradeoffs and branching decisions that should survive +chat. Resolve in-place when answered (turn `[ ]` into `[x]` and +add the resolution). + +- [ ] **Wait-for-merge cost ceiling**: at 5 submodules × full-pr + cycle, total merge wait can exceed + `GUARDEX_FINISH_MERGE_TIMEOUT` (default 1800s). + Options: (a) increase timeout, (b) parallelize the poller + and treat merge as eventually-consistent, (c) fall back + to `commit-only` mode automatically when N >= 3 dirty + submodules. Recommend (b). + +- [ ] **Cross-org token strategy**: submodules span `NagyVikt/*` + and `recodeee/*`. Use a single PAT scoped to both, or + per-org GitHub Apps installed via `gh auth`? PAT is + simpler; App is auditable. Recommend PAT for v1, App for + v2. + +- [ ] **Per-submodule openspec gate-skip — exact rule**: skip + when (a) submodule has no `openspec/` dir, (b) submodule + has `openspec/` but no `changes/` for the current slug, + or (c) operator opt-out via marker file. The proposal + uses (a); confirm. + +- [ ] **`commit-only` PR-base mismatch**: in `commit-only` mode + the parent's gitlink targets a topic branch HEAD, not the + upstream protected branch. If the submodule's protected + branch later diverges, the parent points at orphaned + history. Mitigation: emit a warning and write the + `child_branch` field into `submodules.json` so a future + `gx submodules promote` can roll the topic into a real + PR. Confirm this is acceptable. + +- [ ] **Worktree-vs-clone for submodules**: `git submodule update + --init` clones into the parent's `.git/modules/` + but the working tree under the submodule path is shared + with the primary checkout. With multiple agent worktrees + running in parallel, two agents editing the same + submodule path race. Options: (a) one agent at a time per + submodule (lock at the submodule level), (b) per-worktree + submodule clones (heavier disk). Recommend (a) — the + lock claim already enforces this. + +- [ ] **Trojan-detection on auto-init**: auto-running `git + submodule update --init` on `branch start` will fetch and + check out arbitrary code into the worktree. If a + submodule URL is hijacked or a malicious commit is + pushed to its protected branch, that code now sits on + disk and can run via post-checkout hooks. Mitigation: + (a) verify submodule remote URL against an allowlist in + `.gx/config.json`, (b) record submodule HEAD SHAs at + branch start and warn on diff at finish, (c) rely on + repo-level branch protection on the submodule. Pick. + +- [ ] **Failed init recovery**: if `git submodule update --init` + fails partway (network, auth, missing remote), the + worktree is half-populated. Should `branch start` roll + back the worktree, or surface a partial-init manifest? + Default: surface partial-init; finish refuses if any + submodule entry is `state: "init-failed"`. + +- [ ] **Helper sub-branch interaction**: AGENTS.md says helper + sub-branches do not create OpenSpec artifacts. Does that + extend to per-submodule child branches? Treat + `agent//` inside a submodule as a helper — + no nested OpenSpec required. diff --git a/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/planner/tasks.md b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/planner/tasks.md new file mode 100644 index 00000000..3d6ac46b --- /dev/null +++ b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/planner/tasks.md @@ -0,0 +1,53 @@ +# planner tasks — submodule-aware gx + +> **Role goal**: shape the change so the implementation lane is +> small, testable, and reversible. Own the open-questions list and +> the §V invariant set. You do NOT write code or scripts. + +## Scope boundary + +In scope: +- `proposal.md`, `tasks.md`, and `specs/gitguardex-submodules/spec.md` +- `open-questions.md` +- choosing the default `GUARDEX_SUBMODULE_MODE` (currently `full-pr`) +- choosing the auto-init default (currently on; opt-out env var) + +Out of scope: +- editing scripts under `scripts/agent-*` +- editing `agent-file-locks.py` (that is the executor) +- editing `AGENTS.md` (that is the writer) + +## 1. Spec +- [ ] 1.1 Confirm the four ADDED requirements in + `specs/gitguardex-submodules/spec.md` cover detection, + lock-keying, atomic gitlink bump, and token preflight; cut + any that overlap. +- [ ] 1.2 Pin acceptance criteria: each requirement has at least + one negative scenario (BLOCKED / refusal path). +- [ ] 1.3 Resolve every open question in `open-questions.md` or + hand it to the architect with a `BLOCKED:` note. + +## 2. Tests +- [ ] 2.1 Confirm the test list under `tasks.md §2` exercises every + scenario in the spec; each scenario maps to at least one + test case. + +## 3. Implementation +- [ ] 3.1 Reject any task scope creep into Rust runtime or frontend + — this lane is shell + python + a small new helper only. +- [ ] 3.2 Decide whether `gx submodules status` ships in this PR or + a follow-up. Record the decision in `summary.md`. + +## 4. Checkpoints +- [ ] 4.1 Publish a planner checkpoint after spec freeze. +- [ ] 4.2 Publish a second checkpoint after open-questions resolve. + +## Handoff fields (paste into Colony when blocked or done) + +``` +branch=agent/claude/submodule-aware-gx-2026-05-07-18-46 +task=planner +blocker= +next= +evidence=openspec/changes//proposal.md, specs/gitguardex-submodules/spec.md +``` diff --git a/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/summary.md b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/summary.md new file mode 100644 index 00000000..d80ac3f6 --- /dev/null +++ b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/summary.md @@ -0,0 +1,68 @@ +# Plan Summary: submodule-aware gitguardex + +- **Mode:** opsx (T3 plan workspace) +- **Status:** drafted + +## Context + +Today the gx lifecycle (`scripts/agent-branch-start.sh`, +`agent-branch-finish.sh`, `agent-file-locks.py`, `codex-agent.sh`) +contains zero references to git submodules, even though +`.gitmodules` configures five of them in this repo. As a result, +agent edits inside `examples/hive`, `examples/skills_for_claude`, +etc. land on disk but never reach the submodule's remote, and the +parent's gitlink is never bumped. Current observable evidence: +`git submodule status` reports three submodules as `-` prefixed +(uninitialized) while `git status` reports `m` (modified) +for the same paths — stranded edits with no commit path. + +This change makes the lifecycle submodule-aware: +- detect at `branch start` +- claim per `(submodule_root, relative_path)` +- per-submodule commit→push→PR→merge cycle on `branch finish` +- atomic gitlink bump in **one** parent commit only after every + child PR reaches `MERGED` +- preflight `GITHUB_TOKEN` write permission for every github.com + submodule remote +- per-submodule `openspec validate` only when that submodule has + its own `openspec/` + +## Decisions + +- **Default mode**: `full-pr` — every dirty submodule gets its own + PR cycle. Operator can override via `GUARDEX_SUBMODULE_MODE` + to `off | sync-only | commit-only | full-pr`. +- **Auto-init**: on by default; opt-out via + `GUARDEX_SUBMODULE_INIT=0`. +- **Lock keying**: `(submodule_root, relative_path)` tuple; + legacy bare-path entries remain readable. +- **Atomic bump**: parent stages all gitlink updates in a single + `chore(submodules): bump gitlinks for ` commit, only + after all child PRs reach `MERGED`. Partial failure → no + parent bump, BLOCKED handoff. +- **gx submodules subcommand**: `status` and `preflight` ship in + this PR; richer subcommands (`list`, `pin`, `bump`) deferred + unless planner upgrades the scope. + +## Quality Risks + +- Multi-org token: submodules span `NagyVikt` and `recodeee` orgs; + a token without write to one of them silently breaks finish. + Mitigation: preflight at `branch start` (§V Requirement 4). +- Wait-for-merge fan-out: 5 submodules × 5 protected-branch + dances. Mitigation: shared poller, fail-fast preflight. +- Atomic bump failure surface: rollback is the executor's + hardest step. Mitigation: parent never receives an + intermediate commit; the bump commit is the only one. + +## Verification Snapshot + +Pending — verifier role records command outputs here. + +## Handoff Notes + +- Helper sub-branches MUST NOT create their own OpenSpec change + artifacts (per AGENTS.md owner-branch rule). +- After archive, write + `openspec/specs/gitguardex-submodules/context.md` capturing + rationale, decisions, and at least one concrete walkthrough. diff --git a/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/verifier/tasks.md b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/verifier/tasks.md new file mode 100644 index 00000000..94bdd995 --- /dev/null +++ b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/verifier/tasks.md @@ -0,0 +1,60 @@ +# verifier tasks — submodule-aware gx + +> **Role goal**: prove the change works against this exact repo +> before archive. No "looks good" without command-output evidence. + +## Scope boundary + +In scope: +- running every verification gate defined in `tasks.md §4` +- recording exact commands and output (or summarized last lines) +- approving archive only when every §V invariant has empirical + evidence of working + +Out of scope: +- writing fixes (executor) or specs (planner) + +## 1. Spec +- [ ] 1.1 Confirm `openspec validate --specs` passes against the + worktree's OpenSpec layout. + +## 2. Tests +- [ ] 2.1 Run `npm test`; record pass count and any pre-existing + failures separately from this lane's failures. +- [ ] 2.2 Run the new submodule tests in isolation: + `node --test test/agent-submodules-detect.test.js`, + `node --test test/agent-submodules-finish.test.js`, + `node --test test/agent-submodules-preflight.test.js`, + `python3 -m unittest test/agent-submodules-locks.test.py` + (or pytest if available). Record full output. +- [ ] 2.3 Live walkthrough on this very repo: + 1. `gx submodules status` → confirm detection of the three + dirty `m` paths from `git status`. + 2. `gx submodules preflight` → confirm cross-org token + probe completes for `NagyVikt/*` and `recodeee/*`. + 3. Pick the smallest dirty submodule, edit one file inside + its tree, run `gx branch finish ... --via-pr + --wait-for-merge` and confirm: + - child PR opens in the submodule's repo + - parent's last commit subject is + `chore(submodules): bump gitlinks for ` + - parent commit's diff updates exactly one gitlink. + +## 3. Implementation +- [ ] 3.1 N/A — verifier does not implement. + +## 4. Checkpoints +- [ ] 4.1 Publish verifier checkpoint with explicit + `verified` / `BLOCKED` outcome and the commands run. +- [ ] 4.2 Approve archive (`/opsx:archive`) only after the live + walkthrough succeeds end-to-end. + +## Handoff fields + +``` +branch=agent/claude/submodule-aware-gx-2026-05-07-18-46 +task=verifier +blocker= +next=archive +evidence=command outputs in summary.md > Verification Snapshot +``` diff --git a/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/writer/tasks.md b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/writer/tasks.md new file mode 100644 index 00000000..bcbdc514 --- /dev/null +++ b/openspec/plan/agent-claude-submodule-aware-gx-2026-05-07-18-46/writer/tasks.md @@ -0,0 +1,54 @@ +# writer tasks — submodule-aware gx + +> **Role goal**: keep durable docs in sync with the change. Update +> AGENTS.md, README, and OpenSpec context docs. Do not edit +> CHANGELOG.md. + +## Scope boundary + +In scope: +- `AGENTS.md` (Multi-Agent Execution Contract → new "Submodules" + subsection) +- `README.md` (one-paragraph note in the "How it works" or "Branch + lifecycle" section) +- `openspec/specs/gitguardex-submodules/context.md` (new) when this + change archives — narrative purpose, rationale, examples. +- this plan workspace's `summary.md` and `checkpoints.md` updates. + +Out of scope: +- `CHANGELOG.md` (release process owns it) +- `docs/` (per AGENTS.md, behavior contracts live in OpenSpec) +- frontend marketing pages + +## 1. Spec +- [ ] 1.1 Confirm doc updates do not duplicate spec normative + text. Specs use MUST/SHALL; docs explain *why*. + +## 2. Tests +- [ ] 2.1 N/A — docs ride the executor's verification. + +## 3. Implementation +- [ ] 3.1 Append a `### Submodules` subsection under + `AGENTS.md > Multi-Agent Execution Contract`, summarizing + detection, lock-keying, modes, atomic bump, preflight. +- [ ] 3.2 Add a 4-6 line note to `README.md` describing how `gx` + handles nested git repos when present, with a pointer to + the spec. +- [ ] 3.3 On archive, write + `openspec/specs/gitguardex-submodules/context.md` with + purpose, decisions, constraints, failure modes, and at + least one concrete example (commands + expected output). + +## 4. Checkpoints +- [ ] 4.1 Publish writer checkpoint after AGENTS.md + README land. +- [ ] 4.2 Publish writer checkpoint after archive context.md lands. + +## Handoff fields + +``` +branch=agent/claude/submodule-aware-gx-2026-05-07-18-46 +task=writer +blocker= +next=verifier +evidence=git diff --stat AGENTS.md README.md +```