@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,196 @@
1
+ #!/usr/bin/env bash
2
+ # do-recon-pack.sh <slug> [--files <path>...] — the DETERMINISTIC W1.
3
+ #
4
+ # manifest: needs-env
5
+ #
6
+ # needs-env, not portable: it JOINs do-brief.sh (needs-env) and do-recon-cache.sh,
7
+ # so it can be no cleaner than its inputs.
8
+ #
9
+ # WHY. `.claude/commands/do.md` Step 0.5 already states the contract: "with the
10
+ # brief compiled, a cycle needs at most TWO model calls — W2 (decide) and W3
11
+ # (make it). W1 collapses into the brief plus do-recon-cache.sh check." Step 3
12
+ # still spawned a `w1-recon` agent every cycle anyway. Measured 2026-09-01, that
13
+ # spawn re-loads 6.4KB of agent prose to narrate, in <400 words, facts that are
14
+ # on disk: which files exist, how big they are, what they export, who imports
15
+ # them, which docs carry the slug, and what the previous cycle left in
16
+ # `.w4-improvements.json` / `.claude/improvements.queue.md`. None of that is
17
+ # judgment. All of it is grep.
18
+ #
19
+ # This is a JOIN plus a symbol inventory, not new logic:
20
+ # do-brief.sh -> slug, plan, ready, files, folders, tier, spine, ceiling
21
+ # do-recon-cache.sh -> a warm prior recon for the same file shas (0 tokens)
22
+ # git grep / wc -> exports, imports, consumers, doc family
23
+ #
24
+ # It does NOT replace the `w1-recon` AGENT for SURVEY or INVESTIGATE (the spine
25
+ # stops in do.md Step 2). Those are once-per-PLAN judgment calls — reuse verdicts
26
+ # and root-cause forensics — and are not the per-cycle bill. This replaces the
27
+ # per-cycle RECON spawn only.
28
+ #
29
+ # Output: ONE json object on stdout. Non-zero exit + EMPTY stdout when the brief
30
+ # cannot be compiled — a half-pack is worse than no pack, because the caller
31
+ # cannot tell which half is missing (same law as do-brief.sh).
32
+ #
33
+ # --self-test runs the fixtures INCLUDING a red proof; exits non-zero on failure.
34
+ set -euo pipefail
35
+
36
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
37
+ S="$ROOT/.claude/scripts"
38
+ MAX_FILES="${RECON_PACK_MAX_FILES:-40}"
39
+
40
+ _exports() { # exported symbol names, capped
41
+ grep -hoE '^[[:space:]]*export[[:space:]]+(default[[:space:]]+)?(async[[:space:]]+)?(function|const|let|class|type|interface|enum)[[:space:]]+[A-Za-z_$][A-Za-z0-9_$]*' "$1" 2>/dev/null \
42
+ | awk '{print $NF}' | sort -u | head -30
43
+ }
44
+ _imports() { # module specifiers this file pulls in, capped
45
+ [ "$WIDE" = true ] || return 0
46
+ grep -hoE "from[[:space:]]+['\"][^'\"]+['\"]" "$1" 2>/dev/null \
47
+ | sed -E "s/.*['\"]([^'\"]+)['\"].*/\1/" | sort -u | head -30
48
+ }
49
+ _consumers() { # files that import this one, by basename stem, capped
50
+ local stem; stem="$(basename "$1")"; stem="${stem%.*}"
51
+ [ -n "$stem" ] || return 0
52
+ git -C "$ROOT" grep -l -F -- "/$stem'" -- '*.ts' '*.tsx' '*.astro' 2>/dev/null \
53
+ | grep -vxF "$1" | head -"$CO_CAP" || true
54
+ }
55
+
56
+ # WIDTH CAP. This pack replaces a receipt do.md caps at 400 words (~2.5KB), and
57
+ # a recon input bigger than the agent prompt it removed is not a saving.
58
+ # Measured 2026-09-01: 5 files -> 6.4KB, 15 files -> 12.1KB uncapped. Past 10
59
+ # files the per-file detail narrows — `imports` goes first, because W2 needs the
60
+ # exported surface and who depends on it, almost never the module specifiers.
61
+ CO_CAP=12
62
+ WIDE=true
63
+ _set_caps() { # <file-count>
64
+ if [ "$1" -gt "${RECON_PACK_WIDE_MAX:-10}" ]; then CO_CAP=5; WIDE=false
65
+ else CO_CAP=12; WIDE=true; fi
66
+ }
67
+
68
+ _json_arr() { # stdin lines -> json array (empty stdin -> [])
69
+ jq -R -s 'split("\n") | map(select(length > 0))'
70
+ }
71
+
72
+ _pack() {
73
+ local brief slug files_json
74
+ # The brief is the floor. If it refuses, we refuse — identically.
75
+ brief="$(bash "$S/do-brief.sh" "$@" 2>/dev/null)" || return 1
76
+ [ -n "$brief" ] || return 1
77
+ printf '%s' "$brief" | jq -e . >/dev/null 2>&1 || return 1
78
+
79
+ slug="$(printf '%s' "$brief" | jq -r '.slug // ""')"
80
+ files_json="$(printf '%s' "$brief" | jq -r '.files[]?' | head -"$MAX_FILES" | _json_arr)"
81
+ _set_caps "$(printf '%s' "$files_json" | jq 'length')"
82
+
83
+ # ---- per-file inventory (the part W1 used to narrate) ----
84
+ local inv='[]' f entry
85
+ while IFS= read -r f; do
86
+ [ -n "$f" ] || continue
87
+ if [ -f "$ROOT/$f" ]; then
88
+ entry="$(jq -n \
89
+ --arg path "$f" \
90
+ --argjson loc "$(wc -l < "$ROOT/$f" | tr -d ' ')" \
91
+ --arg sha "$(git -C "$ROOT" hash-object "$f" 2>/dev/null || echo '')" \
92
+ --argjson exports "$(_exports "$ROOT/$f" | _json_arr)" \
93
+ --argjson imports "$(_imports "$ROOT/$f" | _json_arr)" \
94
+ --argjson consumers "$(_consumers "$f" | _json_arr)" \
95
+ '{path:$path, exists:true, loc:$loc, sha:$sha, exports:$exports, imports:$imports, consumers:$consumers}')"
96
+ else
97
+ entry="$(jq -n --arg path "$f" '{path:$path, exists:false, loc:0, sha:"", exports:[], imports:[], consumers:[]}')"
98
+ fi
99
+ inv="$(printf '%s' "$inv" | jq --argjson e "$entry" '. + [$e]')"
100
+ done < <(printf '%s' "$files_json" | jq -r '.[]?')
101
+
102
+ # ---- prior-cycle carry-over (W1's non-recon side effects — must not be lost) ----
103
+ local improvements='[]' queue='[]' docs='[]'
104
+ if [ -f "$ROOT/.w4-improvements.json" ]; then
105
+ improvements="$(jq -c '[.. | objects | select(has("item") or has("improve"))] | .[0:10]' \
106
+ "$ROOT/.w4-improvements.json" 2>/dev/null || echo '[]')"
107
+ fi
108
+ if [ -n "$slug" ] && [ -f "$ROOT/.claude/improvements.queue.md" ]; then
109
+ queue="$(grep -i -- "$slug" "$ROOT/.claude/improvements.queue.md" 2>/dev/null | head -5 | _json_arr)"
110
+ fi
111
+ if [ -n "$slug" ]; then
112
+ docs="$(cd "$ROOT" && ls "text/$slug.md" "text/$slug-plan.md" "text/$slug-features.md" \
113
+ "text/$slug-ui.md" "text/$slug-todo.md" "text/$slug-docs.md" 2>/dev/null | _json_arr)"
114
+ fi
115
+
116
+ # ---- warm recon cache (0-token prior findings for the same shas) ----
117
+ local cache_hit=false cache_findings="" flist
118
+ flist="$(printf '%s' "$files_json" | jq -r '.[]?')"
119
+ if [ -n "$flist" ]; then
120
+ if cache_findings="$(cd "$ROOT" && bash "$S/do-recon-cache.sh" check $flist 2>/dev/null)"; then
121
+ cache_hit=true
122
+ else
123
+ cache_findings=""
124
+ fi
125
+ fi
126
+
127
+ jq -n \
128
+ --argjson brief "$brief" \
129
+ --argjson inventory "$inv" \
130
+ --argjson improvements "$improvements" \
131
+ --argjson queue "$queue" \
132
+ --argjson docs "$docs" \
133
+ --argjson cache_hit "$cache_hit" \
134
+ --arg cache_findings "$cache_findings" \
135
+ '{brief:$brief, inventory:$inventory, doc_family:$docs,
136
+ improvements_open:$improvements, improvements_queue:$queue,
137
+ recon_cache:{hit:$cache_hit, findings:$cache_findings}}'
138
+ }
139
+
140
+ _self_test() {
141
+ local fails=0 out
142
+
143
+ # 1. refusal parity with do-brief: a missing plan must refuse with EMPTY stdout.
144
+ if out=$(bash "$S/do-recon-pack.sh" __nosuchplan__ 2>/dev/null); then
145
+ echo "FAIL: missing plan produced a pack"; fails=$((fails+1))
146
+ else
147
+ [ -z "${out:-}" ] || { echo "FAIL: missing plan wrote to stdout"; fails=$((fails+1)); }
148
+ echo "ok: missing plan refuses (exit non-zero, empty stdout)"
149
+ fi
150
+
151
+ # 2. the pack is valid JSON and carries the brief + a real symbol inventory.
152
+ if out=$(bash "$S/do-recon-pack.sh" --files one.ie/web/src/lib/authority.ts 2>/dev/null); then
153
+ if printf '%s' "$out" | jq -e '.brief.tier and (.inventory | length > 0)' >/dev/null 2>&1; then
154
+ echo "ok: pack is valid JSON with brief + inventory"
155
+ else
156
+ echo "FAIL: pack missing brief/inventory"; fails=$((fails+1))
157
+ fi
158
+ if printf '%s' "$out" | jq -e '.inventory[0].exports | length > 0' >/dev/null 2>&1; then
159
+ echo "ok: exports extracted from a real source file"
160
+ else
161
+ echo "FAIL: no exports extracted from authority.ts"; fails=$((fails+1))
162
+ fi
163
+ else
164
+ echo "FAIL: --files pack did not run"; fails=$((fails+1))
165
+ fi
166
+
167
+ # 3. RED PROOF — a checker that cannot go red proves nothing.
168
+ # Feed a file that does NOT exist. The pack must report exists:false and
169
+ # exports:[] rather than fabricate an inventory — AND the exact assertion
170
+ # that passed in (2) must now FAIL, proving it bites.
171
+ if out=$(bash "$S/do-recon-pack.sh" --files no/such/file.ts 2>/dev/null); then
172
+ if printf '%s' "$out" | jq -e '.inventory[0].exists == false and (.inventory[0].exports | length == 0)' >/dev/null 2>&1; then
173
+ echo "ok: RED PROOF — a missing target is reported exists:false, not fabricated"
174
+ else
175
+ echo "FAIL: RED PROOF — missing target was not marked absent"; fails=$((fails+1))
176
+ fi
177
+ if printf '%s' "$out" | jq -e '.inventory[0].exports | length > 0' >/dev/null 2>&1; then
178
+ echo "FAIL: RED PROOF — the exports assertion passed on a file with no exports"; fails=$((fails+1))
179
+ else
180
+ echo "ok: RED PROOF — the exports assertion goes RED on a file with none"
181
+ fi
182
+ else
183
+ echo "FAIL: RED PROOF — pack refused a syntactically fine --files target"; fails=$((fails+1))
184
+ fi
185
+
186
+ if [ "$fails" -eq 0 ]; then echo "do-recon-pack: self-test PASS"; return 0; fi
187
+ echo "do-recon-pack: self-test FAIL ($fails)"; return 1
188
+ }
189
+
190
+ case "${1:-}" in
191
+ --self-test) _self_test; exit $? ;;
192
+ ""|-h|--help) echo "usage: do-recon-pack.sh <slug> | --files <path>... | --self-test" >&2; exit 2 ;;
193
+ esac
194
+
195
+ out="$(_pack "$@")" || exit 1
196
+ printf '%s\n' "$out"
@@ -47,6 +47,36 @@ _sanitize() {
47
47
  | _strip_allow
48
48
  }
