@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,298 @@
1
+ #!/usr/bin/env bash
2
+ # deploy-emit.sh — ONE deploy gate, onto the run/event stream every viewer reads.
3
+ #
4
+ # manifest: monorepo-only
5
+ # Same classification as deploy-record.sh, and for the same reason: deploy.sh
6
+ # is its only caller and deploy.sh does not ship. It DOES read a credential —
7
+ # GATEWAY_API_KEY through ONE_ENV_FILE (default one.ie/web/.env, plus the
8
+ # monorepo fallbacks do-signal.sh walks), honoured so that a future ship needs
9
+ # no change — and no credential is exit 3 BY NAME, never a quiet success and
10
+ # never a skipped gate that reads as a gate that ran.
11
+ #
12
+ # WHY IT EXISTS. `deploy.sh` emitted NOTHING — `grep -n signal deploy.sh` returns
13
+ # two comment lines and no call. A deploy's only trace was a committed JSON file,
14
+ # and `/deploy` says on its face what that costs: "nothing here animates a run
15
+ # that is happening now, because nothing here can know about one". The file stays
16
+ # (a page must render with no network); this makes the RUN a row as well.
17
+ #
18
+ # THE CALLER MUST NEVER LET THIS FAIL A DEPLOY. Every call site in deploy.sh ends
19
+ # in `|| true`, exactly as `deploy-record.sh` is called. This script is a
20
+ # reporter: a gate that passed while the network was down is still a gate that
21
+ # passed, and a deploy that goes red because its telemetry could not phone home
22
+ # is a worse failure than the one it was reporting. The other half of that rule
23
+ # lives in the mapper: a stage that never arrives is ABSENT, never a default
24
+ # frame, so a dropped emit degrades to a gap in the trace and never to a lie.
25
+ #
26
+ # USAGE
27
+ # deploy-emit.sh --run <id> --target <worker> [--stage <gate> --status <s>]
28
+ # [--verdict green|red] [--sha <sha>] [--door <text>]
29
+ # [--slug <ws>] [--env dev] [--reason <text>] [--wall-ms <n>]
30
+ # [--detail k=v]... [--dry-run]
31
+ # deploy-emit.sh --self-test # local red proofs, no network
32
+ #
33
+ # stage : tree credentials typecheck tests build smoke approval migrations
34
+ # ship health (DEPLOY_SPINE_STEPS)
35
+ # status : start | ok | fail (DeployStageStatus)
36
+ # verdict: green | red — the FINAL call, and the only thing that closes the
37
+ # run. Separate from the stages because `--gates-only` is a complete,
38
+ # successful run that never reaches ship or health; closing on the
39
+ # last stage would leave every one of those runs open forever.
40
+ #
41
+ # --run IS HALF THE RUN KEY, and the sha cannot serve. On 2026-09-06 the same sha
42
+ # (af3ba5919) ran gates-only, went red, went green, then shipped — four runs.
43
+ # Keyed on sha they would have interleaved into one. deploy.sh already mints a
44
+ # usable id for its own log name: deploy-20260906-225246.log.
45
+ #
46
+ # NEVER CALL THIS ON --dry-run. A run row for a deploy that did not happen is the
47
+ # same theater the page refuses to render. Enforced at the call site, because a
48
+ # script cannot know its caller's flags.
49
+ #
50
+ # EXITS 0 emitted (or --dry-run printed) · 2 bad arguments · 3 no credential
51
+ # 4 the receiver refused — its own words on stderr · 5 jq missing
52
+ #
53
+ # THE REFUSAL IS THE WHOLE POINT — measured against prod 2026-09-06:
54
+ # unknown receiver → HTTP 200 {"outcome":"dissolved","reason":"unknown_receiver"}
55
+ # forbidden call → HTTP 200 {"outcome":"result","result":{"ok":false,...}}
56
+ # A caller reading curl's exit code, the HTTP status, or `outcome` alone records
57
+ # a gate that never landed. Success is ONE shape: .outcome == "result" AND
58
+ # .result.ok == true.
59
+ #
60
+ # ENV DEPLOY_EMIT_URL default $ONE_API_URL, else https://one.ie
61
+ # DEPLOY_EVENT_RECEIVER default deploy:event
62
+ # ONE_ENV_FILE / DO_ENV_FILE the credential file
63
+ # DEPLOY_EMIT_SKIP_FALLBACKS=1 ONE_ENV_FILE is the WHOLE ladder. Without it
64
+ # the no-credential state is unreachable from inside the monorepo, so
65
+ # exit 3 could not be driven red. Same reason and shape as
66
+ # FACTORY_EMIT_SKIP_FALLBACKS / DO_SIGNAL_SKIP_FALLBACKS.
67
+ set -uo pipefail
68
+
69
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
70
+ ONE_ENV_FILE="${ONE_ENV_FILE:-${DO_ENV_FILE:-one.ie/web/.env}}"
71
+ URL="${DEPLOY_EMIT_URL:-${ONE_API_URL:-https://one.ie}}"
72
+ RECEIVER="${DEPLOY_EVENT_RECEIVER:-deploy:event}"
73
+ TIMEOUT="${DEPLOY_EMIT_TIMEOUT:-8}"
74
+
75
+ die() { printf 'deploy-emit: %s\n' "$1" >&2; exit "$2"; }
76
+
77
+ STAGES='tree credentials typecheck tests build smoke approval migrations ship health'
78
+
79
+ # ---------------------------------------------------------------------------
80
+ # --self-test — every LOCAL refusal, driven for real. No network, no credential.
81
+ # Each refusal is asserted by its EXIT CODE, never by its message: a gate this
82
+ # script would have posted anyway is the frame nobody meant.
83
+ # ---------------------------------------------------------------------------
84
+ self_test() {
85
+ local me="${BASH_SOURCE[0]}" fails=0 rc out pl
86
+ _case() {
87
+ local label="$1" want="$2"; shift 2
88
+ out="$(ONE_ENV_FILE=/dev/null DEPLOY_EMIT_SKIP_FALLBACKS=1 bash "$me" "$@" 2>&1)"; rc=$?
89
+ if [ "$rc" = "$want" ]; then printf ' ok %-52s exit %s\n' "$label" "$rc"
90
+ else printf ' FAIL %-52s exit %s, wanted %s\n %s\n' "$label" "$rc" "$want" "$(printf '%s' "$out" | head -1)" >&2
91
+ fails=$((fails + 1)); fi
92
+ }
93
+ printf '== deploy-emit --self-test\n'
94
+ # THE broken shape this exists for: deployEventToRunEvents returns [] for a
95
+ # gate outside the ten, and an empty mapping posted anyway is a lie on the stream.
96
+ _case 'unknown stage is REFUSED before the wire' 2 --run r1 --target one-prod --stage lint --status ok
97
+ _case 'unknown status is REFUSED' 2 --run r1 --target one-prod --stage tests --status green
98
+ _case 'unknown verdict is REFUSED' 2 --run r1 --target one-prod --verdict amber
99
+ _case 'missing --run is REFUSED' 2 --target one-prod --stage tests --status ok
100
+ _case 'missing --target is REFUSED' 2 --run r1 --stage tests --status ok
101
+ # Half a stage frame is not a stage frame — the mapper returns [] for either half.
102
+ _case 'stage without status is REFUSED' 2 --run r1 --target one-prod --stage tests
103
+ _case 'status without stage is REFUSED' 2 --run r1 --target one-prod --status ok
104
+ # A payload that names no stage AND no verdict would upsert a run and say nothing.
105
+ _case 'neither stage nor verdict is REFUSED' 2 --run r1 --target one-prod
106
+ _case 'non-numeric --wall-ms is REFUSED' 2 --run r1 --target one-prod --stage tests --status ok --wall-ms fast
107
+ _case 'malformed --detail is REFUSED' 2 --run r1 --target one-prod --stage tests --status ok --detail nokv
108
+ _case 'unknown argument is REFUSED' 2 --run r1 --target one-prod --stage tests --status ok --wat
109
+ _case 'no credential is exit 3, not a quiet pass' 3 --run r1 --target one-prod --stage tests --status ok
110
+ _case '--dry-run needs no credential' 0 --run r1 --target one-prod --stage build --status ok --dry-run
111
+ _case 'verdict alone is a valid final call' 0 --run r1 --target one-prod --verdict green --dry-run
112
+
113
+ pl="$(ONE_ENV_FILE=/dev/null DEPLOY_EMIT_SKIP_FALLBACKS=1 bash "$me" \
114
+ --run 20260906-225246 --target one-prod --stage tests --status ok --sha af3ba5919 \
115
+ --door './deploy' --slug one --reason none --wall-ms 168000 \
116
+ --detail reused=true --detail waived=false --dry-run 2>/dev/null)"
117
+ if printf '%s' "$pl" | jq -e '.data.run == "20260906-225246" and .data.target == "one-prod"
118
+ and .data.stage == "tests" and .data.status == "ok" and .data.sha == "af3ba5919"
119
+ and .data.slug == "one" and .data.wallMs == 168000
120
+ and .data.detail.reused == true and .data.detail.waived == false' >/dev/null 2>&1; then
121
+ printf ' ok %-52s\n' 'dry-run payload carries every named field'
122
+ else
123
+ printf ' FAIL %-52s\n %s\n' 'dry-run payload is incomplete' "$pl" >&2; fails=$((fails + 1))
124
+ fi
125
+
126
+ # wallMs must be a NUMBER and booleans must be BOOLEANS — zod rejects a string
127
+ # for z.number(), so a quoted 168000 is a receiver refusal at deploy time
128
+ # rather than a test failure here.
129
+ if printf '%s' "$pl" | jq -e '(.data.wallMs | type) == "number"
130
+ and (.data.detail.reused | type) == "boolean"' >/dev/null 2>&1; then
131
+ printf ' ok %-52s\n' 'wallMs is a number, booleans are booleans'
132
+ else
133
+ printf ' FAIL %-52s\n %s\n' 'metric types would be refused by zod' "$pl" >&2; fails=$((fails + 1))
134
+ fi
135
+
136
+ # Optional fields ABSENT rather than empty — zod strips undeclared keys
137
+ # silently, and an empty `slug` is not the same as no slug.
138
+ pl="$(ONE_ENV_FILE=/dev/null DEPLOY_EMIT_SKIP_FALLBACKS=1 bash "$me" \
139
+ --run r --target one-prod --stage tree --status start --dry-run 2>/dev/null)"
140
+ if printf '%s' "$pl" | jq -e '(.data | has("slug") | not) and (.data | has("detail") | not)
141
+ and (.data | has("wallMs") | not) and (.data | has("verdict") | not)' >/dev/null 2>&1; then
142
+ printf ' ok %-52s\n' 'unset optional fields are absent, not empty'
143
+ else
144
+ printf ' FAIL %-52s\n %s\n' 'unset optional fields leaked into the payload' "$pl" >&2; fails=$((fails + 1))
145
+ fi
146
+
147
+ # Every stage in STAGES must be accepted — a typo here silently narrows the
148
+ # pipeline to the gates somebody remembered.
149
+ local st
150
+ for st in $STAGES; do
151
+ ONE_ENV_FILE=/dev/null DEPLOY_EMIT_SKIP_FALLBACKS=1 bash "$me" \
152
+ --run r --target t --stage "$st" --status ok --dry-run >/dev/null 2>&1 \
153
+ || { printf ' FAIL %-52s\n' "stage '$st' rejected by its own script" >&2; fails=$((fails + 1)); }
154
+ done
155
+ printf ' ok %-52s\n' "all 10 DEPLOY_SPINE_STEPS accepted"
156
+
157
+ if [ "$fails" -eq 0 ]; then printf 'deploy-emit --self-test: PASS (17 checks)\n'; return 0; fi
158
+ printf 'deploy-emit --self-test: RED — %s check(s) failed\n' "$fails" >&2; return 1
159
+ }
160
+
161
+ RUN=""; TARGET=""; STAGE=""; STATUS=""; VERDICT=""; SHA=""; DOOR=""
162
+ SLUG=""; ENVW=""; REASON=""; WALLMS=""; DETAILS=""; DRY=0
163
+ while [ $# -gt 0 ]; do
164
+ case "$1" in
165
+ --run) RUN="${2:-}"; shift 2 ;;
166
+ --target) TARGET="${2:-}"; shift 2 ;;
167
+ --stage) STAGE="${2:-}"; shift 2 ;;
168
+ --status) STATUS="${2:-}"; shift 2 ;;
169
+ --verdict) VERDICT="${2:-}"; shift 2 ;;
170
+ --sha) SHA="${2:-}"; shift 2 ;;
171
+ --door) DOOR="${2:-}"; shift 2 ;;
172
+ --slug) SLUG="${2:-}"; shift 2 ;;
173
+ --env) ENVW="${2:-}"; shift 2 ;;
174
+ --reason) REASON="${2:-}"; shift 2 ;;
175
+ --wall-ms) WALLMS="${2:-}"; shift 2 ;;
176
+ --detail) DETAILS="${DETAILS}${DETAILS:+$'\n'}${2:-}"; shift 2 ;;
177
+ --dry-run) DRY=1; shift ;;
178
+ --self-test) command -v jq >/dev/null 2>&1 || die "jq not found" 5; self_test; exit $? ;;
179
+ -h|--help) sed -n '2,48p' "${BASH_SOURCE[0]}"; exit 0 ;;
180
+ *) die "unknown argument: $1" 2 ;;
181
+ esac
182
+ done
183
+
184
+ command -v jq >/dev/null 2>&1 || die "jq not found — this script builds and reads JSON with it, and a hand-rolled parser is how a refusal becomes a pass" 5
185
+ [ -n "$RUN" ] || die "--run <id> is required — it is half the run key (deploy:<target>:<run>, event.ts deployRunKey). The sha cannot serve: one sha ships repeatedly and the attempts would interleave" 2
186
+ [ -n "$TARGET" ] || die "--target <worker> is required — the other half of the run key, so 'did pay go out' does not need disambiguating by timestamp" 2
187
+
188
+ # Half a stage frame is not a stage frame. The mapper returns [] for either half
189
+ # alone, so refusing here keeps the wire and the mapper saying the same thing.
190
+ if [ -n "$STAGE" ] || [ -n "$STATUS" ]; then
191
+ [ -n "$STAGE" ] || die "--status without --stage — deployEventToRunEvents returns [] for half a frame, and guessing the other half records a frame nobody meant" 2
192
+ [ -n "$STATUS" ] || die "--stage without --status — same reason" 2
193
+ case " $STAGES " in
194
+ *" $STAGE "*) ;;
195
+ *) die "--stage must be one of: $STAGES (got '$STAGE') — deployEventToRunEvents returns [] for anything else, and an empty mapping posted anyway is a frame nobody meant" 2 ;;
196
+ esac
197
+ case "$STATUS" in
198
+ start|ok|fail) ;;
199
+ *) die "--status must be start|ok|fail (got '$STATUS'). There is deliberately no 'skip': a gate that did not run must be ABSENT, and an unrun gate is never a pass" 2 ;;
200
+ esac
201
+ fi
202
+
203
+ if [ -n "$VERDICT" ]; then
204
+ case "$VERDICT" in
205
+ green|red) ;;
206
+ *) die "--verdict must be green|red (got '$VERDICT')" 2 ;;
207
+ esac
208
+ fi
209
+
210
+ [ -n "$STAGE" ] || [ -n "$VERDICT" ] || \
211
+ die "nothing to say: pass --stage/--status, or --verdict, or both. A payload naming neither would upsert a run and record no frame" 2
212
+
213
+ if [ -n "$WALLMS" ]; then
214
+ case "$WALLMS" in
215
+ ''|*[!0-9]*) die "--wall-ms wants an integer of milliseconds (got '$WALLMS') — the receiver types it z.number() and would refuse a string at deploy time" 2 ;;
216
+ esac
217
+ fi
218
+
219
+ # k=v, with `true`/`false`/integers kept as JSON scalars. `reused` and `waived`
220
+ # are the two metrics this whole feature exists to carry, and both are booleans:
221
+ # quoting them would make `waived:"false"` truthy in every reader.
222
+ detail_json='{}'
223
+ if [ -n "$DETAILS" ]; then
224
+ while IFS= read -r kv; do
225
+ [ -n "$kv" ] || continue
226
+ case "$kv" in *=*) ;; *) die "--detail wants k=v, got: $kv" 2 ;; esac
227
+ k="${kv%%=*}"; v="${kv#*=}"
228
+ case "$v" in
229
+ true|false) detail_json="$(jq -n --argjson d "$detail_json" --arg k "$k" --argjson v "$v" '$d + {($k): $v}')" ;;
230
+ ''|*[!0-9]*) detail_json="$(jq -n --argjson d "$detail_json" --arg k "$k" --arg v "$v" '$d + {($k): $v}')" ;;
231
+ *) detail_json="$(jq -n --argjson d "$detail_json" --arg k "$k" --argjson v "$v" '$d + {($k): $v}')" ;;
232
+ esac
233
+ [ -n "$detail_json" ] || die "could not build --detail object" 2
234
+ done <<EOF
235
+ $DETAILS
236
+ EOF
237
+ fi
238
+
239
+ payload="$(jq -n \
240
+ --arg run "$RUN" --arg target "$TARGET" --arg stage "$STAGE" --arg status "$STATUS" \
241
+ --arg verdict "$VERDICT" --arg sha "$SHA" --arg door "$DOOR" --arg slug "$SLUG" \
242
+ --arg env "$ENVW" --arg reason "$REASON" --arg wallms "$WALLMS" \
243
+ --argjson detail "$detail_json" '
244
+ {data: ({run: $run, target: $target}
245
+ + (if $stage != "" then {stage: $stage} else {} end)
246
+ + (if $status != "" then {status: $status} else {} end)
247
+ + (if $verdict != "" then {verdict: $verdict} else {} end)
248
+ + (if $sha != "" then {sha: $sha} else {} end)
249
+ + (if $door != "" then {door: $door} else {} end)
250
+ + (if $slug != "" then {slug: $slug} else {} end)
251
+ + (if $env != "" then {env: $env} else {} end)
252
+ + (if $reason != "" then {reason: $reason} else {} end)
253
+ + (if $wallms != "" then {wallMs: ($wallms | tonumber)} else {} end)
254
+ + (if ($detail | length) > 0 then {detail: $detail} else {} end))}')" \
255
+ || die "could not build the payload" 2
256
+
257
+ if [ "$DRY" = 1 ]; then printf '%s\n' "$payload"; exit 0; fi
258
+
259
+ # The key never reaches argv (ps) and never reaches stdout. --config, chmod 600,
260
+ # removed on every path. Same shape as factory-emit.sh and do-signal.sh.
261
+ _gateway_key() {
262
+ local f v
263
+ for f in "$ONE_ENV_FILE" "$ROOT/one.ie/web/.env" "$ROOT/one.ie/web/.dev.vars" "$ROOT/.env"; do
264
+ [ -f "$f" ] || continue
265
+ if [ "${DEPLOY_EMIT_SKIP_FALLBACKS:-0}" = "1" ] && [ "$f" != "$ONE_ENV_FILE" ]; then continue; fi
266
+ v="$(grep -E '^GATEWAY_API_KEY=' "$f" 2>/dev/null | head -1 | sed 's/^[^=]*=//;s/^"//;s/"$//')"
267
+ [ -n "$v" ] && { printf '%s' "$v"; return 0; }
268
+ done
269
+ printf ''
270
+ }
271
+
272
+ KEYVAL="$(_gateway_key)"
273
+ [ -n "$KEYVAL" ] || die "no GATEWAY_API_KEY in $ONE_ENV_FILE (or the monorepo fallbacks) — an unauthenticated emit is refused at the door and would look exactly like a gate that never happened" 3
274
+
275
+ cfg="$(mktemp)"; chmod 600 "$cfg"
276
+ trap 'rm -f "$cfg"' EXIT INT TERM
277
+ printf 'header = "Authorization: Bearer %s"\n' "$KEYVAL" > "$cfg"
278
+
279
+ resp="$(curl -sS -X POST "$URL/api/ask/$RECEIVER" \
280
+ -H 'content-type: application/json' --config "$cfg" \
281
+ --max-time "$TIMEOUT" -d "$payload" -w $'\n%{http_code}' 2>&1)"; rc=$?
282
+ rm -f "$cfg"; trap - EXIT INT TERM
283
+
284
+ code="$(printf '%s' "$resp" | tail -1)"
285
+ body="$(printf '%s' "$resp" | sed '$d')"
286
+ ok="$(printf '%s' "$body" | jq -r 'if (.outcome == "result" and (.result.ok == true)) then "yes" else "no" end' 2>/dev/null)"
287
+
288
+ if [ "$rc" -ne 0 ] || [ "$ok" != "yes" ]; then
289
+ why="$(printf '%s' "$body" | jq -r '.reason // .result.error // .error // .hint // empty' 2>/dev/null)"
290
+ [ -n "$why" ] || why="curl rc=$rc http=${code:-none} body=$(printf '%s' "$body" | head -c 200)"
291
+ printf 'deploy-emit: RED run=%s target=%s stage=%s%s http=%s — %s\n' \
292
+ "$RUN" "$TARGET" "${STAGE:-none}" "${VERDICT:+ verdict=$VERDICT}" "${code:-none}" "$why" >&2
293
+ exit 4
294
+ fi
295
+
296
+ printf 'deploy-emit: ok run=%s target=%s stage=%s%s receiver=%s\n' \
297
+ "$RUN" "$TARGET" "${STAGE:-none}" "${VERDICT:+ verdict=$VERDICT}" "$RECEIVER"
298
+ exit 0
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env bash
2
+ # deploy-gate-check.sh — red proofs for the deploy gate wrappers. Ships nothing,
3
+ # deploys nothing, touches no service. Exits non-zero on failure.
4
+ #
5
+ # manifest: monorepo-only
6
+ #
7
+ # WHY. deploy.sh grew a memoised test gate, a cached build gate, and a rule that
8
+ # an unpaid deferred-pin debt refuses a deploy. Each is a place a gate could
9
+ # quietly stop biting, and a wiring check that only greps for a string proves
10
+ # nothing about behaviour. So the deferred-pin block is EXTRACTED VERBATIM from
11
+ # deploy.sh and executed against stubs -- a copy of it here would prove only that
12
+ # the copy works.
13
+ set -uo pipefail
14
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
15
+ DEPLOY="$ROOT/.claude/scripts/deploy.sh"
16
+ fails=0
17
+
18
+ # --- deploy must NOT have re-wired tsc through the shared-lock cache --------
19
+ # It was wired that way for one commit and reverted on measurement: the five
20
+ # typechecks are 8s serial on a warm box and finish inside vitest's shadow, while
21
+ # tsc-cached.sh takes the same gate_lock do-reconcile.sh takes -- so a concurrent
22
+ # /do cycle could make the ship gate queue 900s and then go RED over nothing.
23
+ if grep -qE 'gate_start(_governed)? "tsc-\$\(echo "\$svc" \| tr / -\)" "\$svc" bunx tsc --noEmit' "$DEPLOY"; then
24
+ echo "ok: typechecks run bare (no shared lock on the ship path)"
25
+ else
26
+ echo "FAIL: the typecheck gate no longer runs bare — check the lock-contention note in deploy.sh"; fails=$((fails+1))
27
+ fi
28
+
29
+ # --- deploy must still call the cached build, not a bare `bun run build` -----
30
+ if grep -q 'gate_start_heavy build one.ie/web bash "\$ROOT/.claude/scripts/astro-build-cached.sh"' "$DEPLOY"; then
31
+ echo "ok: the build gate routes through astro-build-cached.sh"
32
+ else
33
+ echo "FAIL: the build gate no longer routes through astro-build-cached.sh"; fails=$((fails+1))
34
+ fi
35
+ # --- and the vitest gate must keep its pty, or it hangs on a log file --------
36
+ # The assertion names test-full.sh, NOT test-cached.sh, and that is the whole
37
+ # point of the edit that put it here. deploy.sh used to spell the vitest argv out
38
+ # itself and call test-cached.sh directly; test-full.sh was then created as the
39
+ # ONE definition of the full suite so deploy and /close could not present
40
+ # different argv to the memo and miss each other's stamps. This grep kept naming
41
+ # the old call, so it emitted "the vitest gate lost TEST_CACHE_PTY" over wiring
42
+ # that was correct -- a FALSE RED on a ship gate, which is the failure mode that
43
+ # gets a gate ignored. The pty is still set, twice: here, and per-lane at
44
+ # test-lanes.sh:102/107.
45
+ if grep -q 'TEST_CACHE_PTY=1 bash "\$ROOT/.claude/scripts/test-full.sh"' "$DEPLOY"; then
46
+ echo "ok: the vitest gate runs through test-full.sh with a pty"
47
+ else
48
+ echo "FAIL: the vitest gate lost TEST_CACHE_PTY — it will hang on a non-TTY stdout"; fails=$((fails+1))
49
+ fi
50
+
51
+ # --- and the memo behind that gate must actually MISS when the tree moves ----
52
+ # The line above is a wiring grep. This drives the real thing on one small
53
+ # selection: a cold MISS, then a HIT on identical inputs, then a MISS again after
54
+ # a source file the selection reads is edited. Without the third step a memo that
55
+ # always hit would look perfect -- and would hand deploy a green suite over a
56
+ # changed tree, which is the only way this optimisation can ship bad code.
57
+ _SEL="tests/unit/agents/subscribe-tags-parity.test.ts"
58
+ _SRC="one.ie/web/src/lib/in/spaces.ts"
59
+ if [ ! -d "$ROOT/one.ie/web/node_modules" ] || [ ! -f "$ROOT/$_SRC" ]; then
60
+ # An unrunnable check is n/a. Never ok: a worktree without node_modules cannot
61
+ # start vitest, and a startup crash exits non-zero for reasons that have
62
+ # nothing to do with the property under test.
63
+ echo "n/a: no one.ie/web/node_modules here — the memo drive cannot run (NOT a pass)"
64
+ else
65
+ _mc="$(mktemp -d)"; _memo() { TEST_CACHE_DIR="$_mc" bash "$ROOT/.claude/scripts/test-cached.sh" \
66
+ one.ie/web -- --reporter=dot "$_SEL" 2>&1; }
67
+ m1="$(_memo)"; r1=$?
68
+ m2="$(_memo)"; r2=$?
69
+ if [ "$r1" -ne 0 ] || [ "$r2" -ne 0 ]; then
70
+ echo "n/a: the probe selection did not pass, cannot measure the memo (NOT a pass)"
71
+ else
72
+ case "$m1" in *"cache HIT"*) echo "FAIL: a cold cache reported a HIT"; fails=$((fails+1));;
73
+ *) echo "ok: a cold memo MISSES and runs the tests";; esac
74
+ case "$m2" in *"cache HIT"*) echo "ok: identical inputs HIT the memo";;
75
+ *) echo "FAIL: identical inputs did not hit — the memo buys nothing"; fails=$((fails+1));; esac
76
+ # RED PROOF — move a source file the selection reads; the memo MUST miss.
77
+ cp "$ROOT/$_SRC" "$_mc/src.bak"
78
+ echo "// __deploy_gate_check_probe" >> "$ROOT/$_SRC"
79
+ m3="$(_memo)"
80
+ cp -f "$_mc/src.bak" "$ROOT/$_SRC"
81
+ case "$m3" in *"cache HIT"*) echo "FAIL: a CHANGED tree replayed a cached PASS"; fails=$((fails+1));;
82
+ *) echo "ok: a source edit invalidates the memo";; esac
83
+ fi
84
+ rm -rf "$_mc"
85
+ fi
86
+ # --- the deferred-pin ledger: driven, not grepped ---------------------------
87
+ # Extract deploy.sh's debt block verbatim and run it with stubbed reporters. The
88
+ # block is the last chance a deferred pin has to come due: verify-fast.sh settles
89
+ # the ledger only in its own FULL_VERIFY branch, and deploy sets FULL_VERIFY=1 but
90
+ # invokes the suite directly -- so before this block existed, a production deploy
91
+ # shipped straight over pins that no run had ever executed.
92
+ _debt_block="$(awk '/^DEBT_FILE=/{f=1} f{print} f&&/^fi$/{exit}' "$DEPLOY")"
93
+ if [ -z "$_debt_block" ]; then
94
+ echo "FAIL: could not extract the debt block from deploy.sh — this checker is blind"; fails=$((fails+1))
95
+ else
96
+ _drive_debt() { # <ledger-contents|""> <SKIP_TESTS> <TESTS_REPORT>
97
+ local led; led="$(mktemp)"
98
+ [ -n "$1" ] && printf '%s\n' "$1" > "$led" || : > "$led"
99
+ VERIFY_FAST_DEBT_FILE="$led" SKIP_TESTS="$2" TESTS_REPORT="$3" LOG=/dev/null \
100
+ bash -c '
101
+ say(){ printf "%s\n" "$*"; }; step(){ :; }; ok(){ printf "ok %s\n" "$*"; }
102
+ die(){ printf "DIE %s\n" "$*"; exit 1; }
103
+ '"$_debt_block"'
104
+ exit 0' >/dev/null 2>&1
105
+ local rc=$?; rm -f "$led"; return $rc
106
+ }
107
+ # RED PROOF — an outstanding debt with a SKIPPED suite must refuse the deploy.
108
+ if _drive_debt "pins deferred at cycle C3" 1 "skipped"; then
109
+ echo "FAIL: an unpaid pin debt did NOT refuse a --skip-tests deploy"; fails=$((fails+1))
110
+ else echo "ok: an unpaid pin debt refuses a test-skipping deploy"; fi
111
+ # RED PROOF — the same debt with no suite REPORT must refuse too (an unrun gate
112
+ # is not a passing gate, whatever the flags said).
113
+ if _drive_debt "pins deferred at cycle C3" 0 "skipped"; then
114
+ echo "FAIL: an unpaid debt passed with no suite result"; fails=$((fails+1))
115
+ else echo "ok: an unpaid debt with no suite result refuses"; fi
116
+ # GREEN half — a debt paid by a green full suite must let the deploy through,
117
+ # or the two proofs above are just a gate that always says no.
118
+ if _drive_debt "pins deferred at cycle C3" 0 "Tests 10780 passed"; then
119
+ echo "ok: a green full suite settles the debt and the deploy continues"
120
+ else echo "FAIL: a green full suite did not settle the debt"; fails=$((fails+1)); fi
121
+ # and an EMPTY ledger must never block anything
122
+ if _drive_debt "" 1 "skipped"; then echo "ok: no debt, no refusal"
123
+ else echo "FAIL: an empty ledger blocked the deploy"; fails=$((fails+1)); fi
124
+ fi
125
+
126
+ echo ""
127
+ [ "$fails" -eq 0 ] && { echo "deploy-gate-check: PASS"; exit 0; }
128
+ echo "deploy-gate-check: FAILED ($fails)"; exit 1
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bash
2
+ # deploy-ready.sh — say which branch the next deploy will take, BEFORE it takes it.
3
+ #
4
+ # manifest: monorepo-only
5
+ #
6
+ # WHY THIS EXISTS. deploy.sh prices its heavy gates in memory, not cores:
7
+ # vitest (~6GB) and the astro build (--max-old-space-size=8192) overlap only
8
+ # when `gate_mem_avail_mb` reports >= DEPLOY_HEAVY_NEED_GB (default 14) at the
9
+ # moment the gates start. deploy.sh's own comment carries the measurement:
10
+ #
11
+ # overlapped 176s
12
+ # serialised 1145s
13
+ #
14
+ # That is a 6.5x swing decided by ONE sample taken once, at gate-start, and
15
+ # nothing in the pipeline records which way it went. Two runs on a byte-identical
16
+ # tree can differ by 6.5x and the ledger cannot say why.
17
+ #
18
+ # So this is not a new gate and it changes no behaviour. It reads the same
19
+ # probe deploy.sh reads, through the same function, and prints the branch the
20
+ # deploy WOULD take. It turns a coin-flip into a decision.
21
+ #
22
+ # THE PROBE IS THE HONEST ONE. `memory_pressure`'s "free percentage" counts file
23
+ # cache, purgeable and compressed pages: measured 2026-09-03 it read 74% on a box
24
+ # with 1.3% actually free and 9.4GB in swap. gate_mem_avail_mb is what
25
+ # lib/govern.sh and deploy.sh both use; this reads it rather than inventing a
26
+ # second definition that drifts.
27
+ #
28
+ # Exit codes, so a caller can branch on it:
29
+ # 0 the fast branch — heavy gates will overlap
30
+ # 1 the slow branch — they will serialise (deploy still WORKS, just ~6.5x)
31
+ # 2 the probe could not read memory
32
+ #
33
+ # It never refuses and never kills anything: what to do about a short box is the
34
+ # operator's call, and a pre-flight that took that call would be a gate.
35
+ set -uo pipefail
36
+
37
+ ROOT="${DEPLOY_READY_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
38
+ . "$ROOT/.claude/scripts/lib/govern.sh"
39
+
40
+ NEED_GB="${DEPLOY_HEAVY_NEED_GB:-14}"
41
+
42
+ avail_mb="$(gate_mem_avail_mb 2>/dev/null || echo "")"
43
+ if [ -z "$avail_mb" ]; then
44
+ echo " RED memory probe unreadable — deploy.sh will assume 999GB and OVERLAP"
45
+ echo " (a broken sensor must never silently serialise the pipeline, so"
46
+ echo " the fallback is the fast branch. On a short box that pages.)"
47
+ exit 2
48
+ fi
49
+ free_gb=$(( avail_mb / 1024 ))
50
+
51
+ swap="$(sysctl -n vm.swapusage 2>/dev/null || echo '')"
52
+ load="$(uptime | sed 's/.*load averages*: //')"
53
+ cores="$(sysctl -n hw.ncpu 2>/dev/null || echo '?')"
54
+
55
+ printf '\n deploy-ready — will the heavy gates overlap?\n\n'
56
+ printf ' free memory %sGB (need >= %sGB)\n' "$free_gb" "$NEED_GB"
57
+ printf ' swap %s\n' "$swap"
58
+ printf ' load %s on %s cores\n' "$load" "$cores"
59
+
60
+ # What is holding memory right now, so "unthrash" is actionable rather than advice.
61
+ printf '\n top memory holders:\n'
62
+ ps -Ao rss=,comm=,args= | sort -rn | head -6 | while read -r rss comm rest; do
63
+ printf ' %6s MB %s\n' "$((rss/1024))" "$(printf '%.58s' "$comm $rest")"
64
+ done
65
+
66
+ printf '\n'
67
+ if (( free_gb >= NEED_GB )); then
68
+ printf ' ok OVERLAP — vitest and build run together (measured ~176s)\n\n'
69
+ exit 0
70
+ fi
71
+ printf ' RED SERIALISE — vitest then build, one after the other\n'
72
+ printf ' measured 1145s vs 176s: this is the 6.5x branch\n'
73
+ printf ' short by %sGB. Free memory before shipping — browsers and\n' "$(( NEED_GB - free_gb ))"
74
+ printf ' sessions cost RAM; worktrees cost only disk, so sweeping\n'
75
+ printf ' trees is hygiene, not the fix.\n\n'
76
+ printf ' Do NOT set DEPLOY_HEAVY_PARALLEL=1 to force the fast branch:\n'
77
+ printf ' overlapping ~7GB gates on a short box is what measured 1145s.\n\n'
78
+ exit 1