@oneie/claude 0.8.0 → 0.10.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 (230) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/abm-strategist.md +67 -1
  3. package/agents/ads-meta.md +67 -1
  4. package/agents/analyst.md +67 -1
  5. package/agents/animator.md +108 -0
  6. package/agents/architect.md +269 -20
  7. package/agents/brand-guardian.md +67 -1
  8. package/agents/brand-strategist.md +67 -1
  9. package/agents/campaign-content.md +67 -1
  10. package/agents/campaign-email.md +67 -1
  11. package/agents/campaign-sms.md +67 -1
  12. package/agents/campaign-social.md +67 -1
  13. package/agents/cco.md +83 -2
  14. package/agents/ceo.md +108 -11
  15. package/agents/chairman.md +197 -0
  16. package/agents/cmo.md +82 -2
  17. package/agents/community-greeter.md +67 -1
  18. package/agents/community-moderator.md +67 -1
  19. package/agents/compliance.md +67 -1
  20. package/agents/copywriter.md +67 -1
  21. package/agents/creative-strategist.md +67 -1
  22. package/agents/cro.md +81 -1
  23. package/agents/cto.md +266 -28
  24. package/agents/customer-interviewer.md +67 -1
  25. package/agents/customer-researcher.md +67 -1
  26. package/agents/customer-success-manager.md +67 -1
  27. package/agents/customer-trainer.md +67 -1
  28. package/agents/cxo.md +82 -1
  29. package/agents/demand-creator.md +67 -1
  30. package/agents/demo-mover.md +67 -1
  31. package/agents/demo-specialist.md +67 -1
  32. package/agents/demo-thai-family-law.md +67 -1
  33. package/agents/designer.md +67 -1
  34. package/agents/discovery-caller.md +67 -1
  35. package/agents/doctor.md +269 -0
  36. package/agents/educate-coach.md +67 -1
  37. package/agents/elevate-tutor.md +67 -1
  38. package/agents/email-lifecycle-marketer.md +67 -1
  39. package/agents/engage-specialist.md +67 -1
  40. package/agents/events-coordinator.md +67 -1
  41. package/agents/foundation-builder.md +67 -1
  42. package/agents/funnel-architect.md +67 -1
  43. package/agents/gift-creator.md +67 -1
  44. package/agents/google-ads.md +67 -1
  45. package/agents/guide.md +67 -1
  46. package/agents/helpdesk-dispatcher.md +67 -1
  47. package/agents/hook-specialist.md +67 -1
  48. package/agents/identify-optimizer.md +67 -1
  49. package/agents/implementer.md +313 -45
  50. package/agents/incident-commander.md +67 -1
  51. package/agents/insights-lead.md +87 -1
  52. package/agents/journey-runner.md +67 -1
  53. package/agents/linkedin-ads.md +67 -1
  54. package/agents/live-sales-chat.md +67 -1
  55. package/agents/market-researcher.md +67 -1
  56. package/agents/media-buyer.md +67 -1
  57. package/agents/memory-keeper.md +195 -0
  58. package/agents/movers-customer-researcher.md +67 -1
  59. package/agents/movers-foundation-builder.md +67 -1
  60. package/agents/movers-market-researcher.md +67 -1
  61. package/agents/movers-pricing-strategist.md +67 -1
  62. package/agents/nurture-architect.md +67 -1
  63. package/agents/offer-architect.md +67 -1
  64. package/agents/onboarder.md +67 -1
  65. package/agents/onboarding-specialist.md +67 -1
  66. package/agents/operations-dashboard.md +87 -1
  67. package/agents/perf-engineer.md +333 -37
  68. package/agents/playbook-writer.md +67 -1
  69. package/agents/plg-strategist.md +67 -1
  70. package/agents/positioning-architect.md +67 -1
  71. package/agents/press-officer.md +67 -1
  72. package/agents/pricing-strategist.md +67 -1
  73. package/agents/privacy-officer.md +67 -1
  74. package/agents/referral-manager.md +67 -1
  75. package/agents/refine-analyst.md +67 -1
  76. package/agents/release-manager.md +446 -39
  77. package/agents/renewals-upsell-rep.md +67 -1
  78. package/agents/review-engineer.md +319 -45
  79. package/agents/rewards-steward.md +67 -1
  80. package/agents/sales-call-coach.md +67 -1
  81. package/agents/sales-closer.md +67 -1
  82. package/agents/security-auditor.md +343 -48
  83. package/agents/sell-closer.md +67 -1
  84. package/agents/share-amplifier.md +67 -1
  85. package/agents/social-media-manager.md +67 -1
  86. package/agents/storyteller.md +301 -0
  87. package/agents/strategist.md +67 -1
  88. package/agents/strategy-aligner.md +67 -1
  89. package/agents/support-agent.md +67 -1
  90. package/agents/tagger.md +327 -0
  91. package/agents/tech-writer.md +195 -22
  92. package/agents/test-engineer.md +398 -29
  93. package/agents/tiktok-ads.md +67 -1
  94. package/agents/tracking-engineer.md +67 -1
  95. package/agents/trailkeeper.md +181 -0
  96. package/agents/upsell-strategist.md +67 -1
  97. package/agents/voice.md +67 -1
  98. package/agents/w1-recon.md +1 -1
  99. package/agents/w2-decide.md +1 -1
  100. package/agents/w3-edit.md +8 -2
  101. package/agents/w4-verify.md +13 -0
  102. package/agents/workflow-optimiser.md +81 -1
  103. package/commands/close.md +916 -160
  104. package/commands/deploy.md +102 -724
  105. package/commands/do.md +58 -2
  106. package/commands/sweep.md +159 -0
  107. package/commands/tasks.md +222 -0
  108. package/hooks/scripts/dev-only.sh +135 -0
  109. package/hooks/scripts/git-add-guard.sh +37 -2
  110. package/hooks/scripts/session-start.sh +32 -4
  111. package/package.json +1 -1
  112. package/rules/scripts.md +85 -0
  113. package/scripts/CLAUDE.md +315 -0
  114. package/scripts/ad-copy-lint.sh +656 -0
  115. package/scripts/agent-actor-parity.sh +129 -0
  116. package/scripts/blocks-manifest-cached.sh +100 -0
  117. package/scripts/chat-context-check.sh +89 -0
  118. package/scripts/chrome.mjs +18 -0
  119. package/scripts/close-metrics.sh +587 -0
  120. package/scripts/close-owner.sh +326 -0
  121. package/scripts/db-sync-lock-check.sh +116 -0
  122. package/scripts/deploy-emit.sh +311 -0
  123. package/scripts/deploy-gate-check.sh +155 -0
  124. package/scripts/deploy-ready.sh +78 -0
  125. package/scripts/deploy-record.sh +605 -0
  126. package/scripts/deploy-schema-check.sh +58 -0
  127. package/scripts/deploy.sh +393 -243
  128. package/scripts/do-auto.sh +127 -26
  129. package/scripts/do-board.sh +429 -0
  130. package/scripts/do-close.sh +1184 -0
  131. package/scripts/do-consumer-sweep.sh +18 -1
  132. package/scripts/do-decide.sh +476 -0
  133. package/scripts/do-fleet.sh +8 -2
  134. package/scripts/do-plan-json.mjs +110 -12
  135. package/scripts/do-prove-selftest.sh +108 -0
  136. package/scripts/do-prove.sh +86 -10
  137. package/scripts/do-rank.py +200 -3
  138. package/scripts/do-reconcile.sh +73 -12
  139. package/scripts/do-signal.sh +101 -23
  140. package/scripts/do-smoke.sh +18 -1
  141. package/scripts/do-w4-gates.sh +11 -1
  142. package/scripts/do-world-check.sh +153 -0
  143. package/scripts/download-stats.sh +172 -0
  144. package/scripts/factory-brief-check.sh +330 -0
  145. package/scripts/factory-check.sh +18 -1
  146. package/scripts/factory-close-check.sh +257 -0
  147. package/scripts/factory-emit.sh +211 -0
  148. package/scripts/factory-executor-check.mjs +353 -0
  149. package/scripts/factory-peak.sh +301 -0
  150. package/scripts/factory-repo.sh +71 -0
  151. package/scripts/factory-review-check.mjs +61 -0
  152. package/scripts/factory-tasks-check.sh +18 -1
  153. package/scripts/fixtures/factory-brief-real.md +44 -0
  154. package/scripts/flywheel-outcome.sh +63 -0
  155. package/scripts/gate-reaper-check.sh +98 -0
  156. package/scripts/gate-reaper.sh +9 -0
  157. package/scripts/gate-watchdog.sh +619 -0
  158. package/scripts/gc-content-check.sh +142 -0
  159. package/scripts/gh-traffic-capture.sh +153 -0
  160. package/scripts/govern-order-check.sh +202 -0
  161. package/scripts/governor-doors-check.sh +86 -5
  162. package/scripts/health.sh +448 -0
  163. package/scripts/id-inventory.mjs +418 -0
  164. package/scripts/incident.sh +212 -0
  165. package/scripts/land.sh +755 -45
  166. package/scripts/lib/gc-finished.sh +77 -0
  167. package/scripts/livekit-ratchet.sh +18 -1
  168. package/scripts/machine-check.sh +1 -1
  169. package/scripts/memory-index-budget.sh +79 -0
  170. package/scripts/npm-downloads.sh +109 -0
  171. package/scripts/one-agents.mjs +204 -8
  172. package/scripts/one-resume.sh +31 -3
  173. package/scripts/pr-body.sh +335 -0
  174. package/scripts/preview-fd-check.sh +289 -0
  175. package/scripts/redirect-lint.sh +169 -0
  176. package/scripts/release.sh +40 -6
  177. package/scripts/resume-lost-sessions.sh +68 -0
  178. package/scripts/shoot-pages.mjs +140 -0
  179. package/scripts/signal-meta-backfill.ts +451 -0
  180. package/scripts/signal-watch.sh +63 -6
  181. package/scripts/speed-cache-check.sh +12 -2
  182. package/scripts/sweep.sh +426 -0
  183. package/scripts/task-titles-dump.ts +101 -0
  184. package/scripts/test-cached.sh +47 -10
  185. package/scripts/test-lanes.sh +14 -0
  186. package/scripts/thread-name-backfill.ts +215 -0
  187. package/scripts/triage-shape-check.sh +149 -0
  188. package/scripts/tsc-cached.sh +155 -8
  189. package/scripts/typedb-flake-check.sh +3 -1
  190. package/scripts/urls-lint.sh +8 -0
  191. package/scripts/verify-board-doors.sh +80 -0
  192. package/scripts/verify-fast.sh +159 -6
  193. package/scripts/worktree-up.sh +21 -3
  194. package/skills/astro/SKILL.md +9 -3
  195. package/skills/astro/optimize-performance.md +3 -2
  196. package/skills/cloudflare/SKILL.md +3 -2
  197. package/skills/cloudflare-security-audit/AI-AND-LLM.md +83 -0
  198. package/skills/cloudflare-security-audit/ATTACK-CLASSES.md +130 -0
  199. package/skills/cloudflare-security-audit/CLIENT-SIDE.md +83 -0
  200. package/skills/cloudflare-security-audit/CLOUD-AND-DEPLOYMENT.md +86 -0
  201. package/skills/cloudflare-security-audit/DATA-ISOLATION-AND-LIFECYCLE.md +84 -0
  202. package/skills/cloudflare-security-audit/DESKTOP-MOBILE-AND-LOCAL-IPC.md +89 -0
  203. package/skills/cloudflare-security-audit/HUNTING.md +251 -0
  204. package/skills/cloudflare-security-audit/LICENSE +21 -0
  205. package/skills/cloudflare-security-audit/MEMORY-SAFETY-AND-BINARY.md +101 -0
  206. package/skills/cloudflare-security-audit/PROTOCOLS-RPC-AND-MESSAGING.md +81 -0
  207. package/skills/cloudflare-security-audit/PROVENANCE.md +78 -0
  208. package/skills/cloudflare-security-audit/RECONNAISSANCE.md +156 -0
  209. package/skills/cloudflare-security-audit/RESOURCE-EXHAUSTION-AND-AVAILABILITY.md +78 -0
  210. package/skills/cloudflare-security-audit/SKILL.md +192 -0
  211. package/skills/cloudflare-security-audit/SUPPLY-CHAIN-AND-RELEASE.md +73 -0
  212. package/skills/cloudflare-security-audit/VALIDATION-AND-REPORTING.md +186 -0
  213. package/skills/cloudflare-security-audit/WEB-PROTOCOL-AND-AUTH.md +105 -0
  214. package/skills/cloudflare-security-audit/report-schema.json +461 -0
  215. package/skills/cloudflare-security-audit/validate-coverage-ledger.cjs +872 -0
  216. package/skills/cloudflare-security-audit/validate-coverage-ledger.test.cjs +740 -0
  217. package/skills/cloudflare-security-audit/validate-findings.cjs +773 -0
  218. package/skills/cloudflare-security-audit/validate-findings.test.cjs +652 -0
  219. package/skills/deploy/REFERENCE.md +713 -0
  220. package/skills/deploy/SKILL.md +140 -0
  221. package/skills/fleet-audit/SKILL.md +58 -0
  222. package/skills/meeting/SKILL.md +220 -0
  223. package/skills/planning/SKILL.md +256 -0
  224. package/skills/shadcn/SKILL.md +1 -1
  225. package/skills/typedb/SKILL.md +7 -0
  226. package/skills/voice/SKILL.md +94 -6
  227. package/skills/voice/corpus-check.sh +87 -0
  228. package/templates/template-agent.md +7 -1
  229. package/templates/template-feature.md +9 -0
  230. package/templates/template-todo.md +29 -0