49
49
 
50
+ # Read stdin with a wall clock. macOS ships no `timeout(1)` (the same absence the
51
+ # gate governor had to work around), so the bound is built from `read -t`.
52
+ #
53
+ # NOT with a backgrounded `cat`: the first attempt did that and read NOTHING,
54
+ # because a job backgrounded by a non-interactive shell gets /dev/null for stdin.
55
+ # The checker then reported OK on input containing a dead name -- a green gate
56
+ # over a red input, which is worse than the hang it was fixing. Caught by the red
57
+ # proof (`echo 'colony trail' | do-reconcile.sh dictionary` must FAIL); the
58
+ # original caught 'trail', the rewrite did not.
59
+ #
60
+ # `read -t` distinguishes the two endings that matter: exit >128 is the timeout,
61
+ # exit 1 is a clean EOF. Real pipes hit EOF in milliseconds.
62
+ _read_stdin_bounded() {
63
+ local secs="${RECONCILE_STDIN_TIMEOUT:-10}" line out="" st rc=0
64
+ while true; do
65
+ if IFS= read -r -t "$secs" line; then
66
+ out="$out$line
67
+ "
68
+ else
69
+ st=$?
70
+ # a final unterminated line still carries content
71
+ [ -n "$line" ] && out="$out$line"
72
+ [ "$st" -gt 128 ] && rc=1
73
+ break
74
+ fi
75
+ done
76
+ printf '%s' "$out"
77
+ return "$rc"
78
+ }
79
+
50
80
  _load_text() {
51
81
  local text=""
52
82
  if [ "$#" -gt 0 ]; then
@@ -56,7 +86,32 @@ _load_text() {
56
86
  if [ -f "$a" ]; then text="$text $(_sanitize < "$a")"; else text="$text $(printf '%s' "$a" | _sanitize)"; fi
57
87
  done
58
88
  else
59
- text="$(_sanitize)" # stdin fallback (matches original do-reconcile.sh behaviour)
89
+ # STDIN FALLBACK -- bounded, because an unbounded one hangs the loop.
90
+ #
91
+ # Measured 2026-09-01: `do-reconcile.sh dictionary` with no file argument and
92
+ # an inherited-but-idle stdin blocked for over NINE MINUTES before it was
93
+ # killed. It is not slow; it is waiting for an EOF that never comes. And
94
+ # do.md's own toolbox documents exactly that argument-less form
95
+ # ("do-reconcile.sh dictionary # no dead name, no new synonym"), so an agent
96
+ # copying the documented invocation stalls its whole cycle until do-auto's
97
+ # hard cap reaps it -- which reads as a mysteriously slow wave, not as a hang.
98
+ #
99
+ # Two guards. A terminal stdin can never deliver piped input, so that is a
100
+ # usage error, immediately. Anything else is read under a wall clock: real
101
+ # pipes (`git diff --name-only | do-reconcile.sh types`) close in
102
+ # milliseconds, so the bound only ever fires on the pathological case.
103
+ if [ -t 0 ]; then
104
+ printf 'do-reconcile: %s needs a file argument, or input on stdin.\n' "$CANON" >&2
105
+ printf ' do-reconcile.sh %s <file>...\n git diff --name-only | do-reconcile.sh %s\n' "$CANON" "$CANON" >&2
106
+ exit 2
107
+ fi
108
+ local _raw=""
109
+ if ! _raw="$(_read_stdin_bounded)"; then
110
+ printf 'do-reconcile: %s timed out waiting on stdin after %ss — no file argument was given.\n' \
111
+ "$CANON" "${RECONCILE_STDIN_TIMEOUT:-10}" >&2
112
+ exit 2
113
+ fi
114
+ text="$(printf '%s' "$_raw" | _sanitize)"
60
115
  fi
61
116
  printf '%s' "$text"
62
117
  }
@@ -247,9 +302,71 @@ case "$CANON" in
247
302
 
248
303
  current=0
249
304
  if [ -d "$folder" ] && [ -f "$folder/tsconfig.json" ]; then
250
- _tsc_tmp=$(mktemp)
251
- ( cd "$folder" && bunx tsc --noEmit 2>&1 | grep -c 'error TS' > "$_tsc_tmp" ) 2>/dev/null || true
252
- current=$(cat "$_tsc_tmp" 2>/dev/null | tr -d '[:space:]'); rm -f "$_tsc_tmp"
305
+ # ── Governed tsc (2026-08-18) ──────────────────────────────────────
306
+ # Six concurrent Claude sessions ran this canon at once, each spawning
307
+ # its own `tsc --noEmit` over the SAME tree for the SAME answer: load 57
308
+ # on 10 cores, swap exhausted, ~26 MB/s swapins. Starved, each tsc took
309
+ # 18 min instead of 2, sessions timed out, and retries piled on more.
310
+ # Fix: one tsc per (folder, tree-fingerprint); everyone else reuses its
311
+ # result. Identical work collapses instead of multiplying.
312
+ . "$(dirname "${BASH_SOURCE[0]}")/lib/govern.sh"
313
+
314
+ # Fingerprint the tree: same fingerprint => same tsc answer.
315
+ #
316
+ # ONE derivation, shared with tsc-cached.sh: tsc_tree_fingerprint in
317
+ # lib/govern.sh hashes the CONTENT of everything tsc reads (the folder's
318
+ # .ts/.tsx/.json + lockfile, tracked or not, plus packages/sdk/src for the
319
+ # folders that resolve @oneie/sdk types). It used to be an mtime stat
320
+ # here AND a second copy in tsc-cached.sh — two derivations of one key is
321
+ # how the memo split, and mtime is why it never shared across worktrees.
322
+ _fp_extra=""
323
+ case "$folder" in one.ie/web|channels) _fp_extra="packages/sdk/src" ;; esac
324
+ _fp=$(tsc_tree_fingerprint "$folder" $_fp_extra)
325
+ _cache="$GOVERN_DIR/tsc-$(printf '%s' "$folder" | tr '/' '_').$_fp"
326
+
327
+ if [ -s "$_cache" ]; then
328
+ current=$(cat "$_cache" | tr -d '[:space:]') # someone already computed it
329
+ elif gate_lock "tsc-$(printf '%s' "$folder" | tr '/' '_')" "${RECONCILE_TSC_WAIT:-900}"; then
330
+ trap 'gate_release_all' EXIT INT TERM
331
+ if [ -s "$_cache" ]; then
332
+ current=$(cat "$_cache" | tr -d '[:space:]') # computed while we queued
333
+ else
334
+ _tsc_tmp=$(mktemp)
335
+ # THE SLOT, not just the lock — same defect as tsc-cached.sh, same fix.
336
+ # The gate_lock above dedupes this folder against other reconciles; it
337
+ # does nothing about N worktrees typechecking N different folders at
338
+ # once. See tsc-cached.sh for the 2026-09-07 measurement.
339
+ # Resolve gate-run from THIS script's own directory. An earlier
340
+ # version used "$ROOT/..." -- ROOT is never set in this file, and under
341
+ # `set -u` that is a hard "unbound variable" that killed the whole
342
+ # `types` canon. Caught by tests/integration/reconcile.test.ts, which is
343
+ # the reason that test exists.
344
+ _gr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/gate-run.sh"
345
+ GOVERN_GATE_TIMEOUT="${RECONCILE_TSC_TIMEOUT:-600}" \
346
+ bash "$_gr" "tsc-reconcile:$folder" -- \
347
+ bash -c "cd '$folder' && bunx tsc --noEmit 2>&1 | grep -c 'error TS' > '$_tsc_tmp'" \
348
+ || true
349
+ current=$(cat "$_tsc_tmp" 2>/dev/null | tr -d '[:space:]'); rm -f "$_tsc_tmp"
350
+ current=${current:-0}
351
+ printf '%s' "$current" > "$_cache"
352
+ fi
353
+ gate_release_all; trap - EXIT INT TERM
354
+ else
355
+ # Lock held for the full wait. Re-check the cache once — the holder may
356
+ # have finished and released between our last poll and now.
357
+ if [ -s "$_cache" ]; then
358
+ current=$(cat "$_cache" | tr -d '[:space:]')
359
+ else
360
+ # Never exit 0 without an answer: a gate that passes because it was
361
+ # busy is a fail-open, which is worse than the load it avoids.
362
+ echo "RECONCILE[types]: lock held ${RECONCILE_TSC_WAIT:-900}s — checking anyway" >&2
363
+ _tsc_tmp=$(mktemp)
364
+ run_bounded "${RECONCILE_TSC_TIMEOUT:-600}" \
365
+ bash -c "cd '$folder' && bunx tsc --noEmit 2>&1 | grep -c 'error TS' > '$_tsc_tmp'" \
366
+ 2>/dev/null || true
367
+ current=$(cat "$_tsc_tmp" 2>/dev/null | tr -d '[:space:]'); rm -f "$_tsc_tmp"
368
+ fi
369
+ fi
253
370
  current=${current:-0}
254
371
  fi
255
372