@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,173 @@
1
+ #!/usr/bin/env bash
2
+ # env-sync.sh — localhost bun dev from the dev worktree, on production data.
3
+ #
4
+ # bash .claude/scripts/env-sync.sh # D1 (if idle) + astro
5
+ # bash .claude/scripts/env-sync.sh --astro-only # start :4321, leave D1 alone
6
+ # bash .claude/scripts/env-sync.sh --d1-only # prod → local mirror, no astro
7
+ #
8
+ # Geography:
9
+ # Code — env-sync-local worktree if it exists, else $ROOT/.dev-stable
10
+ # Data — $ROOT/one.ie/web/.wrangler (prod D1/KV/R2 mirror; worktree links it)
11
+ # Bind — 0.0.0.0:4321 so both http://127.0.0.1:4321 and http://localhost:4321 work
12
+ #
13
+ # Does not kill 8787–8799. Does not start a second `wrangler d1 export`.
14
+ # Does not `git reset --hard` a fleet branch — only the detached .dev-stable tree.
15
+ set -euo pipefail
16
+
17
+ ASTRO=1 D1=1
18
+ for a in "$@"; do
19
+ case "$a" in
20
+ --astro-only) D1=0 ;;
21
+ --d1-only) ASTRO=0 ;;
22
+ -h|--help) sed -n '2,16p' "$0"; exit 0 ;;
23
+ -*) echo "env-sync.sh: unknown flag $a" >&2; exit 2 ;;
24
+ esac
25
+ done
26
+
27
+ here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28
+ # Shared checkout, even when this file is invoked from a linked worktree copy.
29
+ git_common="$(cd "$here/../.." && git rev-parse --git-common-dir)"
30
+ [[ "$git_common" == /* ]] || git_common="$(cd "$here/../.." && cd "$git_common" && pwd)"
31
+ ROOT="$(cd "$git_common/.." && pwd)"
32
+ # Prefer the env-sync-local worktree while that fleet is alive (has the `ai`
33
+ # pin Vite needs when node_modules is a symlink). Else the stable detached tree.
34
+ if [[ -z "${DEV_WT:-}" ]]; then
35
+ if [[ -e "$ROOT/.claude/worktrees/env-sync-local/.git" ]]; then
36
+ DEV_WT="$ROOT/.claude/worktrees/env-sync-local"
37
+ else
38
+ DEV_WT="$ROOT/.dev-stable"
39
+ fi
40
+ fi
41
+ WEB_MAIN="$ROOT/one.ie/web"
42
+ WEB_DEV="$DEV_WT/one.ie/web"
43
+ HOST="${ASTRO_HOST:-0.0.0.0}"
44
+ PORT="${ASTRO_PORT:-4321}"
45
+
46
+ say() { printf '%s\n' "$*"; }
47
+ die() { printf 'env-sync.sh: %s\n' "$*" >&2; exit 1; }
48
+
49
+ link_file() {
50
+ local src="$1" dest="$2"
51
+ [[ -f "$src" ]] || return 0
52
+ if [[ -L "$dest" ]]; then
53
+ local now; now="$(readlink "$dest")"
54
+ [[ "$now" == "$src" ]] && return 0
55
+ rm -f "$dest"
56
+ elif [[ -e "$dest" ]]; then
57
+ return 0
58
+ fi
59
+ ln -s "$src" "$dest"
60
+ say " linked ${dest#"$DEV_WT/"} → main"
61
+ }
62
+
63
+ link_dir() {
64
+ local src="$1" dest="$2"
65
+ [[ -d "$src" ]] || return 0
66
+ if [[ -L "$dest" ]]; then
67
+ local now; now="$(readlink "$dest")"
68
+ [[ "$now" == "$src" ]] && return 0
69
+ rm -f "$dest"
70
+ elif [[ -d "$dest" ]]; then
71
+ mv "$dest" "${dest}.unlinked.$$"
72
+ say " moved ${dest#"$DEV_WT/"} aside (not production data)"
73
+ fi
74
+ ln -s "$src" "$dest"
75
+ say " linked ${dest#"$DEV_WT/"} → main"
76
+ }
77
+
78
+ # --- 0. worktree: production data via links ----------------------------------
79
+ ensure_dev_worktree() {
80
+ git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null \
81
+ || die "not a git repo: $ROOT"
82
+ local head; head="$(git -C "$ROOT" rev-parse HEAD)"
83
+
84
+ if [[ ! -d "$DEV_WT/.git" && ! -f "$DEV_WT/.git" ]]; then
85
+ say "creating dev worktree $DEV_WT @ $head"
86
+ git -C "$ROOT" worktree add --detach "$DEV_WT" "$head"
87
+ fi
88
+
89
+ local here_sha; here_sha="$(git -C "$DEV_WT" rev-parse HEAD)"
90
+ if [[ "$DEV_WT" == "$ROOT/.dev-stable" && "$here_sha" != "$head" ]]; then
91
+ say "fast-forward $DEV_WT $here_sha → $head"
92
+ git -C "$DEV_WT" reset --hard "$head"
93
+ else
94
+ say "dev worktree $DEV_WT @ $here_sha"
95
+ fi
96
+
97
+ mkdir -p "$WEB_DEV"
98
+ link_dir "$WEB_MAIN/.wrangler" "$WEB_DEV/.wrangler"
99
+ link_dir "$WEB_MAIN/node_modules" "$WEB_DEV/node_modules"
100
+ link_dir "$ROOT/packages/sdk/node_modules" "$DEV_WT/packages/sdk/node_modules"
101
+ link_file "$WEB_MAIN/.env" "$WEB_DEV/.env"
102
+ link_file "$WEB_MAIN/.dev.vars" "$WEB_DEV/.dev.vars"
103
+ if [[ -f "$WEB_MAIN/src/data/fleets.json" ]]; then
104
+ link_file "$WEB_MAIN/src/data/fleets.json" "$WEB_DEV/src/data/fleets.json"
105
+ fi
106
+ }
107
+
108
+ # --- 1. D1: one export at a time ---------------------------------------------
109
+ sync_d1() {
110
+ local pids
111
+ pids="$(pgrep -f 'wrangler d1 export one-owners --remote' || true)"
112
+ if [[ -n "$pids" ]]; then
113
+ say "D1 export already running pid=$pids — not starting a second"
114
+ return 0
115
+ fi
116
+ say "syncing production D1 → $WEB_MAIN/.wrangler"
117
+ ( cd "$WEB_MAIN" && bun run db:sync )
118
+ }
119
+
120
+ # --- 2. astro on 4321 from the worktree --------------------------------------
121
+ http_code() {
122
+ curl -sS -o /dev/null -w '%{http_code}' --max-time 3 "$1" 2>/dev/null || echo 000
123
+ }
124
+
125
+ kill_port() {
126
+ local pids
127
+ pids="$(lsof -tiTCP:"$PORT" -sTCP:LISTEN 2>/dev/null || true)"
128
+ [[ -n "$pids" ]] || return 0
129
+ say "killing listeners on $PORT: $pids"
130
+ # shellcheck disable=SC2086
131
+ kill $pids 2>/dev/null || true
132
+ sleep 1
133
+ pids="$(lsof -tiTCP:"$PORT" -sTCP:LISTEN 2>/dev/null || true)"
134
+ [[ -z "$pids" ]] || kill -9 $pids 2>/dev/null || true
135
+ }
136
+
137
+ start_astro() {
138
+ local url_v4="http://127.0.0.1:$PORT"
139
+ if [[ "$(http_code "$url_v4/")" == 200 ]]; then
140
+ say "already 200 $url_v4/"
141
+ return 0
142
+ fi
143
+ kill_port
144
+ mkdir -p "$WEB_DEV/.dev"
145
+ say "starting bun run dev in $WEB_DEV (--host $HOST --port $PORT)"
146
+ (
147
+ cd "$WEB_DEV"
148
+ bun run dev -- --host "$HOST" --port "$PORT"
149
+ ) >"$WEB_DEV/.dev/astro.log" 2>&1 &
150
+ echo $! >"$WEB_DEV/.dev/astro.pid"
151
+ say " pid=$(cat "$WEB_DEV/.dev/astro.pid") log=$WEB_DEV/.dev/astro.log"
152
+
153
+ local i code_root code_voice
154
+ for i in $(seq 1 90); do
155
+ code_root="$(http_code "$url_v4/")"
156
+ code_voice="$(http_code "$url_v4/demo/voice")"
157
+ if [[ "$code_root" == 200 && "$code_voice" == 200 ]]; then
158
+ say " ✓ $url_v4/ $code_root"
159
+ say " ✓ $url_v4/demo/voice $code_voice"
160
+ say " ✓ http://localhost:$PORT/ (same process; bound on $HOST)"
161
+ return 0
162
+ fi
163
+ sleep 2
164
+ done
165
+ say "last 40 lines of $WEB_DEV/.dev/astro.log:"
166
+ tail -n 40 "$WEB_DEV/.dev/astro.log" || true
167
+ die "astro did not reach 200 on $url_v4/ and /demo/voice in 180s (got /=$code_root voice=$code_voice)"
168
+ }
169
+
170
+ ensure_dev_worktree
171
+ (( D1 )) && sync_d1
172
+ (( ASTRO )) && start_astro
173
+ say "done. code=$DEV_WT data=$WEB_MAIN/.wrangler"
@@ -0,0 +1,330 @@
1
+ #!/usr/bin/env bash
2
+ # factory-brief-check.sh — pre-flight a factory brief for DEFERRED DECISIONS.
3
+ #
4
+ # manifest: portable
5
+ # Reads no credential and names no monorepo path — every rule is a literal
6
+ # in this file and the brief arrives as an argument or on stdin. The red
7
+ # half of --self-test reads fixtures/factory-brief-real.md, which ships
8
+ # beside it; absent, the self-test goes RED rather than skipping.
9
+ #
10
+ # WHY THIS EXISTS. A brief that hands the doer a decision instead of a decision
11
+ # already made costs a whole run. The first factory run spent ~46 minutes and
12
+ # ended in a refutation because one parenthesis said "verify which is right" —
13
+ # the launching session had two candidate reads of the workspace and shipped
14
+ # both. The doer is the wrong place to settle that: it has no board, no
15
+ # neighbours, and no cheap way to be wrong. This gate reads the brief BEFORE a
16
+ # worktree, a port, or a model call is spent, and refuses.
17
+ #
18
+ # Usage:
19
+ # factory-brief-check.sh <brief-file> check a brief file
20
+ # factory-brief-check.sh - check a brief on stdin
21
+ # cat brief.md | factory-brief-check.sh same
22
+ # factory-brief-check.sh --self-test prove the gate goes RED and GREEN
23
+ #
24
+ # Exit: 0 = brief is launchable · 1 = RED (or self-test failed) · 2 = usage.
25
+ #
26
+ # Env:
27
+ # FACTORY_BRIEF_FIXTURE override the --self-test red fixture (default:
28
+ # fixtures/factory-brief-real.md beside this script)
29
+
30
+ set -uo pipefail
31
+
32
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # The rules. Each is a NAMED pattern, matched case-insensitively, one per line
36
+ # of the brief. A rule fires when the brief leaves a decision for the doer.
37
+ #
38
+ # Third field is the input the pattern is matched against: `line` is the raw
39
+ # line, `stripped` is the line with every file-path token removed first.
40
+ #
41
+ # Deliberate deviation, documented: `tbd` and `todo` match against `stripped`,
42
+ # so `text/template-todo.md` and `<slug>-todo.md` — which nearly every real
43
+ # brief in this repo cites — are NOT hits, while a bare `TBD.` at the end of a
44
+ # sentence still is. A gate that goes red on the name of a file it expects you
45
+ # to name is decoration, and gets disabled; a gate that misses `TBD.` because
46
+ # of the full stop is worse.
47
+ # ---------------------------------------------------------------------------
48
+ RULES=(
49
+ 'verify-which|verify[[:space:]]+which|line'
50
+ 'check-whether|check[[:space:]]+whether|line'
51
+ 'tbd|(^|[^-_/[:alnum:]])tbd([^-_[:alnum:]]|$)|stripped'
52
+ 'todo|(^|[^-_/[:alnum:]])todo([^-_[:alnum:]]|$)|stripped'
53
+ 'decide-later|decide[[:space:]]+later|line'
54
+ 'or-alternatively|or[[:space:]]+alternatively|line'
55
+ 'either-or|(^|[^[:alnum:]])either[^[:alnum:]].*[[:space:]]or[[:space:]]|line'
56
+ 'trailing-question|\?[[:space:]]*$|line'
57
+ 'if-possible|if[[:space:]]+possible|line'
58
+ 'ideally|(^|[^[:alnum:]])ideally([^[:alnum:]]|$)|line'
59
+ )
60
+
61
+ # Rule 'code-span-options': two options joined by ` ?? ` or ` or ` INSIDE a code
62
+ # span. `a ?? b` in prose is a decision the doer has to make; the same string in
63
+ # shipped code is not, so only backtick spans count. Returns 0 when the line has
64
+ # one.
65
+ code_span_options() {
66
+ awk '
67
+ {
68
+ n = split($0, part, "`")
69
+ # odd-indexed parts (2,4,…) are inside a span
70
+ for (i = 2; i <= n; i += 2) {
71
+ s = part[i]
72
+ if (s ~ /[^[:space:]][[:space:]]+\?\?[[:space:]]+[^[:space:]]/) { found = 1 }
73
+ if (s ~ /[^[:space:]][[:space:]]+or[[:space:]]+[^[:space:]]/) { found = 1 }
74
+ }
75
+ }
76
+ END { exit !found }
77
+ ' <<< "$1"
78
+ }
79
+
80
+ # A file path token. The spec class is lowercase; this adds A-Z so the count and
81
+ # any listing read correctly (`ComponentBrowser.tsx`, not `omponentBrowser.tsx`).
82
+ # Presence is identical either way.
83
+ PATH_RE='[A-Za-z0-9_./-]+\.(tsx|astro|ts|sh|md|mjs|js)([^A-Za-z0-9]|$)'
84
+
85
+ # A 'Done' section: a line that STARTS with the word done, optionally behind a
86
+ # markdown heading or bold marker. "DONE LOOKS LIKE", "## Done", "**Done**".
87
+ DONE_RE='^[[:space:]]*(#+[[:space:]]*)?(\*\*)?[[:space:]]*done([^[:alnum:]]|$)'
88
+
89
+ check_brief() {
90
+ local text="$1" label="${2:-brief}"
91
+ local hits=0 line rule name pat
92
+
93
+ if [ -z "${text//[[:space:]]/}" ]; then
94
+ printf 'BRIEF RED: empty-brief — "<%s is empty — a brief that says nothing defers everything>"\n' "$label"
95
+ return 1
96
+ fi
97
+
98
+ local rest src stripped
99
+ while IFS= read -r line; do
100
+ [ -z "${line//[[:space:]]/}" ] && continue
101
+ stripped="$(sed -E "s#$PATH_RE# #g" <<< "$line")"
102
+ for rule in ${RULES[@]+"${RULES[@]}"}; do
103
+ name="${rule%%|*}"; rest="${rule#*|}"
104
+ pat="${rest%|*}"; src="${rest##*|}"
105
+ if grep -qiE "$pat" <<< "$([ "$src" = stripped ] && printf '%s' "$stripped" || printf '%s' "$line")"; then
106
+ printf 'BRIEF RED: %s — "%s"\n' "$name" "$line"
107
+ hits=$((hits + 1))
108
+ fi
109
+ done
110
+ if code_span_options "$line"; then
111
+ printf 'BRIEF RED: code-span-options — "%s"\n' "$line"
112
+ hits=$((hits + 1))
113
+ fi
114
+ done <<< "$text"
115
+
116
+ local files
117
+ files="$(grep -oE "$PATH_RE" <<< "$text" | sed -E 's/[^A-Za-z0-9]$//' | sort -u)"
118
+ local nfiles=0
119
+ [ -n "$files" ] && nfiles="$(wc -l <<< "$files" | tr -d ' ')"
120
+
121
+ if [ "$nfiles" -eq 0 ]; then
122
+ printf 'BRIEF RED: no-file-path — "<%s names no file the doer can open>"\n' "$label"
123
+ hits=$((hits + 1))
124
+ fi
125
+
126
+ if ! grep -qiE "$DONE_RE" <<< "$text"; then
127
+ printf 'BRIEF RED: no-done-section — "<%s has no Done section — nothing says when to stop>"\n' "$label"
128
+ hits=$((hits + 1))
129
+ fi
130
+
131
+ if [ "$hits" -gt 0 ]; then
132
+ return 1
133
+ fi
134
+ printf 'BRIEF OK: %s files named, 0 deferred decisions\n' "$nfiles"
135
+ return 0
136
+ }
137
+
138
+ # ---------------------------------------------------------------------------
139
+ # --self-test — the red proof. Four cases; the red half runs against the ACTUAL
140
+ # brief that produced the first run's refutation, copied verbatim, not retyped.
141
+ # ---------------------------------------------------------------------------
142
+ self_test() {
143
+ local fixture="${FACTORY_BRIEF_FIXTURE:-$HERE/fixtures/factory-brief-real.md}"
144
+ local tmp out rc bad=0 cases=0
145
+
146
+ if [ ! -f "$fixture" ]; then
147
+ printf 'SELF-TEST RED: the red fixture is missing at %s — the red half cannot be run, so nothing here is proven. Refusing to pass.\n' "$fixture" >&2
148
+ printf 'SELF-TEST RED: 0 cases — fixture absent\n'
149
+ return 1
150
+ fi
151
+
152
+ # (a) the real brief MUST go red, and MUST name verify-which on the real line.
153
+ cases=$((cases + 1))
154
+ out="$(check_brief "$(cat "$fixture")" "$fixture")"; rc=$?
155
+ if [ $rc -eq 0 ]; then
156
+ printf ' FAIL (a) the real brief passed — the gate is blind\n' >&2; bad=1
157
+ elif ! grep -q '^BRIEF RED: verify-which — ' <<< "$out"; then
158
+ printf ' FAIL (a) the real brief went red, but not on verify-which:\n%s\n' "$out" >&2; bad=1
159
+ elif ! grep -qi 'verify which is right' <<< "$(grep '^BRIEF RED: verify-which — ' <<< "$out")"; then
160
+ printf ' FAIL (a) verify-which fired on the wrong line:\n%s\n' "$(grep '^BRIEF RED: verify-which' <<< "$out")" >&2; bad=1
161
+ else
162
+ printf ' ok (a) the real brief is RED on verify-which, quoting "verify which is right"\n'
163
+ fi
164
+
165
+ tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' RETURN
166
+
167
+ # (b) a clean brief MUST pass.
168
+ cat > "$tmp/clean.md" <<'CLEAN'
169
+ BUILD: the picker gate on the component gallery.
170
+
171
+ WHAT TO BUILD
172
+ Add an "Add to page" control to the preview footer in
173
+ src/components/puck/ComponentBrowser.tsx. It lists the workspace pages, takes
174
+ one, appends the block, then offers two exits.
175
+
176
+ THE DECISION, ALREADY MADE
177
+ Read the workspace slug server-side from Astro.locals.slug in
178
+ src/pages/components.astro and pass it down as an optional prop. There is no
179
+ session, so there is no slug, so the control does not render.
180
+
181
+ TEST
182
+ Extend tests/unit/puck/add-to-page.test.tsx with one case that names the wrong
183
+ result: a missing slug renders the control anyway.
184
+
185
+ DONE LOOKS LIKE
186
+ 1. The control renders signed in and is absent signed out.
187
+ 2. The block lands at the end of the picked page.
188
+ 3. bash .claude/scripts/gate-run.sh doer -- bunx vitest related --run is green.
189
+ 4. node .claude/scripts/wf-check.mjs is green.
190
+ CLEAN
191
+ cases=$((cases + 1))
192
+ out="$(check_brief "$(cat "$tmp/clean.md")" "$tmp/clean.md")"; rc=$?
193
+ if [ $rc -ne 0 ]; then
194
+ printf ' FAIL (b) the clean brief went red — the gate cries wolf:\n%s\n' "$out" >&2; bad=1
195
+ elif ! grep -q '^BRIEF OK: ' <<< "$out"; then
196
+ printf ' FAIL (b) the clean brief exited 0 without printing BRIEF OK\n' >&2; bad=1
197
+ else
198
+ printf ' ok (b) the clean brief is GREEN — %s\n' "$out"
199
+ fi
200
+
201
+ # (c) a brief with no file path MUST go red, even with nothing deferred.
202
+ cat > "$tmp/nopath.md" <<'NOPATH'
203
+ BUILD: make the gallery let you put a block on a page.
204
+
205
+ DONE LOOKS LIKE
206
+ 1. It works signed in and is absent signed out.
207
+ NOPATH
208
+ cases=$((cases + 1))
209
+ out="$(check_brief "$(cat "$tmp/nopath.md")" "$tmp/nopath.md")"; rc=$?
210
+ if [ $rc -eq 0 ] || ! grep -q '^BRIEF RED: no-file-path' <<< "$out"; then
211
+ printf ' FAIL (c) a brief naming no file passed:\n%s\n' "$out" >&2; bad=1
212
+ else
213
+ printf ' ok (c) a brief naming no file is RED on no-file-path\n'
214
+ fi
215
+
216
+ # (d) a brief with no Done section MUST go red.
217
+ cat > "$tmp/nodone.md" <<'NODONE'
218
+ BUILD: the picker gate.
219
+ Edit src/components/puck/ComponentBrowser.tsx and read the slug server-side in
220
+ src/pages/components.astro. Extend tests/unit/puck/add-to-page.test.tsx.
221
+ NODONE
222
+ cases=$((cases + 1))
223
+ out="$(check_brief "$(cat "$tmp/nodone.md")" "$tmp/nodone.md")"; rc=$?
224
+ if [ $rc -eq 0 ] || ! grep -q '^BRIEF RED: no-done-section' <<< "$out"; then
225
+ printf ' FAIL (d) a brief with no Done section passed:\n%s\n' "$out" >&2; bad=1
226
+ else
227
+ printf ' ok (d) a brief with no Done section is RED on no-done-section\n'
228
+ fi
229
+
230
+ # (e) EVERY named rule must fire. A declared rule that no case exercises is a
231
+ # rule that can ship broken and pass a brief it exists to refuse — one line
232
+ # per rule, all eleven, and the count asserted so a line tripping two rules
233
+ # (or none) is caught rather than averaged away.
234
+ cat > "$tmp/allrules.md" <<'ALLRULES'
235
+ BUILD: one line per deferred decision, in src/lib/one.ts.
236
+ Verify which read is right.
237
+ Check whether the snapshot is warm.
238
+ The signal name is TBD.
239
+ Leave a TODO where the gate goes.
240
+ Name the signal now and decide later on the payload.
241
+ Append the block, or alternatively insert it at the top.
242
+ Use either the snapshot or a live read.
243
+ Should the picker show archived pages?
244
+ Reuse the existing helper if possible.
245
+ Ideally the control renders in the footer.
246
+ Set the base to `4321 or 4325` first.
247
+ DONE LOOKS LIKE
248
+ 1. It renders.
249
+ ALLRULES
250
+ cases=$((cases + 1))
251
+ out="$(check_brief "$(cat "$tmp/allrules.md")" "$tmp/allrules.md")"; rc=$?
252
+ local missing="" r n
253
+ for r in verify-which check-whether tbd todo decide-later or-alternatively \
254
+ either-or trailing-question if-possible ideally code-span-options; do
255
+ grep -q "^BRIEF RED: $r — " <<< "$out" || missing="$missing $r"
256
+ done
257
+ n="$(grep -c '^BRIEF RED: ' <<< "$out" | tr -d ' ')"
258
+ if [ -n "$missing" ]; then
259
+ printf ' FAIL (e) rules declared but never fired:%s\n%s\n' "$missing" "$out" >&2; bad=1
260
+ elif [ "$n" != 11 ]; then
261
+ printf ' FAIL (e) expected 11 hits, one per rule, got %s:\n%s\n' "$n" "$out" >&2; bad=1
262
+ else
263
+ printf ' ok (e) all 11 named rules fire — 11 hits, one per rule, no structural hit\n'
264
+ fi
265
+
266
+ # (f) THE RED PROOF ITSELF. Gut this script — empty the RULES array, keep
267
+ # everything else — and case (a) MUST flip: the real brief that cost ~46
268
+ # minutes now passes. A checker that stays green against its own gutted copy
269
+ # proves nothing, which is the failure this whole script exists to refuse.
270
+ #
271
+ # Gut by STRUCTURE, never by line number, so an edit above cannot quietly turn
272
+ # this into a no-op. Two things were measured before this assertion was
273
+ # written, and both are why it reads the way it does:
274
+ # - `"${RULES[@]}"` on an EMPTY array is an unbound variable under `set -u`
275
+ # in bash 3.2, so the gutted copy used to die with a bash error and exit 1.
276
+ # "exit non-zero" would have looked like proof and measured the crash. The
277
+ # expansion in check_brief is empty-safe for exactly this reason.
278
+ # - code_span_options lives OUTSIDE Rules and still runs in the gutted copy;
279
+ # the real fixture happens to trip none of its spans, so the gutted copy
280
+ # reaches BRIEF OK. Both halves are asserted separately so a future change
281
+ # to either one names itself instead of averaging away.
282
+ cases=$((cases + 1))
283
+ awk '/^RULES=\(/{print "RULES=()"; s=1; next} s&&/^\)/{s=0; next} !s' \
284
+ "${BASH_SOURCE[0]}" > "$tmp/gutted.sh"
285
+ if ! grep -q '^RULES=()$' "$tmp/gutted.sh"; then
286
+ printf ' FAIL (f) the gutting did not take — no empty RULES in the copy\n' >&2; bad=1
287
+ elif ! bash -n "$tmp/gutted.sh" 2>/dev/null; then
288
+ printf ' FAIL (f) the gutted copy does not parse — the proof measures a syntax error, not the rules\n' >&2; bad=1
289
+ else
290
+ out="$(bash "$tmp/gutted.sh" "$fixture" 2>&1)"; rc=$?
291
+ if grep -q '^BRIEF RED: verify-which' <<< "$out"; then
292
+ printf ' FAIL (f) the gutted copy STILL refused the brief on verify-which — the RULES array is not load-bearing:\n%s\n' "$out" >&2; bad=1
293
+ elif [ $rc -ne 0 ]; then
294
+ printf ' FAIL (f) the gutted copy stopped the brief for some OTHER reason (rc=%s) — the red half of (a) is not attributable to the rules:\n%s\n' "$rc" "$out" >&2; bad=1
295
+ else
296
+ printf ' ok (f) rules emptied ⇒ the real brief PASSES (%s) — case (a) is attributable to the rules, not to the structural checks\n' "$out"
297
+ fi
298
+ fi
299
+
300
+ if [ $bad -ne 0 ]; then
301
+ printf 'SELF-TEST RED: %s cases — the gate does not hold\n' "$cases"
302
+ return 1
303
+ fi
304
+ printf 'SELF-TEST OK: %s cases — red half red, green half green\n' "$cases"
305
+ return 0
306
+ }
307
+
308
+ main() {
309
+ case "${1:-}" in
310
+ --self-test) self_test; exit $? ;;
311
+ -h|--help)
312
+ awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "${BASH_SOURCE[0]}"
313
+ exit 0 ;;
314
+ "")
315
+ if [ -t 0 ]; then
316
+ printf 'usage: factory-brief-check.sh <brief-file> | - | --self-test\n' >&2
317
+ exit 2
318
+ fi
319
+ check_brief "$(cat)" "stdin"; exit $? ;;
320
+ -) check_brief "$(cat)" "stdin"; exit $? ;;
321
+ *)
322
+ if [ ! -f "$1" ]; then
323
+ printf 'factory-brief-check.sh: no such brief file: %s\n' "$1" >&2
324
+ exit 2
325
+ fi
326
+ check_brief "$(cat "$1")" "$1"; exit $? ;;
327
+ esac
328
+ }
329
+
330
+ main "$@"
@@ -37,6 +37,23 @@
37
37
  # must not assume "broken" — see text/factory.md § A note on "red" vs "cannot
38
38
  # run". Never widen a `cannot run` into a `red` to make a chain simpler.
39
39
  set -uo pipefail
40
+
41
+ # _GATE — route a heavy compute through the machine governor. A gate_lock only
42
+ # dedupes IDENTICAL work; a SLOT is what bounds N worktrees each running one of
43
+ # these at once (measured 2026-09-07: three concurrent 2.5GB typecheckers, every
44
+ # lock uncontended, load 171). Empty when already inside a gate, so a nested call
45
+ # inherits the outer slot rather than taking a second one.
46
+ #
47
+ # Resolves gate-run.sh from its OWN directory, deliberately: an earlier version
48
+ # keyed off $ROOT and got inserted above the line that sets it, so _GATE was
49
+ # silently empty and every call ran ungoverned -- a fail-OPEN, which is the exact
50
+ # defect this preamble exists to close.
51
+ _GATE=()
52
+ if [ "${GOVERN_IN_GATE:-0}" != "1" ]; then
53
+ _GR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/gate-run.sh"
54
+ [ -f "$_GR" ] && _GATE=( bash "$_GR" "compute:$(basename "${BASH_SOURCE[0]}")" -- )
55
+ fi
56
+
40
57
  ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
41
58
  MODE="${1:-all}"
42
59
  VERBOSE=0; [[ "${2:-}" == "--verbose" ]] && VERBOSE=1
@@ -375,7 +392,7 @@ check_belief_guard() {
375
392
  TYPEDB_USERNAME="${TYPEDB_USERNAME:-}" \
376
393
  TYPEDB_PASSWORD="${TYPEDB_PASSWORD:-}" \
377
394
  TYPEDB_TEST_DB="${TYPEDB_TEST_DB:-}" \
378
- bunx vitest run src/lib/resolvers/learning-evidence.test.ts 2>&1)
395
+ "${_GATE[@]}" bunx vitest run src/lib/resolvers/learning-evidence.test.ts 2>&1)
379
396
  # The scratch db needs the schema loaded; the test defines none of its own. An
380
397
  # empty db answers "Type label 'hypothesis' not found", which is cannot-run.
381
398
  printf '%s' "$out" | grep -qE "Type label '(hypothesis|group|path)' not found" \
@@ -544,15 +561,36 @@ check_fn_one_allowlist() {
544
561
  # clean slate is the fail-open trap in visual form — the operator reads "no gaps"
545
562
  # when the truth is "nothing seeded". So the component must distinguish the two
546
563
  # and the check asserts the empty-state copy exists alongside the query wiring.
564
+ # NAMED FILES, never `ls | head -1`.
565
+ #
566
+ # This check and check_stream both used to glob components/factory/*.tsx and read
567
+ # whichever sorted FIRST. On 2026-09-01 `FactoryFlow.tsx` landed in that directory,
568
+ # sorted before `GapsBoard.tsx`, and silently stole both checks: view-gaps went RED
569
+ # against a canvas that was never the board, and stream went GREEN against the same
570
+ # canvas, because a play loop contains `setInterval` and the word `stop`. A check
571
+ # aimed at "whatever sorts first" is a check anyone can re-aim by adding a file.
572
+ #
573
+ # So each check names the file it means. Adding a component to that directory is now
574
+ # a free action; renaming or deleting one of THESE is what has to fail loudly.
575
+ #
576
+ # RE-AIMED 2026-09-04. Naming the file was the right fix and it was aimed at the
577
+ # wrong file: `IdeaBoard.tsx` was mounted by NO page — `factory.astro` mounts
578
+ # `FactoryFlow` and nothing else — so this check was green against a component a
579
+ # person could not open. Deleted with R3. The surface that ships is the canvas,
580
+ # and the empty-state copy lives in its `ideasNote`.
547
581
  check_view_gaps() {
548
- local c
549
- c=$(ls "$ROOT"/one.ie/web/src/components/factory/*.tsx 2>/dev/null | head -1)
550
- [ -n "$c" ] || return $(red "no one.ie/web/src/components/factory/ component — the gaps board does not exist")
551
- grep -qE "uncovered|ready-tasks|readyTasks" "$c" \
552
- || return $(red "gaps board does not query uncovered()/ready-tasks() it renders nothing real")
553
- grep -qiE "nothing seeded|no rows yet|not seeded|graph is empty" "$c" \
554
- || return $(red "gaps board has no distinct EMPTY state — zero rows would read as 'no gaps', which is the fail-open trap in visual form")
555
- say "gaps board queries the ladder and is honest when empty"; echo "ok view-gaps"
582
+ local board read
583
+ board="$ROOT/one.ie/web/src/components/factory/FactoryFlow.tsx"
584
+ read="$ROOT/one.ie/web/src/lib/factory/board-read.ts"
585
+ [ -f "$board" ] || return $(red "no FactoryFlow.tsx — /factory has no canvas to render")
586
+ [ -f "$read" ] || return $(red "no board-read.ts the board has nothing to read the ladder with")
587
+ grep -qE "derived-from|thing-type \"(deliverable|objective|task)\"" "$read" \
588
+ || return $(red "the board's read does not walk the ladder it renders nothing real")
589
+ grep -qiE "nothing is seeded yet|no rows yet|not seeded|graph is empty" "$board" \
590
+ || return $(red "the canvas has no distinct EMPTY state — zero rows would read as 'nothing missing', which is the fail-open trap in visual form")
591
+ grep -qiE "could not read the board" "$board" \
592
+ || return $(red "the canvas cannot say it failed to read — an outage would render as an empty board")
593
+ say "the board walks the ladder and is honest when empty"; echo "ok view-gaps"
556
594
  }
557
595
 
558
596
  # The templates are where every FUTURE plan inherits or repeats this session's
@@ -598,14 +636,30 @@ print(b)")
598
636
  # becomes the bottleneck — that is the failure mode of every "just add more
599
637
  # review" factory. Streaming + stop is parallel: it never blocks a cycle, and
600
638
  # it never lets one run unwatched either.
639
+ # Named, for the reason spelled out above check_view_gaps.
640
+ #
641
+ # RE-AIMED 2026-09-04, same defect as check_view_gaps and worse: this one was
642
+ # GREEN. `RunPanel.tsx` was mounted by no page, so "factory streams and can be
643
+ # stopped" was asserted about a file nobody could open while the canvas that
644
+ # ships had `grep -c 'do:halt\|workflow:stop'` = 0. Deleted with R3; the stop now
645
+ # lives in `RunTrack.tsx` (the panel FactoryFlow renders) over `stop-run.ts`.
646
+ #
647
+ # The three greps are now aimed at three DIFFERENT files on purpose: the stream
648
+ # is the hook, the control is the rendered panel, and the receiver is the
649
+ # resolver. A single file satisfying all three is what let a play loop pass as a
650
+ # stream control.
601
651
  check_stream() {
602
- local c
603
- c=$(ls "$ROOT"/one.ie/web/src/components/factory/*.tsx 2>/dev/null | head -1)
604
- [ -n "$c" ] || return $(red "no factory component — nothing streams")
605
- grep -qiE "EventSource|text/event-stream|useSWR|setInterval|poll" "$c" \
652
+ local panel hook
653
+ panel="$ROOT/one.ie/web/src/components/factory/RunTrack.tsx"
654
+ hook="$ROOT/one.ie/web/src/lib/use-workflow-stream.ts"
655
+ [ -f "$panel" ] || return $(red "no RunTrack.tsx — nothing shows what is running")
656
+ [ -f "$hook" ] || return $(red "no use-workflow-stream.ts — the panel has nothing to stream from")
657
+ grep -qiE "EventSource|text/event-stream" "$hook" \
606
658
  || return $(red "/factory does not stream — a static page cannot show what is being built RIGHT NOW")
607
- grep -qiE "stop|halt|abort" "$c" \
659
+ grep -qF "stopRun" "$panel" \
608
660
  || return $(red "/factory has no stop control — the operator can watch but not intervene, which is lights-off with a window")
661
+ grep -qiE "not permitted" "$ROOT/one.ie/web/src/lib/factory/stop-run.ts" 2>/dev/null \
662
+ || return $(red "the stop control does not render its refusal — a stop that silently no-ops for an anonymous reader tells the operator nothing")
609
663
  grep -rqE '"(do:halt|workflow:stop)"' "$ROOT/one.ie/web/src/lib/resolvers/" 2>/dev/null \
610
664
  || return $(red "the stop control is not wired to a receiver that actually halts a run")
611
665
  say "factory streams and can be stopped"; echo "ok stream"