@oneie/claude 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/abm-strategist.md +67 -1
  3. package/agents/ads-meta.md +67 -1
  4. package/agents/analyst.md +67 -1
  5. package/agents/animator.md +108 -0
  6. package/agents/architect.md +269 -20
  7. package/agents/brand-guardian.md +67 -1
  8. package/agents/brand-strategist.md +67 -1
  9. package/agents/campaign-content.md +67 -1
  10. package/agents/campaign-email.md +67 -1
  11. package/agents/campaign-sms.md +67 -1
  12. package/agents/campaign-social.md +67 -1
  13. package/agents/cco.md +83 -2
  14. package/agents/ceo.md +108 -11
  15. package/agents/chairman.md +197 -0
  16. package/agents/cmo.md +82 -2
  17. package/agents/community-greeter.md +67 -1
  18. package/agents/community-moderator.md +67 -1
  19. package/agents/compliance.md +67 -1
  20. package/agents/copywriter.md +67 -1
  21. package/agents/creative-strategist.md +67 -1
  22. package/agents/cro.md +81 -1
  23. package/agents/cto.md +266 -28
  24. package/agents/customer-interviewer.md +67 -1
  25. package/agents/customer-researcher.md +67 -1
  26. package/agents/customer-success-manager.md +67 -1
  27. package/agents/customer-trainer.md +67 -1
  28. package/agents/cxo.md +82 -1
  29. package/agents/demand-creator.md +67 -1
  30. package/agents/demo-mover.md +67 -1
  31. package/agents/demo-specialist.md +67 -1
  32. package/agents/demo-thai-family-law.md +67 -1
  33. package/agents/designer.md +67 -1
  34. package/agents/discovery-caller.md +67 -1
  35. package/agents/doctor.md +269 -0
  36. package/agents/educate-coach.md +67 -1
  37. package/agents/elevate-tutor.md +67 -1
  38. package/agents/email-lifecycle-marketer.md +67 -1
  39. package/agents/engage-specialist.md +67 -1
  40. package/agents/events-coordinator.md +67 -1
  41. package/agents/foundation-builder.md +67 -1
  42. package/agents/funnel-architect.md +67 -1
  43. package/agents/gift-creator.md +67 -1
  44. package/agents/google-ads.md +67 -1
  45. package/agents/guide.md +67 -1
  46. package/agents/helpdesk-dispatcher.md +67 -1
  47. package/agents/hook-specialist.md +67 -1
  48. package/agents/identify-optimizer.md +67 -1
  49. package/agents/implementer.md +313 -45
  50. package/agents/incident-commander.md +67 -1
  51. package/agents/insights-lead.md +87 -1
  52. package/agents/journey-runner.md +67 -1
  53. package/agents/linkedin-ads.md +67 -1
  54. package/agents/live-sales-chat.md +67 -1
  55. package/agents/market-researcher.md +67 -1
  56. package/agents/media-buyer.md +67 -1
  57. package/agents/memory-keeper.md +195 -0
  58. package/agents/movers-customer-researcher.md +67 -1
  59. package/agents/movers-foundation-builder.md +67 -1
  60. package/agents/movers-market-researcher.md +67 -1
  61. package/agents/movers-pricing-strategist.md +67 -1
  62. package/agents/nurture-architect.md +67 -1
  63. package/agents/offer-architect.md +67 -1
  64. package/agents/onboarder.md +67 -1
  65. package/agents/onboarding-specialist.md +67 -1
  66. package/agents/operations-dashboard.md +87 -1
  67. package/agents/perf-engineer.md +333 -37
  68. package/agents/playbook-writer.md +67 -1
  69. package/agents/plg-strategist.md +67 -1
  70. package/agents/positioning-architect.md +67 -1
  71. package/agents/press-officer.md +67 -1
  72. package/agents/pricing-strategist.md +67 -1
  73. package/agents/privacy-officer.md +67 -1
  74. package/agents/referral-manager.md +67 -1
  75. package/agents/refine-analyst.md +67 -1
  76. package/agents/release-manager.md +446 -39
  77. package/agents/renewals-upsell-rep.md +67 -1
  78. package/agents/review-engineer.md +319 -45
  79. package/agents/rewards-steward.md +67 -1
  80. package/agents/sales-call-coach.md +67 -1
  81. package/agents/sales-closer.md +67 -1
  82. package/agents/security-auditor.md +343 -48
  83. package/agents/sell-closer.md +67 -1
  84. package/agents/share-amplifier.md +67 -1
  85. package/agents/social-media-manager.md +67 -1
  86. package/agents/storyteller.md +301 -0
  87. package/agents/strategist.md +67 -1
  88. package/agents/strategy-aligner.md +67 -1
  89. package/agents/support-agent.md +67 -1
  90. package/agents/tagger.md +327 -0
  91. package/agents/tech-writer.md +195 -22
  92. package/agents/test-engineer.md +398 -29
  93. package/agents/tiktok-ads.md +67 -1
  94. package/agents/tracking-engineer.md +67 -1
  95. package/agents/trailkeeper.md +181 -0
  96. package/agents/upsell-strategist.md +67 -1
  97. package/agents/voice.md +67 -1
  98. package/agents/w1-recon.md +1 -1
  99. package/agents/w2-decide.md +1 -1
  100. package/agents/w3-edit.md +8 -2
  101. package/agents/w4-verify.md +13 -0
  102. package/agents/workflow-optimiser.md +81 -1
  103. package/commands/close.md +916 -160
  104. package/commands/deploy.md +102 -724
  105. package/commands/do.md +58 -2
  106. package/commands/sweep.md +159 -0
  107. package/commands/tasks.md +222 -0
  108. package/hooks/scripts/dev-only.sh +135 -0
  109. package/hooks/scripts/git-add-guard.sh +37 -2
  110. package/hooks/scripts/session-start.sh +32 -4
  111. package/package.json +1 -1
  112. package/rules/scripts.md +85 -0
  113. package/scripts/CLAUDE.md +315 -0
  114. package/scripts/ad-copy-lint.sh +656 -0
  115. package/scripts/agent-actor-parity.sh +129 -0
  116. package/scripts/blocks-manifest-cached.sh +100 -0
  117. package/scripts/chat-context-check.sh +89 -0
  118. package/scripts/chrome.mjs +18 -0
  119. package/scripts/close-metrics.sh +587 -0
  120. package/scripts/close-owner.sh +326 -0
  121. package/scripts/db-sync-lock-check.sh +116 -0
  122. package/scripts/deploy-emit.sh +311 -0
  123. package/scripts/deploy-gate-check.sh +155 -0
  124. package/scripts/deploy-ready.sh +78 -0
  125. package/scripts/deploy-record.sh +605 -0
  126. package/scripts/deploy-schema-check.sh +58 -0
  127. package/scripts/deploy.sh +393 -243
  128. package/scripts/do-auto.sh +127 -26
  129. package/scripts/do-board.sh +429 -0
  130. package/scripts/do-close.sh +1184 -0
  131. package/scripts/do-consumer-sweep.sh +18 -1
  132. package/scripts/do-decide.sh +476 -0
  133. package/scripts/do-fleet.sh +8 -2
  134. package/scripts/do-plan-json.mjs +110 -12
  135. package/scripts/do-prove-selftest.sh +108 -0
  136. package/scripts/do-prove.sh +86 -10
  137. package/scripts/do-rank.py +200 -3
  138. package/scripts/do-reconcile.sh +73 -12
  139. package/scripts/do-signal.sh +101 -23
  140. package/scripts/do-smoke.sh +18 -1
  141. package/scripts/do-w4-gates.sh +11 -1
  142. package/scripts/do-world-check.sh +153 -0
  143. package/scripts/download-stats.sh +172 -0
  144. package/scripts/factory-brief-check.sh +330 -0
  145. package/scripts/factory-check.sh +18 -1
  146. package/scripts/factory-close-check.sh +257 -0
  147. package/scripts/factory-emit.sh +211 -0
  148. package/scripts/factory-executor-check.mjs +353 -0
  149. package/scripts/factory-peak.sh +301 -0
  150. package/scripts/factory-repo.sh +71 -0
  151. package/scripts/factory-review-check.mjs +61 -0
  152. package/scripts/factory-tasks-check.sh +18 -1
  153. package/scripts/fixtures/factory-brief-real.md +44 -0
  154. package/scripts/flywheel-outcome.sh +63 -0
  155. package/scripts/gate-reaper-check.sh +98 -0
  156. package/scripts/gate-reaper.sh +9 -0
  157. package/scripts/gate-watchdog.sh +619 -0
  158. package/scripts/gc-content-check.sh +142 -0
  159. package/scripts/gh-traffic-capture.sh +153 -0
  160. package/scripts/govern-order-check.sh +202 -0
  161. package/scripts/governor-doors-check.sh +86 -5
  162. package/scripts/health.sh +448 -0
  163. package/scripts/id-inventory.mjs +418 -0
  164. package/scripts/incident.sh +212 -0
  165. package/scripts/land.sh +755 -45
  166. package/scripts/lib/gc-finished.sh +77 -0
  167. package/scripts/livekit-ratchet.sh +18 -1
  168. package/scripts/machine-check.sh +1 -1
  169. package/scripts/memory-index-budget.sh +79 -0
  170. package/scripts/npm-downloads.sh +109 -0
  171. package/scripts/one-agents.mjs +204 -8
  172. package/scripts/one-resume.sh +31 -3
  173. package/scripts/pr-body.sh +335 -0
  174. package/scripts/preview-fd-check.sh +289 -0
  175. package/scripts/redirect-lint.sh +169 -0
  176. package/scripts/release.sh +40 -6
  177. package/scripts/resume-lost-sessions.sh +68 -0
  178. package/scripts/shoot-pages.mjs +140 -0
  179. package/scripts/signal-meta-backfill.ts +451 -0
  180. package/scripts/signal-watch.sh +63 -6
  181. package/scripts/speed-cache-check.sh +12 -2
  182. package/scripts/sweep.sh +426 -0
  183. package/scripts/task-titles-dump.ts +101 -0
  184. package/scripts/test-cached.sh +47 -10
  185. package/scripts/test-lanes.sh +14 -0
  186. package/scripts/thread-name-backfill.ts +215 -0
  187. package/scripts/triage-shape-check.sh +149 -0
  188. package/scripts/tsc-cached.sh +155 -8
  189. package/scripts/typedb-flake-check.sh +3 -1
  190. package/scripts/urls-lint.sh +8 -0
  191. package/scripts/verify-board-doors.sh +80 -0
  192. package/scripts/verify-fast.sh +159 -6
  193. package/scripts/worktree-up.sh +21 -3
  194. package/skills/astro/SKILL.md +9 -3
  195. package/skills/astro/optimize-performance.md +3 -2
  196. package/skills/cloudflare/SKILL.md +3 -2
  197. package/skills/cloudflare-security-audit/AI-AND-LLM.md +83 -0
  198. package/skills/cloudflare-security-audit/ATTACK-CLASSES.md +130 -0
  199. package/skills/cloudflare-security-audit/CLIENT-SIDE.md +83 -0
  200. package/skills/cloudflare-security-audit/CLOUD-AND-DEPLOYMENT.md +86 -0
  201. package/skills/cloudflare-security-audit/DATA-ISOLATION-AND-LIFECYCLE.md +84 -0
  202. package/skills/cloudflare-security-audit/DESKTOP-MOBILE-AND-LOCAL-IPC.md +89 -0
  203. package/skills/cloudflare-security-audit/HUNTING.md +251 -0
  204. package/skills/cloudflare-security-audit/LICENSE +21 -0
  205. package/skills/cloudflare-security-audit/MEMORY-SAFETY-AND-BINARY.md +101 -0
  206. package/skills/cloudflare-security-audit/PROTOCOLS-RPC-AND-MESSAGING.md +81 -0
  207. package/skills/cloudflare-security-audit/PROVENANCE.md +78 -0
  208. package/skills/cloudflare-security-audit/RECONNAISSANCE.md +156 -0
  209. package/skills/cloudflare-security-audit/RESOURCE-EXHAUSTION-AND-AVAILABILITY.md +78 -0
  210. package/skills/cloudflare-security-audit/SKILL.md +192 -0
  211. package/skills/cloudflare-security-audit/SUPPLY-CHAIN-AND-RELEASE.md +73 -0
  212. package/skills/cloudflare-security-audit/VALIDATION-AND-REPORTING.md +186 -0
  213. package/skills/cloudflare-security-audit/WEB-PROTOCOL-AND-AUTH.md +105 -0
  214. package/skills/cloudflare-security-audit/report-schema.json +461 -0
  215. package/skills/cloudflare-security-audit/validate-coverage-ledger.cjs +872 -0
  216. package/skills/cloudflare-security-audit/validate-coverage-ledger.test.cjs +740 -0
  217. package/skills/cloudflare-security-audit/validate-findings.cjs +773 -0
  218. package/skills/cloudflare-security-audit/validate-findings.test.cjs +652 -0
  219. package/skills/deploy/REFERENCE.md +713 -0
  220. package/skills/deploy/SKILL.md +140 -0
  221. package/skills/fleet-audit/SKILL.md +58 -0
  222. package/skills/meeting/SKILL.md +220 -0
  223. package/skills/planning/SKILL.md +256 -0
  224. package/skills/shadcn/SKILL.md +1 -1
  225. package/skills/typedb/SKILL.md +7 -0
  226. package/skills/voice/SKILL.md +94 -6
  227. package/skills/voice/corpus-check.sh +87 -0
  228. package/templates/template-agent.md +7 -1
  229. package/templates/template-feature.md +9 -0
  230. package/templates/template-todo.md +29 -0