@@ -0,0 +1,142 @@
1
+ #!/bin/bash
2
+ # gc-content-check.sh — the red proof for lib/gc-finished.sh.
3
+ #
4
+ # manifest: needs-env
5
+ # (portable by behaviour — its own sandbox — but it must spell one.ie/web/.astro
6
+ # to exercise GC_EPHEMERAL. See factory-repo.sh's manifest block comment.)
7
+ #
8
+ # Builds a sandbox repo carrying the branch shapes the sweep meets, drives the
9
+ # REAL predicate (sourced, never re-implemented), and then proves the OLD
10
+ # commit-count predicate gets the important one wrong. A checker that only shows
11
+ # the new code passing would not tell you the change was worth making.
12
+ #
13
+ # NOT reproduced here: `git cherry`'s blind spot on a merge commit. That was
14
+ # measured on this repo (merge/trunk-reconcile, 2026-09-07: cherry reported
15
+ # 0-new for a branch holding 24 files and 3416 lines dev had never seen) and the
16
+ # reasoning lives in lib/gc-finished.sh. Faking a multi-merge-base history to
17
+ # re-stage it would be asserting a claim this sandbox does not actually show —
18
+ # the fourth case below covers the same risk directly: a merge carrying unique
19
+ # content must read NOT finished.
20
+ #
21
+ # bash .claude/scripts/gc-content-check.sh # exits non-zero on failure
22
+ set -uo pipefail
23
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24
+ . "$HERE/lib/gc-finished.sh"
25
+
26
+ SANDBOX="$(mktemp -d)"; trap 'rm -rf "$SANDBOX"' EXIT
27
+ fails=0
28
+ EXPECT_RED=0 # set while driving the neutered predicate, so an INTENDED failure
29
+ # does not print the word FAIL and read as a broken checker.
30
+ ok() { printf ' ok %s\n' "$1"; }
31
+ bad() {
32
+ if [ "$EXPECT_RED" = 1 ]; then printf ' red %s — as intended\n' "$1"
33
+ else printf ' FAIL %s\n' "$1"; fi
34
+ fails=$((fails+1))
35
+ }
36
+ want() { # <expected 0|1> <branch> <what>
37
+ gc_finished trunk "$2"; local got=$?
38
+ [ "$got" -eq "$1" ] && ok "$3" || bad "$3 (wanted finished=$1, got $got)"
39
+ }
40
+
41
+ cd "$SANDBOX"
42
+ git init -q -b trunk .
43
+ git config user.email t@e.st; git config user.name t
44
+ git config commit.gpgsign false
45
+ echo base > a.txt; git add a.txt; git commit -qm base
46
+ echo one > c.txt; git add c.txt; git commit -qm second
47
+ B1="$(git rev-parse HEAD)"
48
+
49
+ # 1 — REBASED DUPLICATE: the branch's patch reaches trunk under a DIFFERENT sha.
50
+ # This is the case the commit count gets wrong, and the reason for this file.
51
+ git checkout -q -b dup "$B1"
52
+ echo fixed > a.txt; git commit -qam "fix a"
53
+ git checkout -q trunk
54
+ git cherry-pick dup >/dev/null 2>&1 || { bad "sandbox: cherry-pick failed"; exit 1; }
55
+ # Re-message it. A cherry-pick onto the same parent, in the same second, with the
56
+ # same author and message, produces a byte-identical commit OBJECT — trunk then
57
+ # fast-forwards onto the branch and there is no divergence left to detect. That
58
+ # is a sandbox artifact, not the real world, where a re-landed patch always
59
+ # arrives with a different sha. The amend forces the sha apart while leaving the
60
+ # patch identical, which is precisely the case under test.
61
+ git commit -q --amend -m "fix a (relanded under a different sha)"
62
+
63
+ # 2 — ORDINARY UNLANDED BRANCH.
64
+ git checkout -q -b ahead trunk
65
+ echo new > b.txt; git add b.txt; git commit -qm "adds b"
66
+
67
+ # 3 — FULLY CONTAINED: tip is an ancestor of trunk.
68
+ git checkout -q -b behind "$B1"
69
+
70
+ # 4 — MERGE COMMIT CARRYING UNIQUE CONTENT. The shape that makes a
71
+ # patch-counting test (git cherry) unsafe on its own.
72
+ git checkout -q -b side "$B1"
73
+ echo unique > only-here.txt; git add only-here.txt; git commit -qm "unique file"
74
+ git checkout -q -b mergey trunk
75
+ git merge --no-edit side >/dev/null 2>&1 || { bad "sandbox: merge failed"; exit 1; }
76
+
77
+ echo "== the predicate the sweep runs"
78
+ want 0 dup "a rebased duplicate reads FINISHED — its content is already on the platform"
79
+ want 1 ahead "an ordinary unlanded branch reads NOT finished"
80
+ want 0 behind "a branch fully contained in the base reads FINISHED"
81
+ want 1 mergey "a merge carrying a file the base lacks reads NOT finished"
82
+
83
+ echo "== what the OLD commit-count predicate would have said"
84
+ old_finished() { [ "$(git rev-list --count "trunk..$1" 2>/dev/null || echo 1)" -eq 0 ]; }
85
+ if old_finished dup
86
+ then bad "the count predicate should MISS the duplicate — this proof is broken"
87
+ else ok "the count predicate hoards the duplicate for ever — the ~120MB this fixes"; fi
88
+ if old_finished ahead
89
+ then bad "the count predicate wrongly sweeps an unlanded branch"
90
+ else ok "the count predicate keeps an unlanded branch (both agree here)"; fi
91
+
92
+ echo "== the clean-worktree path survives set -e"
93
+ # The sweep runs under `set -euo pipefail`, and `grep -v` exits 1 when it filters
94
+ # EVERYTHING out — the clean-worktree case. Inline, that killed the sweep at the
95
+ # first worktree it was meant to remove: four keep lines, exit 1, no summary, and
96
+ # invisible behind a `| cat`. Drive the real function in a real subshell under
97
+ # the real flags, because that is the only way this comes back red.
98
+ git checkout -q trunk
99
+ if out="$(bash -c '
100
+ set -euo pipefail
101
+ . "'"$HERE"'/lib/gc-finished.sh"
102
+ d="$(gc_real_dirt "'"$SANDBOX"'")"
103
+ [ -z "$d" ] && echo CLEAN-AND-ALIVE
104
+ ' 2>&1)" && [ "$out" = "CLEAN-AND-ALIVE" ]; then
105
+ ok "a clean worktree reports no dirt AND the shell survives it"
106
+ else
107
+ bad "the clean-tree path died under set -e (got: ${out:-<nothing>})"
108
+ fi
109
+ # And it still SEES real work, or the fix would be a mute button.
110
+ echo scratch > "$SANDBOX/uncommitted.txt"
111
+ if [ -n "$(gc_real_dirt "$SANDBOX")" ]
112
+ then ok "uncommitted work is still reported — the guard did not silence it"
113
+ else bad "gc_real_dirt missed an uncommitted file"; fi
114
+ rm -f "$SANDBOX/uncommitted.txt"
115
+ # Ephemera alone must NOT read as work, or nothing is ever swept.
116
+ # The tracked file is required, not decoration: git COLLAPSES a wholly-untracked
117
+ # directory to its top level, so an unrepresentative sandbox reports `?? one.ie/`
118
+ # and no path pattern can match. A real worktree has one.ie/web/ tracked, so git
119
+ # descends and reports `?? one.ie/web/.astro` — which is what the filter is
120
+ # written against. Committing one file reproduces that.
121
+ mkdir -p "$SANDBOX/one.ie/web"; echo keep > "$SANDBOX/one.ie/web/keep.txt"
122
+ git add one.ie/web/keep.txt; git commit -qm "tracked file under one.ie/web"
123
+ mkdir -p "$SANDBOX/one.ie/web/.astro"; echo x > "$SANDBOX/one.ie/web/.astro/gen.d.ts"
124
+ if [ -z "$(gc_real_dirt "$SANDBOX")" ]
125
+ then ok "build output alone does not count as work"
126
+ else bad "ephemeral dirt was reported as work — nothing would ever be swept"; fi
127
+ rm -rf "$SANDBOX/one.ie/web/.astro"
128
+
129
+ echo "== red proof: neuter the predicate and the checks must fail"
130
+ gc_finished() { return 0; } # claim every branch is finished
131
+ before=$fails; EXPECT_RED=1
132
+ want 1 ahead "(neutered) an unlanded branch must NOT read finished"
133
+ want 1 mergey "(neutered) a unique-content merge must NOT read finished"
134
+ if [ "$fails" -eq $((before+2)) ]; then
135
+ fails=$before; EXPECT_RED=0; ok "both checks bite when the predicate is gutted"
136
+ else
137
+ fails=$((before+1)); EXPECT_RED=0; printf ' FAIL a gutted predicate still passed — this checker proves nothing\n'
138
+ fi
139
+
140
+ echo
141
+ [ "$fails" -eq 0 ] && { echo "gc-content-check: PASS"; exit 0; }
142
+ echo "gc-content-check: FAIL ($fails)"; exit 1
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env bash
2
+ # gh-traffic-capture.sh — keep GitHub traffic before GitHub throws it away.
3
+ #
4
+ # bash .claude/scripts/gh-traffic-capture.sh # capture, default repos
5
+ # bash .claude/scripts/gh-traffic-capture.sh --repo o/r # one repo
6
+ # bash .claude/scripts/gh-traffic-capture.sh --report # read the ledger
7
+ # bash .claude/scripts/gh-traffic-capture.sh --install-cron # daily at 07:15
8
+ # bash .claude/scripts/gh-traffic-capture.sh --self-test
9
+ #
10
+ # manifest: needs-env — (gh CLI + network; no repo state beyond its own ledger)
11
+ #
12
+ # WHY: /traffic/clones and /traffic/views return THE LAST 14 DAYS AND NOTHING
13
+ # ELSE. There is no backfill, no paging, no "all time" — a day that scrolls out
14
+ # of that window is gone from the API permanently. Measured 2026-09-14 on
15
+ # one-ie/one: the repo has existed since 2024-11-12 and the API could account
16
+ # for 14 days of it. Every day this does not run, a day is lost for good.
17
+ #
18
+ # THE MERGE IS THE WHOLE JOB, and it has one rule that is easy to get wrong:
19
+ # TODAY'S ROW IS PARTIAL. Capture at 07:15 and the day is a few hours old; the
20
+ # same date re-fetched tomorrow is larger and correct. So a merge must take the
21
+ # GREATER of stored and incoming per date — never "skip if the date exists"
22
+ # (freezes every day at its morning value) and never "last write wins" (a later
23
+ # call that drops a day to 0 would erase a real number). max() is the only
24
+ # rule that is right on the first write, the same-day rewrite, and the replay.
25
+ #
26
+ # Idempotent by construction: re-running an hour later, or ten times, converges.
27
+ set -uo pipefail
28
+
29
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
30
+ DATA="${GH_TRAFFIC_DIR:-$ROOT/.claude/data/gh-traffic}"
31
+ REPOS=(); MODE=capture
32
+
33
+ while [[ $# -gt 0 ]]; do
34
+ case "$1" in
35
+ --repo) REPOS+=("$2"); shift ;;
36
+ --report) MODE=report ;;
37
+ --install-cron) MODE=cron ;;
38
+ --self-test) MODE=selftest ;;
39
+ -h|--help) sed -n '2,9p' "${BASH_SOURCE[0]}"; exit 0 ;;
40
+ *) echo "gh-traffic-capture.sh: unknown flag '$1'" >&2; exit 2 ;;
41
+ esac
42
+ shift
43
+ done
44
+ # ENUMERATE THE ORG, never a hardcoded list. The first version of this script
45
+ # listed the seven subtree-mirror repos from the root CLAUDE.md table — and so
46
+ # it silently missed `ontology` (53 clones) and `astro-shadcn` (18 clones, 17
47
+ # uniques, 26 views), the org's second- and third-busiest repos. A capture that
48
+ # is blind to a repo loses that repo's history permanently, which is the one
49
+ # failure this script exists to prevent. A new repo joins the ledger by
50
+ # existing, not by someone remembering to add it here.
51
+ # GH_TRAFFIC_ORG overrides; --repo still pins an explicit set.
52
+ if (( ! ${#REPOS[@]} )); then
53
+ while IFS= read -r r; do [[ -n "$r" ]] && REPOS+=("$r"); done < <(
54
+ gh api "orgs/${GH_TRAFFIC_ORG:-one-ie}/repos?per_page=100&type=public" --jq '.[].full_name' 2>/dev/null
55
+ )
56
+ (( ${#REPOS[@]} )) || { echo "gh-traffic-capture.sh: could not enumerate org repos" >&2; exit 1; }
57
+ fi
58
+
59
+ command -v jq >/dev/null || { echo "gh-traffic-capture.sh: needs jq" >&2; exit 2; }
60
+ command -v gh >/dev/null || { echo "gh-traffic-capture.sh: needs the gh CLI" >&2; exit 2; }
61
+
62
+ mkdir -p "$DATA"
63
+
64
+ # merge_series <file> <key> <json-array of {timestamp,count,uniques}>
65
+ # max() per date, per the rule above.
66
+ merge_series() {
67
+ local f="$1" key="$2" incoming="$3" tmp
68
+ tmp="$(mktemp)"
69
+ [[ -s "$f" ]] || printf '{"repo":"","clones":{},"views":{},"captured":[]}' > "$f"
70
+ jq --arg k "$key" --argjson inc "$incoming" '
71
+ .[$k] = ( (.[$k] // {}) as $have
72
+ | reduce $inc[] as $d ($have;
73
+ ($d.timestamp[0:10]) as $day
74
+ | .[$day] = {
75
+ count: ([ (.[$day].count // 0), ($d.count // 0) ] | max),
76
+ uniques: ([ (.[$day].uniques // 0), ($d.uniques // 0) ] | max)
77
+ }
78
+ ) )
79
+ ' "$f" > "$tmp" && mv "$tmp" "$f"
80
+ }
81
+
82
+ capture_one() {
83
+ # TWO statements, deliberately. `local repo="$1" f="$DATA/${repo//\//__}.json"`
84
+ # expands EVERY word before it performs ANY assignment, so ${repo} there is
85
+ # still unset: f became "$DATA/.json" and all seven repos shared one ledger.
86
+ # The tell was subtle — the merge is max(), so the first repo's 163 clones
87
+ # survived every later repo's zeros and each one reported 163.
88
+ local repo="$1"
89
+ local f="$DATA/${repo//\//__}.json"
90
+ local c v
91
+ c="$(gh api "repos/$repo/traffic/clones" --jq '.clones' 2>/dev/null)"
92
+ v="$(gh api "repos/$repo/traffic/views" --jq '.views' 2>/dev/null)"
93
+ if [[ -z "$c" && -z "$v" ]]; then
94
+ echo " ! $repo — no traffic data (needs push access on that repo)" >&2
95
+ return 1
96
+ fi
97
+ [[ -s "$f" ]] || printf '{"repo":"%s","clones":{},"views":{},"captured":[]}' "$repo" > "$f"
98
+ [[ -n "$c" && "$c" != "null" ]] && merge_series "$f" clones "$c"
99
+ [[ -n "$v" && "$v" != "null" ]] && merge_series "$f" views "$v"
100
+ local tmp; tmp="$(mktemp)"
101
+ jq --arg r "$repo" --arg t "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
102
+ '.repo=$r | .captured = ((.captured // []) + [$t] | .[-200:])' "$f" > "$tmp" && mv "$tmp" "$f"
103
+ local days tot
104
+ days="$(jq '(.clones|length)' "$f")"; tot="$(jq '[.clones[].count]|add // 0' "$f")"
105
+ printf ' ✓ %-22s %s day(s) kept · %s clones total\n' "$repo" "$days" "$tot"
106
+ }
107
+
108
+ case "$MODE" in
109
+ capture)
110
+ echo "── capturing GitHub traffic → $DATA"
111
+ rc=0; for r in "${REPOS[@]}"; do capture_one "$r" || rc=1; done
112
+ exit $rc ;;
113
+
114
+ report)
115
+ printf '\n %-24s %8s %9s %8s %9s %s\n' "REPO" "CLONES" "UNIQ" "VIEWS" "UNIQ" "DAYS KEPT"
116
+ printf ' %s\n' "$(printf '─%.0s' {1..76})"
117
+ for f in "$DATA"/*.json; do
118
+ [[ -e "$f" ]] || { echo " (ledger empty — run a capture first)"; break; }
119
+ jq -r '" \(.repo[0:24] + " "|.[0:24]) \([.clones[].count]|add // 0|tostring|((" "+.)|.[-8:])) \([.clones[].uniques]|add // 0|tostring|((" "+.)|.[-9:])) \([.views[].count]|add // 0|tostring|((" "+.)|.[-8:])) \([.views[].uniques]|add // 0|tostring|((" "+.)|.[-9:])) \(.clones|length)"' "$f"
120
+ done
121
+ printf '\n NOTE: totals are what THIS LEDGER has kept, not GitHub all-time —\n'
122
+ printf ' anything before the first capture was already unrecoverable.\n\n' ;;
123
+
124
+ cron)
125
+ line="15 7 * * * cd $ROOT && /bin/bash .claude/scripts/gh-traffic-capture.sh >> $DATA/cron.log 2>&1"
126
+ if crontab -l 2>/dev/null | grep -qF 'gh-traffic-capture.sh'; then
127
+ echo " already installed:"; crontab -l 2>/dev/null | grep -F 'gh-traffic-capture.sh' | sed 's/^/ /'
128
+ else
129
+ ( crontab -l 2>/dev/null; echo "$line" ) | crontab - \
130
+ && echo " ✓ installed — daily 07:15" && crontab -l | grep -F 'gh-traffic' | sed 's/^/ /'
131
+ fi
132
+ echo " NOTE: cron only fires while this Mac is awake. A missed day is still"
133
+ echo " recoverable for 14 days — that is the window, and the reason for daily." ;;
134
+
135
+ selftest)
136
+ # The merge is the only logic worth proving, and the property is max().
137
+ d="$(mktemp -d)"; f="$d/t.json"; fails=0
138
+ printf '{"repo":"x","clones":{},"views":{},"captured":[]}' > "$f"
139
+ merge_series "$f" clones '[{"timestamp":"2026-09-01T00:00:00Z","count":5,"uniques":2}]'
140
+ [[ "$(jq -r '.clones["2026-09-01"].count' "$f")" == "5" ]] || { echo " FAIL: first write"; fails=1; }
141
+ # same day, larger (the partial-today case) -> must grow
142
+ merge_series "$f" clones '[{"timestamp":"2026-09-01T00:00:00Z","count":9,"uniques":4}]'
143
+ [[ "$(jq -r '.clones["2026-09-01"].count' "$f")" == "9" ]] || { echo " FAIL: partial day did not grow"; fails=1; }
144
+ # same day, smaller (a late/rescoped answer) -> must NOT shrink
145
+ merge_series "$f" clones '[{"timestamp":"2026-09-01T00:00:00Z","count":1,"uniques":1}]'
146
+ [[ "$(jq -r '.clones["2026-09-01"].count' "$f")" == "9" ]] || { echo " FAIL: a smaller number overwrote a larger one"; fails=1; }
147
+ # a day older than the 14d window must survive a capture that omits it
148
+ merge_series "$f" clones '[{"timestamp":"2026-09-02T00:00:00Z","count":3,"uniques":1}]'
149
+ [[ "$(jq -r '.clones|length' "$f")" == "2" ]] || { echo " FAIL: an out-of-window day was dropped"; fails=1; }
150
+ rm -rf "$d"
151
+ (( fails )) && { echo "gh-traffic-capture.sh: self-test FAILED"; exit 1; }
152
+ echo "gh-traffic-capture.sh: self-test PASS (grows, never shrinks, never forgets)" ;;
153
+ esac
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env bash
2
+ # govern-order-check.sh — the governor's LOCK ORDER, proved by deadlocking it.
3
+ #
4
+ # manifest: needs-env
5
+ #
6
+ # THE DEFECT (measured 2026-09-13, three occurrences in ~35 minutes, each
7
+ # stalling every session on the box for 8-28 minutes at 0% CPU — one of them a
8
+ # production release gate):
9
+ #
10
+ # tsc-cached.sh took the per-folder dedupe LOCK and then, still holding it,
11
+ # queued for a governor SLOT. LOCK -> SLOT
12
+ # Any caller already inside a gate has the opposite order: `bun run verify:fast`
13
+ # IS `gate-run.sh verify-fast -- verify-fast.sh`, so it holds a SLOT, and its
14
+ # child tsc-cached.sh then waits for the LOCK. SLOT -> LOCK
15
+ #
16
+ # A: gate-run(verify-fast) holds slot-1 -> child tsc-cached waits for lock-tsc-one.ie_web
17
+ # B: tsc-cached holds lock-tsc-one.ie_web -> its inner gate-run waits for a slot
18
+ #
19
+ # Two orders, one cycle. Neither owner is DEAD, so nothing reaps it:
20
+ # `gate_lock`/`gate_slot` only reap an owner whose pid is gone (govern.ts
21
+ # `reapLock`), and gate-reaper.sh is the same predicate. Measured owner pairs:
22
+ # 22173/68920, then 15762/97557, then 87579.
23
+ #
24
+ # KILLING THE LOCK HOLDER DOES NOT FIX IT — observed twice. The lock is released
25
+ # and immediately taken by the next waiter, which is itself inside a gate holding
26
+ # a slot, and the cycle re-forms within seconds. Killing the SLOT holder is what
27
+ # breaks it, because that is the resource nobody else can produce.
28
+ #
29
+ # THE FIX this proves: SLOT before LOCK, everywhere. A cache HIT still takes
30
+ # neither.
31
+ #
32
+ # WHAT EACH HALF PROVES. The green half proves tsc-cached.sh is no longer an
33
+ # inverted participant. It does NOT prove the cycle is impossible — a synthetic
34
+ # LOCK -> SLOT holder still deadlocks a fixed tsc-cached, and that is exactly
35
+ # what the red half plants, so this checker is never green for lack of power.
36
+ # Both halves run in their own GOVERN_DIR: nothing here touches the machine's
37
+ # real governor, so a real gate in another session is never delayed or killed.
38
+ set -uo pipefail
39
+
40
+ _HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
41
+ ROOT="$(cd "$_HERE/../.." && pwd)"
42
+ GR="$_HERE/gate-run.sh"
43
+ TSC="$_HERE/tsc-cached.sh"
44
+
45
+ # A folder inside ROOT, because tsc_tree_fingerprint cd's to ROOT and resolves
46
+ # the folder relatively. Never one.ie/web: that is a real 2.5GB typecheck.
47
+ REL=".govern-order-probe"
48
+ P="$ROOT/$REL"
49
+ LOCKNAME="tsc-$(printf '%s' "$REL" | tr '/' '_')"
50
+
51
+ # Waits are SHORT and bounded on purpose: a deadlocked pair must expire by
52
+ # itself in under a minute, on a box where other sessions are running real
53
+ # gates. OBSERVE is when the verdict is read — long enough for both roles to
54
+ # have taken their ground, short enough to finish inside a Bash tool call.
55
+ WAIT="${GOVERN_ORDER_WAIT:-20}"
56
+ OBSERVE="${GOVERN_ORDER_OBSERVE:-10}"
57
+
58
+ bad=0
59
+ ok() { echo " ok: $*"; }
60
+ nope() { echo " FAIL: $*"; bad=$((bad + 1)); }
61
+
62
+ probe_up() {
63
+ rm -rf "$P"; mkdir -p "$P/src"
64
+ printf '{ "compilerOptions": { "strict": true, "noEmit": true }, "include": ["src"] }\n' > "$P/tsconfig.json"
65
+ printf 'export const x: number = 1;\n' > "$P/src/a.ts"
66
+ }
67
+ probe_down() { rm -rf "$P"; }
68
+
69
+ # _race <sandbox> <role-b-command...> — start the two roles in the two orders and
70
+ # read the state at OBSERVE seconds.
71
+ #
72
+ # Role A is always the REAL shape a session runs: gate-run holds a slot, and its
73
+ # child tsc-cached asks for the lock. The `sleep 3` is the whole determinism of
74
+ # this repro — it lets role B take its ground first, so the interleaving is
75
+ # fixed rather than raced.
76
+ #
77
+ # Echoes "done" (both roles exited) or "stalled <lock-owner> <slot-owner>".
78
+ _race() {
79
+ local sb="$1"; shift
80
+ local aout="$sb/a.log" bout="$sb/b.log" pa pb t
81
+
82
+ ( GOVERN_DIR="$sb" GOVERN_MAX_GATES=1 GOVERN_QUEUE_WAIT="$WAIT" \
83
+ GOVERN_GATE_TIMEOUT=$((WAIT * 4)) TSC_CACHE_WAIT="$WAIT" TSC_CACHE_TIMEOUT="$WAIT" \
84
+ TSC_CACHE_CMD='echo no-errors-here' \
85
+ bash "$GR" role-a -- bash -c "sleep 3; bash '$TSC' '$REL'" ) >"$aout" 2>&1 &
86
+ pa=$!
87
+ sleep 2
88
+ ( GOVERN_DIR="$sb" GOVERN_MAX_GATES=1 GOVERN_QUEUE_WAIT="$WAIT" \
89
+ GOVERN_GATE_TIMEOUT=$((WAIT * 4)) TSC_CACHE_WAIT="$WAIT" TSC_CACHE_TIMEOUT="$WAIT" \
90
+ TSC_CACHE_CMD='echo no-errors-here' \
91
+ "$@" ) >"$bout" 2>&1 &
92
+ pb=$!
93
+
94
+ t=0
95
+ while [ "$t" -lt "$OBSERVE" ]; do
96
+ kill -0 "$pa" 2>/dev/null || kill -0 "$pb" 2>/dev/null || break
97
+ sleep 1; t=$((t + 1))
98
+ done
99
+
100
+ # The exit codes go to DISK, not to variables: _race is read through a command
101
+ # substitution, so anything it assigns dies with the subshell.
102
+ if ! kill -0 "$pa" 2>/dev/null && ! kill -0 "$pb" 2>/dev/null; then
103
+ wait "$pa"; echo $? > "$sb/rc.a"; wait "$pb"; echo $? > "$sb/rc.b"
104
+ echo "done"; return 0
105
+ fi
106
+ # Still alive at OBSERVE: name the two owners, then stop them. These are our
107
+ # own children in our own sandbox — never another session's gate.
108
+ local lo so
109
+ lo=$(cat "$sb/lock-$LOCKNAME/pid" 2>/dev/null | tr -d '[:space:]')
110
+ so=$(cat "$sb/lock-slot-1/pid" 2>/dev/null | tr -d '[:space:]')
111
+ kill -TERM "$pa" "$pb" 2>/dev/null
112
+ sleep 1
113
+ kill -KILL "$pa" "$pb" 2>/dev/null
114
+ wait "$pa" 2>/dev/null; wait "$pb" 2>/dev/null
115
+ echo 99 > "$sb/rc.a"; echo 99 > "$sb/rc.b"
116
+ echo "stalled lock=${lo:-none} slot=${so:-none}"
117
+ }
118
+
119
+ # ── 1. THE REAL PAIR — both roles are the shipped tsc-cached.sh ──────────────
120
+ real_pair() {
121
+ echo "== the real pair — gate-run(slot)->tsc-cached(lock) vs bare tsc-cached"
122
+ local sb r ra rb
123
+ sb="$(mktemp -d)"; probe_up
124
+ r=$(_race "$sb" bash "$TSC" "$REL")
125
+ ra=$(cat "$sb/rc.a" 2>/dev/null); rb=$(cat "$sb/rc.b" 2>/dev/null)
126
+ case "$r" in
127
+ done)
128
+ if [ "${ra:-1}" = "0" ] && [ "${rb:-1}" = "0" ]; then
129
+ ok "both roles completed (rc 0/0) — tsc-cached takes the SLOT before the LOCK"
130
+ else
131
+ nope "both roles exited but not cleanly (a=${ra:-?} b=${rb:-?})
132
+ $(sed 's/^/ A| /' "$sb/a.log"; sed 's/^/ B| /' "$sb/b.log")"
133
+ fi ;;
134
+ stalled*)
135
+ nope "DEADLOCK reproduced against this code — $r
136
+ neither owner is dead, so nothing reaps it. This is the 2026-09-13 stall.
137
+ $(sed 's/^/ A| /' "$sb/a.log"; sed 's/^/ B| /' "$sb/b.log")" ;;
138
+ esac
139
+ probe_down; rm -rf "$sb"
140
+ }
141
+
142
+ # ── 2. RED PROOF — plant the pre-fix order and the SAME harness must stall ───
143
+ # Without this, a green section 1 could mean "the harness cannot see a deadlock".
144
+ # The planted role is tsc-cached.sh's own pre-2026-09-13 body in miniature: take
145
+ # the folder lock, then queue for a slot while holding it.
146
+ red_proof() {
147
+ echo "== red proof — a planted LOCK->SLOT holder must still deadlock the pair"
148
+ local sb r
149
+ sb="$(mktemp -d)"; probe_up
150
+ r=$(_race "$sb" bash -c ". '$_HERE/lib/govern.sh'
151
+ gate_lock '$LOCKNAME' $WAIT || exit 9
152
+ trap 'gate_release_all' EXIT INT TERM
153
+ bash '$GR' planted -- echo computed")
154
+ case "$r" in
155
+ stalled*)
156
+ ok "the inverted order still deadlocks and the harness sees it — $r" ;;
157
+ done)
158
+ nope "a planted LOCK->SLOT holder did NOT stall — this checker has no power,
159
+ so section 1 going green proves nothing.
160
+ $(sed 's/^/ A| /' "$sb/a.log"; sed 's/^/ B| /' "$sb/b.log")" ;;
161
+ esac
162
+ probe_down; rm -rf "$sb"
163
+ }
164
+
165
+ # ── 3. A CACHE HIT COSTS NOTHING — no lock, no slot ─────────────────────────
166
+ # The memo lookup is what makes this cache worth having. If a HIT queued for a
167
+ # slot, every cheap verify-fast would join the same queue as a real typecheck —
168
+ # and land.sh's `--probe` (a lookup, never a compute) would be able to hang.
169
+ hit_is_free() {
170
+ echo "== a cache HIT takes neither the lock nor a slot"
171
+ local sb out n
172
+ sb="$(mktemp -d)"; probe_up
173
+ # compute once (this one legitimately takes a slot)
174
+ GOVERN_DIR="$sb" GOVERN_MAX_GATES=1 TSC_CACHE_CMD='echo no-errors-here' \
175
+ bash "$TSC" "$REL" >/dev/null 2>&1
176
+ n=$(ls "$sb" 2>/dev/null | grep -c "^${LOCKNAME}\." | tr -d ' ')
177
+ [ "${n:-0}" -ge 1 ] || nope "nothing was memoised — the rest of this section cannot bind"
178
+ # Now a HIT, with the slots ALL taken. If it needs one, it cannot finish.
179
+ mkdir -p "$sb/lock-slot-1"; echo $$ > "$sb/lock-slot-1/pid"
180
+ out=$(GOVERN_DIR="$sb" GOVERN_MAX_GATES=1 GOVERN_QUEUE_WAIT=6 TSC_CACHE_WAIT=6 \
181
+ TSC_CACHE_CMD='echo no-errors-here' bash "$TSC" "$REL" 2>/dev/null)
182
+ if [ "$(printf '%s' "$out" | tr -d '[:space:]')" = "0" ]; then
183
+ ok "a HIT answers 0 with every slot held (it never queued)"
184
+ else
185
+ nope "a HIT could not answer with the slots held (got '${out:-}') — it is taking a slot"
186
+ fi
187
+ [ -d "$sb/lock-$LOCKNAME" ] && nope "a HIT left the folder lock behind — it took the lock" \
188
+ || ok "a HIT took no folder lock"
189
+ # --probe, the pure lookup land.sh uses, under the same starvation
190
+ out=$(GOVERN_DIR="$sb" GOVERN_MAX_GATES=1 GOVERN_QUEUE_WAIT=6 \
191
+ bash "$TSC" --probe "$REL" 2>/dev/null)
192
+ [ "$out" = "green" ] && ok "--probe answers green with every slot held" \
193
+ || nope "--probe could not answer with the slots held (got '${out:-}')"
194
+ probe_down; rm -rf "$sb"
195
+ }
196
+
197
+ echo "govern-order-check — SLOT before LOCK, proved by deadlocking it"
198
+ real_pair
199
+ red_proof
200
+ hit_is_free
201
+ [ "$bad" -eq 0 ] && { echo "govern-order: PASS"; exit 0; }
202
+ echo "govern-order: FAILED ($bad)"; exit 1
@@ -22,9 +22,17 @@
22
22
  # hop is followed: its own file, not its package.json, holds the two runners.
23
23
  #
24
24
  # WHAT IS DELIBERATELY NOT A DOOR is in SKIP below, each with its reason. That
25
- # list is the argument, not an allowlist of convenience: a governed slot on a
26
- # 2-second typecheck is cost with no benefit, and `tsc` already has its own
27
- # lock via tsc-cached.sh -- nesting it would put two locks on one logical gate.
25
+ # list is the argument, not an allowlist of convenience.
26
+ #
27
+ # A LOCK IS NOT A SLOT. Until 2026-09-07 the typecheck skip below read "tsc
28
+ # already has its own lock via tsc-cached.sh", and that reasoning is wrong in a
29
+ # way that cost the machine: gate_lock is keyed by FOLDER and dedupes identical
30
+ # work, while a SLOT caps concurrent heavy work machine-wide. N worktrees are N
31
+ # folders, so N locks are all uncontended while N typecheckers run. Measured that
32
+ # day: three 2.5GB tsc processes at once, load 171, localhost at 8.4s a page, and
33
+ # THIS CHECKER REPORTING "0 open". The "2-second typecheck" half was wrong too --
34
+ # the measured runs were 2.5GB and 7+ minutes. The compute sites now take a slot
35
+ # (see the `compute sites` section below, which is what would have caught it).
28
36
  # SHIPS (needs-env). In a generated factory clone this enumerates THAT repo
29
37
  # package.json files, while every line in the skip list names a monorepo path
30
38
  # that can never match there -- so it may report open doors it has no opinion
@@ -44,7 +52,7 @@ HEAVY_RE='vitest|tsc[[:space:]]+--noEmit|astro[[:space:]]+check|playwright[[:spa
44
52
  # <pkg-relpath>::<script> why
45
53
  skips() {
46
54
  cat <<'SKIP'
47
- *::typecheck a bare tsc, seconds not minutes, and verify-fast routes tsc through tsc-cached.sh which takes its OWN gate lock -- wrapping would be a second lock on one logical gate
55
+ *::typecheck routes through tsc-cached.sh, which since 2026-09-07 takes a real SLOT around its compute (gate-run.sh), not merely its dedupe lock -- and since 2026-09-13 takes that slot BEFORE the lock, never while holding it (govern-order-check.sh proves both halves). Enforced by the `compute sites` section, not asserted here
48
56
  *::test:watch a watch never exits; a wall-clock bound would kill it
49
57
  *::test:raw an escape hatch by definition -- ungoverned is the whole point
50
58
  *::verify:raw an escape hatch by definition
@@ -236,9 +244,82 @@ reentrancy() {
236
244
  else
237
245
  nope "unexpected red-half output. out=[$(cat "$o")] err=[$(cat "$e")]"
238
246
  fi
247
+ # RED PROOF for `compute sites`: a script that spawns a heavy compute with no
248
+ # gate-run.sh must turn compute_sites red. This is the exact shape tsc-cached.sh
249
+ # had before 2026-09-07 -- a gate_lock, and a bare bunx tsc inside it.
250
+ local probe_dir probe
251
+ probe_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
252
+ # NOT a dotfile: `for f in "$d"/*.sh` does not glob dotfiles, so a probe named
253
+ # .zz-*.sh is invisible to the very check it is supposed to drive red -- the
254
+ # proof would pass by never being seen. Named plainly, and rm'd below.
255
+ probe="$probe_dir/zz-compute-probe.sh"
256
+ cat > "$probe" <<'PROBE'
257
+ #!/usr/bin/env bash
258
+ # a lock, and a heavy compute with no slot -- the 2026-09-07 defect, in miniature
259
+ gate_lock "probe" 10 && bunx tsc --noEmit
260
+ PROBE
261
+ local b0=$bad
262
+ compute_sites >/dev/null 2>&1
263
+ if [ "$bad" -gt "$b0" ]; then
264
+ ok "a lock-without-slot compute site turns the audit RED"
265
+ bad=$b0
266
+ else
267
+ nope "compute_sites stayed green against a bare 'bunx tsc --noEmit' with no gate-run"
268
+ fi
269
+ rm -f "$probe"
270
+
239
271
  rm -rf "$sandbox"
240
272
  }
241
273
 
274
+ # ── compute sites — a gate_lock is not a slot ───────────────────────────────
275
+ #
276
+ # The `doors` section above audits PACKAGE SCRIPTS. It cannot see the defect
277
+ # that actually cost the machine on 2026-09-07, because that one lived one layer
278
+ # down: tsc-cached.sh held a per-folder gate_lock and spawned `bunx tsc` with no
279
+ # slot at all. Every door was green, the skip list had a stated reason, and three
280
+ # 2.5GB typecheckers ran concurrently while the governor reported 1 of 2 slots.
281
+ #
282
+ # The rule this enforces: ANY script here that spawns a heavy compute must also
283
+ # reference gate-run.sh. Not "takes a lock" -- a lock dedupes identical work, a
284
+ # slot bounds the machine, and only one of those is a concurrency cap.
285
+ compute_sites() {
286
+ echo "== compute sites — a heavy compute behind a lock still needs a SLOT"
287
+ local d f n_ok=0 hits
288
+ d="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
289
+ for f in "$d"/*.sh; do
290
+ case "$(basename "$f")" in
291
+ gate-run.sh|governor-doors-check.sh|governor-escape-check.sh|machine-check.sh) continue ;;
292
+ # FIXTURES — these NAME a heavy command as test data and spawn nothing:
293
+ # load-guard-check.sh passes command STRINGS to the hook ( check 1 "bunx ..." )
294
+ # govern-mem-check.sh embeds them in JSON tool_input payloads
295
+ # deploy-gate-check.sh greps deploy.sh for the command as a PATTERN
296
+ # Each was verified by reading the matching lines, not assumed.
297
+ load-guard-check.sh|govern-mem-check.sh|deploy-gate-check.sh) continue ;;
298
+ esac
299
+ # PRESENCE, not command-position. The first version of this asked "is the
300
+ # compute in command position", blanking quoted regions -- and it could not
301
+ # see the very defect it was written for: tsc-cached.sh spawned its tsc from
302
+ # inside a quoted `bash -c "... ${TSC_CACHE_CMD:-bunx tsc --noEmit} ..."`.
303
+ # Reverting tsc-cached.sh to its pre-fix state left that check GREEN, which
304
+ # is the only test of a checker that matters.
305
+ #
306
+ # So the rule is blunt on purpose: if a heavy token appears outside a comment,
307
+ # this file must reference gate-run.sh somewhere. Scripts that only NAME a
308
+ # command as a test fixture are handled by FIXTURES below, by name and with a
309
+ # reason -- the same "the skip list is the argument" doctrine as the doors.
310
+ hits=$(grep -nE '(bunx|npx|bun x) +(tsc +--noEmit|vitest +run)' "$f" 2>/dev/null \
311
+ | grep -vE '^[0-9]+:[[:space:]]*#' || true)
312
+ [ -n "$hits" ] || continue
313
+ if grep -q 'gate-run\.sh' "$f"; then
314
+ n_ok=$((n_ok+1))
315
+ else
316
+ nope "$(basename "$f") spawns a heavy compute with no gate-run.sh anywhere:
317
+ $(printf '%s' "$hits" | head -3 | sed 's/^/ /')"
318
+ fi
319
+ done
320
+ [ "$bad" -eq 0 ] && ok "$n_ok script(s) spawn a heavy compute, every one routes through gate-run.sh"
321
+ }
322
+
242
323
  # ── red proof ───────────────────────────────────────────────────────────────
243
324
  self_test() {
244
325
  echo "== self-test — plant an open door, the REAL audit must go RED"
@@ -273,7 +354,7 @@ case "${1:-}" in
273
354
  --list) enumerate | sort | column -t -s$'\t' 2>/dev/null || enumerate | sort; exit 0 ;;
274
355
  --self-test) self_test ;;
275
356
  *) if [ -n "${GOV_DOORS_INNER:-}" ]; then audit
276
- else audit; echo; paths; echo; reentrancy; echo; self_test; fi ;;
357
+ else audit; echo; paths; echo; compute_sites; echo; reentrancy; echo; self_test; fi ;;
277
358
  esac
278
359
 
279
360
  echo