From bfbd4eeb5f67140bddbdd52476b805392890eea9 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Mon, 12 Oct 2026 23:08:56 +0000 Subject: [PATCH 1/5] README.md and AGENTS.md: 25 statements the v1.0.0 accuracy pass found false are corrected to what the tools do on main (#483) Three audits read README.md and AGENTS.md line by line against the code at ec4a7a0 and ran the tools offline against local bare remotes. This corrects the statements no open pull request edits, to what the code and the runs showed, and leaves the rest to the pull request that edits the same lines (#491, #484, #468, #465) or makes the sentence true (#490). AGENTS.md: `execute`, not the preflight, prints the install command for git-filter-repo (the preflight does not ask about it); the seeded leg's verification row has no colon; a descendant may pin a layer and record the chain; an installed `openRepoShape --doctor` runs from a temporary clone it deletes on exit and takes a path and nothing else, so `--json`, `--placement-plan` and a row's next command are for `./shape-doctor.py` from a checkout; the doctor's next commands are `update-shape.py check`, a `git submodule update` and a holder's `scripts/bootstrap.py`, never `bump-leg.py`. README.md: step 4's preflight offers `gh` and its login and not `git`, and `openRepoShape --preflight` exits 2 when `git` or Python 3.9 is missing; `--visibility` defaults to `private`; the rehearsal leaves the scaffold's working trees in a temporary directory; a lowercase name passes the naming check; the workspace form is lowercase too; the credential rewrite covers HTTPS and scp-form SSH, and `SHAPE_LEGS_TOKEN` is not read while the App secrets are set; the one-name chain command needs `--pins`, and a bare `--link-source` is looked up last; adopt's follow-ups name at most eight files per directory and the topic is not a change to the source's history; the `sorted-ls-tree-r-v1` records are each followed by LF; `check` has four more verdicts and a `both` file has its `--accept-local` exit; the doctor's rows do not all name a command, `COMPLIANT` means no FINDING, SHAPE BEHIND and DRIFTED have more spellings, and usage errors exit 2; bare `park` asks and `park --all` sweeps. docs/handbook.html repeats those sentences and is corrected where it does, with the footer's `README.md blob` recomputed. No tool, test, template or docs/cli.md changed. Both files sit exactly at the line caps tests/test_repo_hygiene.py holds them to (AGENTS.md 482, README.md 1422), so each correction is line-neutral: four paragraphs of each file that no correction touches are re-wrapped, with no word changed, to give the lines back. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 124 ++++++++-------- README.md | 350 ++++++++++++++++++++++----------------------- docs/handbook.html | 128 ++++++++++------- 3 files changed, 315 insertions(+), 287 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 31fee71f..e876b1ef 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -115,11 +115,11 @@ the three names, `scaffold-project.py --dry-run`, an explicit human yes, ## Adopting an existing repository -`adopt-project.py` converts a repository that already exists, IN PLACE: it -keeps its name, its identity and its full history and becomes the assembly -root, and the two legs are new repositories extracted with `git filter-repo` -(a hard dependency; the preflight prints the install command). Seven steps, in -order, and step 4 is the one you must not skip. +`adopt-project.py` converts a repository that already exists, IN PLACE: it keeps +its name, its identity and its full history and becomes the assembly root, and +the two legs are new repositories extracted with `git filter-repo` (a hard +dependency: `execute` refuses without it, printing the install command). Seven +steps, in order, and step 4 is the one you must not skip. ```sh ./adopt-project.py plan --source --project @@ -173,14 +173,15 @@ legitimate thing to adopt. plan that lost its code paths to a bad edit is indistinguishable from a repository that has no code. Do not pass it because the tool asked for it; pass it because the human confirmed the repository genuinely has none. -2. Everything else is unchanged, step 7 included: the verification table - reads `code: 0 of N source paths (seeded from template)` and still - accounts for every source path by blob sha. +2. Everything else is unchanged, step 7 included: the verification table's + `code` row reads `0 of N source paths (seeded from template)`, and the table + still accounts for every source path by blob sha. ## Scaffolding a project that DECLARES descent -`MedxGlass` descends from `openGlass` only if it PINS it (2026-09-02). Pass -the pin and the scaffold records the descent and still gives it two legs: +`MedxGlass` descends from `openGlass` only if it PINS it, or pins a layer that +does and records the chain (`--referent-chain`; 2026-09-02, chains 2026-09-05). +Pass the pin and the scaffold records the descent and still gives it two legs: ```sh ./scaffold-project.py --org MedxSoft --project MedxGlass \ @@ -202,34 +203,37 @@ the tool, not scaffold. ## Checking a repository's compliance -`openRepoShape --doctor []` — or `./shape-doctor.py --root ` from -a checkout — answers ONE question about ONE repository: is it compliant with -this shape, and what is missing. Run it before you propose a change to a -repository you did not scaffold, and again after any procedure above. It -writes nothing into the repository and fetches nothing, so there is no -confirmation to get and no `--dry-run` to do first. The checks are a REGISTRY with an empty `fix` slot +`openRepoShape --doctor []` — or `./shape-doctor.py --root ` from a +checkout — answers ONE question about ONE repository: is it compliant with this +shape, and what is missing. Run it before you propose a change to a repository +you did not scaffold, and again after any procedure above. It writes nothing +into the repository and fetches nothing, so there is no confirmation to get and +no `--dry-run` to do first. The checks are a REGISTRY with an empty `fix` slot in each of them, which is where a repair mode will hang; nothing in it writes -today. The one exception to "writes nothing" is `--placement-plan `, -which writes that file and nothing else — see 4. +today. The one exception to "writes nothing" is `--placement-plan `, which +writes that file and nothing else — see 4. Installed, `openRepoShape --doctor` +runs from a temporary clone of the standard, deleted on exit, and takes a path +and nothing else: to ACT on a row (`--json`, `--placement-plan`, its next +command) run `./shape-doctor.py --root ` from a checkout. 1. **Read the verdict, then read the rows.** `COMPLIANT`, `COMPLIANT, SHAPE - BEHIND`, `DRIFTED`, `MISPLACED`, `INVALID`, `NOT A SHAPE ROOT` — exits 0, - 1, 1, 1, 1, 2 — and `CANNOT ANSWER`, exit 3, which is THIS CHECKOUT - failing to ask, never - a statement about their repository. The verdict names the MOST SPECIFIC - finding and the TABLE names every one, so a report summarised from the - verdict line alone is a report you did not read. Relay the rows. + BEHIND`, `DRIFTED`, `MISPLACED`, `INVALID`, `NOT A SHAPE ROOT` — exits 0, 1, + 1, 1, 1, 2 — and `CANNOT ANSWER`, exit 3, which is THIS CHECKOUT failing to + ask, never a statement about their repository. The verdict names the MOST + SPECIFIC finding and the TABLE names every one, so a report summarised from + the verdict line alone is a report you did not read. Relay the rows. 2. **`note` is not a finding and you do not "fix" it.** A row says `note` when it found a difference nothing asserts — a leg file no pin names, a rendered `AGENTS.md`. It never moves the verdict. Report it; propose a change only if the human asks. 3. **A FINDING names its fix, and you run THAT.** Every one carries the exact - next command — `update-shape.py apply …`, `make bootstrap`, `bump-leg.py - …`, the project's own validator. Run the one the row names. Do not - improvise a repair, and never edit a pinned file, a digest or a manifest - to make a row go green: that is the hand-edited pin this standard spends a - section refusing. Each of those commands has its own section above and its - own refusals, and they all still apply. + next command — `update-shape.py check …` and the `apply` it leads to, `git + submodule update …` (in a holder, its `scripts/bootstrap.py`), the project's + own validator. Run the one the row names. Do not improvise a repair, and + never edit a pinned file, a digest or a manifest to make a row go green: that + is the hand-edited pin this standard spends a section refusing. Each of those + tools has its own section in this file and its own refusals, and they all + still apply. 4. **`placement` says what is in the wrong leg, and you never move a file to answer it.** It runs the adoption's own path policy over each mounted leg and names the paths it would have put elsewhere — code in the spec @@ -250,13 +254,12 @@ which writes that file and nothing else — see 4. which of them is wanted is a fact about their repository. Report the verdict and both commands; adopt or scaffold nothing on your own initiative. There is no `--force`, deliberately. -6. **The `machine` row is not about the repository.** It is `n/a` by status - and never changes the verdict: it says whether THIS workstation can run - the fixes the other rows named. Relay a missing prerequisite; - `openRepoShape --preflight` is what offers to install one, and the human - types that yes. -7. `--json` is the same report as one object, each row carrying its `id`, for - when you have to act on a row rather than quote it. +6. **The `machine` row is not about the repository.** It is `n/a` by status and + never changes the verdict: it says whether THIS workstation can run the fixes + the other rows named. Relay a missing prerequisite; `openRepoShape + --preflight` is what offers to install one, and the human types that yes. +7. `./shape-doctor.py --json` is the same report as one object, each row + carrying its `id`, for when you have to act on a row rather than quote it. ## Updating a project's shape @@ -380,29 +383,27 @@ python3 scripts/family.py siblings --family-root 2. **A family pins ASSEMBLY ROOTS, never legs.** `--member /`, never `-spec`. The tool refuses a leg, and the refusal is correct: a leg has no `project.yaml` and belongs to its own root. -3. **Each of `add`, `bump` and `remove` writes ONE commit** moving the - gitlink and the pin together. Land it as a pull request; never suggest a - push to the default branch. Do not hand-edit `family.yaml`'s `members:` - block — the tool rewrites it wholesale. `bump` takes the same repeatable - `--trailer ": "` as `update-shape.py apply`, for the lines its - commit must end with. +3. **Each of `add`, `bump` and `remove` writes ONE commit** moving the gitlink + and the pin together. Land it as a pull request; never suggest a push to the + default branch. Do not hand-edit `family.yaml`'s `members:` block — the tool + rewrites it wholesale. `bump` takes the same repeatable `--trailer ": + "` as `update-shape.py apply`, for the lines its commit must end with. 4. **The member must be scaffolded or adopted FIRST.** A family cannot make a project out of a repository; it can only pin one that already is. 5. **`make siblings` in the holder — or `family.py siblings --family-root - `, the same file — places the WORKSTATION layout**: each member - cloned BESIDE the holder, in the family folder, on its tracking branch, - with its own bootstrap run. Two copies of every member is intended: - `members/` inside the holder is pinned and DETACHED, for - `bootstrap` and `validate`; the sibling is where the human works. - **What it never does, and neither do you:** it MOVES NOTHING — when the - parent folder is not named after the family it warns and prints an `mv`, - and running that `mv` yourself relocates a checkout out from under a - shell, an editor and every agent lane, and breaks any linked worktree - (its `.git` file carries an absolute path). It also never touches an - existing clone beyond `git fetch`: no checkout, no reset, no pull, - because somebody is working in there. A directory that is a clone of a - different repository is reported and skipped; the exit is the human's - hand, not an overwrite. + `, the same file — places the WORKSTATION layout**: each member cloned + BESIDE the holder, in the family folder, on its tracking branch, with its own + bootstrap run. Two copies of every member is intended: `members/` + inside the holder is pinned and DETACHED, for `bootstrap` and `validate`; the + sibling is where the human works. **What it never does, and neither do you:** + it MOVES NOTHING — when the parent folder is not named after the family it + warns and prints an `mv`, and running that `mv` yourself relocates a checkout + out from under a shell, an editor and every agent lane, and breaks any linked + worktree (its `.git` file carries an absolute path). It also never touches an + existing clone beyond `git fetch`: no checkout, no reset, no pull, because + somebody is working in there. A directory that is a clone of a different + repository is reported and skipped; the exit is the human's hand, not an + overwrite. 6. `update-shape.py` updates a family root exactly as it updates a project, and the same four refusals apply. It mirrors into `family.yaml`. An existing holder receives `scripts/siblings.py` that way: `check` reports @@ -423,11 +424,10 @@ make park # then leave make bootstrap && make resume # on the other machine, after a clone ``` -1. **The overlay is a PREREQUISITE and the refusal names it.** Both targets - exit 2 with `install it with: setup-openspeckit` when - `.specify/extensions/git/` is not there. Relay that line; never hand-roll - the WIP commit, the `git worktree add` or the soft reset yourself because - a target refused. +1. **The overlay is a PREREQUISITE and the refusal names it.** Both targets exit + 2 with `install it with: setup-openspeckit` when `.specify/extensions/git/` + is not there. Relay that line; never hand-roll the WIP commit, the `git + worktree add` or the soft reset yourself because a target refused. 2. **`resume` refusing on DIVERGENCE is not something to work around.** The remote tip is not the commit that was parked, which means somebody's work is on that branch; the refusal names both commits and the commands to diff --git a/README.md b/README.md index 5465d135..6252b58b 100644 --- a/README.md +++ b/README.md @@ -172,15 +172,16 @@ run the four steps below INSIDE the WSL shell. > [!NOTE] > **The tool offers the rest.** The four steps below are the reference — what > each platform needs, and how to install it by hand. You do not have to: step 4 -> opens with a preflight that names what is missing, prints the command for THIS -> machine and asks once — `Type yes to continue:`, the same words it asks -> everything else with — then runs it and carries on. Nothing is installed -> without that typed `yes`; `--yes` does not answer it, and with no terminal -> attached there is no offer at all. Homebrew is the one install never run for -> you: on a Mac without `brew` the preflight names and stops -> there. To check a machine and stop, creating nothing, run -> `openRepoShape --preflight` — `py setup-project.py --preflight` on Windows — -> which exits 0 when everything is present and 1 when it is not. +> opens with a preflight that names what is missing and, for `gh` and its login, +> prints the command for THIS machine and asks once — `Type yes to continue:`, +> the same words it asks everything else with — then runs it and carries on. +> Nothing is installed without that typed `yes`; `--yes` does not answer it, and +> with no terminal attached there is no offer at all. Homebrew is the one +> install never run for you: on a Mac without `brew` the preflight names +> and stops there. To check a machine and stop, creating +> nothing, run `openRepoShape --preflight` — `py setup-project.py --preflight` +> on Windows — which exits 0 when everything is present and 1 when it is not +> (`openRepoShape --preflight` exits 2 when `git` or Python 3.9 is missing). **1. Install the GitHub CLI**, `gh`. The official instructions are at (and ): @@ -211,13 +212,13 @@ sudo dnf config-manager addrepo --from-repofile=https://cli.github.com/packages/ sudo dnf install gh ``` -`git` and Python 3.9 or newer are needed too (see Requirements below). -Ubuntu under WSL2 ships both, so there `gh` is the only install; step 4 -opens with a preflight that names anything missing and where to get it. -`git` itself comes from `xcode-select --install` on macOS, `sudo apt-get -install -y git` on Debian and Ubuntu, `sudo dnf install -y git` on Fedora and -`winget install --id Git.Git -e` on Windows — the same commands that preflight -offers to run for you. +`git` and Python 3.9 or newer are needed too (see Requirements below). Ubuntu +under WSL2 ships both, so there `gh` is the only install; step 4 opens with a +preflight that names anything missing and where to get it. `git` itself comes +from `xcode-select --install` on macOS, `sudo apt-get install -y git` on Debian +and Ubuntu, `sudo dnf install -y git` on Fedora and `winget install --id Git.Git +-e` on Windows — the same commands `setup-project.py --preflight` offers to run +for you. **2. Log in.** `gh auth login`, as the account that will own the act — a member of the organisation who can create repositories there. Accept the @@ -266,12 +267,12 @@ for, refusals and all. openRepoShape --org ``` -`` is one positional CamelCase name (e.g. `Atlas`); `--org` is -required — from the flag, `$OPENREPOSHAPE_ORG`, or the command's own prompt -— and add `--visibility private` if you want that. It shows the plan and -asks once, `Type yes to continue:`; type `yes`. Afterwards `./` is -the cloned root. The worked example below walks through what this produces, -prompt by prompt. +`` is one positional CamelCase name (e.g. `Atlas`); `--org` is required +— from the flag, `$OPENREPOSHAPE_ORG`, or the command's own prompt — and +`--visibility` defaults to `private`: add `--visibility public` (or `internal`) +if you want that. It shows the plan and asks once, `Type yes to continue:`; type +`yes`. Afterwards `./` is the cloned root. The worked example below +walks through what this produces, prompt by prompt. **Joining a family.** `--family ` lands the clone at `/` instead of `` — the plain folder that holds a @@ -300,17 +301,17 @@ run to read before an organisation is involved. It still reaches GitHub before the run starts — to fetch `setup.sh`, then for the self-bootstrap clone of this standard into a temporary directory; run the same flag on `./setup.sh` from a checkout instead and nothing touches the network at all. On Windows the same -flag goes on the Python entry point — -`py setup-project.py Atlas --org --local-remote-dir .\rehearsal` — -which, run from a download, still clones the standard into a temporary -directory first, and from a checkout clones nothing. What it leaves behind is -`./Atlas` and three bare repositories in `./rehearsal`, both of which you can -delete. `python3 setup-project.py --help` and, from a checkout, `setup.sh --help` -list every flag, this one included. From a download, `setup.sh --help` clones -nothing and lists none: it names `setup-project.py --help`, which answers -offline from that one file too, and [`docs/cli.md`](docs/cli.md), every -tool's help and every subcommand's in one file, generated from the tools -themselves. +flag goes on the Python entry point — `py setup-project.py Atlas --org + --local-remote-dir .\rehearsal` — which, run from a download, still +clones the standard into a temporary directory first, and from a checkout clones +nothing. What it leaves behind is `./Atlas`, three bare repositories in +`./rehearsal` and the scaffold's three working trees, in the temporary directory +its `Working trees are at` line names, all of which you can delete. `python3 +setup-project.py --help` and, from a checkout, `setup.sh --help` list every +flag, this one included. From a download, `setup.sh --help` clones nothing and +lists none: it names `setup-project.py --help`, which answers offline from that +one file too, and [`docs/cli.md`](docs/cli.md), every tool's help and every +subcommand's in one file, generated from the tools themselves. Without installing anything — no fork, no manual clone — one command: @@ -435,7 +436,7 @@ human says it rather than lets it default. how bootstrap will run, and that `gh` is authenticated — and, for anything missing, an offer to install it that Dana answers `yes` to. (2) The three names checked against the naming policy — `Atlas`, `Atlas-spec`, `Atlas-code`; a -hyphenated or lowercase name is refused here with the rule printed. (3) The +hyphenated name is refused here with the rule printed. (3) The scaffold plan, a dry run of everything about to be created. (4) `This will create THREE repositories in 'Northwind'.` and `Type yes to continue:` — Dana types `yes`. If an AI agent is driving, the agent runs the command WITHOUT @@ -567,13 +568,13 @@ flowchart LR The six naming families live in `contracts/repository-naming.yaml`: neutral products `open`, domain descendants ``, installs -`-Install`, project legs as above, family holders (below), and one form -that is no repository of a project at all — a person's own workspace -repository `-wip` (#81, and -[below](#carrying-in-flight-work-to-another-workstation)). The leg suffixes -are lowercase and hyphenated precisely so they sit in a different visual class -from every other family, all of which are CamelCase words. Every repository of -a project also carries the GitHub topic `xf-project-`. +`-Install`, project legs as above, family holders (below), and one form that +is no repository of a project at all — a person's own workspace repository +`-wip` (#81, and +[below](#carrying-in-flight-work-to-another-workstation)). The leg suffixes are +lowercase and hyphenated precisely so they sit in a different visual class from +every other family but `-wip`, all of which are CamelCase words. Every +repository of a project also carries the GitHub topic `xf-project-`. ### Reading private legs in CI: a GitHub App first, `SHAPE_LEGS_TOKEN` as fallback @@ -608,26 +609,26 @@ only to PUBLIC repositories, so on a private one `secrets.SHAPE_LEGS_APP_ID` is silently empty and `validate` goes GREEN with the pin check skipped. Set both per repository, or upgrade to Team; the creating tools check and say so. -The root repository is always readable by the workflow's own default token, -so `actions/checkout` never carries a `token:` override. Putting a legs -credential there was itself the next defect, on the first real use of the PAT -(MedxSoft/MedxEHR and MedxSoft/MedxGlass, runs 33821509948 and 33821512605): -a token correctly scoped to `contents:read` on the legs alone cannot read -the root, so `actions/checkout` itself failed with a 403 before any check -ran. Whichever credential resolves is read only inside the guarded "fetch the -legs (submodules)" step, scoped to that step's `env:`, and used through a -`git -c url.<...>.insteadOf=<...>` rewrite covering both HTTPS and SSH leg -URLs — never persisted onto the root checkout. - -`validate` tries the App first: a `mint a leg-reader token from the GitHub -App` step (`actions/create-github-app-token`, pinned by commit) runs when -both App secrets are present, scoped by `repositories:` to the legs -`.gitmodules` names that this repository's own owner also owns — an -installation token is per-owner, so a leg under a different owner is excluded -with a warning and can only be reached via `SHAPE_LEGS_TOKEN` — and falls back -to the PAT when the App is not configured. A configured App that fails to -mint is a misconfiguration, not a degrade, and fails the job outright, naming -both secrets and the required installation. +The root repository is always readable by the workflow's own default token, so +`actions/checkout` never carries a `token:` override. Putting a legs credential +there was itself the next defect, on the first real use of the PAT +(MedxSoft/MedxEHR and MedxSoft/MedxGlass, runs 33821509948 and 33821512605): a +token correctly scoped to `contents:read` on the legs alone cannot read the +root, so `actions/checkout` itself failed with a 403 before any check ran. +Whichever credential resolves is read only inside the guarded "fetch the legs +(submodules)" step, scoped to that step's `env:`, and used through a `git -c +url.<...>.insteadOf=<...>` rewrite covering both HTTPS and scp-form SSH +(`git@github.com:`) leg URLs — never persisted onto the root checkout. + +`validate` tries the App first: a `mint a leg-reader token from the GitHub App` +step (`actions/create-github-app-token`, pinned by commit) runs when both App +secrets are present, scoped by `repositories:` to the legs `.gitmodules` names +that this repository's own owner also owns — an installation token is per-owner, +so a leg under a different owner is excluded with a warning, and +`SHAPE_LEGS_TOKEN` is read only when the App is not configured, so that leg is +reachable only with the App secrets removed. A configured App that fails to mint +is a misconfiguration, not a degrade, and fails the job outright, naming both +secrets and the required installation. Without either credential the workflow still degrades instead of failing: it checks out the root without submodules, attempts `git submodule update --init @@ -678,22 +679,23 @@ is pinned by its descendants at the LAYER: `codexDox` pins `openXdox`, and family deliberately does not hold. The chain is RECORDED, never inferred — the descendant's own `naming.referent_chain: [openXdox, openDox]` names the pin it holds first and the referent its name claims last, and -`validate-repository-naming.py --referent-chain openXdox,openDox` asks the same -question for one name. A DIRECT PIN STAYS SUFFICIENT: this is an addition, and -`MedxChart` answers exactly as it did on 2026-09-02. - -Each link past the first is a declaration in ANOTHER tree — `openXdox` -declaring `neutral_product_pins: [openDox]` — and it is read where that tree -happens to be on the disk: `--link-source [PRODUCT=]`, -`SHAPE_PIN_SOURCE_`, or a checkout beside the root, the same lookup +`validate-repository-naming.py --pins openXdox --referent-chain +openXdox,openDox` asks the same question for one name. A DIRECT PIN STAYS +SUFFICIENT: this is an addition, and `MedxChart` answers exactly as it did on +2026-09-02. + +Each link past the first is a declaration in ANOTHER tree — `openXdox` declaring +`neutral_product_pins: [openDox]` — and it is read where that tree happens to be +on the disk: `--link-source PRODUCT=`, `SHAPE_PIN_SOURCE_`, a +checkout beside the root, then a bare `--link-source `: the same lookup order `validate-pins.py` uses below for the same checkout. A link no tree -answers for is reported `declared-unverified` — a warning, never a finding, -and the name still classifies — because the recorded declaration is the -offline fact and the tree is the stronger check, available or not. A link that -IS readable and declares something else BREAKS the chain: the residual -assembly-root reading wins, the overlap is recorded, and the message names the -link that broke. `LedgerxWallet` -> `openXwallet` -> `openWallet` is the same -shape, and `openWallet` need not exist anywhere for that to be the answer. +answers for is reported `declared-unverified` — a warning, never a finding, and +the name still classifies — because the recorded declaration is the offline fact +and the tree is the stronger check, available or not. A link that IS readable +and declares something else BREAKS the chain: the residual assembly-root reading +wins, the overlap is recorded, and the message names the link that broke. +`LedgerxWallet` -> `openXwallet` -> `openWallet` is the same shape, and +`openWallet` need not exist anywhere for that to be the answer. **`validate-pins.py` rechecks the referent too, not only the declaration.** Every `neutral_product_pins:` entry's `contracts/-pin.yaml` @@ -732,8 +734,8 @@ A repository that already exists is converted **in place**, by `adopt-project.py`, and this is a ruling (**Brett Heap, 2026-09-02**): it KEEPS ITS NAME, ITS IDENTITY AND ITS FULL HISTORY and becomes the assembly root, while `-spec` and `-code` are NEW repositories extracted with -history-preserving filters. The source is never deleted, never renamed and -never force-pushed; the only change to it is ONE split commit, arriving on a +history-preserving filters. The source is never deleted, never renamed and never +force-pushed; the only change to its history is ONE split commit, arriving on a branch by pull request — the organisations this serves are pull-request only, and a tool needing a bypass cannot be used where it is needed. @@ -759,15 +761,16 @@ drifts. | `.specify/`, `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, … , `.cursor/`, `.roo/` | **root** | project-level workflow tooling spanning both legs | | `examples/golden-run/` | **asked** | acceptance evidence the spec cites, or a test fixture? | -The contracts decision costs something, and the plan says so instead of -letting it be discovered later: `scripts/validate.py` reads `contracts/*.yaml` -from beside it and must now read ACROSS the root. `plan` finds every such file -with `git grep` and writes it into `follow_ups:`; the root Makefile it -materializes exports `CONTRACTS_DIR ?= $(CURDIR)/spec/contracts`. The tool -does not make the edit — naming it is the honest half, making it silently -would be the dangerous one. And `.specify/` and the ten identical assistant -stubs stay at the root because an assistant reading `CLAUDE.md` from inside -the code leg would be told about specifications it cannot see. +The contracts decision costs something, and the plan says so instead of letting +it be discovered later: `scripts/validate.py` reads `contracts/*.yaml` from +beside it and must now read ACROSS the root. `plan` finds every such file with +`git grep` and lists up to eight per directory in `follow_ups:`, counting the +rest; the root Makefile it materializes exports `CONTRACTS_DIR ?= +$(CURDIR)/spec/contracts`. The tool does not make the edit — naming it is the +honest half, making it silently would be the dangerous one. And `.specify/` and +the ten identical assistant stubs stay at the root because an assistant reading +`CLAUDE.md` from inside the code leg would be told about specifications it +cannot see. ### Ambiguous is an answer, and nothing is overwritten @@ -807,23 +810,22 @@ scaffold writes — and the split mounts and pins it like an extracted one. The plan records `seeding.code.seeded_from_template: true` and the template it would use; `check` WARNS and still passes, because a spec-only repository is a -legitimate thing to adopt; the verification table reads `code: 0 of N source -paths (seeded from template)` and still accounts for every source path by blob -sha. `execute` refuses without `--allow-empty-leg code`, in the plan or on its -own command line: a plan that lost its code paths to a bad edit looks -identical from here, so the consent is a human's word rather than an -inference. Where the recorded block and the entries disagree — somebody -answered an ambiguous path INTO the empty leg, which is the plan working — the -entries win and it is a note, not a refusal. +legitimate thing to adopt; the verification table's `code` row reads `0 of N +source paths (seeded from template)`, and the table still accounts for every +source path by blob sha. `execute` refuses without `--allow-empty-leg code`, in +the plan or on its own command line: a plan that lost its code paths to a bad +edit looks identical from here, so the consent is a human's word rather than an +inference. Where the recorded block and the entries disagree — somebody answered +an ambiguous path INTO the empty leg, which is the plan working — the entries +win and it is a note, not a refusal. ## Families: a holder for projects that ship separately Ruled by **Brett Heap on 2026-09-04**, about InkRouter: *"InkRouter is a set of -microservices and they deploy separately as api's. so maybe they each need -their own assembly repo. So probably InkRouter is only something that can -download all the others easily? like a holder folder and some utilities for -the family of services. Then IRRS would be assembly and IRRS-spec and -IRRS-code."* +microservices and they deploy separately as api's. so maybe they each need their +own assembly repo. So probably InkRouter is only something that can download all +the others easily? like a holder folder and some utilities for the family of +services. Then IRRS would be assembly and IRRS-spec and IRRS-code."* A **family** is a repository that pins other projects' **assembly roots** as submodules under `members/` and carries the utilities to fetch and bootstrap @@ -937,11 +939,10 @@ copy of without changing any existing name's answer. ## The double pin, and the lockstep invariant -Each leg is pinned TWICE, in the same commit: by the **gitlink** git records -for the submodule, and by **`contracts/-pin.yaml`** carrying -`revision_kind: commit`, the 40-hex commit, and a sha256 digest. A tag is never -the referent — a tag can be moved and a commit cannot — and a pin never -expresses a range. +Each leg is pinned TWICE, in the same commit: by the **gitlink** git records for +the submodule, and by **`contracts/-pin.yaml`** carrying `revision_kind: +commit`, the 40-hex commit, and a sha256 digest. A tag is never the referent — a +tag can be moved and a commit cannot — and a pin never expresses a range. Three things therefore move together, in ONE commit: @@ -955,16 +956,16 @@ for months, seven consecutive pin-syncs from 2026-08-25 moved the gitlink alone, and `validate` was red on every pull request for a day — unnoticed because the check runs on pull requests only, so `main` never reports it. -**What is digested.** `sorted-ls-tree-r-v1`: the sha256 of the sorted -`git ls-tree -r -z ` listing — each record ` SP SP -TAB ` with the path unquoted, sorted bytewise, joined with LF. Not -`git archive`: its tar bytes carry a pax header naming the commit, uname/gname, -an mtime, and `export-ignore` handling from `.gitattributes`, so the same -commit can digest differently across git versions and platforms — which is -disqualifying for a number two machines must agree on. The `ls-tree` listing is -a complete content address (mode catches permission changes, oid catches -content, `160000 commit ` catches a nested pin moving) and reproduces from -nothing but git's plumbing. +**What is digested.** `sorted-ls-tree-r-v1`: the sha256 of the sorted `git +ls-tree -r -z ` listing — each record ` SP SP TAB +` with the path unquoted, sorted bytewise, each followed by LF, the last +one included. Not `git archive`: its tar bytes carry a pax header naming the +commit, uname/gname, an mtime, and `export-ignore` handling from +`.gitattributes`, so the same commit can digest differently across git versions +and platforms — which is disqualifying for a number two machines must agree on. +The `ls-tree` listing is a complete content address (mode catches permission +changes, oid catches content, `160000 commit ` catches a nested pin moving) +and reproduces from nothing but git's plumbing. ### Advancing a leg: `scripts/bump-leg.py` @@ -1041,8 +1042,10 @@ validators. `update-shape.py` is that, as one command: `check` prints one verdict per file the pin names, and the verdict is the product: **`unchanged`**, **`upstream-changed`** (the bytes differ between the pinned commit and the target), **`locally-modified`** (the project's bytes -differ from the pinned digest — drift it introduced) or **`both`**. It exits 1 -when there is anything to do, so it can be a scheduled job. +differ from the pinned digest — drift it introduced) or **`both`**; also +`already-at-target` (the root has the target's bytes) and `upstream-removed`, +`unmapped` and `copy-missing`; `apply` refuses the last three. It exits 1 when +there is anything to do, so it can be a scheduled job. `apply` copies the `upstream-changed` files, rewrites `contracts/shape-pin.yaml` (`commit`, `digests.tree_sha256` recomputed over the upstream tree under the @@ -1054,13 +1057,13 @@ either goes red. **What it refuses, and why the refusals are the feature.** A `locally-modified` file keeps its bytes and is named in a refusal unless the human passes `--accept-local ` — recomputing that row silently would turn a drift -finding into a digest that agrees with the project's own edited bytes, which -is the standard quietly recording someone's edit as if it were upstream's. A -file changed on **both** sides is refused outright: two -edits to one file is a merge, and a merge is a human's judgement. So is a copy -that was never verbatim — `adopt-project.py` appends a `CONTRACTS_DIR` block to -an adopted Makefile, and copying the upstream bytes over that would delete it -without saying so. +finding into a digest that agrees with the project's own edited bytes, which is +the standard quietly recording someone's edit as if it were upstream's. A file +changed on **both** sides is refused until a human merges it, commits the merge +and re-runs with `--accept-local `: two edits to one file is a merge, and +a merge is a human's judgement. So is a copy that was never verbatim — +`adopt-project.py` appends a `CONTRACTS_DIR` block to an adopted Makefile, and +copying the upstream bytes over that would delete it without saying so. **Only the pin's own rows are RE-SYNCED.** The file list is read from `contracts/shape-pin.yaml` and never re-derived from this repository's copy @@ -1135,28 +1138,28 @@ not silent about gets the **Triad** row, status `advisory`: the preference said once and stepped over by the verdict (a leg gets its assembly root). Every row is `ok`, `note`, `FINDING` or `n/a` — or `advisory`, the Triad row -alone — and every one that is not `ok` names the exact command that fixes it, -except the Triad row, which names none in any status. **Only a `FINDING` -moves the verdict**, and `FINDING` means exactly *something else asserts this* — a -validator, a pin row, a manifest, a gitlink. A difference nothing asserts is -a `note`: printed, in `--json`, and stepped over by the verdict. That line -was drawn after this command answered `INVALID` about a live estate whose -every real gate was green, because a leg was missing a `.gitignore` that -entered the standard *after* that project was scaffolded. A doctor that -invents a rule to fail you by is not believed the next time. Then one -verdict, and its exit code: +alone — and a row that is not `ok` names the exact command that fixes it +whenever there is one to run, except the Triad row, which names none in any +status. **Only a `FINDING` moves the verdict**, and `FINDING` means exactly +*something else asserts this* — a validator, a pin row, a manifest, a gitlink. A +difference nothing asserts is a `note`: printed, in `--json`, and stepped over +by the verdict. That line was drawn after this command answered `INVALID` about +a live estate whose every real gate was green, because a leg was missing a +`.gitignore` that entered the standard *after* that project was scaffolded. A +doctor that invents a rule to fail you by is not believed the next time. Then +one verdict, and its exit code: | verdict | exit | when | |---|---|---| -| `COMPLIANT` | 0 | every row ok, and the pin names this standard's commit | -| `COMPLIANT, SHAPE BEHIND (n upstream-changed, m upstream-added)` | 1 | valid, and the standard has moved on | -| `DRIFTED (…)` | 1 | a copy was edited here, or a leg is off its pin | +| `COMPLIANT` | 0 | no row is a `FINDING`, and the pin names this standard's commit | +| `COMPLIANT, SHAPE BEHIND (n upstream-changed, m upstream-added)`, or `(pin -> , no copied file differs)` | 1 | valid, and the standard has moved on | +| `DRIFTED (…)` | 1 | a copy was edited, deleted or no longer maps onto the standard, or a leg (a member, in a holder) is off its pin | | `MISPLACED (n paths)` | 1 | a tracked path sits in a leg the path policy puts elsewhere | | `INVALID ()` | 1 | a validator went red | | `NOT A SHAPE ROOT` | 2 | neither manifest is there — adopt, or scaffold | -Exit 3 is usage or environment — nothing about it is a statement about the -tree you pointed at: no such path, a `shape-doctor.py` not sitting in a +Exit 3 is environment alone (a usage error is exit 2) — nothing about it is a statement +about the tree you pointed at: no such path, a `shape-doctor.py` not sitting in a checkout of the standard, or a pin naming a commit **this** checkout does not carry (`CANNOT ANSWER`). **Drift outranks a red validator** on the verdict line, deliberately — an edited shape copy is exactly what makes @@ -1200,14 +1203,13 @@ Its entries are `adopt-project.py plan`'s entries exactly — `path`, `leg`, file is in today) and an empty `resolution:`, so a person who has resolved an adoption plan has already resolved this one. It carries `kind: placement-plan` and **not** `adoption-plan`, deliberately: an adoption plan is the input to -`adopt-project.py execute`, which creates two repositories and rewrites -history, and a file that called itself one would be that command aimed at a -project that is already split. `adopt-project.py` refuses this kind by name, -and the refusal says which tool consumes one. A later `--fix --plan ` -will carry a **resolved** plan out by paired leg pull requests and a pin bump, -printing what it will do and asking first; until then the moves are yours. -`--placement-plan` is the only thing this command writes, and it writes only -the file you name. +`adopt-project.py execute`, which creates two repositories and rewrites history, +and a file that called itself one would be that command aimed at a project that +is already split. `adopt-project.py` refuses this kind by name, and the refusal +says which tool consumes one. A later `--fix --plan ` will carry a +**resolved** plan out by paired leg pull requests and a pin bump, printing what +it will do and asking first; until then the moves are yours. `--placement-plan` +is the only thing this command writes, and it writes only the file you name. **`machine` is the one row that is not about the repository**, so it is `n/a` by status and can never move the verdict: a workstation with no `gh` is not @@ -1264,23 +1266,22 @@ That flag is the only thing in this standard that writes `${AGENT_PROTOCOL_ROOT:-$HOME/.agents}/workspace.yaml` (default: `~/.agents`), and it writes it only because you named the repository. Afterwards `resume ` needs no flag. -**How the estate is found.** `` is a folder under your projects -directory: `~/projects/`, then `~/Projects/` — people spell it -both ways — or wherever `$PROJECTS_DIR` points. A FAMILY folder -(`//family.yaml`) wins over a standalone root -(`/project.yaml`) of the same name, because the holder is what drives -the members. With no `` the estate around the current directory is used: -the nearest holder beside you, or project root above you. With no estate -around it either, `park` PARKS EVERY ESTATE it finds under your projects -directory instead of refusing (Brett Heap's RULING of 2026-09-10, #91, -superseding ruling 3 of #82 for this one case): it lists what it found, then -runs itself on each one in name order, continuing past a refusal. `resume` -deliberately keeps that old refusal for its own bare form — rebuilding every -estate on a fresh machine by accident is the opposite risk from failing to -park the one you meant. `--repo ` names the estate by the -`origin` of a clone you ALREADY have, in any url spelling; it never clones -one, because `resume` is the command that clones and it clones from the -record. +**How the estate is found.** `` is a folder under your projects directory: +`~/projects/`, then `~/Projects/` — people spell it both ways — or +wherever `$PROJECTS_DIR` points. A FAMILY folder (`//family.yaml`) +wins over a standalone root (`/project.yaml`) of the same name, because +the holder is what drives the members. With no `` the estate around the +current directory is used: the nearest holder beside you, or project root above +you. With no estate around it either, `park` lists every estate it finds under +your projects directory and ASKS whether to park them all (with no terminal on +stdin it refuses and names `--all`); `park --all`, or `-a`, parks them all +unasked, in name order, continuing past a refusal and skipping a root with no +Speckit overlay (Brett Heap's RULINGs of 2026-09-10, the later superseding #91's +unasked sweep). `resume` has no `--all`, and its bare form still refuses — +rebuilding every estate on a fresh machine by accident is the opposite risk from +failing to park the one you meant. `--repo ` names the estate by the +`origin` of a clone you ALREADY have, in any url spelling; it never clones one, +because `resume` is the command that clones and it clones from the record. **What they leave behind, said out loud.** `park` finishes by printing what did NOT park and why none of it is park's to touch: commits on a `main` that @@ -1344,17 +1345,16 @@ which carries no feature branch at all. > a tool that discards somebody's pushed commit to make its own record fit. A FAMILY holder carries both verbs too and runs each member's own — **in the -members' WORKING CLONES BESIDE THE HOLDER**, through `scripts/siblings.py ---make park`, and never in `members/`. That is the layout's own -rule: the pinned copy is detached, for `bootstrap` and `validate`, and the -sibling is where a person works, so the sibling is where the features and the -worktrees are. A holder that parked the pinned copies would report "nothing -to park" for every member while the work sat untouched next door. The -dispatch clones nothing and fetches nothing; a member with no working clone is -reported and SKIPPED naming `make siblings`, a member whose verb refuses has -that refusal printed where it happened, and the holder exits non-zero if any -member was skipped or refused. A skip is not a pass, and a refusal is never -hidden. +members' WORKING CLONES BESIDE THE HOLDER**, through `scripts/siblings.py --make +park`, and never in `members/`. That is the layout's own rule: the +pinned copy is detached, for `bootstrap` and `validate`, and the sibling is +where a person works, so the sibling is where the features and the worktrees +are. A holder that parked the pinned copies would report "nothing to park" for +every member while the work sat untouched next door. The dispatch clones nothing +and fetches nothing; a member with no working clone is reported and SKIPPED +naming `make siblings`, a member whose verb refuses has that refusal printed +where it happened, and the holder exits non-zero if any member was skipped or +refused. A skip is not a pass, and a refusal is never hidden. > [!WARNING] > **Native Windows parks nothing.** The extension's `park` and `resume` are diff --git a/docs/handbook.html b/docs/handbook.html index 1baf4391..506ed542 100644 --- a/docs/handbook.html +++ b/docs/handbook.html @@ -637,11 +637,16 @@

What this machine needs

sudo dnf install gh

git itself comes from sudo apt-get install -y git on Debian and Ubuntu and sudo dnf install -y git on Fedora — the same commands - that preflight offers to run for you.

+ setup-project.py --preflight offers to run for you; the run and + openRepoShape need git before their preflight starts.

The tool offers the rest

-

The run opens with a preflight that names what is missing, prints the command for THIS - machine and asks once — Type yes to continue:, the same words it asks - everything else with — then runs it and carries on.

+

The run opens with a preflight that names what is missing and, for gh and + its login, prints the command for THIS machine and asks once — + Type yes to continue:, the same words it asks everything else with — + then runs it and carries on. A missing git is refused before the preflight + starts, with where to get it (setup.sh does the same for a Python older than + 3.9), so only setup-project.py --preflight, run directly, offers to install + it.

@@ -673,8 +678,9 @@

What this machine needs

That the login shell is zsh changes nothing — both entry points are run under bash explicitly.

The tool offers the rest

-

The preflight names what is missing, prints the command for THIS machine and asks once - — Type yes to continue: — then runs it and carries on. +

@@ -693,10 +699,12 @@

What this machine needs

git-scm.com/downloads, and gh with winget install --id GitHub.cli -e.

The tool offers the rest

-

The preflight names anything missing, prints the command for THIS machine and asks once - — Type yes to continue: — then runs it and carries on. On Windows - those offers are the winget ones: winget install --id Git.Git -e for - git and winget install --id GitHub.cli -e for gh. +

The preflight names anything missing and, for gh and its login, prints the + command for THIS machine and asks once — Type yes to continue: — + then runs it and carries on. On Windows those offers are the winget ones: + winget install --id GitHub.cli -e for gh and, only under + py setup-project.py --preflight, winget install --id Git.Git -e + for git: a run refuses a missing git before its preflight starts. There is no openRepoShape command to install here: that two-liner IS the Windows way in, and the openRepoShape command is a macOS, Linux and WSL2 convenience with no Windows twin, because on Windows there is nothing to install.

@@ -724,9 +732,9 @@

What this machine needs

&& sudo apt update \ && sudo apt install gh -y

The tool offers the rest

-

The preflight names anything missing and where to get it, prints the command for THIS - machine and asks once — Type yes to continue: — then runs it and - carries on.

+

The preflight names anything missing and where to get it and, for gh and + its login, prints the command for THIS machine and asks once — + Type yes to continue: — then runs it and carries on.

@@ -799,14 +807,17 @@

Check the machine, creating nothing

openRepoShape --preflight
py setup-project.py --preflight

It checks this machine, offers what is missing and creates nothing. It exits 0 when - everything is present and 1 when it is not.

-

It exits 0 when everything is present and 1 when it is not, and creates nothing. This - is also where a python3 below 3.9 is named.

+ everything is present and 1 when it is not; it exits 2 instead, before any check, when + git or a Python 3.9 or newer is missing.

+

It exits 0 when everything is present and 1 when it is not, and creates nothing; it + exits 2 instead, before any check, when git or a Python 3.9 or newer is + missing. This is also where a python3 below 3.9 is named.

It exits 0 when everything is present and 1 when it is not. python setup-project.py --preflight is the same command on a machine with no py: Python from the Microsoft Store installs python and no launcher at all.

-

It exits 0 when everything is present and 1 when it is not.

+

It exits 0 when everything is present and 1 when it is not; it exits 2 + instead, before any check, when git or a Python 3.9 or newer is missing.

  • @@ -856,8 +867,9 @@

    Rehearse first, creating nothing

    fetch setup.sh, then for the self-bootstrap clone of this standard into a temporary directory. Run the same flag on ./setup.sh from a checkout instead and nothing touches the network at all. What it leaves behind is - ./Atlas and three bare repositories in ./rehearsal, both of - which you can delete.

    + ./Atlas, three bare repositories in ./rehearsal, and the + scaffold’s three working trees in the temporary directory its + Working trees are at line names, all of which you can delete.

    Honestly: it still reaches GitHub before the run starts — to fetch setup.sh, then for the self-bootstrap clone of this standard into a temporary directory. Run the same flag on ./setup.sh from a checkout @@ -878,8 +890,10 @@

    Run it

    py setup-project.py <Project> --org <your-org>

    <Project> is one positional CamelCase name (e.g. Atlas); --org is required — from the flag, - $OPENREPOSHAPE_ORG, or the command’s own prompt — and add - --visibility private if you want that. It shows the plan and asks once, + $OPENREPOSHAPE_ORG, or the command’s own prompt — and + --visibility defaults to private: add + --visibility public (or internal) if you want that. It shows + the plan and asks once, Type yes to continue:; type yes. Afterwards ./<Project> is the cloned root.

    <Project> is one positional CamelCase name (e.g. @@ -1049,7 +1063,8 @@

    Reading private legs in CI

    that fails to mint is a misconfiguration, not a degrade, and fails the job outright, naming both secrets and the required installation.
  • An installation token is per-owner, so a leg under a different owner is excluded with a - warning and can only be reached via SHAPE_LEGS_TOKEN.
  • + warning. SHAPE_LEGS_TOKEN is read only when the App is not configured, so + that leg is reachable only with the App secrets removed.
  • The root repository is always readable by the workflow’s own default token, so actions/checkout never carries a token: override — putting a legs credential there was itself the next defect, because a token scoped to the legs alone @@ -1057,8 +1072,10 @@

    Reading private legs in CI

    ran.
  • Whichever credential resolves is read only inside the guarded “fetch the legs (submodules)” step, scoped to that step’s env:, and used through a - git -c url.<...>.insteadOf=<...> rewrite covering both HTTPS and SSH - leg URLs — never persisted onto the root checkout.
  • + git -c url.<...>.insteadOf=<...> rewrite covering both HTTPS + (https://github.com/…) and scp-form SSH + (git@github.com:…) leg URLs — never persisted onto the root + checkout.
  • Without either credential the workflow degrades instead of failing: it still runs the naming and manifest checks and skips validate-pins.py with a warning explaining why. It fails outright only if a credential is configured and the fetch @@ -1102,8 +1119,10 @@

    The three legs, and naming

    — <user>-wip, a person’s own workspace repository, unambiguous by construction because nothing else here spells that suffix. The leg suffixes are lowercase and hyphenated precisely so they sit in a different visual class - from every other family, all of which are CamelCase words. Every repository of a project also - carries the GitHub topic xf-project-<id>.

    + from every other family but one, all of which are CamelCase words; the exception is the + workspace form <user>-wip, lowercase and hyphenated too, with a suffix no + leg form admits. Every repository of a project also carries the GitHub topic + xf-project-<id>.

    A descendant form is a claim, and a claim needs a referent: <Domainx><Product> counts as a descendant only if it PINS open<Product>, declared as neutral_product_pins: in the @@ -1135,8 +1154,8 @@

    Adopting an existing repository

    ITS IDENTITY AND ITS FULL HISTORY and becomes the assembly root, while <Project>-spec and <Project>-code are NEW repositories extracted with history-preserving filters. The source is never deleted, never renamed and - never force-pushed; the only change to it is ONE split commit, arriving on a branch by pull - request.

    + never force-pushed; the only change to its history is ONE split commit, arriving on a branch + by pull request.

    ./adopt-project.py plan --source MedxSoft/MedxEHR --project MedxEHR
     #   ... a human or an AI answers every `review_required` entry in the plan
     ./adopt-project.py check   --plan adoption-plan.yaml
    @@ -1296,7 +1315,8 @@ 

    The double pin, and the lockstep invariant

    What is digested. sorted-ls-tree-r-v1: the sha256 of the sorted git ls-tree -r -z <commit> listing — each record <mode> SP <type> SP <oid> TAB <path> with the path - unquoted, sorted bytewise, joined with LF. Not git archive: its tar bytes carry a + unquoted, sorted bytewise, each followed by LF, the last one included. Not + git archive: its tar bytes carry a pax header naming the commit, uname/gname, an mtime, and export-ignore handling from .gitattributes, so the same commit can digest differently across git versions and platforms — disqualifying for a number two machines must agree on.

    @@ -1372,8 +1392,12 @@

    Keeping a project’s shape current

    check prints one verdict per file the pin names, and the verdict is the product: unchanged, upstream-changed (the bytes differ between the pinned commit and the target), locally-modified (the project’s bytes differ - from the pinned digest — drift it introduced) or both. It exits 1 when there - is anything to do, so it can be a scheduled job.

    + from the pinned digest — drift it introduced), both or + already-at-target (the root already holds the target’s bytes) — or, + where there is nothing to compare, upstream-removed (the standard no longer has + the file), unmapped (no file there corresponds to it) or + copy-missing (the root has no such file), each of which apply + refuses. It exits 1 when there is anything to do, so it can be a scheduled job.

    apply copies the upstream-changed files, rewrites contracts/shape-pin.yaml and mirrors the two fields into project.yaml. Then it runs the project’s OWN validate-pins.py @@ -1384,8 +1408,9 @@

    Keeping a project’s shape current

    human passes --accept-local <path> — recomputing that row silently would turn a drift finding into a digest that agrees with the project’s own edited bytes.
  • -
  • A file changed on both sides is refused outright: two edits to one file - is a merge, and a merge is a human’s judgement. So is a copy that was never +
  • A file changed on both sides is refused until a human merges it by hand, + commits the merge and re-runs with --accept-local <path>: two edits to + one file is a merge, and a merge is a human’s judgement. So is a copy that was never verbatim.
  • Only the pin’s own rows are RE-SYNCED. The file list is read from contracts/shape-pin.yaml and never re-derived from this repository’s copy @@ -1440,9 +1465,9 @@

    Is this repository compliant? openRepoShape --doctor

    preference said once and stepped over by the verdict (a leg gets its assembly root).

    Every row is ok, note, FINDING or - n/a — or advisory, the Triad row alone — and every one - that is not ok names the exact command that fixes it, except the Triad row, - which names none in any status. Only a FINDING moves the verdict, and + n/a — or advisory, the Triad row alone — and a row that + is not ok names the exact command that fixes it whenever there is one to run, + except the Triad row, which names none in any status. Only a FINDING moves the verdict, and FINDING means exactly something else asserts this — a validator, a pin row, a manifest, a gitlink. A difference nothing asserts is a note: printed, in --json, and stepped over by the verdict. That line was drawn after @@ -1452,16 +1477,16 @@

    Is this repository compliant? openRepoShape --doctor

    - - - + + +
    verdictexitwhen
    COMPLIANT0every row ok, and the pin names this standard’s commit
    COMPLIANT, SHAPE BEHIND (n upstream-changed, m upstream-added)1valid, and the standard has moved on
    DRIFTED (…)1a copy was edited here, or a leg is off its pin
    COMPLIANT0no row is a FINDING, and the pin names this standard’s commit
    COMPLIANT, SHAPE BEHIND (n upstream-changed, m upstream-added), or (pin <a> -> <b>, no copied file differs)1valid, and the standard has moved on
    DRIFTED (…)1a copy was edited, deleted or no longer maps onto the standard, or a leg (a member, in a holder) is off its pin
    MISPLACED (n paths)1a tracked path sits in a leg the path policy puts elsewhere
    INVALID (<rows>)1a validator went red
    NOT A SHAPE ROOT2neither manifest is there — adopt, or scaffold
    -

    Exit 3 is usage or environment — nothing about it is a statement about the tree - you pointed at: no such path, a shape-doctor.py not in a checkout of the +

    A malformed command line is exit 2, as argparse makes it. Exit 3 is environment — + nothing about it is a statement about the tree you pointed at: no such path, a shape-doctor.py not in a checkout of the standard, or a pin naming a commit this checkout does not carry (CANNOT ANSWER). Drift outranks a red validator on the verdict line, deliberately: an edited shape copy is exactly what makes @@ -1571,15 +1596,17 @@

    park <Name> and resume <Name>, from an (<Name>/project.yaml) of the same name, because the holder is what drives the members. With no <Name> the estate around the current directory is used: the nearest holder beside you, or project root above you. With no estate around it either, - park PARKS EVERY ESTATE it finds under your projects directory instead of - refusing (Brett Heap’s RULING of 2026-09-10, #91, superseding ruling 3 of #82 for this - one case): it lists what it found, then runs itself on each one in name order, continuing - past a refusal. + park lists every estate it finds under your projects directory and ASKS whether + to park them all (with no terminal on stdin it cannot ask, so it refuses and names + --all); park --all, or -a, parks them all unasked, + from anywhere, in name order, continuing past a refusal and skipping a root that carries no + Speckit overlay (Brett Heap’s RULINGs of 2026-09-10: #91’s unasked sweep, + superseded the same day by this question). --repo <owner/name> names the estate by the origin of a clone you ALREADY have, in any url spelling; it never clones one, because resume is the command that clones and it clones from the record.

    -

    Refuses resume keeps the old - refusal: with no <Name> and no estate around it either, +

    Refuses resume has no + --all: with no <Name> and no estate around it either, resume’s own bare form still REFUSES and lists the estates it found — rebuilding every estate on a fresh machine by accident is the opposite risk from park failing to park the one you meant.

    @@ -1799,9 +1826,10 @@

    For AI assistants

    name, added (#289); bump-leg.py refusing a workflow file, the workflows directory or the leg’s pin that this user cannot open, by name, added (#383); what setup.sh --help does from a download, which clones nothing, and which commands - list every flag, added (#341) — a page cannot name the commit that contains it, and - the blob below is the fact a test checks. - README.md blob 5465d135ca7263010ee354424041f90c4553d2e7. Every command, flag, exit code + list every flag, added (#341); the statements the v1.0.0 accuracy pass found false, + corrected here as in the README, added (#483) — a page cannot name the commit that + contains it, and the blob below is the fact a test checks. + README.md blob 6252b58b32dc7b72cfd57baeb04c9e175a991336. Every command, flag, exit code and file name on this page is quoted from that README and from AGENTS.md.

    From ccf520bd5468ad5414ce1678ed917457118be0e3 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Mon, 12 Oct 2026 23:51:30 +0000 Subject: [PATCH 2/5] README.md, AGENTS.md and docs/handbook.html: 12 corrections the independent review of the v1.0.0 accuracy pass asked for, among them exit 3 and the piped setup one-liners (#483) The review of bfbd4eeb found two false corrections and nine gaps. This merges main (7a8b981d, #394) and fixes them, line-neutral (AGENTS.md 490, README.md 1422, the caps tests/test_repo_hygiene.py holds them to). - Exit 3 is "usage or environment": a flag argparse rejects is exit 2, but a `--placement-plan` the tree cannot take is exit 3 (shape-doctor.py `main`, `shape-doctor-no-placement-audit`). The round-one text said "environment alone". - AGENTS.md no longer says the doctor's next commands each have a section in that file; it says each command keeps its own refusals, and that a FINDING carries a next command where one can be named. - The `curl ... | bash -s --` one-liners (README.md, twice, and the worked example; AGENTS.md section 2) run from a file, `bash <(curl ...)`, so stdin stays the terminal and the run can ask. Under a pty each form printed `Type yes to continue:`; the piped form printed `REFUSED: ... no terminal to confirm on`. - AGENTS.md `check` lists `already-at-target`, `upstream-removed`, `unmapped` and `copy-missing` as README.md does; its machine-row item says `openRepoShape --preflight` offers only the `gh` set-up. - The preflight also offers `gh`'s credential helper (README.md, four handbook panels); `openRepoShape --preflight` exits 2 when `git` or a Python 3.9 or newer is missing; the park sweep skips a STANDALONE root with no overlay; the leg suffixes are lowercase against the neutral-product, domain-descendant and family forms. - Since #394 a submodule in the other leg than its `submodules:` record's is MISPLACED; README.md's placement paragraph and the handbook's say so (the table row sits against #468's INVALID row, so it stays for #468). docs/handbook.html mirrors the changes and its footer blob is recomputed. Four paragraphs of README.md and two of AGENTS.md that no correction touches are re-wrapped, no word changed, to give back the lines. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 56 +++++++++--------- README.md | 142 ++++++++++++++++++++++----------------------- docs/handbook.html | 33 ++++++----- 3 files changed, 116 insertions(+), 115 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ea419fc0..d51ce488 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,13 +1,12 @@ # AGENTS.md — scaffolding, adopting, updating, advancing a leg, families -You are an AI assistant a human has asked to "scaffold a new project with -this shape", or "convert this repository to it", or "put these services in a -family". This is the whole procedure; `README.md` says what the shape is. -Sections 1-3 scaffold a NEW project; the ones after them cover an EXISTING -repository (including one with no code yet), a project that declares descent -from a neutral product, a repository nobody is sure about, a project whose -copied shape files have fallen behind the upstream, a leg that has moved on, -and a FAMILY holder. +You are an AI assistant a human has asked to "scaffold a new project with this +shape", or "convert this repository to it", or "put these services in a family". +This is the whole procedure; `README.md` says what the shape is. Sections 1-3 +scaffold a NEW project; the ones after them cover an EXISTING repository +(including one with no code yet), a project that declares descent from a neutral +product, a repository nobody is sure about, a project whose copied shape files +have fallen behind the upstream, a leg that has moved on, and a FAMILY holder. `docs/cli.md` is the flag-level reference: every tool's own `--help`, and every subcommand's, in one file — the ones you run from a checkout of this @@ -56,9 +55,9 @@ visibility they want, ask — do not assume. `elected_by` records whose act it w ## 2. Run the one-liner, without `--yes` ```sh -curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/main/setup.sh \ - | bash -s -- --org --project \ - --visibility --elected-by '' +bash <(curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/main/setup.sh) \ + --org --project \ + --visibility --elected-by '' ``` `openRepoShape --org --visibility <…> --elected-by ''` @@ -162,11 +161,10 @@ only. Never suggest a direct push to the default branch, and never `--admin`. ### Adopting a repository that has NO code yet (or no spec) -A leg no plan entry assigns a path to is SEEDED from -`templates/-root/`, not extracted — `git filter-repo` over an empty -path list yields an empty history, not an empty repository. `plan` and -`check` both WARN; `check` still passes, because a spec-only repository is a -legitimate thing to adopt. +A leg no plan entry assigns a path to is SEEDED from `templates/-root/`, +not extracted — `git filter-repo` over an empty path list yields an empty +history, not an empty repository. `plan` and `check` both WARN; `check` still +passes, because a spec-only repository is a legitimate thing to adopt. ```sh ./adopt-project.py plan --source / --project \ @@ -232,13 +230,13 @@ command) run `./shape-doctor.py --root ` from a checkout. rendered `AGENTS.md`. It never moves the verdict. Report it; propose a change only if the human asks. 3. **A FINDING names its fix, and you run THAT.** Every one carries the exact - next command — `update-shape.py check …` and the `apply` it leads to, `git - submodule update …` (in a holder, its `scripts/bootstrap.py`), the project's - own validator. Run the one the row names. Do not improvise a repair, and - never edit a pinned file, a digest or a manifest to make a row go green: that - is the hand-edited pin this standard spends a section refusing. Each of those - tools has its own section in this file and its own refusals, and they all - still apply. + next command, where one can be named — `update-shape.py check …` and the + `apply` it leads to, `git submodule update …` (in a holder, its + `scripts/bootstrap.py`), the project's own validator. Run the one the row + names. Do not improvise a repair, and never edit a pinned file, a digest or a + manifest to make a row go green: that is the hand-edited pin this standard + spends a section refusing. Each of those commands keeps its own refusals, and + they all still apply. 4. **`placement` says what is in the wrong leg, and you never move a file to answer it.** It runs the adoption's own path policy over each mounted leg and names the paths it would have put elsewhere — code in the spec @@ -265,7 +263,7 @@ command) run `./shape-doctor.py --root ` from a checkout. 6. **The `machine` row is not about the repository.** It is `n/a` by status and never changes the verdict: it says whether THIS workstation can run the fixes the other rows named. Relay a missing prerequisite; `openRepoShape - --preflight` is what offers to install one, and the human types that yes. + --preflight` offers only the `gh` set-up, and the human types that yes. 7. `./shape-doctor.py --json` is the same report as one object, each row carrying its `id`, for when you have to act on a row rather than quote it. @@ -285,10 +283,12 @@ git -C push -u origin shape/update- # then open a pull request ``` 1. **`check` first, always.** It writes nothing and prints one verdict per - PINNED file: `unchanged`, `upstream-changed`, `locally-modified` (the - project edited its own copy) or `both`. A file with no row in - `contracts/shape-pin.yaml` is not a shape copy and is none of your business - — an adopted project merged some of them away on purpose. + PINNED file: `unchanged`, `upstream-changed`, `locally-modified` (the project + edited its own copy) or `both`; also `already-at-target` and + `upstream-removed`, `unmapped` and `copy-missing`; `apply` refuses the last + three. A file with no row in `contracts/shape-pin.yaml` is not a shape copy + and is none of your business — an adopted project merged some of them away on + purpose. 2. **Show the human the verdicts and let them say yes.** Re-pinning records which openRepoShape this project is a copy of. Do not pass `--yes` on your own initiative. diff --git a/README.md b/README.md index 6252b58b..c5c3c181 100644 --- a/README.md +++ b/README.md @@ -93,20 +93,19 @@ and is reported beside the advisory, and nothing fails on its account. Command Line Tools supply `git`, `make` and a `python3` — install them with `xcode-select --install`, or type `git` once on a fresh Mac and the installer offers itself; step 4 opens with a preflight that prints the `python3` it found -and refuses anything below 3.9. If it refuses the one the Tools supplied — -older macOS shipped 3.8 — install a newer one from - or with `brew install python`, and the -preflight will find it first on `PATH`. `gh` comes from Homebrew, which is its -own install if `brew` is absent (): on Apple Silicon Homebrew -lives in `/opt/homebrew`, which is on no `PATH` by default, and its installer -finishes by printing the line that puts it there — run that line, or the next -shell cannot find `brew`. Then `brew install gh`, and `brew install -git-filter-repo` only if you are ADOPTING an existing repository rather than -scaffolding a new one. The stock `/bin/bash` is 3.2 and is enough: `setup.sh` -and the `openRepoShape` command use no syntax newer than that, and CI runs the -whole suite on `macos-latest`, parsing both there with `/bin/bash` itself. That -the login shell is zsh changes nothing — both entry points are run under `bash` -explicitly. +and refuses anything below 3.9. If it refuses the one the Tools supplied — older +macOS shipped 3.8 — install a newer one from +or with `brew install python`, and the preflight will find it first on `PATH`. +`gh` comes from Homebrew, which is its own install if `brew` is absent +(): on Apple Silicon Homebrew lives in `/opt/homebrew`, which +is on no `PATH` by default, and its installer finishes by printing the line that +puts it there — run that line, or the next shell cannot find `brew`. Then `brew +install gh`, and `brew install git-filter-repo` only if you are ADOPTING an +existing repository rather than scaffolding a new one. The stock `/bin/bash` is +3.2 and is enough: `setup.sh` and the `openRepoShape` command use no syntax +newer than that, and CI runs the whole suite on `macos-latest`, parsing both +there with `/bin/bash` itself. That the login shell is zsh changes nothing — +both entry points are run under `bash` explicitly. @@ -172,16 +171,17 @@ run the four steps below INSIDE the WSL shell. > [!NOTE] > **The tool offers the rest.** The four steps below are the reference — what > each platform needs, and how to install it by hand. You do not have to: step 4 -> opens with a preflight that names what is missing and, for `gh` and its login, -> prints the command for THIS machine and asks once — `Type yes to continue:`, -> the same words it asks everything else with — then runs it and carries on. -> Nothing is installed without that typed `yes`; `--yes` does not answer it, and -> with no terminal attached there is no offer at all. Homebrew is the one -> install never run for you: on a Mac without `brew` the preflight names -> and stops there. To check a machine and stop, creating -> nothing, run `openRepoShape --preflight` — `py setup-project.py --preflight` -> on Windows — which exits 0 when everything is present and 1 when it is not -> (`openRepoShape --preflight` exits 2 when `git` or Python 3.9 is missing). +> opens with a preflight that names what is missing and, for `gh`, its login and +> its credential helper, prints the command for THIS machine and asks once — +> `Type yes to continue:`, the same words it asks everything else with — then +> runs it and carries on. Nothing is installed without that typed `yes`; `--yes` +> does not answer it, and with no terminal attached there is no offer at all. +> Homebrew is the one install never run for you: on a Mac without `brew` the +> preflight names and stops there. To check a machine and +> stop, creating nothing, run `openRepoShape --preflight` — `py setup-project.py +> --preflight` on Windows — which exits 0 when everything is present and 1 when +> it is not (`openRepoShape --preflight` exits 2 when `git` or a Python 3.9 or +> newer is missing). **1. Install the GitHub CLI**, `gh`. The official instructions are at (and ): @@ -316,16 +316,16 @@ subcommand's in one file, generated from the tools themselves. Without installing anything — no fork, no manual clone — one command: ```sh -curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/main/setup.sh \ - | bash -s -- --org --project Atlas +bash <(curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/main/setup.sh) \ + --org --project Atlas ``` An organisation whose policy blocks raw GitHub downloads can fetch the same bytes through the API instead: ```sh -gh api repos/opensoft/openRepoShape/contents/setup.sh --jq .content \ - | base64 --decode | bash -s -- --org --project Atlas +bash <(gh api repos/opensoft/openRepoShape/contents/setup.sh --jq .content \ + | base64 --decode) --org --project Atlas ``` Shorter still, once: install the `openRepoShape` command and this becomes @@ -371,15 +371,14 @@ create repositories in. `make` is not used by the entry point — it runs Or run `openRepoShape --preflight`, which checks this machine, offers what is missing and creates nothing. -**2. Login.** `gh auth login`, as the account that will own the act: a member -of `Northwind` with permission to create repositories there, or an owner — +**2. Login.** `gh auth login`, as the account that will own the act: a member of +`Northwind` with permission to create repositories there, or an owner — `scaffold-project.py` creates the three with `gh repo create` under whatever account `gh auth status` shows. Accept the offer to configure git's credential -helper, or run `gh auth setup-git`: the first commits go up with a plain -`git push` over HTTPS, and without it the three repositories are created and -the push fails. `--elected-by` authenticates nothing — it is only the name -`project.yaml` records as whose act the election was, and it defaults to the -`gh` login. +helper, or run `gh auth setup-git`: the first commits go up with a plain `git +push` over HTTPS, and without it the three repositories are created and the push +fails. `--elected-by` authenticates nothing — it is only the name `project.yaml` +records as whose act the election was, and it defaults to the `gh` login. #### Install the command @@ -415,9 +414,9 @@ curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoTools/main/openRep Or type the long line, on a machine you would rather install nothing on: ```sh -curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/main/setup.sh \ - | bash -s -- --org Northwind --project Atlas \ - --visibility private --elected-by 'Dana Okafor' +bash <(curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/main/setup.sh) \ + --org Northwind --project Atlas \ + --visibility private --elected-by 'Dana Okafor' ``` #### Run @@ -573,8 +572,9 @@ is no repository of a project at all — a person's own workspace repository `-wip` (#81, and [below](#carrying-in-flight-work-to-another-workstation)). The leg suffixes are lowercase and hyphenated precisely so they sit in a different visual class from -every other family but `-wip`, all of which are CamelCase words. Every -repository of a project also carries the GitHub topic `xf-project-`. +the neutral-product, domain-descendant and family forms, all of which are +CamelCase words. Every repository of a project also carries the GitHub topic +`xf-project-`. ### Reading private legs in CI: a GitHub App first, `SHAPE_LEGS_TOKEN` as fallback @@ -719,14 +719,13 @@ confers nothing, so `openDox` mounting `openDox-spec` and `openDox-code` is a fact about LAYOUT and not a claim about neutrality: exactly the reasoning that already lets a declared descendant be a root. `open` in front still WINS the classification — the manifest records `form: neutral-product, role: assembly` -with `also_matches: [project-leg/assembly]`, so the overlap the scaffold used -to compute and then throw away is now recorded like every other one, and +with `also_matches: [project-leg/assembly]`, so the overlap the scaffold used to +compute and then throw away is now recorded like every other one, and `validate-repository-naming.py --explain --role assembly openDox` names the admission. The legs are untouched: `openDox-spec` carries the lowercase suffix -and is an ordinary project leg, a descendant of nothing and a neutral product -of nothing. `-Install` is admitted into nothing at all — it carries a -hyphen, so it satisfies no leg form to be admitted into, and is still refused -as any leg. +and is an ordinary project leg, a descendant of nothing and a neutral product of +nothing. `-Install` is admitted into nothing at all — it carries a hyphen, so +it satisfies no leg form to be admitted into, and is still refused as any leg. ## Adopting an existing repository @@ -1158,8 +1157,9 @@ one verdict, and its exit code: | `INVALID ()` | 1 | a validator went red | | `NOT A SHAPE ROOT` | 2 | neither manifest is there — adopt, or scaffold | -Exit 3 is environment alone (a usage error is exit 2) — nothing about it is a statement -about the tree you pointed at: no such path, a `shape-doctor.py` not sitting in a +Exit 3 is usage or environment (a flag argparse rejects is exit 2) — nothing about +it is a statement about the tree you pointed at: no such path, a refused +`--placement-plan`, a `shape-doctor.py` not sitting in a checkout of the standard, or a pin naming a commit **this** checkout does not carry (`CANNOT ANSWER`). **Drift outranks a red validator** on the verdict line, deliberately — an edited shape copy is exactly what makes @@ -1176,19 +1176,20 @@ each row carrying its `id`. adoption's own policy — `contracts/path-classification.yaml`, walked with `adopt-project.py`'s own `walk()`, so there is no second copy of that classification to drift — over every tracked path of every mounted leg, and -names the paths the policy would have put somewhere else, each with the rule -id that judged it. What belongs to **every** repository is never judged: the -four files `templates/spec-root/` and `templates/code-root/` themselves ship -(`README.md`, `AGENTS.md`, `CLAUDE.md`, `.gitignore`), read from those -templates at run time so the scaffold's own output can never be a finding, -plus `LICENSE`, `.gitattributes`, `CODEOWNERS`, `.github/**` and the rest of -the forge furniture — `--json` prints the whole list under `everywhere`, -because what a report declined to look at belongs in the report. A path the -policy will not call carries `review_required` and is a **`note`**: it is a -question for a human, and an unanswered question is never a finding. One -`git ls-files` per leg, one process for the classifying, and a directory whose -files agree is ONE path however many are under it — so a leg of several -thousand files answers in seconds and reads in a line. +names the paths the policy would have put somewhere else, each with the rule id +that judged it, and a submodule in the other leg than its `submodules:` +record's. What belongs to **every** repository is never judged: the four files +`templates/spec-root/` and `templates/code-root/` themselves ship (`README.md`, +`AGENTS.md`, `CLAUDE.md`, `.gitignore`), read from those templates at run time +so the scaffold's own output can never be a finding, plus `LICENSE`, +`.gitattributes`, `CODEOWNERS`, `.github/**` and the rest of the forge furniture +— `--json` prints the whole list under `everywhere`, because what a report +declined to look at belongs in the report. A path the policy will not call +carries `review_required` and is a **`note`**: it is a question for a human, and +an unanswered question is never a finding. One `git ls-files` per leg, one +process for the classifying, and a directory whose files agree is ONE path +however many are under it — so a leg of several thousand files answers in +seconds and reads in a line. **And the doctor still moves nothing.** A path changing legs is a pull request on the leg it leaves, a pull request on the leg it joins, and one pin bump in @@ -1275,13 +1276,13 @@ current directory is used: the nearest holder beside you, or project root above you. With no estate around it either, `park` lists every estate it finds under your projects directory and ASKS whether to park them all (with no terminal on stdin it refuses and names `--all`); `park --all`, or `-a`, parks them all -unasked, in name order, continuing past a refusal and skipping a root with no -Speckit overlay (Brett Heap's RULINGs of 2026-09-10, the later superseding #91's -unasked sweep). `resume` has no `--all`, and its bare form still refuses — -rebuilding every estate on a fresh machine by accident is the opposite risk from -failing to park the one you meant. `--repo ` names the estate by the -`origin` of a clone you ALREADY have, in any url spelling; it never clones one, -because `resume` is the command that clones and it clones from the record. +unasked, in name order, continuing past a refusal and skipping a standalone root +with no Speckit overlay (Brett Heap's RULING of 2026-09-10, superseding #91). +`resume` has no `--all`, and its bare form still refuses — rebuilding every +estate on a fresh machine by accident is the opposite risk from failing to park +the one you meant. `--repo ` names the estate by the `origin` of a +clone you ALREADY have, in any url spelling; it never clones one, because +`resume` is the command that clones and it clones from the record. **What they leave behind, said out loud.** `park` finishes by printing what did NOT park and why none of it is park's to touch: commits on a `main` that @@ -1302,9 +1303,8 @@ left behind, which is the point of printing that report. **This repository implements neither, deliberately** (ruled by Brett Heap, 2026-09-09, #77). The WIP commit, the push, the `git worktree add`, the `.specify/feature.json` rewrite and the soft reset are the **Speckit git -extension's**, which `setup-openspeckit` installs — one implementation. The -two targets are three lines each and refuse BY NAME when the overlay is not -there: +extension's**, which `setup-openspeckit` installs — one implementation. The two +targets are three lines each and refuse BY NAME when the overlay is not there: make park needs the Speckit worktree overlay; install it with: setup-openspeckit diff --git a/docs/handbook.html b/docs/handbook.html index 506ed542..564e08c7 100644 --- a/docs/handbook.html +++ b/docs/handbook.html @@ -640,8 +640,8 @@

    What this machine needs

    setup-project.py --preflight offers to run for you; the run and openRepoShape need git before their preflight starts.

    The tool offers the rest

    -

    The run opens with a preflight that names what is missing and, for gh and - its login, prints the command for THIS machine and asks once — +

    The run opens with a preflight that names what is missing and, for gh, its + login and its credential helper, prints the command for THIS machine and asks once — Type yes to continue:, the same words it asks everything else with — then runs it and carries on. A missing git is refused before the preflight starts, with where to get it (setup.sh does the same for a Python older than @@ -678,8 +678,8 @@

    What this machine needs

    That the login shell is zsh changes nothing — both entry points are run under bash explicitly.

    The tool offers the rest

    -

    The preflight names what is missing and, for gh and its login, prints the - command for THIS machine and asks once — Type yes to continue: — +

    The preflight names what is missing and, for gh, its login and its + credential helper, prints the command for THIS machine and asks once — Type yes to continue: — then runs it and carries on. Homebrew is the one install never run for you: on a Mac without brew the preflight names brew.sh and stops @@ -699,8 +699,8 @@

    What this machine needs

    git-scm.com/downloads, and gh with winget install --id GitHub.cli -e.

    The tool offers the rest

    -

    The preflight names anything missing and, for gh and its login, prints the - command for THIS machine and asks once — Type yes to continue: — +

    The preflight names anything missing and, for gh, its login and its + credential helper, prints the command for THIS machine and asks once — Type yes to continue: — then runs it and carries on. On Windows those offers are the winget ones: winget install --id GitHub.cli -e for gh and, only under py setup-project.py --preflight, winget install --id Git.Git -e @@ -732,8 +732,8 @@

    What this machine needs

    && sudo apt update \ && sudo apt install gh -y

    The tool offers the rest

    -

    The preflight names anything missing and where to get it and, for gh and - its login, prints the command for THIS machine and asks once — +

    The preflight names anything missing and where to get it and, for gh, its + login and its credential helper, prints the command for THIS machine and asks once — Type yes to continue: — then runs it and carries on.

    @@ -1119,9 +1119,8 @@

    The three legs, and naming

    — <user>-wip, a person’s own workspace repository, unambiguous by construction because nothing else here spells that suffix. The leg suffixes are lowercase and hyphenated precisely so they sit in a different visual class - from every other family but one, all of which are CamelCase words; the exception is the - workspace form <user>-wip, lowercase and hyphenated too, with a suffix no - leg form admits. Every repository of a project also carries the GitHub topic + from the neutral-product, domain-descendant and family forms, all of which are CamelCase + words. Every repository of a project also carries the GitHub topic xf-project-<id>.

    A descendant form is a claim, and a claim needs a referent: <Domainx><Product> counts as a descendant only if it PINS @@ -1485,8 +1484,9 @@

    Is this repository compliant? openRepoShape --doctor

    NOT A SHAPE ROOT2neither manifest is there — adopt, or scaffold -

    A malformed command line is exit 2, as argparse makes it. Exit 3 is environment — - nothing about it is a statement about the tree you pointed at: no such path, a shape-doctor.py not in a checkout of the +

    Exit 3 is usage or environment (a flag argparse rejects is exit 2) — nothing about + it is a statement about the tree you pointed at: no such path, a refused + --placement-plan, a shape-doctor.py not in a checkout of the standard, or a pin naming a commit this checkout does not carry (CANNOT ANSWER). Drift outranks a red validator on the verdict line, deliberately: an edited shape copy is exactly what makes @@ -1504,7 +1504,8 @@

    Is this repository compliant? openRepoShape --doctor

    contracts/path-classification.yaml, walked with adopt-project.py’s own walk(), so there is no second copy of that classification to drift — over every tracked path of every mounted leg, and names - the paths the policy would have put somewhere else, each with the rule id that judged it. + the paths the policy would have put somewhere else, each with the rule id that judged it, + and a submodule in the other leg than its submodules: record’s. What belongs to every repository is never judged: the four files templates/spec-root/ and templates/code-root/ themselves ship (README.md, AGENTS.md, CLAUDE.md, @@ -1599,7 +1600,7 @@

    park <Name> and resume <Name>, from an park lists every estate it finds under your projects directory and ASKS whether to park them all (with no terminal on stdin it cannot ask, so it refuses and names --all); park --all, or -a, parks them all unasked, - from anywhere, in name order, continuing past a refusal and skipping a root that carries no + from anywhere, in name order, continuing past a refusal and skipping a standalone root that carries no Speckit overlay (Brett Heap’s RULINGs of 2026-09-10: #91’s unasked sweep, superseded the same day by this question). --repo <owner/name> names the estate by the origin of a clone @@ -1829,7 +1830,7 @@

    For AI assistants

    list every flag, added (#341); the statements the v1.0.0 accuracy pass found false, corrected here as in the README, added (#483) — a page cannot name the commit that contains it, and the blob below is the fact a test checks. - README.md blob 6252b58b32dc7b72cfd57baeb04c9e175a991336. Every command, flag, exit code + README.md blob c5c3c1817026e82b875245e69d6daee0ff3908c9. Every command, flag, exit code and file name on this page is quoted from that README and from AGENTS.md.

    From f35e59fa38ff3c0592a0aba84eaf1e143ac468fc Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Tue, 13 Oct 2026 01:23:39 +0000 Subject: [PATCH 3/5] README.md, AGENTS.md and docs/handbook.html: the piped one-liners go back to main's text, `--dry-run` fetches, and the family steps are main's bytes again (#483) The independent re-check of ccf520bd (0 blockers, three landing should-fixes) and two later heads asked for these. README.md stays at 1422 lines and AGENTS.md at 490, the caps tests/test_repo_hygiene.py holds them to. - D2-F5 and D1-F1's block go back to #491: at 2d59d141 setup-project.py reads the answer from the controlling terminal (`CONTROLLING_TERMINAL`), so `curl ... | bash -s --` asks too, and #491's test strips `<...>` from README.md, which a `bash <(` line breaks. README.md's three one-liner blocks and AGENTS.md section 2's block are byte-identical to main's again; no `<(` is left in either file or the handbook. - D3-F5, now that #484 landed: `--dry-run` "writes nothing" is false because scripts/bump-leg.py fetches into the leg before the `--dry-run` exit. README.md (the code comment and the refusal sentence) and the handbook say "it fetches into the leg and writes nothing else", beside #484's text that the dry run runs on the tracking branch and names `git switch -c`. - AGENTS.md's family steps 3 and 5 are origin/main's exact bytes, so #486-#488 (10c93ded), which edits steps 2 to 5, merges clean; the two lines come from inside sentences this pass already corrects (update-shape `check`'s verdict list, the doctor's next-command item). - README.md's park parenthetical is round one's again, with `RULINGs` and `#91's unasked sweep`, so it no longer reads as if the overlay skip superseded #91; the placement paragraph says "a submodule in a leg other than the one its `submodules:` record names" (README.md and the handbook); two words go from sentences this pass corrects to keep the line count. The handbook footer's README.md blob is recomputed. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 66 +++++++++++++++++++++++----------------------- README.md | 58 ++++++++++++++++++++-------------------- docs/handbook.html | 9 ++++--- 3 files changed, 67 insertions(+), 66 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9624b4b4..9da92344 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -55,9 +55,9 @@ visibility they want, ask — do not assume. `elected_by` records whose act it w ## 2. Run the one-liner, without `--yes` ```sh -bash <(curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/main/setup.sh) \ - --org --project \ - --visibility --elected-by '' +curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/main/setup.sh \ + | bash -s -- --org --project \ + --visibility --elected-by '' ``` `openRepoShape --org --visibility <…> --elected-by ''` @@ -230,13 +230,12 @@ command) run `./shape-doctor.py --root ` from a checkout. rendered `AGENTS.md`. It never moves the verdict. Report it; propose a change only if the human asks. 3. **A FINDING names its fix, and you run THAT.** Every one carries the exact - next command, where one can be named — `update-shape.py check …` and the - `apply` it leads to, `git submodule update …` (in a holder, its - `scripts/bootstrap.py`), the project's own validator. Run the one the row - names. Do not improvise a repair, and never edit a pinned file, a digest or a - manifest to make a row go green: that is the hand-edited pin this standard - spends a section refusing. Each of those commands keeps its own refusals, and - they all still apply. + next command, where one can be named — `update-shape.py check …` and its + `apply`, `git submodule update …` (in a holder, its `scripts/bootstrap.py`), + the project's own validator. Run the one the row names. Do not improvise a + repair, and never edit a pinned file, a digest or a manifest to make a row go + green: that is the hand-edited pin this standard spends a section refusing. + Each of those commands keeps its own refusals, and they all still apply. 4. **`placement` says what is in the wrong leg, and you never move a file to answer it.** It runs the adoption's own path policy over each mounted leg and names the paths it would have put elsewhere — code in the spec @@ -284,11 +283,10 @@ git -C push -u origin shape/update- # then open a pull request 1. **`check` first, always.** It writes nothing and prints one verdict per PINNED file: `unchanged`, `upstream-changed`, `locally-modified` (the project - edited its own copy) or `both`; also `already-at-target` and - `upstream-removed`, `unmapped` and `copy-missing`; `apply` refuses the last - three. A file with no row in `contracts/shape-pin.yaml` is not a shape copy - and is none of your business — an adopted project merged some of them away on - purpose. + edited its own copy), `both`, `already-at-target`, `upstream-removed`, + `unmapped` or `copy-missing`; `apply` refuses the last three. A file with no + row in `contracts/shape-pin.yaml` is not a shape copy and is none of your + business — an adopted project merged some of them away on purpose. 2. **Show the human the verdicts and let them say yes.** Re-pinning records which openRepoShape this project is a copy of. Do not pass `--yes` on your own initiative. @@ -391,27 +389,29 @@ python3 scripts/family.py siblings --family-root 2. **A family pins ASSEMBLY ROOTS, never legs.** `--member /`, never `-spec`. The tool refuses a leg, and the refusal is correct: a leg has no `project.yaml` and belongs to its own root. -3. **Each of `add`, `bump` and `remove` writes ONE commit** moving the gitlink - and the pin together. Land it as a pull request; never suggest a push to the - default branch. Do not hand-edit `family.yaml`'s `members:` block — the tool - rewrites it wholesale. `bump` takes the same repeatable `--trailer ": - "` as `update-shape.py apply`, for the lines its commit must end with. +3. **Each of `add`, `bump` and `remove` writes ONE commit** moving the + gitlink and the pin together. Land it as a pull request; never suggest a + push to the default branch. Do not hand-edit `family.yaml`'s `members:` + block — the tool rewrites it wholesale. `bump` takes the same repeatable + `--trailer ": "` as `update-shape.py apply`, for the lines its + commit must end with. 4. **The member must be scaffolded or adopted FIRST.** A family cannot make a project out of a repository; it can only pin one that already is. 5. **`make siblings` in the holder — or `family.py siblings --family-root - `, the same file — places the WORKSTATION layout**: each member cloned - BESIDE the holder, in the family folder, on its tracking branch, with its own - bootstrap run. Two copies of every member is intended: `members/` - inside the holder is pinned and DETACHED, for `bootstrap` and `validate`; the - sibling is where the human works. **What it never does, and neither do you:** - it MOVES NOTHING — when the parent folder is not named after the family it - warns and prints an `mv`, and running that `mv` yourself relocates a checkout - out from under a shell, an editor and every agent lane, and breaks any linked - worktree (its `.git` file carries an absolute path). It also never touches an - existing clone beyond `git fetch`: no checkout, no reset, no pull, because - somebody is working in there. A directory that is a clone of a different - repository is reported and skipped; the exit is the human's hand, not an - overwrite. + `, the same file — places the WORKSTATION layout**: each member + cloned BESIDE the holder, in the family folder, on its tracking branch, + with its own bootstrap run. Two copies of every member is intended: + `members/` inside the holder is pinned and DETACHED, for + `bootstrap` and `validate`; the sibling is where the human works. + **What it never does, and neither do you:** it MOVES NOTHING — when the + parent folder is not named after the family it warns and prints an `mv`, + and running that `mv` yourself relocates a checkout out from under a + shell, an editor and every agent lane, and breaks any linked worktree + (its `.git` file carries an absolute path). It also never touches an + existing clone beyond `git fetch`: no checkout, no reset, no pull, + because somebody is working in there. A directory that is a clone of a + different repository is reported and skipped; the exit is the human's + hand, not an overwrite. 6. `update-shape.py` updates a family root exactly as it updates a project, and the same four refusals apply. It mirrors into `family.yaml`. An existing holder receives `scripts/siblings.py` that way: `check` reports diff --git a/README.md b/README.md index a071d6bf..9ff4d30f 100644 --- a/README.md +++ b/README.md @@ -217,8 +217,7 @@ under WSL2 ships both, so there `gh` is the only install; step 4 opens with a preflight that names anything missing and where to get it. `git` itself comes from `xcode-select --install` on macOS, `sudo apt-get install -y git` on Debian and Ubuntu, `sudo dnf install -y git` on Fedora and `winget install --id Git.Git --e` on Windows — the same commands `setup-project.py --preflight` offers to run -for you. +-e` on Windows — the same commands `setup-project.py --preflight` offers to run. **2. Log in.** `gh auth login`, as the account that will own the act — a member of the organisation who can create repositories there. Accept the @@ -316,16 +315,16 @@ subcommand's in one file, generated from the tools themselves. Without installing anything — no fork, no manual clone — one command: ```sh -bash <(curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/main/setup.sh) \ - --org --project Atlas +curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/main/setup.sh \ + | bash -s -- --org --project Atlas ``` An organisation whose policy blocks raw GitHub downloads can fetch the same bytes through the API instead: ```sh -bash <(gh api repos/opensoft/openRepoShape/contents/setup.sh --jq .content \ - | base64 --decode) --org --project Atlas +gh api repos/opensoft/openRepoShape/contents/setup.sh --jq .content \ + | base64 --decode | bash -s -- --org --project Atlas ``` Shorter still, once: install the `openRepoShape` command and this becomes @@ -414,9 +413,9 @@ curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoTools/main/openRep Or type the long line, on a machine you would rather install nothing on: ```sh -bash <(curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/main/setup.sh) \ - --org Northwind --project Atlas \ - --visibility private --elected-by 'Dana Okafor' +curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/main/setup.sh \ + | bash -s -- --org Northwind --project Atlas \ + --visibility private --elected-by 'Dana Okafor' ``` #### Run @@ -680,9 +679,8 @@ family deliberately does not hold. The chain is RECORDED, never inferred — the descendant's own `naming.referent_chain: [openXdox, openDox]` names the pin it holds first and the referent its name claims last, and `validate-repository-naming.py --pins openXdox --referent-chain -openXdox,openDox` asks the same question for one name. A DIRECT PIN STAYS -SUFFICIENT: this is an addition, and `MedxChart` answers exactly as it did on -2026-09-02. +openXdox,openDox` asks the same of one name. A DIRECT PIN STAYS SUFFICIENT: this +is an addition, and `MedxChart` answers exactly as it did on 2026-09-02. Each link past the first is a declaration in ANOTHER tree — `openXdox` declaring `neutral_product_pins: [openDox]` — and it is read where that tree happens to be @@ -972,7 +970,7 @@ Moving those three facts by hand is how they come apart, so one command moves them together, run from a checkout of this standard: ```sh -python3 scripts/bump-leg.py --root --leg spec --to <40 hex> # --dry-run prints the move and writes nothing +python3 scripts/bump-leg.py --root --leg spec --to <40 hex> # --dry-run prints the move; it fetches into the leg and writes nothing else ``` It fetches the commit into the leg, checks the leg out AT it, recomputes the @@ -988,11 +986,12 @@ remote contains (including one committed in the mount and never pushed — a pin the rest of the world cannot fetch is a root it cannot bootstrap), a dirty root, an untracked or ignored workflow file it would rewrite, a root standing on its own tracking branch (the next step is a pull request, never a push to the -default branch: `git switch -c ` first; `--dry-run` writes nothing, so -it runs there and prints that command), and a validator that goes red — rolling -the pin, the workflow files, the leg's checkout and the index back first, so a -refusal leaves the tree as it was found. The leg is left DETACHED at the new -commit; `make bootstrap` re-places it on its tracking branch AT the new pin. +default branch: `git switch -c ` first; `--dry-run` fetches into the leg +and writes nothing else, so it runs there and prints that command), and a +validator that goes red — rolling the pin, the workflow files, the leg's +checkout and the index back first, so a refusal leaves the tree as it was found. +The leg is left DETACHED at the new commit; `make bootstrap` re-places it on its +tracking branch AT the new pin. ## Bootstrap is COPIED into the project, not fetched @@ -1177,11 +1176,11 @@ adoption's own policy — `contracts/path-classification.yaml`, walked with `adopt-project.py`'s own `walk()`, so there is no second copy of that classification to drift — over every tracked path of every mounted leg, and names the paths the policy would have put somewhere else, each with the rule id -that judged it, and a submodule in the other leg than its `submodules:` -record's. What belongs to **every** repository is never judged: the four files -`templates/spec-root/` and `templates/code-root/` themselves ship (`README.md`, -`AGENTS.md`, `CLAUDE.md`, `.gitignore`), read from those templates at run time -so the scaffold's own output can never be a finding, plus `LICENSE`, +that judged it, and a submodule in a leg other than the one its `submodules:` +record names. What belongs to **every** repository is never judged: the four +files `templates/spec-root/` and `templates/code-root/` themselves ship +(`README.md`, `AGENTS.md`, `CLAUDE.md`, `.gitignore`), read from those templates +at run time so the scaffold's own output can never be a finding, plus `LICENSE`, `.gitattributes`, `CODEOWNERS`, `.github/**` and the rest of the forge furniture — `--json` prints the whole list under `everywhere`, because what a report declined to look at belongs in the report. A path the policy will not call @@ -1277,12 +1276,13 @@ you. With no estate around it either, `park` lists every estate it finds under your projects directory and ASKS whether to park them all (with no terminal on stdin it refuses and names `--all`); `park --all`, or `-a`, parks them all unasked, in name order, continuing past a refusal and skipping a standalone root -with no Speckit overlay (Brett Heap's RULING of 2026-09-10, superseding #91). -`resume` has no `--all`, and its bare form still refuses — rebuilding every -estate on a fresh machine by accident is the opposite risk from failing to park -the one you meant. `--repo ` names the estate by the `origin` of a -clone you ALREADY have, in any url spelling; it never clones one, because -`resume` is the command that clones and it clones from the record. +with no Speckit overlay (Brett Heap's RULINGs of 2026-09-10, the later +superseding #91's unasked sweep). `resume` has no `--all`, and its bare form +still refuses — rebuilding every estate on a fresh machine by accident is the +opposite risk from failing to park the one you meant. `--repo ` +names the estate by the `origin` of a clone you ALREADY have, in any url +spelling; it never clones one, because `resume` is the command that clones and +it clones from the record. **What they leave behind, said out loud.** `park` finishes by printing what did NOT park and why none of it is park's to touch: commits on a `main` that diff --git a/docs/handbook.html b/docs/handbook.html index eea7333e..32d1cc22 100644 --- a/docs/handbook.html +++ b/docs/handbook.html @@ -1323,7 +1323,7 @@

    Advancing a leg

    Moving those three facts by hand is how they come apart, so one command moves them together, run from a checkout of this standard:

    python3 scripts/bump-leg.py --root <path-to-the-project> --leg spec \
    -    --to <40 hex commit>       # --dry-run prints the move and writes nothing
    + --to <40 hex commit> # --dry-run prints the move; it fetches into the leg and writes nothing else

    It fetches the commit into the leg, checks the leg out AT it, recomputes the sorted-ls-tree-r-v1 digest from the leg’s own objects, rewrites commit: and digests.tree_sha256, rewrites every TRACKED workflow @@ -1341,7 +1341,8 @@

    Advancing a leg

    and the remedy of making it readable and running the command again; a root standing on its own tracking branch (the next step is a pull request, never a push to the default branch: git switch -c <branch> first; - --dry-run writes nothing, so it runs there and prints that command); and a + --dry-run fetches into the leg and writes nothing else, so it runs there and + prints that command); and a validator that goes red — rolling the pin, the workflow files, the leg’s checkout and the index back first, so a refusal leaves the tree exactly as it was found.

    @@ -1507,7 +1508,7 @@

    Is this repository compliant? openRepoShape --doctor

    adopt-project.py’s own walk(), so there is no second copy of that classification to drift — over every tracked path of every mounted leg, and names the paths the policy would have put somewhere else, each with the rule id that judged it, - and a submodule in the other leg than its submodules: record’s. + and a submodule in a leg other than the one its submodules: record names. What belongs to every repository is never judged: the four files templates/spec-root/ and templates/code-root/ themselves ship (README.md, AGENTS.md, CLAUDE.md, @@ -1834,7 +1835,7 @@

    For AI assistants

    v1.0.0 accuracy pass found false, corrected here as in the README, added (#483) — a page cannot name the commit that contains it, and the blob below is the fact a test checks. - README.md blob a071d6bfcb10089899808cf3530b4d9a6224f9be. Every command, flag, exit code + README.md blob 9ff4d30f157027499c8ea103d5cca99e4722d357. Every command, flag, exit code and file name on this page is quoted from that README and from AGENTS.md.

    From ea6340437687588361aeb75fd30fe751f9b1f6da Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Tue, 13 Oct 2026 09:01:18 +0000 Subject: [PATCH 4/5] README.md and AGENTS.md: D1-F9 and D3-F7, what make bootstrap does with a bumped leg (#483) D1-F9: AGENTS.md:356 "re-places it on its tracking branch AT the new pin" becomes "puts it on its tracking branch AT the new pin, or says why not; relay which": since #490, templates/assembly-root/scripts/bootstrap.py:393-448 moves a branch at, behind, or (with no commit of its own) ahead of the pin, and leaves any other branch where it is with `not moving an existing branch`. D3-F7: README.md:994 and its docs/handbook.html copy (1368) end the same sentence "AT the new pin, or says why not." docs/handbook.html: README.md blob recomputed. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 2 +- README.md | 2 +- docs/handbook.html | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 496cff24..4b94a0a7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -353,7 +353,7 @@ git -C push -u origin # then open a pull request world cannot fetch is a root it cannot bootstrap. A red validator has already been rolled back; the finding above it is the thing to fix. 5. The leg is left DETACHED at the new commit. `make bootstrap` in the root - re-places it on its tracking branch AT the new pin; say so in your relay. + puts it on its tracking branch AT the new pin, or says why not; relay which. 6. **`--trailer ": "` puts a line on the commit this writes.** Repeatable, kept in the order given, as `update-shape.py apply` and `family.py bump` take it. `--dry-run` prints them after the `old -> new` diff --git a/README.md b/README.md index 3a0887ea..48ddbc90 100644 --- a/README.md +++ b/README.md @@ -991,7 +991,7 @@ and writes nothing else, so it runs there and prints that command), and a validator that goes red — rolling the pin, the workflow files, the leg's checkout and the index back first, so a refusal leaves the tree as it was found. The leg is left DETACHED at the new commit; `make bootstrap` re-places it on its -tracking branch AT the new pin. +tracking branch AT the new pin, or says why not. ## Bootstrap is COPIED into the project, not fetched diff --git a/docs/handbook.html b/docs/handbook.html index 35194806..8b09fbc4 100644 --- a/docs/handbook.html +++ b/docs/handbook.html @@ -1365,8 +1365,8 @@

    Advancing a leg

    workflow files, the leg’s checkout and the index back first, so a refusal leaves the tree exactly as it was found.

    The leg is left DETACHED at the new commit; make bootstrap re-places it on its - tracking branch AT the new pin. Editing the pin file by hand to make the validator agree is - what the validator exists to refuse.

    + tracking branch AT the new pin, or says why not. Editing the pin file by hand to make the + validator agree is what the validator exists to refuse.

    Full text — the double pin @@ -1872,7 +1872,7 @@

    For AI assistants

    from the pipe, added (#492); the statements the v1.0.0 accuracy pass found false, corrected here as in the README, added (#483) — a page cannot name the commit that contains it, and the blob below is the fact a test checks. - README.md blob 3a0887ea647f99ab498d5d1be85a5c31bcc716e5. Every command, flag, exit code + README.md blob 48ddbc90252fdec665e2ac45e7444f4df2f6ecc1. Every command, flag, exit code and file name on this page is quoted from that README and from AGENTS.md.

    From 9ac90d163228bce5a8adadb45057ca1fb7069d5d Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Tue, 13 Oct 2026 09:34:42 +0000 Subject: [PATCH 5/5] README.md: "a hyphenated project name is refused" (#483), the leg names are hyphenated and pass The reviewer's N1: "a hyphenated name is refused" read literally refused Atlas-spec and Atlas-code; the policy refuses a hyphen only in the project name. The handbook footer blob follows. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- docs/handbook.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 48ddbc90..1417b9ec 100644 --- a/README.md +++ b/README.md @@ -433,7 +433,7 @@ human says it rather than lets it default. bootstrap will run, and that `gh` is installed and authenticated — and, for `gh`, an offer to install it, log it in or set up git's credential helper, whichever is missing, which Dana answers `yes` to. (2) The three names checked against the naming -policy — `Atlas`, `Atlas-spec`, `Atlas-code`; a hyphenated name is +policy — `Atlas`, `Atlas-spec`, `Atlas-code`; a hyphenated project name is refused here with the rule printed. (3) The scaffold plan, a dry run of everything about to be created. (4) `This will create THREE repositories in 'Northwind'.` and `Type yes to continue:` — Dana types `yes`. If an AI agent is driving, it runs the diff --git a/docs/handbook.html b/docs/handbook.html index 8b09fbc4..ee0b9cc7 100644 --- a/docs/handbook.html +++ b/docs/handbook.html @@ -1872,7 +1872,7 @@

    For AI assistants

    from the pipe, added (#492); the statements the v1.0.0 accuracy pass found false, corrected here as in the README, added (#483) — a page cannot name the commit that contains it, and the blob below is the fact a test checks. - README.md blob 48ddbc90252fdec665e2ac45e7444f4df2f6ecc1. Every command, flag, exit code + README.md blob 1417b9ec0235675da8f73ca023756411bc3fbe8a. Every command, flag, exit code and file name on this page is quoted from that README and from AGENTS.md.

  • The preflight names what is missing and, for gh and its login, prints the + command for THIS machine and asks once — Type yes to continue: — + then runs it and carries on. Homebrew is the one install never run for you: on a Mac without brew the preflight names brew.sh and stops there.