@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,166 @@
1
+ #!/usr/bin/env bash
2
+ # test-lanes.sh — the full suite as TWO concurrent lanes, because it has two
3
+ # different binding constraints and one pool cannot serve both.
4
+ #
5
+ # manifest: needs-env
6
+ #
7
+ # THE MEASUREMENT THAT FORCED THIS (2026-09-03). Four full-suite runs on an
8
+ # UNCHANGED tree produced four DIFFERENT failure sets — 8, 12, 13, 8 failures.
9
+ # Every failing file in all four runs was one of the 19 suites marked
10
+ # `real TypeDB`; not one of the other ~1126 files ever failed. Per run the log
11
+ # carried 41-72 `typedbQuery upstream 503` and 25-38 `upstream 500` warnings,
12
+ # and those are emitted only AFTER the retry budget is spent (substrate.ts:208).
13
+ #
14
+ # The cause is capacity, not code: `substrate.ts:56-60` records that the shared
15
+ # TypeDB Cloud gateway 503s in bursts lasting SECONDS, while the retry cover is
16
+ # 500ms + 1000ms. Eight vitest forks hammering one external singleton turn that
17
+ # into a coin flip, and a coin-flip gate costs far more than it saves — the
18
+ # 2026-09-03 deploy spent hours on re-runs and root-cause agents for failures
19
+ # that were never in the diff.
20
+ #
21
+ # WHY TWO LANES BEAT ONE. The lanes have opposite constraints, so they overlap
22
+ # almost for free:
23
+ # pool — ~1126 files, CPU-bound, 8 forks, touches no gateway
24
+ # typedb — 19 files, NETWORK-bound, serial, one gateway conversation at a time
25
+ # Lane `typedb` spends its life waiting on a socket, so it costs little CPU while
26
+ # `pool` saturates the cores. Wall clock is max(pool, typedb), not pool+typedb.
27
+ #
28
+ # WHAT THIS IS NOT. It is not `--no-file-parallelism` over the whole suite (that
29
+ # serialises 1126 innocent files to fix 19), and it is not a retry that swallows
30
+ # a 503. Serialising ONLY the suites that share the external singleton removes
31
+ # the contention at its source while leaving the real gate intact: every test
32
+ # still runs, no assertion is weakened, nothing is skipped.
33
+ #
34
+ # The selector is the `real TypeDB` marker the suites already carry — read from
35
+ # the tree at run time, never a hardcoded list that would rot silently.
36
+ #
37
+ # bash .claude/scripts/test-lanes.sh # both lanes, concurrent
38
+ # bash .claude/scripts/test-lanes.sh --list # show the split, run nothing
39
+ # bash .claude/scripts/test-lanes.sh --self-test
40
+ set -uo pipefail
41
+
42
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
43
+ FOLDER="${TEST_FULL_FOLDER:-one.ie/web}"
44
+ DIR="$ROOT/$FOLDER"
45
+
46
+ # Timeouts match test-full.sh's single definition — see that file's comment on
47
+ # why every caller must present identical argv to test-cached.sh.
48
+ # test-full.sh owns the ONE definition of these flags and hands them over in
49
+ # TEST_FULL_ARGS. The literal here is only the standalone-invocation fallback —
50
+ # if the two ever drift, the memo key splits and a warm tree silently pays the
51
+ # suite twice, which is the exact bug test-full.sh was created to kill.
52
+ if [ -n "${TEST_FULL_ARGS:-}" ]; then
53
+ COMMON=(); for _a in $TEST_FULL_ARGS; do COMMON+=("$_a"); done
54
+ else
55
+ COMMON=(--testTimeout=15000 --hookTimeout=15000 --teardownTimeout=15000 --reporter=dot)
56
+ fi
57
+
58
+ # The 19. Read from the tree so adding a real-TypeDB suite puts it in the serial
59
+ # lane automatically; a hardcoded list is how this rots back into flakiness.
60
+ typedb_files() { (cd "$DIR" && grep -rl 'real TypeDB' tests --include='*.test.ts' 2>/dev/null | sort); }
61
+
62
+ if [ "${1:-}" = "--list" ]; then
63
+ n=$(typedb_files | wc -l | tr -d ' ')
64
+ total=$(cd "$DIR" && find tests -name '*.test.ts*' | wc -l | tr -d ' ')
65
+ echo "typedb lane (serial, network-bound): $n files"
66
+ typedb_files | sed 's/^/ /'
67
+ echo "pool lane (8 forks, CPU-bound): $(( total - n )) files"
68
+ exit 0
69
+ fi
70
+
71
+ # --self-test: the selector must actually select, and must not select everything.
72
+ if [ "${1:-}" = "--self-test" ]; then
73
+ fails=0
74
+ n=$(typedb_files | wc -l | tr -d ' ')
75
+ total=$(cd "$DIR" && find tests -name '*.test.ts*' | wc -l | tr -d ' ')
76
+ if [ "$n" -gt 0 ]; then echo " ok selector found $n real-TypeDB suites"
77
+ else echo " FAIL selector found ZERO files — the marker moved, lane is empty"; fails=$((fails+1)); fi
78
+ if [ "$n" -lt "$total" ]; then echo " ok selector is a strict subset ($n of $total)"
79
+ else echo " FAIL selector matched everything — no split happened"; fails=$((fails+1)); fi
80
+ # Every selected file must exist (guards a stale/quoted path)
81
+ miss=0; while read -r f; do [ -n "$f" ] && [ -f "$DIR/$f" ] || miss=$((miss+1)); done < <(typedb_files)
82
+ if [ "$miss" -eq 0 ]; then echo " ok every selected path exists"
83
+ else echo " FAIL $miss selected path(s) do not exist"; fails=$((fails+1)); fi
84
+ [ "$fails" -eq 0 ] && { echo "test-lanes: self-test PASS"; exit 0; }
85
+ echo "test-lanes: self-test FAILED ($fails)"; exit 1
86
+ fi
87
+
88
+ # bash 3.2 (what macOS ships) has no `mapfile` — read the list the portable way.
89
+ TDB=()
90
+ while IFS= read -r _f; do [ -n "$_f" ] && TDB+=("$_f"); done < <(typedb_files)
91
+ if [ "${#TDB[@]}" -eq 0 ]; then
92
+ echo "test-lanes: no 'real TypeDB' suites found — falling back to the single full run"
93
+ exec bash "$ROOT/.claude/scripts/test-cached.sh" "$FOLDER" -- "${COMMON[@]}"
94
+ fi
95
+
96
+ EXCL=(); for f in "${TDB[@]}"; do EXCL+=(--exclude "$f"); done
97
+
98
+ LOG_A="$(mktemp)"; LOG_B="$(mktemp)"
99
+ echo "test-lanes: pool lane (8 forks) + typedb lane (${#TDB[@]} files, serial) — concurrent"
100
+
101
+ # Lane A: everything except the gateway suites. Full fan-out, no external dep.
102
+ #
103
+ # ...unless something else CPU-heavy is running beside us. Measured on the
104
+ # 2026-09-03 production deploy: the astro build (8 GiB heap) and this lane ran
105
+ # concurrently on a 10-core box and BOTH took 256s — the lane alone is 104s and
106
+ # the build alone is 133s, so the overlap bought nothing and cost 2x each. Eight
107
+ # forks plus a build is oversubscription, not parallelism.
108
+ #
109
+ # BUT CAPPING THE POOL DOES NOT FIX IT — measured, same box, same tree:
110
+ # 8 forks (baseline) gates wall-clock 256s build 133s
111
+ # 5 forks (VERIFY_POOL_FORKS=5) 272s build 209s
112
+ # Starving the pool did not feed the build; both got worse. Parallel, serial and
113
+ # capped all land at 250-270s, so that is simply the floor for build+suite on a
114
+ # 10-core box as currently shaped. The knob is left here, UNSET by default and
115
+ # wired to nothing, purely so the next person can re-measure cheaply instead of
116
+ # re-deriving the idea. Nothing in deploy.sh sets it.
117
+ POOL_FORKS="${VERIFY_POOL_FORKS:-}"
118
+ POOL_ARGS=(); [ -n "$POOL_FORKS" ] && POOL_ARGS=(--maxWorkers="$POOL_FORKS")
119
+ # ${A[@]+"${A[@]}"} is not defensive noise -- it is REQUIRED here. macOS ships
120
+ # bash 3.2.57, where expanding an EMPTY array under `set -u` (line 40) is an
121
+ # "unbound variable" error, not an empty expansion. POOL_ARGS is empty by
122
+ # default because VERIFY_POOL_FORKS is deliberately unset, so the bare
123
+ # "${POOL_ARGS[@]}" killed the pool lane before vitest was ever invoked:
124
+ #
125
+ # test-lanes.sh: line 119: POOL_ARGS[@]: unbound variable
126
+ # ----- pool lane (rc=1) ----- 0 of ~1126 files run
127
+ #
128
+ # The gate reported FAILED, which is correct and is the only reason this was
129
+ # caught rather than shipped as a pass -- but the FULL SUITE COULD NOT RUN on
130
+ # main from the commit that added the knob until this one. EXCL and TDB get the
131
+ # same guard: both are empty if the `real TypeDB` marker ever matches nothing,
132
+ # which would break BOTH lanes the same way, silently, on a rename.
133
+ ( TEST_CACHE_PTY=1 bash "$ROOT/.claude/scripts/test-cached.sh" "$FOLDER" -- \
134
+ "${COMMON[@]}" ${POOL_ARGS[@]+"${POOL_ARGS[@]}"} ${EXCL[@]+"${EXCL[@]}"} >"$LOG_A" 2>&1 ) & PID_A=$!
135
+
136
+ # Lane B: the gateway suites, ONE at a time. --no-file-parallelism is the whole
137
+ # point — it is what stops 8 forks bursting a singleton that 503s under load.
138
+ ( TEST_CACHE_PTY=1 bash "$ROOT/.claude/scripts/test-cached.sh" "$FOLDER" -- \
139
+ "${COMMON[@]}" --no-file-parallelism --maxWorkers=1 ${TDB[@]+"${TDB[@]}"} >"$LOG_B" 2>&1 ) & PID_B=$!
140
+
141
+ wait "$PID_A"; RC_A=$?
142
+ wait "$PID_B"; RC_B=$?
143
+
144
+ echo "───── pool lane (rc=$RC_A) ─────"; cat "$LOG_A"
145
+ echo "───── typedb lane (rc=$RC_B) ─────"; cat "$LOG_B"
146
+ rm -f "$LOG_A" "$LOG_B"
147
+
148
+ # TYPEDB_LANE_NONBLOCKING: the typedb lane still runs (signal is preserved) but
149
+ # its exit code is never a gate. Set in deploy.sh because the deploy must not
150
+ # block on the shared TypeDB Cloud cluster — the request path goes through
151
+ # Cloudflare (BrainDO / KV), TypeDB is writes + sync only (§ The brain and the edge).
152
+ if [[ "${TYPEDB_LANE_NONBLOCKING:-0}" == "1" ]]; then
153
+ if [ "$RC_B" -ne 0 ]; then
154
+ echo "test-lanes: typedb lane FAILED (rc=$RC_B) — non-blocking, gate is pool lane only"
155
+ fi
156
+ if [ "$RC_A" -eq 0 ]; then
157
+ echo "test-lanes: pool lane green — PASS (typedb lane non-blocking)"; exit 0
158
+ fi
159
+ echo "test-lanes: FAILED (pool=$RC_A typedb=$RC_B, typedb non-blocking)"; exit 1
160
+ fi
161
+
162
+ # Both must pass. A lane that could not run is a failure, never a pass.
163
+ if [ "$RC_A" -eq 0 ] && [ "$RC_B" -eq 0 ]; then
164
+ echo "test-lanes: BOTH lanes green"; exit 0
165
+ fi
166
+ echo "test-lanes: FAILED (pool=$RC_A typedb=$RC_B)"; exit 1
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env bash
2
+ # test-speed.sh — the ms ledger for the test suite.
3
+ #
4
+ # WHY: "the suite is slow" is not actionable; "two files are 93% of the slice"
5
+ # is. This runs vitest with the JSON reporter, aggregates per-FILE milliseconds,
6
+ # and writes a ranked ledger so the next cycle has a target instead of a mood.
7
+ #
8
+ # It measures, it never gates. Exit status mirrors vitest's so a red suite still
9
+ # reads as red, but nothing here fails a build for being slow.
10
+ #
11
+ # bash .claude/scripts/test-speed.sh # whole suite
12
+ # bash .claude/scripts/test-speed.sh tests/unit # a subtree
13
+ # bash .claude/scripts/test-speed.sh --report-only # re-rank the last run
14
+ #
15
+ # Output: one.ie/web/tests/.speed-ledger.json (committed — a prerendered page
16
+ # can read it at build time; it cannot query live timings).
17
+ set -uo pipefail
18
+
19
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
20
+ WEB="$ROOT/one.ie/web"
21
+ LEDGER="$WEB/tests/.speed-ledger.json"
22
+ RAW="${TEST_SPEED_RAW:-$WEB/tests/.speed-raw.json}"
23
+
24
+ report_only=0
25
+ args=()
26
+ for a in "$@"; do
27
+ case "$a" in
28
+ --report-only) report_only=1 ;;
29
+ *) args+=("$a") ;;
30
+ esac
31
+ done
32
+
33
+ st=0
34
+ if [ "$report_only" -eq 0 ]; then
35
+ cd "$WEB" || exit 1
36
+ # The governor owns concurrency; a raw vitest here would bypass it and trip
37
+ # hook:load-guard. Same gate label the other lanes use.
38
+ bash "$ROOT/.claude/scripts/gate-run.sh" test-speed -- \
39
+ npx vitest run --reporter=json --outputFile="$RAW" "${args[@]+"${args[@]}"}"
40
+ st=$?
41
+ fi
42
+
43
+ [ -f "$RAW" ] || { echo "[test-speed] no run data at $RAW" >&2; exit 1; }
44
+
45
+ LEDGER="$LEDGER" RAW="$RAW" python3 <<'PY'
46
+ import json, os, collections, datetime
47
+
48
+ raw, ledger = os.environ['RAW'], os.environ['LEDGER']
49
+ d = json.load(open(raw))
50
+
51
+ files = collections.Counter()
52
+ counts = collections.Counter()
53
+ for t in d.get('testResults', []):
54
+ name = t['name'].split('one.ie/web/')[-1]
55
+ # assertionResults carry per-TEST ms. A file's wall time also includes
56
+ # import+transform, which the JSON reporter does not attribute per file --
57
+ # so this is the FLOOR of a file's cost, never an overstatement.
58
+ files[name] += sum(a.get('duration') or 0 for a in t.get('assertionResults', []))
59
+ counts[name] += len(t.get('assertionResults', []))
60
+
61
+ total = sum(files.values())
62
+ rows = [
63
+ {'file': f, 'ms': round(ms), 'tests': counts[f],
64
+ 'share': round(ms / total, 4) if total else 0.0}
65
+ for f, ms in files.most_common()
66
+ ]
67
+
68
+ # The headline: how few files hold the suite hostage.
69
+ cum, p80 = 0.0, 0
70
+ for r in rows:
71
+ cum += r['ms']
72
+ p80 += 1
73
+ if total and cum >= 0.8 * total:
74
+ break
75
+
76
+ out = {
77
+ 'generated': datetime.datetime.now(datetime.timezone.utc)
78
+ .strftime('%Y-%m-%dT%H:%M:%SZ'),
79
+ 'files': len(rows),
80
+ 'tests': sum(counts.values()),
81
+ 'total_ms': round(total),
82
+ 'files_for_80pct': p80,
83
+ 'slowest': rows[:50],
84
+ }
85
+ json.dump(out, open(ledger, 'w'), indent=2)
86
+
87
+ print(f"\n[test-speed] {out['files']} files · {out['tests']} tests · {out['total_ms']}ms in test bodies")
88
+ print(f"[test-speed] {p80} file(s) carry 80% of it\n")
89
+ for r in rows[:15]:
90
+ print(f" {r['ms']:>8}ms {r['share']*100:5.1f}% {r['tests']:>4}t {r['file']}")
91
+ print(f"\n[test-speed] ledger -> {ledger}")
92
+ PY
93
+
94
+ exit $st
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env bash
2
+ # triage-shape-check.sh — prove the `shape` half of .claude/workflows/triage.js
3
+ # writes a sane, non-destructive plan. Same idiom as do-tier.sh --self-test and
4
+ # do-triage.sh --self-test: one command, exits non-zero on failure, and carries
5
+ # a RED PROOF so a checker that stays green against gutted code is caught.
6
+ #
7
+ # manifest: monorepo-only
8
+ #
9
+ # WHY THIS EXISTS. The shape phase emits a LIST of board writes, and the list is
10
+ # where the damage lives, not the words. Two of these invariants were broken in
11
+ # the first version and neither would have failed a syntax check:
12
+ # · TWO tasks:notes writes. tasks:notes SETS the body, it does not append, so
13
+ # the second silently destroyed the first — and the survivor said
14
+ # "accept: see above" pointing at the line it had just deleted. Exactly the
15
+ # defect the board already carries one layer down (an absent notes key is a
16
+ # silent DELETE returning ok:true).
17
+ # · tasks:assign, which is not in the receiver registry and would dissolve as
18
+ # unknown_receiver.
19
+ # Both are invisible to the eye and obvious to this file.
20
+ #
21
+ # Usage: bash .claude/scripts/triage-shape-check.sh [--red]
22
+ # --red gut the guard and assert this checker goes RED
23
+
24
+ set -uo pipefail
25
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
26
+ WF="$ROOT/.claude/workflows/triage.js"
27
+ SDK="$ROOT/packages/sdk/src/receivers.ts"
28
+ RED=0
29
+ [ "${1:-}" = "--red" ] && RED=1
30
+
31
+ [ -f "$WF" ] || { echo "FAIL: no $WF"; exit 2; }
32
+
33
+ run_cases() { # <workflow file>
34
+ node - "$1" "$SDK" <<'NODE'
35
+ // eval() IS DELIBERATE AND BOUNDED HERE, and JSON.parse cannot replace it: the
36
+ // tables it lifts contain REGEX LITERALS (ROUTES) and unquoted keys (SPLIT),
37
+ // neither of which is JSON. The input is not untrusted — it is a tracked file
38
+ // in this repo, read by path, and the whole point of the checker is to test the
39
+ // SHIPPING definitions rather than a copy that can drift. A copy is the failure
40
+ // mode this file exists to prevent: two definitions of one rule is how the
41
+ // routing table and its fixtures disagree silently. Never point this at a path
42
+ // a caller supplies.
43
+ const fs = require('fs')
44
+ const src = fs.readFileSync(process.argv[2], 'utf8')
45
+ const sdk = fs.existsSync(process.argv[3]) ? fs.readFileSync(process.argv[3], 'utf8') : ''
46
+ const grab = (re) => { const m = src.match(re); if (!m) { console.log('FAIL: could not find ' + re); process.exit(3) } return m[0] }
47
+ const ROUTES = eval('(' + grab(/const ROUTES = \[[\s\S]*?\n\]/).replace('const ROUTES = ', '') + ')')
48
+ const SPLIT = eval('(' + grab(/const SPLIT = \{[\s\S]*?\n\}/).replace('const SPLIT = ', '') + ')')
49
+ eval(grab(/function routeFor[\s\S]*?\n\}/))
50
+ eval(grab(/function plannedWrites[\s\S]*?\n return writes\n\}/))
51
+
52
+ let bad = 0
53
+ const fail = (m) => { console.log(' FAIL ' + m); bad++ }
54
+ const ok = (m) => console.log(' ok ' + m)
55
+
56
+ // ── 1. routing: what a file IS beats what it is ABOUT ───────────────────────
57
+ // The first draft put the money nouns first and sent a wallet COMPONENT and a
58
+ // wallet TEST to security-auditor. These fixtures are that bug, frozen.
59
+ for (const [p, want] of [
60
+ ['one.ie/web/src/components/wallet/AllWallets.tsx', 'implementer'],
61
+ ['one.ie/web/tests/unit/wallet/x.test.ts', 'test-engineer'],
62
+ ['one.ie/web/src/lib/auth/passkey-webauthn.ts', 'security-auditor'],
63
+ ['pay/backend/src/signing-key.ts', 'security-auditor'],
64
+ ['schema/one.tql', 'architect'],
65
+ ['one.ie/web/src/lib/resolvers/tasks.ts', 'architect'],
66
+ ['text/story.md', 'tech-writer'],
67
+ ['one.ie/web/wrangler.toml', 'release-manager'],
68
+ ['one.ie/web/src/pages/about.astro', 'implementer'],
69
+ ['somewhere/random.go', 'cto'],
70
+ ]) {
71
+ const got = routeFor([p])
72
+ got === want ? ok(`route ${want.padEnd(17)}${p}`) : fail(`route ${p} → ${got}, want ${want}`)
73
+ }
74
+
75
+ // ── 2. the write plan ───────────────────────────────────────────────────────
76
+ const C = {
77
+ FIX: { title: 'send button does nothing', triage: { verdict: 'sizeable', paths: ['one.ie/web/src/components/wallet/AllWallets.tsx'] }, size: { tier: 'FIX' },
78
+ shape: { want: 'Stop the Send button pretending it moved money', why: 'It emits telemetry and signs nothing.', subtasks: ['a', 'b', 'c'], chain: true, assignee: 'implementer' } },
79
+ UNSIZED: { title: 'make chat nicer', triage: { verdict: 'idea', paths: [] }, size: { tier: 'UNSIZED' }, shape: null },
80
+ PATCH: { title: 'typo', triage: { verdict: 'sizeable', paths: ['text/readme.md'] }, size: { tier: 'PATCH' },
81
+ shape: { want: 'Fix the misspelled word', why: '', subtasks: [], chain: false, assignee: 'tech-writer' } },
82
+ }
83
+ const plans = Object.fromEntries(Object.entries(C).map(([k, t]) => [k, plannedWrites(t)]))
84
+
85
+ for (const [k, w] of Object.entries(plans)) {
86
+ const ops = w.map((x) => x.op)
87
+ // EXACTLY ONE notes write. Two means the second clobbers the first.
88
+ const notes = w.filter((x) => x.op === 'tasks:notes')
89
+ notes.length <= 1 ? ok(`${k}: one notes write`) : fail(`${k}: ${notes.length} notes writes — the later SETS over the earlier`)
90
+ if (notes[0] && /accept:\s*see above/i.test(notes[0].text)) fail(`${k}: notes back-reference a line this plan deletes`)
91
+ // The capture lands FIRST, before anything can overwrite the title.
92
+ ops[0] === 'tasks:comment' ? ok(`${k}: capture first`) : fail(`${k}: first write is ${ops[0]}, not tasks:comment`)
93
+ // Every emitted receiver must exist.
94
+ if (sdk) for (const op of new Set(ops)) {
95
+ if (!sdk.includes('"' + op + '"')) fail(`${k}: ${op} is not in the receiver registry — would dissolve as unknown_receiver`)
96
+ }
97
+ }
98
+ // UNSIZED is shaped by nobody.
99
+ {
100
+ const ops = plans.UNSIZED.map((x) => x.op)
101
+ ops.some((o) => ['tasks:rename', 'tasks:reassign', 'tasks:subtask'].includes(o))
102
+ ? fail('UNSIZED row was renamed/assigned/split — absence of recon must not read as simplicity')
103
+ : ok('UNSIZED: no title, no owner, no children')
104
+ }
105
+ // PATCH is one piece.
106
+ plans.PATCH.some((x) => x.op === 'tasks:subtask') ? fail('PATCH got children') : ok('PATCH: no children')
107
+ // The chain leaves exactly one sibling ready.
108
+ {
109
+ const subs = plans.FIX.filter((x) => x.op === 'tasks:subtask')
110
+ subs.length === 3 ? ok('FIX: 3 children') : fail(`FIX: ${subs.length} children, want 3`)
111
+ subs[0] && subs[0].blockedBy ? fail('first sibling is blocked — nothing would be ready') : ok('FIX: first sibling ready')
112
+ subs.slice(1).every((s) => s.blockedBy) ? ok('FIX: later siblings chained') : fail('FIX: a later sibling is unchained — siblings race')
113
+ }
114
+ process.exit(bad ? 1 : 0)
115
+ NODE
116
+ }
117
+
118
+ if [ "$RED" -eq 0 ]; then
119
+ echo "triage-shape-check — invariants"
120
+ run_cases "$WF"; rc=$?
121
+ [ $rc -eq 0 ] && echo "triage-shape: self-test green" || echo "triage-shape: self-test FAILED"
122
+ exit $rc
123
+ fi
124
+
125
+ # ── RED PROOF ────────────────────────────────────────────────────────────────
126
+ # A checker that stays green against gutted code proves nothing. Re-introduce
127
+ # the two defects that were actually shipped and assert this file catches both.
128
+ tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
129
+ gut="$tmp/triage.js"
130
+
131
+ echo "RED PROOF 1 — second notes write restored (the clobber)"
132
+ sed 's|if (body) writes.splice(1, 0, { op: .tasks:notes., text: body })|if (body) writes.splice(1, 0, { op: "tasks:notes", text: body }); if (task.shape \&\& task.shape.why) writes.push({ op: "tasks:notes", text: task.shape.why + "\\n\\naccept: see above" })|' "$WF" > "$gut"
133
+ if run_cases "$gut" >/dev/null 2>&1; then echo " FAIL: checker stayed GREEN against a double notes write"; exit 1
134
+ else echo " ok checker went RED"; fi
135
+
136
+ echo "RED PROOF 2 — tasks:assign restored (the absent receiver)"
137
+ sed "s|op: 'tasks:reassign'|op: 'tasks:assign'|" "$WF" > "$gut"
138
+ if run_cases "$gut" >/dev/null 2>&1; then echo " FAIL: checker stayed GREEN against an unwired receiver"; exit 1
139
+ else echo " ok checker went RED"; fi
140
+
141
+ echo "RED PROOF 3 — money rule moved back to the front (the mis-route)"
142
+ node -e '
143
+ const fs=require("fs");let s=fs.readFileSync(process.argv[1],"utf8");
144
+ s=s.replace(/const ROUTES = \[[\s\S]*?\n\]/,`const ROUTES = [\n [/(^|\\\\/)(auth|passkey|vault|signer|wallet|escrow|payment|credits)/i, "security-auditor"],\n [/\\\\.(astro|tsx|jsx|css)$|components?\\\\/|pages?\\\\//i, "implementer"],\n]`);
145
+ fs.writeFileSync(process.argv[2],s)' "$WF" "$gut"
146
+ if run_cases "$gut" >/dev/null 2>&1; then echo " FAIL: checker stayed GREEN against a greedy money rule"; exit 1
147
+ else echo " ok checker went RED"; fi
148
+
149
+ echo "triage-shape: red proofs green (all three defects caught)"
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env bash
2
+ # tsc-cached.sh <folder> — run `tsc --noEmit` for <folder>, or reuse the answer
3
+ # someone else already computed for this exact tree. Prints the error COUNT.
4
+ #
5
+ # manifest: needs-env
6
+ #
7
+ # WHY. do-reconcile.sh solved this in 2026-08: one tsc per (folder,
8
+ # tree-fingerprint), everyone else reuses it, so six identical runs collapse to
9
+ # one compute and five cache hits. verify-fast.sh never used that cache, so every
10
+ # /do cycle paid full price for a typecheck of a tree that had not changed since
11
+ # the last cycle typechecked it. The cache KEY FORMAT here is byte-identical to
12
+ # do-reconcile's on purpose: they share entries, so a reconcile and N cycle gates
13
+ # on one tree cost exactly one tsc between them.
14
+ #
15
+ # CRITICAL, inherited from do-reconcile: the fingerprint must cover everything
16
+ # tsc READS, not just the folder being checked. one.ie/web resolves @oneie/sdk
17
+ # types out of packages/sdk/dist, and its tests cross into channels/. A
18
+ # fingerprint scoped to the folder alone would miss an SDK edit and hand back a
19
+ # cached PASS for a tree that no longer type-checks -- a green gate over a red
20
+ # tree, which is the one thing this harness exists to prevent.
21
+ #
22
+ # TSC_CACHE_DISABLE=1 forces a real run (and still records the result).
23
+ set -uo pipefail
24
+
25
+ # --self-test — the RED half. A cache that has never been seen to refuse is not
26
+ # known to refuse. Builds a throwaway package with a real type error and proves:
27
+ # a red count is never memoised, a red tree never replays as a HIT, and a tsc that
28
+ # could not finish exits non-zero instead of reporting "0 errors".
29
+ if [ "${1:-}" = "--self-test" ]; then
30
+ _ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
31
+ _P="$_ROOT/.tsc-cache-probe"
32
+ fails=0
33
+ export GOVERN_DIR="$(mktemp -d)"
34
+ rm -rf "$_P"; mkdir -p "$_P/src"
35
+ cat > "$_P/tsconfig.json" <<'EOF'
36
+ { "compilerOptions": { "strict": true, "noEmit": true, "skipLibCheck": true }, "include": ["src"] }
37
+ EOF
38
+ echo 'export const x: number = "not a number";' > "$_P/src/a.ts"
39
+ n1=$(bash "$0" .tsc-cache-probe 2>/dev/null)
40
+ if [ "${n1:-0}" -gt 0 ] 2>/dev/null; then echo "ok: a type error is reported ($n1)"
41
+ else echo "FAIL: type error not reported (got '$n1')"; fails=$((fails+1)); fi
42
+ out2=$(bash "$0" .tsc-cache-probe 2>&1 >/dev/null)
43
+ case "$out2" in *"cache HIT"*) echo "FAIL: a RED count replayed as a cache HIT"; fails=$((fails+1));;
44
+ *) echo "ok: a red tree is re-run, never replayed";; esac
45
+ if ls "$GOVERN_DIR"/tsc-_tsc-cache-probe.* >/dev/null 2>&1; then
46
+ echo "FAIL: a red count was written to the cache"; fails=$((fails+1))
47
+ else echo "ok: no cache entry written for a red tree"; fi
48
+ # green half — a pass IS memoised
49
+ echo 'export const x: number = 1;' > "$_P/src/a.ts"
50
+ n3=$(bash "$0" .tsc-cache-probe 2>/dev/null)
51
+ [ "$n3" = "0" ] && echo "ok: clean tree reports 0" || { echo "FAIL: clean tree reported '$n3'"; fails=$((fails+1)); }
52
+ out4=$(bash "$0" .tsc-cache-probe 2>&1 >/dev/null)
53
+ case "$out4" in *"cache HIT"*) echo "ok: a PASS is memoised";;
54
+ *) echo "FAIL: identical clean tree did not HIT"; fails=$((fails+1));; esac
55
+ # THE KEY IS CONTENT, NOT MTIME — both halves, because a constant key would
56
+ # pass every check above (red is never cached, so red-then-green-then-HIT
57
+ # proves nothing about the key moving). Measured 2026-09-04: the first cut of
58
+ # the content key hashed EMPTY input under zsh word-splitting and every check
59
+ # above stayed green. A checker that cannot go red has no demonstrated power.
60
+ touch "$_P/src/a.ts" # mtime moves, bytes do not
61
+ out5=$(bash "$0" .tsc-cache-probe 2>&1 >/dev/null)
62
+ case "$out5" in *"cache HIT"*) echo "ok: an mtime-only change still HITs (key is content)";;
63
+ *) echo "FAIL: a touch invalidated the memo — the key is reading mtime"; fails=$((fails+1));; esac
64
+ echo '{"wave":"W2"}' > "$_P/.w2-spec.json" # a cycle receipt, not a tsc input
65
+ out5b=$(bash "$0" .tsc-cache-probe 2>&1 >/dev/null)
66
+ case "$out5b" in *"cache HIT"*) echo "ok: a dot-file receipt does not move the key";;
67
+ *) echo "FAIL: .w2-spec.json invalidated the memo — the key reads cycle receipts"; fails=$((fails+1));; esac
68
+ echo 'export const x: number = 2;' > "$_P/src/a.ts" # bytes move, still green
69
+ out6=$(bash "$0" .tsc-cache-probe 2>&1 >/dev/null)
70
+ case "$out6" in *"cache HIT"*) echo "FAIL: a content change replayed as a HIT — the key does not read the bytes"; fails=$((fails+1));;
71
+ *) echo "ok: a content change MISSes (key covers the bytes)";; esac
72
+ # RED PROOF — an unrun tsc must NOT read as 0 errors. A child that cannot
73
+ # finish inside the bound is killed by run_bounded, leaving the count file
74
+ # EMPTY -- exactly the shape a real 600s timeout produces. The old
75
+ # `n=${n:-0}` turned that empty file into "0 errors" AND cached it, so a
76
+ # typecheck that never ran was replayed forever as a PASS.
77
+ rm -rf "$GOVERN_DIR"; export GOVERN_DIR="$(mktemp -d)"
78
+ if TSC_CACHE_TIMEOUT=1 TSC_CACHE_CMD='sleep 30' bash "$0" .tsc-cache-probe >/dev/null 2>&1; then
79
+ echo "FAIL: a timed-out tsc exited 0 — an unrun gate read as a pass"; fails=$((fails+1))
80
+ else echo "ok: a tsc that could not finish exits non-zero"; fi
81
+ rm -rf "$_P" "$GOVERN_DIR"
82
+ [ "$fails" -eq 0 ] && { echo "tsc-cached: self-test PASS"; exit 0; }
83
+ echo "tsc-cached: self-test FAILED ($fails)"; exit 1
84
+ fi
85
+
86
+ folder="${1:-}"
87
+ [ -n "$folder" ] || { echo "usage: tsc-cached.sh <folder>" >&2; exit 2; }
88
+
89
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
90
+ cd "$ROOT" || exit 2
91
+ [ -d "$folder" ] || { echo "tsc-cached: no such folder: $folder" >&2; exit 2; }
92
+
93
+ # shellcheck source=lib/govern.sh
94
+ . "$(dirname "${BASH_SOURCE[0]}")/lib/govern.sh"
95
+
96
+ # The key is tsc_tree_fingerprint (lib/govern.sh) — CONTENT, never mtime, over
97
+ # everything tsc reads. It used to live here as an mtime stat over src/tests +
98
+ # packages/sdk/src, and that key could not share across worktrees: two checkouts
99
+ # of one commit have identical bytes and different mtimes, so the 28 stamps on
100
+ # disk on 2026-09-03 were N worktrees each paying full price. do-reconcile.sh
101
+ # `types` calls the SAME function, so a reconcile and N cycle gates on one tree
102
+ # still cost exactly one tsc between them.
103
+ # one.ie/web and channels resolve @oneie/sdk types out of packages/sdk/dist.
104
+ _fp_extra=""
105
+ case "$folder" in one.ie/web|channels) _fp_extra="packages/sdk/src" ;; esac
106
+ _fp=$(tsc_tree_fingerprint "$folder" $_fp_extra)
107
+ [ -n "$_fp" ] || { echo "tsc-cached: could not fingerprint $folder" >&2; exit 1; }
108
+ _key="tsc-$(printf '%s' "$folder" | tr '/' '_')"
109
+ _cache="$GOVERN_DIR/${_key}.${_fp}"
110
+
111
+ # ONLY A ZERO IS REUSABLE. do-reconcile.sh writes its own counts into this same
112
+ # key namespace on purpose (shared entries, one tsc per tree), and it records
113
+ # NON-zero counts too because it wants a delta, not a verdict. This script is a
114
+ # GATE, so it obeys the gate rule: only a pass may be memoised. A cached "17" is
115
+ # re-run from scratch rather than replayed -- a red gate keeps biting until the
116
+ # tree is actually green.
117
+ _cache_ok() { # <file> — 0 when it holds a reusable PASS
118
+ [ -s "$1" ] || return 1
119
+ [ "$(tr -d '[:space:]' < "$1")" = "0" ]
120
+ }
121
+ if [ "${TSC_CACHE_DISABLE:-0}" != "1" ] && _cache_ok "$_cache"; then
122
+ echo "[tsc-cached] $folder — cache HIT (tree unchanged since it was checked)" >&2
123
+ echo 0
124
+ exit 0
125
+ fi
126
+
127
+ if gate_lock "$_key" "${TSC_CACHE_WAIT:-900}"; then
128
+ trap 'gate_release_all' EXIT INT TERM
129
+ # Someone may have computed it while we queued for the lock.
130
+ if [ "${TSC_CACHE_DISABLE:-0}" != "1" ] && _cache_ok "$_cache"; then
131
+ echo "[tsc-cached] $folder — cache HIT (computed while queued)" >&2
132
+ echo 0
133
+ gate_release_all; trap - EXIT INT TERM; exit 0
134
+ fi
135
+ echo "[tsc-cached] $folder — cache MISS, running tsc" >&2
136
+ _tmp=$(mktemp)
137
+ # TSC_CACHE_CMD exists for --self-test only (the `__probe__` idiom from
138
+ # test-cached.sh): it substitutes a child that cannot finish inside the bound,
139
+ # so the timeout branch below can be driven deterministically instead of raced.
140
+ # THE SLOT, not just the lock. gate_lock above dedupes by FOLDER: it stops two
141
+ # sessions computing the SAME answer twice. It does not cap concurrency across
142
+ # DIFFERENT folders, and N worktrees are N folders. Measured 2026-09-07: three
143
+ # worktrees each ran a 2.5GB tsc at once, every lock uncontended and doing its
144
+ # job, load hit 171 and localhost served a page in 8.4s. A lock DEDUPES; only a
145
+ # SLOT BOUNDS THE MACHINE. Route the compute through gate-run.sh so it queues
146
+ # behind GOVERN_MAX_GATES like every other heavy gate.
147
+ #
148
+ # gate-run.sh, never a bare gate_slot: its GOVERN_IN_GATE guard makes a nested
149
+ # gate INHERIT the outer slot rather than take a second one, and a second slot
150
+ # held by one logical gate is what deadlocks the queue at cap 2.
151
+ #
152
+ # stderr is deliberately NOT sent to /dev/null: gate-run announces "QUEUEING",
153
+ # and a wait that is silent gets read as a hang and then as a failure.
154
+ GOVERN_GATE_TIMEOUT="${TSC_CACHE_TIMEOUT:-600}" \
155
+ bash "$ROOT/.claude/scripts/gate-run.sh" "tsc-cache:$folder" -- \
156
+ bash -c "cd '$folder' && ${TSC_CACHE_CMD:-bunx tsc --noEmit} 2>&1 | grep -c 'error TS' > '$_tmp'" \
157
+ || true
158
+ n=$(cat "$_tmp" 2>/dev/null | tr -d '[:space:]'); rm -f "$_tmp"
159
+ gate_release_all; trap - EXIT INT TERM
160
+ # AN EMPTY COUNT IS NOT ZERO. run_bounded kills tsc at the bound and its `|| true`
161
+ # swallows the status, so $_tmp is empty on a timeout. The old line was
162
+ # `n=${n:-0}` -- an unrun typecheck was written to the cache as "0 errors" and
163
+ # every later reader replayed that as a PASS. An uncomputable result is a
164
+ # could-not-run, and a could-not-run is RED.
165
+ case "$n" in
166
+ ''|*[!0-9]*)
167
+ echo "[tsc-cached] $folder — tsc produced no error count (timeout or crash); check did NOT run" >&2
168
+ exit 1 ;;
169
+ esac
170
+ # Only a pass is recorded. See _cache_ok.
171
+ [ "$n" = "0" ] && printf '%s' "$n" > "$_cache"
172
+ echo "$n"
173
+ exit 0
174
+ fi
175
+
176
+ # Could not get the lock within the wait. An UNRUN check is not a passing check:
177
+ # say so and fail, rather than emitting 0 and reading as green.
178
+ echo "[tsc-cached] $folder — could not acquire the tsc lock; check did NOT run" >&2
179
+ exit 1