Repository navigation
docs, ci: close the release with docs that match the code and a CI gate - #24
Merged
Merged
Conversation
The audit counted 1,809 em dashes in 128 files at the 2026-10-18 baseline; the port releases rewrote most of them and 139 remained in 16 files (no en dashes). Each is rewritten with a colon, comma, semicolon, period or parentheses so the sentence still reads. Code changes are limited to comments and printed strings: the handoff banner, two selftest failure messages, the sync-check header and separator, and the ops-verify unit description. No test greps any of the changed strings.
The repo had no CI, so a broken script could merge unnoticed. gate.yml runs on every push and pull request, with no secrets, so a fork's pull request gets the same run: - syntax: bash -n, node --check, python3 -m py_compile and a PowerShell parse over every tracked script, found by extension or #! line; an empty list fails the job so a broken discovery cannot pass as clean. - suites: every suite under bin/tests, the *-selftest scripts and the two PowerShell suites; destructive ones run inside sandbox-run (bubblewrap from apt, with a probe that fails loud if the sandbox cannot be built). - smoke: a fresh install in a throwaway HOME with the current Stage-1 settings, a git identity and deploy.sh --no-system run twice, then verify-ops.sh. - secrets: gitleaks over the full history from a binary pinned by SHA-256 (the official action needs a license key for organization repositories), then OPS's own secrets-scan.sh. Actions are pinned by commit. Every job is skipped outside the template repository: a private copy inherits this file, and its daily backup push would otherwise spend runner minutes on a gate written for the template. The image is ubuntu-24.04, not ubuntu-latest, because the sandbox tests depend on how the distribution's AppArmor treats bubblewrap. The PowerShell parse step runs a parser file with -File: passing the path after -Command makes pwsh append it to the command text and run each script (deploy.ps1 included) instead of parsing it.
One file lists every work the skills adapt, with its author, license, what OPS took and where the full license text lives: grill-me, tdd and diagnosing-bugs from mattpocock/skills (MIT), the writing model meta-skill-creator names from the same repository, the communication defaults adapted from i-have-adhd (MIT), the AI-tell catalog adapted from cursor/plugins unslop (MIT), and the design method adapted from anthropics/skills frontend-design (Apache-2.0). The design method carried the Apache attribution in one line with no copy of the license, which section 4 of that license asks for; the full text is now an appendix, with the MIT text of mattpocock/skills beside it. Both are reproduced verbatim from the upstream files. The file lives under SKILLS/ because every adapted work does, and the root of this repository holds only the canonical files that verify-ops.sh check 1 allows.
Every claim was checked against the scripts or by running the command. README: requirements (git with an identity, a signed-in gh, python3, jq), the T3 Code path, and sections on what deploy does, the push gate and its limits, the daily backup, skill mounts, template sync and how to check a copy. The git guard's list now includes a bare tmux kill-server and an unverified push. DEPLOYMENT: the trust anchor is ~/.claude.json, beside ~/.claude/, not ~/.claude/.claude.json as written; a failed skill clone fails the deploy (skills-mount) rather than warning; the verification block gains the exact origin-private-check line, a git identity check and the verify-ops summary line; the machinery deploy does not wire is three pieces, not two; and a new section gives the throwaway-HOME recipe that the CI smoke job runs. CONTRIBUTING: the private-term rule, where destructive tests run and why, the GIT_GUARD=off rule for a PR branch, and the full suite command. The old loop printed FAILED but always exited 0; this one counts failures, exits non-zero, and covers the self-tests and the PowerShell suites. verify-ops.sh on a bare checkout reads host state from HOME and fails on a machine with no deployed OPS, so the docs now point at the throwaway-HOME run. AGENTS and BOOTSTRAP: the CI pointer, the same verify-ops caveat, the notices file, and a git identity probe in the bootstrap recon.
The two port releases each added a 2026-10-19 entry (about 320 lines for the core boot and the fleet retirement, 70 for the hooks, sandbox and review split), so a reader met the same day twice with no overview. They are now one entry with an intro that says what the release is and why, and subsections: boot and doctrine, hooks and compaction, the private-origin push gate, backups, skill mounts, template sync, deploy and the drift gate, landing and review, the sandbox, and fixes from review. A final subsection records this change: the em dash sweep, the doc corrections, the third-party notices and the CI gate. All 51 bullets are kept word for word, so no fact is lost. The only edits are the lead-ins of the two bullets that were both titled "Review hardening", which now say what each hardened. The 2026-10-18 entry and everything below it are untouched.
Two defects found by running the gate's steps in an ubuntu:24.04 container instead of the maintainer's newer host: - The workflow scripts are ES modules (a top-level export). node --check treats a .js file as CommonJS unless the release detects module syntax, so Node 18 rejected four valid files and the result depended on the runner's node. A file that fails as a script is now checked again as a module; a real syntax error fails both ways. - The bubblewrap probe used only --ro-bind, which passes where sandbox-run's own setup (a PID and network namespace, /dev, a fresh /proc) does not, so a blocked sandbox would have shown up as every suite failing instead of at the probe. The probe now uses sandbox-run's flags, and its fallback and final check are unchanged.
The gate's jobs are guarded to the template repository, so a push to a fork starts nothing and the run begins when the PR opens. Say so, next to the instruction to run the checks locally first.
The lane body for context-coherence was a template literal with no closing
backtick, so the whole file was a syntax error ("Unexpected identifier 'LANE'"
at the next lane). Every other lane ends its body with a backtick and a comma;
this one now does too.
It went unnoticed because node --check on a file that contains an export is a
no-op on Node 24: the file is detected as a module and the check reports
success even for an unclosed brace. The CI gate checks workflow scripts as the
async function bodies they run as, which fails this file at origin/main and
passes it with this change.
The previous node step (a second try as an ES module) was still wrong. The .js files here are workflow scripts with an export const meta and a body that uses top-level await and return. node --check on such a file passes vacuously on Node 24 (the export makes it a module and the check then reports no error, even for an unclosed brace) and fails the valid files on Node 18. Each file is now checked in the shape it runs in: export dropped, the body wrapped in an async function, that file given to node --check. A planted export plus an unclosed brace fails; the valid files pass on Node 18 and Node 24.
CONTRIBUTING and AGENTS told contributors to run node --check on workflow scripts, which is wrong for them (see the gate commit); they now give the wrapper command CI uses. README and AGENTS cited "The push gate", which is a bold paragraph inside step 8 of "What Stage 2 does", not a heading; both now say so. The CHANGELOG entry gains the fixed harness-audit.js prompt and the node check.
branch-gc reads git rev-parse --show-ref-format (git 2.45) and skips every repository without it. Ubuntu 24.04's own git is 2.43, and in an ubuntu:24.04 container 71 branch-gc assertions failed for that reason, with output that never named it. The suites job now checks the version, upgrades from the git-core PPA when it is older (the runner image normally ships a newer git, so this is usually a no-op), and fails with a clear message if it still is not 2.45.
branch-gc needs git 2.45 or newer and, on an older git such as Ubuntu 24.04's 2.43, skips every repository and fails the nightly branch-cleanup stage (the log names the skip). README's requirements and DEPLOYMENT step 8 now say so, and the CHANGELOG entry records it.
branch-gc quarantines a worktree with mv --no-copy --update=none-fail.
--update=none-fail is new in GNU coreutils 9.5, and ubuntu-24.04 ships 9.4, so
there mv rejects the option ("invalid argument 'none-fail' for '--update'") and
34 branch-gc assertions across four suites fail; in an ubuntu:24.04 container
the other 21 suites pass. ubuntu-latest still means 24.04 and is announced to
move to 26.04 in November 2026. All four jobs name ubuntu-26.04, the image the
maintainer's own host matches, so the local run of these steps is representative.
branch-gc needs git 2.45 for --show-ref-format and GNU coreutils 9.5 for mv --update=none-fail. On Ubuntu 24.04 (git 2.43, coreutils 9.4) it skips every repository or cannot move a worktree to the trash, and logs it. README's requirements, DEPLOYMENT step 8 and the CHANGELOG entry say so, and the CHANGELOG entry records the runner image the gate uses.
The "Every suite" command ended with a bare echo of the failure count, so its exit status was always 0 and a red run looked green to a script, a wrapper or an && chain. CONTRIBUTING and the changelog both said it exits non-zero. End the block with [ "$fails" -eq 0 ], as gate.yml's suite steps do, so the claim is true. Checked by running the documented block: with a failing suite its exit status is 1 (it was 0 at c465ce2), with only passing suites it stays 0.
The recipe is meant to run without touching your real setup, but a clone of a URL that is a private GitHub repo let deploy's memory sync pass the push gate and push a feat(memory): seed commit to the real origin. A new HOME hides neither a GH_TOKEN/GITHUB_TOKEN/GH_CONFIG_DIR login nor SSH keys, and --no-system only skips the crontab and root's rc files. Point the clone's push URL at /dev/null so the gate answers "origin is a local path", say why next to the recipe, and note it in the changelog. Checked by running the recipe against an emulated private origin: the origin head moved at c465ce2 and stays put with the new line.
The syntax job wrapped each workflow script in an async arrow function and ran node --check on the result. A body that closes the wrapper and opens another function passes that check while the script itself is invalid. The step now builds an AsyncFunction from the body, which parses without running it, and removes only the leading `export const meta`, so a malformed later export stays invalid. Any other .js is checked in explicit CommonJS, then module, mode, because Node 24's automatic module detection can accept an unclosed brace. A workflow script is chosen by its first statement, not its path. A path rule sent SKILLS/memory-prune/audit_workflow.js to the module check, which rejects its top-level return, so the gate failed on the repository's own tree. tests/ci-gate-test.py runs the step itself over every tracked .js and fifteen fixtures. Under sandbox-run it skips when node lives in HOME (nvm), and says so; in GitHub Actions a missing node fails it. CONTRIBUTING points at that test instead of a second copy of the parser, and the gate's header no longer says the bin README lists the same commands.
The tests section of the bin README carried its own suite loop, which printed FAILED but always exited 0 and skipped the self-tests and the PowerShell suites. It now points at step 2 of CONTRIBUTING's "Before you open the PR", the command CI mirrors, and says when ci-gate-test.py skips under the sandbox.
CONTRIBUTING's suite step now says the four branch-gc suites need git 2.45 and GNU coreutils 9.5, so a run on Ubuntu 24.04 shows why they fail. It no longer says OPS_DIR="$PWD" checks a checkout directly: the core-link check still compares the deployed rule with that checkout's core.md, so from any checkout but the deployed one it fails whatever changed. AGENTS says the same. The skills index names the brand file's real path.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the 2026-10-19 port release (#21, #22, #23): docs that match the code, no em or en dashes anywhere, third-party notices, one release entry in the changelog, and the template's first CI gate.
.github/workflows/gate.yml): runs on every push and pull request with a read-only token and no secrets, so a fork's PR gets the same run. Actions are pinned by SHA and gitleaks by SHA-256. Every job is skipped outside this repository, so private copies made from the template never spend minutes on it. It runs onubuntu-26.04becausebranch-gcneeds coreutils 9.5 and git 2.45. Four jobs:bash -n, a JavaScript parse,py_compileand a PowerShell parse over every tracked script, found by extension or#!line.export const meta, which also covers the one bundled withmemory-prune..jsfiles are checked in explicit CommonJS, then module, mode.sandbox-run, and the job fails if bubblewrap cannot build a sandbox.deploy.sh --no-systemrun twice), thenverify-ops.shmust report 0 fail.secrets-scan.sh.~/.claude.json; a failed skill clone fails the deploy; deploy needs a git identity;branch-gcneeds git 2.45 and coreutils 9.5; the T3 browser step.OPS_DIR="$PWD"fails the core-link check from a checkout other than the deployed one.SKILLS/THIRD_PARTY_NOTICES.md: every adapted work, with its author, license and what OPS took. The Apache-2.0 text for the design method is now included.harness-audit.jshad an unclosed template literal, a syntax error thatnode --checkon Node 24 reported as clean.Test plan
run:step ofgate.yml, run from the committed head with the runner's bash flags: 13 of 13 steps pass. Positive controls failed the right job: a broken.sh, an extensionless bash script,.jsand.pyfiles, a.ps1, a failing suite, a missingcore.md, and a planted token.sandbox-run, plus the newci-gate-test.py: 15 cases, including the step run over every tracked.js. Its new cases fail against the path-based check they replace.verify-ops.shreports 29 ok, 0 warn, 0 fail, and the crontab is untouched.