@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,185 @@
1
+ #!/usr/bin/env bash
2
+ # tasks-loop.sh — the task board as a continuously-running loop.
3
+ #
4
+ # Each pass does three things, in this order:
5
+ # 0. REAP sweep stranded `picked` leases back to `open`. This is the half
6
+ # atomicity does NOT fix: a lease whose owner died is invisible to
7
+ # the claimable view and blocks every dependent via ready-tasks.
8
+ # It runs FIRST so this pass can claim what it just freed.
9
+ # 1. ACCEPTS re-run every seed row's `accept:` check. Green ⇒ the task is
10
+ # done in the tree; it is not filed (and is reported as settled).
11
+ # 2. SEED file every still-RED seed row that is not on the board yet,
12
+ # tagged with its BARE-WORD stake.
13
+ # 3. CLAIM lease up to --claims tasks this pass.
14
+ #
15
+ # SAFE-BY-DEFAULT: it prints what it would do and writes NOTHING unless --live.
16
+ # The board lives in PRODUCTION TypeDB (there is no separate task store — dev
17
+ # and prod share it), so a dry run is the honest default.
18
+ #
19
+ # bash .claude/scripts/tasks-loop.sh --accepts # just score the checks
20
+ # bash .claude/scripts/tasks-loop.sh --once # one pass, DRY
21
+ # bash .claude/scripts/tasks-loop.sh --once --live
22
+ # bash .claude/scripts/tasks-loop.sh --live # continuous
23
+ # bash .claude/scripts/tasks-loop.sh --status # read liveness, don't guess
24
+ # bash .claude/scripts/tasks-loop.sh --stop
25
+ #
26
+ # Liveness is a PUBLISHED FILE, never `ps | grep`. --status reads it.
27
+ #
28
+ # ── why this loop is safe to run beside other fleets ────────────────────────
29
+ # `tasks:claim` is ATOMIC as of 2026-08-26: one guarded TypeDB pipeline, one
30
+ # commit, and the loser of a race is refused at commit time with
31
+ # "[STC2] Commit ... failed with isolation conflict: Transaction uses a lock
32
+ # held by a concurrent commit."
33
+ # Proven both ways by `node .claude/scripts/tasks-claim-race.mjs` (which also
34
+ # demonstrates its own red half). So a second claimer here cannot double-lease.
35
+ # This loop is therefore NOT single-claimer-by-construction; it is safe because
36
+ # the seam underneath it was fixed. If that harness ever stops passing, drop
37
+ # CLAIMS_PER_PASS to 0 until it does again.
38
+ set -uo pipefail
39
+
40
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
41
+ SEED="${TASKS_SEED:-$ROOT/.claude/tasks/seed.tsv}"
42
+ STATE_DIR="${TMPDIR:-/tmp}/one-tasks-loop"
43
+ LIVE="$STATE_DIR/live.json"
44
+ LOG="$STATE_DIR/loop.log"
45
+ LOCK="$STATE_DIR/lock"
46
+ STOP="$STATE_DIR/stop"
47
+ FILED="$STATE_DIR/filed" # slugs already filed by this loop
48
+ INTERVAL="${TASKS_LOOP_INTERVAL:-300}"
49
+ CLAIMS_PER_PASS="${TASKS_LOOP_CLAIMS:-1}"
50
+ WS="${CC_TASKS_WORKSPACE:-one}"
51
+ DRY=true
52
+ mkdir -p "$STATE_DIR"; touch "$FILED"
53
+
54
+ now() { date -u +%Y-%m-%dT%H:%M:%SZ; }
55
+ log() { printf '[%s] %s\n' "$(now)" "$*" | tee -a "$LOG"; }
56
+
57
+ publish() { # publish(state, detail, red, green, filed, claimed)
58
+ cat > "$LIVE" <<JSON
59
+ {"state":"$1","pid":$$,"detail":"$2","red":$3,"green":$4,"filed":$5,"claimed":$6,"dry":$DRY,"workspace":"$WS","at":"$(now)","interval":$INTERVAL,"log":"$LOG"}
60
+ JSON
61
+ }
62
+
63
+ # rows() — the seed, comments and blanks stripped.
64
+ rows() { grep -v '^#' "$SEED" | grep -v '^[[:space:]]*$'; }
65
+
66
+ # score_accepts — run every accept check. Prints "RED <slug>" / "GREEN <slug>".
67
+ # An accept that exits 0 means the work is already in the tree.
68
+ score_accepts() {
69
+ local red=0 green=0
70
+ while IFS=$'\t' read -r slug title tags accept; do
71
+ [ -z "${slug:-}" ] && continue
72
+ if ( cd "$ROOT" && eval "$accept" ) >/dev/null 2>&1; then
73
+ echo "GREEN $slug"; green=$((green+1))
74
+ else
75
+ echo "RED $slug"; red=$((red+1))
76
+ fi
77
+ done < <(rows)
78
+ echo "# red=$red green=$green"
79
+ }
80
+
81
+ case "${1:-}" in
82
+ --status)
83
+ if [ ! -f "$LIVE" ]; then echo "no loop has ever published liveness here ($LIVE)"; exit 1; fi
84
+ cat "$LIVE"
85
+ pid=$(sed -n 's/.*"pid":\([0-9]*\).*/\1/p' "$LIVE")
86
+ if kill -0 "$pid" 2>/dev/null; then echo "owner pid $pid is ALIVE"
87
+ else echo "owner pid $pid is GONE — this file is a record, not a running loop"; fi
88
+ exit 0 ;;
89
+ --stop) touch "$STOP"; echo "stop requested — the loop exits after its current pass"; exit 0 ;;
90
+ --accepts) score_accepts; exit 0 ;;
91
+ esac
92
+
93
+ ONCE=false
94
+ for a in "$@"; do
95
+ case "$a" in
96
+ --live) DRY=false ;;
97
+ --once) ONCE=true ;;
98
+ --claims=*) CLAIMS_PER_PASS="${a#*=}" ;;
99
+ *) echo "unknown flag: $a" >&2; exit 2 ;;
100
+ esac
101
+ done
102
+
103
+ # single instance — mkdir(2) is atomic; macOS ships no flock(1)
104
+ if ! mkdir "$LOCK" 2>/dev/null; then
105
+ owner=$(cat "$LOCK/pid" 2>/dev/null || echo "?")
106
+ if [ "$owner" != "?" ] && kill -0 "$owner" 2>/dev/null; then
107
+ echo "[tasks-loop] already running as pid $owner — refusing to double-file"; exit 1
108
+ fi
109
+ echo "[tasks-loop] reaping dead lock (pid $owner)"; rm -rf "$LOCK"; mkdir "$LOCK" || exit 1
110
+ fi
111
+ echo $$ > "$LOCK/pid"
112
+ rm -f "$STOP"
113
+ cleanup() { rm -rf "$LOCK"; publish stopped "loop exited" 0 0 0 0; }
114
+ trap cleanup EXIT INT TERM
115
+
116
+ log "start — seed=$SEED ws=$WS interval=${INTERVAL}s claims/pass=$CLAIMS_PER_PASS dry=$DRY"
117
+
118
+ one_pass() {
119
+ local red=0 green=0 filed=0 claimed=0 reaped=0
120
+ # 0. REAP — before anything else, so a freed lease is claimable this pass.
121
+ if $DRY; then
122
+ log "DRY would reap stranded leases in workspace $WS"
123
+ else
124
+ rout=$(CC_TASKS_WORKSPACE="$WS" bash "$ROOT/.claude/scripts/do-signal.sh" --task-reap 2>&1)
125
+ case "$rout" in
126
+ *'"reaped":[]'*) log "reap: nothing stranded" ;;
127
+ *'"ok":true'*) log "reap: ${rout:0:220}"; reaped=1 ;;
128
+ *) log "reap inconclusive: ${rout:0:220}" ;;
129
+ esac
130
+ fi
131
+ publish scoring "running accept checks" 0 0 0 0
132
+ while IFS=$'\t' read -r slug title tags accept; do
133
+ [ -z "${slug:-}" ] && continue
134
+ if ( cd "$ROOT" && eval "$accept" ) >/dev/null 2>&1; then
135
+ green=$((green+1))
136
+ log "SETTLED $slug — accept is green, not filing"
137
+ continue
138
+ fi
139
+ red=$((red+1))
140
+ grep -qx "$slug" "$FILED" && continue
141
+ if $DRY; then
142
+ log "DRY would file: $slug [$tags] — $title"
143
+ else
144
+ if CC_TASKS_WORKSPACE="$WS" bash "$ROOT/.claude/scripts/do-signal.sh" \
145
+ --task-create "$slug" "$title" "tags=$tags" "notes=accept: $accept" >>"$LOG" 2>&1; then
146
+ echo "$slug" >> "$FILED"; log "FILED $slug [$tags]"
147
+ else
148
+ log "file FAILED $slug — see $LOG"
149
+ fi
150
+ fi
151
+ filed=$((filed+1))
152
+ done < <(rows)
153
+
154
+ # CLAIM — bounded per pass. Atomic at the seam (see header), so a concurrent
155
+ # fleet claiming the same slug is refused, not silently co-granted.
156
+ local n=0
157
+ while IFS=$'\t' read -r slug title tags accept; do
158
+ [ -z "${slug:-}" ] && continue
159
+ [ "$n" -ge "$CLAIMS_PER_PASS" ] && break
160
+ grep -qx "$slug" "$FILED" || $DRY || continue
161
+ if $DRY; then
162
+ log "DRY would claim: $slug"
163
+ else
164
+ out=$(CC_TASKS_WORKSPACE="$WS" bash "$ROOT/.claude/scripts/do-signal.sh" --task-claim "$slug" 2>&1)
165
+ case "$out" in
166
+ *already-claimed*) log "SKIP $slug — already leased (that is the atomic guard working)" ;;
167
+ *'"ok":true'*) log "CLAIMED $slug"; claimed=$((claimed+1)) ;;
168
+ *) log "claim inconclusive $slug: ${out:0:160}" ;;
169
+ esac
170
+ fi
171
+ n=$((n+1))
172
+ done < <(rows)
173
+
174
+ publish idle "pass complete" "$red" "$green" "$filed" "$claimed"
175
+ log "pass: red=$red green=$green filed=$filed claimed=$claimed reaped=$reaped"
176
+ }
177
+
178
+ if $ONCE; then one_pass; exit $?; fi
179
+ while true; do
180
+ one_pass || true
181
+ for _ in $(seq 1 "$INTERVAL"); do
182
+ [ -f "$STOP" ] && { log "stop file seen — exiting"; exit 0; }
183
+ sleep 1
184
+ done
185
+ done
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env bash
2
+ # test-cached.sh <folder> -- <vitest args...> — run a vitest selection, or reuse
3
+ # the PASS someone already computed for this exact input. Exits 0 on pass/hit,
4
+ # non-zero on fail or on could-not-run.
5
+ #
6
+ # manifest: needs-env
7
+ #
8
+ # WHY. The dev lane re-ran identical work on every cycle. The pinned block alone
9
+ # is 44 files / 420 tests / ~20s, and it runs on EVERY cycle regardless of the
10
+ # diff -- so a plan of six cycles paid it six times for a tree whose pinned
11
+ # surface never moved. Same for `vitest related` when two cycles touch the same
12
+ # file. Measured 2026-09-01: fast lane ~30s/cycle, two thirds of it the pins.
13
+ #
14
+ # The key is the exact INPUT to the run, so a hit is sound by construction:
15
+ # sha( vitest args + content of every selected test file
16
+ # + content of every tracked source file that differs from the base )
17
+ # Nothing else can change the answer. Change a test, change a source file it
18
+ # reads, or change the selection, and the key moves. This is the tsc-cached.sh
19
+ # idiom (one compute per tree-fingerprint, everyone else reuses) narrowed from
20
+ # the whole tree to the actual inputs of one selection.
21
+ #
22
+ # ONLY PASSES ARE CACHED. A red result is never memoised: a failing gate must be
23
+ # re-run every single time until it is green, because the value of a gate is that
24
+ # it keeps saying no. This is the `unrun-gate-is-not-a-pass` rule applied to its
25
+ # mirror image -- a *remembered* pass is only a pass while its inputs are identical.
26
+ #
27
+ # TEST_CACHE_DISABLE=1 forces a real run (and still records the result).
28
+ # --self-test runs the fixtures.
29
+ set -uo pipefail
30
+
31
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
32
+ CACHE_DIR="${TEST_CACHE_DIR:-${TMPDIR:-/tmp}/one-test-cache}"
33
+ MAX_AGE_DAYS="${TEST_CACHE_MAX_AGE_DAYS:-7}"
34
+
35
+ _self_test() {
36
+ local t fails=0
37
+ t="$(mktemp -d)"; export TEST_CACHE_DIR="$t"
38
+ # 1. a PASS is remembered
39
+ bash "$ROOT/.claude/scripts/test-cached.sh" __probe__ -- --probe-pass >/dev/null 2>&1
40
+ local r1=$?
41
+ local out2; out2=$(bash "$ROOT/.claude/scripts/test-cached.sh" __probe__ -- --probe-pass 2>&1)
42
+ if printf '%s' "$out2" | grep -q 'cache HIT'; then echo "ok: pass is remembered"
43
+ else echo "FAIL: identical pass did not hit (r1=$r1): $out2"; fails=$((fails+1)); fi
44
+ # 2. RED PROOF — a FAIL must never be remembered
45
+ bash "$ROOT/.claude/scripts/test-cached.sh" __probe__ -- --probe-fail >/dev/null 2>&1
46
+ local out3; out3=$(bash "$ROOT/.claude/scripts/test-cached.sh" __probe__ -- --probe-fail 2>&1)
47
+ if printf '%s' "$out3" | grep -q 'cache HIT'; then
48
+ echo "FAIL: a RED result was cached — the gate would stop biting"; fails=$((fails+1))
49
+ else echo "ok: a fail is never cached (re-runs every time)"; fi
50
+ # 3. a different selection must not collide with a remembered pass
51
+ local out4; out4=$(bash "$ROOT/.claude/scripts/test-cached.sh" __probe__ -- --probe-pass --extra 2>&1)
52
+ if printf '%s' "$out4" | grep -q 'cache HIT'; then
53
+ echo "FAIL: different args collided with a cached key"; fails=$((fails+1))
54
+ else echo "ok: key discriminates on args"; fi
55
+ # 4. RED PROOF against a REAL vitest -- a genuinely failing run must exit
56
+ # non-zero AND leave no stamp. Checks 1-3 use the __probe__ folder, which never
57
+ # touches the runner; this one drives the actual code path the gate uses.
58
+ local pt; pt="$(mktemp -d)"
59
+ cat > "$pt/red.test.ts" <<'REDEOF'
60
+ import { it, expect } from "vitest";
61
+ it("is deliberately red", () => { expect(1).toBe(2); });
62
+ REDEOF
63
+ local before after
64
+ before=$(ls "$t" 2>/dev/null | wc -l | tr -d ' ')
65
+ if TEST_CACHE_DIR="$t" bash "$ROOT/.claude/scripts/test-cached.sh" \
66
+ one.ie/web -- --reporter=dot --root "$pt" >/dev/null 2>&1; then
67
+ echo "FAIL: a red vitest exited 0"; fails=$((fails+1))
68
+ else echo "ok: a real failing vitest exits non-zero"; fi
69
+ after=$(ls "$t" 2>/dev/null | wc -l | tr -d ' ')
70
+ if [ "$before" = "$after" ]; then echo "ok: a red run recorded no stamp"
71
+ else echo "FAIL: a red run wrote a cache stamp"; fails=$((fails+1)); fi
72
+
73
+ # 5. the pty path, only where it can actually be exercised. `script -qeF` needs
74
+ # a controlling tty on stdin; without one it dies with
75
+ # "tcgetattr/ioctl: Operation not supported on socket" -- which is a NON-ZERO
76
+ # exit, so a naive red proof here passes for the wrong reason and proves
77
+ # nothing about status propagation. Measured 2026-09-01. An unrunnable check
78
+ # reports n/a; it is never reported as ok.
79
+ if [ -t 0 ]; then
80
+ before=$(ls "$t" 2>/dev/null | wc -l | tr -d ' ')
81
+ if TEST_CACHE_PTY=1 TEST_CACHE_DIR="$t" bash "$ROOT/.claude/scripts/test-cached.sh" \
82
+ one.ie/web -- --reporter=dot --root "$pt" >/dev/null 2>&1; then
83
+ echo "FAIL: a red vitest under the pty wrapper exited 0"; fails=$((fails+1))
84
+ else echo "ok: pty wrapper propagates a red exit status"; fi
85
+ after=$(ls "$t" 2>/dev/null | wc -l | tr -d ' ')
86
+ if [ "$before" = "$after" ]; then echo "ok: a red pty run recorded no stamp"
87
+ else echo "FAIL: a red pty run wrote a cache stamp"; fails=$((fails+1)); fi
88
+ else
89
+ echo "n/a: no controlling tty — the pty wrapper cannot be exercised here (NOT a pass)"
90
+ fi
91
+ rm -rf "$pt"
92
+ rm -rf "$t"
93
+ [ "$fails" -eq 0 ] && { echo "test-cached: self-test PASS"; return 0; }
94
+ echo "test-cached: self-test FAILED ($fails)"; return 1
95
+ }
96
+ [ "${1:-}" = "--self-test" ] && { _self_test; exit $?; }
97
+
98
+ folder="${1:-}"; shift || true
99
+ [ "${1:-}" = "--" ] && shift
100
+ [ -n "$folder" ] || { echo "usage: test-cached.sh <folder> -- <vitest args>" >&2; exit 2; }
101
+ ARGS=("$@")
102
+
103
+ # --- key -------------------------------------------------------------------
104
+ # Selected test files: whichever of the args name a real file, plus (for a
105
+ # substring selection like the pins) the files those substrings resolve to.
106
+ _sel=""
107
+ for a in "${ARGS[@]}"; do
108
+ case "$a" in
109
+ --*) continue ;;
110
+ *) _sel="$_sel$a"$'\n' ;;
111
+ esac
112
+ done
113
+
114
+ # <hash> <repo-relative path> — the shape `shasum <file>` prints, minus $ROOT.
115
+ #
116
+ # This is the SECOND memo key of the two the governor owns, and it is now
117
+ # computed by lib/govern.ts (cycle 16) — the same program that computes
118
+ # tsc_tree_fingerprint, so both keys have one implementation and one place to be
119
+ # wrong. The BYTES ARE IDENTICAL to the `shasum -a 256 < file | cut -d' ' -f1`
120
+ # form it replaces, including the empty hash a missing file produced, so every
121
+ # stamp already on disk still hits: verified 2026-09-04 by comparing
122
+ # TEST_CACHE_KEY_ONLY=1 across three selections before and after.
123
+ # GOVERN_BUN unresolvable -> the hash comes back empty for every file, which
124
+ # moves the key rather than freezing it, so a broken toolchain can only cost a
125
+ # re-run and can never replay a stale pass.
126
+ _GOV_LIB="$(dirname "${BASH_SOURCE[0]}")/lib/govern.sh"
127
+ # shellcheck source=lib/govern.sh
128
+ [ -r "$_GOV_LIB" ] && . "$_GOV_LIB"
129
+ if command -v _gov >/dev/null 2>&1 && [ -n "${GOVERN_BUN:-}" ]; then
130
+ _hash_rel() { _gov hash-rel "$ROOT" "$@"; }
131
+ else
132
+ # No governor lib and no bun (a fresh clone, CI). Same bytes, one file per call.
133
+ _hash_rel() {
134
+ local f
135
+ for f in "$@"; do
136
+ printf '%s %s\n' "$(shasum -a 256 < "$ROOT/$f" 2>/dev/null | cut -d' ' -f1)" "$f"
137
+ done
138
+ }
139
+ fi
140
+
141
+ _key_src=$(
142
+ printf '%s\0' "${ARGS[@]}"
143
+ # content of every file the selection names, when it exists
144
+ while IFS= read -r f; do
145
+ [ -n "$f" ] || continue
146
+ # REPO-RELATIVE names, never "$ROOT/...": `shasum <path>` prints the path
147
+ # beside the hash, and an absolute path puts the WORKTREE into the key. The
148
+ # same bytes hashed from two checkouts then produced two keys, so the memo
149
+ # was private per worktree for every selection that named a file — `related`
150
+ # and BOTH full-suite lanes — and a plan that closed in a worktree and shipped
151
+ # from main paid ~122s twice. Measured 2026-09-03 (three-cell probe: the
152
+ # path-free pins shared, anything naming a file did not). Same bytes, same
153
+ # key, any checkout. This is NOT a narrowing: nothing leaves the key.
154
+ [ -f "$ROOT/$folder/$f" ] && _hash_rel "$folder/$f"
155
+ [ -f "$ROOT/$f" ] && _hash_rel "$f"
156
+ done <<< "$_sel"
157
+ # The working delta, BY CONTENT. `git status --porcelain` was the first attempt
158
+ # and it is a trap: it names files, so " M src/lib/authority.ts" is the same
159
+ # string whether that file gained one comment or was rewritten. Caught in the
160
+ # act 2026-09-01 -- appending a line to an already-modified file did not move
161
+ # the key, and the run returned a cached PASS for a tree that had changed. A
162
+ # green gate over a changed tree is the exact failure this cache must not have.
163
+ # `git diff HEAD` hashes the CONTENT of every tracked change; untracked files
164
+ # are hashed directly since no diff covers them.
165
+ git -C "$ROOT" diff HEAD 2>/dev/null
166
+ git -C "$ROOT" ls-files --others --exclude-standard 2>/dev/null \
167
+ | while IFS= read -r u; do _hash_rel "$u"; done
168
+ git -C "$ROOT" rev-parse HEAD 2>/dev/null
169
+ # Inputs git CANNOT see. `.env`/`.env.local` are gitignored, so neither
170
+ # `git diff HEAD` nor `ls-files --others` (which honours .gitignore) covers
171
+ # them -- yet the suite reads them. Without these lines a pass could be
172
+ # replayed across an environment change. Absent hashes as "ABSENT" so that
173
+ # DELETING one still moves the key.
174
+ for _e in "$folder/.env" "$folder/.env.local" "$folder/.env.test"; do
175
+ if [ -f "$ROOT/$_e" ]; then _hash_rel "$_e"
176
+ else echo "ABSENT $_e"; fi
177
+ done
178
+ # for a substring selection the matched SET can change without any arg changing
179
+ ( cd "$ROOT/$folder" 2>/dev/null && git ls-files '*.test.*' 2>/dev/null | shasum -a 256 )
180
+ )
181
+ KEY=$(printf '%s' "$_key_src" | shasum -a 256 | cut -d' ' -f1)
182
+ # TEST_CACHE_KEY_ONLY=1 prints the key and runs nothing — how a caller (or a
183
+ # sharing proof) asks "would this selection hit?" without paying for the run.
184
+ if [ "${TEST_CACHE_KEY_ONLY:-0}" = "1" ]; then echo "$KEY"; exit 0; fi
185
+ STAMP="$CACHE_DIR/$folder-${KEY}.pass"
186
+ STAMP="${STAMP//\//_}"; STAMP="$CACHE_DIR/$(basename "$STAMP")"
187
+
188
+ # TEST_CACHE_PROBE=1 answers "would this HIT?" and runs nothing: exit 0 on a
189
+ # hit, 1 on a miss. It sits AFTER the STAMP lines above on purpose -- the
190
+ # probe and the real check must resolve the same path or the probe is a
191
+ # second, drifting definition of a hit. KEY_ONLY prints the key; PROBE
192
+ # answers the question the caller actually has.
193
+ #
194
+ # WHY IT EXISTS. verify-fast.sh took a GOVERNOR SLOT and then asked whether
195
+ # there was anything to run. Measured 2026-09-09 on a saturated box: 855s
196
+ # and 1324s queued for slots whose work was a memo hit costing ~0. A cache
197
+ # hit must never wait behind a real gate -- it needs no memory, no forks and
198
+ # no slot. Probe first, take the slot only on a MISS.
199
+ if [ "${TEST_CACHE_PROBE:-0}" = "1" ]; then
200
+ if [ "${TEST_CACHE_DISABLE:-0}" != "1" ] && [ -f "$STAMP" ]; then
201
+ echo "[test-cached] probe: HIT ${KEY:0:12}"; exit 0
202
+ fi
203
+ echo "[test-cached] probe: MISS ${KEY:0:12}"; exit 1
204
+ fi
205
+
206
+ mkdir -p "$CACHE_DIR" 2>/dev/null
207
+ find "$CACHE_DIR" -name '*.pass' -mtime "+${MAX_AGE_DAYS}" -delete 2>/dev/null
208
+
209
+ if [ "${TEST_CACHE_DISABLE:-0}" != "1" ] && [ -f "$STAMP" ]; then
210
+ echo "[test-cached] cache HIT ${KEY:0:12} — identical inputs already PASSED $(cat "$STAMP" 2>/dev/null)"
211
+ exit 0
212
+ fi
213
+
214
+ # --- run -------------------------------------------------------------------
215
+ # __probe__ is the self-test's fake folder: no vitest, deterministic outcome.
216
+ if [ "$folder" = "__probe__" ]; then
217
+ case " ${ARGS[*]} " in *--probe-fail*) st=1 ;; *) st=0 ;; esac
218
+ else
219
+ # THE SLOT. This script takes no lock, so unlike tsc-cached.sh there is no
220
+ # ordering hazard -- adding a slot here is purely additive.
221
+ #
222
+ # verify-fast.sh already wraps ITS calls to this script in gate-run, and
223
+ # gate-run exports GOVERN_IN_GATE=1, so _gate below collapses to a plain `env`
224
+ # on that path: the outer slot is INHERITED, never doubled (a second slot held
225
+ # by one logical gate is what deadlocks the queue at cap 2).
226
+ #
227
+ # What this closes is the other caller: `bun run test` -> test-lanes.sh -> here
228
+ # reached vitest with no slot at all. Measured 2026-09-07 in one worktree at
229
+ # 256% CPU and ~800MB, with the governor reporting "1 of 2 slots held".
230
+ _gate=( env )
231
+ if [ "${TEST_CACHE_GATE:-1}" = "1" ] && [ "${GOVERN_IN_GATE:-0}" != "1" ] \
232
+ && [ -f "$ROOT/.claude/scripts/gate-run.sh" ]; then
233
+ _gate=( env "GOVERN_GATE_TIMEOUT=${TEST_CACHE_TIMEOUT:-1800}"
234
+ bash "$ROOT/.claude/scripts/gate-run.sh" "test-cache:$folder" -- )
235
+ fi
236
+ if [ "${TEST_CACHE_PTY:-0}" = "1" ]; then
237
+ # The deploy gate's vitest HANGS when stdout is not a TTY -- a fork is spawned
238
+ # and never given work (see .claude/commands/deploy.md "The vitest gate hangs").
239
+ # `script -qeF /dev/null` allocates a pty and flushes. The -e is load-bearing:
240
+ # it propagates the CHILD's exit status. Without it `script` exits 0 over a red
241
+ # suite and this cache would record a PASS for a failing run.
242
+ ( cd "$ROOT/$folder" && "${_gate[@]}" script -qeF /dev/null bunx vitest run "${ARGS[@]}" )
243
+ st=$?
244
+ else
245
+ ( cd "$ROOT/$folder" && "${_gate[@]}" bunx vitest run "${ARGS[@]}" )
246
+ st=$?
247
+ fi
248
+ fi
249
+
250
+ # ONLY a pass is recorded. A red gate must re-run until it is green.
251
+ if [ "$st" -eq 0 ]; then
252
+ date -u +%Y-%m-%dT%H:%M:%SZ > "$STAMP" 2>/dev/null
253
+ echo "[test-cached] PASS recorded ${KEY:0:12}"
254
+ fi
255
+ exit "$st"
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env bash
2
+ # test-full.sh — THE full vitest suite. One definition, every caller.
3
+ #
4
+ # manifest: needs-env
5
+ #
6
+ # WHY THIS FILE EXISTS. The full suite had two callers that ran it with
7
+ # DIFFERENT arguments:
8
+ #
9
+ # ./deploy → test-cached.sh one.ie/web -- --testTimeout=15000 … --reporter=dot
10
+ # /close → bun run test → gate-run test -- vitest run (no memo at all)
11
+ #
12
+ # test-cached.sh keys a PASS on the exact input, and the argv is part of that
13
+ # key. So the two callers could not share a stamp even on a byte-identical
14
+ # tree — and the /close path was not memoised in the first place. A warm tree
15
+ # therefore paid the full suite TWICE: once to close the plan, once to ship it,
16
+ # for inputs that had not moved between them.
17
+ #
18
+ # The fix is not a smarter cache. It is removing the second definition: both
19
+ # callers now run THIS script, so the argv they present to test-cached.sh is
20
+ # identical by construction and the second run is a hit.
21
+ #
22
+ # WHAT THIS IS NOT. It is not the fast lane's stamps being reused to shrink the
23
+ # full run. That was the first idea and it is unsound, recorded here so nobody
24
+ # rebuilds it:
25
+ # · `vitest related --run <files>` and `vitest run` are different runs — fork
26
+ # assignment and module-graph order differ. A file that passes in a 2-file
27
+ # selection is NOT thereby proven in the 875-file pool, and this repo has
28
+ # order-dependent shared-TypeDB suites.
29
+ # · On a do/* branch the fast lane DEFERS the pins (.verify-fast-debt), so its
30
+ # stamps systematically exclude the gates the full run exists to pay.
31
+ # · The window is empty anyway: a fast stamp is valid only at tree-state T, and
32
+ # the full gate is only slow when the tree is not T.
33
+ # The inputs to a full-pool run include the pool. Only a whole-suite pass may
34
+ # stand in for a whole-suite run.
35
+ #
36
+ # TEST_CACHE_PTY=1 for a non-TTY stdout (the deploy gate logs to a file, and the
37
+ # runner hangs there — see deploy.md "The vitest gate hangs").
38
+ # --check asserts no caller has grown a second definition.
39
+ set -uo pipefail
40
+
41
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
42
+ FOLDER="${TEST_FULL_FOLDER:-one.ie/web}"
43
+
44
+ # The one definition. Changing a flag here changes it for every caller at once,
45
+ # which is the whole point — a flag added on one side only would split the key
46
+ # again and silently cost a second full suite.
47
+ ARGS=(--testTimeout=15000 --hookTimeout=15000 --teardownTimeout=15000 --reporter=dot)
48
+
49
+ # --- --check: the second definition must not come back ----------------------
50
+ # A caller that runs the full suite by hand gets a different argv, so its PASS
51
+ # cannot be reused by the other caller. That is invisible — everything is green,
52
+ # it just costs an extra ~3 minutes — so it needs a checker, not a comment.
53
+ if [ "${1:-}" = "--check" ]; then
54
+ fails=0
55
+ # deploy.sh must reach the suite through this script
56
+ if grep -qE 'test-cached\.sh[^|]*one\.ie/web[^|]*--[^|]*--reporter' "$ROOT/.claude/scripts/deploy.sh"; then
57
+ echo "FAIL: deploy.sh still spells out its own vitest args — it must call test-full.sh"; fails=$((fails+1))
58
+ else echo "ok: deploy.sh has no second definition"; fi
59
+ # package.json's `test` must not be a bare vitest run (test:raw is the escape)
60
+ _t=$(python3 -c "import json;print(json.load(open('$ROOT/$FOLDER/package.json'))['scripts'].get('test',''))" 2>/dev/null)
61
+ case "$_t" in
62
+ *test-full.sh*) echo "ok: package.json test routes through test-full.sh" ;;
63
+ *) echo "FAIL: package.json test is '$_t' — not routed through test-full.sh"; fails=$((fails+1)) ;;
64
+ esac
65
+ # and the two must present the SAME argv, which is only true if neither adds any
66
+ # A caller may prefix env (TEST_CACHE_PTY=1) but must pass NO vitest args --
67
+ # an arg on one side only splits the key and quietly costs a second full suite.
68
+ _extra=$(grep -n 'test-full\.sh' "$ROOT/.claude/scripts/deploy.sh" | grep -- '--' | grep -v -- '--check' || true)
69
+ if [ -n "$_extra" ]; then
70
+ echo "FAIL: deploy.sh passes args to test-full.sh -- that splits the key:"; echo "$_extra"; fails=$((fails+1))
71
+ else echo "ok: no caller adds arguments"; fi
72
+ [ "$fails" -eq 0 ] && { echo "test-full: check PASS"; exit 0; }
73
+ echo "test-full: check FAILED ($fails)"; exit 1
74
+ fi
75
+
76
+ # TWO LANES, not one run. The suite has two binding constraints — CPU for ~1126
77
+ # files, and one shared external TypeDB gateway for 19 — and a single 8-fork pool
78
+ # serves the second one so badly the gate became a coin flip (four runs on an
79
+ # unchanged tree: 8, 12, 13, 8 different failures). test-lanes.sh splits them and
80
+ # runs them concurrently. Measured 2026-09-03, same tree:
81
+ # before 238-282s, RED, rotating victims
82
+ # after 121.9s wall, GREEN — pool 1125 passed / typedb 19 passed
83
+ # It still reaches test-cached.sh (once per lane), so passes are still memoised
84
+ # and a RED is still never cached. ARGS above stays the one definition of the
85
+ # vitest flags; test-lanes.sh consumes it via TEST_FULL_ARGS.
86
+ export TEST_FULL_ARGS="${ARGS[*]}"
87
+ exec bash "$ROOT/.claude/scripts/test-lanes.sh"
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+ // test-honesty — find tests that CANNOT FAIL.
3
+ //
4
+ // Written 2026-08-18 after an audit found 8 security tests whose bodies were
5
+ // comment-only. They backed a SETTLED contract: text/SECURITY-plan.md names the
6
+ // UC9 and UC26 canaries as its proof observable, and the kill-switch grepped
7
+ // `[1-9][0-9]+ passed` over the same file — satisfied by ~130 unrelated tests in
8
+ // it. An empty body cannot fail, so the contract rested on nothing.
9
+ //
10
+ // It reports two classes, and only ever counts what it can prove from the source:
11
+ //
12
+ // EMPTY a test body containing no assertion at all. Always a defect: the
13
+ // test is scored as a pass and can never go red.
14
+ // SKIP a conditional skip (skipIf / it.skip / runIf / ternary-gated it).
15
+ // Not a defect by itself — but a suite that skips is INDISTINGUISHABLE
16
+ // from one that passes, so the count must be visible. 31 tests in this
17
+ // repo sat behind an env var vitest could not see and had never once
18
+ // executed on any machine.
19
+ //
20
+ // Deliberately NOT a linter. It makes no style judgements and flags nothing it
21
+ // cannot demonstrate from the text of the file.
22
+ //
23
+ // Usage: node .claude/scripts/test-honesty.mjs [--json] [dir ...]
24
+ // Exit: 1 if any EMPTY body is found, else 0.
25
+
26
+ import { readdirSync, statSync, readFileSync } from 'node:fs'
27
+ import path from 'node:path'
28
+
29
+ const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..')
30
+ const args = process.argv.slice(2)
31
+ const JSON_OUT = args.includes('--json')
32
+ const dirs = args.filter((a) => !a.startsWith('--'))
33
+ const TARGETS = dirs.length ? dirs : ['one.ie/web/tests', 'channels/test', 'channels/tests']
34
+
35
+ function walk(dir, out = []) {
36
+ let entries
37
+ try { entries = readdirSync(dir) } catch { return out }
38
+ for (const e of entries) {
39
+ const full = path.join(dir, e)
40
+ let st
41
+ try { st = statSync(full) } catch { continue }
42
+ if (st.isDirectory()) walk(full, out)
43
+ else if (/\.(test|spec)\.[tj]sx?$/.test(e)) out.push(full)
44
+ }
45
+ return out
46
+ }
47
+
48
+ // Walk from the opening brace, tracking strings/comments, and return the body.
49
+ function bodyFrom(src, openIdx) {
50
+ let depth = 0, i = openIdx
51
+ let inS = null, inLine = false, inBlock = false
52
+ for (; i < src.length; i++) {
53
+ const c = src[i], n = src[i + 1]
54
+ if (inLine) { if (c === '\n') inLine = false; continue }
55
+ if (inBlock) { if (c === '*' && n === '/') { inBlock = false; i++ } continue }
56
+ if (inS) { if (c === '\\') { i++; continue } if (c === inS) inS = null; continue }
57
+ if (c === '/' && n === '/') { inLine = true; i++; continue }
58
+ if (c === '/' && n === '*') { inBlock = true; i++; continue }
59
+ if (c === '"' || c === "'" || c === '`') { inS = c; continue }
60
+ if (c === '{') depth++
61
+ else if (c === '}') { depth--; if (depth === 0) return src.slice(openIdx + 1, i) }
62
+ }
63
+ return null
64
+ }
65
+
66
+ function stripInert(s) {
67
+ return s
68
+ .replace(/\/\*[\s\S]*?\*\//g, '')
69
+ .replace(/(^|[^:])\/\/[^\n]*/g, '$1')
70
+ }
71
+
72
+ // An assertion, or a call that defers to one. `expect.assertions`, a bare
73
+ // `await somethingThatThrows()`, and custom helpers all count — the point is to
74
+ // catch bodies with NOTHING, not to police assertion style.
75
+ const ASSERTS = /\b(expect|assert|should|chai|\.rejects|\.resolves|toMatchSnapshot|toThrow|fail\()/
76
+
77
+ const findings = { empty: [], declared: [], indirect: [], skips: [] }
78
+
79
+ for (const target of TARGETS) {
80
+ for (const file of walk(path.join(ROOT, target))) {
81
+ const src = readFileSync(file, 'utf8')
82
+ const rel = path.relative(ROOT, file)
83
+
84
+ // conditional skips
85
+ for (const m of src.matchAll(/\b(?:it|test|describe)\.(skipIf|runIf|skip)\b|\b(?:skipWithout\w*|maybeIt|maybeDescribe)\s*\(/g)) {
86
+ const line = src.slice(0, m.index).split('\n').length
87
+ findings.skips.push({ file: rel, line, form: m[0].trim() })
88
+ }
89
+
90
+ // assertion-free bodies
91
+ const re = /\b(?:it|test)(\.\w+)?\s*\(\s*(['"`])([\s\S]*?)\2\s*,\s*(?:async\s*)?\(\s*\)\s*=>\s*\{/g
92
+ for (const m of re.exec_all ? [] : [...src.matchAll(re)]) {
93
+ const open = src.indexOf('{', m.index + m[0].length - 1)
94
+ const body = bodyFrom(src, open)
95
+ if (body === null) continue
96
+ const code = stripInert(body).trim()
97
+ // Two classes, deliberately unequal. A body with NO CODE is provably
98
+ // unable to fail — a hard error. A body with code but no visible assertion
99
+ // token is only a SUSPICION: it may delegate to a helper that asserts
100
+ // internally (engine-golden.test.ts does exactly that via goldenCheck()).
101
+ // Reporting the second as a failure would make this checker the very thing
102
+ // it exists to catch — confident, and wrong.
103
+ const rec = {
104
+ file: rel,
105
+ line: src.slice(0, m.index).split('\n').length,
106
+ name: m[3].slice(0, 100),
107
+ }
108
+ // An empty body under .skip/.todo is HONEST — vitest reports it as
109
+ // skipped/todo, never as passed, and it documents intent. The defect is an
110
+ // empty body that WILL BE SCORED: it reports a pass and can never go red.
111
+ // That is exactly the shape of the 8 security tests that backed a settled
112
+ // contract while asserting nothing.
113
+ const declared = /^\.(skip|todo|fails)$/.test(m[1] ?? '')
114
+ if (code.length === 0 && !declared) findings.empty.push({ ...rec, reason: 'empty body that will be SCORED AS A PASS' })
115
+ else if (code.length === 0) findings.declared.push({ ...rec, reason: `honest placeholder (${m[1]})` })
116
+ else if (!ASSERTS.test(code)) findings.indirect.push({ ...rec, reason: 'no assertion token — verify it delegates to one' })
117
+ }
118
+ }
119
+ }
120
+
121
+ if (JSON_OUT) {
122
+ console.log(JSON.stringify(findings, null, 2))
123
+ } else {
124
+ const bySkipFile = new Map()
125
+ for (const s of findings.skips) bySkipFile.set(s.file, (bySkipFile.get(s.file) ?? 0) + 1)
126
+ console.log(`test-honesty: scanned ${TARGETS.join(', ')}`)
127
+ console.log(`\nSKIP — conditional skips (visible, not failures): ${findings.skips.length} across ${bySkipFile.size} file(s)`)
128
+ for (const [f, n] of [...bySkipFile].sort((a, b) => b[1] - a[1]).slice(0, 15)) console.log(` ${String(n).padStart(3)} ${f}`)
129
+ console.log(`\nDECLARED — empty but honestly marked .skip/.todo: ${findings.declared.length}`)
130
+ console.log(`\nINDIRECT — no assertion token; verify each delegates to one: ${findings.indirect.length}`)
131
+ for (const e of findings.indirect.slice(0, 8)) console.log(` ${e.file}:${e.line} ${e.name}`)
132
+ if (findings.indirect.length > 8) console.log(` … ${findings.indirect.length - 8} more (--json for all)`)
133
+ console.log(`\nEMPTY — tests that CANNOT FAIL: ${findings.empty.length}`)
134
+ for (const e of findings.empty) console.log(` ${e.file}:${e.line} ${e.reason}\n ${e.name}`)
135
+ }
136
+
137
+ process.exit(findings.empty.length > 0 ? 1 : 0)