@@ -54,7 +54,18 @@ _is_blanket() {
54
54
  fi
55
55
  return 1
56
56
  }
57
- _is_blanket "$CMD" || exit 0
57
+ # Any commit on the shared main tree — main receives PRs, never development.
58
+ # QUOTE-STRIPPED and POSITION-ANCHORED, so a command that merely NAMES a commit
59
+ # — a grep for "git commit", a commit message quoting it, a doc being written —
60
+ # is never refused. Measured 2026-09-07: the unanchored form denied
61
+ # `grep -rn "git commit" text/`.
62
+ _is_commit() {
63
+ local bare
64
+ bare=$(printf '%s' "$1" | sed -E "s/'[^']*'//g; s/\"[^\"]*\"//g")
65
+ printf '%s' "$bare" | grep -qE '(^|[;&|][[:space:]]*)git[^;&|]*[[:space:]]commit([[:space:]]|$)'
66
+ }
67
+
68
+ _is_blanket "$CMD" || _is_commit "$CMD" || exit 0
58
69
 
59
70
  # Carve-out: deploy/release publishes the whole tree on purpose.
60
71
  printf '%s' "$CMD" | grep -qE '\bgit\b[^|&;]*\bpush\b' && exit 0
@@ -78,7 +89,31 @@ GITDIR=$(git -C "$TARGET" rev-parse --absolute-git-dir 2>/dev/null)
78
89
  [[ -z "$GITDIR" ]] && exit 0 # not a git repo — let git handle it
