diff --git a/openspec/changes/migrate-to-triad/adoption-plan.yaml b/openspec/changes/migrate-to-triad/adoption-plan.yaml index f1067e30..8b360179 100644 --- a/openspec/changes/migrate-to-triad/adoption-plan.yaml +++ b/openspec/changes/migrate-to-triad/adoption-plan.yaml @@ -1,8 +1,8 @@ -# Rehearsal input, NOT the cutover plan: regenerated 2026-10-07 from source e43b2432976a22eb3bf8c419c96209399251d375 (chore/openreposhape-pin-7f84ca4) +# Rehearsal input, NOT the cutover plan: regenerated 2026-10-08 from source 837928a1029ac53b050a9bdcbc0ef4055b10719b (main) # with openRepoShape adopt-project.py at 7f84ca42ca86a8902928345109d2bf6bad87bd91. -# The October 4 mapping's 19 reasoned overrides are reapplied verbatim; the new paths lane-worktrees -# and lanes-index (code) and ideation/ (spec) are resolved with reasons. -# Disposable rehearsal evidence: ../../../specs/004-migrate-to-triad/adopter-rehearsal-2026-10-07.md. +# Run C: the run B plan's 22 resolutions (the October 4 mapping's 19, plus lane-worktrees and +# lanes-index to code and ideation/ to spec) are reapplied verbatim; no new top-level path. +# Disposable rehearsal evidence: ../../../specs/004-migrate-to-triad/adopter-rehearsal-2026-10-08.md. # Gate B remains open (T006-T011); execution needs a frozen baseline and explicit Gate C approval. # Paths are relative to the source checkout (local_path published as "."); generated metadata # does not authorize repository creation. @@ -15,17 +15,17 @@ kind: adoption-plan # drop and adding one line of `resolution:` beside it. `check` then # has to pass, and `execute` REFUSES while any leg is still null. -generated_on: 2026-10-07 +generated_on: 2026-10-08 tool: opensoft/openRepoShape adopt-project.py source: repository: opensoft/openRepoTools local_path: . - default_branch: chore/openreposhape-pin-7f84ca4 - commit: e43b2432976a22eb3bf8c419c96209399251d375 - files: 100 - bytes: 6139047 - commits: 91 + default_branch: main + commit: "837928a1029ac53b050a9bdcbc0ef4055b10719b" + files: 120 + bytes: 6292407 + commits: 92 # The adopted repository KEEPS its name, its identity and # its history and becomes the assembly root. `new-root` — a new @@ -310,8 +310,8 @@ paths: confidence: high rule: spec-governance reason: "Requirements, decisions, contracts and governance prose — what the project has DECIDED, as opposed to what it currently runs. `contracts/` is here by the 2026-09-02 ruling even where the code reads it: the code reads across the assembly-root mount (`spec/contracts` from the root, `../spec/contracts` from `code/`), which is what the legs are mounted for." - files: 21 - bytes: 135920 + files: 28 + bytes: 185941 review_required: false - path: park leg: code @@ -357,8 +357,8 @@ paths: confidence: high rule: spec-governance reason: "Requirements, decisions, contracts and governance prose — what the project has DECIDED, as opposed to what it currently runs. `contracts/` is here by the 2026-09-02 ruling even where the code reads it: the code reads across the assembly-root mount (`spec/contracts` from the root, `../spec/contracts` from `code/`), which is what the legs are mounted for." - files: 10 - bytes: 20264 + files: 23 + bytes: 123603 review_required: false - path: status leg: code @@ -412,7 +412,7 @@ follow_ups: - "Preserve original .gitmodules, upstream/openRepoShape gitlink and contracts/openreposhape-pin.yaml together in code; rehearse existing-submodule extraction/removal and recursive bootstrap before real execution. Never weaken blob verification or edit the standard in place." - "Add an assembly openRepoTools compatibility entry point after byte-preserving split verification; resolve one immutable code pin for all installer artifacts and preserve OPENREPOTOOLS_REPO/REF forks, raw/gh and offline checkout behavior." - "Separate code, assembly and spec roots in test fixtures and hygiene checks; code CI must supply the exact compatible spec/assembly context where a test reads product documentation." - - "Current .specify and agent links are ignored local bootstrap artifacts, not the 100-file source tree; capture local configuration and regenerate root workflow using setup-openspeckit after manifest/legs are present." + - "Current .specify and agent links are ignored local bootstrap artifacts, not the 120-file source tree; capture local configuration and regenerate root workflow using setup-openspeckit after manifest/legs are present." - "Preserve all branch/PR/worktree and dirty/untracked work; source main extraction does not transport unfinished feature branches. Migrate selected patches to paired feature branches with old-to-new receipts before rebinding lanes." - "Regenerate this preliminary plan after planning lands and baseline freeze; require current coverage check, local bare-remote rehearsal, prerequisite availability and explicit approval before execute." - "Review genuine bounded features//openspec/ amendment records explicitly as code; canonical/full proposals and archives remain spec even when created on a feature branch. No existing bounded amendment paths were observed; do not manufacture them for historical features." @@ -422,20 +422,20 @@ follow_ups: summary: spec: - files: 45 - bytes: 493610 - file_share: 0.4500 - byte_share: 0.0804 + files: 65 + bytes: 646970 + file_share: 0.5417 + byte_share: 0.1028 code: files: 49 bytes: 5579879 - file_share: 0.4900 - byte_share: 0.9089 + file_share: 0.4083 + byte_share: 0.8868 root: files: 6 bytes: 65558 - file_share: 0.0600 - byte_share: 0.0107 + file_share: 0.0500 + byte_share: 0.0104 unresolved: files: 0 bytes: 0 diff --git a/specs/004-migrate-to-triad/adopter-rehearsal-2026-10-08.md b/specs/004-migrate-to-triad/adopter-rehearsal-2026-10-08.md new file mode 100644 index 00000000..26f0a537 --- /dev/null +++ b/specs/004-migrate-to-triad/adopter-rehearsal-2026-10-08.md @@ -0,0 +1,135 @@ +# Gate B rehearsal, run C, at the post-landing source — October 8, 2026 + +**Task:** T003–T005 rehearsal evidence under [#186](https://github.com/opensoft/openRepoTools/issues/186). +No task is checked complete by it. +**Tool:** openRepoShape `7f84ca42ca86a8902928345109d2bf6bad87bd91` on its `main` +(`compare main...7f84ca4`: `identical` at measurement). +**Source:** main `837928a1029ac53b050a9bdcbc0ef4055b10719b`, read with `git ls-remote` +at 2026-10-08T20:54:21Z just before cloning. It is `c4864ac` plus #187, the +planning documents squashed as `44983ce`, and #188, the repin to `7f84ca4` +squashed as `837928a`. +**Private receipt ID:** `adopter-fix-20261008-1HztL0`. + +**Run C supersedes nothing.** Runs A (`c4864ac`) and B (the repin candidate +`e43b243`) stand as recorded in +[adopter-rehearsal-2026-10-07.md](adopter-rehearsal-2026-10-07.md). That file +is on branch `004-migrate-to-triad-rehearsal` and reaches main with the +combined evidence PR. Run C is the same rehearsal, with the same tool and the +same resolutions, at the source that now exists after both landings. It is +also the durable rehearsed arrangement that T006–T010 take as input. It is +kept host-local outside every repository so it survives a bench restart. +Clone paths, local remote URLs, exact command lines and complete logs stay in +the private receipt. + +## Setup + +- **filter-repo:** `git-filter-repo` 2.47.0 from a private venv. The rebuilt + bench has no `ensurepip`, and `python3 -m venv` refused with + `The virtual environment was not created successfully because ensurepip is + not available.` So the venv was created with `--without-pip`. The wheel + came from `pip download` with SHA256 + `2cd04929b9024e83e65db571cbe36aec65ead0cb5f9ec5abe42158654af5ad83`, matching + the recorded value. That exact file was installed into the venv with + `--no-index`. The venv's `git-filter-repo` came first on `PATH`, and + `git filter-repo --version` printed `a40bce548d2c`. +- **Tool and source:** a fresh clone of openRepoShape at `7f84ca4`, unedited + and clean afterwards, ignored files included. A fresh `--single-branch` + clone of main, with one worktree and no linked worktrees. Its nested + `upstream/openRepoShape` was not initialized. +- **Transport:** limited to local files (`GIT_ALLOW_PROTOCOL=file`). `gh` and + `curl` shims refused and logged; no call was logged. The recursive clone + reached code's nested openRepoShape through a local mirror of the tool clone. + +## Mapping (T003) + +Since the run B source, the tree gained 20 files and no new top-level path: + +- 13 under `specs/004-migrate-to-triad/`; +- 6 under `openspec/changes/migrate-to-triad/`, including the October 4 + `adoption-plan.yaml` itself; +- `openspec/config.yaml`. + +The pin change is the one run B already carried; its contract blob is +identical. `plan` left the same 18 questions. All 22 resolutions of the run B +plan were reapplied verbatim: the October 4 mapping's 19, plus +`lane-worktrees` and `lanes-index` to code and `ideation/` to spec. Every +other entry kept the tool's leg, unchanged from run B. The 20 new files are +spec through the existing `openspec/` and `specs/` entries. + +The tool's follow-ups, re-derived over the resolved entries, are identical to +run B's. The ten reasoned follow-ups are unchanged except the source file +count, now 120. `check` printed `plan ok` for the executed plan and again +for the published copy. On this branch the +[mapping](../../openspec/changes/migrate-to-triad/adoption-plan.yaml) is that +published copy. It differs from the executed plan only by its header, +`local_path: .`, and the two closing status fields +(`execution_authorized: false`). + +## Run C — main `837928a` + +`execute --local-remote-dir … --work-dir … --yes` exited **0** and printed +`adoption verified: every source path is in exactly one place`. + +| Source accounting (the standard's table) | Original entries | +| --- | ---: | +| Spec | 65 | +| Code | 49 | +| Assembly root | 6 | +| Dropped | 0 | +| Total | 120 | + +The standard reported 18 added assembly paths, the same set as runs A and B. +An independent comparison matched path, mode, object type and object ID for +all 120 entries of `ls-tree -r 837928a`: 65 spec, 49 code and 6 root. It +found zero missing, duplicate or misplaced owners and no leg extras. The +gitlink `upstream/openRepoShape` at `7f84ca4` has exactly one owner, code. + +| Disposable repository | Commit | Kept commits | +| --- | --- | ---: | +| Spec | `593250d600ca291f610671d993c2a6fc36cd690e` | 35 | +| Code | `73b04d637634f897ad24e22ee4c12bed287172d6` | 88 | +| Assembly split | `69b89245029d0339afd3317df7c9b1b3bbaf7fc1` | — | + +Spec gained one kept commit, the planning squash. Code's tree digest +`b4940a13bd8ecbe2d1c6ed49368d1545d1a5f1f7a92623c7fe11ac1cb3617604` has the +same prefix run B recorded. Only the code commit identities differ, because +the repin landed as a different commit. The split commit's only parent is +`837928a`. + +The recursive clone, `scripts/bootstrap.py` (`bootstrap ok`) and the +naming, manifest and pins validators all passed, and every tree was clean +afterwards. Code's nested openRepoShape was checked out at `7f84ca4`, matching +code's `contracts/openreposhape-pin.yaml`. The pinned checkout's own +`scripts/repo_shape.py` recomputed +`3be52767e727d924dd05f9e78b18eb70f1682fe48614fab519f85331eeff5fa8`, which +matches the contract. + +The source clone was unchanged except for the one documented local-mode ref, +`refs/heads/adopt/three-repo-shape` at the split commit. That ref and the +two leg remotes are left in place, read-only, as the input to T006–T010. + +## Private log SHA256s + +- Execute: `f766bc707c88272598b6947ae850d9d42922763f0820940a0202ebcbc1835a46`. +- Bootstrap: `ea2be5dc7455c869a48c7bd36d73024c53ef6f233916cbf8e67ba897b3b732f7`. +- Independent comparison: `d3acbcd6274267eeadd36892bdbe2a08a9b08269d3215b373f84d630b91da007`. + +The receipt holds every log with its SHA256. None was over 2 MB, so none was +trimmed. + +## Remaining gates + +**Gate B is not complete.** T006–T011 remain: + +- root collisions and leg guidance; +- the installer entry point; +- composed tests and CI; +- the restore and rollback rehearsal. + +Gate A preservation must be refreshed at a coordinated breakpoint. Before +Gate C, regenerate the plan from the frozen source that is actually chosen. +Brett Heap's explicit Gate C approval must name the revisions, the leg +repositories, their visibility and the final plan. + +This evidence authorizes no real conversion, repository creation, merge, +default-branch move or lane rebinding. diff --git a/specs/004-migrate-to-triad/patches/t006/assembly-0001-The-assembly-root-s-front-door-resolves-the-split-s-.patch b/specs/004-migrate-to-triad/patches/t006/assembly-0001-The-assembly-root-s-front-door-resolves-the-split-s-.patch new file mode 100644 index 00000000..f9b6d7e5 --- /dev/null +++ b/specs/004-migrate-to-triad/patches/t006/assembly-0001-The-assembly-root-s-front-door-resolves-the-split-s-.patch @@ -0,0 +1,377 @@ +From 33618121e18b4b0d720324a3b747033adde80f07 Mon Sep 17 00:00:00 2001 +From: Brett Heap <1513478+brettheap@users.noreply.github.com> +Date: Fri, 9 Oct 2026 00:29:16 +0000 +Subject: [PATCH 1/3] The assembly root's front door resolves the split's + collisions: AGENTS.md opens with the shape's pointer and a short map of the + three repositories, README.md gains them and the lockstep invariant, and + every path that crossed into a leg names that leg. + +Rehearsal patch for opensoft/openRepoTools#186, task T006 of +specs/004-migrate-to-triad/tasks.md, against run C's rehearsed split +69b8924. Nothing under shape/ and no file with a row in +contracts/shape-pin.yaml is touched. + +- AGENTS.md: line 1 is the exact pointer that openRepoShape 7f84ca4's + SHAPE_POINTER_LINE and its adopt follow-up require. A seven-entry map + follows: spec, code, this root, the tests, the lane tooling's code and + manual, paired feature worktrees, and the local-rehearsal file-transport + flag. The original AGENTS.md comes after it, unchanged except for 16 path + repairs (docs/ -> spec/docs/, openspec/ -> spec/openspec/, and upstream/, + contracts/, tests/ and .github/ -> code/...). 316 -> 340 lines. +- README.md: a "Three repositories" section and "The lockstep invariant", + which contracts/{spec,code}-pin.yaml's `resync_runbook: + README.md#the-lockstep-invariant` names and which only shape/README.md + carried. The install section now says the one-liner is unchanged and that + the installer is code/openRepoTools. 12 path repairs, and the pin-bump + block re-rooted with `cd code`. 486 -> 510 lines. +- .gitignore: the shape's `.venv/` and `.DS_Store` merged in, and + `/worktrees/`, the line the three-leg bootstrap requires. +- .gitattributes: the same `* text=auto eol=lf` rule, re-explained for this + root's pinned copies. The installer reason moved with its files to + code/.gitattributes. +- CLAUDE.md is unchanged: "Read AGENTS.md." is byte-identical to the + shape's own rendered shape/CLAUDE.md. + +The two length caps in code/tests/test_repo_hygiene.py, 316 and 486, are +today's counts. Once T009 points those tests at the assembly root, each cap +is raised in a dated entry naming the rule its lines bought. + +Lane: openRepoTools-1 +Co-Authored-By: Claude Opus 5.5 +--- + .gitattributes | 43 +++++++++++++++----------------------- + .gitignore | 6 ++++++ + AGENTS.md | 56 +++++++++++++++++++++++++++++++++++--------------- + README.md | 44 ++++++++++++++++++++++++++++++--------- + 4 files changed, 96 insertions(+), 53 deletions(-) + +diff --git a/.gitattributes b/.gitattributes +index 3560973..6dc2212 100644 +--- a/.gitattributes ++++ b/.gitattributes +@@ -1,32 +1,21 @@ +-# THIS REPOSITORY'S OWN INSTALLER COMPARES ITS BYTES, so the bytes have to be +-# the same bytes on every platform. ++# THE BYTES A CLONE SEES ARE THE BYTES THIS ROOT'S CHECKS DESCRIBE. + # +-# `openRepoTools --install` decides between `installed at`, `updated at` and +-# `already installed … (unchanged)` with `cmp -s`, and +-# `tests/test_openrepotools_command.py` compares an installed copy to this +-# checkout's file BYTE FOR BYTE. Git for Windows' installer default is +-# `core.autocrlf=true`; a checkout of THIS repository made under it turns every +-# `\n` in `openRepoTools`, `park`, `resume` and `status` into `\r\n`, and every one +-# of those comparisons is about line endings rather than about content — a +-# second `--install` reporting three files `updated at` on a machine where +-# nothing changed, and a colleague whose only sin was cloning on a stock +-# Windows laptop watching the suite fail on lines nobody touched. CI never sees +-# it: the Linux and macOS jobs check out LF by default and the Windows job pins +-# `core.autocrlf=false`. ++# `contracts/shape-pin.yaml` records a sha256 of every shape file copied here, ++# and `scripts/validate-pins.py` recomputes each one from the bytes on disk. ++# Git for Windows' installer default is `core.autocrlf=true`; a clone made under ++# it writes CRLF into the worktree, and `make validate` then goes red on every ++# pinned file for a colleague who did nothing but clone this repository ++# (openRepoShape #51, ruled by Brett Heap on 2026-09-05; the shape's own ++# statement of the rule is `shape/.gitattributes`, pinned and left in place). + # +-# The same rule for a second reason. `tests/test_park_resume_commands.py` +-# asserts that `park`, `resume` and `status` carry the shared estate resolver +-# BYTE FOR BYTE — that is what makes the deliberate duplication safe — and a CRLF +-# checkout would either break the claim or hide a real drift behind it. ++# Before the split into three repositories this file carried the same rule for ++# a second reason: `openRepoTools --install` and its tests compare installed ++# copies with the checkout BYTE FOR BYTE. Those files are the code leg's now, ++# and a superproject's attributes never reach into a submodule's worktree, so ++# that statement moved with them, word for word, to `code/.gitattributes`; the ++# documents in `spec/` carry their own copy of the rule too. + # +-# NO PATH IS LISTED BY NAME. Every file this repository ships is text — bash, +-# Python, YAML, Markdown, a workflow — so there is nothing for git's binary +-# heuristic to misjudge, and `text=auto` leaves that detection where it +-# belongs: with git. The pinned `upstream/openRepoShape` is a gitlink and +-# carries its own copy of this rule. +-# +-# openRepoShape #63 wrote this rule for the standard's own root, and its #51 +-# (2026-09-05) wrote it for every scaffolded project; this is the same rule +-# retold for the repository that ships the two commands. ++# NO PATH IS LISTED BY NAME. Every file here is text, so `text=auto` leaves ++# binary detection with git. + + * text=auto eol=lf +diff --git a/.gitignore b/.gitignore +index a160f49..2d04abf 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,9 @@ + __pycache__/ + *.py[cod] + .pytest_cache/ ++# Merged from the shape's copy, `shape/.gitignore` (pinned, left in place). ++.venv/ ++.DS_Store ++# Paired feature worktrees, `worktrees//{spec,code}/`: linked ++# worktrees of the two legs, machine plumbing, never committed to this root. ++/worktrees/ +diff --git a/AGENTS.md b/AGENTS.md +index 443880a..f2509a2 100644 +--- a/AGENTS.md ++++ b/AGENTS.md +@@ -1,3 +1,27 @@ ++Read AGENTS-shape.md first — the rules of this repository's shape. ++ ++This is the assembly root of a three-repository project. The text after ++this list is what this repository said before the split, each path re-pointed ++at the leg that now holds it; a path beginning `spec/` or `code/` is relative ++to this root. ++ ++- **`spec/`**, `opensoft/openRepoTools-spec`: `openspec/`, `specs/`, `docs/` ++ (the manuals) and `ideation/`; `openspec` runs there. ++- **`code/`**, `opensoft/openRepoTools-code`: every command ++ `openRepoTools --install` places, `repos.tsv`, `commands/`, `skills/`, ++ `tests/`, its CI, and the pinned standard at `code/upstream/openRepoShape`. ++- **This root**: the front door, `project.yaml`, the leg pins and the gate ++ (`make validate`); no product code. ++- **Tests**: `code/tests/run.sh` from here, or `tests/run.sh` inside `code/`. ++- **The lane tooling**: its code is in `code/`; its manual is ++ `spec/docs/README-lanes.md` (`docs/README-lanes.md` in the spec leg). ++- **Feature work**: paired worktrees `worktrees//{spec,code}/`, ++ never committed here; full proposals and Speckit files in `spec`, a genuine ++ bounded amendment only in `code`'s `features//openspec/`. ++- **A local rehearsal** whose `.gitmodules` names bare repositories on disk ++ needs `git -c protocol.file.allow=always submodule update --init ++ --recursive`; the real legs are https and need no flag. ++ + # Working in openRepoTools + + Three estate commands, `park`, `resume` and `status`; the lane tooling +@@ -19,7 +43,7 @@ person is driving the two INSTALLED commands. + + The lane tooling answers to a different document — the lane collision protocol + at `$AGENT_PROTOCOL_ROOT/protocols/lane-collision-protocol.md` and its +-amendments — and `docs/README-lanes.md` is its manual. **The code is here; the ++amendments — and `spec/docs/README-lanes.md` is its manual. **The code is here; the + data is not.** + + **A LANE HAS ONE BINDING** (Amendment 18, in force 2026-09-14): the `host`, +@@ -41,7 +65,7 @@ handoffs through `$AGENT_PROTOCOL_ROOT/workspace.yaml`'s `repository:` and + unreadable workspace with **2**: only 2 blocks a prompt; 1 would let it through. + Profile-only guard calls carrying exact `CLAUDE_NO_LANE=1` return 0 before input + or identity probes, even with unreadable configuration; other values enforce. +-Brett Heap directed this exception in `openspec/changes/skip-no-lane-guard/`; ++Brett Heap directed this exception in `spec/openspec/changes/skip-no-lane-guard/`; + Shared hooks stay installed. Exit 1 means *registry not found* in lane tooling, + and *findings were printed* in estate status; identify the command before interpreting it. + +@@ -197,13 +221,13 @@ sequence, and relay their per-repository lines rather than summarising them. + reads them all WITHOUT asking, because a read — fetched or not — moves + nothing of yours; `status --all` from anywhere says the same thing. + +-## The pinned standard at `upstream/openRepoShape` ++## The pinned standard at `code/upstream/openRepoShape` + + The suite builds real estates out of eight of openRepoShape's own files, and it +-reads them out of a submodule pinned by `contracts/openreposhape-pin.yaml`. ++reads them out of a submodule pinned by `code/contracts/openreposhape-pin.yaml`. + Three rules, and none of them is negotiable: + +-1. **Never edit anything under `upstream/openRepoShape` in place.** It is ++1. **Never edit anything under `code/upstream/openRepoShape` in place.** It is + somebody else's repository, checked out detached at a commit. A change the + standard needs is a pull request THERE, then a pin bump here. + 2. **Never pin a commit that is not on that repository's `main`.** +@@ -211,17 +235,17 @@ Three rules, and none of them is negotiable: + pull request lands and the next person's fetch of it 404s. Check with + `gh api repos/opensoft/openRepoShape/compare/main...` — `identical` or + `behind`. +-3. **The digest is RECOMPUTED, never adjusted.** `contracts/openreposhape-pin.yaml` ++3. **The digest is RECOMPUTED, never adjusted.** `code/contracts/openreposhape-pin.yaml` + carries the bump command. A `tree_sha256` edited to make +- `tests/test_upstream_pin.py` pass records this checkout as the upstream, ++ `code/tests/test_upstream_pin.py` pass records this checkout as the upstream, + which is the one thing the row exists to prevent. The pin file and the + gitlink move in ONE commit. + + ## Testing your changes to this repository + + ```sh +-git submodule update --init upstream/openRepoShape +-tests/run.sh # the suite, serialized — pass any pytest argument ++git -C code submodule update --init upstream/openRepoShape ++code/tests/run.sh # the suite, serialized — pass any pytest argument + ``` + + Brett Heap's RULING of 2026-09-16 ("do all three", on the coordinator's three +@@ -230,7 +254,7 @@ Linux's 17-22, pull requests drawing 6-12 Copilot review rounds, and this + suite queuing up to 1h20 behind the workstation lock described below): + + 1. **`TESTS-MACOS` LEAVES THE PER-PUSH GATE.** On `pull_request` +- (`.github/workflows/tests.yml`) this workflow runs `tests`, ++ (`code/.github/workflows/tests.yml`) this workflow runs `tests`, + `tests-no-submodule` and `tests-windows` only; `tests-macos` itself runs + there only once the coordinator has put the `ready` label on the PR — the + landing gate, applied once, right before the squash — and otherwise on +@@ -244,10 +268,10 @@ suite queuing up to 1h20 behind the workstation lock described below): + and two are taken; a finding after that is filed as an issue and the PR + lands regardless. + 3. **CI IS THE SUITE OF RECORD.** A writer runs the shell-suite section they +- touched locally — `bash tests/test_lane_helpers.sh` in full is fine when +- the box is quiet — and the full local `tests/run.sh` behind the ++ touched locally — `bash code/tests/test_lane_helpers.sh` in full is fine when ++ the box is quiet — and the full local `code/tests/run.sh` behind the + workstation lock is no longer required before a push: CI's `tests` job is +- the footer of record. `tests/run.sh` stays exactly as it is above: how to ++ the footer of record. `code/tests/run.sh` stays exactly as it is above: how to + run the whole suite locally, serialized, whenever a person wants that + answer anyway, and `python3 -m pytest tests -q` by hand — unserialized + against every other lane on the same workstation — is still the thing it +@@ -282,14 +306,14 @@ while [ "$(pgrep -f "$pat" | awk 'END { print NR + 0 }')" -gt 0 ]; do sleep 20; + PYTHONPYCACHEPREFIX="${XDG_CACHE_HOME:-$HOME/.cache}/openRepoTools/pycache" flock "${TMPDIR:-/tmp}/openrepotools-pytest.lock" python3 -m pytest tests -q -p no:cacheprovider + ``` + +-`awk` AND NOT `pgrep -fc`, which is what `tests/run.sh` does and for the reason ++`awk` AND NOT `pgrep -fc`, which is what `code/tests/run.sh` does and for the reason + it gives: `-c` is not in every `pgrep` this repository runs under, and the + count — never `pgrep`'s exit status — is what decides. The wrapper is the + canonical implementation of this guard; the lines above are it in one place for + a person with no checkout in front of them. + +-`tests/test_lane_helpers.sh` is 122 KB of bash that arrived with the move; +-`tests/test_lane_helpers_suite.py` is what makes `pytest` run it, so it is one ++`code/tests/test_lane_helpers.sh` is 122 KB of bash that arrived with the move; ++`code/tests/test_lane_helpers_suite.py` is what makes `pytest` run it, so it is one + slow test rather than no test at all. Everything shipped here is parsed under + macOS **bash 3.2** in CI, where `${x,,}`, `mapfile`, `declare -A` and + `local -n` are syntax errors — write `tr '[:upper:]' '[:lower:]'` and a loop. +diff --git a/README.md b/README.md +index 18c8f7e..8a65e41 100644 +--- a/README.md ++++ b/README.md +@@ -101,6 +101,25 @@ parked. No config, or no record, is a note. + That is all five layers: the local layer, `--fetch`, the fork, the pin and + the record. + ++## Three repositories ++ ++This is the ASSEMBLY ROOT of a three-repository project, the shape ++[openRepoShape](https://github.com/opensoft/openRepoShape) calls the Triad: it ++holds this front door, `project.yaml`, the pins and the gate, and mounts ++`opensoft/openRepoTools-spec` at `spec/` (`openspec/`, `specs/`, the manuals in ++`docs/`, `ideation/`) and `opensoft/openRepoTools-code` at `code/` (every ++command below, `repos.tsv`, `skills/`, `commands/`, `tests/`, CI and the pinned ++standard). Clone it with `git clone --recurse-submodules` and run ++`make bootstrap`. Nothing a person types changes, and a path in this file that ++begins `spec/` or `code/` is relative to this root. ++ ++### The lockstep invariant ++ ++A leg's gitlink, `commit:` in `contracts/-pin.yaml` and every workflow ++`@` naming that leg move in ONE commit here, after the leg's own pull ++request lands: openRepoShape's `scripts/bump-leg.py` writes it, and ++`make pins` refuses when the three disagree. ++ + ## The lane tooling + + `lane-start`, `lane-end`, `lanes-edit.sh` and `link-estates` are here too, with +@@ -154,7 +173,7 @@ and 5 put it and where it stays. What moved here is the CODE. A `-wip` is + a private repository whose own rules file says "No secrets and no code", nobody + would look inside somebody else's for a command, and until this move the four + helpers existed in exactly one place in the world: one person's private +-repository. `docs/README-lanes.md` is the manual; `docs/README-claude-current.md` is the one for the Claude Code `lane-start` launches and the RESTART NEEDED line. ++repository. `spec/docs/README-lanes.md` is the manual; `spec/docs/README-claude-current.md` is the one for the Claude Code `lane-start` launches and the RESTART NEEDED line. + + Every one of them finds that data through `$AGENT_PROTOCOL_ROOT/workspace.yaml` + — `repository:` and `path:`, the same pointer file `resume` and `status` +@@ -180,7 +199,7 @@ different hat). + + Then it clones the repository, seeds it from openRepoShape's + `templates/workspace-root/` at the commit pinned in +-`contracts/openreposhape-pin.yaml`, and pushes one commit to `main` — **which is ++`code/contracts/openreposhape-pin.yaml`, and pushes one commit to `main` — **which is + also the check that matters**: a new repository is inside the organisation's + PR-only ruleset until an administrator excludes it, and until it is, every + register write will be refused by that gate. Better to meet it here than at +@@ -237,6 +256,10 @@ gh api repos/opensoft/openRepoTools/contents/openRepoTools \ + -H 'Accept: application/vnd.github.raw' | bash -s -- --install + ``` + ++Both lines are unchanged by the split: the `openRepoTools` they fetch is this ++root's entry point, which hands over to the installer `code/openRepoTools` at ++the commit this root pins. ++ + It places SEVENTEEN files into `~/.local/bin` — `openRepoTools`, `park`, `resume`, + `status`, `lane`, `lanes`, `lane-handoff`, `lane-rename`, `lanes-edit.sh`, + `lane-start`, `lane-end`, `link-estates`, the alias table `repos.tsv`, +@@ -260,7 +283,7 @@ that would not take it, and a create needs a directory's search bit as well as + its write bit. A refusal creates none of those directories either. `cp` follows + a symlink, and an install through one leaves the command uninstalled and writes + these bytes into whatever it points at. Then a `17 of 17 placed in ` line, +-and the `export PATH=…` line if that directory is not on your `PATH`. Last, it names this repository's virtual environment, `${XDG_CACHE_HOME:-~/.cache}/openRepoTools/venvs/openRepoTools/` (one per repository, outside the estate; #162), says whether it is there, and prints the two commands that make it: it never makes one, and `tests/run.sh` runs from it once it has `pytest`. ++and the `export PATH=…` line if that directory is not on your `PATH`. Last, it names this repository's virtual environment, `${XDG_CACHE_HOME:-~/.cache}/openRepoTools/venvs/openRepoTools/` (one per repository, outside the estate; #162), says whether it is there, and prints the two commands that make it: it never makes one, and `code/tests/run.sh` runs from it once it has `pytest`. + + It also places **fourteen things that are not files in that directory**: THREE + SKILLS, `/handoff`, `/lane-swap` and `/restart`, each at +@@ -300,7 +323,7 @@ arm keys on the VERB and not on the word anywhere in a path. It + never writes a profile's own `settings.json`: the launcher owns that one. + + Thirty-one artifacts, and the count is the invariant. It was sixteen until A11 +-Addendum 4 ruling 9 gave `--install` a command-file list and `commands/swap.md` ++Addendum 4 ruling 9 gave `--install` a command-file list and `code/commands/swap.md` + in it, at the same pair of paths a skill takes — because `opensoft/workBenches#74` + deletes the launcher's copy and `/swap` would otherwise be installed by nobody; + eighteen until Amendment 12 adoption act 3 added the name guard's entry; and +@@ -414,19 +437,19 @@ the way in is WSL2, exactly as it is for openRepoShape's `setup.sh`. + + **Tools depend on the shape; the shape depends on nothing.** This repository + pins the openRepoShape commit its tests were verified against, as a submodule +-at `upstream/openRepoShape` recorded in `contracts/openreposhape-pin.yaml`. ++at `code/upstream/openRepoShape` recorded in `code/contracts/openreposhape-pin.yaml`. + openRepoShape pins this repository NOWHERE, and knows of it only through one + pointer line its own `--install` prints — text, not a fetch. The reverse would + make the standard unbuildable without its tools. + +-MOUNTED, NOT COPIED. `tests/test_park_resume_commands.py` builds real estates ++MOUNTED, NOT COPIED. `code/tests/test_park_resume_commands.py` builds real estates + out of EIGHT of the standard's own files — the two root Makefiles, their two + `.gitignore`s, the two `bootstrap.py`s, `siblings.py` and + `scripts/repo_shape.py` — and a submodule hands those tests the standard's real + bytes at the pinned commit rather than copies here that could have been edited + into agreeing with a broken expectation. + +-`tests/test_upstream_pin.py` is what says the pin file and the gitlink still ++`code/tests/test_upstream_pin.py` is what says the pin file and the gitlink still + agree. Four of its checks need no submodule at all — the gitlink is read out of + `git ls-tree HEAD` and nothing imports from the checkout — so the lockstep + check bites in a clone that never materialized it. +@@ -434,6 +457,7 @@ check bites in a clone that never materialized it. + ### Bumping the pin + + ```sh ++cd code # the pin and the gitlink are the code leg's + git -C upstream/openRepoShape fetch origin + git -C upstream/openRepoShape checkout <40 hex on openRepoShape's main> + python3 -c "import sys; sys.path.insert(0, 'upstream/openRepoShape/scripts'); from repo_shape import tree_digest; print(tree_digest('upstream/openRepoShape', 'HEAD'))" +@@ -446,7 +470,7 @@ git commit + **The edit comes before the `git add`, and the block is in that order for a + reason.** Staging first and editing after stages the moved gitlink with the OLD + pin file — and that is invisible where you are standing, because +-`tests/test_upstream_pin.py` reads the pin out of the WORKING TREE and the ++`code/tests/test_upstream_pin.py` reads the pin out of the WORKING TREE and the + gitlink out of `git ls-tree HEAD`: green in your checkout, red in CI, which is + the wrong way round. The pin and the gitlink are one fact in two places. The + digest is recomputed with the PINNED CHECKOUT'S OWN `scripts/repo_shape.py` +@@ -461,10 +485,10 @@ orphaned and the next person's fetch of it 404s. + + ```sh + git clone --recurse-submodules https://github.com/opensoft/openRepoTools.git +-cd openRepoTools && python3 -m pytest tests -q ++cd openRepoTools/code && python3 -m pytest tests -q + ``` + +-From a clone that already exists, `git submodule update --init ++From a clone that already exists, `git -C code submodule update --init + upstream/openRepoShape` first. **Without the submodule the suite SKIPS rather + than fails**, naming that command: a fork's first `pytest` going red on a + missing submodule is a fork nobody finishes. The estate tests need `bash`, +-- +2.43.0 + diff --git a/specs/004-migrate-to-triad/patches/t006/assembly-0002-Bump-spec-leg-to-39c18877e042-in-openRepoTools.patch b/specs/004-migrate-to-triad/patches/t006/assembly-0002-Bump-spec-leg-to-39c18877e042-in-openRepoTools.patch new file mode 100644 index 00000000..c0d9ea5d --- /dev/null +++ b/specs/004-migrate-to-triad/patches/t006/assembly-0002-Bump-spec-leg-to-39c18877e042-in-openRepoTools.patch @@ -0,0 +1,55 @@ +From 1024f186cce1b0c03a295ae6514c43ce6fd52a31 Mon Sep 17 00:00:00 2001 +From: openRepoShape bump-leg +Date: Fri, 9 Oct 2026 00:29:17 +0000 +Subject: [PATCH 2/3] Bump spec leg to 39c18877e042 in openRepoTools +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +593250d600ca -> 39c18877e042, tree 3a4a84beb20b343390f5bae64cb166df4d0cfd693baff6309fc13c8060d1c866. + +No workflow reference names this leg, so the gitlink and the pin file are the whole of it. + +The gitlink at spec, `commit:` and `digests.tree_sha256` in contracts/spec-pin.yaml, and every workflow reference to opensoft/openRepoTools-spec move in ONE commit because `scripts/validate-pins.py` refuses when they disagree — a gitlink advanced alone is red on the next pull request, and on somebody else's change. + +Written by openRepoShape's `scripts/bump-leg.py`; the digest is recomputed from the leg's own objects, not adjusted. + +Lane: openRepoTools-1 +Co-Authored-By: Claude Opus 5.5 +--- + contracts/spec-pin.yaml | 4 ++-- + spec | 2 +- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/contracts/spec-pin.yaml b/contracts/spec-pin.yaml +index 65f8736..af1d2f7 100644 +--- a/contracts/spec-pin.yaml ++++ b/contracts/spec-pin.yaml +@@ -21,7 +21,7 @@ leg_role: spec + source_repository: opensoft/openRepoTools-spec + submodule_path: spec + +-commit: "593250d600ca291f610671d993c2a6fc36cd690e" ++commit: "39c18877e042e13156849b861d07bc3c92e64db4" + revision_kind: commit + + digest_algorithm: sha256 +@@ -36,7 +36,7 @@ digest_algorithm: sha256 + # `, so a leg's own pin moving is drift too. + digest_definition: sorted-ls-tree-r-v1 + digests: +- tree_sha256: "92da0a8a72f8a9ed3c8827b63e2c39b1734bc1e5418612e2fa41e40b2f148b45" ++ tree_sha256: "3a4a84beb20b343390f5bae64cb166df4d0cfd693baff6309fc13c8060d1c866" + + verify_pin: scripts/validate-pins.py + resync_runbook: "README.md#the-lockstep-invariant" +diff --git a/spec b/spec +index 593250d..39c1887 160000 +--- a/spec ++++ b/spec +@@ -1 +1 @@ +-Subproject commit 593250d600ca291f610671d993c2a6fc36cd690e ++Subproject commit 39c18877e042e13156849b861d07bc3c92e64db4 +-- +2.43.0 + diff --git a/specs/004-migrate-to-triad/patches/t006/assembly-0003-Bump-code-leg-to-6169e8e96164-in-openRepoTools.patch b/specs/004-migrate-to-triad/patches/t006/assembly-0003-Bump-code-leg-to-6169e8e96164-in-openRepoTools.patch new file mode 100644 index 00000000..a7eb0ad2 --- /dev/null +++ b/specs/004-migrate-to-triad/patches/t006/assembly-0003-Bump-code-leg-to-6169e8e96164-in-openRepoTools.patch @@ -0,0 +1,55 @@ +From 3e87e3941ff947d8160fa7d8b6204670af7926df Mon Sep 17 00:00:00 2001 +From: openRepoShape bump-leg +Date: Fri, 9 Oct 2026 00:29:18 +0000 +Subject: [PATCH 3/3] Bump code leg to 6169e8e96164 in openRepoTools +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +73b04d637634 -> 6169e8e96164, tree 697a080f3e86d368c31b82daf9088ee834537db6f3f49888dfcc7aa9c9ecfb4f. + +No workflow reference names this leg, so the gitlink and the pin file are the whole of it. + +The gitlink at code, `commit:` and `digests.tree_sha256` in contracts/code-pin.yaml, and every workflow reference to opensoft/openRepoTools-code move in ONE commit because `scripts/validate-pins.py` refuses when they disagree — a gitlink advanced alone is red on the next pull request, and on somebody else's change. + +Written by openRepoShape's `scripts/bump-leg.py`; the digest is recomputed from the leg's own objects, not adjusted. + +Lane: openRepoTools-1 +Co-Authored-By: Claude Opus 5.5 +--- + code | 2 +- + contracts/code-pin.yaml | 4 ++-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/code b/code +index 73b04d6..6169e8e 160000 +--- a/code ++++ b/code +@@ -1 +1 @@ +-Subproject commit 73b04d637634f897ad24e22ee4c12bed287172d6 ++Subproject commit 6169e8e9616425ce93a49447af5df426f4325312 +diff --git a/contracts/code-pin.yaml b/contracts/code-pin.yaml +index 68e06c8..18b44c7 100644 +--- a/contracts/code-pin.yaml ++++ b/contracts/code-pin.yaml +@@ -21,7 +21,7 @@ leg_role: code + source_repository: opensoft/openRepoTools-code + submodule_path: code + +-commit: "73b04d637634f897ad24e22ee4c12bed287172d6" ++commit: "6169e8e9616425ce93a49447af5df426f4325312" + revision_kind: commit + + digest_algorithm: sha256 +@@ -36,7 +36,7 @@ digest_algorithm: sha256 + # `, so a leg's own pin moving is drift too. + digest_definition: sorted-ls-tree-r-v1 + digests: +- tree_sha256: "b4940a13bd8ecbe2d1c6ed49368d1545d1a5f1f7a92623c7fe11ac1cb3617604" ++ tree_sha256: "697a080f3e86d368c31b82daf9088ee834537db6f3f49888dfcc7aa9c9ecfb4f" + + verify_pin: scripts/validate-pins.py + resync_runbook: "README.md#the-lockstep-invariant" +-- +2.43.0 + diff --git a/specs/004-migrate-to-triad/patches/t006/code-0001-The-code-leg-gets-its-own-front-door-the-shape-s-leg.patch b/specs/004-migrate-to-triad/patches/t006/code-0001-The-code-leg-gets-its-own-front-door-the-shape-s-leg.patch new file mode 100644 index 00000000..2d2dc029 --- /dev/null +++ b/specs/004-migrate-to-triad/patches/t006/code-0001-The-code-leg-gets-its-own-front-door-the-shape-s-leg.patch @@ -0,0 +1,417 @@ +From 6169e8e9616425ce93a49447af5df426f4325312 Mon Sep 17 00:00:00 2001 +From: Brett Heap <1513478+brettheap@users.noreply.github.com> +Date: Thu, 8 Oct 2026 21:41:17 +0000 +Subject: [PATCH] The code leg gets its own front door: the shape's leg + AGENTS.md, CLAUDE.md and README.md, and the LF rule, ignore list and licence + it lost when the root kept them. + +Rehearsal patch for opensoft/openRepoTools#186, task T006 of +specs/004-migrate-to-triad/tasks.md, against run C's code leg 73b04d6. The +split left this leg with no AGENTS.md, CLAUDE.md, README.md, LICENSE, +.gitattributes or .gitignore, because all six stayed in the assembly root, +and a superproject's attributes and ignore rules never reach into a +submodule's worktree. + +- AGENTS.md, CLAUDE.md and README.md are openRepoShape 7f84ca4's + templates/code-root/ files, rendered by its own copy_tree with this + project's names, so the leg-clone detector in scripts/shape_advisory.py + recognises them. Each AGENTS.md and README.md then adds one short section: + where the suite runs, the pinned standard, where the manuals and the + specifications now live, and where a feature's code and a bounded + amendment are written. +- .gitattributes is the monorepo's own file, byte for byte (blob 3560973). + Its reason, the installer's byte-for-byte comparisons, is this leg's. + `git add --renormalize .` changes nothing here. +- .gitignore is the shape's templates/code-root/.gitignore, a superset of + the three lines the monorepo ignored. +- LICENSE is the root's Apache-2.0 text, byte for byte (blob 261eeb9). + +No executable, test or workflow is touched. + +Lane: openRepoTools-1 +Co-Authored-By: Claude Opus 5.5 +--- + .gitattributes | 32 ++++++++ + .gitignore | 12 +++ + AGENTS.md | 40 ++++++++++ + CLAUDE.md | 1 + + LICENSE | 201 +++++++++++++++++++++++++++++++++++++++++++++++++ + README.md | 45 +++++++++++ + 6 files changed, 331 insertions(+) + create mode 100644 .gitattributes + create mode 100644 .gitignore + create mode 100644 AGENTS.md + create mode 100644 CLAUDE.md + create mode 100644 LICENSE + create mode 100644 README.md + +diff --git a/.gitattributes b/.gitattributes +new file mode 100644 +index 0000000..3560973 +--- /dev/null ++++ b/.gitattributes +@@ -0,0 +1,32 @@ ++# THIS REPOSITORY'S OWN INSTALLER COMPARES ITS BYTES, so the bytes have to be ++# the same bytes on every platform. ++# ++# `openRepoTools --install` decides between `installed at`, `updated at` and ++# `already installed … (unchanged)` with `cmp -s`, and ++# `tests/test_openrepotools_command.py` compares an installed copy to this ++# checkout's file BYTE FOR BYTE. Git for Windows' installer default is ++# `core.autocrlf=true`; a checkout of THIS repository made under it turns every ++# `\n` in `openRepoTools`, `park`, `resume` and `status` into `\r\n`, and every one ++# of those comparisons is about line endings rather than about content — a ++# second `--install` reporting three files `updated at` on a machine where ++# nothing changed, and a colleague whose only sin was cloning on a stock ++# Windows laptop watching the suite fail on lines nobody touched. CI never sees ++# it: the Linux and macOS jobs check out LF by default and the Windows job pins ++# `core.autocrlf=false`. ++# ++# The same rule for a second reason. `tests/test_park_resume_commands.py` ++# asserts that `park`, `resume` and `status` carry the shared estate resolver ++# BYTE FOR BYTE — that is what makes the deliberate duplication safe — and a CRLF ++# checkout would either break the claim or hide a real drift behind it. ++# ++# NO PATH IS LISTED BY NAME. Every file this repository ships is text — bash, ++# Python, YAML, Markdown, a workflow — so there is nothing for git's binary ++# heuristic to misjudge, and `text=auto` leaves that detection where it ++# belongs: with git. The pinned `upstream/openRepoShape` is a gitlink and ++# carries its own copy of this rule. ++# ++# openRepoShape #63 wrote this rule for the standard's own root, and its #51 ++# (2026-09-05) wrote it for every scaffolded project; this is the same rule ++# retold for the repository that ships the two commands. ++ ++* text=auto eol=lf +diff --git a/.gitignore b/.gitignore +new file mode 100644 +index 0000000..2fb4cee +--- /dev/null ++++ b/.gitignore +@@ -0,0 +1,12 @@ ++__pycache__/ ++*.py[cod] ++.pytest_cache/ ++.mypy_cache/ ++.ruff_cache/ ++*.egg-info/ ++.venv/ ++venv/ ++.DS_Store ++*.swp ++.idea/ ++.vscode/ +diff --git a/AGENTS.md b/AGENTS.md +new file mode 100644 +index 0000000..810eac1 +--- /dev/null ++++ b/AGENTS.md +@@ -0,0 +1,40 @@ ++This is the **code leg** of openRepoTools (`openrepotools`): the implementation ++and its tests. ++ ++**The project's rules are not here.** They are in the assembly root ++`opensoft/openRepoTools`, in `AGENTS-shape.md` — read that before touching anything ++that spans the legs. This leg is mounted there at `code/`, and the other leg, ++`opensoft/openRepoTools-spec`, beside it at `../spec/`, once the root is cloned with ++`--recurse-submodules`. ++ ++Working here is ordinary — an ordinary repository on an ordinary branch. What ++advancing this leg does NOT do is advance the project: that is ONE commit in ++`opensoft/openRepoTools` moving the gitlink, `contracts/code-pin.yaml` and every ++workflow `@` for this leg together. ++ ++Being the code leg confers no authority over the implementation. The split is ++navigation. ++ ++## What is particular to openRepoTools' code leg ++ ++The project's own instructions — driving `park`, `resume` and `status`, the ++lane tooling's protocol, the pinned standard's three rules, how to test and the ++CI policy — are the assembly root's `AGENTS.md`, and they govern here. This ++section adds only what standing in this leg changes. ++ ++- **The suite runs here**: `git submodule update --init upstream/openRepoShape`, ++ then `tests/run.sh`, which serializes every lane on a workstation behind ++ `${TMPDIR:-/tmp}/openrepotools-pytest.lock` and passes any pytest argument on. ++- **The pinned standard is here**: `upstream/openRepoShape`, recorded in ++ `contracts/openreposhape-pin.yaml`. Never edit it in place, never pin a commit ++ that is not on openRepoShape's `main`, and recompute the digest rather than ++ adjust it — the root `AGENTS.md` gives the three rules in full. ++- **The manuals and the specifications are not here.** They are the spec ++ leg's `docs/` (`README-lanes.md`, `README-claude-current.md`), `openspec/` and ++ `specs/`: `../spec/` when this leg is mounted in the assembly root, and ++ `opensoft/openRepoTools-spec` when it is cloned on its own. ++- **A feature's code is written in its code worktree**, ++ `worktrees//code/` under the assembly root, on the feature's ++ branch. A genuine bounded amendment to an approved spec baseline is recorded ++ in that worktree's `features//openspec/`; a full proposal never ++ is, and goes to the spec leg. +diff --git a/CLAUDE.md b/CLAUDE.md +new file mode 100644 +index 0000000..5d30268 +--- /dev/null ++++ b/CLAUDE.md +@@ -0,0 +1 @@ ++Read AGENTS.md. +diff --git a/LICENSE b/LICENSE +new file mode 100644 +index 0000000..261eeb9 +--- /dev/null ++++ b/LICENSE +@@ -0,0 +1,201 @@ ++ Apache License ++ Version 2.0, January 2004 ++ http://www.apache.org/licenses/ ++ ++ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION ++ ++ 1. Definitions. ++ ++ "License" shall mean the terms and conditions for use, reproduction, ++ and distribution as defined by Sections 1 through 9 of this document. ++ ++ "Licensor" shall mean the copyright owner or entity authorized by ++ the copyright owner that is granting the License. ++ ++ "Legal Entity" shall mean the union of the acting entity and all ++ other entities that control, are controlled by, or are under common ++ control with that entity. For the purposes of this definition, ++ "control" means (i) the power, direct or indirect, to cause the ++ direction or management of such entity, whether by contract or ++ otherwise, or (ii) ownership of fifty percent (50%) or more of the ++ outstanding shares, or (iii) beneficial ownership of such entity. ++ ++ "You" (or "Your") shall mean an individual or Legal Entity ++ exercising permissions granted by this License. ++ ++ "Source" form shall mean the preferred form for making modifications, ++ including but not limited to software source code, documentation ++ source, and configuration files. ++ ++ "Object" form shall mean any form resulting from mechanical ++ transformation or translation of a Source form, including but ++ not limited to compiled object code, generated documentation, ++ and conversions to other media types. ++ ++ "Work" shall mean the work of authorship, whether in Source or ++ Object form, made available under the License, as indicated by a ++ copyright notice that is included in or attached to the work ++ (an example is provided in the Appendix below). ++ ++ "Derivative Works" shall mean any work, whether in Source or Object ++ form, that is based on (or derived from) the Work and for which the ++ editorial revisions, annotations, elaborations, or other modifications ++ represent, as a whole, an original work of authorship. For the purposes ++ of this License, Derivative Works shall not include works that remain ++ separable from, or merely link (or bind by name) to the interfaces of, ++ the Work and Derivative Works thereof. ++ ++ "Contribution" shall mean any work of authorship, including ++ the original version of the Work and any modifications or additions ++ to that Work or Derivative Works thereof, that is intentionally ++ submitted to Licensor for inclusion in the Work by the copyright owner ++ or by an individual or Legal Entity authorized to submit on behalf of ++ the copyright owner. For the purposes of this definition, "submitted" ++ means any form of electronic, verbal, or written communication sent ++ to the Licensor or its representatives, including but not limited to ++ communication on electronic mailing lists, source code control systems, ++ and issue tracking systems that are managed by, or on behalf of, the ++ Licensor for the purpose of discussing and improving the Work, but ++ excluding communication that is conspicuously marked or otherwise ++ designated in writing by the copyright owner as "Not a Contribution." ++ ++ "Contributor" shall mean Licensor and any individual or Legal Entity ++ on behalf of whom a Contribution has been received by Licensor and ++ subsequently incorporated within the Work. ++ ++ 2. Grant of Copyright License. Subject to the terms and conditions of ++ this License, each Contributor hereby grants to You a perpetual, ++ worldwide, non-exclusive, no-charge, royalty-free, irrevocable ++ copyright license to reproduce, prepare Derivative Works of, ++ publicly display, publicly perform, sublicense, and distribute the ++ Work and such Derivative Works in Source or Object form. ++ ++ 3. Grant of Patent License. Subject to the terms and conditions of ++ this License, each Contributor hereby grants to You a perpetual, ++ worldwide, non-exclusive, no-charge, royalty-free, irrevocable ++ (except as stated in this section) patent license to make, have made, ++ use, offer to sell, sell, import, and otherwise transfer the Work, ++ where such license applies only to those patent claims licensable ++ by such Contributor that are necessarily infringed by their ++ Contribution(s) alone or by combination of their Contribution(s) ++ with the Work to which such Contribution(s) was submitted. If You ++ institute patent litigation against any entity (including a ++ cross-claim or counterclaim in a lawsuit) alleging that the Work ++ or a Contribution incorporated within the Work constitutes direct ++ or contributory patent infringement, then any patent licenses ++ granted to You under this License for that Work shall terminate ++ as of the date such litigation is filed. ++ ++ 4. Redistribution. You may reproduce and distribute copies of the ++ Work or Derivative Works thereof in any medium, with or without ++ modifications, and in Source or Object form, provided that You ++ meet the following conditions: ++ ++ (a) You must give any other recipients of the Work or ++ Derivative Works a copy of this License; and ++ ++ (b) You must cause any modified files to carry prominent notices ++ stating that You changed the files; and ++ ++ (c) You must retain, in the Source form of any Derivative Works ++ that You distribute, all copyright, patent, trademark, and ++ attribution notices from the Source form of the Work, ++ excluding those notices that do not pertain to any part of ++ the Derivative Works; and ++ ++ (d) If the Work includes a "NOTICE" text file as part of its ++ distribution, then any Derivative Works that You distribute must ++ include a readable copy of the attribution notices contained ++ within such NOTICE file, excluding those notices that do not ++ pertain to any part of the Derivative Works, in at least one ++ of the following places: within a NOTICE text file distributed ++ as part of the Derivative Works; within the Source form or ++ documentation, if provided along with the Derivative Works; or, ++ within a display generated by the Derivative Works, if and ++ wherever such third-party notices normally appear. The contents ++ of the NOTICE file are for informational purposes only and ++ do not modify the License. You may add Your own attribution ++ notices within Derivative Works that You distribute, alongside ++ or as an addendum to the NOTICE text from the Work, provided ++ that such additional attribution notices cannot be construed ++ as modifying the License. ++ ++ You may add Your own copyright statement to Your modifications and ++ may provide additional or different license terms and conditions ++ for use, reproduction, or distribution of Your modifications, or ++ for any such Derivative Works as a whole, provided Your use, ++ reproduction, and distribution of the Work otherwise complies with ++ the conditions stated in this License. ++ ++ 5. Submission of Contributions. Unless You explicitly state otherwise, ++ any Contribution intentionally submitted for inclusion in the Work ++ by You to the Licensor shall be under the terms and conditions of ++ this License, without any additional terms or conditions. ++ Notwithstanding the above, nothing herein shall supersede or modify ++ the terms of any separate license agreement you may have executed ++ with Licensor regarding such Contributions. ++ ++ 6. Trademarks. This License does not grant permission to use the trade ++ names, trademarks, service marks, or product names of the Licensor, ++ except as required for reasonable and customary use in describing the ++ origin of the Work and reproducing the content of the NOTICE file. ++ ++ 7. Disclaimer of Warranty. Unless required by applicable law or ++ agreed to in writing, Licensor provides the Work (and each ++ Contributor provides its Contributions) on an "AS IS" BASIS, ++ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or ++ implied, including, without limitation, any warranties or conditions ++ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A ++ PARTICULAR PURPOSE. You are solely responsible for determining the ++ appropriateness of using or redistributing the Work and assume any ++ risks associated with Your exercise of permissions under this License. ++ ++ 8. Limitation of Liability. In no event and under no legal theory, ++ whether in tort (including negligence), contract, or otherwise, ++ unless required by applicable law (such as deliberate and grossly ++ negligent acts) or agreed to in writing, shall any Contributor be ++ liable to You for damages, including any direct, indirect, special, ++ incidental, or consequential damages of any character arising as a ++ result of this License or out of the use or inability to use the ++ Work (including but not limited to damages for loss of goodwill, ++ work stoppage, computer failure or malfunction, or any and all ++ other commercial damages or losses), even if such Contributor ++ has been advised of the possibility of such damages. ++ ++ 9. Accepting Warranty or Additional Liability. While redistributing ++ the Work or Derivative Works thereof, You may choose to offer, ++ and charge a fee for, acceptance of support, warranty, indemnity, ++ or other liability obligations and/or rights consistent with this ++ License. However, in accepting such obligations, You may act only ++ on Your own behalf and on Your sole responsibility, not on behalf ++ of any other Contributor, and only if You agree to indemnify, ++ defend, and hold each Contributor harmless for any liability ++ incurred by, or claims asserted against, such Contributor by reason ++ of your accepting any such warranty or additional liability. ++ ++ END OF TERMS AND CONDITIONS ++ ++ APPENDIX: How to apply the Apache License to your work. ++ ++ To apply the Apache License to your work, attach the following ++ boilerplate notice, with the fields enclosed by brackets "[]" ++ replaced with your own identifying information. (Don't include ++ the brackets!) The text should be enclosed in the appropriate ++ comment syntax for the file format. We also recommend that a ++ file or class name and description of purpose be included on the ++ same "printed page" as the copyright notice for easier ++ identification within third-party archives. ++ ++ Copyright [yyyy] [name of copyright owner] ++ ++ Licensed under the Apache License, Version 2.0 (the "License"); ++ you may not use this file except in compliance with the License. ++ You may obtain a copy of the License at ++ ++ http://www.apache.org/licenses/LICENSE-2.0 ++ ++ Unless required by applicable law or agreed to in writing, software ++ distributed under the License is distributed on an "AS IS" BASIS, ++ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ See the License for the specific language governing permissions and ++ limitations under the License. +diff --git a/README.md b/README.md +new file mode 100644 +index 0000000..1084a22 +--- /dev/null ++++ b/README.md +@@ -0,0 +1,45 @@ ++# openRepoTools-code ++ ++The **code leg** of the `openrepotools` project: the implementation and its ++tests. The requirements it implements live in ++[`opensoft/openRepoTools-spec`](https://github.com/opensoft/openRepoTools-spec). ++ ++**Clone the assembly root, not this repository.** This leg is mounted as a ++submodule at `code/` inside ++[`opensoft/openRepoTools`](https://github.com/opensoft/openRepoTools), which ++is what pins the commit of this repository that the project currently is: ++ ++```sh ++git clone --recurse-submodules https://github.com/opensoft/openRepoTools.git ++cd openRepoTools ++make bootstrap ++``` ++ ++Working here directly is fine — it is an ordinary repository with an ordinary ++branch. What advancing this leg does NOT do is advance the project: that is a ++commit in the assembly root moving the gitlink, `contracts/code-pin.yaml` and ++any workflow `@` reference together. ++ ++Being the code leg confers no authority over the implementation. The split is ++navigation; authority travels in grants, and a project that keeps spec and ++code in one repository is reviewed identically. ++ ++Topic: `xf-project-openrepotools`. ++ ++## What is in it ++ ++Every command `openRepoTools --install` places is a file at this leg's root — ++`openRepoTools` itself, `park`, `resume`, `status` and the lane tooling — with ++the alias table `repos.tsv`, the `skills/` and `commands/` it installs, the ++suite under `tests/` (`tests/run.sh` runs it), the CI under ++`.github/workflows/`, and the pinned standard at `upstream/openRepoShape` ++(`contracts/openreposhape-pin.yaml`). Installing is unchanged: the one-liner in ++the assembly root's `README.md` fetches the root's `openRepoTools`, which hands ++over to the installer here at the commit the root pins. ++ ++The manuals — `docs/README-lanes.md` for the lane tooling and ++`docs/README-claude-current.md` for `claude-current` and ++`claude-restart-check` — and every specification are in ++[`opensoft/openRepoTools-spec`](https://github.com/opensoft/openRepoTools-spec). ++ ++Licence: Apache-2.0. See [LICENSE](LICENSE). +-- +2.43.0 + diff --git a/specs/004-migrate-to-triad/patches/t006/spec-0001-The-spec-leg-gets-its-own-front-door-the-shape-s-leg.patch b/specs/004-migrate-to-triad/patches/t006/spec-0001-The-spec-leg-gets-its-own-front-door-the-shape-s-leg.patch new file mode 100644 index 00000000..f8373be7 --- /dev/null +++ b/specs/004-migrate-to-triad/patches/t006/spec-0001-The-spec-leg-gets-its-own-front-door-the-shape-s-leg.patch @@ -0,0 +1,398 @@ +From 39c18877e042e13156849b861d07bc3c92e64db4 Mon Sep 17 00:00:00 2001 +From: Brett Heap <1513478+brettheap@users.noreply.github.com> +Date: Thu, 8 Oct 2026 21:41:43 +0000 +Subject: [PATCH] The spec leg gets its own front door: the shape's leg + AGENTS.md, CLAUDE.md and README.md, the LF rule and licence it lost when the + root kept them, and the claude-current manual's three test paths re-pointed + at the code leg. +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Rehearsal patch for opensoft/openRepoTools#186, task T006 of +specs/004-migrate-to-triad/tasks.md, against run C's spec leg 593250d. The +split left this leg with no AGENTS.md, CLAUDE.md, README.md, LICENSE or +.gitattributes, because all of them stayed in the assembly root, and a +superproject's attributes never reach into a submodule's worktree. + +- AGENTS.md, CLAUDE.md and README.md are openRepoShape 7f84ca4's + templates/spec-root/ files, rendered by its own copy_tree with this + project's names, so the leg-clone detector in scripts/shape_advisory.py + recognises them. Each AGENTS.md and README.md then adds one short section: + what this leg holds; that full proposals and canonical Speckit files live + here, and a bounded amendment in the code leg's feature folder; where + `openspec validate` runs; and how to read a pre-split path or commit hash. +- .gitattributes restores the `* text=auto eol=lf` rule the monorepo's root + file applied to these documents. `git add --renormalize .` changes nothing. +- LICENSE is the root's Apache-2.0 text, byte for byte (blob 261eeb9). +- docs/README-claude-current.md:275-276: `tests/test_claude_current.py`, + `tests/test_claude_restart_check.py` and + `tests/test_lane_start_claude_current.py` become `code/tests/…`, relative + to the assembly root. + +No approval record is rewritten. Paths and commit hashes recorded in +openspec/changes/ and specs/ are left as their authors wrote them, under +FR-014 of specs/004-migrate-to-triad/spec.md. docs/README-lanes.md is left +to the lane-tooling analysis. + +Lane: openRepoTools-1 +Co-Authored-By: Claude Opus 5.5 +--- + .gitattributes | 10 ++ + AGENTS.md | 48 ++++++++ + CLAUDE.md | 1 + + LICENSE | 201 ++++++++++++++++++++++++++++++++++ + README.md | 39 +++++++ + docs/README-claude-current.md | 4 +- + 6 files changed, 301 insertions(+), 2 deletions(-) + create mode 100644 .gitattributes + create mode 100644 AGENTS.md + create mode 100644 CLAUDE.md + create mode 100644 LICENSE + create mode 100644 README.md + +diff --git a/.gitattributes b/.gitattributes +new file mode 100644 +index 0000000..974ecca +--- /dev/null ++++ b/.gitattributes +@@ -0,0 +1,10 @@ ++# LF IN EVERY WORKTREE, as these documents had before the split into three ++# repositories: the monorepo's `* text=auto eol=lf` covered them, and a ++# superproject's attributes never reach into a submodule's worktree, so the ++# rule travels with this leg. The code leg's hygiene tests read the manuals in ++# `docs/` and match their lines; a CRLF checkout would make those matches about ++# line endings rather than about content. ++# ++# NO PATH IS LISTED BY NAME. Every file here is text. ++ ++* text=auto eol=lf +diff --git a/AGENTS.md b/AGENTS.md +new file mode 100644 +index 0000000..b5504c1 +--- /dev/null ++++ b/AGENTS.md +@@ -0,0 +1,48 @@ ++This is the **spec leg** of openRepoTools (`openrepotools`): requirements, decisions ++and acceptance criteria. ++ ++**The project's rules are not here.** They are in the assembly root ++`opensoft/openRepoTools`, in `AGENTS-shape.md` — read that before touching anything ++that spans the legs. This leg is mounted there at `spec/`, and the other leg, ++`opensoft/openRepoTools-code`, beside it at `../code/`, once the root is cloned with ++`--recurse-submodules`. ++ ++Working here is ordinary — an ordinary repository on an ordinary branch. What ++advancing this leg does NOT do is advance the project: that is ONE commit in ++`opensoft/openRepoTools` moving the gitlink, `contracts/spec-pin.yaml` and every ++workflow `@` for this leg together. ++ ++Being the spec leg confers no authority over specifications. The split is ++navigation. ++ ++## What is particular to openRepoTools' spec leg ++ ++The project's own instructions are the assembly root's `AGENTS.md`, and they ++govern here. This section adds only what standing in this leg changes. ++ ++- **What is here**: `openspec/` (full OpenSpec proposals with their delta ++ specs, and their archive once archived), `specs/` (the Speckit feature ++ directories, `specs//`), `docs/` (the manuals: ++ `README-lanes.md` for the lane tooling, `README-claude-current.md` for ++ `claude-current` and `claude-restart-check`) and `ideation/` (brainstorm ++ packets). ++- **Full proposals and canonical Speckit files live here**, whichever branch ++ they were written on. The one exception is a genuine bounded amendment to an ++ approved baseline: it is recorded in the code leg's feature worktree, under ++ `features//openspec/`, and reconciled here once, when its ++ feature completes (the shared workflow's "Triad Feature Amendments"). ++- **`openspec` runs here**, never from the assembly root, which has no ++ `openspec/`: `openspec validate --strict` from this leg's root, or ++ from a feature's spec worktree, `worktrees//spec/` under the ++ assembly root, for a change on that feature's branch. A local root takes ++ precedence over a docs-store pointer; pass `--store` only where it resolves ++ to the checkout you mean. ++- **The code these documents describe is the code leg's.** A path such as ++ `tests/…`, `upstream/openRepoShape` or `contracts/openreposhape-pin.yaml` in a ++ document written before the split names a file of ++ `opensoft/openRepoTools-code` (`../code/` when mounted); where such a path has ++ been repaired it reads `code/…`, relative to the assembly root. A commit of this ++ project that such a document cites is a commit of `opensoft/openRepoTools`, ++ the assembly, which kept the whole history and its branches; the split ++ rewrote this leg's copy of that history, so the same change has a different ++ hash here. +diff --git a/CLAUDE.md b/CLAUDE.md +new file mode 100644 +index 0000000..5d30268 +--- /dev/null ++++ b/CLAUDE.md +@@ -0,0 +1 @@ ++Read AGENTS.md. +diff --git a/LICENSE b/LICENSE +new file mode 100644 +index 0000000..261eeb9 +--- /dev/null ++++ b/LICENSE +@@ -0,0 +1,201 @@ ++ Apache License ++ Version 2.0, January 2004 ++ http://www.apache.org/licenses/ ++ ++ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION ++ ++ 1. Definitions. ++ ++ "License" shall mean the terms and conditions for use, reproduction, ++ and distribution as defined by Sections 1 through 9 of this document. ++ ++ "Licensor" shall mean the copyright owner or entity authorized by ++ the copyright owner that is granting the License. ++ ++ "Legal Entity" shall mean the union of the acting entity and all ++ other entities that control, are controlled by, or are under common ++ control with that entity. For the purposes of this definition, ++ "control" means (i) the power, direct or indirect, to cause the ++ direction or management of such entity, whether by contract or ++ otherwise, or (ii) ownership of fifty percent (50%) or more of the ++ outstanding shares, or (iii) beneficial ownership of such entity. ++ ++ "You" (or "Your") shall mean an individual or Legal Entity ++ exercising permissions granted by this License. ++ ++ "Source" form shall mean the preferred form for making modifications, ++ including but not limited to software source code, documentation ++ source, and configuration files. ++ ++ "Object" form shall mean any form resulting from mechanical ++ transformation or translation of a Source form, including but ++ not limited to compiled object code, generated documentation, ++ and conversions to other media types. ++ ++ "Work" shall mean the work of authorship, whether in Source or ++ Object form, made available under the License, as indicated by a ++ copyright notice that is included in or attached to the work ++ (an example is provided in the Appendix below). ++ ++ "Derivative Works" shall mean any work, whether in Source or Object ++ form, that is based on (or derived from) the Work and for which the ++ editorial revisions, annotations, elaborations, or other modifications ++ represent, as a whole, an original work of authorship. For the purposes ++ of this License, Derivative Works shall not include works that remain ++ separable from, or merely link (or bind by name) to the interfaces of, ++ the Work and Derivative Works thereof. ++ ++ "Contribution" shall mean any work of authorship, including ++ the original version of the Work and any modifications or additions ++ to that Work or Derivative Works thereof, that is intentionally ++ submitted to Licensor for inclusion in the Work by the copyright owner ++ or by an individual or Legal Entity authorized to submit on behalf of ++ the copyright owner. For the purposes of this definition, "submitted" ++ means any form of electronic, verbal, or written communication sent ++ to the Licensor or its representatives, including but not limited to ++ communication on electronic mailing lists, source code control systems, ++ and issue tracking systems that are managed by, or on behalf of, the ++ Licensor for the purpose of discussing and improving the Work, but ++ excluding communication that is conspicuously marked or otherwise ++ designated in writing by the copyright owner as "Not a Contribution." ++ ++ "Contributor" shall mean Licensor and any individual or Legal Entity ++ on behalf of whom a Contribution has been received by Licensor and ++ subsequently incorporated within the Work. ++ ++ 2. Grant of Copyright License. Subject to the terms and conditions of ++ this License, each Contributor hereby grants to You a perpetual, ++ worldwide, non-exclusive, no-charge, royalty-free, irrevocable ++ copyright license to reproduce, prepare Derivative Works of, ++ publicly display, publicly perform, sublicense, and distribute the ++ Work and such Derivative Works in Source or Object form. ++ ++ 3. Grant of Patent License. Subject to the terms and conditions of ++ this License, each Contributor hereby grants to You a perpetual, ++ worldwide, non-exclusive, no-charge, royalty-free, irrevocable ++ (except as stated in this section) patent license to make, have made, ++ use, offer to sell, sell, import, and otherwise transfer the Work, ++ where such license applies only to those patent claims licensable ++ by such Contributor that are necessarily infringed by their ++ Contribution(s) alone or by combination of their Contribution(s) ++ with the Work to which such Contribution(s) was submitted. If You ++ institute patent litigation against any entity (including a ++ cross-claim or counterclaim in a lawsuit) alleging that the Work ++ or a Contribution incorporated within the Work constitutes direct ++ or contributory patent infringement, then any patent licenses ++ granted to You under this License for that Work shall terminate ++ as of the date such litigation is filed. ++ ++ 4. Redistribution. You may reproduce and distribute copies of the ++ Work or Derivative Works thereof in any medium, with or without ++ modifications, and in Source or Object form, provided that You ++ meet the following conditions: ++ ++ (a) You must give any other recipients of the Work or ++ Derivative Works a copy of this License; and ++ ++ (b) You must cause any modified files to carry prominent notices ++ stating that You changed the files; and ++ ++ (c) You must retain, in the Source form of any Derivative Works ++ that You distribute, all copyright, patent, trademark, and ++ attribution notices from the Source form of the Work, ++ excluding those notices that do not pertain to any part of ++ the Derivative Works; and ++ ++ (d) If the Work includes a "NOTICE" text file as part of its ++ distribution, then any Derivative Works that You distribute must ++ include a readable copy of the attribution notices contained ++ within such NOTICE file, excluding those notices that do not ++ pertain to any part of the Derivative Works, in at least one ++ of the following places: within a NOTICE text file distributed ++ as part of the Derivative Works; within the Source form or ++ documentation, if provided along with the Derivative Works; or, ++ within a display generated by the Derivative Works, if and ++ wherever such third-party notices normally appear. The contents ++ of the NOTICE file are for informational purposes only and ++ do not modify the License. You may add Your own attribution ++ notices within Derivative Works that You distribute, alongside ++ or as an addendum to the NOTICE text from the Work, provided ++ that such additional attribution notices cannot be construed ++ as modifying the License. ++ ++ You may add Your own copyright statement to Your modifications and ++ may provide additional or different license terms and conditions ++ for use, reproduction, or distribution of Your modifications, or ++ for any such Derivative Works as a whole, provided Your use, ++ reproduction, and distribution of the Work otherwise complies with ++ the conditions stated in this License. ++ ++ 5. Submission of Contributions. Unless You explicitly state otherwise, ++ any Contribution intentionally submitted for inclusion in the Work ++ by You to the Licensor shall be under the terms and conditions of ++ this License, without any additional terms or conditions. ++ Notwithstanding the above, nothing herein shall supersede or modify ++ the terms of any separate license agreement you may have executed ++ with Licensor regarding such Contributions. ++ ++ 6. Trademarks. This License does not grant permission to use the trade ++ names, trademarks, service marks, or product names of the Licensor, ++ except as required for reasonable and customary use in describing the ++ origin of the Work and reproducing the content of the NOTICE file. ++ ++ 7. Disclaimer of Warranty. Unless required by applicable law or ++ agreed to in writing, Licensor provides the Work (and each ++ Contributor provides its Contributions) on an "AS IS" BASIS, ++ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or ++ implied, including, without limitation, any warranties or conditions ++ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A ++ PARTICULAR PURPOSE. You are solely responsible for determining the ++ appropriateness of using or redistributing the Work and assume any ++ risks associated with Your exercise of permissions under this License. ++ ++ 8. Limitation of Liability. In no event and under no legal theory, ++ whether in tort (including negligence), contract, or otherwise, ++ unless required by applicable law (such as deliberate and grossly ++ negligent acts) or agreed to in writing, shall any Contributor be ++ liable to You for damages, including any direct, indirect, special, ++ incidental, or consequential damages of any character arising as a ++ result of this License or out of the use or inability to use the ++ Work (including but not limited to damages for loss of goodwill, ++ work stoppage, computer failure or malfunction, or any and all ++ other commercial damages or losses), even if such Contributor ++ has been advised of the possibility of such damages. ++ ++ 9. Accepting Warranty or Additional Liability. While redistributing ++ the Work or Derivative Works thereof, You may choose to offer, ++ and charge a fee for, acceptance of support, warranty, indemnity, ++ or other liability obligations and/or rights consistent with this ++ License. However, in accepting such obligations, You may act only ++ on Your own behalf and on Your sole responsibility, not on behalf ++ of any other Contributor, and only if You agree to indemnify, ++ defend, and hold each Contributor harmless for any liability ++ incurred by, or claims asserted against, such Contributor by reason ++ of your accepting any such warranty or additional liability. ++ ++ END OF TERMS AND CONDITIONS ++ ++ APPENDIX: How to apply the Apache License to your work. ++ ++ To apply the Apache License to your work, attach the following ++ boilerplate notice, with the fields enclosed by brackets "[]" ++ replaced with your own identifying information. (Don't include ++ the brackets!) The text should be enclosed in the appropriate ++ comment syntax for the file format. We also recommend that a ++ file or class name and description of purpose be included on the ++ same "printed page" as the copyright notice for easier ++ identification within third-party archives. ++ ++ Copyright [yyyy] [name of copyright owner] ++ ++ Licensed under the Apache License, Version 2.0 (the "License"); ++ you may not use this file except in compliance with the License. ++ You may obtain a copy of the License at ++ ++ http://www.apache.org/licenses/LICENSE-2.0 ++ ++ Unless required by applicable law or agreed to in writing, software ++ distributed under the License is distributed on an "AS IS" BASIS, ++ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ See the License for the specific language governing permissions and ++ limitations under the License. +diff --git a/README.md b/README.md +new file mode 100644 +index 0000000..89beb3b +--- /dev/null ++++ b/README.md +@@ -0,0 +1,39 @@ ++# openRepoTools-spec ++ ++The **spec leg** of the `openrepotools` project: requirements, decisions and ++acceptance criteria. The implementation lives in ++[`opensoft/openRepoTools-code`](https://github.com/opensoft/openRepoTools-code). ++ ++**Clone the assembly root, not this repository.** This leg is mounted as a ++submodule at `spec/` inside ++[`opensoft/openRepoTools`](https://github.com/opensoft/openRepoTools), which ++is what pins the commit of this repository that the project currently is: ++ ++```sh ++git clone --recurse-submodules https://github.com/opensoft/openRepoTools.git ++cd openRepoTools ++make bootstrap ++``` ++ ++Working here directly is fine — it is an ordinary repository with an ordinary ++branch. What advancing this leg does NOT do is advance the project: that is a ++commit in the assembly root moving the gitlink, `contracts/spec-pin.yaml` and ++any workflow `@` reference together. ++ ++Being the spec leg confers no authority over specifications. The split is ++navigation; authority travels in grants, and a project that keeps spec and ++code in one repository is reviewed identically. ++ ++Topic: `xf-project-openrepotools`. ++ ++## What is in it ++ ++`openspec/` holds the project's full OpenSpec proposals, `specs/` its Speckit ++feature directories, `docs/` the manuals — `docs/README-lanes.md` for the lane ++tooling and `docs/README-claude-current.md` for `claude-current` and ++`claude-restart-check` — and `ideation/` the brainstorm packets the proposals ++grew from. The commands, their tests and their CI are in ++[`opensoft/openRepoTools-code`](https://github.com/opensoft/openRepoTools-code). ++`openspec validate --strict` runs from this leg's root. ++ ++Licence: Apache-2.0. See [LICENSE](LICENSE). +diff --git a/docs/README-claude-current.md b/docs/README-claude-current.md +index 03687b9..36818dc 100644 +--- a/docs/README-claude-current.md ++++ b/docs/README-claude-current.md +@@ -272,6 +272,6 @@ The status line that calls it is workBenches' + + Both commands parse and run under bash 3.2, which is what macOS ships. Neither + uses `mapfile`, associative arrays, `readlink -f` or GNU-only flags. +-`tests/test_claude_current.py`, `tests/test_claude_restart_check.py` and +-`tests/test_lane_start_claude_current.py` are their suites, and every one of ++`code/tests/test_claude_current.py`, `code/tests/test_claude_restart_check.py` and ++`code/tests/test_lane_start_claude_current.py` are their suites, and every one of + them runs against fakes. None reaches npm or runs a real Claude Code. +-- +2.43.0 + diff --git a/specs/004-migrate-to-triad/root-guidance-2026-10-08.md b/specs/004-migrate-to-triad/root-guidance-2026-10-08.md new file mode 100644 index 00000000..ed610850 --- /dev/null +++ b/specs/004-migrate-to-triad/root-guidance-2026-10-08.md @@ -0,0 +1,432 @@ +# T006: root and leg guidance, collisions, and workflow bootstrap (run C rebuild) + +**Task**: T006 of [tasks.md](tasks.md), for opensoft/openRepoTools#186. +**Writer**: lane openRepoTools-1, on 2026-10-08 and 2026-10-09. +**Evidence branch**: `004-migrate-to-triad-root-guidance`. +**State**: rehearsal evidence only. Nothing here lands on `main`, and today's layout is untouched. + +The 2026-10-07 build of this task ran against run B. It was lost when the bench +restart cleared `/tmp`: clones, patches and logs alike. Nothing from it was ever +committed. This document is its rebuild on **run C**, the rehearsal regenerated +on 2026-10-08. Everything below was measured again on run C, and none of it is +carried over from run B. + +## Inputs and outputs + +| what | identity | +|---|---| +| source (`main` when run C cloned it) | `837928a` (includes #187's planning files and #188's openRepoShape repin) | +| rehearsed assembly split (`adopt/three-repo-shape`) | `69b8924` | +| spec leg / code leg (run C bare remotes) | `593250d` (35 commits) / `73b04d6` (88 commits) | +| code's nested `upstream/openRepoShape`, and the tool | `7f84ca4` (opensoft/openRepoShape `main`) | +| **code leg patch** (my bare copy, branch `t006/leg-guidance`) | `6169e8e` | +| **spec leg patch** (my bare copy, branch `t006/leg-guidance`) | `39c1887` | +| **assembly patch** (my bare copy, branch `t006/root-guidance-c2`) | `3361812`, then `bump-leg.py`'s `1024f18` (spec) and `3e87e39` (code) | + +All work happened in host-local private scratch: my own bare copies of the +three run C remotes and of the openRepoShape mirror. Nothing was written into +the rehearsal directory, and nothing reached a real remote except this +evidence branch. + +The patches are in [patches/t006/](patches/t006/), as `git format-patch` +output: + +| patch | sha256 | +|---|---| +| `assembly-0001-…front-door…` | `cd69c2a9afb7bb11…` | +| `assembly-0002-Bump-spec-leg-to-39c18877e042…` | `574c0df16dddda7d…` | +| `assembly-0003-Bump-code-leg-to-6169e8e96164…` | `a7f8521bb7dbb9b5…` | +| `code-0001-…own-front-door…` | `74b98ff97b0b9b6b…` | +| `spec-0001-…own-front-door…` | `c11070f05a9e4611…` | + +Each was applied with `git am` onto its base in a fresh clone, and each +reproduced the pushed tree exactly: + +| patch set | `git am` | tree check | `git diff --check` on the range | +|---|---|---|---| +| assembly | exit 0 | `6eb8145e…` = `6eb8145e…` | exit 0 | +| code | exit 0 | `ca65c3b9…` = `ca65c3b9…` | exit 0 | +| spec | exit 0 | `10256135…` = `10256135…` | exit 0 | + +`assembly-0002` and `-0003` are commits written by openRepoShape's +`bump-leg.py`, and they pin the rehearsal's leg hashes. At T014 they are +**regenerated against the real legs with the same tool, not applied**. +`assembly-0001`, `code-0001` and `spec-0001` are the portable patches. + +The evidence branch's first commit, `f06e1af`, recorded an earlier `assembly-0001`. +Its root additions were 46 and 39 lines. The hygiene caps in section 7 made +those too long, so this commit replaces the three assembly patches with the +c2 versions. The legs' patches are unchanged. + +## 1. Collision inventory at the assembly root + +The root tree after run C's split, path by path. The front-door blobs are the +monorepo's own: `AGENTS.md` 443880a, `README.md` 18c8f7e, `.gitattributes` +3560973 and `.gitignore` a160f49, all equal to `main`'s. + +| path | class | resolution in this patch set | +|---|---|---| +| `AGENTS.md` | **collision** (shape wrote `shape/AGENTS.md`) | Line 1 is the shape's exact pointer, then a short map, then the original text with 16 path repairs. This is the shape's own follow-up: "add the line … to the existing AGENTS.md rather than replacing it". | +| `CLAUDE.md` | **collision** (shape wrote `shape/CLAUDE.md`) | Unchanged. `Read AGENTS.md.` is byte-identical to the shape's own rendered `shape/CLAUDE.md` (`cmp` equal), so the pointer reaches it through `AGENTS.md` line 1. The shape's generic follow-up text ("add the line to the existing CLAUDE.md") is satisfied the way the shape's own scaffold satisfies it. | +| `README.md` | **collision** (shape wrote `shape/README.md`) | Merged. A "Three repositories" section and "The lockstep invariant" are added. `contracts/{spec,code}-pin.yaml` name `resync_runbook: "README.md#the-lockstep-invariant"`, an anchor that only `shape/README.md` had. Plus 12 path repairs and a re-rooted pin-bump block. | +| `.gitignore` | **collision** (shape wrote `shape/.gitignore`, pinned) | The project's file absorbs the shape's two extra lines, `.venv/` and `.DS_Store`, plus `/worktrees/`, the line the three-leg bootstrap requires. Nothing was lost: the root kept `__pycache__/`, `*.py[cod]` and `.pytest_cache/`. | +| `.gitattributes` | **collision** (shape wrote `shape/.gitattributes`, pinned) | Same rule, `* text=auto eol=lf`, with the comment rewritten for this root's pinned copies. The monorepo's reason (the installer's byte-for-byte comparisons) moved, word for word, to `code/.gitattributes`. | +| `LICENSE` | front door | Unchanged. A byte-identical copy is added to each leg, which had none. | +| `shape/.gitignore`, `shape/.gitattributes` | shape-generated, **pinned** (rows in `contracts/shape-pin.yaml`) | Left in place. Deleting or editing them is drift. `update-shape.py` knows a collision copy by its `shape/` row. | +| `shape/AGENTS.md`, `shape/CLAUDE.md`, `shape/README.md` | shape-generated, **not pinned** (no row; verified) | Left in place under this task's rule. The shape's own follow-up says to delete `shape/AGENTS.md` and `shape/CLAUDE.md` once the pointer line is in `AGENTS.md`, which this patch does, and to merge `shape/README.md`, which this patch also does. Removing all three is drift-free. **Decision for T014's reviewer.** `shape/README.md` also carries the rehearsal's local clone path, an artifact of rehearsing with local remotes. | +| `AGENTS-shape.md`, `Makefile`, `scripts/{bootstrap,repo_shape,validate-manifest,validate-pins,validate-repository-naming}.py`, `contracts/repository-naming.yaml`, `.github/workflows/validate.yml` | shape-generated, **pinned** | Untouched. 11 rows; all 11 digests match before and after. | +| `project.yaml`, `contracts/{spec,code,shape}-pin.yaml`, `.gitmodules` (the two mounts) | shape-generated, rendered | Untouched. The leg pins move only through `bump-leg.py`. | +| `spec`, `code` gitlinks | shape-generated | Moved only by `bump-leg.py`, each together with its pin. | +| `contracts/`, `.github/`, `.gitmodules` as **names** | not collisions | The source's `contracts/openreposhape-pin.yaml`, `.github/workflows/tests.yml` and `.gitmodules` left whole for the code leg. The root's same-named files are the shape's. No source path exists at `shape/`, `scripts/`, `Makefile`, `project.yaml`, `worktrees/` or `.specify/` (checked at `837928a`). | +| (lost by the split) | **legs' front door** | Neither leg has `AGENTS.md`, `CLAUDE.md`, `README.md`, `LICENSE`, `.gitattributes` or `.gitignore`. A superproject's attributes and ignore rules never reach into a submodule, so the code leg lost the LF rule its installer depends on. Section 4 restores them. | + +## 2. Root agent instructions + +- **`AGENTS.md`**, 316 → 340 lines. + - Line 1 is `Read AGENTS-shape.md first — the rules of this repository's shape.`, the exact `SHAPE_POINTER_LINE` of openRepoShape 7f84ca4. + - A seven-entry map follows: + - `spec/`, with `openspec` run there; + - `code/`, with the pinned standard; + - this root; + - where the tests run; + - the lane tooling's code (`code/`) and its manual (`spec/docs/README-lanes.md`, or `docs/README-lanes.md` in `opensoft/openRepoTools-spec` standalone); + - paired feature worktrees, with full proposals in spec and bounded amendments in code; + - the local-rehearsal file-transport flag (the real https legs need none). + - The original text follows, unchanged except for the 16 repairs in section 3. + - The shape's 14-line rendered repository table is **not** repeated: the map names the same repositories, and the repository's length cap (section 7) admits lines only for a rule. +- **`README.md`**, 486 → 510 lines. + - The front door is kept. + - "Three repositories" says what the root is, what each leg holds, the recursive clone and `make bootstrap`, that nothing a person types changes, and how paths read. + - "The lockstep invariant" is the anchor the leg pins name. + - The install section adds: "Both lines are unchanged by the split: the `openRepoTools` they fetch is this root's entry point, which hands over to the installer `code/openRepoTools` at the commit this root pins." The entry point itself is T007's. +- **`CLAUDE.md`**: unchanged, as briefed. + +## 3. Repaired links (32) + +Line numbers refer to the original blobs at `69b8924`. Every repair was +asserted to match exactly once. + +| file:line | was | now | +|---|---|---| +| AGENTS.md:22 | `docs/README-lanes.md` | `spec/docs/README-lanes.md` | +| AGENTS.md:44 | `openspec/changes/skip-no-lane-guard/` | `spec/openspec/changes/skip-no-lane-guard/` | +| AGENTS.md:200 | heading `upstream/openRepoShape` | `code/upstream/openRepoShape` | +| AGENTS.md:203 | `contracts/openreposhape-pin.yaml` | `code/contracts/openreposhape-pin.yaml` | +| AGENTS.md:206 | `upstream/openRepoShape` | `code/upstream/openRepoShape` | +| AGENTS.md:214 | `contracts/openreposhape-pin.yaml` | `code/contracts/openreposhape-pin.yaml` | +| AGENTS.md:216 | `tests/test_upstream_pin.py` | `code/tests/test_upstream_pin.py` | +| AGENTS.md:223 | `git submodule update --init upstream/openRepoShape` | `git -C code submodule update --init upstream/openRepoShape` | +| AGENTS.md:224 | `tests/run.sh` | `code/tests/run.sh` (the wrapper `cd`s to its own root, so it runs from here) | +| AGENTS.md:233 | `.github/workflows/tests.yml` | `code/.github/workflows/tests.yml` | +| AGENTS.md:247 | `bash tests/test_lane_helpers.sh` | `bash code/tests/test_lane_helpers.sh` (it derives `SRC_DIR` from its own path) | +| AGENTS.md:248, 250, 285 | `tests/run.sh` | `code/tests/run.sh` | +| AGENTS.md:291 | `tests/test_lane_helpers.sh` | `code/tests/test_lane_helpers.sh` | +| AGENTS.md:292 | `tests/test_lane_helpers_suite.py` | `code/tests/test_lane_helpers_suite.py` | +| README.md:157 | `docs/README-lanes.md`, `docs/README-claude-current.md` | `spec/docs/…` (2) | +| README.md:183 | `contracts/openreposhape-pin.yaml` | `code/contracts/openreposhape-pin.yaml` | +| README.md:263 | `tests/run.sh` | `code/tests/run.sh` | +| README.md:303 | `commands/swap.md` | `code/commands/swap.md` | +| README.md:417 | `upstream/openRepoShape`, `contracts/openreposhape-pin.yaml` | `code/…` (2) | +| README.md:422 | `tests/test_park_resume_commands.py` | `code/tests/test_park_resume_commands.py` | +| README.md:429 | `tests/test_upstream_pin.py` | `code/tests/test_upstream_pin.py` | +| README.md:437–442 | the pin-bump block (`upstream/openRepoShape` ×4, `contracts/openreposhape-pin.yaml` ×2) | re-rooted with one added line, `cd code`, because the pin and gitlink are a commit in the code leg | +| README.md:449 | `tests/test_upstream_pin.py` | `code/tests/test_upstream_pin.py` | +| README.md:464 | `cd openRepoTools && python3 -m pytest tests -q` | `cd openRepoTools/code && …` | +| README.md:467 | `git submodule update --init upstream/openRepoShape` | `git -C code submodule update --init upstream/openRepoShape` (the command the suite's skip message names, run in the code leg) | +| spec `docs/README-claude-current.md`:275–276 | `tests/test_claude_current.py`, `tests/test_claude_restart_check.py`, `tests/test_lane_start_claude_current.py` | `code/tests/…` (3) | + +That is 16 repairs in `AGENTS.md`, 13 in `README.md` (12 rewrites and the +re-rooted block) and 3 in the spec leg. All six markdown links in the spec leg +that use relative `](…)` targets stay inside the spec leg, so none crossed a leg. + +**Deliberately unchanged:** +- `AGENTS.md`'s `python3 -m pytest tests -q …` lines describe the command line `tests/run.sh` itself runs, inside the code leg, which is what every other lane's `pgrep` counts. +- `contracts/shape-pin.yaml` (AGENTS.md:118, README.md:46) names every estate's shape pin, not a path into a leg. +- README.md:242 `repos.tsv` is a file installed into `~/.local/bin`. +- README.md:41 `upstream/` is a git remote. + +## 4. Leg guidance added + +The shape does provide leg guidance. `templates/code-root/` and +`templates/spec-root/` carry `AGENTS.md`, `CLAUDE.md`, `README.md` and +`.gitignore`. `scaffold-project.py` writes them, and `adopt-project.py` seeds them +only into an empty leg, so an extracted leg gets none. The leg files below are those +templates, rendered by the shape's own `shape_materialize.copy_tree` with this +project's values; no placeholder is left. Each `AGENTS.md` and `README.md` then +appends one short openRepoTools section. + +| leg | files | openRepoTools section says | +|---|---|---| +| code | `AGENTS.md`, `CLAUDE.md` ("Read AGENTS.md."), `README.md`, `.gitattributes` (monorepo blob 3560973, byte for byte), `.gitignore` (the shape's code-root template, a superset of the monorepo's three lines), `LICENSE` (blob 261eeb9) | The root's guidance governs. The suite runs here: `git submodule update --init upstream/openRepoShape`, then `tests/run.sh` behind `${TMPDIR:-/tmp}/openrepotools-pytest.lock`. The pinned standard's three rules, by pointer. The manuals and specifications are `../spec/` when mounted and `opensoft/openRepoTools-spec` standalone. A feature's code goes in its code worktree, a bounded amendment in `features//openspec/`. | +| spec | `AGENTS.md`, `CLAUDE.md`, `README.md`, `.gitattributes` (the LF rule the monorepo's root file applied to these documents), `LICENSE` | It holds `openspec/`, `specs/`, `docs/` and `ideation/`. Full proposals and canonical Speckit files live here; bounded amendments live in the code leg's feature folder, under the shared workflow's "Triad Feature Amendments". `openspec validate --strict` runs here, never at the root, and a local root takes precedence over a store pointer. A pre-split path names a code-leg file (`code/…` once repaired), and a cited commit is the assembly's. | + +`git add --renormalize .` changes nothing in either leg. Every file is LF: +code 54 + 1 gitlink, spec 70. + +The spec leg gets no `.gitignore`. Nothing ignorable was lost there, and the +doctor's row for it is a `note`, which the shape's own agent rules say is +"not a finding and you do not 'fix' it". + +**Shape agreement, measured:** +- **Doctor `leg shape files`.** Before: 6 files missing (AGENTS/CLAUDE/.gitignore × 2). After: "spec: AGENTS.md rendered, CLAUDE.md identical, .gitignore absent; code: AGENTS.md rendered, CLAUDE.md identical, .gitignore identical". +- **`scripts/shape_advisory.py`'s leg-clone reader on standalone clones.** Before: recognised by name only (`openRepoTools-code` … "is the code-leg form"). After: "its AGENTS.md is the code leg's, as templates/code-root/ writes it", with the assembly `opensoft/openRepoTools` named. The spec leg reads the same way. +- **Doctor `placement` finding.** Identical before and after: the same 21 flagged paths, `misplaced: 3`, `review_required: 18`. It comes from the adoption plan's reasoned overrides for `commands/`, `skills/` and `contracts/`, and none of the 11 added leg files is flagged. Per the shape's rule 4 it is relayed, not answered. + +## 5. Cross-leg references this task does not repair, with owners + +- **Spec records (approval provenance, FR-014): kept as written.** + - Pre-split code paths appear in 17 files under `openspec/` and `specs/`. + - In completed features (002, 003 and the four other changes) they record acts done against the monorepo at a commit. Example: specs/002 tasks.md:9, "`bash tests/run.sh -k …` passed 165 checks … on implementation commit `64f4e93`". Rewriting such a line would falsify the record. + - **Feature 004's own working files** carry 23 mentions in 9 files: plan.md 4, installer-design.md 4, proposal.md 4, design.md 3, adoption-plan.yaml 3, tasks.md 2, adopter-rehearsal.md 2, and spec.md, quickstart.md, preparation.md and adopter-blocker.md with 1 each. These are T017's to translate with the feature. + - 005's `analysis.md`:13 is likewise T016/T017's. +- **`docs/README-lanes.md` (lane 3: the manual's content).** 21 lines name code-leg files: + - lines 72, 572, 3601, 4163: `tests/test_lane_helpers.sh`; + - lines 573, 576, 594, 3777, 3979, 3997: `tests/run.sh`; + - line 3601 (again): `tests/test_lanes_index.py`; + - lines 74, 637, 1013, 1062, 1236, 2369, 2477, 4162: `repos.tsv` (some of these mean the installed table); + - lines 2551–2553: `skills/handoff/SKILL.md`, `skills/lane-swap/SKILL.md`, `commands/{ctx,handoff,swap}.md`. +- **Code-leg citations of the manuals: 23 lines in 8 files.** + - `lane-start` 2 and `lanes-edit.sh` 1, plus `tests/test_lane_helpers.sh` 4 (lines 3730, 3738, 6222, 6228), go to **lane 3**. + - `openRepoTools` 1 (line 1218, the `venv_note` comment) goes to **T007**, which patches that file. + - `tests/test_repo_hygiene.py` 11, `tests/test_lane_start_claude_current.py` 1 and `tests/run.sh` 1 (comment, line 36) go to **T009/T010**. + - `claude-current` 2: line 89 is a comment, and line 123 is `--help` output, so changing it changes the shipped command. Its owner is whoever owns `claude-current`'s text; it is not changed here. + - A comment-only repair ahead of the functional change beside it would leave the comment disagreeing with the code. +- **Code-leg OpenSpec object keys (lane 3).** `lanes-edit.sh` (12 lines, e.g. 280, 2770, 2814–2827, 2895–2897) and `lane-handoff`:2439 key a claim as `owner/repo:openspec/changes/`. After the split a change lives in `opensoft/openRepoTools-spec`, not the assembly. +- **External referrers (lane 2's reads; follow-ups, not edited here).** From lane 2's `consumer-reads-2026-10-07.md` on `004-migrate-to-triad-reads` (76dcb93, which contains ae70dc5); line numbers are as lane 2 recorded them. + - **brett-wip:** + - `lanes/LANES.md`:36 and :1305; + - 12 `docs/README-lanes` lines in 9 files (first: `handoffs/openRepoTools/brief-lane-openRepoTools-1-2026-09-16.md`:9, `session-handoff-2026-09-16-lane-openRepoTools-1.md`:33, `lanes/log/openRepoTools-1.md`:22); + - `lanes/repos.tsv`:52, which has no leg aliases. + - **`~/.agents`:** + - `protocols/lane-collision-protocol-amendment-9.md`:735 and 943, `-11.md`:1511, `-12.md`:315, `-14.md`:185, `-17.md`:175 and `-18.md`:222, all naming the manual; + - `AGENTS.md`:273-278 ("places from `opensoft/openRepoTools`"); + - `AGENTS.md`:327 and others ("the code lands in `opensoft/openRepoTools`"). + - **openRepoShape 7f84ca4:** + - `templates/workspace-root/AGENTS.md`:32, 71-76, 91-95, plus `README.md`:16,18,31,58, `handoffs/README.md`:24, `lanes/LANES.md`:19,40, `lanes/log/README.md`:4,6 and `.gitattributes`:8, all in that template; + - `scripts/shape_advisory.py`:509; + - the `--install` pointer at `openRepoShape`:279-280. + - **workBenches 7fd01e3:** + - `README.md`:188 and 190; + - `docs/claude-multi-account-profiles.md`:314; + - the vendored openrepotools tree and its `upstream-pin.yaml` rows. + - **Installed handoff skill:** `~/.claude/skills/handoff/SKILL.md`:329, whose writer-poll globs do not cover `/worktrees/`. + +## 6. Provenance correspondence (FR-014) + +From run C's own filter-repo `commit-map`, copied read-only. For every +spec-leg commit, every path that commit changed was compared blob for blob +with its original commit: **35 of 35 match, 0 mismatches**. + +Correspondence is deterministic. The first 34 spec commits have exactly the +hashes run B produced (for example `30d7bf7`, `b4e1cb7`, `558a12b`). Only +`593250d`, from #187's `44983ce`, is new. Re-extracting the same history +reproduces the same leg identities. + +| # | spec leg (run C) | original (assembly history) | date | paths | of which openspec/ or specs/ | blob check | +|---|---|---|---|---|---|---| +| 1 | `af9806736072` | `3fc62156e798` | 2026-09-10 | 1 | 0 | ok=1 bad=0 | +| 2 | `7ab0b61163ec` | `9e3e241f4f3c` | 2026-09-10 | 1 | 0 | ok=1 bad=0 | +| 3 | `1a12f36f7b5e` | `792a9dede1ec` | 2026-09-10 | 1 | 0 | ok=1 bad=0 | +| 4 | `a1ebef63f4a6` | `3179f50d4d6c` | 2026-09-10 | 1 | 0 | ok=1 bad=0 | +| 5 | `46ef1be6a9af` | `e6044d3a4189` | 2026-09-12 | 1 | 0 | ok=1 bad=0 | +| 6 | `780b66740b1a` | `2108ce3ab6f7` | 2026-09-12 | 1 | 0 | ok=1 bad=0 | +| 7 | `e26759dffc6d` | `bd27fc346682` | 2026-09-13 | 1 | 0 | ok=1 bad=0 | +| 8 | `422f7f8b6ef4` | `9000e86796b2` | 2026-09-13 | 1 | 0 | ok=1 bad=0 | +| 9 | `f53e46c45137` | `f97530bb936e` | 2026-09-13 | 1 | 0 | ok=1 bad=0 | +| 10 | `cfae3d330bae` | `63a74af1b741` | 2026-09-14 | 1 | 0 | ok=1 bad=0 | +| 11 | `f245d9f16c86` | `8345d2f1dc42` | 2026-09-14 | 1 | 0 | ok=1 bad=0 | +| 12 | `7a76cad941fe` | `a95443591ab7` | 2026-09-14 | 1 | 0 | ok=1 bad=0 | +| 13 | `52acec4817a2` | `2faa883db081` | 2026-09-14 | 1 | 0 | ok=1 bad=0 | +| 14 | `17ba18660c6a` | `fcccbe08b1bd` | 2026-09-14 | 1 | 0 | ok=1 bad=0 | +| 15 | `6d8aa95504bb` | `f98d7347b8b3` | 2026-09-15 | 1 | 0 | ok=1 bad=0 | +| 16 | `21c1e268489d` | `9449557495b3` | 2026-09-15 | 1 | 0 | ok=1 bad=0 | +| 17 | `30d7bf7dc109` | `f9e63f44dc37` | 2026-09-15 | 6 | 5 | ok=6 bad=0 | +| 18 | `6a9f75c6f1a9` | `4c2ffefdec64` | 2026-09-16 | 1 | 0 | ok=1 bad=0 | +| 19 | `219184864a87` | `4218e6dacdaf` | 2026-09-23 | 1 | 0 | ok=1 bad=0 | +| 20 | `b4e1cb7a7797` | `a7d125716829` | 2026-09-30 | 9 | 8 | ok=9 bad=0 | +| 21 | `4fe91d309f75` | `cbb59813ceea` | 2026-09-30 | 3 | 3 | ok=3 bad=0 | +| 22 | `ac36505644f4` | `6faed35f418c` | 2026-10-02 | 1 | 0 | ok=1 bad=0 | +| 23 | `525a95dd752d` | `c1bac0dfc99a` | 2026-10-03 | 1 | 0 | ok=1 bad=0 | +| 24 | `03818f50c581` | `14641fd67cee` | 2026-10-03 | 1 | 0 | ok=1 bad=0 | +| 25 | `993d1e1d01c9` | `daed20957f2d` | 2026-10-04 | 1 | 0 | ok=1 bad=0 | +| 26 | `fa21860f9063` | `b1015f4672f9` | 2026-10-04 | 12 | 5 | ok=12 bad=0 | +| 27 | `705058619ba5` | `fea9a2904ce0` | 2026-10-05 | 1 | 0 | ok=1 bad=0 | +| 28 | `75ada176939b` | `00971b929f58` | 2026-10-06 | 18 | 10 | ok=18 bad=0 | +| 29 | `2374d2ec8f8f` | `f5444c5e1f3c` | 2026-10-06 | 1 | 0 | ok=1 bad=0 | +| 30 | `0c2b499dc1b4` | `2080f3d83520` | 2026-10-06 | 1 | 0 | ok=1 bad=0 | +| 31 | `74e48d3b605b` | `500687c4b746` | 2026-10-06 | 1 | 0 | ok=1 bad=0 | +| 32 | `659087420e44` | `5eb3d5f816f8` | 2026-10-06 | 1 | 0 | ok=1 bad=0 | +| 33 | `c9b870270cf5` | `2fb854f12855` | 2026-10-06 | 1 | 0 | ok=1 bad=0 | +| 34 | `558a12b67400` | `c4864ac5e59d` | 2026-10-07 | 1 | 0 | ok=1 bad=0 | +| 35 | `593250d600ca` | `44983ce250ab` | 2026-10-08 | 20 | 20 | ok=20 bad=0 | + +**Per document.** These are the 51 files under `openspec/` and `specs/` at +`593250d`. They were computed with plain `git log -- `. `--follow` falsely +linked the tiny `.openspec.yaml` files across changes. + +| documents | files | introduced (spec <- original) | last changed (spec <- original) | +|---|---|---|---| +| `openspec/changes/add-crash-consistent-lane-worktree-recovery/` | 5 | `fa21860<-b1015f4` | `fa21860<-b1015f4` | +| `openspec/changes/add-supervised-context-restart/` | 6 | `75ada17<-00971b9` | `75ada17<-00971b9` | +| `openspec/changes/interactive-lane-name-repair/` | 5 | `30d7bf7<-f9e63f4` | `30d7bf7<-f9e63f4` | +| `openspec/changes/migrate-to-triad/` | 6 | `593250d<-44983ce` | `593250d<-44983ce` | +| `openspec/changes/skip-no-lane-guard/` | 5 | `b4e1cb7<-a7d1257` | `b4e1cb7<-a7d1257` | +| `openspec/config.yaml` | 1 | `593250d<-44983ce` | `593250d<-44983ce` | +| `specs/002-skip-no-lane-guard/` | 3 | `b4e1cb7<-a7d1257` | `b4e1cb7<-a7d1257` | +| `specs/003-binary-only-guard-runner/` | 3 | `4fe91d3<-cbb5981` | `4fe91d3<-cbb5981` | +| `specs/004-migrate-to-triad/` | 13 | `593250d<-44983ce` | `593250d<-44983ce` | +| `specs/005-supervised-legacy-ctx/` | 4 | `75ada17<-00971b9` | `75ada17<-00971b9` | + +**Where cited hashes resolve.** The spec documents at `593250d` contain 85 +distinct hex tokens. Each was resolved against the assembly's history (run C's +source, `main` only) and against the original checkout's branches: + +| resolves | tokens | examples | +|---|---|---| +| assembly `main` history | 12 | `daed209`, `b1015f4`, `14641fd`, `82ecebe`, `c1bac0d`, `f5444c5` | +| original repository's branches or PR objects only | 35 | `3c26041` (001), `5164ce1`/`b20882a` (`feat/supervised-context-restart`), `11037f0` (`feat/claude-current`), `dda91f1`/`52ce64e` (004 planning branch), `64f4e93`/`708395e` (no local branch: PR heads) | +| openRepoShape | 2 | `39d5c98`, `91d5685` | +| not an openRepoTools commit | 36 | session uuids, pids, dates, examples (`abc1234`), commits of other repositories | + +None of them resolves in the spec leg, whose history filter-repo rewrote. All +47 openRepoTools citations resolve in `opensoft/openRepoTools`, which keeps its +whole history and its branches because the adoption is in place. So the +recorded citations stay valid as written and are not rewritten. The spec +`AGENTS.md` says so. + +One consequence: a citation of a squash-merged branch commit resolves only +while that branch or its PR ref survives. For example, feature 004's +`dda91f1` and `52ce64e` predate the #187 squash. + +## 7. Bootstrap, validators, OpenSpec and Speckit: results + +Every check ran against a fresh clone of my bare copies, with +`-c protocol.file.allow=always`. On git 2.43.0 the file transport is refused +without that flag: on run C, plain `submodule update` exits 1 and the flagged +one exits 0. + +| check | before (split `69b8924`) | after (c2 `3e87e39`) | +|---|---|---| +| `python3 scripts/bootstrap.py` | exit 0; legs on `main` at their pins | exit 0; spec `39c1887`, code `6169e8e`, both "branch tip == pin" | +| `make validate` (naming, manifest, pins) | exit 0 | exit 0. Gitlinks equal their pins, tree digests recompute (spec `3a4a84be…`, code `697a080f…`), all 11 copied shape files match | +| `bump-leg.py --dry-run`, then the real bump (spec, code) | — | exit 0 ×4; the tool's own validators "pins ok" | +| `shape-doctor.py` | exit 1, `MISPLACED (3 paths)` (pre-existing; section 4) | exit 1, the same `MISPLACED (3 paths)`; `leg shape files` note down to 1 missing file | +| `setup-openspeckit --shape on --integration codex --base-branch main --no-global-agent-pointers`, in a disposable copy with an isolated HOME and agent root | — | exit 0. **Line 1 unchanged**; the managed block landed before the first `## ` heading (line 72, before line 94), as the protocol prescribes for an adopted root; `git-config.yml` reads `checkout_mode: worktree`, `base_branch: main`, `worktree_root: worktrees`; ".gitignore unchanged" (`/worktrees/` already present); the spec leg gains `openspec/README.md`, `changes/archive/.gitkeep` and `specs/.gitkeep`, and its tracked `openspec/config.yaml` is kept. No file in the real agent root is newer than the run. The only workBenches files newer than it are another writer's QA evidence under `sysBenches/cloudBench/devcontainer.example/tmp/qa1-*`; the bootstrap writes only to its `--repo`. | +| paired selection: the overlay's `create-new-feature.sh --number 4 --short-name migrate-to-triad` (dry run, then real) | — | exit 0 ×2. `004-migrate-to-triad` branched in both legs from `origin/main`; worktrees at `worktrees/004-migrate-to-triad/{spec,code}`; `.specify/feature.json` = `{"feature_directory":"worktrees/004-migrate-to-triad/spec/specs/004-migrate-to-triad"}`; `git status --ignored` at the root shows only `!! worktrees/` | +| `.specify/scripts/bash/check-prerequisites.sh --json --paths-only` from the root | — | exit 0; `BRANCH 004-migrate-to-triad`, `FEATURE_DIR worktrees/004-migrate-to-triad/spec/specs/004-migrate-to-triad` | +| `$SPECKIT_GIT_WORKTREE_ROOT=wt-override` (dry run) | — | exit 0; `WORKTREE_PATH wt-override/009-probe`. The override wins, but a root other than `worktrees/` is **not** covered by the root's ignore line, so point it at `worktrees/` or outside the root. | +| `openspec validate migrate-to-triad --strict` in the **spec leg** (`main`, `39c1887`) | — | exit 0, "Change 'migrate-to-triad' is valid" | +| the same in the feature's **spec worktree** | — | exit 0, valid | +| `openspec validate --changes --strict` in the spec leg | — | exit 0, 5 passed, 0 failed | +| the same command from the **assembly root** | — | exit 1, "Unknown item"; `openspec context`: "No OpenSpec root found" | +| store precedence (a disposable spec clone registered as store `ort-spec-main` in the isolated config) | — | spec worktree without `--store` resolves `source: nearest`, the worktree itself (**local root wins**). With `--store` it resolves `source: store`, the store clone (explicit selection overrides, so check where it points). The assembly root without `--store` gives exit 1, naming the store rather than picking it; with `--store` it is valid. | +| `git diff --check`, every patch range | — | exit 0 ×3 | +| code-leg hygiene subset, `PATH=/usr/bin:$PATH code/tests/run.sh -k repo_hygiene` (serialized: it waited for a live suite, then held the lock) | 17 failed, 135 passed | 13 failed, 139 passed (exit 1 both times; section 10, finding 6) | + +**Tool versions on this bench, 2026-10-08:** +- git 2.43.0; `/usr/bin/openspec` 1.6.0; `specify` 0.12.12.dev0. +- `setup-openspeckit` is now a shim that runs workBenches' `devBenches/base-image/files/openspeckit/setup-openspeckit` (last changed in d86ba59, #140). Under an isolated HOME it needs `WORKBENCHES_ROOT`. +- The `openspec` first on PATH (`~/.npm-global/bin`, 1.2.0) has no `--store`. The checks above name `/usr/bin/openspec`. +- `python3` on PATH has no pytest, so locked runs need `PATH=/usr/bin:$PATH`. +- `~/.agents` is a symlink; an isolated copy must dereference it (`cp -rL`). `cp -a` copies the link, and that copy then writes to the real agent root. + +## 8. `.specify/` today, and what the triad root needs + +**Today, on `main`'s shared checkout:** +- `.specify/` is local and ignored. It is listed in `.git/info/exclude` (`/.specify/`, beside `.claude/`, `/.agents/`, `/.codex/` and `/handoffs`), not in the tracked `.gitignore`. +- Its values are machine-local: integration `codex`; Speckit `1.0.7.dev0`; `git-config.yml` with `worktree_root: ../openRepoTools-worktrees` (the single-repository layout); and a `feature.json` pointing into a sibling worktree. +- No agent file in this repository's history has ever carried the bootstrap's managed block (`git log -S'OPENSPEC-SPECKIT-GLOBAL'`: none). + +**What the shape and protocol prescribe for a three-leg root:** +- `.specify/` at the root; +- `git-config.yml` with `worktree_root: worktrees`; +- `/worktrees/` in the root's own `.gitignore`; +- paired worktrees `worktrees//{spec,code}`; +- `$SPECKIT_GIT_WORKTREE_ROOT` as an override. + +**The root scaffolding patch carries only `/worktrees/`** (plus the shape's +two ignore lines), and the bootstrap leaves it unchanged. It copies no +machine-specific configuration or secret: `feature.json`, `init-options.json`, +`integration.json`, `git-config.yml` and the skill links are all regenerated +by the bootstrap on each workstation. + +It deliberately does **not** commit `.specify/`, the managed blocks, or the +twenty generated agent files. That keeps today's posture: local scaffolding +excluded per clone. The in-place checkout keeps its `.git/info/exclude` +through the adoption. On a fresh clone elsewhere these files show as untracked +after the bootstrap, exactly as they do on a fresh clone of today's +monorepo. Changing that posture, by ignoring them in the tracked +`.gitignore` or by committing `.specify/`, is Brett Heap's decision and is +not made here. The root `AGENTS.md` map carries what the managed block would +otherwise say about the split. + +## 9. Distribution is not bootstrap + +Running the shape-aware bootstrap in a root writes that root's scaffolding. +It does not distribute the October 4 manual decision ("Triad Feature +Amendments") to any workstation. + +The installed `project-agent-bootstrap.md` says so itself: "Existing global +protocol files are written only when missing … Do not run a whole bootstrap +merely to apply this manual decision". The fixture run confirms it: it logged +"kept existing" for the agent root's `AGENTS.md` and both protocol files. + +On this workstation (Eagle) the installed protocol files carry the "Triad +Feature Amendments" section. Their sha256 (`f9dd6464…`, `bbbc2b2c…`) are newer +than the October 4 snapshots recorded in protocol-review.md. Nothing here +speaks for any other workstation. + +What workBenches delivery would carry to each target workstation: +- the updated `openspec-speckit-workflow.md` and `project-agent-bootstrap.md`; +- the amendment documents and templates (`docs/triad-feature-amendments.md`, `docs/triad-feature-amendment-templates.md`); +- the opsx command and Speckit skill guidance with its shape blocks; +- the `setup-openspeckit` installer and the speckit-worktree overlay templates in the bench image. + +Each of these is verified per workstation through workBenches' own delivery, +under its governing `openspec/changes/adopt-triad-feature-amendments/`. +Lane 2 is reading that side. + +## 10. Findings for the other writers + +1. **T010.** The root `.github/workflows/validate.yml` has a row in `contracts/shape-pin.yaml` (sha256 `fb8c0360…`). A job added there is drift, and `validate-pins.py` will refuse it. The root integration job needs its own, unpinned workflow file. +2. **Legs lose their front door.** Neither leg has `AGENTS.md`, `CLAUDE.md`, `README.md`, `LICENSE`, `.gitattributes` or `.gitignore` after the split. The root's attributes never reach a submodule, so the code leg loses the LF rule behind its installer's byte-for-byte comparisons. `code-0001` and `spec-0001` restore them. +3. **Root README anchor.** `contracts/{spec,code}-pin.yaml` name `README.md#the-lockstep-invariant`, a section only `shape/README.md` had. `assembly-0001` adds it. +4. **`shape/` copies.** Keep `shape/.gitignore` and `shape/.gitattributes` (pinned). `shape/AGENTS.md`, `shape/CLAUDE.md` and `shape/README.md` are unpinned, and the shape's own follow-up says to delete the first two. Their deletion is left to T014's reviewer. +5. **Doctor placement.** `MISPLACED (3 paths)` (`code/commands/`, `code/contracts/`, `code/skills/`) is the adoption plan's reasoned overrides, identical before and after. Relay it; do not move files. +6. **T009: the hygiene tests read the wrong front door after the split.** + - 17 test functions read `README.md`, `AGENTS.md`, `CLAUDE.md`, `.gitattributes` or `docs/README-lanes.md` at the **code leg's** root: 16 in `test_repo_hygiene.py`, 1 in `test_openrepotools_command.py`. + - Measured on the code leg alone: before `code-0001`, 17 failed (all `FileNotFoundError`). After it, 13 fail (4 `FileNotFoundError` on `docs/README-lanes.md`, 9 assertions on the short leg README and AGENTS). + - **4 now pass only because they read the leg's own files:** `test_agents_md_is_short_enough_to_be_read`, `test_readme_is_short_enough_to_be_read`, `test_claude_md_points_at_agents_md` and `test_the_root_carries_the_line_ending_rule`. A green there is about the wrong file. + - Pointed back at the assembly root, the two length caps (`<= 316`, `<= 486`, today's counts) must rise to 340 and 510, each in a dated entry naming the rule its lines buy: + - AGENTS.md: the shape's first-line pointer and the triad map the bootstrap protocol requires. + - README.md: the lockstep-invariant anchor the leg pins name, and the three-repository front door. + - `test_lane_helpers.sh`:3738 and :6228 read the manual through `cat … || :`, so a missing file reads as empty rather than failing. +7. **Lane 3 (lane tooling).** + - `lanes-edit.sh` and `lane-handoff` key an OpenSpec claim as `owner/repo:openspec/changes/`; the triad's changes live in `opensoft/openRepoTools-spec`. + - The manual has 21 lines naming code-leg files (section 5). + - `lane-worktrees`' and `lanes-edit.sh`'s lane roots (`.claude/worktrees`, `.lane-worktrees/`) do not include `/worktrees//{spec,code}`. + - `repos.tsv` has no `openRepoTools-spec` or `openRepoTools-code` aliases. + - The installed handoff skill's writer-poll globs miss `/worktrees/`. + - The checkout's `/handoffs` symlink and `.git/info/exclude` persist through the in-place adoption. +8. **T017.** Feature 004's own working files in the spec leg carry 23 pre-split path mentions. Some cited 004 commits (`dda91f1`, `52ce64e`) resolve only while the 004 branch or #187's PR ref survives. +9. **T007.** `assembly-0001`'s README sentence assumes T007's design: the root `openRepoTools` hands over to `code/openRepoTools` at the pinned commit. `code/openRepoTools`:1218 (`venv_note`) still cites `docs/README-lanes.md`. + +## 11. What this task did not do + +- It patched no executable, test, workflow or lane-tooling file in the code leg. Lane 3, T007, T009 and T010 own those. +- It did not edit `docs/README-lanes.md`'s content (lane 3), any external referrer, any pinned shape copy, or any approval record. +- It did not delete the three unpinned `shape/*.md` copies (T014's reviewer decides). +- It did not commit `.specify/`, managed blocks or generated agent files. +- It did not change the `.specify/` posture. +- It did not run the full suite. Only the serialized `repo_hygiene` subset ran. +- It did not write anything into the rehearsal directory or any real remote except this evidence branch. + +## 12. Nothing lands until the real legs exist + +These patches apply only to run C's rehearsed arrangement. Today's layout on +`main` is unchanged. At T014, after Gate C and the real `execute`, the +portable patches (`assembly-0001`, `code-0001`, `spec-0001`) are applied +through the real leg and root pull requests. The pin bumps are then +regenerated with `bump-leg.py` against the real leg commits, and the +reviewer settles the `shape/*.md` deletion and the hygiene cap raises with +T009.