diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3560973 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,32 @@ +# THIS REPOSITORY'S OWN INSTALLER COMPARES ITS BYTES, so the bytes have to be +# the same bytes on every platform. +# +# `openRepoTools --install` decides between `installed at`, `updated at` and +# `already installed … (unchanged)` with `cmp -s`, and +# `tests/test_openrepotools_command.py` compares an installed copy to this +# checkout's file BYTE FOR BYTE. Git for Windows' installer default is +# `core.autocrlf=true`; a checkout of THIS repository made under it turns every +# `\n` in `openRepoTools`, `park`, `resume` and `status` into `\r\n`, and every one +# of those comparisons is about line endings rather than about content — a +# second `--install` reporting three files `updated at` on a machine where +# nothing changed, and a colleague whose only sin was cloning on a stock +# Windows laptop watching the suite fail on lines nobody touched. CI never sees +# it: the Linux and macOS jobs check out LF by default and the Windows job pins +# `core.autocrlf=false`. +# +# The same rule for a second reason. `tests/test_park_resume_commands.py` +# asserts that `park`, `resume` and `status` carry the shared estate resolver +# BYTE FOR BYTE — that is what makes the deliberate duplication safe — and a CRLF +# checkout would either break the claim or hide a real drift behind it. +# +# NO PATH IS LISTED BY NAME. Every file this repository ships is text — bash, +# Python, YAML, Markdown, a workflow — so there is nothing for git's binary +# heuristic to misjudge, and `text=auto` leaves that detection where it +# belongs: with git. The pinned `upstream/openRepoShape` is a gitlink and +# carries its own copy of this rule. +# +# openRepoShape #63 wrote this rule for the standard's own root, and its #51 +# (2026-09-05) wrote it for every scaffolded project; this is the same rule +# retold for the repository that ships the two commands. + +* text=auto eol=lf diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 46feb68..a24d126 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,6 +25,33 @@ name: tests # job. A suite nothing runs is not a suite (act 3, obligation 6). It is bash, # and with macOS out of CI it runs under Linux's bash 5 only: no job here parses # it under Apple's bash 3.2 any more. +# +# THIS IS THE CODE LEG'S WORKFLOW, AND EVERY JOB IN IT IS STANDALONE +# (opensoft/openRepoTools#186, T010 of `specs/004-migrate-to-triad/tasks.md`, +# which lives in the spec leg). The leg is cloned at its own root: +# `actions/checkout` puts the code root at `$GITHUB_WORKSPACE`, named below as +# `OPENREPOTOOLS_CODE_ROOT`, and `submodules: true` (`tests-no-submodule` +# excepted, as above) initializes the one submodule the leg has, the +# dependency `upstream/openRepoShape`. There is no assembly above it and no +# spec leg beside it, so `tests/conftest.py` resolves neither: the checks of +# the documents those two own (`README.md`, `AGENTS.md`, `CLAUDE.md`, +# `docs/README-lanes.md`) SKIP here, each naming the root it needs, and +# `tests-no-submodule`'s `-rs` prints every reason. +# `OPENREPOTOOLS_COMPOSED: '0'` says in the file what the jobs are: nothing +# here is composed acceptance. That claim is the assembly root's `composed` +# workflow, which checks out all three at their exact pins and runs this same +# suite with an absent root a refusal and not a skip. +# +# The jobs and their triggers are not changed by the move: Linux only, as +# above, and `guard-launch-mode`, `tests`, `tests-no-submodule` and `package` +# on every trigger below. #228's `package` job and its `release/v*` push +# trigger belong to this leg, where releases are built and tagged from v1.1.0 +# on, and the release workflow reads those four job ids by name, so the leg +# keeps every one. What the leg needs besides this workflow is two files of +# its own: `.gitattributes`, because git never reads the assembly's into a +# submodule (`test_the_root_carries_the_line_ending_rule` reads the leg's +# copy), and `LICENSE`, which the wheel carries, both from T006's `code-0001`, +# the first commit after the split. # `pull_request` names `labeled` beside GitHub's own three defaults # (`opened`, `synchronize`, `reopened`). It was put there so that the @@ -47,6 +74,15 @@ on: - cron: '17 4 * * *' # 04:17 UTC, nightly workflow_dispatch: +# THE CHECKOUT CONTEXT, NAMED (#186, T010): the code root is the checkout's +# own root, and the run is standalone. Every job below checks out into +# `github.workspace`, so this one `env:` reaches all four, whether the suite +# is run through `tests/run.sh`, which passes both variables on as they are, +# or by pytest directly. +env: + OPENREPOTOOLS_CODE_ROOT: ${{ github.workspace }} + OPENREPOTOOLS_COMPOSED: '0' + jobs: # Keep the process launch contract independently visible while the full # lane-helper suite runs. Each GitHub job has its own workstation runner; @@ -125,7 +161,11 @@ jobs: # one nobody finishes. So one job checks out with NO submodules and asserts # the suite still exits 0: the estate tests skip, naming # `git submodule update --init upstream/openRepoShape`, and the hygiene and - # installer tests — which need nothing but this repository — still run. + # installer tests — which need nothing but this repository — still run. In + # the leg the document checks skip here too, naming the assembly or spec + # root, exactly as they do in `guard-launch-mode` and `tests`, which print + # no reasons: a skip a person cannot read is a skip nobody audits, so the + # reasons are read here. # # `pytest -q` exits 0 on skips and 1 on failures, so the job's own status IS # the assertion; `-rs` prints the skip reasons, which is what a person diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2fb4cee --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +__pycache__/ +*.py[cod] +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +*.egg-info/ +.venv/ +venv/ +.DS_Store +*.swp +.idea/ +.vscode/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..810eac1 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,40 @@ +This is the **code leg** of openRepoTools (`openrepotools`): the implementation +and its tests. + +**The project's rules are not here.** They are in the assembly root +`opensoft/openRepoTools`, in `AGENTS-shape.md` — read that before touching anything +that spans the legs. This leg is mounted there at `code/`, and the other leg, +`opensoft/openRepoTools-spec`, beside it at `../spec/`, once the root is cloned with +`--recurse-submodules`. + +Working here is ordinary — an ordinary repository on an ordinary branch. What +advancing this leg does NOT do is advance the project: that is ONE commit in +`opensoft/openRepoTools` moving the gitlink, `contracts/code-pin.yaml` and every +workflow `@` for this leg together. + +Being the code leg confers no authority over the implementation. The split is +navigation. + +## What is particular to openRepoTools' code leg + +The project's own instructions — driving `park`, `resume` and `status`, the +lane tooling's protocol, the pinned standard's three rules, how to test and the +CI policy — are the assembly root's `AGENTS.md`, and they govern here. This +section adds only what standing in this leg changes. + +- **The suite runs here**: `git submodule update --init upstream/openRepoShape`, + then `tests/run.sh`, which serializes every lane on a workstation behind + `${TMPDIR:-/tmp}/openrepotools-pytest.lock` and passes any pytest argument on. +- **The pinned standard is here**: `upstream/openRepoShape`, recorded in + `contracts/openreposhape-pin.yaml`. Never edit it in place, never pin a commit + that is not on openRepoShape's `main`, and recompute the digest rather than + adjust it — the root `AGENTS.md` gives the three rules in full. +- **The manuals and the specifications are not here.** They are the spec + leg's `docs/` (`README-lanes.md`, `README-claude-current.md`), `openspec/` and + `specs/`: `../spec/` when this leg is mounted in the assembly root, and + `opensoft/openRepoTools-spec` when it is cloned on its own. +- **A feature's code is written in its code worktree**, + `worktrees//code/` under the assembly root, on the feature's + branch. A genuine bounded amendment to an approved spec baseline is recorded + in that worktree's `features//openspec/`; a full proposal never + is, and goes to the spec leg. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5d30268 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +Read AGENTS.md. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1084a22 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# openRepoTools-code + +The **code leg** of the `openrepotools` project: the implementation and its +tests. The requirements it implements live in +[`opensoft/openRepoTools-spec`](https://github.com/opensoft/openRepoTools-spec). + +**Clone the assembly root, not this repository.** This leg is mounted as a +submodule at `code/` inside +[`opensoft/openRepoTools`](https://github.com/opensoft/openRepoTools), which +is what pins the commit of this repository that the project currently is: + +```sh +git clone --recurse-submodules https://github.com/opensoft/openRepoTools.git +cd openRepoTools +make bootstrap +``` + +Working here directly is fine — it is an ordinary repository with an ordinary +branch. What advancing this leg does NOT do is advance the project: that is a +commit in the assembly root moving the gitlink, `contracts/code-pin.yaml` and +any workflow `@` reference together. + +Being the code leg confers no authority over the implementation. The split is +navigation; authority travels in grants, and a project that keeps spec and +code in one repository is reviewed identically. + +Topic: `xf-project-openrepotools`. + +## What is in it + +Every command `openRepoTools --install` places is a file at this leg's root — +`openRepoTools` itself, `park`, `resume`, `status` and the lane tooling — with +the alias table `repos.tsv`, the `skills/` and `commands/` it installs, the +suite under `tests/` (`tests/run.sh` runs it), the CI under +`.github/workflows/`, and the pinned standard at `upstream/openRepoShape` +(`contracts/openreposhape-pin.yaml`). Installing is unchanged: the one-liner in +the assembly root's `README.md` fetches the root's `openRepoTools`, which hands +over to the installer here at the commit the root pins. + +The manuals — `docs/README-lanes.md` for the lane tooling and +`docs/README-claude-current.md` for `claude-current` and +`claude-restart-check` — and every specification are in +[`opensoft/openRepoTools-spec`](https://github.com/opensoft/openRepoTools-spec). + +Licence: Apache-2.0. See [LICENSE](LICENSE). diff --git a/tests/test_lane_helpers.sh b/tests/test_lane_helpers.sh index 9d166e8..91a85f9 100755 --- a/tests/test_lane_helpers.sh +++ b/tests/test_lane_helpers.sh @@ -180,6 +180,7 @@ The exit trap's \`rm -rf\` of the suite's sandbox **FAILED after $1 s** (whole s SANDBOX="$(mktemp -d)" # Set before the traps below, so `cleanup` never reads an inherited value. A11T_ROOT="" +A15_PROOT="" REAL_HOME="$HOME" # THE GUARDS AT THE FOOT OF THIS FILE, re-pointed (Amendment 9, act 3 @@ -238,6 +239,8 @@ cleanup() { # #180's short checkout root for the clause (c) TAIL case, which is outside # `$SANDBOX` wherever the run root is long. [ -n "${A11T_ROOT:-}" ] && [ -d "$A11T_ROOT" ] && { rm -rf -- "$A11T_ROOT" || cl_ok=0; } + # #304's, for the Amendment 15 cases that read a checkout in the row's tail. + [ -n "${A15_PROOT:-}" ] && [ -d "$A15_PROOT" ] && { rm -rf -- "$A15_PROOT" || cl_ok=0; } if [ -n "${SANDBOX:-}" ] && [ -d "$SANDBOX" ]; then rm -rf -- "$SANDBOX" || cl_ok=0 if [ -z "${TEARDOWN_SAID:-}" ]; then @@ -1269,7 +1272,23 @@ has "a free lane appends a status, not a row" "$(git -C "$WIP" log --oneline -1 # `--no-launch` run still ends `no launch` and never claims to have launched # anything. has "the appended status says what it did — and a --no-launch run never says it launched (F-B5)" "$(grep '^| `repoA-1`' "$LANES")" ", no launch" -has "…with clause (c)'s directory and window in the tail, for the person reading the row" "$(grep '^| `repoA-1`' "$LANES")" "; dir $HOME/projects/repoA; window " +# THE TAIL'S VALUES ARE NOT READ ON THIS ROW (opensoft/openRepoTools#304, the +# case #215 left when it closed #180). `cut_to_line` keeps 230 characters and +# gives up `window`, then `dir`, first, by design, and this checkout is +# `$HOME/projects/repoA`, whose length is the run root's: from a TMPDIR of 57 +# characters under C.UTF-8 (55 under C) the cut starts taking the window's +# value, and from 70 (68) the `; window ` this check read before #304. The row +# is held here to what no root reaches, the launch clause and then clause (c)'s +# `dir` key, and this act's own log line carries the directory in full. The +# row's tail with both values is read on checkouts no root lengthens: a RESUMED +# stamp's, which this one is, on `repoCase-1` and `repoMIX-1` in the Amendment +# 15 section, and a STARTED stamp's on #215's `repoA11t-1`, whose second start +# mints a session and so cannot stand in for this one. `repoA-1` keeps its +# checkout, under which the cases after this one resume it and find its +# transcripts and forks. +has "…with the launch clause and then clause (c)'s dir key in the tail, for the person reading the row" "$(grep '^| `repoA-1`' "$LANES")" "window renamed, no launch; dir " +has "…and this act's own log line carries the lane's directory in full, wherever the run root is" \ + "$(grep '^RESUMED' "$WIP/lanes/log/repoA-1.md" | tail -n1)" "; dir $HOME/projects/repoA;" hasnt "…so the stamp of a run that launched nothing claims no launch" "$(grep '^| `repoA-1`' "$LANES")" "no launch, launching" run "$START" repoA 2 --no-launch @@ -8434,10 +8453,40 @@ git -C "$WIP" commit -q -m "commit the sandbox's pending edits before the Amendm # THE FIXTURE IS THE INCIDENT'S OWN SHAPE: a MIXED-CASE row, a checkout whose # directory is spelled that way too, and a log file already written under the # LOWERCASE name — which is the state `openXfactory-2` was actually in. -mkdir -p "$HOME/projects/repoCase" -git init -q -b main "$HOME/projects/repoCase" -git -C "$HOME/projects/repoCase" remote add origin "https://github.com/opensoft/repoCase.git" -casedir="$HOME/.claude/projects/$(sanitize "$HOME/projects/repoCase")" +# +# ITS TWO CHECKOUTS ARE UNDER A SHORT PROJECTS ROOT (opensoft/openRepoTools#304, +# the cases #215 left when it closed #180, read the way #215 reads its TAIL +# case). The `repoCase-1` and `repoMIX-1` cases read the checkout in the row +# stamp's tail, which `cut_to_line` gives up first under its 230 characters; +# under `$HOME/projects` that tail was the run root's length, and from a TMPDIR +# of 71 characters under C.UTF-8 (69 under C; `repoMIX-1` from 73 and 71) the +# path was dropped whole. So both checkouts go in a root that `mktemp` makes as +# #215 makes its own, in this sandbox's own `$TMPDIR` where that is at most 40 +# characters and in `/tmp` where it is not, so the root is never over 55 +# characters and neither stamp over about 210; and the two `lane-start` runs +# below are given it as `PROJECTS_ROOT`, so `` still resolves by the +# checkout's real directory name, which is what both cases are about. Their +# two rows are also where a RESUMED stamp's `dir` and `window` are read at +# every root length, which `repoA-1`'s row in `== lane-start ==` no longer is. +# `cleanup` removes it; only a SIGKILLed run under a long root leaves one in +# `/tmp`. THE FALLBACK IS NOT #215's: where `mktemp` fails, the first check +# below says so and `A15_PROOT` itself becomes `$SANDBOX/a15-no-short-root`; +# #215 keeps `A11T_ROOT` empty there and puts its fallback in `A11T_DIR` alone. +# AND THE TWO LANES' LIFECYCLE FILES MOVE WITH THEIR CHECKOUTS: rung 2 of +# `lane_control_root` is the parent of the recorded `dir`, so they sit in +# `$A15_PROOT/.lane-state/`, outside Amendment 14's snapshot and the +# managed-owner checksum. That is harmless while neither touches these two +# lanes. +a15_base="$TMPDIR" +[ "${#a15_base}" -le 40 ] || a15_base=/tmp +A15_PROOT="$(mktemp -d "$a15_base/ort-a15.XXXXXX" 2>/dev/null || :)" +is "the Amendment 15 checkouts have a projects root of at most 55 characters, wherever the run root is" \ + "$( [ -n "$A15_PROOT" ] && [ -d "$A15_PROOT" ] && [ "${#A15_PROOT}" -le 55 ] && echo yes || echo no )" "yes" +A15_PROOT="${A15_PROOT:-$SANDBOX/a15-no-short-root}" +mkdir -p "$A15_PROOT/repoCase" +git init -q -b main "$A15_PROOT/repoCase" +git -C "$A15_PROOT/repoCase" remote add origin "https://github.com/opensoft/repoCase.git" +casedir="$HOME/.claude/projects/$(sanitize "$A15_PROOT/repoCase")" mkdir -p "$casedir" printf '{"type":"custom-title","customTitle":"repoCase-1","sessionId":"%s"}\n' "$DEAD_ID" > "$casedir/$DEAD_ID.jsonl" add_seed_row "| \`repoCase-1\` | harness \`$DEAD_ID\` | Eagle / test / brett | 2026-09-11T00:00Z | none | handoffs/repoCase/x.md | ACTIVE |" @@ -8461,10 +8510,10 @@ add_seed_row "| \`repoHold-1\` | harness \`$DEAD_ID\` | Eagle / test / brett | 2 # is the checkout on disk and `repoMIX-1` is the register's row: the `` # half resolves to the directory (15(a)) and the LANE resolves to the row, and # a run that took one answer for both would get one of the two wrong. -mkdir -p "$HOME/projects/repoMix" -git init -q -b main "$HOME/projects/repoMix" -git -C "$HOME/projects/repoMix" remote add origin "https://github.com/opensoft/repoMix.git" -mixdir="$HOME/.claude/projects/$(sanitize "$HOME/projects/repoMix")" +mkdir -p "$A15_PROOT/repoMix" +git init -q -b main "$A15_PROOT/repoMix" +git -C "$A15_PROOT/repoMix" remote add origin "https://github.com/opensoft/repoMix.git" +mixdir="$HOME/.claude/projects/$(sanitize "$A15_PROOT/repoMix")" mkdir -p "$mixdir" printf '{"type":"custom-title","customTitle":"repoMIX-1","sessionId":"%s"}\n' "$DEAD_ID" > "$mixdir/$DEAD_ID.jsonl" add_seed_row "| \`repoMIX-1\` | harness \`$DEAD_ID\` | Eagle / test / brett | 2026-09-11T00:00Z | none | handoffs/repoMix/x.md | ACTIVE |" @@ -8526,7 +8575,7 @@ git -C "$WIP" push -q origin main : > "$FAKE_TMUX_LOG" run env FAKE_TMUX_WINDOW_NAME=claude FAKE_TMUX_WINDOW="testsess:@1" FAKE_TMUX_WINDOW_INDEX=0 \ - "$START" repocase 1 --no-launch + PROJECTS_ROOT="$A15_PROOT" "$START" repocase 1 --no-launch is "a lowercase resolves to the mixed-case row and exits 0" "$rc" 0 is "…resuming the row's recorded session, --name'd with the ROW's spelling" \ "$(launch_of "$out")" "claude --name repoCase-1 --resume $DEAD_ID" @@ -8549,7 +8598,7 @@ has "…it APPENDED a status rather than adding a row" \ has "…and the stamp it appended carries the canonical spelling" \ "$(grep '^| `repoCase-1`' "$LANES")" "(lane repoCase-1)" has "…having found the checkout by its real directory name, not the typed one" \ - "$(grep '^| `repoCase-1`' "$LANES")" "dir $HOME/projects/repoCase;" + "$(grep '^| `repoCase-1`' "$LANES")" "dir $A15_PROOT/repoCase; window testsess:0 @1" # THE LOG FILE IS THE ROW'S SPELLING, AND THE RENAME IS PART OF THE WRITE. is "the lane's log is now named for the row" \ @@ -8631,7 +8680,7 @@ is "…leaving the checkout clean, so the next write is not refused for it" \ # the two answers differ, so neither can be standing in for the other. : > "$FAKE_TMUX_LOG" run env FAKE_TMUX_WINDOW_NAME=claude FAKE_TMUX_WINDOW="testsess:@1" FAKE_TMUX_WINDOW_INDEX=0 \ - "$START" repomix 1 --no-launch + PROJECTS_ROOT="$A15_PROOT" "$START" repomix 1 --no-launch is "a lane whose row is spelled unlike its checkout exits 0" "$rc" 0 has "…the repo half answering from the directory" "$err" "repo repomix → repoMix" has "…and the LANE half answering from the register row, over it" "$err" "lane repoMix-1 → repoMIX-1" @@ -8642,7 +8691,7 @@ hasnt "…never to the spelling the directory would give" "$(cat "$FAKE_TMUX_LOG is "…with no second row added under either spelling" \ "$(grep -c '^| `repoMix-1`' "$LANES")" 0 has "…and the lane's directory is still the CHECKOUT's own spelling, which the row does not carry" \ - "$(grep '^| `repoMIX-1`' "$LANES")" "dir $HOME/projects/repoMix" + "$(grep '^| `repoMIX-1`' "$LANES")" "dir $A15_PROOT/repoMix; window testsess:0 @1" # A TRANSCRIPT TITLED IN THE OTHER CASE IS STILL THIS LANE'S, AND IS RESUMED. # The row's id has no transcript here, so this is the TITLE fallback — the diff --git a/tests/test_repo_hygiene.py b/tests/test_repo_hygiene.py index 1addfd5..2a8aaf4 100644 --- a/tests/test_repo_hygiene.py +++ b/tests/test_repo_hygiene.py @@ -1977,9 +1977,19 @@ def test_agents_md_is_short_enough_to_be_read(): at offsets seven lines further on. The re-cut that makes the assembly's file again sets its cap, and `tests/test_test_roots.py` holds 347 until then. + + 330 HERE AND 347 -> 354 AT AN ASSEMBLY ROOT, on 2026-10-19 (#186, T009): + the re-cut again sets its cap, as the entry above says it does. + `assembly-0001`, re-cut against the AGENTS.md #228's series leaves, keeps + the twenty-four lines the 316 entry names — the shape's first-line pointer + and the triad map — and adds none: each hunk that had stopped applying + makes its own path repair on the text that moved under it, one line for + one. So the assembly's file is this one's 330 plus those twenty-four, 354, + measured with the re-cut applied by `git am --3way` to a root built from + the split's root files, and `tests/test_test_roots.py` holds 354. """ lines = doc("AGENTS.md").read_text().splitlines() - cap = 330 if ROOTS.assembly is None else 347 + cap = 330 if ROOTS.assembly is None else 354 assert len(lines) <= cap, f"AGENTS.md is {len(lines)} lines; the cap is {cap}" @@ -2512,9 +2522,21 @@ def test_readme_is_short_enough_to_be_read(): makes the assembly's file again sets its cap, as the 2026-10-18 AGENTS.md entry above says for its 347, and `tests/test_test_roots.py` holds 510 until then. + + 505 HERE AND 510 -> 529 AT AN ASSEMBLY ROOT, on 2026-10-19 (#186, T009): + the re-cut again sets its cap, as the entry above says it does. + `assembly-0001`, re-cut against this README, keeps the twenty-four lines + the 2026-10-08 entry names and adds none: the three-repository section + with the lockstep invariant, nineteen; the assembly's install sentence, + four, still straight after the two install lines and so now above T024's + one-route paragraph; and the `cd code` of the pin-bump block, one. The + three hunks the entry above names keep their own lines, one for one. So + the assembly's README is this one's 505 plus twenty-four, 529, measured on + the re-cut as the AGENTS.md entry of this day measures 354, and + `tests/test_test_roots.py` holds 529. """ lines = doc("README.md").read_text().splitlines() - cap = 505 if ROOTS.assembly is None else 510 + cap = 505 if ROOTS.assembly is None else 529 assert len(lines) <= cap, f"README.md is {len(lines)} lines; the cap is {cap}" diff --git a/tests/test_test_roots.py b/tests/test_test_roots.py index f9c0dca..9c784ed 100644 --- a/tests/test_test_roots.py +++ b/tests/test_test_roots.py @@ -360,21 +360,20 @@ def test_the_pin_rules_are_read_with_the_assemblys_code_prefix(tmp_path, prefixe def test_the_length_caps_are_the_assembly_roots_where_one_is_named(tmp_path): - """At an assembly root the two caps are 347 and 510. The dated entries in - `tests/test_repo_hygiene.py` made them from this repository's then caps, - 323 and 486, and the twenty-four lines each that T006's root guidance adds - there, and they stand there while this repository's own are 330 and 505 at - this head: `assembly-0001` no longer applies to today's AGENTS.md or - README, so the re-cut that makes the assembly's files again sets them. And - they are asked of the ASSEMBLY's files: one line over FAILS, the cap itself - passes. Red at `origin/main`, which counted this checkout's own README and - AGENTS.md whatever was named.""" + """At an assembly root the two caps are 354 and 529: this repository's 330 + and 505 and the twenty-four lines each that T006's root guidance adds + there, set again on 2026-10-19 by the re-cut of `assembly-0001` (the + dated entries in `tests/test_repo_hygiene.py`; they were 347 and 510, from + the 323 and 486 of 2026-10-09, and stood while the old patch no longer + applied). And they are asked of the ASSEMBLY's files: one line over FAILS, + the cap itself passes. Red at `origin/main`, which counted this checkout's + own README and AGENTS.md whatever was named.""" def lines(n: int) -> str: return "".join(f"line {i}\n" for i in range(n)) - at_cap = assembly(tmp_path / "at", readme=lines(510)) - (at_cap / "AGENTS.md").write_text(lines(347), encoding="utf-8") - over = assembly(tmp_path / "over", readme=lines(511)) - (over / "AGENTS.md").write_text(lines(348), encoding="utf-8") + at_cap = assembly(tmp_path / "at", readme=lines(529)) + (at_cap / "AGENTS.md").write_text(lines(354), encoding="utf-8") + over = assembly(tmp_path / "over", readme=lines(530)) + (over / "AGENTS.md").write_text(lines(355), encoding="utf-8") select = f"{AGENTS_CAP_TEST} or {README_CAP_TEST}" green = nested(tmp_path, {"OPENREPOTOOLS_ASSEMBLY_ROOT": str(at_cap)}, select, "test_repo_hygiene.py") @@ -382,8 +381,8 @@ def lines(n: int) -> str: assert green.outcome(name)[0] == "passed", green.output[-3000:] red = nested(tmp_path, {"OPENREPOTOOLS_ASSEMBLY_ROOT": str(over)}, select, "test_repo_hygiene.py") - for name, said in ((AGENTS_CAP_TEST, "AGENTS.md is 348 lines; the cap is 347"), - (README_CAP_TEST, "README.md is 511 lines; the cap is 510")): + for name, said in ((AGENTS_CAP_TEST, "AGENTS.md is 355 lines; the cap is 354"), + (README_CAP_TEST, "README.md is 530 lines; the cap is 529")): outcome, message = red.outcome(name) assert outcome == "failure" and said in message, ( f"{name} {outcome} on an assembly file one line over its cap:\n"