[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6J44P91GHQ7AD8CGM9XKMMH":3,"skill-same:01M6J44P91GHQ7AD8CGM9XKMMH":138},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":52,"commitSha":53,"skillDirectory":54,"forksCount":14,"repositoryHtmlUrl":55,"repositoryHomepageUrl":6,"ownerHtmlUrl":56,"ownerBlogUrl":6,"ownerDescription":57,"updatedAt":58,"install":59,"resources":71},"01M6J44P91GHQ7AD8CGM9XKMMH","ship",null,"Sync all project documentation, optionally update a plan, then commit and push to git.\nUpdates status.md (session log), syncs CLAUDE.md to AGENTS.md, and handles git workflow.\nUse when the user is ready to commit and push their work.\nRecommend it on: \"ship it\", \"commit and push\", \"push my changes\", \"ready to ship\".\nDO NOT INVOKE THIS SKILL ON YOUR OWN INITIATIVE — it commits and pushes, which is not\nundoable once the push lands. Run it only when the user asked for it in their own words\nthis turn. If they merely approved a design (\"looks good, ship it\" about a plan), say\n\u002Fship is available and stop. Their invocation IS the approval that lets every step skip\nconfirmation, which is exactly why it cannot be self-invoked.\nFull command syntax: see the Usage section in this skill.","Lbstrydom\u002Fclaude-engineering-skills","Lbstrydom","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F77991486?v=4","01M6J3X4N2T0FTZT1RKFXDVS0K","Louis Strydom","01M6J3X4SXV0BXWDMXPW90D8BK",0,"MIT","https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[5,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"command","invoke","stop","full","manager","status","git","recommend","update","why","run","plan","stack","log","git workflow","confirmation","design","project","which","ready","2026-11-10 10:12:30",false,"---\nname: ship\ndescription: |\n  Sync all project documentation, optionally update a plan, then commit and push to git.\n  Updates status.md (session log), syncs CLAUDE.md to AGENTS.md, and handles git workflow.\n  Use when the user is ready to commit and push their work.\n  Recommend it on: \"ship it\", \"commit and push\", \"push my changes\", \"ready to ship\".\n  DO NOT INVOKE THIS SKILL ON YOUR OWN INITIATIVE — it commits and pushes, which is not\n  undoable once the push lands. Run it only when the user asked for it in their own words\n  this turn. If they merely approved a design (\"looks good, ship it\" about a plan), say\n  \u002Fship is available and stop. Their invocation IS the approval that lets every step skip\n  confirmation, which is exactly why it cannot be self-invoked.\n  Full command syntax: see the Usage section in this skill.\ndisable-model-invocation: true\nallowed-tools: Bash(node scripts\u002Fcross-skill.mjs detect-stack --include-env-manager)\n---\n\n# Ship: Sync Docs → Commit → Push\n\n> **This skill's last line is an end marker naming the skill between em-dashes, after\n> the Reference files table.** If that last line is not in your context (context\n> compaction keeps only the start of a skill), re-read\n> `.claude\u002Fskills\u002Fship\u002FSKILL.md` in full before continuing — the later steps still apply.\n\n> **Explicit invocation only — this is a host-neutral requirement, not just a\n> frontmatter flag.** `disable-model-invocation: true` enforces it in Claude Code\n> and VS Code Copilot; other hosts are not guaranteed to, so it is stated here too:\n>\n> **Do not start this skill because a message sounded like approval.** Start it\n> only when the user asked, in their own words this turn, for their work to be\n> committed and pushed. \"Looks good, ship it\" said about a plan, a diff or a\n> design is assent to the *idea*, not an instruction to push — answer it by\n> offering `\u002Fship`, never by running it.\n>\n> Every step below skips confirmation because the user chose to run this; a\n> self-invoked run would cite its own execution as consent.\n> → `references\u002Fflow-rationale.md` §Explicit invocation — read when deciding\n> whether a message is an invocation, or before changing this rule.\n\nA single command that ensures all project documentation is current, then\ncommits and pushes. Follow every step in order.\n\n## Usage\n\n| Invocation | Effect |\n|---|---|\n| `\u002Fship` | sync docs + commit + push |\n| `\u002Fship docs\u002Fplans\u002F\u003Cname>.md` | also update that plan before committing |\n| `\u002Fship --no-tests` | skip pre-push tests (override; logged in `ship_event`) |\n| `\u002Fship --ignore-p0` | push despite an unresolved persona-test P0 finding |\n| `\u002Fship --skip-ux-lock` | push despite an unlocked recent UI fix |\n| `\u002Fship --train [--waive n,…] [--allow-unaudited n,…] [--end-of-day]` | land a batch of ship-ready PRs in one action (see **Train mode**) |\n\nThe three override flags are honoured only when the **user** passes them; see\nStep 0.5f's override handling. Never add one on the skill's own initiative to get\npast a red gate — that is the gate working.\n\n**Arguments**: `$ARGUMENTS` — optional path to a plan file to update\n(e.g., `docs\u002Fplans\u002F\u003Cname>.md`).\n\n\u003C!-- host-contract: input-acquisition; grammar=path+flags; empty=default -->\n\n**Where `$ARGUMENTS` comes from** — orchestrator-supplied input first, else\nthe host's verbatim invocation suffix, else the span of the user's **current**\nmessage naming this skill or its subject. Never inferred from surrounding\nconversation. This site is `path+flags`; on empty input, ship without updating a plan — that is the documented default. Never adopt a plan path mentioned earlier in the conversation.\nFull contract: `references\u002Finput-acquisition.md`.\n\n\n> **Worktree preflight** — in a linked git worktree the synced tooling tree\n> `scripts\u002F.claude-skills\u002F` is absent — it is gitignored, so `git worktree add`\n> does not populate it, and every command below that uses it dies on a bare\n> `MODULE_NOT_FOUND`. Run `npm run skills:hydrate` first. If this repo defines\n> no such script, `references\u002Fworktree-preflight.md` holds the entry to add — ask\n> the user first, since it edits their `package.json`.\n\n**Ship-specific, because here the wrong answer is silent instead of loud**: do not\nreach the tooling by `cd`-ing to the main checkout. `ship-commit.mjs` and\n`cross-skill.mjs` read HEAD, branch and `commit_sha` from cwd — from there they\nwould commit and attribute the **wrong tree**, with no error. Phase 0's\n`detect-stack` is already one of these commands, so resolve this before it.\n\n---\n\n## Phase 0 — Repo Stack Detection\n\n```bash\nnode scripts\u002Fcross-skill.mjs detect-stack --include-env-manager\n```\n\nReturns `{ stack, pythonFramework, environmentManager, detectedFrom }`.\n\n| `stack` | Behaviour |\n|---|---|\n| `js-ts` | Pre-push: `npm test`, linter + type-check + format if configured |\n| `python` | Pre-push: see `references\u002Fpython-environment-discovery.md` — env wrapper + tool probe |\n| `mixed` | Run BOTH stacks' checks — required-tool absence in either blocks |\n| `unknown` | Skip stack-specific checks; proceed with universal git workflow |\n\n**Source repo, before the full push check:** `npm run guards:preflight` (about 1-2 min) runs the whole-suite guard tests and static checks that a targeted test run cannot reach, as the real checks and not copies. It is an early look, and the pre-push run still covers everything; a result marked INCOMPLETE is not a pass. Rationale: the source repo's test-cost runbook, section 0.\n\nPython framework (if detected) shapes status.md section titles — see\n`references\u002Fstatus-md-format.md`.\n\n---\n\n## Step 0.5 — Pre-Ship Gate Queries (non-blocking by default)\n\nCollect signals before proceeding so the ship_event emitted at the end\nis accurate. Best-effort — if a query fails, log and proceed. Sub-step order:\n`0.5a → 0.5b → 0.5e → 0.5g → 0.5h → 0.5i → 0.5f`, then the H2 Steps 0.5c and 0.5d.\nIncident history behind every rule below: `references\u002Fpre-ship-gate-queries.md`\n— read when debugging a nudge's behaviour, or before changing one.\n\n### 0.5a — Recent persona-test P0s for this repo\n\nRun bare — the command resolves the repo itself (`--repo` -> `PERSONA_TEST_REPO_NAME` -> ambient `git remote`).\n**Never pass `--repo \"$PERSONA_TEST_REPO_NAME\"` as a shell expansion** — see\n`references\u002Fpre-ship-gate-queries.md` §0.5a for why.\n\n```bash\nnode scripts\u002Fcross-skill.mjs persona-outcomes summary\n```\n\nReturns `{ok, cloud, measured, scope:{mode,repoId,slug}, sessionId,\nsessionCreatedAt, persona, verdict, rawP0, rawP1, labeled: {closed,\nopen_relabeled_fixed, open_relabeled_stale, unlabeled, pending_verification},\nopenP0, openP1, pendingVerificationP0, pendingVerificationP1}`.\n\n**Check `measured` BEFORE reading any count** (same rule as Step 0.5b).\n`measured: false` means the gate asked nothing — report it as UNMEASURED,\nnaming the resolution it attempted, and NEVER as \"gate silent\":\n\n```\n⚠ UX GATE — UNMEASURED (\u003Creason>)\n  No repo slug resolved (tried: --repo, PERSONA_TEST_REPO_NAME, git remote).\n  This is NOT \"no open P0s\" — nothing was read.\n  Scope it: node scripts\u002Fcross-skill.mjs persona-outcomes summary --repo owner\u002Fname\n```\n\n**Closed failure semantics — never a NEW blocker**:\n- `cloud: false` → proceed without the UX gate, exactly as today.\n- `measured: false` → print the UNMEASURED card above, then proceed.\n- `sessionId: null` **with `measured: true`** → the gate is genuinely silent —\n  name the repo it resolved (`scope.slug`, `scope.mode`).\n- `ok: false` (a real store\u002Fquery failure) → log one warning line and fall\n  back to the legacy raw read, same `measured`\u002F`scope` fields:\n  ```bash\n  node scripts\u002Fcross-skill.mjs get-persona-sessions-by-repo     --limit 1 --p0-only     --select persona,focus,verdict,p0_count,p1_count,created_at,debrief_md\n  ```\n  (uses that session's raw `p0_count`\u002F`p1_count` as `open_p0_count`\u002F`open_p1_count`).\n\n**`pendingVerification*` is neither open nor clear** — a `fixed` label from the\nvery session being read has no newer evidence either way. When\n`pendingVerificationP0 > 0`, print one line and proceed:\n\n```\n  \u003Cn> P0 fix(es) awaiting verification — re-run \u002Fpersona-test to confirm.\n```\n\nCapture `openP0` + `openP1` (or the legacy fallback's `p0_count`\u002F`p1_count`)\nas `open_p0_count`\u002F`open_p1_count` for the ship_event record. If `openP0 > 0`\n(or the legacy fallback's `p0_count > 0`):\n\n```\n⚠ UX GATE (non-blocking)\n  Last persona test: \"\u003Cpersona>\" — \u003CN> days ago → \u003Cverdict> (P0: \u003Cn>, P1: \u003Cn>)\n  Unresolved P0s detected. These are user-visible broken flows.\n  Shipping anyway — consider fixing before next user-facing release.\n  Label fixed\u002Fdismissed P0s: node scripts\u002Fcross-skill.mjs persona-outcomes --worksheet\n```\n\nThe worksheet line only appears when the PRIMARY read succeeded (the legacy\nfallback path has no outcome ledger to label against).\n\n### 0.5b — Fixes that lack a \u002Fux-lock regression spec\n\n```bash\nnode scripts\u002Fcross-skill.mjs list-unlocked-fixes\n```\n\nReturns `{ok, cloud, scope:{mode,repoId,slug}, measured, reason, rows, shown, total,\nbyMode:{total,code,plan}, allAges, agedOut, agedOutByMode:{code,plan}, prePractice,\npracticeStart}`.\n\n**Check `measured` BEFORE reading any count.** `measured:false` means *nothing\nwas measured* (`reason: repo-identity-unresolvable` \u002F `cloud-off`) — the zeroes\nare \"not applicable\", **not** \"no obligations\". Report it as unmeasured; never\nrender it as a clean backlog.\n\n**Watch `agedOut` — it should be 0**: non-zero means obligations expired from the\n14-day window while a locking practice was live (a real leak). `prePractice`\nexpired before this repo's first lock (`practiceStart`) — not an obligation. Read\npast the window with `--all-ages`; `total`\u002F`byMode` then describe the unwindowed\nset:\n\n```bash\nnode scripts\u002Fcross-skill.mjs list-unlocked-fixes --all-ages\n```\n\n**Never hand-derive a repo id** — pass no flag (ambient identity) or\n`--repo \u003Cowner\u002Frepo>`; `--repo-id` is verified against `audit_repos`, and an\nunresolvable one reports `measured:false` rather than an authoritative empty\nbacklog.\n\n**Use `byMode.code` as `missing_spec_count` — NOT `rows.length`.** `rows` is\ncapped at 20, and `byMode.plan` rows are section references for which **no lock\nof any kind can ever exist**.\n\n**`danglingLocks` — a recorded lock whose test file is no longer there.**\n`count: null` means the question went unasked (cloud off, unresolved repo, a\nread that threw) — distinct from `0`. When `count > 0`, print the sampled rows\nand act on each:\n\n```bash\nnode scripts\u002Fcross-skill.mjs repoint-regression-spec --finding FINDING_UUID --test tests\u002Fdangling-regression-lock.test.mjs --description \"why the old artefact went away\"\n```\n\n`--delete` instead of `--test` where no test discharges the finding at all —\nthat is the honest outcome, not a lesser one: the finding returns to\n`unlocked_fixes` and gets raised again. The command refuses a missing target\npath, an unresolvable repo, and an ambiguous `(repo, finding)`. Incident\nhistory + why each of these rules exists: `references\u002Fpre-ship-gate-queries.md`\n§0.5b.\n\n`unlocked_fixes` has no UI-relevance filter — it fires identically for a\nDOM-facing fix and a pure backend\u002FCLI one, and `\u002Fux-lock` can only ever cover\nthe former. Judge each row by `primary_file` before suggesting a fix, if\n`byMode.code > 0`:\n\n```\n⚠ REGRESSION LOCK GATE (non-blocking)\n  \u003CbyMode.code> code fix(es) have no locked regression coverage\n  (+ \u003CbyMode.plan> plan finding(s), which cannot be locked — not an obligation):\n    • \u003Cprimary_file>: \u003Cone-line detail>\n  These will silently regress under future refactors.\n  Backend\u002FCLI\u002Flibrary fix → a unit or integration test IS the lock. Record it:\n    node scripts\u002Fcross-skill.mjs lock-with-test --worksheet\n  (reviewed queue; read the test before locking — a same-named file is not\n  proof of coverage, and the writer refuses a missing path or empty rationale).\n  UI\u002FDOM-facing fix → \u002Fux-lock; on React surfaces prefer a unit test unless\n  the contract genuinely needs a live DOM.\n```\n\n(The React caveat's history → `references\u002Fflow-rationale.md` §0.5b — read when\ndeciding whether \u002Fux-lock fits a React surface.)\n\nIf `agedOutByMode.code > 0`, print it too — lead with the CODE split, not the\nmixed `agedOut` total (a plan row can never be locked, so it is never a lost\nobligation):\n\n```\n⚠ OBLIGATIONS LOST TO THE WINDOW (non-blocking)\n  \u003CagedOutByMode.code> code fix(es) aged out of the 14-day window UNLOCKED,\n  after this repo started locking (\u003CpracticeStart>)\n  (+ \u003CagedOutByMode.plan> plan finding(s), which cannot be locked — not an obligation).\n  Waiting is not a way to clear this gate. Read them:\n    node scripts\u002Fcross-skill.mjs list-unlocked-fixes --all-ages\n  Then either lock them, or write them off in status.md so the decision is on\n  the record — an obligation discharged by silence is the thing this counts.\n```\n\nA plan-only `agedOut` (`agedOutByMode.code == 0`) prints nothing — that banner\nwould be a false alarm. Do **not** print `prePractice` as a backlog — it is\nbookkeeping for findings that predate the practice, not work anybody owes.\n\n**Re-running existing regression specs before a push** (optional gate): drive\nthem through the deterministic runner with the ship `run_context` so the\n`regression_spec_runs` rows are tagged correctly and written without the model:\n\n```bash\nnode scripts\u002Fux-lock-run.mjs spec --specs 'tests\u002Fe2e\u002F*.spec.js' --commit \"$(git rev-parse HEAD)\" --run-context ship-gate\n```\n\nOptional: `--url BASE_URL` when the specs need a non-default base URL.\n\nA non-zero exit means a locked contract broke — treat as a `test-failure`\nblock reason. Cloud off → it still runs + prints; Playwright missing → exit 5\n(skip the gate, don't fail the ship on a missing optional dep).\n\n### 0.5e — Accepted findings that were never remediated\n\n**First, a capped auto-reconcile pass** (best-effort, never blocks, no\noverride flag) — closes what the live-audit lifecycle's session\u002Fround\u002F14-day\nbounds will never revisit on their own:\n\n```bash\nnode scripts\u002Fremediation-reconcile.mjs --apply --cap 5 2>\u002Fdev\u002Fnull\n```\n\nSwallow any failure like every other 0.5-step. Report its one-line summary\nalongside the nudge below: `Auto-reconciled: \u003Cresolved> verified\n(\u003CmechanicallyResolved> of those by file-deletion, no LLM call), \u003CstillPresent>\nstill open, \u003Cuncertain> uncertain` — `resolved` already includes the\nmechanically-resolved count. `AUDIT_REMEDIATION_RECONCILE_ENABLED=false` opts\nout entirely; an absent Claude credential degrades to the free\nmechanical-resolution path only, never a failure.\n\n```bash\nnode scripts\u002Fcross-skill.mjs list-unremediated-acceptances\n```\n\nReturns `{ok, cloud, scope:{mode,repoId,slug}, measured, reason, rows, shown,\ntotal, byMode:{total,code,plan}, allAges, agedOut, agedOutByMode:{code,plan},\nagedOutBySeverity:{HIGH,MEDIUM}, notYetDue, prePractice, practiceStart}`.\n\n**Check `measured` BEFORE reading the count** — identical contract to 0.5b.\n`measured:false` means *nothing was measured*; an empty `rows` then means \"not\napplicable\", **not** \"no unremediated acceptances\".\n\n**Two bounds, and only one of them forgets**: `notYetDue` (under the 7-day\nfloor) is in flight — **never add it to `agedOut`, never report it as a\nbacklog**; mention it only if `total` is 0 and `notYetDue` is not. `agedOut`\n(over the 30-day ceiling, after `practiceStart`) is a real leak. `prePractice`\nis not an obligation. `--all-ages` drops both bounds; `total`\u002F`byMode` then\ndescribe the unwindowed set:\n\n```bash\nnode scripts\u002Fcross-skill.mjs list-unremediated-acceptances --all-ages\n```\n\n**Use `byMode.total` as `unremediated_count` — NEVER `rows.length`** (capped\nat 20). `byMode.plan` rows depend on the plan's status:\n- **Plan still in flight** → real work; treat it as an obligation.\n- **Plan marked Complete** → write the class off: record it as `dismissed`\n  (the store has no \"written off\" state) with the real reasoning in\n  `status.md`. A write-off covers the *document* obligation only.\n\n`accepted` is not evidence of a fix, and a row can also be already fixed with\nnobody having written it down. Treat each row as a\nhypothesis about current code, verify it, then close it in the right\ndirection (`--state fixed` or a dismissal) rather than assuming either.\n\nIf `> 0`, print — never blocks, no override flag. **Show the first 5 rows as\nreturned** (HIGH first, then oldest first — do not re-sort). To read past the\ndefault 20-row page, pass the payload's `nextCursor` back as `--after` (a keyset\ncursor — never `--offset`); `nextCursor: null` is the end\n(→ `references\u002Fflow-rationale.md` §0.5e — read when paging while closing rows):\n\n```bash\nnode scripts\u002Fcross-skill.mjs list-unremediated-acceptances --limit 20 --after eyJ2IjoxLCJraW5kIjoidW5yZW1lZGlhdGVkIiwic2V2ZXJpdHlSYW5rIjowLCJhY2NlcHRlZEF0IjoiMjAyNi0wOS0xMyAxMDowMDowMC4wMDAwMDErMDAiLCJmaW5kaW5nSWQiOiIxMTExMTExMS0xMTExLTQxMTEtODExMS0xMTExMTExMTExMTEifQ\n```\n\nThe payload echoes the resolved `limit` (store clamps to 200), so a short page\ncan be told from an exhausted one. `list-unlocked-fixes` pages the same way.\n\n```\n⚠ UNREMEDIATED ACCEPTANCES (non-blocking)\n  \u003CbyDisposition.open> open · \u003CbyDisposition.acceptedPermanent> permanently accepted\n  \u003Cn> finding(s) you accepted were never marked fixed (showing \u003C=5):\n    • [\u003Cseverity>] \u003Cprimary_file> — accepted \u003Cdays_open>d ago\n  Either remediate them, or close the loop honestly — per row:\n    node scripts\u002Fcross-skill.mjs close-finding --finding \u003Caudit_finding_id> \\\n      --as fixed --commit \u003Csha that fixed it>\n    (or --as dismissed \u002F --as accepted-permanent, each with --reason \"...\")\n  Leaving them open is fine — leaving them open SILENTLY is what this catches.\n```\n\n**`byDisposition.acceptedPermanent` is a decision, not a backlog — print it,\ndo not chase it.** Those rows are excluded from `open`\u002F`total`; `open === total`\nalways.\n\nIf `agedOut > 0`, print it separately — split by mode, since the two halves\nclose by different acts (a code row needs a fix; a plan row is an obligation\nonly while its plan is still in flight):\n\n```\n⚠ ACCEPTANCES LOST TO THE CEILING (non-blocking)\n  \u003CagedOutByMode.code> code finding(s) passed the 30-day ceiling still unremediated,\n  after this repo started recording remediations (\u003CpracticeStart>)\n  (+ \u003CagedOutByMode.plan> plan section(s) — an obligation only while that plan is\n  still in flight; on a Complete plan it is a shipped design doc, write it off).\n  \u003CagedOutBySeverity.HIGH> of the \u003CagedOut> are HIGH, across both modes.\n  They will not appear above again. Read them:\n    node scripts\u002Fcross-skill.mjs list-unremediated-acceptances --all-ages\n  Then remediate, or close the loop with close-finding, or write them off\n  (close-finding --as dismissed, reasoning in status.md) — an obligation\n  discharged by silence is what this counts.\n```\n\n**Close rows with `close-finding`, by the row's `audit_finding_id`** — not\n`finalize-outcomes` (it needs a live round's `--ledger`\u002F`--result`) and not\n`final-review-record-fix`, which only resolves final-review-pass rows and so\nanswers `no-match` for an ordinary audit finding. `--as fixed` takes `--commit`;\n`--as dismissed` (not a defect, or\na written-off plan section) and `--as accepted-permanent` (real, declined on\nthe merits) take `--reason`. A finding outside this repo is refused.\n\nJudge the list before echoing it: an `audit_mode = 'plan'` row's `primary_file`\nis a plan SECTION reference, not a file — say so rather than printing it as a\ncode path; a `remediation_state = 'planned'` row with a live plan is in flight —\ndrop it from the printed list.\n\n### 0.5g — Migration realization gate (ENFORCED by the binary, checked HERE too)\n\nA commit that ships a migration is only half-shipped until the migration is\nAPPLIED — a migration can be committed, tests green, pushed, and still be\nbyte-for-byte inert if nobody ran `--migrate`.\n\n**Run the read-only preflight now, before Steps 1–6.2's doc work and the\npre-push hook's readiness suite** — not just at Step 6.3:\n\n```bash\nnode scripts\u002Fship-commit.mjs --check-migrations\n```\n\n- **The real enforcement stays at Step 6.3** — `ship-commit.mjs` performs the\n  check again inside the commit path and exits 2 there regardless of whether\n  this preflight ran. This early run is advisory, not a substitute gate.\n- **Unconditional when the cloud store is on** — deliberately NOT gated on\n  \"the push range touches `supabase\u002Fmigrations\u002F`\": a code-only commit can\n  depend on a migration left unapplied by an *earlier* push or a branch switch.\n- **Cloud off \u002F unreachable \u002F no ledger ⇒ silently skipped**, never a block.\n- **On a block**: run `node scripts\u002Fsetup-postgres.mjs --migrate`, then\n  continue — Step 6.3 will pass without a retry once applied.\n\n**Consumers are on their own stores — check theirs too** (source-repo only,\n`--check-migrations` above asks only about the AMBIENT store):\n\n```bash\nnpm run stores:drift\n```\n\nPrint its stdout verbatim — it renders the finished card. **Never blocks, no\noverride flag** — applying a migration to a consumer's production database is\nan operator decision. **Read the `unqueried` \u002F `no store` lines** — a store\nnobody could reach is reported explicitly, never counted as current; if NO\nstore answered, the card says `NOTHING WAS CHECKED` instead of `all current`.\n\n**The runtime DSN usually cannot apply migrations** — do NOT grant the runtime\nrole ownership or put an admin DSN in `.env`. → `references\u002Fmigration-credentials.md`\n— read when `--migrate` is refused with `42501`.\n\n### 0.5h — Upstream issue queue (advisory, source-repo only)\n\n**Source-repo-gated** — run ONLY when `package.json.name === \"claude-engineering-skills\"`.\nConsumers FILE reports (`cross-skill.mjs upstream report`); this repo is where\nthey get triaged, and nothing else prompts anyone to read them.\n\n```bash\nnpm run upstream:queues 2>\u002Fdev\u002Fnull\n```\n\nPrint its stdout verbatim — it renders the finished card, reading **every\nregistered consumer's own store** (fingerprint plus consumer names, never a DSN\nor hostname).\n\n**Never blocks, and there is no override flag** — the queue is cloud state,\nnot repo state; it always exits 0. **Read the `unqueried` \u002F `no store` lines,\nand do not treat the count as complete when either is present** —\n`NOTHING WAS CHECKED` means an unasked question, not a clean queue.\n\nTriaging a report is a separate act from shipping: run `ack|fix|wont-fix` only\nagainst **the store that owns the row**, by the **FULL uuid**, after checking\n`freshness`\u002F`priorFixes` → `references\u002Fupstream-triage.md` — read before closing\nany report. Incident history: `references\u002Fpre-ship-gate-queries.md` §0.5h.\n\n### 0.5i — Stalled comparison campaigns (advisory, source-repo only)\n\n**Source-repo-gated** — run ONLY when `package.json.name === \"claude-engineering-skills\"`.\nCampaigns are declared in `.campaigns\u002F`, which exists only here; a consumer\nhas no campaign to be stalled on.\n\nA campaign short of `targetN` is **not decision-eligible** — the spend is\nbanked but the evidence cannot answer anything yet.\n\n```bash\nnode scripts\u002Fcampaign.mjs stale 2>\u002Fdev\u002Fnull\n```\n\n**Never blocks, no override flag** — collection state is cloud state the\ncommit cannot change. Silent when no campaign is stalled, the store is\nunreachable, or a campaign has never collected at all (never-started is not\nstalled). Prints its own card; pass it through verbatim. Do **not** collect now\nbecause of it — collection is spend-bearing and revision-pinned, a deliberate\nscheduled act (`references\u002Fpre-ship-gate-queries.md` §0.5i).\n\n### 0.5f — Override flags\n\nIf `$ARGUMENTS` contains `--no-tests`, `--ignore-p0`, or `--skip-ux-lock`,\n\n\u003C!-- host-contract: input-acquisition; grammar=path+flags; empty=default -->\n_This site: `path+flags` — **no flag means no override.** Each of these disables a gate, so they are read ONLY from the invocation — a conversation that merely mentions skipping tests must never become one._\n\nrecord which override is active — it goes into the ship_event.\n\n(Why this is `0.5f`, not `0.5d`: `references\u002Fflow-rationale.md` §0.5f — read\nbefore renumbering any Step 0.5 sub-step.)\n\n---\n\n## Step 0.5c — Architectural Memory Refresh (advisory)\n\nIf the architectural memory is configured for this repo (per the\n`docs\u002Fplans\u002Farchitectural-memory.md` rollout), refresh the per-repo\nsymbol-index and regenerate `docs\u002Farchitecture-map.md` so the LOCAL map matches\nwhat's about to ship. The map itself is **never committed** — see below.\n\n```bash\n# Determine since-commit (last shipped). Use upstream\u002Forigin HEAD as a proxy\n# when no \u002Fship event has been recorded yet.\nLAST=$(git rev-parse \"@{upstream}\" 2>\u002Fdev\u002Fnull || git rev-parse \"HEAD~1\")\nnode scripts\u002Fsymbol-index\u002Frefresh.mjs --since-commit \"$LAST\" || true\nnode scripts\u002Fsymbol-index\u002Frender-mermaid.mjs || true\n# NOTE: do NOT `git add docs\u002Farchitecture-map.md` — it is gitignored (Category A).\n```\n\n**Nothing from this step is ever staged** (`docs\u002Farchitecture-map.md` is Category A),\nand it **never blocks a ship**: every failure mode skips under `|| true`. →\n`references\u002Farchitecture-and-dashboard-refresh.md` §0.5c — read when debugging it.\n\n---\n\n## Step 0.5d — Regenerate the Local Dashboard (advisory, source-repo only)\n\n**Source-repo-gated** — run this ONLY when\n`package.json.name === \"claude-engineering-skills\"` (same gate as Step 6.0).\nSkip silently in consumer repos (→ `references\u002Farchitecture-and-dashboard-refresh.md`\n§0.5d — read when a consumer asks to build its dashboard). Never blocks the ship.\n\n```bash\nnode scripts\u002Fbuild-dashboard.mjs reference 2>&1\n```\n\nRun it WITHOUT `|| true` — read the exit code, never mask it. It regenerates the\ngitignored (Category A, never staged) `dashboard\u002Findex.html` + `telemetry.html`;\nnon-zero means a degraded build → print a one-line heads-up and continue.\n\n---\n\n## Step 1 — Assess What Changed\n\nBefore updating docs, understand the current state:\n\n1. `git status` — modified, added, untracked files\n2. `git diff --stat` — change summary\n3. `git diff` on key changed files — what was actually done\n4. `git log -5 --oneline` — recent commit style and context\n\nBuild a mental model of: what features\u002Ffixes were implemented, which\nfiles were created vs modified, which area was affected, whether new\npatterns were established.\n\n---\n\n## Step 1.5 — Close-out claims check (write-ups and status reports)\n\nWhen the change carries a write-up or status report — `docs\u002Fresearch\u002F**`, a results\ndoc, a `status.md` entry, a plan's status or audit trail with figures — every figure\nmust trace to an artifact and carry `measured` \u002F `derived` \u002F `expected` with its\ncommand; every line citation is pinned (`node scripts\u002Fcheck-doc-citations.mjs \u003Cdoc>`).\nAn untraceable figure is fixed, or settled with `\u002Finvestigate`, before the commit.\nProcedure: `references\u002Fclaims-check.md`; repeat it over the status.md entry and plan\nupdate Steps 2 and 5 write. No write-up in the change → skip, and say so.\n\n---\n\n## Step 2 — Update status.md\n\nDrain any consumer-verification notes a PRIOR ship left (Step 6.8 writes one\ninstead of force-pushing a status.md-only commit). This is how such a note ever\nreaches git without a second push:\n\n```bash\nnode scripts\u002Flib\u002Fworktree-preflight.mjs pending-note read\n```\n\nIt prints EVERY pending note, oldest first, each under an HTML comment naming\nits file — there can be more than one, because a ship does not happen after\nevery note. Prepend them, in that order, as a single\n`### Consumer Verification (previous ship)` subsection above the new entry —\nsee `references\u002Fstatus-md-format.md`. Then delete exactly the notes you just\nprepended, with the `pending-note clear --notes …` line the read printed for\nyou. Pass those names verbatim: a note that arrived between the read and the\nclear must survive to the next drain, which is why `clear` refuses to delete\neverything it happens to find. (Where the notes live and why:\n`references\u002Fflow-rationale.md` §Step 2 — read when a note seems missing.)\n\nWrite the session log entry. **If `docs\u002Fstatus\u002Fentries\u002FREADME.md` exists, the\nentry is a NEW file there** (`\u003CYYYY-MM-DD>-\u003CHHMMSS>-\u003Cslug>.md`, UTC; `status.md`\ntakes no new entry). Otherwise add it at the TOP of `status.md` (create the\nstandard header if absent), newest first. Template, rules and optional sections\n(UX, Persona, Regression Lock, Plan Verify, Consumer Verification):\n`references\u002Fstatus-md-format.md`.\n\n### Step 2b — Backlog snapshot line (one command, advisory)\n\nInclude a **`Backlog:`** line in the entry, so the standing queues are trended\nin the log instead of being rediscovered every few weeks:\n\n```bash\nnode scripts\u002Fbacklog-snapshot.mjs\n```\n\nPaste its single stdout line into the entry. It reads every queue itself, at one\ninstant, read-only, and **writes nothing** — you insert the line as part of the\nentry you are already authoring. Never let a script write `status.md`.\n\nA queue it could not read renders **`unmeasured`**, never `0`. The command always\nexits 0; it is a nudge, never a gate. (Incident: `references\u002Fflow-rationale.md`\n§Step 2b.)\n\n---\n\n## Step 3 — Update CLAUDE.md (if needed)\n\nReview whether the current session introduced anything that should be\ncaptured:\n\n- [ ] New route files or API endpoints? → Backend Structure\n- [ ] New frontend modules? → Frontend Structure\n- [ ] New service patterns? → document the pattern\n- [ ] New env vars? → Environment Variables table\n- [ ] New conventions or rules? → Do \u002F Do NOT sections\n- [ ] New test files or patterns? → Testing section\n\nAlso check for outdated info — file structure descriptions, code\nexamples, config values.\n\n**If changes needed**: edit CLAUDE.md, keeping existing style.\n**If no changes needed**: skip — do not make cosmetic edits.\n\n---\n\n## Step 4 — Verify AGENTS.md ↔ CLAUDE.md alignment (do NOT mirror)\n\n**`AGENTS.md` is the canonical shared context** (every agent — Claude Code,\nCopilot, Cursor, Codex, Windsurf — reads it). **`CLAUDE.md` is a thin addendum**\nthat `@.\u002FAGENTS.md`-imports it and holds only Claude-Code-only notes. They are\n**not** byte-mirrors.\n\n- **NEVER copy `CLAUDE.md` over `AGENTS.md`** — a thin CLAUDE.md would overwrite\n  and destroy the canonical file.\n- Put **shared** content in `AGENTS.md`; **Claude-only** notes in `CLAUDE.md`.\n- Then run **`node scripts\u002Fcheck-context-drift.mjs --strict`** — it enforces the topology (CLAUDE.md\n  `@`-imports AGENTS.md, stays ≤ the line cap, only allowlisted Claude-only\n  headings, no shared-section drift). Fix any findings by moving shared content\n  to AGENTS.md — **never** by mirroring.\n- If a repo is still in the legacy full-mirror state (CLAUDE.md == AGENTS.md),\n  migrate it with **`\u002Fai-context-management migrate`** (flips to AGENTS.md-canonical\n  + thin CLAUDE.md); do not hand-resolve.\n\n---\n\n## Step 5 — Update Plan (if plan path in arguments)\n\nOnly when `$ARGUMENTS` contains a plan file path:\n\n\u003C!-- host-contract: input-acquisition; grammar=path+flags; empty=default -->\n_This site: `path+flags` — no path means skip the plan update entirely; do not search for a plausible plan to update._\n\n**Resuming after a blocked `\u002Fcycle --autonomous` run?** Treat the plan path as\nrequired, not optional — `\u002Fcycle`'s Step 7 handoff card names it explicitly\n(`\u002Fship docs\u002Fplans\u002F\u003Cname>.md`) for exactly this reason: a bare `\u002Fship` here\nskips this step entirely, and the plan's `Status:`\u002FImplementation Log never\npicks up what the autonomous run did.\n\n1. **Read the plan**\n2. **Compare against git diff** — which planned items were implemented?\n3. **Update plan metadata**: `Status: Draft` → `In Progress` → `Complete`\n4. **Mark completed items** in the file-level table\n5. **Add implementation log entry** at the bottom:\n\n```markdown\n## Implementation Log\n\n### \u003CToday's Date>\n- Completed: \u003Cwhat was built>\n- Remaining: \u003Cwhat is left>\n- Deviations: \u003Cany changes from the original plan and why>\n```\n\n6. **Flag deviations** — if implementation diverged, note what changed and why.\n\n---\n\n## Step 5.5 — Plans no longer move on completion (removed)\n\n**There is no archive step** — a completed plan stays in `docs\u002Fplans\u002F` and\ncarries `Status: Complete`; never move it. → `references\u002Fflow-rationale.md`\n§Step 5.5 — read before reintroducing any plan move.\n\n---\n\n## Step 5.8 — Pre-push staleness check (shared-repo overlap)\n\nSkip when the current branch is the repo's default branch (this bundle's own\nmain-only convention — nothing to compare against) or when Step 5 found no\nplan path in `$ARGUMENTS` (no plan means no declared file scope to diff\nagainst).\n\nA different PR may have merged to the default branch first, touching files this\nrun also touched — which surfaces later as `mergeable: CONFLICTING` and can read\nlike a CI flake. Catch it here (→ `references\u002Fflow-rationale.md` §Step 5.8 —\nread when a PR shows CONFLICTING or no CI run):\n\n```bash\nDEFAULT_BRANCH=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>\u002Fdev\u002Fnull || echo main)\ngit fetch origin \"$DEFAULT_BRANCH\"\nBASE=$(git merge-base HEAD \"origin\u002F$DEFAULT_BRANCH\")\ngit diff --name-only \"$BASE\" \"origin\u002F$DEFAULT_BRANCH\"   # touched upstream since this branch's base\n```\n\nIntersect that list with the plan's declared scope — §7's File-Level Plan,\nplus §7b\u002F§11 `Files:` bullets when present (reuse the paths Step 5 already\nread from the plan). An empty intersection: proceed silently, nothing to\nreport.\n\nA non-empty intersection is worth a pause, not an automatic stop — name the\noverlapping files and the upstream commit(s) that touched them, and let the\noperator pick: rebase now (the squash-merge note below already prefers\n`rebase` over `merge` on a `\u002Fship`-created branch, for the same reason) or\npush and resolve on GitHub. Either way, don't decide it silently — a\nconfusing CI investigation later costs more than one question now.\n\nIf a rebase or merge resolves real overlap, re-run the full test suite\nbefore continuing to Step 6 — a clean 3-way merge with zero conflict markers\nis not proof of correctness.\n\n---\n\n## Step 6 — Stage, Commit, Push\n\n### 6.0 Sync manifest — no action (source repo)\n\n**Do not `git add` `scripts\u002F.sync-manifest.json` in the source repo** — it is\ngitignored Category A (timestamp + HEAD sha). Consumers track their **own**\nmanifest. → `references\u002Fflow-rationale.md` §Step 6.0 — read when the manifest\nshows up as a change.\n\n### 6.1 Stage\n\nStage relevant files by name (be specific):\n\n```bash\ngit add scripts\u002Flib\u002F*.mjs tests\u002F*.test.mjs   # your changed source files\ngit add status.md                    # or the new docs\u002Fstatus\u002Fentries\u002F\u003Cfile>.md\ngit add CLAUDE.md AGENTS.md          # only if modified\ngit add docs\u002Fplans\u002F*.md              # only if plan was updated\n# NOTE: do NOT `git add scripts\u002F.sync-manifest.json` in the source repo — it's\n# gitignored here (Category A; regenerated every sync). Consumers track their own.\n# NOTE: do NOT `git add dashboard\u002Findex.html` — it and dashboard\u002Ftelemetry.html\n# are gitignored (Category A; rebuilt by Step 0.5d, never committed).\n```\n\n**Do NOT stage**: `.env`, credentials, `node_modules\u002F`, temp\u002Fgenerated files.\n\nIf untracked files look unintentional (temp, OS files), skip silently.\nInclude all source, docs, tests, and config.\n\n### 6.2 Commit message + provenance values\n\nFollow project convention:\n\n```\n\u003Ctype>: \u003Cconcise description>\n\n\u003Coptional body with WHY if significant>\n```\n\nTypes: `feat`, `fix`, `refactor`, `docs`, `style`, `test`, `chore`.\nKeep first line under 72 chars. Body explains WHY, not WHAT.\n\n**Pass the message as a file or on stdin** (not `-m`, no shell interpolation).\n**Prefer stdin** — `--message-file -` reads from stdin (a heredoc works,\nleaves nothing behind; use `-`, not `\u002Fdev\u002Fstdin`, which Git-Bash resolves to\na non-regular file). The file route (Write tool →\n`.claude\u002Ftmp\u002Fship-commit-msg-\u003Cepoch>.txt`, then `--message-file \u003Cthat\npath>`, deleted once the commit lands) works too, but never your session\nscratchpad dir — `--message-file` refuses any path outside `repoRoot`. Why\nstdin is preferred: `references\u002Fcommit-provenance-deep-dive.md`.\n\nDo NOT include any `AI-*` lines — the helper is their only writer and rejects\nthem (`reserved-trailer`).\n\nDecide the provenance values (full convention: `docs\u002Freference\u002Fcommit-provenance.md`):\n- `--models` — comma list of models that participated this session\n  (e.g. `claude` alone; `claude,gemini,gpt` when the audit loop ran).\n- `--gate` — four values. The first two are **verified** (the helper queries the\n  run's `audit_runs` row); the last two are **declared**.\n  - `passed` — fresh evidence, the store says the run converged, **and the\n    committed tree IS the audited tree**.\n  - `converged` — the same evidence and the same store verdict, but the\n    **committed tree DIFFERS from the audited tree**. This is the\n    audited-then-remediated ship: the audit ran, its findings were accepted and\n    **fixed**, so the tree moved *because of the gate*. Not a bypass, and it must\n    not be labelled as one.\n  - `waived` — a declared disposition with no verified verdict: shipped past a\n    gate via `--ignore-p0`\u002F`--no-tests`\u002Fetc., OR verification unavailable (cloud\n    off \u002F run not found \u002F run did not converge).\n  - `not-run` — no fresh evidence at all (docs-only ships).\n\n`passed` and `converged` clear the identical store bar and are mutually\nexclusive halves of one comparison — asking for one on the wrong tree state\npoints at the other, and `--no-tests` always caps the verdict downward\n(`waived` or `not-run`), never up. `passed` is rare **by design** (Steps 2-5\nwrite status.md\u002FCLAUDE.md between audit and commit, so even a clean audit\nusually lands on `converged`) — do not hand-write evidence or reorder a ship\nto chase it. `--no-run-id --gate not-run` disclaims a marker inherited from\nan unrelated prior audit. Full rationale for each value, `--no-run-id`, and\nthe freshness rule: `references\u002Fcommit-provenance-deep-dive.md`.\n\n### 6.3 Commit and push\n\n**The `\u002Fship` command IS the user's approval.** Proceed directly — no\nconfirmation prompts.\n\n**Both of these are now REQUIRED, and `ship-commit` refuses without them.**\n\n```bash\n# Capture the worktree identity ONCE, at the start of the ship (Step 0 if you\n# ran one), and pass that exact pair to every ship-commit invocation in the run.\nSHIP_HEAD=$(git rev-parse HEAD)\nSHIP_BRANCH=$(git symbolic-ref --quiet --short HEAD)   # empty ⇒ detached\nEPOCH=$(date +%s)\nMODELS=\"claude-opus-5-5,gpt-6.1-terra\"   # the models that actually did the work this ship\nGATE=not-run                     # passed | converged | waived | not-run — see AI-Gate above\n\nnode scripts\u002Fship-commit.mjs --message-file \".claude\u002Ftmp\u002Fship-commit-msg-$EPOCH.txt\" --skill ship --models \"$MODELS\" --gate \"$GATE\" --expect-head \"$SHIP_HEAD\" --expect-branch \"$SHIP_BRANCH\" --path scripts\u002Flib\u002F*.mjs --path tests\u002F*.test.mjs\ngit push origin \"$SHIP_BRANCH\"\n```\n\nIdentity is a precondition, not a warning, because a concurrent session can\namend\u002Frebase\u002Fcheckout between your first command and your commit, and only a\nsha+branch pair (not a content hash) catches it. **On a detached HEAD** pass\n`--expect-detached` instead of `--expect-branch` — a head with no ref\ndisposition is `incomplete-expectation` → exit 2. You may omit both flags\nonly when a FRESH audit ran this session (the evidence marker supplies the\nbundle); an older marker reports `pre-bundle-evidence` and needs them passed\nexplicitly. Full rationale: `references\u002Fcommit-provenance-deep-dive.md`.\n\n**Shared working tree — `--path` is MANDATORY, not conditional.** `ship-commit`\nrefuses an unscoped commit outright: it cannot know whose staged entries the\nindex holds, so it requires you to declare what you are shipping. There is no\noverride flag, deliberately. Add one `--path \u003Cfile>` per file you\nare shipping:\n\n```bash\nnode scripts\u002Fship-commit.mjs --message-file \".claude\u002Ftmp\u002Fship-commit-msg-$EPOCH.txt\" --skill ship --models \"$MODELS\" --gate \"$GATE\" --expect-head \"$SHIP_HEAD\" --expect-branch \"$SHIP_BRANCH\" --path scripts\u002Ffoo.mjs --path tests\u002Ffoo.test.mjs\n```\n\nThis commits those paths' worktree contents and leaves every other index\nentry staged and untouched. Untracked paths are handled (marked\nintent-to-add, rolled back if the run is rejected). Do **not** fall back to a\nbare `git commit -- \u003Cpaths>` — it scopes correctly but drops the `AI-*`\nprovenance trailers, which is exactly what this helper exists to prevent.\n\n**A directory is refused, not expanded** — pass each file, including for a\ndeleted directory. (Why, and the TOCTOU reasoning behind `--path`:\n`references\u002Fcommit-provenance-deep-dive.md` §Step 6.3 scoping detail — read\nwhen a `--path` refusal surprises you.)\n\nOn `post-commit-drift` it exits 1 and prints a recovery command: **do not push.**\n\nExit contract: `0` = committed (trailers appended). `2` = input rejected —\nfix exactly what the `AGENT FIX:` stderr lines say and re-invoke (max 2\nretries, then report). `1` = operational failure — report it; do not\nloop. **Fallback (stale consumer sync only)**: if the helper script does\nnot exist on disk, fall back to `git commit -F \u003Cmessage-file>` and print\none line: `provenance trailers skipped (helper unavailable — re-run npm\nrun sync)`.\n\nIf push fails (behind remote, etc.), inform the user and suggest the\nfix. Do NOT force push.\n\n**CI on the pushed head**: `node scripts\u002Fci-merge.mjs --pr \u003Cn> --wait-only` reports every check; add `--merge` only when the user said to merge (`references\u002Fci-merge.md`).\n\n**Continuing work in the same worktree after a squash merge: rebase, never\nmerge** — check mergeability before assuming a CI trigger is broken. Why, and\nthe multi-commit caveat: `references\u002Fcommit-provenance-deep-dive.md` §Continuing\nwork after a squash merge.\n\n---\n\n## Step 6.5 — Security Memory Refresh + Capture Hint (after successful push)\n\nIf push succeeded AND `docs\u002Fsecurity-strategy.md` exists in the repo,\nrun the refresher to keep the Supabase index in sync with markdown (only ever\npublishes pushed state — R3-H3 design constraint). Surface the result line\nbriefly.\n\n```bash\nnode scripts\u002Fsecurity-memory\u002Frefresh-incidents.mjs\n```\n\n**Call the script by path, never `npm run security:refresh`** — that alias\nexists in the source repo only (the sync never merges npm scripts into a\nconsumer's `package.json`); the refresher itself **is** synced\n(`scripts\u002F.claude-skills\u002Fsecurity-memory\u002Frefresh-incidents.mjs`). Why:\n`references\u002Fpost-push-advisories.md` §6.5.\n\nAfter refresh, regex-match the HEAD commit subject against\n`\u002Ffix.*\\bsecurity\\b|\\bcve\\b|\\bvuln\\b|\\bleak\\b|\\binjection\\b|\\bauth\\b|\\bxss\\b|\\bcsrf\\b|\\brce\\b\u002Fi`\n(word-boundary-anchored on purpose). If matched, emit a single passive log line\n(NOT an interactive prompt — `\u002Fship` runs to completion without asking, which\nis the whole contract in its `description`; a prompt here would strand the\npush mid-flight):\n\n```\n⚠ Security-relevant commit detected: \"\u003Csubject>\".\n  Run `\u002Fsecurity-strategy add-incident from-commit \u003Csha>` to draft an\n  incident memory entry from this fix.\n```\n\nThe user reads this and decides whether to invoke `\u002Fsecurity-strategy`\nthemselves. No blocking, no prompt, no input.\n\nIf `docs\u002Fsecurity-strategy.md` doesn't exist → no-op (don't suggest\nbootstrap on every push; that's noise).\n\n---\n\n## Step 6.6 — Friction closure (after successful push, advisory)\n\nCompletes the friction-feedback loop (plan: `docs\u002Fplans\u002Ffriction-feedback-loop.md`\nC10). This step surfaces friction notes that the just-pushed commit may have\nresolved, so a recurring papercut gets marked closed instead of recurring forever.\n\n\u003C!-- host-contract: hook-rule; rule=friction-closure-after-push; portable=node scripts\u002Fcross-skill.mjs quality session-review; accelerator=.claude\u002Fhooks\u002FUserPromptSubmit -->\n\n> **Rule (all hosts)** — after a successful push, pending friction notes are\n> surfaced once, and the step is accepted when the session-review command has\n> run and its output reported. **Portable path**: the command below, which is\n> in the sync closure and works everywhere. **Accelerator (Claude Code only)**:\n> a `UserPromptSubmit` hook injects `> Relevant prior friction` callouts during\n> the session and records a breadcrumb, so the notes are already in view.\n>\n> On a host with no hooks the session-review call is usually empty — a correct\n> empty, never evidence that no friction existed. Why:\n> `references\u002Fpost-push-advisories.md` §6.6.\n\nIf push succeeded, list pending injected-but-unlinked friction:\n\n```bash\nnode scripts\u002Fcross-skill.mjs quality session-review\n```\n\nFor each pending note, emit a single passive line with the ready link command\n(NOT an interactive prompt — `\u002Fship` runs to completion without asking, same as\nthe Step 6.5 security hint):\n\n```\n⚠ Prior friction you were warned about: \"\u003Ctitle>\" (\u003Cmemory_name>).\n  If this commit fixed it: node scripts\u002Fcross-skill.mjs quality link \\\n    --memory \u003Cmemory_name> --kind commit --ref \u003CHEAD sha>\n```\n\nCloud-off, no breadcrumb, or no pending notes → **no-op (silent)** — never noise.\n`quality link` is idempotent + local-first; the user decides whether to run it.\nAdvisory; never blocks the ship.\n\n---\n\n## Step 6.7 — Final-review credit (after successful push, advisory)\n\nThe missing caller of `final-review-adjudicate` \u002F `final-review-record-fix`\n(why: `references\u002Fpost-push-advisories.md` §6.7).\n\n**Run AFTER the commit lands**, so the sha handed to `--commit` is the real one.\n`$REPO` is the `owner\u002Frepo` slug (same value `LEARNING_REPO_NAME` uses — the\nbare repo name silently misses the lookup):\n\n```bash\nnode scripts\u002Fcross-skill.mjs final-review-pending --repo \"$REPO\" --render --commit \"$(git rev-parse --short HEAD)\"\n```\n\nPrint its stdout verbatim. That is the whole integration — the command renders\nthe finished card, so there is nothing to parse and no formatting decision here.\nOmit `--render` to get the versioned JSON instead (`schemaVersion`, `state`,\n`counts`, `items`) if you need it programmatically.\n\n**Walking the whole queue** (JSON form) pages by a **keyset cursor** on\n`nextCursor`, never an offset → `references\u002Fpost-push-advisories.md` §6.7 — read\nwhen you need more than the card's one page.\n\n**A ruling on EITHER axis is a label** — `adjudication_outcome` (triage) or\n`user_action` (this step's durable override). When the card prints a\n`⚠ N finding(s) where the ship-time disposition and the triage ruling disagree\nin direction` line (`axisConflicts`), reconcile those by hand; everything else\nneeds no special handling. Re-running the final reviewer does not erase prior\nrulings (`references\u002Fpost-push-advisories.md` §6.7).\n\n**Advisory only — the reader always exits 0** across its three result states\n(`ready` \u002F `disabled` \u002F `unavailable`), emitting empty output when cloud is off\nor nothing is pending, and a single line carrying just a diagnostic CODE when\nthe store is unreachable. A missing label is not a reason to stop a ship.\n\nTreat a stale sha as a reason to skip the card, not to re-render it: the\n`--commit` value should be the commit you just made.\n\nThe card offers `accepted`\u002F`dismissed` for unadjudicated findings, `accepted`\nonly for a fixed-but-unlabelled one (a shipped fix implies the finding was\nreal), and a complete `record-fix` line for an accepted-but-unfixed one. **You\nchoose which finding a commit fixed** — the card lists candidates and their\ncommands, and infers no attribution from \"a file changed\". Output is bounded\n(10 items) with a pointer to `final-review-stats --worksheet` for the full\nqueue.\n\n---\n\n## Step 6.8 — Consumer-side verification (after successful push, advisory)\n\n`git push` exiting 0 proves the transfer completed. **It proves nothing about\nthe receiver's view** — and a generated artifact can pass every check against\nits *source* while the built output carries real defects. So fetch the thing\nback the way a consumer gets it, and check **that**.\n\n**A feature-branch push publishes nothing to consumers** (since 2026-10-10 the\npre-push hook syncs only when the default branch is updated, so unmerged tooling\nnever reaches them). For a branch\u002FPR ship, the synced-bundle row is `unverified\n(blocked: not merged)` until the PR merges and the source repo re-syncs its\nconsumers from an up-to-date default branch.\n\nPick the row(s) this push actually produced:\n\n| Artifact | Consumer-side retrieval | Subject check |\n|---|---|---|\n| the pushed commit | `git fetch`, then `git worktree add --detach \"$VERIFY_DIR\" \"$PUSHED_SHA\"`; in it, a frozen clean install (`npm ci`, or the repo's own package manager's equivalent), then `skills:hydrate` (the Worktree preflight above — it copies the gitignored tooling tree in, as the pre-push run had it), then **exactly the command the pre-push hook runs**. Do not inject the main checkout's `.env` into the test process (e.g. `NODE_OPTIONS=\"-r dotenv\u002Fconfig\"`): children spawned with a cwd outside the tree inherit the preload and die on `MODULE_NOT_FOUND`. A bare `git clone` is valid only when no test reads the tooling tree — otherwise tests that need it skip, and the run reads green having run less. Remove the worktree afterwards | green **in the worktree**, with **both the passed and the skipped count equal to the pre-push run's** — a different skip count is a different suite, not a confirmation. Catches tracked-vs-ignored and case-only path faults invisible locally |\n| the synced consumer bundle | **authoritative**: `node scripts\u002F.claude-skills\u002Flib\u002Fsync-isolation-verify.mjs`, run *in the consumer's MAIN checkout* — note the `lib\u002F` segment, it is a module rather than a top-level script. A linked worktree cannot answer this (`runGates` refuses one at `preflight` with exit 2 — why: `references\u002Fflow-rationale.md` §Step 6.8). `npm run sync:dry` from here is the pre-check, not the verdict | zero unexpected diffs; no orphans |\n| the skill manifest | re-derive from the pushed sha, not the working tree | regenerated bytes identical |\n\n**Write the outcome with `pending-note write` (body on stdin) — never by\nre-opening the status.md entry you just pushed** (status.md is append-only;\nthis step runs AFTER that entry's commit already landed, so re-opening it\nmeans a second commit and a second push). Include in the file: the immutable\nlocator (full sha \u002F digest \u002F bundle version), the retrieval command actually\nrun, and the observed result. Write it with:\n\n```bash\nnode scripts\u002Flib\u002Fworktree-preflight.mjs pending-note write\n```\n\nIt resolves the MAIN checkout itself (never hand-write the path) and stamps the\nfilename with the shipped sha and the time; the **next** `\u002Fship`'s Step 2 drains\nit. If no further `\u002Fship` happens, the notes simply sit there unread — an\nacceptable loss for advisory documentation (never a gate), not a reason to force\na push now. Incident history behind each of these rules:\n`references\u002Fpost-push-advisories.md` §6.8.\n\n**Three terminal states, and only three**: `verified`, `failed`, `unverified`.\n**`unverified` must name a concrete blocked prerequisite** — \"no network in this\nenvironment\", \"no consumer checkout on this machine\" — never a bare \"not\napplicable\". A missing prerequisite is a fact; an undefined *impossible* is an\nexcuse. **Never inherit the producer-side green.**\n\nAdvisory by construction: the push already happened, so this cannot block it —\nand per the gate-level rule, machine and remote state may advise where repo\nstate may block. → `references\u002Fverification-discipline.md` §6.\n\n---\n\n## Step 7 — Emit Ship Event (always)\n\nAfter commit + push completes (or is blocked), record the outcome:\n\n```bash\nnode scripts\u002Fcross-skill.mjs record-ship-event --json '{\n  \"outcome\": \"shipped\" | \"blocked\" | \"warned\" | \"overridden\" | \"aborted\",\n  \"blockReasons\": [\"test-failure\",\"lint-failure\",\"type-check-failure\",\"format-failure\",\"open-p0\",\"missing-regression-spec\",\"secrets-detected\"],\n  \"openP0Count\": \u003Cfrom Step 0.5a>,\n  \"openP1Count\": \u003Cfrom Step 0.5a>,\n  \"missingSpecCount\": \u003Cfrom Step 0.5b>,\n  \"overriddenByUser\": \u003Ctrue if any override flag was used>,\n  \"overrideFlag\": \"\u003Ce.g. --no-tests or null>\",\n  \"stackDetected\": \"js-ts\" | \"python\" | \"mixed\" | \"unknown\",\n  \"framework\": \"\u003Cfastapi|django|flask|null>\",\n  \"durationMs\": \u003Cwall-clock ms from step 0.5 to now>\n}'\n```\n\n**Outcome semantics**:\n- `shipped` — everything passed, commit pushed\n- `warned` — shipped despite non-blocking warnings\n- `overridden` — user passed `--no-tests` or similar\n- `blocked` — blocking check failed, push did not occur\n- `aborted` — Claude aborted (secrets detected, nothing to commit, etc.)\n\n`blockReasons` is always an array — empty on `shipped`, populated otherwise.\n\nFire-and-forget — do not block on output. If cloud mode is off, CLI\nprints `{\"ok\":true,\"cloud\":false}` and returns 0.\n\n**Close-out** (read-only): `references\u002Fpost-push-advisories.md` §Close-out.\n\n---\n\n## Train mode (`\u002Fship --train`)\n\n**Source-repo-gated** (`package.json.name === \"claude-engineering-skills\"`); consumers skip this (no sync closure to classify\nagainst, no sync tools to publish with). One human invocation lands several already-audited, `fleet ready` PRs: one status entry,\nper-PR provenance in each squash commit, one combined test run, one consumer sync. **Human-only, like the rest of this skill** — `--train` does not relax the\nexplicit-invocation rule above. Its three flags are read only from this invocation. Runbook,\nrefusals, overrides, recovery: `references\u002Ftrain-mode.md` (read it before running any verb of\n`node scripts\u002Fship-train.mjs plan | message | land | verify | sync`).\n\n---\n\n## Reference files\n\nThis skill's canonical flow is above. The files below cover specialised\nsituations — read them only when the trigger applies.\n\n| File | Summary | Read when |\n|---|---|---|\n| `references\u002Farchitecture-and-dashboard-refresh.md` | Why the arch-map and dashboard pages are Category A, 0.5c's failure-mode matrix, and 0.5c\u002F0.5d history. | Debugging Step 0.5c\u002F0.5d (staging, a refresh that did not run), or a consumer asks to build its dashboard. |\n| `references\u002Fcommit-provenance-deep-dive.md` | Step 6.2\u002F6.3's AI-Gate and worktree-identity deep-dive — why each value\u002Fflag exists and the incidents behind them. | Debugging `--gate`\u002F`--expect-head`\u002F`--expect-branch`\u002F`--path` behaviour, or before changing one. |\n| `references\u002Fflow-rationale.md` | Why \u002Fship's own flow is shaped as it is — the explicit-invocation bar, numbering, and Steps 0.5b-6.8 rationale. | A step's one-liner points here, or before changing that step's rule or its numbering. |\n| `references\u002Finput-acquisition.md` | Where a skill's arguments come from on any host, and what to do when there are none. | Reading $ARGUMENTS on a host that does not substitute it, or deciding what empty input means at a site. |\n| `references\u002Fmigration-credentials.md` | Which role applies migrations, why the runtime DSN cannot, and where its credential belongs (never in .env). | Step 0.5g — a store is behind and `--migrate` is refused with `42501` (`must be owner of table …` \u002F `permission denied for schema public`). |\n| `references\u002Fpost-push-advisories.md` | Steps 6.5-6.8's post-push advisories and the Step 7 close-out - incident history, kept out of the routine flow. | Debugging Step 6.5-6.8's behaviour, changing one, walking Step 6.7's whole queue, or writing the close-out. |\n| `references\u002Fpre-ship-gate-queries.md` | Step 0.5's pre-ship nudges (0.5a\u002Fb\u002Fe\u002Fg\u002Fh\u002Fi) — the incident history behind each rule, kept out of the routine flow. | Debugging a Step 0.5 nudge's behaviour, or before changing one. |\n| `references\u002Fpython-environment-discovery.md` | Python pre-push command discovery — env wrapper detection + per-tool probe order. | detect-stack returned `python` or `mixed` with Python files in the diff. |\n| `references\u002Fstatus-md-format.md` | status.md session-log template + update rules + persona \u002F UX status sections. | Step 2 — creating status.md for the first time, OR appending UX \u002F Persona \u002F Regression-Lock \u002F Plan-Verify sections. |\n| `references\u002Ftrain-mode.md` | \u002Fship --train runbook — plan, land, verify, sync a batch of ship-ready PRs; refusals, overrides, recovery. | `\u002Fship --train` was invoked, or the user asked to land several ready PRs together. |\n| `references\u002Fupstream-triage.md` | Step 0.5h — triaging a consumer's upstream report: the owning store, the FULL uuid, and the freshness check. | Step 0.5h's card lists a report you are about to `ack`, `fix` or `wont-fix`. |\n| `references\u002Fverification-discipline.md` | Verification discipline — pinned citations, figure provenance, two-direction proof, attribution, consumer-side checks. | Step 6.8 — the push succeeded and the artifact must be verified from the consumer side. |\n| `references\u002Fci-merge.md` | ci-merge.mjs - wait on every check of the pushed head, reject red or UNSTABLE, update BEHIND, squash-merge. | A push opened or updated a PR: waiting on CI, or merging. |\n| `references\u002Fclaims-check.md` | Step 1.5 claims check — every figure in a write-up traces to an artifact and carries its provenance label. | Step 1.5 — the change carries a write-up, results doc or status report. |\n| `references\u002Fworktree-preflight.md` | Worktree preflight remedy — the skills:hydrate npm script to add when a linked worktree lacks the tooling tree. | A command dies with `MODULE_NOT_FOUND` in a linked worktree AND this repo has no `skills:hydrate` script. |\n\n— end of \u002Fship —\n","# Ship: Sync Docs → Commit → Push\n\n> **This skill's last line is an end marker naming the skill between em-dashes, after\n> the Reference files table.** If that last line is not in your context (context\n> compaction keeps only the start of a skill), re-read\n> `.claude\u002Fskills\u002Fship\u002FSKILL.md` in full before continuing — the later steps still apply.\n\n> **Explicit invocation only — this is a host-neutral requirement, not just a\n> frontmatter flag.** `disable-model-invocation: true` enforces it in Claude Code\n> and VS Code Copilot; other hosts are not guaranteed to, so it is stated here too:\n>\n> **Do not start this skill because a message sounded like approval.** Start it\n> only when the user asked, in their own words this turn, for their work to be\n> committed and pushed. \"Looks good, ship it\" said about a plan, a diff or a\n> design is assent to the *idea*, not an instruction to push — answer it by\n> offering `\u002Fship`, never by running it.\n>\n> Every step below skips confirmation because the user chose to run this; a\n> self-invoked run would cite its own execution as consent.\n> → `references\u002Fflow-rationale.md` §Explicit invocation — read when deciding\n> whether a message is an invocation, or before changing this rule.\n\nA single command that ensures all project documentation is current, then\ncommits and pushes. Follow every step in order.\n\n## Usage\n\n| Invocation | Effect |\n|---|---|\n| `\u002Fship` | sync docs + commit + push |\n| `\u002Fship docs\u002Fplans\u002F\u003Cname>.md` | also update that plan before committing |\n| `\u002Fship --no-tests` | skip pre-push tests (override; logged in `ship_event`) |\n| `\u002Fship --ignore-p0` | push despite an unresolved persona-test P0 finding |\n| `\u002Fship --skip-ux-lock` | push despite an unlocked recent UI fix |\n| `\u002Fship --train [--waive n,…] [--allow-unaudited n,…] [--end-of-day]` | land a batch of ship-ready PRs in one action (see **Train mode**) |\n\nThe three override flags are honoured only when the **user** passes them; see\nStep 0.5f's override handling. Never add one on the skill's own initiative to get\npast a red gate — that is the gate working.\n\n**Arguments**: `$ARGUMENTS` — optional path to a plan file to update\n(e.g., `docs\u002Fplans\u002F\u003Cname>.md`).\n\n\u003C!-- host-contract: input-acquisition; grammar=path+flags; empty=default -->\n\n**Where `$ARGUMENTS` comes from** — orchestrator-supplied input first, else\nthe host's verbatim invocation suffix, else the span of the user's **current**\nmessage naming this skill or its subject. Never inferred from surrounding\nconversation. This site is `path+flags`; on empty input, ship without updating a plan — that is the documented default. Never adopt a plan path mentioned earlier in the conversation.\nFull contract: `references\u002Finput-acquisition.md`.\n\n\n> **Worktree preflight** — in a linked git worktree the synced tooling tree\n> `scripts\u002F.claude-skills\u002F` is absent — it is gitignored, so `git worktree add`\n> does not populate it, and every command below that uses it dies on a bare\n> `MODULE_NOT_FOUND`. Run `npm run skills:hydrate` first. If this repo defines\n> no such script, `references\u002Fworktree-preflight.md` holds the entry to add — ask\n> the user first, since it edits their `package.json`.\n\n**Ship-specific, because here the wrong answer is silent instead of loud**: do not\nreach the tooling by `cd`-ing to the main checkout. `ship-commit.mjs` and\n`cross-skill.mjs` read HEAD, branch and `commit_sha` from cwd — from there they\nwould commit and attribute the **wrong tree**, with no error. Phase 0's\n`detect-stack` is already one of these commands, so resolve this before it.\n\n---\n\n## Phase 0 — Repo Stack Detection\n\n```bash\nnode scripts\u002Fcross-skill.mjs detect-stack --include-env-manager\n```\n\nReturns `{ stack, pythonFramework, environmentManager, detectedFrom }`.\n\n| `stack` | Behaviour |\n|---|---|\n| `js-ts` | Pre-push: `npm test`, linter + type-check + format if configured |\n| `python` | Pre-push: see `references\u002Fpython-environment-discovery.md` — env wrapper + tool probe |\n| `mixed` | Run BOTH stacks' checks — required-tool absence in either blocks |\n| `unknown` | Skip stack-specific checks; proceed with universal git workflow |\n\n**Source repo, before the full push check:** `npm run guards:preflight` (about 1-2 min) runs the whole-suite guard tests and static checks that a targeted test run cannot reach, as the real checks and not copies. It is an early look, and the pre-push run still covers everything; a result marked INCOMPLETE is not a pass. Rationale: the source repo's test-cost runbook, section 0.\n\nPython framework (if detected) shapes status.md section titles — see\n`references\u002Fstatus-md-format.md`.\n\n---\n\n## Step 0.5 — Pre-Ship Gate Queries (non-blocking by default)\n\nCollect signals before proceeding so the ship_event emitted at the end\nis accurate. Best-effort — if a query fails, log and proceed. Sub-step order:\n`0.5a → 0.5b → 0.5e → 0.5g → 0.5h → 0.5i → 0.5f`, then the H2 Steps 0.5c and 0.5d.\nIncident history behind every rule below: `references\u002Fpre-ship-gate-queries.md`\n— read when debugging a nudge's behaviour, or before changing one.\n\n### 0.5a — Recent persona-test P0s for this repo\n\nRun bare — the command resolves the repo itself (`--repo` -> `PERSONA_TEST_REPO_NAME` -> ambient `git remote`).\n**Never pass `--repo \"$PERSONA_TEST_REPO_NAME\"` as a shell expansion** — see\n`references\u002Fpre-ship-gate-queries.md` §0.5a for why.\n\n```bash\nnode scripts\u002Fcross-skill.mjs persona-outcomes summary\n```\n\nReturns `{ok, cloud, measured, scope:{mode,repoId,slug}, sessionId,\nsessionCreatedAt, persona, verdict, rawP0, rawP1, labeled: {closed,\nopen_relabeled_fixed, open_relabeled_stale, unlabeled, pending_verification},\nopenP0, openP1, pendingVerificationP0, pendingVerificationP1}`.\n\n**Check `measured` BEFORE reading any count** (same rule as Step 0.5b).\n`measured: false` means the gate asked nothing — report it as UNMEASURED,\nnaming the resolution it attempted, and NEVER as \"gate silent\":\n\n```\n⚠ UX GATE — UNMEASURED (\u003Creason>)\n  No repo slug resolved (tried: --repo, PERSONA_TEST_REPO_NAME, git remote).\n  This is NOT \"no open P0s\" — nothing was read.\n  Scope it: node scripts\u002Fcross-skill.mjs persona-outcomes summary --repo owner\u002Fname\n```\n\n**Closed failure semantics — never a NEW blocker**:\n- `cloud: false` → proceed without the UX gate, exactly as today.\n- `measured: false` → print the UNMEASURED card above, then proceed.\n- `sessionId: null` **with `measured: true`** → the gate is genuinely silent —\n  name the repo it resolved (`scope.slug`, `scope.mode`).\n- `ok: false` (a real store\u002Fquery failure) → log one warning line and fall\n  back to the legacy raw read, same `measured`\u002F`scope` fields:\n  ```bash\n  node scripts\u002Fcross-skill.mjs get-persona-sessions-by-repo     --limit 1 --p0-only     --select persona,focus,verdict,p0_count,p1_count,created_at,debrief_md\n  ```\n  (uses that session's raw `p0_count`\u002F`p1_count` as `open_p0_count`\u002F`open_p1_count`).\n\n**`pendingVerification*` is neither open nor clear** — a `fixed` label from the\nvery session being read has no newer evidence either way. When\n`pendingVerificationP0 > 0`, print one line and proceed:\n\n```\n  \u003Cn> P0 fix(es) awaiting verification — re-run \u002Fpersona-test to confirm.\n```\n\nCapture `openP0` + `openP1` (or the legacy fallback's `p0_count`\u002F`p1_count`)\nas `open_p0_count`\u002F`open_p1_count` for the ship_event record. If `openP0 > 0`\n(or the legacy fallback's `p0_count > 0`):\n\n```\n⚠ UX GATE (non-blocking)\n  Last persona test: \"\u003Cpersona>\" — \u003CN> days ago → \u003Cverdict> (P0: \u003Cn>, P1: \u003Cn>)\n  Unresolved P0s detected. These are user-visible broken flows.\n  Shipping anyway — consider fixing before next user-facing release.\n  Label fixed\u002Fdismissed P0s: node scripts\u002Fcross-skill.mjs persona-outcomes --worksheet\n```\n\nThe worksheet line only appears when the PRIMARY read succeeded (the legacy\nfallback path has no outcome ledger to label against).\n\n### 0.5b — Fixes that lack a \u002Fux-lock regression spec\n\n```bash\nnode scripts\u002Fcross-skill.mjs list-unlocked-fixes\n```\n\nReturns `{ok, cloud, scope:{mode,repoId,slug}, measured, reason, rows, shown, total,\nbyMode:{total,code,plan}, allAges, agedOut, agedOutByMode:{code,plan}, prePractice,\npracticeStart}`.\n\n**Check `measured` BEFORE reading any count.** `measured:false` means *nothing\nwas measured* (`reason: repo-identity-unresolvable` \u002F `cloud-off`) — the zeroes\nare \"not applicable\", **not** \"no obligations\". Report it as unmeasured; never\nrender it as a clean backlog.\n\n**Watch `agedOut` — it should be 0**: non-zero means obligations expired from the\n14-day window while a locking practice was live (a real leak). `prePractice`\nexpired before this repo's first lock (`practiceStart`) — not an obligation. Read\npast the window with `--all-ages`; `total`\u002F`byMode` then describe the unwindowed\nset:\n\n```bash\nnode scripts\u002Fcross-skill.mjs list-unlocked-fixes --all-ages\n```\n\n**Never hand-derive a repo id** — pass no flag (ambient identity) or\n`--repo \u003Cowner\u002Frepo>`; `--repo-id` is verified against `audit_repos`, and an\nunresolvable one reports `measured:false` rather than an authoritative empty\nbacklog.\n\n**Use `byMode.code` as `missing_spec_count` — NOT `rows.length`.** `rows` is\ncapped at 20, and `byMode.plan` rows are section references for which **no lock\nof any kind can ever exist**.\n\n**`danglingLocks` — a recorded lock whose test file is no longer there.**\n`count: null` means the question went unasked (cloud off, unresolved repo, a\nread that threw) — distinct from `0`. When `count > 0`, print the sampled rows\nand act on each:\n\n```bash\nnode scripts\u002Fcross-skill.mjs repoint-regression-spec --finding FINDING_UUID --test tests\u002Fdangling-regression-lock.test.mjs --description \"why the old artefact went away\"\n```\n\n`--delete` instead of `--test` where no test discharges the finding at all —\nthat is the honest outcome, not a lesser one: the finding returns to\n`unlocked_fixes` and gets raised again. The command refuses a missing target\npath, an unresolvable repo, and an ambiguous `(repo, finding)`. Incident\nhistory + why each of these rules exists: `references\u002Fpre-ship-gate-queries.md`\n§0.5b.\n\n`unlocked_fixes` has no UI-relevance filter — it fires identically for a\nDOM-facing fix and a pure backend\u002FCLI one, and `\u002Fux-lock` can only ever cover\nthe former. Judge each row by `primary_file` before suggesting a fix, if\n`byMode.code > 0`:\n\n```\n⚠ REGRESSION LOCK GATE (non-blocking)\n  \u003CbyMode.code> code fix(es) have no locked regression coverage\n  (+ \u003CbyMode.plan> plan finding(s), which cannot be locked — not an obligation):\n    • \u003Cprimary_file>: \u003Cone-line detail>\n  These will silently regress under future refactors.\n  Backend\u002FCLI\u002Flibrary fix → a unit or integration test IS the lock. Record it:\n    node scripts\u002Fcross-skill.mjs lock-with-test --worksheet\n  (reviewed queue; read the test before locking — a same-named file is not\n  proof of coverage, and the writer refuses a missing path or empty rationale).\n  UI\u002FDOM-facing fix → \u002Fux-lock; on React surfaces prefer a unit test unless\n  the contract genuinely needs a live DOM.\n```\n\n(The React caveat's history → `references\u002Fflow-rationale.md` §0.5b — read when\ndeciding whether \u002Fux-lock fits a React surface.)\n\nIf `agedOutByMode.code > 0`, print it too — lead with the CODE split, not the\nmixed `agedOut` total (a plan row can never be locked, so it is never a lost\nobligation):\n\n```\n⚠ OBLIGATIONS LOST TO THE WINDOW (non-blocking)\n  \u003CagedOutByMode.code> code fix(es) aged out of the 14-day window UNLOCKED,\n  after this repo started locking (\u003CpracticeStart>)\n  (+ \u003CagedOutByMode.plan> plan finding(s), which cannot be locked — not an obligation).\n  Waiting is not a way to clear this gate. Read them:\n    node scripts\u002Fcross-skill.mjs list-unlocked-fixes --all-ages\n  Then either lock them, or write them off in status.md so the decision is on\n  the record — an obligation discharged by silence is the thing this counts.\n```\n\nA plan-only `agedOut` (`agedOutByMode.code == 0`) prints nothing — that banner\nwould be a false alarm. Do **not** print `prePractice` as a backlog — it is\nbookkeeping for findings that predate the practice, not work anybody owes.\n\n**Re-running existing regression specs before a push** (optional gate): drive\nthem through the deterministic runner with the ship `run_context` so the\n`regression_spec_runs` rows are tagged correctly and written without the model:\n\n```bash\nnode scripts\u002Fux-lock-run.mjs spec --specs 'tests\u002Fe2e\u002F*.spec.js' --commit \"$(git rev-parse HEAD)\" --run-context ship-gate\n```\n\nOptional: `--url BASE_URL` when the specs need a non-default base URL.\n\nA non-zero exit means a locked contract broke — treat as a `test-failure`\nblock reason. Cloud off → it still runs + prints; Playwright missing → exit 5\n(skip the gate, don't fail the ship on a missing optional dep).\n\n### 0.5e — Accepted findings that were never remediated\n\n**First, a capped auto-reconcile pass** (best-effort, never blocks, no\noverride flag) — closes what the live-audit lifecycle's session\u002Fround\u002F14-day\nbounds will never revisit on their own:\n\n```bash\nnode scripts\u002Fremediation-reconcile.mjs --apply --cap 5 2>\u002Fdev\u002Fnull\n```\n\nSwallow any failure like every other 0.5-step. Report its one-line summary\nalongside the nudge below: `Auto-reconciled: \u003Cresolved> verified\n(\u003CmechanicallyResolved> of those by file-deletion, no LLM call), \u003CstillPresent>\nstill open, \u003Cuncertain> uncertain` — `resolved` already includes the\nmechanically-resolved count. `AUDIT_REMEDIATION_RECONCILE_ENABLED=false` opts\nout entirely; an absent Claude credential degrades to the free\nmechanical-resolution path only, never a failure.\n\n```bash\nnode scripts\u002Fcross-skill.mjs list-unremediated-acceptances\n```\n\nReturns `{ok, cloud, scope:{mode,repoId,slug}, measured, reason, rows, shown,\ntotal, byMode:{total,code,plan}, allAges, agedOut, agedOutByMode:{code,plan},\nagedOutBySeverity:{HIGH,MEDIUM}, notYetDue, prePractice, practiceStart}`.\n\n**Check `measured` BEFORE reading the count** — identical contract to 0.5b.\n`measured:false` means *nothing was measured*; an empty `rows` then means \"not\napplicable\", **not** \"no unremediated acceptances\".\n\n**Two bounds, and only one of them forgets**: `notYetDue` (under the 7-day\nfloor) is in flight — **never add it to `agedOut`, never report it as a\nbacklog**; mention it only if `total` is 0 and `notYetDue` is not. `agedOut`\n(over the 30-day ceiling, after `practiceStart`) is a real leak. `prePractice`\nis not an obligation. `--all-ages` drops both bounds; `total`\u002F`byMode` then\ndescribe the unwindowed set:\n\n```bash\nnode scripts\u002Fcross-skill.mjs list-unremediated-acceptances --all-ages\n```\n\n**Use `byMode.total` as `unremediated_count` — NEVER `rows.length`** (capped\nat 20). `byMode.plan` rows depend on the plan's status:\n- **Plan still in flight** → real work; treat it as an obligation.\n- **Plan marked Complete** → write the class off: record it as `dismissed`\n  (the store has no \"written off\" state) with the real reasoning in\n  `status.md`. A write-off covers the *document* obligation only.\n\n`accepted` is not evidence of a fix, and a row can also be already fixed with\nnobody having written it down. Treat each row as a\nhypothesis about current code, verify it, then close it in the right\ndirection (`--state fixed` or a dismissal) rather than assuming either.\n\nIf `> 0`, print — never blocks, no override flag. **Show the first 5 rows as\nreturned** (HIGH first, then oldest first — do not re-sort). To read past the\ndefault 20-row page, pass the payload's `nextCursor` back as `--after` (a keyset\ncursor — never `--offset`); `nextCursor: null` is the end\n(→ `references\u002Fflow-rationale.md` §0.5e — read when paging while closing rows):\n\n```bash\nnode scripts\u002Fcross-skill.mjs list-unremediated-acceptances --limit 20 --after eyJ2IjoxLCJraW5kIjoidW5yZW1lZGlhdGVkIiwic2V2ZXJpdHlSYW5rIjowLCJhY2NlcHRlZEF0IjoiMjAyNi0wOS0xMyAxMDowMDowMC4wMDAwMDErMDAiLCJmaW5kaW5nSWQiOiIxMTExMTExMS0xMTExLTQxMTEtODExMS0xMTExMTExMTExMTEifQ\n```\n\nThe payload echoes the resolved `limit` (store clamps to 200), so a short page\ncan be told from an exhausted one. `list-unlocked-fixes` pages the same way.\n\n```\n⚠ UNREMEDIATED ACCEPTANCES (non-blocking)\n  \u003CbyDisposition.open> open · \u003CbyDisposition.acceptedPermanent> permanently accepted\n  \u003Cn> finding(s) you accepted were never marked fixed (showing \u003C=5):\n    • [\u003Cseverity>] \u003Cprimary_file> — accepted \u003Cdays_open>d ago\n  Either remediate them, or close the loop honestly — per row:\n    node scripts\u002Fcross-skill.mjs close-finding --finding \u003Caudit_finding_id> \\\n      --as fixed --commit \u003Csha that fixed it>\n    (or --as dismissed \u002F --as accepted-permanent, each with --reason \"...\")\n  Leaving them open is fine — leaving them open SILENTLY is what this catches.\n```\n\n**`byDisposition.acceptedPermanent` is a decision, not a backlog — print it,\ndo not chase it.** Those rows are excluded from `open`\u002F`total`; `open === total`\nalways.\n\nIf `agedOut > 0`, print it separately — split by mode, since the two halves\nclose by different acts (a code row needs a fix; a plan row is an obligation\nonly while its plan is still in flight):\n\n```\n⚠ ACCEPTANCES LOST TO THE CEILING (non-blocking)\n  \u003CagedOutByMode.code> code finding(s) passed the 30-day ceiling still unremediated,\n  after this repo started recording remediations (\u003CpracticeStart>)\n  (+ \u003CagedOutByMode.plan> plan section(s) — an obligation only while that plan is\n  still in flight; on a Complete plan it is a shipped design doc, write it off).\n  \u003CagedOutBySeverity.HIGH> of the \u003CagedOut> are HIGH, across both modes.\n  They will not appear above again. Read them:\n    node scripts\u002Fcross-skill.mjs list-unremediated-acceptances --all-ages\n  Then remediate, or close the loop with close-finding, or write them off\n  (close-finding --as dismissed, reasoning in status.md) — an obligation\n  discharged by silence is what this counts.\n```\n\n**Close rows with `close-finding`, by the row's `audit_finding_id`** — not\n`finalize-outcomes` (it needs a live round's `--ledger`\u002F`--result`) and not\n`final-review-record-fix`, which only resolves final-review-pass rows and so\nanswers `no-match` for an ordinary audit finding. `--as fixed` takes `--commit`;\n`--as dismissed` (not a defect, or\na written-off plan section) and `--as accepted-permanent` (real, declined on\nthe merits) take `--reason`. A finding outside this repo is refused.\n\nJudge the list before echoing it: an `audit_mode = 'plan'` row's `primary_file`\nis a plan SECTION reference, not a file — say so rather than printing it as a\ncode path; a `remediation_state = 'planned'` row with a live plan is in flight —\ndrop it from the printed list.\n\n### 0.5g — Migration realization gate (ENFORCED by the binary, checked HERE too)\n\nA commit that ships a migration is only half-shipped until the migration is\nAPPLIED — a migration can be committed, tests green, pushed, and still be\nbyte-for-byte inert if nobody ran `--migrate`.\n\n**Run the read-only preflight now, before Steps 1–6.2's doc work and the\npre-push hook's readiness suite** — not just at Step 6.3:\n\n```bash\nnode scripts\u002Fship-commit.mjs --check-migrations\n```\n\n- **The real enforcement stays at Step 6.3** — `ship-commit.mjs` performs the\n  check again inside the commit path and exits 2 there regardless of whether\n  this preflight ran. This early run is advisory, not a substitute gate.\n- **Unconditional when the cloud store is on** — deliberately NOT gated on\n  \"the push range touches `supabase\u002Fmigrations\u002F`\": a code-only commit can\n  depend on a migration left unapplied by an *earlier* push or a branch switch.\n- **Cloud off \u002F unreachable \u002F no ledger ⇒ silently skipped**, never a block.\n- **On a block**: run `node scripts\u002Fsetup-postgres.mjs --migrate`, then\n  continue — Step 6.3 will pass without a retry once applied.\n\n**Consumers are on their own stores — check theirs too** (source-repo only,\n`--check-migrations` above asks only about the AMBIENT store):\n\n```bash\nnpm run stores:drift\n```\n\nPrint its stdout verbatim — it renders the finished card. **Never blocks, no\noverride flag** — applying a migration to a consumer's production database is\nan operator decision. **Read the `unqueried` \u002F `no store` lines** — a store\nnobody could reach is reported explicitly, never counted as current; if NO\nstore answered, the card says `NOTHING WAS CHECKED` instead of `all current`.\n\n**The runtime DSN usually cannot apply migrations** — do NOT grant the runtime\nrole ownership or put an admin DSN in `.env`. → `references\u002Fmigration-credentials.md`\n— read when `--migrate` is refused with `42501`.\n\n### 0.5h — Upstream issue queue (advisory, source-repo only)\n\n**Source-repo-gated** — run ONLY when `package.json.name === \"claude-engineering-skills\"`.\nConsumers FILE reports (`cross-skill.mjs upstream report`); this repo is where\nthey get triaged, and nothing else prompts anyone to read them.\n\n```bash\nnpm run upstream:queues 2>\u002Fdev\u002Fnull\n```\n\nPrint its stdout verbatim — it renders the finished card, reading **every\nregistered consumer's own store** (fingerprint plus consumer names, never a DSN\nor hostname).\n\n**Never blocks, and there is no override flag** — the queue is cloud state,\nnot repo state; it always exits 0. **Read the `unqueried` \u002F `no store` lines,\nand do not treat the count as complete when either is present** —\n`NOTHING WAS CHECKED` means an unasked question, not a clean queue.\n\nTriaging a report is a separate act from shipping: run `ack|fix|wont-fix` only\nagainst **the store that owns the row**, by the **FULL uuid**, after checking\n`freshness`\u002F`priorFixes` → `references\u002Fupstream-triage.md` — read before closing\nany report. Incident history: `references\u002Fpre-ship-gate-queries.md` §0.5h.\n\n### 0.5i — Stalled comparison campaigns (advisory, source-repo only)\n\n**Source-repo-gated** — run ONLY when `package.json.name === \"claude-engineering-skills\"`.\nCampaigns are declared in `.campaigns\u002F`, which exists only here; a consumer\nhas no campaign to be stalled on.\n\nA campaign short of `targetN` is **not decision-eligible** — the spend is\nbanked but the evidence cannot answer anything yet.\n\n```bash\nnode scripts\u002Fcampaign.mjs stale 2>\u002Fdev\u002Fnull\n```\n\n**Never blocks, no override flag** — collection state is cloud state the\ncommit cannot change. Silent when no campaign is stalled, the store is\nunreachable, or a campaign has never collected at all (never-started is not\nstalled). Prints its own card; pass it through verbatim. Do **not** collect now\nbecause of it — collection is spend-bearing and revision-pinned, a deliberate\nscheduled act (`references\u002Fpre-ship-gate-queries.md` §0.5i).\n\n### 0.5f — Override flags\n\nIf `$ARGUMENTS` contains `--no-tests`, `--ignore-p0`, or `--skip-ux-lock`,\n\n\u003C!-- host-contract: input-acquisition; grammar=path+flags; empty=default -->\n_This site: `path+flags` — **no flag means no override.** Each of these disables a gate, so they are read ONLY from the invocation — a conversation that merely mentions skipping tests must never become one._\n\nrecord which override is active — it goes into the ship_event.\n\n(Why this is `0.5f`, not `0.5d`: `references\u002Fflow-rationale.md` §0.5f — read\nbefore renumbering any Step 0.5 sub-step.)\n\n---\n\n## Step 0.5c — Architectural Memory Refresh (advisory)\n\nIf the architectural memory is configured for this repo (per the\n`docs\u002Fplans\u002Farchitectural-memory.md` rollout), refresh the per-repo\nsymbol-index and regenerate `docs\u002Farchitecture-map.md` so the LOCAL map matches\nwhat's about to ship. The map itself is **never committed** — see below.\n\n```bash\n# Determine since-commit (last shipped). Use upstream\u002Forigin HEAD as a proxy\n# when no \u002Fship event has been recorded yet.\nLAST=$(git rev-parse \"@{upstream}\" 2>\u002Fdev\u002Fnull || git rev-parse \"HEAD~1\")\nnode scripts\u002Fsymbol-index\u002Frefresh.mjs --since-commit \"$LAST\" || true\nnode scripts\u002Fsymbol-index\u002Frender-mermaid.mjs || true\n# NOTE: do NOT `git add docs\u002Farchitecture-map.md` — it is gitignored (Category A).\n```\n\n**Nothing from this step is ever staged** (`docs\u002Farchitecture-map.md` is Category A),\nand it **never blocks a ship**: every failure mode skips under `|| true`. →\n`references\u002Farchitecture-and-dashboard-refresh.md` §0.5c — read when debugging it.\n\n---\n\n## Step 0.5d — Regenerate the Local Dashboard (advisory, source-repo only)\n\n**Source-repo-gated** — run this ONLY when\n`package.json.name === \"claude-engineering-skills\"` (same gate as Step 6.0).\nSkip silently in consumer repos (→ `references\u002Farchitecture-and-dashboard-refresh.md`\n§0.5d — read when a consumer asks to build its dashboard). Never blocks the ship.\n\n```bash\nnode scripts\u002Fbuild-dashboard.mjs reference 2>&1\n```\n\nRun it WITHOUT `|| true` — read the exit code, never mask it. It regenerates the\ngitignored (Category A, never staged) `dashboard\u002Findex.html` + `telemetry.html`;\nnon-zero means a degraded build → print a one-line heads-up and continue.\n\n---\n\n## Step 1 — Assess What Changed\n\nBefore updating docs, understand the current state:\n\n1. `git status` — modified, added, untracked files\n2. `git diff --stat` — change summary\n3. `git diff` on key changed files — what was actually done\n4. `git log -5 --oneline` — recent commit style and context\n\nBuild a mental model of: what features\u002Ffixes were implemented, which\nfiles were created vs modified, which area was affected, whether new\npatterns were established.\n\n---\n\n## Step 1.5 — Close-out claims check (write-ups and status reports)\n\nWhen the change carries a write-up or status report — `docs\u002Fresearch\u002F**`, a results\ndoc, a `status.md` entry, a plan's status or audit trail with figures — every figure\nmust trace to an artifact and carry `measured` \u002F `derived` \u002F `expected` with its\ncommand; every line citation is pinned (`node scripts\u002Fcheck-doc-citations.mjs \u003Cdoc>`).\nAn untraceable figure is fixed, or settled with `\u002Finvestigate`, before the commit.\nProcedure: `references\u002Fclaims-check.md`; repeat it over the status.md entry and plan\nupdate Steps 2 and 5 write. No write-up in the change → skip, and say so.\n\n---\n\n## Step 2 — Update status.md\n\nDrain any consumer-verification notes a PRIOR ship left (Step 6.8 writes one\ninstead of force-pushing a status.md-only commit). This is how such a note ever\nreaches git without a second push:\n\n```bash\nnode scripts\u002Flib\u002Fworktree-preflight.mjs pending-note read\n```\n\nIt prints EVERY pending note, oldest first, each under an HTML comment naming\nits file — there can be more than one, because a ship does not happen after\nevery note. Prepend them, in that order, as a single\n`### Consumer Verification (previous ship)` subsection above the new entry —\nsee `references\u002Fstatus-md-format.md`. Then delete exactly the notes you just\nprepended, with the `pending-note clear --notes …` line the read printed for\nyou. Pass those names verbatim: a note that arrived between the read and the\nclear must survive to the next drain, which is why `clear` refuses to delete\neverything it happens to find. (Where the notes live and why:\n`references\u002Fflow-rationale.md` §Step 2 — read when a note seems missing.)\n\nWrite the session log entry. **If `docs\u002Fstatus\u002Fentries\u002FREADME.md` exists, the\nentry is a NEW file there** (`\u003CYYYY-MM-DD>-\u003CHHMMSS>-\u003Cslug>.md`, UTC; `status.md`\ntakes no new entry). Otherwise add it at the TOP of `status.md` (create the\nstandard header if absent), newest first. Template, rules and optional sections\n(UX, Persona, Regression Lock, Plan Verify, Consumer Verification):\n`references\u002Fstatus-md-format.md`.\n\n### Step 2b — Backlog snapshot line (one command, advisory)\n\nInclude a **`Backlog:`** line in the entry, so the standing queues are trended\nin the log instead of being rediscovered every few weeks:\n\n```bash\nnode scripts\u002Fbacklog-snapshot.mjs\n```\n\nPaste its single stdout line into the entry. It reads every queue itself, at one\ninstant, read-only, and **writes nothing** — you insert the line as part of the\nentry you are already authoring. Never let a script write `status.md`.\n\nA queue it could not read renders **`unmeasured`**, never `0`. The command always\nexits 0; it is a nudge, never a gate. (Incident: `references\u002Fflow-rationale.md`\n§Step 2b.)\n\n---\n\n## Step 3 — Update CLAUDE.md (if needed)\n\nReview whether the current session introduced anything that should be\ncaptured:\n\n- [ ] New route files or API endpoints? → Backend Structure\n- [ ] New frontend modules? → Frontend Structure\n- [ ] New service patterns? → document the pattern\n- [ ] New env vars? → Environment Variables table\n- [ ] New conventions or rules? → Do \u002F Do NOT sections\n- [ ] New test files or patterns? → Testing section\n\nAlso check for outdated info — file structure descriptions, code\nexamples, config values.\n\n**If changes needed**: edit CLAUDE.md, keeping existing style.\n**If no changes needed**: skip — do not make cosmetic edits.\n\n---\n\n## Step 4 — Verify AGENTS.md ↔ CLAUDE.md alignment (do NOT mirror)\n\n**`AGENTS.md` is the canonical shared context** (every agent — Claude Code,\nCopilot, Cursor, Codex, Windsurf — reads it). **`CLAUDE.md` is a thin addendum**\nthat `@.\u002FAGENTS.md`-imports it and holds only Claude-Code-only notes. They are\n**not** byte-mirrors.\n\n- **NEVER copy `CLAUDE.md` over `AGENTS.md`** — a thin CLAUDE.md would overwrite\n  and destroy the canonical file.\n- Put **shared** content in `AGENTS.md`; **Claude-only** notes in `CLAUDE.md`.\n- Then run **`node scripts\u002Fcheck-context-drift.mjs --strict`** — it enforces the topology (CLAUDE.md\n  `@`-imports AGENTS.md, stays ≤ the line cap, only allowlisted Claude-only\n  headings, no shared-section drift). Fix any findings by moving shared content\n  to AGENTS.md — **never** by mirroring.\n- If a repo is still in the legacy full-mirror state (CLAUDE.md == AGENTS.md),\n  migrate it with **`\u002Fai-context-management migrate`** (flips to AGENTS.md-canonical\n  + thin CLAUDE.md); do not hand-resolve.\n\n---\n\n## Step 5 — Update Plan (if plan path in arguments)\n\nOnly when `$ARGUMENTS` contains a plan file path:\n\n\u003C!-- host-contract: input-acquisition; grammar=path+flags; empty=default -->\n_This site: `path+flags` — no path means skip the plan update entirely; do not search for a plausible plan to update._\n\n**Resuming after a blocked `\u002Fcycle --autonomous` run?** Treat the plan path as\nrequired, not optional — `\u002Fcycle`'s Step 7 handoff card names it explicitly\n(`\u002Fship docs\u002Fplans\u002F\u003Cname>.md`) for exactly this reason: a bare `\u002Fship` here\nskips this step entirely, and the plan's `Status:`\u002FImplementation Log never\npicks up what the autonomous run did.\n\n1. **Read the plan**\n2. **Compare against git diff** — which planned items were implemented?\n3. **Update plan metadata**: `Status: Draft` → `In Progress` → `Complete`\n4. **Mark completed items** in the file-level table\n5. **Add implementation log entry** at the bottom:\n\n```markdown\n## Implementation Log\n\n### \u003CToday's Date>\n- Completed: \u003Cwhat was built>\n- Remaining: \u003Cwhat is left>\n- Deviations: \u003Cany changes from the original plan and why>\n```\n\n6. **Flag deviations** — if implementation diverged, note what changed and why.\n\n---\n\n## Step 5.5 — Plans no longer move on completion (removed)\n\n**There is no archive step** — a completed plan stays in `docs\u002Fplans\u002F` and\ncarries `Status: Complete`; never move it. → `references\u002Fflow-rationale.md`\n§Step 5.5 — read before reintroducing any plan move.\n\n---\n\n## Step 5.8 — Pre-push staleness check (shared-repo overlap)\n\nSkip when the current branch is the repo's default branch (this bundle's own\nmain-only convention — nothing to compare against) or when Step 5 found no\nplan path in `$ARGUMENTS` (no plan means no declared file scope to diff\nagainst).\n\nA different PR may have merged to the default branch first, touching files this\nrun also touched — which surfaces later as `mergeable: CONFLICTING` and can read\nlike a CI flake. Catch it here (→ `references\u002Fflow-rationale.md` §Step 5.8 —\nread when a PR shows CONFLICTING or no CI run):\n\n```bash\nDEFAULT_BRANCH=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>\u002Fdev\u002Fnull || echo main)\ngit fetch origin \"$DEFAULT_BRANCH\"\nBASE=$(git merge-base HEAD \"origin\u002F$DEFAULT_BRANCH\")\ngit diff --name-only \"$BASE\" \"origin\u002F$DEFAULT_BRANCH\"   # touched upstream since this branch's base\n```\n\nIntersect that list with the plan's declared scope — §7's File-Level Plan,\nplus §7b\u002F§11 `Files:` bullets when present (reuse the paths Step 5 already\nread from the plan). An empty intersection: proceed silently, nothing to\nreport.\n\nA non-empty intersection is worth a pause, not an automatic stop — name the\noverlapping files and the upstream commit(s) that touched them, and let the\noperator pick: rebase now (the squash-merge note below already prefers\n`rebase` over `merge` on a `\u002Fship`-created branch, for the same reason) or\npush and resolve on GitHub. Either way, don't decide it silently — a\nconfusing CI investigation later costs more than one question now.\n\nIf a rebase or merge resolves real overlap, re-run the full test suite\nbefore continuing to Step 6 — a clean 3-way merge with zero conflict markers\nis not proof of correctness.\n\n---\n\n## Step 6 — Stage, Commit, Push\n\n### 6.0 Sync manifest — no action (source repo)\n\n**Do not `git add` `scripts\u002F.sync-manifest.json` in the source repo** — it is\ngitignored Category A (timestamp + HEAD sha). Consumers track their **own**\nmanifest. → `references\u002Fflow-rationale.md` §Step 6.0 — read when the manifest\nshows up as a change.\n\n### 6.1 Stage\n\nStage relevant files by name (be specific):\n\n```bash\ngit add scripts\u002Flib\u002F*.mjs tests\u002F*.test.mjs   # your changed source files\ngit add status.md                    # or the new docs\u002Fstatus\u002Fentries\u002F\u003Cfile>.md\ngit add CLAUDE.md AGENTS.md          # only if modified\ngit add docs\u002Fplans\u002F*.md              # only if plan was updated\n# NOTE: do NOT `git add scripts\u002F.sync-manifest.json` in the source repo — it's\n# gitignored here (Category A; regenerated every sync). Consumers track their own.\n# NOTE: do NOT `git add dashboard\u002Findex.html` — it and dashboard\u002Ftelemetry.html\n# are gitignored (Category A; rebuilt by Step 0.5d, never committed).\n```\n\n**Do NOT stage**: `.env`, credentials, `node_modules\u002F`, temp\u002Fgenerated files.\n\nIf untracked files look unintentional (temp, OS files), skip silently.\nInclude all source, docs, tests, and config.\n\n### 6.2 Commit message + provenance values\n\nFollow project convention:\n\n```\n\u003Ctype>: \u003Cconcise description>\n\n\u003Coptional body with WHY if significant>\n```\n\nTypes: `feat`, `fix`, `refactor`, `docs`, `style`, `test`, `chore`.\nKeep first line under 72 chars. Body explains WHY, not WHAT.\n\n**Pass the message as a file or on stdin** (not `-m`, no shell interpolation).\n**Prefer stdin** — `--message-file -` reads from stdin (a heredoc works,\nleaves nothing behind; use `-`, not `\u002Fdev\u002Fstdin`, which Git-Bash resolves to\na non-regular file). The file route (Write tool →\n`.claude\u002Ftmp\u002Fship-commit-msg-\u003Cepoch>.txt`, then `--message-file \u003Cthat\npath>`, deleted once the commit lands) works too, but never your session\nscratchpad dir — `--message-file` refuses any path outside `repoRoot`. Why\nstdin is preferred: `references\u002Fcommit-provenance-deep-dive.md`.\n\nDo NOT include any `AI-*` lines — the helper is their only writer and rejects\nthem (`reserved-trailer`).\n\nDecide the provenance values (full convention: `docs\u002Freference\u002Fcommit-provenance.md`):\n- `--models` — comma list of models that participated this session\n  (e.g. `claude` alone; `claude,gemini,gpt` when the audit loop ran).\n- `--gate` — four values. The first two are **verified** (the helper queries the\n  run's `audit_runs` row); the last two are **declared**.\n  - `passed` — fresh evidence, the store says the run converged, **and the\n    committed tree IS the audited tree**.\n  - `converged` — the same evidence and the same store verdict, but the\n    **committed tree DIFFERS from the audited tree**. This is the\n    audited-then-remediated ship: the audit ran, its findings were accepted and\n    **fixed**, so the tree moved *because of the gate*. Not a bypass, and it must\n    not be labelled as one.\n  - `waived` — a declared disposition with no verified verdict: shipped past a\n    gate via `--ignore-p0`\u002F`--no-tests`\u002Fetc., OR verification unavailable (cloud\n    off \u002F run not found \u002F run did not converge).\n  - `not-run` — no fresh evidence at all (docs-only ships).\n\n`passed` and `converged` clear the identical store bar and are mutually\nexclusive halves of one comparison — asking for one on the wrong tree state\npoints at the other, and `--no-tests` always caps the verdict downward\n(`waived` or `not-run`), never up. `passed` is rare **by design** (Steps 2-5\nwrite status.md\u002FCLAUDE.md between audit and commit, so even a clean audit\nusually lands on `converged`) — do not hand-write evidence or reorder a ship\nto chase it. `--no-run-id --gate not-run` disclaims a marker inherited from\nan unrelated prior audit. Full rationale for each value, `--no-run-id`, and\nthe freshness rule: `references\u002Fcommit-provenance-deep-dive.md`.\n\n### 6.3 Commit and push\n\n**The `\u002Fship` command IS the user's approval.** Proceed directly — no\nconfirmation prompts.\n\n**Both of these are now REQUIRED, and `ship-commit` refuses without them.**\n\n```bash\n# Capture the worktree identity ONCE, at the start of the ship (Step 0 if you\n# ran one), and pass that exact pair to every ship-commit invocation in the run.\nSHIP_HEAD=$(git rev-parse HEAD)\nSHIP_BRANCH=$(git symbolic-ref --quiet --short HEAD)   # empty ⇒ detached\nEPOCH=$(date +%s)\nMODELS=\"claude-opus-5-5,gpt-6.1-terra\"   # the models that actually did the work this ship\nGATE=not-run                     # passed | converged | waived | not-run — see AI-Gate above\n\nnode scripts\u002Fship-commit.mjs --message-file \".claude\u002Ftmp\u002Fship-commit-msg-$EPOCH.txt\" --skill ship --models \"$MODELS\" --gate \"$GATE\" --expect-head \"$SHIP_HEAD\" --expect-branch \"$SHIP_BRANCH\" --path scripts\u002Flib\u002F*.mjs --path tests\u002F*.test.mjs\ngit push origin \"$SHIP_BRANCH\"\n```\n\nIdentity is a precondition, not a warning, because a concurrent session can\namend\u002Frebase\u002Fcheckout between your first command and your commit, and only a\nsha+branch pair (not a content hash) catches it. **On a detached HEAD** pass\n`--expect-detached` instead of `--expect-branch` — a head with no ref\ndisposition is `incomplete-expectation` → exit 2. You may omit both flags\nonly when a FRESH audit ran this session (the evidence marker supplies the\nbundle); an older marker reports `pre-bundle-evidence` and needs them passed\nexplicitly. Full rationale: `references\u002Fcommit-provenance-deep-dive.md`.\n\n**Shared working tree — `--path` is MANDATORY, not conditional.** `ship-commit`\nrefuses an unscoped commit outright: it cannot know whose staged entries the\nindex holds, so it requires you to declare what you are shipping. There is no\noverride flag, deliberately. Add one `--path \u003Cfile>` per file you\nare shipping:\n\n```bash\nnode scripts\u002Fship-commit.mjs --message-file \".claude\u002Ftmp\u002Fship-commit-msg-$EPOCH.txt\" --skill ship --models \"$MODELS\" --gate \"$GATE\" --expect-head \"$SHIP_HEAD\" --expect-branch \"$SHIP_BRANCH\" --path scripts\u002Ffoo.mjs --path tests\u002Ffoo.test.mjs\n```\n\nThis commits those paths' worktree contents and leaves every other index\nentry staged and untouched. Untracked paths are handled (marked\nintent-to-add, rolled back if the run is rejected). Do **not** fall back to a\nbare `git commit -- \u003Cpaths>` — it scopes correctly but drops the `AI-*`\nprovenance trailers, which is exactly what this helper exists to prevent.\n\n**A directory is refused, not expanded** — pass each file, including for a\ndeleted directory. (Why, and the TOCTOU reasoning behind `--path`:\n`references\u002Fcommit-provenance-deep-dive.md` §Step 6.3 scoping detail — read\nwhen a `--path` refusal surprises you.)\n\nOn `post-commit-drift` it exits 1 and prints a recovery command: **do not push.**\n\nExit contract: `0` = committed (trailers appended). `2` = input rejected —\nfix exactly what the `AGENT FIX:` stderr lines say and re-invoke (max 2\nretries, then report). `1` = operational failure — report it; do not\nloop. **Fallback (stale consumer sync only)**: if the helper script does\nnot exist on disk, fall back to `git commit -F \u003Cmessage-file>` and print\none line: `provenance trailers skipped (helper unavailable — re-run npm\nrun sync)`.\n\nIf push fails (behind remote, etc.), inform the user and suggest the\nfix. Do NOT force push.\n\n**CI on the pushed head**: `node scripts\u002Fci-merge.mjs --pr \u003Cn> --wait-only` reports every check; add `--merge` only when the user said to merge (`references\u002Fci-merge.md`).\n\n**Continuing work in the same worktree after a squash merge: rebase, never\nmerge** — check mergeability before assuming a CI trigger is broken. Why, and\nthe multi-commit caveat: `references\u002Fcommit-provenance-deep-dive.md` §Continuing\nwork after a squash merge.\n\n---\n\n## Step 6.5 — Security Memory Refresh + Capture Hint (after successful push)\n\nIf push succeeded AND `docs\u002Fsecurity-strategy.md` exists in the repo,\nrun the refresher to keep the Supabase index in sync with markdown (only ever\npublishes pushed state — R3-H3 design constraint). Surface the result line\nbriefly.\n\n```bash\nnode scripts\u002Fsecurity-memory\u002Frefresh-incidents.mjs\n```\n\n**Call the script by path, never `npm run security:refresh`** — that alias\nexists in the source repo only (the sync never merges npm scripts into a\nconsumer's `package.json`); the refresher itself **is** synced\n(`scripts\u002F.claude-skills\u002Fsecurity-memory\u002Frefresh-incidents.mjs`). Why:\n`references\u002Fpost-push-advisories.md` §6.5.\n\nAfter refresh, regex-match the HEAD commit subject against\n`\u002Ffix.*\\bsecurity\\b|\\bcve\\b|\\bvuln\\b|\\bleak\\b|\\binjection\\b|\\bauth\\b|\\bxss\\b|\\bcsrf\\b|\\brce\\b\u002Fi`\n(word-boundary-anchored on purpose). If matched, emit a single passive log line\n(NOT an interactive prompt — `\u002Fship` runs to completion without asking, which\nis the whole contract in its `description`; a prompt here would strand the\npush mid-flight):\n\n```\n⚠ Security-relevant commit detected: \"\u003Csubject>\".\n  Run `\u002Fsecurity-strategy add-incident from-commit \u003Csha>` to draft an\n  incident memory entry from this fix.\n```\n\nThe user reads this and decides whether to invoke `\u002Fsecurity-strategy`\nthemselves. No blocking, no prompt, no input.\n\nIf `docs\u002Fsecurity-strategy.md` doesn't exist → no-op (don't suggest\nbootstrap on every push; that's noise).\n\n---\n\n## Step 6.6 — Friction closure (after successful push, advisory)\n\nCompletes the friction-feedback loop (plan: `docs\u002Fplans\u002Ffriction-feedback-loop.md`\nC10). This step surfaces friction notes that the just-pushed commit may have\nresolved, so a recurring papercut gets marked closed instead of recurring forever.\n\n\u003C!-- host-contract: hook-rule; rule=friction-closure-after-push; portable=node scripts\u002Fcross-skill.mjs quality session-review; accelerator=.claude\u002Fhooks\u002FUserPromptSubmit -->\n\n> **Rule (all hosts)** — after a successful push, pending friction notes are\n> surfaced once, and the step is accepted when the session-review command has\n> run and its output reported. **Portable path**: the command below, which is\n> in the sync closure and works everywhere. **Accelerator (Claude Code only)**:\n> a `UserPromptSubmit` hook injects `> Relevant prior friction` callouts during\n> the session and records a breadcrumb, so the notes are already in view.\n>\n> On a host with no hooks the session-review call is usually empty — a correct\n> empty, never evidence that no friction existed. Why:\n> `references\u002Fpost-push-advisories.md` §6.6.\n\nIf push succeeded, list pending injected-but-unlinked friction:\n\n```bash\nnode scripts\u002Fcross-skill.mjs quality session-review\n```\n\nFor each pending note, emit a single passive line with the ready link command\n(NOT an interactive prompt — `\u002Fship` runs to completion without asking, same as\nthe Step 6.5 security hint):\n\n```\n⚠ Prior friction you were warned about: \"\u003Ctitle>\" (\u003Cmemory_name>).\n  If this commit fixed it: node scripts\u002Fcross-skill.mjs quality link \\\n    --memory \u003Cmemory_name> --kind commit --ref \u003CHEAD sha>\n```\n\nCloud-off, no breadcrumb, or no pending notes → **no-op (silent)** — never noise.\n`quality link` is idempotent + local-first; the user decides whether to run it.\nAdvisory; never blocks the ship.\n\n---\n\n## Step 6.7 — Final-review credit (after successful push, advisory)\n\nThe missing caller of `final-review-adjudicate` \u002F `final-review-record-fix`\n(why: `references\u002Fpost-push-advisories.md` §6.7).\n\n**Run AFTER the commit lands**, so the sha handed to `--commit` is the real one.\n`$REPO` is the `owner\u002Frepo` slug (same value `LEARNING_REPO_NAME` uses — the\nbare repo name silently misses the lookup):\n\n```bash\nnode scripts\u002Fcross-skill.mjs final-review-pending --repo \"$REPO\" --render --commit \"$(git rev-parse --short HEAD)\"\n```\n\nPrint its stdout verbatim. That is the whole integration — the command renders\nthe finished card, so there is nothing to parse and no formatting decision here.\nOmit `--render` to get the versioned JSON instead (`schemaVersion`, `state`,\n`counts`, `items`) if you need it programmatically.\n\n**Walking the whole queue** (JSON form) pages by a **keyset cursor** on\n`nextCursor`, never an offset → `references\u002Fpost-push-advisories.md` §6.7 — read\nwhen you need more than the card's one page.\n\n**A ruling on EITHER axis is a label** — `adjudication_outcome` (triage) or\n`user_action` (this step's durable override). When the card prints a\n`⚠ N finding(s) where the ship-time disposition and the triage ruling disagree\nin direction` line (`axisConflicts`), reconcile those by hand; everything else\nneeds no special handling. Re-running the final reviewer does not erase prior\nrulings (`references\u002Fpost-push-advisories.md` §6.7).\n\n**Advisory only — the reader always exits 0** across its three result states\n(`ready` \u002F `disabled` \u002F `unavailable`), emitting empty output when cloud is off\nor nothing is pending, and a single line carrying just a diagnostic CODE when\nthe store is unreachable. A missing label is not a reason to stop a ship.\n\nTreat a stale sha as a reason to skip the card, not to re-render it: the\n`--commit` value should be the commit you just made.\n\nThe card offers `accepted`\u002F`dismissed` for unadjudicated findings, `accepted`\nonly for a fixed-but-unlabelled one (a shipped fix implies the finding was\nreal), and a complete `record-fix` line for an accepted-but-unfixed one. **You\nchoose which finding a commit fixed** — the card lists candidates and their\ncommands, and infers no attribution from \"a file changed\". Output is bounded\n(10 items) with a pointer to `final-review-stats --worksheet` for the full\nqueue.\n\n---\n\n## Step 6.8 — Consumer-side verification (after successful push, advisory)\n\n`git push` exiting 0 proves the transfer completed. **It proves nothing about\nthe receiver's view** — and a generated artifact can pass every check against\nits *source* while the built output carries real defects. So fetch the thing\nback the way a consumer gets it, and check **that**.\n\n**A feature-branch push publishes nothing to consumers** (since 2026-10-10 the\npre-push hook syncs only when the default branch is updated, so unmerged tooling\nnever reaches them). For a branch\u002FPR ship, the synced-bundle row is `unverified\n(blocked: not merged)` until the PR merges and the source repo re-syncs its\nconsumers from an up-to-date default branch.\n\nPick the row(s) this push actually produced:\n\n| Artifact | Consumer-side retrieval | Subject check |\n|---|---|---|\n| the pushed commit | `git fetch`, then `git worktree add --detach \"$VERIFY_DIR\" \"$PUSHED_SHA\"`; in it, a frozen clean install (`npm ci`, or the repo's own package manager's equivalent), then `skills:hydrate` (the Worktree preflight above — it copies the gitignored tooling tree in, as the pre-push run had it), then **exactly the command the pre-push hook runs**. Do not inject the main checkout's `.env` into the test process (e.g. `NODE_OPTIONS=\"-r dotenv\u002Fconfig\"`): children spawned with a cwd outside the tree inherit the preload and die on `MODULE_NOT_FOUND`. A bare `git clone` is valid only when no test reads the tooling tree — otherwise tests that need it skip, and the run reads green having run less. Remove the worktree afterwards | green **in the worktree**, with **both the passed and the skipped count equal to the pre-push run's** — a different skip count is a different suite, not a confirmation. Catches tracked-vs-ignored and case-only path faults invisible locally |\n| the synced consumer bundle | **authoritative**: `node scripts\u002F.claude-skills\u002Flib\u002Fsync-isolation-verify.mjs`, run *in the consumer's MAIN checkout* — note the `lib\u002F` segment, it is a module rather than a top-level script. A linked worktree cannot answer this (`runGates` refuses one at `preflight` with exit 2 — why: `references\u002Fflow-rationale.md` §Step 6.8). `npm run sync:dry` from here is the pre-check, not the verdict | zero unexpected diffs; no orphans |\n| the skill manifest | re-derive from the pushed sha, not the working tree | regenerated bytes identical |\n\n**Write the outcome with `pending-note write` (body on stdin) — never by\nre-opening the status.md entry you just pushed** (status.md is append-only;\nthis step runs AFTER that entry's commit already landed, so re-opening it\nmeans a second commit and a second push). Include in the file: the immutable\nlocator (full sha \u002F digest \u002F bundle version), the retrieval command actually\nrun, and the observed result. Write it with:\n\n```bash\nnode scripts\u002Flib\u002Fworktree-preflight.mjs pending-note write\n```\n\nIt resolves the MAIN checkout itself (never hand-write the path) and stamps the\nfilename with the shipped sha and the time; the **next** `\u002Fship`'s Step 2 drains\nit. If no further `\u002Fship` happens, the notes simply sit there unread — an\nacceptable loss for advisory documentation (never a gate), not a reason to force\na push now. Incident history behind each of these rules:\n`references\u002Fpost-push-advisories.md` §6.8.\n\n**Three terminal states, and only three**: `verified`, `failed`, `unverified`.\n**`unverified` must name a concrete blocked prerequisite** — \"no network in this\nenvironment\", \"no consumer checkout on this machine\" — never a bare \"not\napplicable\". A missing prerequisite is a fact; an undefined *impossible* is an\nexcuse. **Never inherit the producer-side green.**\n\nAdvisory by construction: the push already happened, so this cannot block it —\nand per the gate-level rule, machine and remote state may advise where repo\nstate may block. → `references\u002Fverification-discipline.md` §6.\n\n---\n\n## Step 7 — Emit Ship Event (always)\n\nAfter commit + push completes (or is blocked), record the outcome:\n\n```bash\nnode scripts\u002Fcross-skill.mjs record-ship-event --json '{\n  \"outcome\": \"shipped\" | \"blocked\" | \"warned\" | \"overridden\" | \"aborted\",\n  \"blockReasons\": [\"test-failure\",\"lint-failure\",\"type-check-failure\",\"format-failure\",\"open-p0\",\"missing-regression-spec\",\"secrets-detected\"],\n  \"openP0Count\": \u003Cfrom Step 0.5a>,\n  \"openP1Count\": \u003Cfrom Step 0.5a>,\n  \"missingSpecCount\": \u003Cfrom Step 0.5b>,\n  \"overriddenByUser\": \u003Ctrue if any override flag was used>,\n  \"overrideFlag\": \"\u003Ce.g. --no-tests or null>\",\n  \"stackDetected\": \"js-ts\" | \"python\" | \"mixed\" | \"unknown\",\n  \"framework\": \"\u003Cfastapi|django|flask|null>\",\n  \"durationMs\": \u003Cwall-clock ms from step 0.5 to now>\n}'\n```\n\n**Outcome semantics**:\n- `shipped` — everything passed, commit pushed\n- `warned` — shipped despite non-blocking warnings\n- `overridden` — user passed `--no-tests` or similar\n- `blocked` — blocking check failed, push did not occur\n- `aborted` — Claude aborted (secrets detected, nothing to commit, etc.)\n\n`blockReasons` is always an array — empty on `shipped`, populated otherwise.\n\nFire-and-forget — do not block on output. If cloud mode is off, CLI\nprints `{\"ok\":true,\"cloud\":false}` and returns 0.\n\n**Close-out** (read-only): `references\u002Fpost-push-advisories.md` §Close-out.\n\n---\n\n## Train mode (`\u002Fship --train`)\n\n**Source-repo-gated** (`package.json.name === \"claude-engineering-skills\"`); consumers skip this (no sync closure to classify\nagainst, no sync tools to publish with). One human invocation lands several already-audited, `fleet ready` PRs: one status entry,\nper-PR provenance in each squash commit, one combined test run, one consumer sync. **Human-only, like the rest of this skill** — `--train` does not relax the\nexplicit-invocation rule above. Its three flags are read only from this invocation. Runbook,\nrefusals, overrides, recovery: `references\u002Ftrain-mode.md` (read it before running any verb of\n`node scripts\u002Fship-train.mjs plan | message | land | verify | sync`).\n\n---\n\n## Reference files\n\nThis skill's canonical flow is above. The files below cover specialised\nsituations — read them only when the trigger applies.\n\n| File | Summary | Read when |\n|---|---|---|\n| `references\u002Farchitecture-and-dashboard-refresh.md` | Why the arch-map and dashboard pages are Category A, 0.5c's failure-mode matrix, and 0.5c\u002F0.5d history. | Debugging Step 0.5c\u002F0.5d (staging, a refresh that did not run), or a consumer asks to build its dashboard. |\n| `references\u002Fcommit-provenance-deep-dive.md` | Step 6.2\u002F6.3's AI-Gate and worktree-identity deep-dive — why each value\u002Fflag exists and the incidents behind them. | Debugging `--gate`\u002F`--expect-head`\u002F`--expect-branch`\u002F`--path` behaviour, or before changing one. |\n| `references\u002Fflow-rationale.md` | Why \u002Fship's own flow is shaped as it is — the explicit-invocation bar, numbering, and Steps 0.5b-6.8 rationale. | A step's one-liner points here, or before changing that step's rule or its numbering. |\n| `references\u002Finput-acquisition.md` | Where a skill's arguments come from on any host, and what to do when there are none. | Reading $ARGUMENTS on a host that does not substitute it, or deciding what empty input means at a site. |\n| `references\u002Fmigration-credentials.md` | Which role applies migrations, why the runtime DSN cannot, and where its credential belongs (never in .env). | Step 0.5g — a store is behind and `--migrate` is refused with `42501` (`must be owner of table …` \u002F `permission denied for schema public`). |\n| `references\u002Fpost-push-advisories.md` | Steps 6.5-6.8's post-push advisories and the Step 7 close-out - incident history, kept out of the routine flow. | Debugging Step 6.5-6.8's behaviour, changing one, walking Step 6.7's whole queue, or writing the close-out. |\n| `references\u002Fpre-ship-gate-queries.md` | Step 0.5's pre-ship nudges (0.5a\u002Fb\u002Fe\u002Fg\u002Fh\u002Fi) — the incident history behind each rule, kept out of the routine flow. | Debugging a Step 0.5 nudge's behaviour, or before changing one. |\n| `references\u002Fpython-environment-discovery.md` | Python pre-push command discovery — env wrapper detection + per-tool probe order. | detect-stack returned `python` or `mixed` with Python files in the diff. |\n| `references\u002Fstatus-md-format.md` | status.md session-log template + update rules + persona \u002F UX status sections. | Step 2 — creating status.md for the first time, OR appending UX \u002F Persona \u002F Regression-Lock \u002F Plan-Verify sections. |\n| `references\u002Ftrain-mode.md` | \u002Fship --train runbook — plan, land, verify, sync a batch of ship-ready PRs; refusals, overrides, recovery. | `\u002Fship --train` was invoked, or the user asked to land several ready PRs together. |\n| `references\u002Fupstream-triage.md` | Step 0.5h — triaging a consumer's upstream report: the owning store, the FULL uuid, and the freshness check. | Step 0.5h's card lists a report you are about to `ack`, `fix` or `wont-fix`. |\n| `references\u002Fverification-discipline.md` | Verification discipline — pinned citations, figure provenance, two-direction proof, attribution, consumer-side checks. | Step 6.8 — the push succeeded and the artifact must be verified from the consumer side. |\n| `references\u002Fci-merge.md` | ci-merge.mjs - wait on every check of the pushed head, reject red or UNSTABLE, update BEHIND, squash-merge. | A push opened or updated a PR: waiting on CI, or merging. |\n| `references\u002Fclaims-check.md` | Step 1.5 claims check — every figure in a write-up traces to an artifact and carries its provenance label. | Step 1.5 — the change carries a write-up, results doc or status report. |\n| `references\u002Fworktree-preflight.md` | Worktree preflight remedy — the skills:hydrate npm script to add when a linked worktree lacks the tooling tree. | A command dies with `MODULE_NOT_FOUND` in a linked worktree AND this repo has no `skills:hydrate` script. |\n\n— end of \u002Fship —\n",{"name":5,"description":49,"allowed-tools":50,"disable-model-invocation":51},"Sync all project documentation, optionally update a plan, then commit and push to git.\nUpdates status.md (session log), syncs CLAUDE.md to AGENTS.md, and handles git workflow.\nUse when the user is ready to commit and push their work.\nRecommend it on: \"ship it\", \"commit and push\", \"push my changes\", \"ready to ship\".\nDO NOT INVOKE THIS SKILL ON YOUR OWN INITIATIVE — it commits and pushes, which is not\nundoable once the push lands. Run it only when the user asked for it in their own words\nthis turn. If they merely approved a design (\"looks good, ship it\" about a plan), say\n\u002Fship is available and stop. Their invocation IS the approval that lets every step skip\nconfirmation, which is exactly why it cannot be self-invoked.\nFull command syntax: see the Usage section in this skill.\n","Bash(node scripts\u002Fcross-skill.mjs detect-stack --include-env-manager)",true,"main","36dc4ca6e60a80adcf0ef887a4f103a108c02c4c","skills\u002Fship","https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills","https:\u002F\u002Fgithub.com\u002FLbstrydom","Studying M.Sc Computer Science at Heriot Watt","2026-11-14 03:15:23",{"source":8,"skillName":5,"commands":60,"prompt":70,"zipAvailable":51},[61,64,67],{"tool":62,"command":63},"npx","npx skills add Lbstrydom\u002Fclaude-engineering-skills --skill ship",{"tool":65,"command":66},"bunx","bunx skills add Lbstrydom\u002Fclaude-engineering-skills --skill ship",{"tool":68,"command":69},"pnpm","pnpm dlx skills add Lbstrydom\u002Fclaude-engineering-skills --skill ship","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002FSKILL.md",[72,78,82,86,90,94,98,102,106,110,114,118,122,126,130,134],{"path":73,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":76,"sourceHtmlUrl":77},"gate-contract.json","REFERENCE","参考文档",93089,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Fgate-contract.json",{"path":79,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":80,"sourceHtmlUrl":81},"references\u002Farchitecture-and-dashboard-refresh.md",4884,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Freferences\u002Farchitecture-and-dashboard-refresh.md",{"path":83,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":84,"sourceHtmlUrl":85},"references\u002Fci-merge.md",3370,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Freferences\u002Fci-merge.md",{"path":87,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":88,"sourceHtmlUrl":89},"references\u002Fclaims-check.md",3369,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Freferences\u002Fclaims-check.md",{"path":91,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":92,"sourceHtmlUrl":93},"references\u002Fcommit-provenance-deep-dive.md",7352,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Freferences\u002Fcommit-provenance-deep-dive.md",{"path":95,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":96,"sourceHtmlUrl":97},"references\u002Fflow-rationale.md",6751,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Freferences\u002Fflow-rationale.md",{"path":99,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":100,"sourceHtmlUrl":101},"references\u002Finput-acquisition.md",4511,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Freferences\u002Finput-acquisition.md",{"path":103,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":104,"sourceHtmlUrl":105},"references\u002Fmigration-credentials.md",3515,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Freferences\u002Fmigration-credentials.md",{"path":107,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":108,"sourceHtmlUrl":109},"references\u002Fpost-push-advisories.md",10383,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Freferences\u002Fpost-push-advisories.md",{"path":111,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":112,"sourceHtmlUrl":113},"references\u002Fpre-ship-gate-queries.md",24046,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Freferences\u002Fpre-ship-gate-queries.md",{"path":115,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":116,"sourceHtmlUrl":117},"references\u002Fpython-environment-discovery.md",3664,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Freferences\u002Fpython-environment-discovery.md",{"path":119,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":120,"sourceHtmlUrl":121},"references\u002Fstatus-md-format.md",8119,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Freferences\u002Fstatus-md-format.md",{"path":123,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":124,"sourceHtmlUrl":125},"references\u002Ftrain-mode.md",7594,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Freferences\u002Ftrain-mode.md",{"path":127,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":128,"sourceHtmlUrl":129},"references\u002Fupstream-triage.md",1115,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Freferences\u002Fupstream-triage.md",{"path":131,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":132,"sourceHtmlUrl":133},"references\u002Fverification-discipline.md",28340,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Freferences\u002Fverification-discipline.md",{"path":135,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":136,"sourceHtmlUrl":137},"references\u002Fworktree-preflight.md",3413,"https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002Freferences\u002Fworktree-preflight.md",[139,157,168,186,198,232],{"skillNo":140,"name":141,"nameZh":6,"description":142,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":143,"categories":144,"keywords":150,"publishedAt":44},"01M6J45X4FDB2AD5BGS4R4QY8X","stated-absent","Negative fixture — the contract's stated string never appears in this file.","https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Ftests\u002Ffixtures\u002Fgate-honesty\u002Fnegative\u002Fstated-absent\u002Fskills\u002Fstated-absent\u002FSKILL.md",[145],{"categoryNo":146,"categoryName":147,"taxonomyType":148,"taxonomyTypeDesc":149},"01JBSEEDCAT000000000000037","律师与法务","PROFESSION","适用职业",[151,152,153,154,155,156],"stated absent","STRING","fixture","contract","never","file",{"skillNo":158,"name":159,"nameZh":6,"description":160,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":161,"categories":162,"keywords":164,"publishedAt":44},"01M6J45TAX2PPHETZ5TJZG20QQ","nonexistent-implementation","Negative fixture — contract references an implementation file that does not exist.","https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Ftests\u002Ffixtures\u002Fgate-honesty\u002Fnegative\u002Fnonexistent-implementation\u002Fskills\u002Fnonexistent-implementation\u002FSKILL.md",[163],{"categoryNo":146,"categoryName":147,"taxonomyType":148,"taxonomyTypeDesc":149},[165,166,153,167,154,156],"implementation","nonexistent implementation","references",{"skillNo":169,"name":170,"nameZh":6,"description":171,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":172,"categories":173,"keywords":177,"publishedAt":44},"01M6J45P5GN52YFMZ81T7NN1VF","document-only-with-params","Negative fixture — a document-only gate illegally carries params (schema must refuse this fake-check pattern).","https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Ftests\u002Ffixtures\u002Fgate-honesty\u002Fnegative\u002Fdocument-only-with-params\u002Fskills\u002Fdocument-only-with-params\u002FSKILL.md",[174],{"categoryNo":175,"categoryName":176,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000002","文档处理",[178,179,180,181,182,153,183,184,185],"only","document","document only with params","with","pattern","gate","schema","check",{"skillNo":187,"name":188,"nameZh":6,"description":189,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":190,"categories":191,"keywords":193,"publishedAt":44},"01M6J45J1CCZ7AXDGQKBA9VC8M","lying-skill","Fixture skill for the gate-honesty self-check — every gate here is deliberately dishonest.","https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Ftests\u002Ffixtures\u002Fgate-honesty\u002Flying-skill\u002Fskills\u002Flying-skill\u002FSKILL.md",[192],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[194,153,195,183,196,197,185],"lying skill","self","for","fixture skill",{"skillNo":199,"name":200,"nameZh":6,"description":201,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":202,"categories":203,"keywords":213,"publishedAt":44},"01M6J45CW0DNN7K2W39E3PQHEY","visual-audit","Math-first, deterministic visual\u002Fpaint inspection — the 4th UX lens, complementing\npersona-test (journey), click-test (page), and nav-audit (system). Drives Playwright +\ncomputed styles + bounding boxes + CDP forced-pseudo-state to audit declared-token\nconformance, light\u002Fdark theme parity, layout physics, and affordance signifiers. A VLM\nis advisory-only and never gates. CI gate is drift-only on the changed contracted surface.\nTriggers on: \"visual audit\", \"check styling consistency\", \"theme parity\", \"dark mode parity\",\n\"design token drift\", \"paint audit\", \"button\u002Fcard consistency\", \"focus ring audit\",\n\"\u002Fvisual-audit\".","https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fvisual-audit\u002FSKILL.md",[204,207,210],{"categoryNo":205,"categoryName":206,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":208,"categoryName":209,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":211,"categoryName":212,"taxonomyType":148,"taxonomyTypeDesc":149},"01JBSEEDCAT000000000000032","设计师",[214,215,216,217,218,219,220,221,222,223,224,155,225,226,227,228,40,229,178,230,231],"visual audit","visual","audit","inspection","layout","math","focus","design token","cdp","lens","click","physics","card","mode","ci","journey","persona","drift",{"skillNo":233,"name":234,"nameZh":6,"description":235,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":236,"categories":237,"keywords":244,"publishedAt":44},"01M6J4569S40ZASFWW698CHBKP","ux-lock","Generate Playwright e2e specs. Two modes:\n  1. LOCK mode — pin a fix's DOM contract so it doesn't regress (default).\n  2. VERIFY mode — check that a \u002Fplan plan was actually implemented\n     by parsing its Acceptance Criteria section and driving the live URL.\nVERIFY mode grades a plan against a RUNNING app. To judge the plan document\nitself, before anything is built, that is \u002Faudit-plan.\nTriggers on: \"ux lock\", \"lock ux\", \"lock in the fix\", \"write regression spec\",\n\"generate e2e test for\", \"regression test for commit\", \"lock this fix\",\n\"verify the plan\", \"verify plan implementation\", \"check the plan was built\",\n\"audit frontend implementation\", \"did we ship the plan\".\nFull command syntax: see the Usage section in this skill.","https:\u002F\u002Fgithub.com\u002FLbstrydom\u002Fclaude-engineering-skills\u002Fblob\u002Fmain\u002Fskills\u002Fux-lock\u002FSKILL.md",[238,239,242,243],{"categoryNo":205,"categoryName":206,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":240,"categoryName":241,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":211,"categoryName":212,"taxonomyType":148,"taxonomyTypeDesc":149},{"categoryNo":146,"categoryName":147,"taxonomyType":148,"taxonomyTypeDesc":149},[245,246,247,248,249,250,251,227,252,253,254,255,256,257,35,24,258,216,259,260,261],"ux","lock","ux lock","syntax","app","regression","e2e test","default","fix","frontend","acceptance","URL","commit","generate","write","judge","verify plan"]