@oneie/claude 0.7.0 → 0.9.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 (263) hide show
  1. package/agents/abm-strategist.md +89 -0
  2. package/agents/ads-meta.md +97 -0
  3. package/agents/analyst.md +173 -0
  4. package/agents/architect.md +280 -0
  5. package/agents/brand-guardian.md +88 -0
  6. package/agents/brand-strategist.md +92 -0
  7. package/agents/campaign-content.md +90 -0
  8. package/agents/campaign-email.md +88 -0
  9. package/agents/campaign-sms.md +87 -0
  10. package/agents/campaign-social.md +87 -0
  11. package/agents/cco.md +99 -0
  12. package/agents/ceo.md +106 -0
  13. package/agents/chairman.md +120 -0
  14. package/agents/cmo.md +101 -0
  15. package/agents/community-greeter.md +89 -0
  16. package/agents/community-moderator.md +92 -0
  17. package/agents/compliance.md +92 -0
  18. package/agents/copywriter.md +100 -0
  19. package/agents/creative-strategist.md +95 -0
  20. package/agents/cro.md +98 -0
  21. package/agents/cto.md +267 -0
  22. package/agents/customer-interviewer.md +93 -0
  23. package/agents/customer-researcher.md +89 -0
  24. package/agents/customer-success-manager.md +90 -0
  25. package/agents/customer-trainer.md +90 -0
  26. package/agents/cxo.md +100 -0
  27. package/agents/demand-creator.md +89 -0
  28. package/agents/demo-mover.md +83 -0
  29. package/agents/demo-specialist.md +89 -0
  30. package/agents/demo-thai-family-law.md +87 -0
  31. package/agents/designer.md +112 -0
  32. package/agents/discovery-caller.md +98 -0
  33. package/agents/doctor.md +139 -0
  34. package/agents/educate-coach.md +100 -0
  35. package/agents/elevate-tutor.md +83 -0
  36. package/agents/email-lifecycle-marketer.md +91 -0
  37. package/agents/engage-specialist.md +99 -0
  38. package/agents/events-coordinator.md +90 -0
  39. package/agents/foundation-builder.md +94 -0
  40. package/agents/funnel-architect.md +89 -0
  41. package/agents/gift-creator.md +100 -0
  42. package/agents/google-ads.md +103 -0
  43. package/agents/guide.md +292 -0
  44. package/agents/helpdesk-dispatcher.md +88 -0
  45. package/agents/hook-specialist.md +91 -0
  46. package/agents/identify-optimizer.md +101 -0
  47. package/agents/implementer.md +305 -0
  48. package/agents/incident-commander.md +120 -0
  49. package/agents/insights-lead.md +93 -0
  50. package/agents/journey-runner.md +113 -0
  51. package/agents/linkedin-ads.md +123 -0
  52. package/agents/live-sales-chat.md +90 -0
  53. package/agents/market-researcher.md +88 -0
  54. package/agents/media-buyer.md +96 -0
  55. package/agents/movers-customer-researcher.md +95 -0
  56. package/agents/movers-foundation-builder.md +96 -0
  57. package/agents/movers-market-researcher.md +97 -0
  58. package/agents/movers-pricing-strategist.md +171 -0
  59. package/agents/nurture-architect.md +99 -0
  60. package/agents/offer-architect.md +92 -0
  61. package/agents/onboarder.md +222 -0
  62. package/agents/onboarding-specialist.md +92 -0
  63. package/agents/operations-dashboard.md +98 -0
  64. package/agents/perf-engineer.md +348 -0
  65. package/agents/playbook-writer.md +71 -0
  66. package/agents/plg-strategist.md +91 -0
  67. package/agents/positioning-architect.md +88 -0
  68. package/agents/press-officer.md +89 -0
  69. package/agents/pricing-strategist.md +94 -0
  70. package/agents/privacy-officer.md +92 -0
  71. package/agents/referral-manager.md +91 -0
  72. package/agents/refine-analyst.md +102 -0
  73. package/agents/release-manager.md +261 -0
  74. package/agents/renewals-upsell-rep.md +90 -0
  75. package/agents/review-engineer.md +327 -0
  76. package/agents/rewards-steward.md +82 -0
  77. package/agents/sales-call-coach.md +94 -0
  78. package/agents/sales-closer.md +98 -0
  79. package/agents/security-auditor.md +313 -0
  80. package/agents/sell-closer.md +101 -0
  81. package/agents/share-amplifier.md +99 -0
  82. package/agents/social-media-manager.md +92 -0
  83. package/agents/storyteller.md +250 -0
  84. package/agents/strategist.md +83 -0
  85. package/agents/strategy-aligner.md +94 -0
  86. package/agents/support-agent.md +90 -0
  87. package/agents/tagger.md +245 -0
  88. package/agents/tech-writer.md +216 -0
  89. package/agents/test-engineer.md +243 -0
  90. package/agents/tiktok-ads.md +105 -0
  91. package/agents/tracking-engineer.md +92 -0
  92. package/agents/upsell-strategist.md +101 -0
  93. package/agents/voice.md +85 -0
  94. package/agents/w4-tools.md +153 -0
  95. package/agents/w4-verify.md +20 -138
  96. package/agents/workflow-optimiser.md +84 -0
  97. package/commands/close.md +814 -160
  98. package/commands/create.md +2 -2
  99. package/commands/deploy.md +554 -175
  100. package/commands/do.md +119 -109
  101. package/commands/fast.md +67 -0
  102. package/commands/improve.md +2 -2
  103. package/commands/one.md +418 -0
  104. package/commands/see.md +1 -1
  105. package/commands/sync.md +1 -1
  106. package/commands/tasks.md +222 -0
  107. package/commands/vespio.md +110 -0
  108. package/commands/vespio.remote.md +227 -0
  109. package/hooks/hooks.json +25 -79
  110. package/hooks/lib/governor-escape-match.sh +88 -0
  111. package/hooks/lib/hook.sh +4 -13
  112. package/hooks/lib/signal.sh +9 -2
  113. package/hooks/scripts/branch-pin.sh +6 -0
  114. package/hooks/scripts/config-protect.sh +6 -0
  115. package/hooks/scripts/do-outcome-gate.sh +19 -2
  116. package/hooks/scripts/git-add-guard.sh +43 -2
  117. package/hooks/scripts/governor-escape.sh +96 -0
  118. package/hooks/scripts/load-guard.sh +127 -0
  119. package/hooks/scripts/post-edit-check.sh +36 -29
  120. package/hooks/scripts/session-start.sh +34 -80
  121. package/hooks/scripts/task-complete-verify.sh +46 -40
  122. package/package.json +1 -1
  123. package/rules/documentation.md +9 -0
  124. package/scripts/ad-copy-lint.sh +656 -0
  125. package/scripts/agentverse-audit.sh +256 -0
  126. package/scripts/asi-walk.sh +435 -0
  127. package/scripts/astro-build-cached.sh +143 -0
  128. package/scripts/blocks-manifest-cached.sh +100 -0
  129. package/scripts/blocks-manifest.mjs +497 -0
  130. package/scripts/blocks-ratchet.sh +116 -0
  131. package/scripts/blocks-render-probe.mjs +529 -0
  132. package/scripts/blocks-usage.mjs +334 -0
  133. package/scripts/chat-context-check.sh +89 -0
  134. package/scripts/close-metrics.sh +558 -0
  135. package/scripts/close-owner.sh +268 -0
  136. package/scripts/db-sync-lock-check.sh +116 -0
  137. package/scripts/deploy-dev.sh +35 -0
  138. package/scripts/deploy-emit.sh +298 -0
  139. package/scripts/deploy-gate-check.sh +128 -0
  140. package/scripts/deploy-ready.sh +78 -0
  141. package/scripts/deploy-record.sh +605 -0
  142. package/scripts/deploy.sh +1273 -0
  143. package/scripts/do-auto.sh +598 -48
  144. package/scripts/do-brief.sh +113 -0
  145. package/scripts/do-close.sh +1137 -0
  146. package/scripts/do-commit.sh +75 -0
  147. package/scripts/do-consumer-sweep.sh +18 -1
  148. package/scripts/do-cycle-shape-check.sh +160 -0
  149. package/scripts/do-decide.sh +476 -0
  150. package/scripts/do-derive-check.sh +436 -0
  151. package/scripts/do-fleet.sh +106 -28
  152. package/scripts/do-folder.sh +10 -1
  153. package/scripts/do-next.sh +106 -0
  154. package/scripts/do-orchestrate.sh +17 -5
  155. package/scripts/do-plan-json.mjs +201 -0
  156. package/scripts/do-plan-json.sh +8 -0
  157. package/scripts/do-preflight.sh +117 -0
  158. package/scripts/do-project.sh +157 -0
  159. package/scripts/do-prove-selftest.sh +108 -0
  160. package/scripts/do-prove.sh +295 -23
  161. package/scripts/do-rank.py +31 -1
  162. package/scripts/do-recon-cache.sh +7 -1
  163. package/scripts/do-recon-pack.sh +196 -0
  164. package/scripts/do-reconcile.sh +121 -4
  165. package/scripts/do-signal.sh +280 -23
  166. package/scripts/do-smoke.sh +18 -1
  167. package/scripts/do-test-gate.sh +80 -0
  168. package/scripts/do-tick.sh +102 -0
  169. package/scripts/do-tier.sh +6 -0
  170. package/scripts/do-triage.sh +182 -0
  171. package/scripts/do-ui-gate.sh +1 -1
  172. package/scripts/do-w4-gates.sh +451 -0
  173. package/scripts/do-walk.sh +12 -1
  174. package/scripts/env-sync.sh +173 -0
  175. package/scripts/factory-brief-check.sh +330 -0
  176. package/scripts/factory-check.sh +68 -14
  177. package/scripts/factory-close-check.sh +257 -0
  178. package/scripts/factory-emit.sh +211 -0
  179. package/scripts/factory-executor-check.mjs +353 -0
  180. package/scripts/factory-peak.sh +301 -0
  181. package/scripts/factory-repo.sh +118 -3
  182. package/scripts/factory-review-check.mjs +61 -0
  183. package/scripts/factory-ship.sh +61 -0
  184. package/scripts/factory-tasks-check.sh +18 -1
  185. package/scripts/factory-turn.sh +326 -0
  186. package/scripts/factory-walk.sh +396 -0
  187. package/scripts/factory-width.sh +57 -0
  188. package/scripts/fade-toxic.sh +4 -3
  189. package/scripts/fixtures/factory-brief-real.md +44 -0
  190. package/scripts/fixtures/triage-dupe.md +5 -0
  191. package/scripts/fleet-manifest.mjs +108 -0
  192. package/scripts/fleet-status.sh +110 -0
  193. package/scripts/full-suite-paths-check.sh +144 -0
  194. package/scripts/gate-reaper-check.sh +98 -0
  195. package/scripts/gate-reaper.sh +125 -0
  196. package/scripts/gate-run.sh +73 -0
  197. package/scripts/gc-content-check.sh +140 -0
  198. package/scripts/gen-dev-config.py +20 -0
  199. package/scripts/govern-bound-check.sh +60 -0
  200. package/scripts/govern-claims-check.sh +233 -0
  201. package/scripts/govern-mem-check.sh +290 -0
  202. package/scripts/governor-doors-check.sh +362 -0
  203. package/scripts/governor-escape-check.sh +171 -0
  204. package/scripts/health.sh +413 -0
  205. package/scripts/id-inventory.mjs +418 -0
  206. package/scripts/land.sh +551 -0
  207. package/scripts/lib/gc-finished.sh +77 -0
  208. package/scripts/lib/govern.sh +361 -0
  209. package/scripts/lib/govern.ts +756 -0
  210. package/scripts/lighthouse-run.sh +187 -0
  211. package/scripts/livekit-live-check.sh +61 -0
  212. package/scripts/livekit-ratchet.sh +46 -0
  213. package/scripts/load-guard-check.sh +49 -0
  214. package/scripts/machine-check.sh +102 -0
  215. package/scripts/machine-watch.sh +177 -0
  216. package/scripts/one-agents.mjs +415 -0
  217. package/scripts/one-resume.sh +78 -0
  218. package/scripts/orphan-baseline.json +182 -0
  219. package/scripts/orphan-modules.mjs +179 -0
  220. package/scripts/pr-body.sh +335 -0
  221. package/scripts/preview-fd-check.sh +289 -0
  222. package/scripts/promise-manifest.mjs +24 -1
  223. package/scripts/release.sh +322 -0
  224. package/scripts/roles-check.sh +946 -0
  225. package/scripts/sdk-build-cached.sh +64 -0
  226. package/scripts/signal-watch.sh +241 -0
  227. package/scripts/skills-publish.sh +94 -0
  228. package/scripts/speed-cache-check.sh +173 -0
  229. package/scripts/speed-check.mjs +907 -0
  230. package/scripts/speed-parity-check.sh +648 -0
  231. package/scripts/speed-waterfall-check.sh +355 -0
  232. package/scripts/substrate-env-parity.mjs +156 -0
  233. package/scripts/tasks-claim-race.mjs +108 -0
  234. package/scripts/tasks-loop.sh +185 -0
  235. package/scripts/test-cached.sh +255 -0
  236. package/scripts/test-full.sh +87 -0
  237. package/scripts/test-honesty.mjs +137 -0
  238. package/scripts/test-lanes.sh +166 -0
  239. package/scripts/test-speed.sh +94 -0
  240. package/scripts/triage-shape-check.sh +149 -0
  241. package/scripts/tsc-cached.sh +179 -0
  242. package/scripts/typedb-flake-check.sh +197 -0
  243. package/scripts/urls-lint.sh +15 -0
  244. package/scripts/verify-fast.sh +445 -0
  245. package/scripts/vespio-sync.sh +149 -0
  246. package/scripts/wf-check.mjs +104 -1
  247. package/scripts/worktree-preview.sh +879 -0
  248. package/scripts/worktree-up.sh +208 -0
  249. package/skills/livekit-agents/SKILL.md +285 -0
  250. package/skills/livekit-agents/references/freshness-rules.md +168 -0
  251. package/skills/shadcn/SKILL.md +1 -1
  252. package/skills/signal/SKILL.md +0 -1
  253. package/skills/voice/SKILL.md +94 -6
  254. package/skills/voice/corpus-check.sh +87 -0
  255. package/hooks/scripts/compact-hint.sh +0 -35
  256. package/hooks/scripts/gate-guard.sh +0 -83
  257. package/hooks/scripts/read-tracker.sh +0 -26
  258. package/hooks/scripts/session-end-verify.sh +0 -51
  259. package/hooks/scripts/stop-reflect.sh +0 -140
  260. package/hooks/scripts/sync-priority-todo.sh +0 -57
  261. package/hooks/scripts/sync-todo-docs.sh +0 -46
  262. package/hooks/scripts/tool-signal.sh +0 -48
  263. package/scripts/do-tasks-bridge.py +0 -366
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ // fleet-manifest.mjs — project the fleet board into one JSON the web app can import.
3
+ //
4
+ // WHY A GENERATOR AND NOT A GLOB: the fleet docs live in text/, which is OUTSIDE
5
+ // the Astro root. `import.meta.glob('../../../../text/*.md')` is denied by vite's
6
+ // server.fs.allow ("Denied ID /Users/toc/Server/one-ie/text/analyst.md?raw",
7
+ // measured 2026-08-26), and text/ holds 1,568 files / 50MB — inlining any part of
8
+ // it fights the 3 MiB gzip CF Worker ceiling astro.config.mjs already stubs
9
+ // packages to stay under. Same precedent as promise-manifest.mjs.
10
+ //
11
+ // This file is the SINGLE SOURCE of the fleet -> doc map. `.claude/scripts/
12
+ // fleet-status.sh` reads the generated JSON, so the CLI board and the web board
13
+ // can never disagree about which fleets exist.
14
+ //
15
+ // Usage: node .claude/scripts/fleet-manifest.mjs [--check]
16
+ // --check exits 3 if the committed output is stale (no write).
17
+
18
+ import { readFileSync, writeFileSync, existsSync, statSync } from 'node:fs'
19
+ import { resolve, dirname } from 'node:path'
20
+ import { fileURLToPath } from 'node:url'
21
+
22
+ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../..')
23
+ const OUT = resolve(ROOT, 'one.ie/web/src/data/fleets.json')
24
+
25
+ // fleet -> doc path (null = running, no doc written yet).
26
+ const FLEETS = [
27
+ ['security', 'text/auth-enforcement-todo.md'],
28
+ ['wallet', 'text/wallet-durability.md'],
29
+ ['keys', 'text/keys.md'],
30
+ ['skill-engine', 'text/skill-engine.md'],
31
+ ['analytics', 'text/journey-analytics.md'],
32
+ ['analyst', 'text/analyst.md'],
33
+ ['departments', 'text/departments.md'],
34
+ ['colony', 'text/colony.md'],
35
+ ['dev-env', 'text/dev-environment.md'],
36
+ ['marketing', null],
37
+ ['sales', null],
38
+ ['service', null],
39
+ ['engineering', null],
40
+ ['tasks', null],
41
+ ['vespio', null],
42
+ ]
43
+
44
+ // Exactly what `grep -ci 'NOT WIRED\|NOT MEASURED\|DARK —'` counts: MATCHING
45
+ // LINES, not occurrences. Diverging here would make the page a second source.
46
+ const OPEN_RE = /NOT WIRED|NOT MEASURED|DARK —/i
47
+
48
+ const board = FLEETS.map(([name, doc]) => {
49
+ if (!doc) return { name, doc: null, lines: null, open: null, updated: null }
50
+ const abs = resolve(ROOT, doc)
51
+ if (!existsSync(abs)) return { name, doc, lines: null, open: null, updated: null }
52
+ const body = readFileSync(abs, 'utf8')
53
+ const lines = body.split('\n')
54
+ // `wc -l` counts newline terminators; the trailing "" from split is not a line.
55
+ const wcl = body.endsWith('\n') ? lines.length - 1 : lines.length
56
+ return {
57
+ name,
58
+ doc,
59
+ lines: wcl,
60
+ open: lines.filter((l) => OPEN_RE.test(l)).length,
61
+ updated: new Date(statSync(abs).mtimeMs).toISOString(),
62
+ }
63
+ })
64
+
65
+ const manifest = { generatedAt: new Date().toISOString(), board }
66
+
67
+ // Compare ignoring generatedAt so --check does not go red on the clock alone.
68
+ const stable = (m) => JSON.stringify(m.board)
69
+ const next = JSON.stringify(manifest, null, 2) + '\n'
70
+
71
+ if (process.argv.includes('--check')) {
72
+ if (!existsSync(OUT)) {
73
+ console.error('fleet-manifest: missing ' + OUT)
74
+ process.exit(3)
75
+ }
76
+ const cur = JSON.parse(readFileSync(OUT, 'utf8'))
77
+ if (stable(cur) !== stable(manifest)) {
78
+ console.error('fleet-manifest: STALE — run `node .claude/scripts/fleet-manifest.mjs`')
79
+ process.exit(3)
80
+ }
81
+ console.log('fleet-manifest: up to date (' + board.length + ' fleets)')
82
+ process.exit(0)
83
+ }
84
+
85
+ // Do not rewrite an unchanged board. fleet-status.sh calls this on every run,
86
+ // and an always-write generator would leave a tracked file dirty in the shared
87
+ // main tree each time somebody looks at the board — the exact sweep hazard
88
+ // .claude/CLAUDE.md warns about with concurrent windows. `generatedAt` alone is
89
+ // not a change.
90
+ if (existsSync(OUT)) {
91
+ try {
92
+ if (stable(JSON.parse(readFileSync(OUT, 'utf8'))) === stable(manifest)) {
93
+ console.log('fleet-manifest: unchanged (' + board.length + ' fleets)')
94
+ process.exit(0)
95
+ }
96
+ } catch {
97
+ // unreadable/corrupt output — fall through and rewrite it
98
+ }
99
+ }
100
+
101
+ writeFileSync(OUT, next)
102
+ console.log(
103
+ 'fleet-manifest: wrote ' +
104
+ board.length +
105
+ ' fleets (' +
106
+ board.filter((f) => f.doc).length +
107
+ ' with a doc) -> src/data/fleets.json',
108
+ )
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env bash
2
+ # fleet-status.sh — what the board looks like right now.
3
+ # Reads the machine, the worktrees and the fleet docs. No agents, no cost.
4
+ set -uo pipefail
5
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
6
+ cd "$ROOT"
7
+
8
+ C_DIM=$'\033[2m'; C_OK=$'\033[32m'; C_WARN=$'\033[33m'; C_BAD=$'\033[31m'; C_0=$'\033[0m'; C_B=$'\033[1m'
9
+
10
+ printf '%s\n' "${C_B} ██████╗ ███╗ ██╗███████╗${C_0}"
11
+ printf '%s\n' "${C_B} ██╔═══██╗████╗ ██║██╔════╝${C_0}"
12
+ printf '%s\n' "${C_B} ██║ ██║██╔██╗ ██║█████╗ ${C_0} ${C_DIM}probe · fan out · join${C_0}"
13
+ printf '%s\n' "${C_B} ██║ ██║██║╚██╗██║██╔══╝ ${C_0} ${C_DIM}no conductor · no collisions${C_0}"
14
+ printf '%s\n' "${C_B} ╚██████╔╝██║ ╚████║███████╗${C_0}"
15
+ printf '%s\n' "${C_B} ╚═════╝ ╚═╝ ╚═══╝╚══════╝${C_0}"
16
+ echo
17
+
18
+ # ── the box ────────────────────────────────────────────────────────────────
19
+ cores=$(sysctl -n hw.ncpu 2>/dev/null || echo '?')
20
+ memgb=$(( $(sysctl -n hw.memsize 2>/dev/null || echo 0) / 1073741824 ))
21
+ load=$(uptime | sed 's/.*load averages*: *//' | awk '{print $1}')
22
+ # This board had a THIRD memory arithmetic of its own: (free+inactive) over
23
+ # (free+inactive+active+wired). It counted every inactive page as free, dirty
24
+ # anonymous ones included, and ignored the compressor entirely — on 2026-09-03
25
+ # it printed 41% while vm_stat said 1.3% free and 9.4 GB sat in swap. One honest
26
+ # probe now answers for the whole harness.
27
+ # shellcheck source=lib/govern.sh
28
+ . "$ROOT/.claude/scripts/lib/govern.sh"
29
+ availmb=$(gate_mem_avail_mb 2>/dev/null || echo "")
30
+ freepct='?'; [ -n "$availmb" ] && [ "$memgb" -gt 0 ] && freepct=$(( availmb * 100 / (memgb * 1024) ))
31
+ swapused=$(sysctl -n vm.swapusage 2>/dev/null | sed 's/.*used = \([0-9.]*\)M.*/\1/')
32
+
33
+ lc=$(printf '%.0f' "${load:-0}" 2>/dev/null || echo 0)
34
+ if [ "$lc" -lt "$((cores))" ]; then lcol=$C_OK
35
+ elif [ "$lc" -lt "$((cores*2))" ]; then lcol=$C_WARN
36
+ else lcol=$C_BAD; fi
37
+
38
+ printf ' %-14s %s cores · %s GB\n' 'box' "$cores" "$memgb"
39
+ printf ' %-14s %b%s%b %s free mem swap %s M\n' 'load' "$lcol" "${load:-?}" "$C_0" "${freepct:-?}%" "${swapused:-?}"
40
+
41
+ # how many cycles can this box actually afford? Ask the governor rather than
42
+ # keeping a second copy of its arithmetic — that copy is how this line drifted.
43
+ res=${GOVERN_RESERVE_GB:-2}
44
+ permb=$(gate_price_mb)
45
+ slots=$(gate_headroom)
46
+ capc=$(( cores - 2 )); [ "$slots" -gt "$capc" ] && slots=$capc
47
+ bind='memory'; [ "$slots" -eq "$capc" ] && bind='cores'
48
+ printf ' %-14s %s %b(bound by %s · %sMB/cycle · %sGB reserved)%b\n' 'affordable' "$slots" "$C_DIM" "$bind" "$permb" "$res" "$C_0"
49
+
50
+ gates=$(ls -d "${TMPDIR:-/tmp}"/one-govern/lock-slot-* 2>/dev/null | wc -l | tr -d ' ')
51
+ printf ' %-14s %s held / %s max\n' 'gate slots' "$gates" "${GOVERN_MAX_GATES:-2}"
52
+
53
+ orph=$(pgrep -f 'esbuild|vite/node_modules' 2>/dev/null | wc -l | tr -d ' ')
54
+ if [ "$orph" -gt 0 ]; then
55
+ printf ' %-14s %b%s%b %bpkill -f "esbuild|vite/node_modules"%b\n' 'orphans' "$C_WARN" "$orph" "$C_0" "$C_DIM" "$C_0"
56
+ fi
57
+ echo
58
+
59
+ # ── worktrees ──────────────────────────────────────────────────────────────
60
+ wt=$(git worktree list 2>/dev/null | wc -l | tr -d ' ')
61
+ wtsz=$(du -sh .claude/worktrees 2>/dev/null | cut -f1)
62
+ printf ' %-14s %s %s %b(~120MB + one tsserver each)%b\n' 'worktrees' "$wt" "${wtsz:-0}" "$C_DIM" "$C_0"
63
+ echo
64
+
65
+ # ── the board ──────────────────────────────────────────────────────────────
66
+ printf ' %bFLEET%b\n' "$C_B" "$C_0"
67
+ while IFS='|' read -r name doc; do
68
+ [ -z "$name" ] && continue
69
+ if [ -f "$doc" ]; then
70
+ lines=$(wc -l < "$doc" | tr -d ' ')
71
+ open=$(grep -ci 'NOT WIRED\|NOT MEASURED\|DARK —' "$doc" 2>/dev/null | head -1 | tr -dc '0-9')
72
+ open=${open:-0}
73
+ if [ "$open" -gt 0 ]; then
74
+ printf ' %b●%b %-13s %-38s %4s lines %b%s open%b\n' "$C_WARN" "$C_0" "$name" "$doc" "$lines" "$C_WARN" "$open" "$C_0"
75
+ else
76
+ printf ' %b●%b %-13s %-38s %4s lines\n' "$C_OK" "$C_0" "$name" "$doc" "$lines"
77
+ fi
78
+ else
79
+ printf ' %b○%b %-13s %-38s %bnot written%b\n' "$C_DIM" "$C_0" "$name" "$doc" "$C_DIM" "$C_0"
80
+ fi
81
+ done < <(
82
+ # Single source of the fleet -> doc map: .claude/scripts/fleet-manifest.mjs,
83
+ # which also feeds /u/one/engineering/fleets. Regenerated here so the CLI and
84
+ # the page can never disagree about which fleets exist. Emits nothing if the
85
+ # manifest is missing — a visibly empty board beats a stale second copy.
86
+ node .claude/scripts/fleet-manifest.mjs >/dev/null 2>&1
87
+ node -e '
88
+ const fs=require("fs");
89
+ const p="one.ie/web/src/data/fleets.json";
90
+ if(!fs.existsSync(p)) process.exit(0);
91
+ for(const f of JSON.parse(fs.readFileSync(p,"utf8")).board)
92
+ console.log(f.name+"|"+(f.doc||"(no doc written)"));
93
+ ' 2>/dev/null
94
+ )
95
+ echo
96
+
97
+ # ── the wall ───────────────────────────────────────────────────────────────
98
+ DB=$(ls one.ie/web/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite 2>/dev/null | head -1)
99
+ if [ -n "$DB" ] && command -v sqlite3 >/dev/null 2>&1; then
100
+ owners=$(sqlite3 "$DB" "SELECT COUNT(*) FROM owners;" 2>/dev/null)
101
+ charge=$(sqlite3 "$DB" "SELECT COUNT(*) FROM owners WHERE charges_enabled=1;" 2>/dev/null)
102
+ addrs=$(sqlite3 "$DB" "SELECT COUNT(*) FROM wallets WHERE sui_address IS NOT NULL AND sui_address!='';" 2>/dev/null)
103
+ sales=$(sqlite3 "$DB" "SELECT COUNT(*) FROM credit_grants WHERE source='credits_product_sale';" 2>/dev/null)
104
+ printf ' %bTHE FUNNEL%b %b(local mirror of prod)%b\n' "$C_B" "$C_0" "$C_DIM" "$C_0"
105
+ printf ' owners %-6s can charge %b%-4s%b wallets w/ address %b%-4s%b sales %b%s%b\n' \
106
+ "${owners:-?}" "$C_BAD" "${charge:-?}" "$C_0" "$C_BAD" "${addrs:-?}" "$C_0" "$C_WARN" "${sales:-?}" "$C_0"
107
+ echo
108
+ fi
109
+
110
+ printf ' %bnext%b re-measure (/one §1) · sweep worktrees · then fan out\n' "$C_DIM" "$C_0"
@@ -0,0 +1,144 @@
1
+ #!/bin/bash
2
+ # full-suite-paths-check.sh — sweep for unaccounted paths to the full test suite
3
+ #
4
+ # The full suite is run in three places: package.json callers (via test-full.sh),
5
+ # hooks, and outcome: strings in text/*-todo.md. This script sweeps all three
6
+ # surfaces and compares against a reviewed allowlist.
7
+ #
8
+ # manifest: portable
9
+ #
10
+ # Exit 0: all paths accounted for.
11
+ # Exit 1: a path found that is not in the allowlist (new caller, new hook, new outcome).
12
+
13
+ set -uo pipefail
14
+
15
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
16
+
17
+ # --- The allowlist — paths we have reviewed and accept -----------------------
18
+ # Format:
19
+ # caller:<file>:<pattern> — a package.json script that runs the full suite
20
+ # hook:<file> — a hook that runs the full suite
21
+ # outcome:<file>:<line> — an outcome: string in a todo file
22
+ #
23
+ # This list is built by examining the sources and is NOT auto-generated.
24
+ ALLOWLIST=(
25
+ # Package.json callers: test-full.sh --check verifies no duplicates here
26
+ # (gates route through test-full.sh, not duplicated across callers)
27
+
28
+ # Hooks: must not run the full suite (but may mention it in comments)
29
+ # task-complete-verify.sh had bun run verify removed (documented in comments)
30
+
31
+ # Outcome strings in text/*-todo.md (these are the 15 known paths that need
32
+ # to be routed through gate-run.sh or otherwise accounted for)
33
+ "outcome:text/best-practices-todo.md:12"
34
+ "outcome:text/blocks-todo.md:17"
35
+ "outcome:text/campaigns-reimagined-todo.md:12"
36
+ "outcome:text/composio-todo.md:13"
37
+ "outcome:text/dashboard-personas-todo.md:10"
38
+ "outcome:text/dashboard-todo.md:10"
39
+ "outcome:text/inbox-simplify-todo.md:10"
40
+ "outcome:text/inbox-ui-simple-todo.md:10"
41
+ "outcome:text/inbox-ui-todo.md:11"
42
+ "outcome:text/lifecycle-loop-todo.md:21"
43
+ "outcome:text/pack-starter-workflows-todo.md:12"
44
+ "outcome:text/payments-integrated-todo.md:29"
45
+ "outcome:text/roles-act-as-switcher-todo.md:11"
46
+ "outcome:text/roles-final-todo.md:10"
47
+ "outcome:text/voice-agents-todo.md:13"
48
+ )
49
+
50
+ # --- Check package.json callers -----------------------------------------------
51
+ # test-full.sh --check ensures no caller duplicates the vitest argv
52
+ if ! bash "$ROOT/.claude/scripts/test-full.sh" --check; then
53
+ echo "FAIL: test-full.sh --check found a duplicate caller" >&2
54
+ exit 1
55
+ fi
56
+ echo "✓ package.json callers: test-full.sh --check passed"
57
+
58
+ # --- Sweep hooks for full suite invocations ----------------------------------
59
+ # Read all hooks and look for patterns that EXECUTE the full suite.
60
+ # We exclude: comments (#), error message output (echo/cat), code examples in help text.
61
+ # We include: actual shell execution like $(bun run verify) or command substitution.
62
+ found_hooks=()
63
+ for hook in "$ROOT"/.claude/hooks/*.sh; do
64
+ [ -f "$hook" ] || continue
65
+ # Look for ACTUAL EXECUTION patterns:
66
+ # - $(bun run verify) — command substitution (not in echo/cat/string)
67
+ # Ignore: comments (#), plain text mentions in error messages
68
+ if grep -E '^\s*\$\(.*bun run verify' "$hook" >/dev/null 2>&1; then
69
+ found_hooks+=("$(basename "$hook")")
70
+ fi
71
+ done
72
+
73
+ if [ ${#found_hooks[@]} -gt 0 ]; then
74
+ echo "FAIL: hooks running full suite (should be routed through gate-run.sh or removed):" >&2
75
+ for h in "${found_hooks[@]}"; do
76
+ echo " hook:$h" >&2
77
+ done
78
+ exit 1
79
+ fi
80
+ echo "✓ hooks: none running full suite directly"
81
+
82
+ # --- Sweep outcome: strings in todo files ------------------------------------
83
+ # Count each `outcome:` field that contains `bun run verify` and check against allowlist
84
+ found_outcomes=()
85
+ for todofile in "$ROOT"/text/*-todo.md; do
86
+ [ -f "$todofile" ] || continue
87
+ # Use relative path from ROOT (e.g., text/best-practices-todo.md)
88
+ relpath="${todofile#$ROOT/}"
89
+
90
+ # Find all lines with outcome: (frontmatter field or inline)
91
+ while IFS=: read -r linenum line; do
92
+ # Check if this line contains bun run verify (unrouted)
93
+ if echo "$line" | grep -q "bun run verify"; then
94
+ found_outcomes+=("outcome:$relpath:$linenum")
95
+ fi
96
+ done < <(grep -n "^outcome:" "$todofile" 2>/dev/null || true)
97
+ done
98
+
99
+ # --- Compare against allowlist -----------------------------------------------
100
+ failed=0
101
+
102
+ # Check for new outcomes not in allowlist
103
+ if [ ${#found_outcomes[@]:-0} -gt 0 ]; then
104
+ for found in "${found_outcomes[@]}"; do
105
+ in_allowlist=0
106
+ for allowed in "${ALLOWLIST[@]}"; do
107
+ if [ "$found" = "$allowed" ]; then
108
+ in_allowlist=1
109
+ break
110
+ fi
111
+ done
112
+ if [ $in_allowlist -eq 0 ]; then
113
+ echo "FAIL: unaccounted path to full suite: $found" >&2
114
+ failed=1
115
+ fi
116
+ done
117
+ fi
118
+
119
+ # Check for allowlist entries that no longer exist (removed paths)
120
+ for allowed in "${ALLOWLIST[@]}"; do
121
+ found_match=0
122
+ if [ ${#found_outcomes[@]:-0} -gt 0 ]; then
123
+ for found in "${found_outcomes[@]}"; do
124
+ if [ "$found" = "$allowed" ]; then
125
+ found_match=1
126
+ break
127
+ fi
128
+ done
129
+ fi
130
+ if [ $found_match -eq 0 ]; then
131
+ # Extract the file:line from the allowlist entry
132
+ if [[ $allowed == outcome:* ]]; then
133
+ echo "INFO: allowlist entry no longer exists (may be fixed): $allowed" >&2
134
+ fi
135
+ fi
136
+ done
137
+
138
+ if [ $failed -eq 1 ]; then
139
+ exit 1
140
+ fi
141
+
142
+ echo "✓ outcome strings: all ${#found_outcomes[@]} accounted for"
143
+ echo "✓ full-suite-paths-check.sh: all paths verified"
144
+ exit 0
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env bash
2
+ # gate-reaper-check.sh — what gate-reaper.sh reaps, and what it must never touch.
3
+ #
4
+ # manifest: portable
5
+ #
6
+ # WHY THIS EXISTS (measured 2026-09-05). Five `workerd` processes left behind by
7
+ # dead `astro dev` sessions sat at ppid=1 for 33-63 minutes holding ~196MB, while
8
+ # BOTH gate-reaper and machine-check printed "orphans: none". The reaper keys on
9
+ # a gate SHAPE, and `workerd serve` was not one of the shapes, so nothing in the
10
+ # harness could see them. They had to be found and killed by hand.
11
+ #
12
+ # The reaper's danger is symmetric, and this checker holds both ends:
13
+ # C1 an abandoned workerd under node_modules IS reaped
14
+ # C2 a LIVE workerd (parent alive) is NEVER reaped
15
+ # C3 a freshly reparented workerd is spared — the age floor still applies
16
+ # C4 a long-lived ppid=1 daemon that merely NAMES a gate word is spared
17
+ # C5 the pre-existing gate shapes still reap — no regression
18
+ # C6 a workerd outside node_modules is spared — not ours to kill
19
+ # R1 RED-PROOF: drop the workerd arm and C1 must FAIL
20
+ #
21
+ # Drives the REAL script with --once --dry-run against a STUBBED `ps`, so it
22
+ # never reads the live process table and never signals anything.
23
+ #
24
+ # bash .claude/scripts/gate-reaper-check.sh
25
+ set -uo pipefail
26
+
27
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
28
+ REAPER="$ROOT/.claude/scripts/gate-reaper.sh"
29
+ SANDBOX="$(mktemp -d "${TMPDIR:-/tmp}/gate-reaper-check.XXXXXX")"
30
+ trap 'rm -rf "$SANDBOX"' EXIT
31
+
32
+ fails=0
33
+ ok() { printf ' \033[32mPASS\033[0m %s\n' "$1"; }
34
+ bad() { printf ' \033[31mFAIL\033[0m %s\n' "$1"; fails=$((fails + 1)); }
35
+
36
+ WD='node_modules/@cloudflare/workerd-darwin-arm64/bin/workerd serve --binary --experimental'
37
+
38
+ # The fixture process table. Columns match `ps -Ao pid=,ppid=,etime=,command=`.
39
+ cat > "$SANDBOX/table" <<EOF
40
+ 1001 1 45:00 /repo/someapp/web/$WD
41
+ 1002 29457 45:00 /repo/someapp/web/$WD
42
+ 1003 1 01:00 /repo/someapp/web/$WD
43
+ 1004 1 45:00 /Users/toc/bin/cc-connect.sh daemon run verify
44
+ 1005 1 45:00 /usr/local/bin/workerd serve --binary --experimental
45
+ 1006 1 45:00 bash /repo/.claude/scripts/gate-run.sh verify -- bun run verify
46
+ EOF
47
+
48
+ # Stub `ps`: serve the fixture for the sweep's -Ao read, and a pgid for lookups.
49
+ mkdir -p "$SANDBOX/bin"
50
+ cat > "$SANDBOX/bin/ps" <<EOF
51
+ #!/usr/bin/env bash
52
+ for a in "\$@"; do case "\$a" in pgid=) echo " 9999"; exit 0 ;; esac; done
53
+ cat "$SANDBOX/table"
54
+ EOF
55
+ chmod +x "$SANDBOX/bin/ps"
56
+
57
+ run_reaper() { PATH="$SANDBOX/bin:$PATH" bash "$1" --once --dry-run 2>&1; }
58
+
59
+ echo "── gate-reaper: reaps the abandoned, spares the live ──"
60
+ out="$(run_reaper "$REAPER")"
61
+
62
+ reaped() { grep -q "WOULD reap pid=$1 " <<<"$out"; }
63
+
64
+ if reaped 1001; then ok "C1 abandoned workerd under node_modules is reaped"
65
+ else bad "C1 abandoned workerd NOT reaped — the orphan shape is still invisible: $out"; fi
66
+
67
+ if ! reaped 1002; then ok "C2 live workerd (parent alive) spared"
68
+ else bad "C2 LIVE workerd would be reaped — this kills a running dev server"; fi
69
+
70
+ if ! reaped 1003; then ok "C3 freshly reparented workerd spared by the age floor"
71
+ else bad "C3 age floor not applied to workerd"; fi
72
+
73
+ if ! reaped 1004; then ok "C4 ppid=1 daemon naming a gate word is spared"
74
+ else bad "C4 cc-connect daemon would be reaped — exclusions broken"; fi
75
+
76
+ if reaped 1006; then ok "C5 pre-existing gate-run.sh shape still reaps"
77
+ else bad "C5 REGRESSION — gate-run.sh shape no longer reaped: $out"; fi
78
+
79
+ if ! reaped 1005; then ok "C6 workerd outside node_modules spared"
80
+ else bad "C6 would reap a workerd this tree did not spawn"; fi
81
+
82
+ # ── R1 RED-PROOF: without the workerd arm, C1 must fail ─────────────────────
83
+ GUTTED="$SANDBOX/gutted.sh"
84
+ grep -v 'node_modules\*workerd" serve"\*) ;;' "$REAPER" > "$GUTTED"
85
+ if [ "$(wc -l < "$GUTTED")" -eq "$(wc -l < "$REAPER")" ]; then
86
+ bad "R1 could not remove the workerd arm — anchor moved, this checker is blind"
87
+ else
88
+ gout="$(run_reaper "$GUTTED")"
89
+ if grep -q "WOULD reap pid=1001 " <<<"$gout"; then
90
+ bad "R1 gutted reaper STILL reaped 1001 — C1 is not testing the new arm"
91
+ else
92
+ ok "R1 red-proof: without the arm 1001 is missed, so C1 has teeth"
93
+ fi
94
+ fi
95
+
96
+ echo
97
+ if [ $fails -eq 0 ]; then echo "ALL GREEN — the reaper sees orphaned workerd and still spares live work"; exit 0; fi
98
+ echo "RED — $fails check(s) failed"; exit 1
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env bash
2
+ # gate-reaper.sh — reap ABANDONED gates, so a stalled fleet cannot poison the box.
3
+ #
4
+ # manifest: portable
5
+ #
6
+ # WHY (measured 2026-08-31). Two fleets were running and the box sat at load 138
7
+ # on 10 cores with swap at 4.3G of 5.1G. The cause was not the live work: four
8
+ # processes had been reparented to launchd (ppid=1) when their parent shell died
9
+ # and were still burning CPU — a `bun run verify` abandoned for 21 minutes, and a
10
+ # gate-run.sh abandoned for 9 minutes that was STILL HOLDING governor slot-3. A
11
+ # dead process holding a slot shrinks the effective gate cap until someone
12
+ # notices by hand; nothing in the harness noticed.
13
+ #
14
+ # gate_sweep_stale reclaims the LOCK a dead owner left behind. Nothing reclaimed
15
+ # the PROCESS. This does.
16
+ #
17
+ # Deliberately narrow. It kills a process only when ALL of:
18
+ # 1. ppid == 1 — genuinely reparented; its parent is gone
19
+ # 2. the command names THIS repo, or is a gate-run.sh / bun-run-verify shape
20
+ # 3. it has burned real CPU time — not merely idle and forgotten
21
+ # so a system daemon (WindowServer and coreaudiod are both ppid=1 and busy) can
22
+ # never match, and neither can a live fleet's gate, whose parent is alive.
23
+ #
24
+ # Usage: gate-reaper.sh [--once] [--interval N] [--dry-run]
25
+ # Prints one line per reap; silent when there is nothing to do, so it is safe to
26
+ # run as a Monitor for the length of a session.
27
+ set -uo pipefail
28
+
29
+ INTERVAL=60
30
+ ONCE=0
31
+ DRY=0
32
+ while [ $# -gt 0 ]; do
33
+ case "$1" in
34
+ --once) ONCE=1 ;;
35
+ --dry-run) DRY=1 ;;
36
+ --interval) INTERVAL="${2:-60}"; shift ;;
37
+ *) echo "gate-reaper: unknown flag $1" >&2; exit 2 ;;
38
+ esac
39
+ shift
40
+ done
41
+
42
+ REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
43
+ # Gate on ELAPSED age, not CPU. An abandoned `bun run verify` parent sits at 0s
44
+ # CPU while the children it spawned burn the box -- gating on the parent's own
45
+ # CPU skipped exactly the processes worth reaping (measured: pids 79192 and
46
+ # 84649, both 0s CPU, both abandoned). ppid=1 plus a gate shape is already
47
+ # conclusive: a live gate always has a live parent. The age floor only avoids
48
+ # racing a process that was reparented moments ago.
49
+ # 90s was too eager. A gate is reparented the moment its launching shell exits,
50
+ # and that happens to LIVE work -- the harness killing a Bash call at its timeout,
51
+ # or a backgrounded launch -- not only to abandoned work. Reaping a 96s-old gate
52
+ # that a fleet is still waiting on slows the plan down, which is the opposite of
53
+ # the point. Every genuinely dead gate found by hand was 7-22 MINUTES old, and
54
+ # the harness Bash ceiling is 600s, so a gate still orphaned past that is one
55
+ # nobody is coming back for.
56
+ MIN_AGE_SECS="${GATE_REAPER_MIN_AGE:-900}"
57
+
58
+ # Elapsed seconds, from `ps -o etime=` (MM:SS | HH:MM:SS | D-HH:MM:SS)
59
+ _age_secs() {
60
+ printf '%s' "$1" | awk -F: '
61
+ { d=0; s=$0
62
+ if (s ~ /-/) { split(s, a, "-"); d=a[1]; s=a[2]; n=split(s, b, ":") }
63
+ else n=split(s, b, ":")
64
+ if (n==3) print int(d*86400 + b[1]*3600 + b[2]*60 + b[3])
65
+ else if (n==2) print int(d*86400 + b[1]*60 + b[2])
66
+ else print int(0+s) }'
67
+ }
68
+
69
+ sweep() {
70
+ local pid ppid age cmd secs pgid n=0
71
+ while read -r pid ppid age cmd; do
72
+ [ "$ppid" = "1" ] || continue
73
+ # Match the GATE SHAPE, never "mentions the repo". The first draft keyed on
74
+ # the repo path and its dry run would have killed cc-connect, the Telegram
75
+ # listener, a `claude daemon run` and Cursor's extension host -- all of them
76
+ # legitimate long-lived daemons that are ppid=1 by design, exactly like an
77
+ # abandoned gate. A reaper that cannot tell those apart is worse than no
78
+ # reaper. Only these shapes are gates, and a gate is always short-lived:
79
+ case "$cmd" in
80
+ *gate-run.sh*) ;;
81
+ *"vitest"*) ;;
82
+ *"tsc --noEmit"*) ;;
83
+ *"astro check"*) ;;
84
+ *"run verify"*|*"run test"*|*"run typecheck"*) ;;
85
+ # An `astro dev` / `wrangler dev` that dies leaves its workerd child behind,
86
+ # reparented and holding ~40MB for as long as the box is up. FIVE of them
87
+ # accumulated in one hour on 2026-09-05 (ages 33-63min, ~196MB) while both
88
+ # this reaper and machine-check reported "orphans: none" -- neither knew the
89
+ # shape, so nothing in the harness could see them. A LIVE workerd always has
90
+ # a live parent (astro dev, or wrangler), so the ppid==1 test above already
91
+ # spares it; the node_modules anchor keeps this to runtimes THIS tree spawned
92
+ # and away from any workerd a user runs by hand.
93
+ *node_modules*workerd" serve"*) ;;
94
+ *) continue ;;
95
+ esac
96
+ # Belt and braces: never a daemon, an editor, or anything system-owned, even
97
+ # if one of the words above appears somewhere in its argv.
98
+ case "$cmd" in
99
+ */System/*|/usr/sbin/*|/usr/libexec/*|*Applications*) continue ;;
100
+ *cc-connect*|*tg-listen*|*"daemon run"*|*Cursor*|*Code\ Helper*) continue ;;
101
+ *--watch*|*watchexec*|*nodemon*) continue ;;
102
+ esac
103
+ secs=$(_age_secs "$age")
104
+ [ "${secs:-0}" -ge "$MIN_AGE_SECS" ] || continue
105
+ pgid=$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')
106
+ if [ "$DRY" = "1" ]; then
107
+ echo "[gate-reaper] WOULD reap pid=$pid pgid=${pgid:-?} age=${secs}s ${cmd:0:60}"
108
+ else
109
+ echo "[gate-reaper] reaped abandoned gate pid=$pid pgid=${pgid:-?} age=${secs}s ${cmd:0:60}"
110
+ kill -TERM -"${pgid:-$pid}" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
111
+ fi
112
+ n=$((n + 1))
113
+ done < <(ps -Ao pid=,ppid=,etime=,command= 2>/dev/null)
114
+
115
+ # A dead owner's lock shrinks the gate cap until someone contends. Reclaim it.
116
+ if [ "$DRY" != "1" ] && [ -f "$REPO/.claude/scripts/lib/govern.sh" ]; then
117
+ local reclaimed
118
+ reclaimed=$(bash -c ". '$REPO/.claude/scripts/lib/govern.sh'; gate_sweep_stale; claim_sweep" 2>/dev/null | tr '\n' ' ')
119
+ case "$reclaimed" in *[1-9]*) echo "[gate-reaper] reclaimed stale governor locks/claims: $reclaimed" ;; esac
120
+ fi
121
+ return 0
122
+ }
123
+
124
+ if [ "$ONCE" = "1" ]; then sweep; exit 0; fi
125
+ while :; do sweep; sleep "$INTERVAL"; done
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env bash
2
+ # gate-run.sh — run a heavy build gate under the machine-wide governor.
3
+ #
4
+ # gate-run.sh <label> -- <command...>
5
+ #
6
+ # Three things it guarantees that a bare `bun run verify` does not:
7
+ # 1. SLOT — at most GOVERN_MAX_GATES heavy gates run machine-wide, across
8
+ # every Claude Code session and every worktree. Session 3+ queues.
9
+ # 2. BOUND — a hard wall-clock cap; macOS ships no timeout(1), so an
10
+ # unbounded gate ran 18 min under load and outlived its session.
11
+ # 3. REAP — kills the whole PROCESS GROUP on timeout/exit. Killing only the
12
+ # shell reparents bun/node/vitest-forks to launchd, which is how
13
+ # orphaned verify trees leaked here.
14
+ #
15
+ # Degrades to a plain exec if the governor lib is missing (CI, fresh clone).
16
+ set -uo pipefail
17
+
18
+ LABEL="${1:-gate}"; shift || true
19
+ [ "${1:-}" = "--" ] && shift
20
+
21
+ LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/govern.sh"
22
+ if [ ! -f "$LIB" ]; then exec "$@"; fi
23
+ . "$LIB"
24
+
25
+ # CI and explicit opt-out run at full throughput.
26
+ if [ -n "${CI:-}" ] || [ "${GOVERN_DISABLE:-0}" = "1" ]; then exec "$@"; fi
27
+
28
+ # Re-entrancy: `verify` calls `test`, and both are wrapped. A nested gate must
29
+ # INHERIT the outer slot, never take a second one — two slots held by one
30
+ # logical gate would deadlock the queue at cap 2.
31
+ if [ "${GOVERN_IN_GATE:-0}" = "1" ]; then exec "$@"; fi
32
+ export GOVERN_IN_GATE=1
33
+
34
+ trap 'gate_release_all' EXIT INT TERM
35
+
36
+ # SAY THAT YOU ARE QUEUEING.
37
+ #
38
+ # gate_slot blocks for up to 1800s and this script printed nothing until it had a
39
+ # slot, so a queued gate and a hung gate looked identical to whoever was waiting.
40
+ # Measured 2026-09-01: lifecycle C8's demo gate was reported by its verifier as
41
+ # "timed out (>120s) and did not complete", and the cycle failed. Run directly it
42
+ # passes 6/6 in 1.19 SECONDS. Nothing was broken — the gate was waiting behind
43
+ # other gates in silence, and the verifier gave up and called it a hang.
44
+ #
45
+ # A wait that is announced is a wait. A wait that is silent is read as a failure.
46
+ _held=$(ls -d "$GOVERN_DIR"/lock-slot-* 2>/dev/null | wc -l | tr -d ' ')
47
+ # The EFFECTIVE cap is min(config, what memory can fund) — gate_slot takes that
48
+ # min every retry. Announcing the config alone misreported the wait on exactly
49
+ # the boxes where the memory probe is doing its job.
50
+ _head=$(gate_headroom 2>/dev/null || echo 99)
51
+ _cap="${GOVERN_MAX_GATES:-2}"
52
+ [ "$_head" -lt "$_cap" ] 2>/dev/null && _cap="$_head"
53
+ [ "${_cap:-0}" -lt 1 ] 2>/dev/null && _cap=1
54
+ if [ "${_held:-0}" -ge "$_cap" ]; then
55
+ echo "[gate-run] $LABEL: QUEUEING for a slot (${_held}/${_cap} held; config ${GOVERN_MAX_GATES:-2}, memory funds ${_head} at $(gate_price_mb)MB each). This is a WAIT, not a hang — do not report it as a timeout. Set GOVERN_QUEUE_WAIT to bound it, or GOVERN_DISABLE=1 to bypass." >&2
56
+ fi
57
+ _q0=$(date +%s)
58
+
59
+ if ! gate_slot "${GOVERN_QUEUE_WAIT:-1800}"; then
60
+ echo "[gate-run] $LABEL: no slot after ${GOVERN_QUEUE_WAIT:-1800}s — machine saturated." >&2
61
+ exit 1
62
+ fi
63
+
64
+ if ! gate_pressure; then
65
+ echo "[gate-run] $LABEL: WARNING — low free memory; running anyway in slot $GOVERN_SLOT." >&2
66
+ fi
67
+
68
+ _qw=$(( $(date +%s) - _q0 ))
69
+ echo "[gate-run] $LABEL: running in $GOVERN_SLOT (cap ${GOVERN_MAX_GATES} machine-wide$([ "$_qw" -gt 2 ] && echo ", queued ${_qw}s"))" >&2
70
+ run_bounded "${GOVERN_GATE_TIMEOUT:-1800}" "$@"
71
+ rc=$?
72
+ gate_release_all; trap - EXIT INT TERM
73
+ exit $rc