79
90
  case "$GITDIR" in */worktrees/*) exit 0 ;; esac # isolated linked worktree — blanket is safe
80
91
 
81
- # Main tree → deny.
92
+ # Main tree → deny. A plain commit gets the dev-only rule; a blanket action gets
93
+ # the sweep warning. Both refuse; the reason must name the right one.
94
+ if ! _is_blanket "$CMD"; then
95
+ CMSG="[git-add-guard] Development on the shared main tree is blocked — we develop on dev.
96
+
97
+ main receives a PULL REQUEST from dev and nothing else. It is never edited,
98
+ never committed to directly, and production only ever ships from a sha that
99
+ arrived on main that way.
100
+
101
+ Do this instead:
102
+ bash .claude/scripts/worktree-up.sh <name> --no-dev # cut from dev
103
+ ...edit, then...
104
+ bash .claude/scripts/land.sh feat/<name> # lands on DEV
105
+ gh pr create --base main --head dev # the only door to main
106
+
107
+ Why: on 2026-09-07 four commits went straight onto main while seven sessions
108
+ shared this tree, and an hour-cold ad conversion map plus its test sat
109
+ uncommitted in it — invisible to every branch, and one blanket stage away from
110
+ landing in a stranger's commit.
111
+
112
+ Override once, for a deliberate rescue: ECC_DISABLED_HOOKS=hook:git-add-guard"
113
+ jq -nc --arg r "$CMSG" '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":$r}}'
114
+ exit 0
115
+ fi
116
+
82
117
  MSG="[git-add-guard] Blanket staging/stashing on the shared main tree is blocked.
83
118
 
84
119
  Multiple windows share this working tree. A blanket add (-A, bare dot, commit -a)
@@ -1,6 +1,7 @@
1
1
  #!/bin/bash
2
- # SESSION-START — one job: reclaim what a dead session left behind, then tell
3
- # the world a session began.
2
+ # SESSION-START — one job: reclaim what a dead session left behind — and, since
3
+ # 2026-09-13, break the one jam whose owners are both ALIVE — then tell the world
4
+ # a session began.
4
5
  #
5
6
  # `_gv_reap` only fires when someone CONTENDS for a lock, so a session that died
6
7
  # holding a governor slot would shrink GOVERN_MAX_GATES until the next contender
@@ -25,11 +26,38 @@ if [ -f "$CLAUDE_PROJECT_DIR/.claude/scripts/lib/govern.sh" ]; then
25
26
  CLAIMS_SWEPT=$(claim_sweep 2>/dev/null || echo 0)
26
27
  fi
27
28
 
29
+ # An `astro dev` / `wrangler dev` that dies leaves its workerd child reparented
30
+ # to launchd, holding ~40MB until the box reboots. gate-reaper.sh has known that
31
+ # shape since 2026-09-05 but nothing ever RAN it: on 2026-09-05 nineteen of them
32
+ # had accumulated over 13 hours and the box sat at 20.5G of 21.5G swap with
33
+ # 997MB free. Reaping them returned swap to 7.8G used / 8.6G free in three
34
+ # seconds. The reaper is 60ms, silent when there is nothing to do, and gated on
35
+ # ppid==1 + a gate shape + a 15-minute age floor, so it can never touch live work.
36
+ REAPED=0
37
+ if [ -x "$CLAUDE_PROJECT_DIR/.claude/scripts/gate-reaper.sh" ]; then
38
+ REAPED=$(bash "$CLAUDE_PROJECT_DIR/.claude/scripts/gate-reaper.sh" --once 2>/dev/null | grep -c '^\[gate-reaper\] reaped' || true)
39
+ fi
40
+
41
+ # The reaper reaps a DEAD owner. The 2026-09-13 deadlock has two LIVE owners —
42
+ # a lock holder with no slot, and a slot holder waiting on that lock — so no
43
+ # reaper could ever see it, and it stalled every session on the box for 8-28
44
+ # minutes at 0% CPU, three times in 35 minutes. gate-watchdog is the one thing
45
+ # that cuts it. A healthy box costs 0.12s and prints nothing; it only spends the
46
+ # confirm delay when it has already found a jam. Its predicate is positive
47
+ # evidence of a closed cycle (--self-test drives all 14 checks, both halves), so
48
+ # a slow-but-working gate is never the victim and a slot owner never is either.
49
+ JAMMED=0
50
+ if [ -x "$CLAUDE_PROJECT_DIR/.claude/scripts/gate-watchdog.sh" ]; then
51
+ JAMMED=$(bash "$CLAUDE_PROJECT_DIR/.claude/scripts/gate-watchdog.sh" --once 2>/dev/null | grep -c '^\[gate-watchdog\] BREAK' || true)
52
+ fi
53
+
28
54
  OUT=""
55
+ [ "${JAMMED:-0}" -gt 0 ] 2>/dev/null && OUT+="🧹 broke ${JAMMED} governor deadlock(s) — a lock was held with no slot while every slot sat idle\n"
56
+ [ "${REAPED:-0}" -gt 0 ] 2>/dev/null && OUT+="🧹 reaped ${REAPED} abandoned gate(s)/workerd orphan(s) burning the box\n"
29
57
  [ "${SWEPT:-0}" -gt 0 ] 2>/dev/null && OUT+="🧹 reclaimed ${SWEPT} stale build slot(s) from a dead session\n"
30
58
  [ "${CLAIMS_SWEPT:-0}" -gt 0 ] 2>/dev/null && OUT+="🧹 reclaimed ${CLAIMS_SWEPT} region claim(s) from a dead worker\n"
31
59
  [ -n "$OUT" ] && { echo ""; printf "%b" "$OUT"; echo ""; }
32
60
 
33
- emit_signal "hook:session-start:ok" 1 "swept=${SWEPT:-0} claims=${CLAIMS_SWEPT:-0}"
34
- emit_world "session:start" "fyi" "session,start" "session started" "swept=${SWEPT:-0}" "claims=${CLAIMS_SWEPT:-0}"
61
+ emit_signal "hook:session-start:ok" 1 "swept=${SWEPT:-0} claims=${CLAIMS_SWEPT:-0} reaped=${REAPED:-0} jams=${JAMMED:-0}"
62
+ emit_world "session:start" "fyi" "session,start" "session started" "swept=${SWEPT:-0}" "claims=${CLAIMS_SWEPT:-0}" "reaped=${REAPED:-0}" "jams=${JAMMED:-0}"
35
63
  exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneie/claude",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "ONE Claude Code plugin — /do lifecycle, W0-W4 BUILD engine, 7-canon reconcile, ONE substrate via MCP, signals, and rules",
5
5
  "files": [
6
6
  ".claude-plugin",
@@ -0,0 +1,85 @@
1
+ ---
2
+ paths:
3
+ - ".claude/scripts/**"
4
+ - "packages/claude/scripts/**"
5
+ ---
6
+
7
+ # Harness script rules
8
+
9
+ Short on purpose. Every rule here is imperative and true at the line it guards;
10
+ the story that earned it is one command away and is never summarised:
11
+
12
+ ```bash
13
+ bash .claude/scripts/incident.sh --for <script> # the accounts for this file
14
+ bash .claude/scripts/incident.sh # every family
15
+ ```
16
+
17
+ Two neighbours, so nothing here restates them: `.claude/scripts/CLAUDE.md` is
18
+ the per-script INVENTORY (what each door does, and the SIGPIPE/`grep -q` and
19
+ governor sections), loaded when you work in that directory;
20
+ `.claude/incidents/` is the ACCOUNTS. This file is only the imperatives.
21
+
22
+ `incident.sh` and `.claude/incidents/` are **monorepo-only** — they hold this
23
+ repo's measured history and do not ship to the factory clone. In a generated
24
+ clone the rules below still apply; the accounts behind them do not travel.
25
+
26
+ ## Writing one
27
+
28
+ - **Declare the manifest — in the TABLE.** The authority is the heredoc in
29
+ `factory-repo.sh` (`portable | needs-env | monorepo-only <script>`); the
30
+ `# manifest:` line in the script itself is a convenience copy the check does
31
+ **not** read. `factory-repo.sh` **refuses to build** with an unclassified
32
+ script, and `--check-portability` only sees TRACKED files — so a new script
33
+ passes the check right up until you commit it.
34
+ - **Split the comment.** A block is two things: the RULE (imperative, 1-2 lines,
35
+ stays inline) and the ACCOUNT (dates, shas, counts, the wrong diagnosis that
36
+ came first — moves to `.claude/incidents/<family>.md` under a `## <id>`, and
37
+ the script keeps a bare `incident:<id>` token). **Carry every measurement,
38
+ date, sha, count and error string over unchanged** — a trap loses its authority
39
+ the moment its numbers are summarised away; rewrapping the prose around them is
40
+ fine. `incident.sh --check` proves every pointer resolves and every account is
41
+ reachable, both directions, both copies — but it scans DIRECTORIES, so it
42
+ cannot see a broken `--for`, which is how deploy.sh's pointers resolved for the
43
+ checker and not for the reader for an hour.
44
+ - **Both copies.** `packages/claude/scripts/` is a hand-maintained verbatim
45
+ mirror with no build step, so it rots silently. `sync-claude-mirror.sh` after
46
+ every edit; a proof that covers canon and not the copy that ships is worse than
47
+ no proof.
48
+ - **`set -uo pipefail`, never `-e`.** These scripts read exit codes as answers
49
+ (`git diff --quiet` exits 1 to mean "differs"); `-e` turns an answer into a
50
+ crash.
51
+
52
+ ## Gates and exit codes
53
+
54
+ - **Never pipe a gate.** `bun run verify | tail` reports tail's status — measured
55
+ reporting **0** for an exit-1 verify. Redirect to a file, or read
56
+ `PIPESTATUS[0]`. In the Claude Code Bash tool (zsh) `PIPESTATUS` is **empty**:
57
+ use `${pipestatus[1]}` or a file.
58
+ - **An unrun gate is never a pass.** `gate-run.sh` exits **127** when its target
59
+ is not on `PATH`. Invoke by absolute path, and distinguish `pass|fail|unrun|n/a`.
60
+ - **Everything heavy goes through `gate-run.sh`** — a raw `vitest` bypasses the
61
+ governor and `hook:load-guard` blocks it.
62
+ - **Exit codes are distinct and named.** "It refused" is not a finding: give each
63
+ refusal its own code and say which one bit.
64
+
65
+ ## Proofs
66
+
67
+ - **A checker must be able to go RED.** Ship a `--self-test` that drives the
68
+ failure, not only the success, and prove it by breaking the code on a copy.
69
+ - **A `.sh` is in no import graph**, so `vitest related` can never select it. A
70
+ shell proof the suite must keep running needs a vitest wrapper **and** a name
71
+ in `VERIFY_FAST_PINS` (`verify-fast.sh`) — otherwise it stops being run the
72
+ moment its author's session ends.
73
+ - **A fixture looser than production certifies the bug.** Build the sandbox to
74
+ match the real tree's shape, then assert.
75
+
76
+ ## Doors
77
+
78
+ - **Run door scripts from the MAIN tree's copy.** `land.sh`, `release.sh`,
79
+ `deploy.sh` and `sweep.sh` all derive `ROOT` from `${BASH_SOURCE[0]}/../..`; a
80
+ worktree's copy operates on that worktree.
81
+ - **And check the main tree is CURRENT** — `git rev-list --count HEAD..origin/main`
82
+ is exactly how many commits old the script you are about to run is. `git status`
83
+ clean says nothing about it.
84
+ - **Deploy and ship are `release-manager`'s**, spawned as a subagent; the box is
85
+ `doctor`'s. See `.claude/commands/deploy.md` and `/sweep`.
@@ -0,0 +1,315 @@
1
+ # CLAUDE.md — `.claude/scripts/`
2
+
3
+ Moved from `.claude/CLAUDE.md` so it loads only when working here.
4
+
5
+ ## Script notes
6
+
7
+ ```
8
+ ├── scripts/ # 65 entries — `ls .claude/scripts/` is the map, not this comment.
9
+ │ # deploy.sh — the whole /deploy pipeline, deterministic:
10
+ │ # gates (tree, tsc ×5, vitest, build, creds, smoke, approval,
11
+ │ # D1) then parallel deploy + health probes. `./deploy` at the
12
+ │ # repo root execs it. It is the AUTHORITY for the deploy
13
+ │ # steps — .claude/commands/deploy.md carries no second copy.
14
+ │ # Its vitest gate PROBES THE MEMO BEFORE TAKING A SLOT (the
15
+ │ # `>>> vitest-memo-probe` block): measured 2026-09-13, the gate
16
+ │ # queued 300s under gate-run.sh and then reported both lanes as
17
+ │ # cache HITs. The probe asks PER LANE the way release.sh's
18
+ │ # receipt does (TEST_CACHE_KEY_ONLY=1 + a stamp per key) and
19
+ │ # skips only when EVERY lane is already green — never by reading
20
+ │ # test-lanes.sh's exit code, which under TYPEDB_LANE_NONBLOCKING=1
21
+ │ # is the pool lane's alone and would skip 19 typedb suites that
22
+ │ # had never run. A miss or an unanswerable probe takes the slot.
23
+ │ # A skipped gate counts as PASSED, stays OUT of HEAVY_IDX (a
24
+ │ # process-less gate there makes the next heavy gate `wait 0` —
25
+ │ # exit 127 — and overwrite its rc), and reports REUSED with the
26
+ │ # stamp's timestamp, never "all pass". --skip-tests is distinct.
27
+ │ # release.sh — the PROD door since 2026-09-05: `promote <sha>`
28
+ │ # fast-forwards branch `release`, materialises .release/ (deps
29
+ │ # by symlink, never bun install) and refuses unless that exact
30
+ │ # tree carries deploy's own full-suite receipt (test-cached
31
+ │ # stamp) + 5 clean tsc; `ship` runs deploy.sh --changed FROM
32
+ │ # .release/ so the suite gate is a memo HIT reported REUSED —
33
+ │ # no --skip-tests, ever. Nine named exit codes; --self-test is
34
+ │ # the red proof. Run MAIN's copy (from inside .release/ it
35
+ │ # resolves ROOT to .release and tries to add .release/.release).
36
+ │ # Step 9 (Lighthouse) is detached by default — SPEED_SYNC=1
37
+ │ # holds it inline, SPEED_TIMEOUT bounds it, 124 reads as unrun.
38
+ │ # Two of its gates are MEMOISED on a byte-identical tree: the
39
+ │ # full vitest suite via test-cached.sh (only passes; a reused
40
+ │ # pass is reported as reused, never as "all pass") and the
41
+ │ # astro build via astro-build-cached.sh (stamp inside dist/,
42
+ │ # key covers one.ie/web + packages/ + the gitignored .env and
43
+ │ # lockfiles). The typechecks are deliberately NOT cached —
44
+ │ # measured 8s serial, inside vitest's shadow, and tsc-cached
45
+ │ # shares a gate_lock with do-reconcile — both take a SLOT
46
+ │ # BEFORE that lock since 2026-09-13, never while holding it
47
+ │ # (govern-order-check.sh). An unpaid deferred-pin
48
+ │ # debt (verify-fast's ledger) now REFUSES a deploy that did not
49
+ │ # run the suite. Red proofs: deploy-gate-check.sh (extracts the
50
+ │ # debt block verbatim and drives it), plus --self-test on
51
+ │ # tsc-cached / test-cached / astro-build-cached.
52
+ │ # roles-check.sh — the nine authority checks (IC row 1 of
53
+ │ # roles-vantage-collapse); --check-gate plants a vantage-vs-rung
54
+ │ # comparison and proves one.ie/web's astro-check ratchet goes red.
55
+ │ # --check-floors runs the page-floor matrix (five callers per
56
+ │ # private page through the real guardPage) and proves an
57
+ │ # ANONYMOUS private page cannot land — it forces anonymity with
58
+ │ # a `signed_out=1` cookie, because DEV_SLUG otherwise makes a
59
+ │ # session-less localhost request the owner of the `one` subtree.
60
+ │ # --check-decide / --check-503 run the authority matrix
61
+ │ # (one.ie/web/tests/authority/) through the real decide(), then
62
+ │ # mutate the FIXTURE to prove the deny cells bite and that a
63
+ │ # swallowed lookup error cannot pass as a 403. --check-worldkey
64
+ │ # (C6) replays the same suite with a world-key Principal — the
65
+ │ # original bug report — and asserts a denial NAMES the node
66
+ │ # instead of comparing a vantage word to a rung word.
67
+ │ # land.sh — the BRANCH door. Bare: merge main in, fast gate in the
68
+ │ # branch's worktree, then --ff-only main. `--pr`: gate then open/
69
+ │ # update a PR, skipping the trunk merge so a reviewer sees what the
70
+ │ # branch added. `--pr --deploy`: the whole workflow in order —
71
+ │ # gate → ship THAT WORKTREE to dev.one.ie → prove the routes with
72
+ │ # do-prove (both bases pinned to dev; it falls back to PROVE_PROD_URL
73
+ │ # and would otherwise prove PRODUCTION) → open the PR carrying the
74
+ │ # dev URL. Reads the probe's ROUTE COUNT, never its exit code —
75
+ │ # `PROVE: skipped` exits 0. RUN MAIN'S COPY: from a worktree, ROOT is
76
+ │ # that worktree and the ff merges the branch into itself (always
77
+ │ # "already up to date", main untouched, three false "landed" reports
78
+ │ # on 2026-09-05). It now verifies with `merge-base --is-ancestor`.
79
+ │ # A red gate is DIAGNOSED, not blamed (2026-09-12): branch-alone and
80
+ │ # dev-alone typecheck read from the memo (`tsc-cached.sh --probe`,
81
+ │ # `TSC_CACHE_ROOT=<tree>`), a gateway pre-flight, and which side of
82
+ │ # the merge touched each failing file — branch / dev / seam /
83
+ │ # environment. The verdict is the run's --note. `--self-test` drives
84
+ │ # all seven shapes, including transport+assertion ⇒ NOT environment.
85
+ │ # It links node_modules for every tracked package.json rather than a
86
+ │ # list — the list missed channels/, pay/backend/ and one.ie/web/ in
87
+ │ # turn, each time reading as a RED gate on a branch of shell scripts.
88
+ │ # deploy-record.sh — appends a run to one.ie/web/src/data/deploy-runs.json,
89
+ │ # which /deploy renders (the factory.astro shape: data, not a live
90
+ │ # query — a deploy runs on a laptop and one-prod cannot read that disk).
91
+ │ # Called by deploy.sh and land.sh on BOTH exits, `|| true`. --self-test.
92
+ │ # Load-bearing few: do-reconcile.sh (7 canons) · do-auto.sh + do-fleet.sh
93
+ │ # (worktree cycles) · do-tier.sh (spine prune) · do-promise-lint/settle.sh
94
+ │ # · signal-watch.sh — every door the factory's signals cross (prod + local),
95
+ │ # with server-timing phases, cf-placement, ratchet budgets, a JSONL
96
+ │ # ledger and --self-test; the instrument for root CLAUDE.md § The brain
97
+ │ # and the edge (a receiver that queries TypeDB inline reads RED here)
98
+ │ # · chrome.mjs (the browser — chrome-headless-shell, backs /browser and
99
+ │ # every do-prove.sh --route check; browser-check.mjs is a shim over it)
100
+ │ # do-prove enforces a LANDING RULE: a route only counts as proven if
101
+ │ # the run ENDED on the path asked for. A signed-out /u/<slug>/* 302s
102
+ │ # to /signin, which renders 200 with no console errors — that scored
103
+ │ # "ok" until 2026-08-04, so authed promise clauses were green on the
104
+ │ # sign-in page. Every route runs SIGNED-OUT first; only a bounce to
105
+ │ # the login wall retries with a dev session (dev-sign-in.ts, or
106
+ │ # OWNER_EMAIL+DEV_PASSWORD over HTTP, localhost base only) so those
107
+ │ # clauses still run in a bare shell. Lazy on purpose: DEV_SLUG makes
108
+ │ # a signed-out request render /u/one/* as workspace `one`, while the
109
+ │ # dev session is tony — signing in up front turned a genuine pass on
110
+ │ # /u/one/workflows into "landed on /u/tony/workflows".
111
+ │ # PROVE_SESSION_COOKIE overrides; PROVE_NO_SESSION=1 drives the red
112
+ │ # half on demand. A worktree without .wrangler/state cannot seed the
113
+ │ # credential — export the two vars or the cookie there.
114
+ │ # · typedb-env.sh (flip one.ie/web/.env local↔cloud) · factory-repo.sh
115
+ │ # (emits one-ie/factory, see below) · sync-claude-mirror.sh (packages/claude)
116
+ │ # None are on PATH — always invoke as `bash .claude/scripts/<name>`.
117
+ │ # TRAP, measured 2026-08-04: in a `set -o pipefail` script,
118
+ │ # `<producer> | grep -q PAT` returns 141 when it MATCHES if the
119
+ │ # producer is still writing — grep -q exits first and the
120
+ │ # producer takes SIGPIPE. Sharp threshold: curl of 87 bytes is
121
+ │ # fine, curl of a 378 KB page is not; shell builtins never
122
+ │ # misfire. It bit do-walk's expect_text (false RED) and
123
+ │ # factory-repo's cycle gate (false GREEN — the pipe sat under a
124
+ │ # `!`, so failures read as green). Capture the output, then
125
+ │ # match it with a here-string. `printf "$out" | grep -q` puts
126
+ │ # the same race one process to the left.
127
+ ```
128
+
129
+ ## A non-run must never read as a pass
130
+
131
+ The SIGPIPE trap above has a sibling class: a gate that never ran, reported green.
132
+ Two shapes, both measured 2026-09-14.
133
+
134
+ - **`gate-run.sh <label> -- <cmd>` exits 127 when `<cmd>` is a bare name not on
135
+ PATH** — `vitest` lives in `node_modules/.bin`, not on PATH. It ran **zero
136
+ tests twice** and the harness reported the wrapper as "completed exit 0". The
137
+ mechanism is unconfirmed in source (`govern.ts` spawns with no `env:`
138
+ override, so PATH *should* inherit); the behaviour is what is measured.
139
+ **Invoke the target by ABSOLUTE path** — `gate-run.sh test --
140
+ /abs/path/node_modules/.bin/vitest run …` — and treat a 127 as RED, never as a
141
+ clean suite.
142
+ - **Piping a gate through `tail` masks its exit code.**
143
+ `FULL_VERIFY=1 bun run verify … | tail -60` printed `[exited with code 0]`
144
+ while `verify` itself exited **1**. Same lesson as the pipefail trap one
145
+ process to the right: read **`${PIPESTATUS[0]}`**, or capture the output first
146
+ and match it with a here-string.
147
+
148
+ ## Machine governor — why N sessions no longer melt the box
149
+
150
+ **Measured 2026-08-18:** several sessions each ran `bun run verify`
151
+ (tsc --noEmit + vitest over 875 test files) concurrently on a 10-core/24GB Mac.
152
+ Six `tsc` and three vitest pools ran at once — load **57**, swap **15.7/16 GB**,
153
+ ~26 MB/s of swapins. Starved by paging, each gate took **18 min instead of ~2**,
154
+ so sessions hit their Bash timeout and launched *more*. The box got slower the
155
+ harder it was pushed. Claude Code was the trigger; the amplifier was that
156
+ nothing in this harness bounded a gate's concurrency or its children's lifetime.
157
+
158
+ macOS ships **neither `flock(1)` nor `timeout(1)`** — both are GNU coreutils.
159
+ That absence is the root reason gates were unbounded here. The governor
160
+ rebuilds both from `mkdir(2)` atomicity and a real process group.
161
+
162
+ **Since 2026-09-04 the mechanics are `scripts/lib/govern.ts` (Bun) and
163
+ `scripts/lib/govern.sh` is a shim.** The design did not change; the runtime
164
+ did — bash 3.2 on macOS has no flock, no timeout and no BASHPID, `set -m`
165
+ needs job control, and a watchdog subshell cannot own the `sleep` it forks.
166
+ Every function NAME, ARGUMENT and EXIT CODE is unchanged, so no caller
167
+ changed. Four functions deliberately stay in bash: `_gv_alive` and the three
168
+ `_gv_vm_stat` / `_gv_swapusage` / `_gv_memsize` wrappers are the seams three
169
+ of the six proofs **stub as bash functions**, and `_gv_claim_key` is BSD
170
+ `cksum` (the POSIX CRC, not zlib's) which the claims checker reads off disk.
171
+ A new `_gv_*` seam a checker stubs belongs in the shim, not in the port.
172
+
173
+ | Piece | Does |
174
+ |---|---|
175
+ | `scripts/lib/govern.ts` | the mechanics — lock/slot loops, `run_bounded`, the claim reaper + TTL, the memory arithmetic, and BOTH memo keys (`tsc_tree_fingerprint`; test-cached's per-file `_hash_rel`). Reached through the shim, never called directly |
176
+ | `scripts/lib/govern.sh` | the shim every caller sources. `gate_lock` (mkdir-atomic named lock, reaps dead owners) · `gate_slot` (machine-wide semaphore, `GOVERN_MAX_GATES`, default 2) · `run_bounded` (wall-clock cap that kills the **process group**) · `gate_pressure` · **claims** — see below. The lock/claim owner is the CALLING SHELL's `$$`, never the short-lived bun process |
177
+ | `scripts/gate-run.sh` | `gate-run.sh <label> -- <cmd>` — slot + bound + group-reap. Re-entrant (`verify` calling `test` inherits the slot, never deadlocks). Execs straight through under `CI=1` or `GOVERN_DISABLE=1` |
178
+ | `hooks/load-guard.sh` | PreToolUse(Bash) backstop for heavy commands typed directly, bypassing package.json |
179
+ | `hooks/governor-escape.sh` | PreToolUse(Bash) — the *escape hatches* themselves are refused unconditionally: `verify:raw`, `test:raw`, a bare `vitest`, an inline `GOVERN_DISABLE=1`/`CI=1`. Matcher shared with the checker at `hooks/lib/governor-escape-match.sh` — one definition, no drift |
180
+ | `scripts/governor-doors-check.sh` | the other half — what the TREE OFFERS. `hook:governor-escape` guards what an executor TYPES, but `cd channels && bun run test` was an ungoverned 135-file two-runner suite and nothing about that string looks like an escape: the hole was the package script. Enumerates every tracked `package.json` at run time (never a hardcoded list), follows one hop through `bash <x>.sh`, and fails on any heavy script that is neither governed nor on the reasoned skip list. Also proves the wrapped relative paths resolve and that a nested gate does not deadlock |
181
+ | `scripts/governor-escape-check.sh` | its proof, three parts: the matcher's case table (bites / does not false-positive), the real hook driven with a payload, and a RED PROOF that guts the matcher and neuters the hook and asserts both halves go red |
182
+ | `scripts/machine-check.sh` | `--watch` for the live read: load, swap **direction**, running gates, held slots, orphans |
183
+ | `scripts/gate-watchdog.sh` | the reaper's OPPOSITE — it breaks the jam whose owners are both **alive**, which by definition no reaper can see (2026-09-13: three deadlocks in 35 minutes, 8-28 min each at 0% CPU). Cuts exactly one shape: a lock holder with **no slot** that has a `gate-run.sh` descendant queueing for one — the single process on the wrong side of the ordering. **Never a slot owner.** The clock is the SLOT's directory mtime, not the lock's: during the live jam the lock changed hands every ~9 minutes (each holder killed by its session's 600s Bash ceiling) while the jam ran >20, so a lock-clocked watchdog never fires. `--once` from `hooks/session-start.sh` (0.12s, silent), `--dry-run` to name a victim without cutting, `--self-test` for 14 checks including a real planted deadlock and three red proofs. The code fix that removes the cause is `govern-order-check.sh`'s SLOT-before-LOCK; this stays useful until every worktree has rebased onto it |
184
+ | `scripts/govern-claims-check.sh` | the claim registry's two properties — exclusion and evaporation — plus a red-proof. `bash .claude/scripts/govern-claims-check.sh`, exits non-zero on failure, touches only a sandbox `GOVERN_DIR` |
185
+ | `scripts/govern-order-check.sh` | **the LOCK ORDER**, proved by deadlocking it. Runs the two roles in the two orders — a gate holding a slot whose child wants the lock, vs a bare caller — and asserts they finish. Its red half plants the pre-2026-09-13 `LOCK -> SLOT` holder and asserts the same harness STALLS, so a green run is never green for lack of power; a third section proves a cache HIT and `--probe` still take neither. ~20s, own sandbox `GOVERN_DIR`, kills only its own children |
186
+
187
+ Wired: `one.ie/web` `verify`/`test`/`check`/`demo:*` plus `api`, `channels`,
188
+ `schema`, `pay/backend`, `packages/sdk` and `packages/cli` `test` all route
189
+ through `gate-run.sh` (`verify:raw`/`test:raw` are the ungoverned escapes,
190
+ and `governor-doors-check.sh` is what keeps that list honest); `do-reconcile.sh types`
191
+ takes a per-folder lock and **caches by tree fingerprint** — git HEAD +
192
+ porcelain + source mtimes — so six identical `tsc` runs collapse to one compute
193
+ and five cache hits; `vitest.config.ts` caps the fork pool (`maxWorkers` **8**,
194
+ `VITEST_MAX_FORKS` to override) with a 1GB per-fork heap.
195
+
196
+ That cap was **4** until 2026-09-01, and the reason it could double is the reason
197
+ it existed: each fork used to hold a ~120MB jsdom. `vitest.config.ts` now defaults
198
+ `environment` to `node` (only ~230 of ~1135 files touch a DOM; the rest opt in
199
+ with a `// @vitest-environment jsdom` docblock), so **jsdom is no longer the memory
200
+ driver** and the same box carries twice the fan-out. Full suite, same tree, all
201
+ green: **518s → 324s → 87.3s**. Memory is still the binding constraint the fleet
202
+ prices in — `gate_headroom` is unchanged, so N sessions cannot each take 8.
203
+
204
+ **The fleet prices its own concurrency in memory, not cores.** `do-fleet.sh`
205
+ capped slots at `cores - 2` — on a 10-core/24GB box that authorised **8
206
+ concurrent worktrees**, and 8 cycles is ~16GB of gates before the editors,
207
+ sessions and OS get a byte. Cores were never the binding constraint.
208
+ `gate_headroom` prices a cycle (~2GB: vitest driver + 4 forks + tsc + node
209
+ overhead), subtracts a reserve, and divides what is actually free. It only ever
210
+ *lowers* the count — `--slots` and the core cap still hold — and the fleet now
211
+ names the binding constraint on its ranking line:
212
+
213
+ ```
214
+ [do-fleet] ranking… (top 5 candidates, 2 slots — bound by memory;
215
+ cores<=8 mem<=2, DRY-RUN)
216
+ ```
217
+
218
+ Tune with `GOVERN_GB_PER_CYCLE` (default 2) and `GOVERN_RESERVE_GB` (default 2).
219
+ A probe that cannot read memory returns 99 — a broken sensor must never
220
+ silently serialise the fleet.
221
+
222
+ `hooks/session-start.sh` calls `gate_sweep_stale`: `_gv_reap` only fires when
223
+ someone *contends* for a lock, so a session that died holding a slot would
224
+ shrink `GOVERN_MAX_GATES` until the next contender happened along. Silent
225
+ unless it actually reclaims something. It also calls `claim_sweep` (below) for
226
+ the same reason.
227
+
228
+ ### SLOT before LOCK — the one ordering rule, and the day it was broken
229
+
230
+ The governor hands out two different things and **the order between them is
231
+ fixed: take the SLOT first, then the LOCK.** A slot bounds the machine; a lock
232
+ dedupes identical work. Take them the other way round anywhere and that site can
233
+ deadlock against every site that takes them correctly.
234
+
235
+ Measured 2026-09-13 — **three occurrences in ~35 minutes**, each stalling every
236
+ session on the box for 8-28 minutes at 0% CPU, one of them a production release
237
+ gate. `tsc-cached.sh` took the per-folder lock and then queued for a slot while
238
+ holding it; `bun run verify:fast` IS `gate-run.sh verify-fast -- …`, so it
239
+ arrives already holding a slot and its child then waits for that lock:
240
+
241
+ ```
242
+ A: gate-run(verify-fast) holds slot-1 -> its tsc-cached waits for lock-tsc-one.ie_web
243
+ B: tsc-cached holds lock-tsc-one.ie_web -> its inner gate-run waits for a slot
244
+ ```
245
+
246
+ Owner pairs measured: 22173/68920, 15762/97557, 87579. **Nothing reaps this** —
247
+ `reapLock` and `gate-reaper.sh` only reap an owner whose pid is GONE, and both
248
+ owners are alive and idle. And **killing the lock holder does not fix it**
249
+ (observed twice): the lock passes straight to the next waiter, which is itself
250
+ inside a gate holding a slot, and the cycle re-forms within seconds. Kill the
251
+ **slot** holder — that is the resource nobody else can produce.
252
+
253
+ Both inverted sites are fixed (`tsc-cached.sh` re-enters itself through
254
+ `gate-run.sh` in an internal `--locked-compute` mode; `do-reconcile.sh` takes the
255
+ lock INSIDE its `gate-run.sh` command). A cache HIT and `tsc-cached.sh --probe`
256
+ still take neither — a memo lookup must never join a queue. Proof, both halves:
257
+ `bash .claude/scripts/govern-order-check.sh`.
258
+
259
+ ### Region claims — one pheromone map, not nine
260
+
261
+ `do-fleet.sh` kept its conflict registry in `LOCK_FILE="$(mktemp)"` — **private
262
+ to one fleet process**. Nine concurrent fleet invocations therefore held nine
263
+ private maps and could not see each other, so two fleets each read their region
264
+ as free and both took it. That is how two fleets came to audit the same auth
265
+ seam on one box: not a lock that failed, a map nobody else could read.
266
+
267
+ The registry now lives at `$GOVERN_DIR/claims/<region>.<cksum>` — the same
268
+ machine-wide directory the gate locks use, so every session and worktree reads
269
+ one map. `ls` it; it is meant to be legible.
270
+
271
+ | Function | Does |
272
+ |---|---|
273
+ | `claim_take <region> [slug]` | deposit. `mkdir(2)` is the arbiter — N racers, exactly one wins. 0 = yours (re-claiming your own is idempotent), 1 = a live worker holds it |
274
+ | `claim_owner <region>` | `<slug> <pid>` of the LIVE owner, empty if free — **reaps before it answers** |
275
+ | `claim_release` / `claim_release_all` | give ground back; the latter is wired to do-fleet's EXIT trap |
276
+ | `claim_list` / `claim_sweep` | the live map / reap everything dead, echo the count |
277
+
278
+ **It is not a lock and there is no coordinator.** A worker that finds its region
279
+ claimed takes the next candidate — contention costs a re-rank, never a wait, so
280
+ there is no queue and nothing to deadlock.
281
+
282
+ **Evaporation is the whole contract.** A claim expires with no human in the
283
+ loop, two ways: the owner pid is dead (`kill -0`, instant) or the lease expired
284
+ (`GOVERN_CLAIM_TTL_SECS`, default 5400s = 90 min — p100 observed fleet duration
285
+ is 79 min). Both fire on the **read** path, so the next contender reclaims dead
286
+ ground by itself; `claim_sweep` at session start only stops uncontended dirs
287
+ accumulating. Note `gate_sweep_stale` matches `lock-*` at depth 1 and will never
288
+ see a claim — that is what `claim_sweep` is for.
289
+
290
+ Proof, not assertion: `bash .claude/scripts/govern-claims-check.sh` runs 12
291
+ concurrent independent processes at one region (exactly one wins), kills a live
292
+ owner and proves the region becomes claimable again, backdates a lease to fire
293
+ the TTL branch, proves a losing multi-region worker holds **no** ground
294
+ (rollback), and proves do-fleet's own `has_conflict` shape clears on a dead
295
+ owner. It also **stubs `_gv_alive` true and asserts the evaporation checks then
296
+ fail** — a checker that stays green against a gutted reaper proves nothing.
297
+
298
+ The lock dir is `$TMPDIR/one-govern` — **machine-wide**, so sessions coordinate
299
+ across worktrees automatically. A worktree only participates once it has this
300
+ code: `do-fleet.sh` now **warns per stale worktree** at launch, because until it
301
+ rebases onto `main` its gates are the old unbounded ones and the slot cap is the
302
+ only thing protecting the box from it.
303
+
304
+ **Editors are the other half of the bill.** Each `/do` cycle drops a ~115MB
305
+ checkout in `.do-worktrees/`; it is gitignored, but editors index gitignored
306
+ paths (Zed only with `search.include_ignored`, which is on here) and every
307
+ indexed worktree costs its own tsserver — measured 16 language-server processes
308
+ holding 2.3-3.0GB from 3 worktrees, entirely outside the gate governor.
309
+ `.vscode/settings.json` + `.cursorignore` exclude it; Zed's equivalent
310
+ (`file_scan_exclusions`) is user-level config, not in this repo.
311
+
312
+ **Reading swap correctly:** swapins spike during *recovery* too, as freed memory
313
+ lets pages fault back in. Thrash is pages going **out** while swap usage grows
314
+ and free memory is scarce. `machine-check.sh --watch` distinguishes the two;
315
+ don't diagnose from the swapin counter alone.