@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,334 @@
1
+ #!/usr/bin/env node
2
+ // The census, regenerable — text/blocks.md deliverable 2.
3
+ //
4
+ // Reads every row of the `pages` table (D1, --remote by default, --local on
5
+ // request), resolves each page's block list EXACTLY the way production does
6
+ // (data column when present, else componentsToPuck(parseComponents(components))
7
+ // — see one.ie/web/src/pages/u/[slug]/p/[pageSlug].astro), tallies pagesUsing
8
+ // (distinct pages with >=1 instance) and instances (total instances) per
9
+ // registered block name, cross-references nonPageRefs (uses of the name
10
+ // outside one.ie/web/src/lib/puck/), and writes
11
+ // one.ie/web/src/lib/puck/block-usage.json.
12
+ //
13
+ // --check: regenerate in memory and diff against the committed file's
14
+ // `blocks` map (measuredAt excluded from the diff). Runs with NO network/D1
15
+ // access as long as the committed file already exists.
16
+ //
17
+ // Usage:
18
+ // node .claude/scripts/blocks-usage.mjs # --remote, write the file
19
+ // node .claude/scripts/blocks-usage.mjs --local # read local D1 instead
20
+ // node .claude/scripts/blocks-usage.mjs --check # diff, no write, no D1 unless file missing
21
+
22
+ import { execFileSync } from 'node:child_process'
23
+ import { writeFileSync, readFileSync, existsSync, mkdtempSync, rmSync } from 'node:fs'
24
+ import { tmpdir } from 'node:os'
25
+ import path from 'node:path'
26
+ import { fileURLToPath } from 'node:url'
27
+
28
+ const __filename = fileURLToPath(import.meta.url)
29
+ const __dirname = path.dirname(__filename)
30
+ const REPO_ROOT = path.resolve(__dirname, '../..')
31
+ const WEB_DIR = path.join(REPO_ROOT, 'one.ie/web')
32
+ const CONFIG_PATH = path.join(WEB_DIR, 'src/lib/puck/config.tsx')
33
+ const OUT_PATH = path.join(WEB_DIR, 'src/lib/puck/block-usage.json')
34
+ const PUCK_LIB_DIR = path.join(WEB_DIR, 'src/lib/puck')
35
+ // GENERATED and gitignored — `src/data/promises.json` is built from text/*.md by
36
+ // the promise-manifest step, so every block name written into a PROMISE lands in
37
+ // `src/` and reads as a code reference. It is not one: a block named in a
38
+ // marketing contract is not a block anything can reach. Counting it made
39
+ // `--check` go red on a DOC edit (measured 2026-08-19: naming HeroCentered and
40
+ // PricingSection in text/blocks.md moved ten blocks and flipped one from
41
+ // unchosen to chosen), and worse, made the archive verdict depend on prose.
42
+ // Redundant since the scan intersects with `git ls-files` (the file is
43
+ // gitignored, so it is never tracked) — kept as a backstop against a stray
44
+ // `git add -f` making prose count again.
45
+ const PROMISES_JSON = path.join(WEB_DIR, 'src/data/promises.json')
46
+
47
+ const args = process.argv.slice(2)
48
+ const CHECK = args.includes('--check')
49
+ const LOCAL = args.includes('--local')
50
+
51
+ function readRegisteredNames() {
52
+ const tmp = mkdtempSync(path.join(tmpdir(), 'blocks-census-'))
53
+ const runner = path.join(tmp, 'names.mjs')
54
+ writeFileSync(
55
+ runner,
56
+ `import { puckConfig } from ${JSON.stringify(CONFIG_PATH)}\nconsole.log(JSON.stringify(Object.keys(puckConfig.components)))\n`,
57
+ )
58
+ try {
59
+ const out = execFileSync('bunx', ['tsx', runner], { cwd: WEB_DIR, encoding: 'utf8' })
60
+ return JSON.parse(out.trim())
61
+ } finally {
62
+ rmSync(tmp, { recursive: true, force: true })
63
+ }
64
+ }
65
+
66
+ // The census must measure the RETIRED names too, not just the 130 that survive
67
+ // collapseFamilies. Stored pages still carry `LandingHero`, and it resolves to
68
+ // Hero through BLOCK_ALIASES at render — so if the census only knows canonicals,
69
+ // every collapsed family measures 0 demand and the usage-ordered palette ranks
70
+ // them last. Measured 2026-08-18 on the first true regeneration since the cut:
71
+ // Hero fell to pagesUsing=0 and palette-order's family-inheritance test went red.
72
+ function readAliasNames() {
73
+ const tmp = mkdtempSync(path.join(tmpdir(), 'blocks-alias-'))
74
+ const runner = path.join(tmp, 'aliases.mjs')
75
+ writeFileSync(
76
+ runner,
77
+ `import { BLOCK_ALIASES } from ${JSON.stringify(path.join(WEB_DIR, 'src/lib/puck/families.ts'))}\nconsole.log(JSON.stringify(Object.keys(BLOCK_ALIASES ?? {})))\n`,
78
+ )
79
+ try {
80
+ const out = execFileSync('bunx', ['tsx', runner], { cwd: WEB_DIR, encoding: 'utf8' })
81
+ return JSON.parse(out.trim())
82
+ } finally {
83
+ rmSync(tmp, { recursive: true, force: true })
84
+ }
85
+ }
86
+
87
+ function readPagesRows() {
88
+ const flag = LOCAL ? '--local' : '--remote'
89
+ const out = execFileSync(
90
+ 'bunx',
91
+ ['wrangler', 'd1', 'execute', 'one-owners', flag, '--json', '--command', 'SELECT slug, workspace, components, data FROM pages'],
92
+ { cwd: WEB_DIR, encoding: 'utf8', maxBuffer: 1024 * 1024 * 256 },
93
+ )
94
+ const parsed = JSON.parse(out)
95
+ return parsed[0]?.results ?? []
96
+ }
97
+
98
+ // Mirrors one.ie/web/src/pages/u/[slug]/p/[pageSlug].astro's resolution order:
99
+ // data column wins when present and parseable; else derive from legacy
100
+ // components via componentsToPuck(parseComponents(...)).
101
+ function resolveBlockNames(row) {
102
+ const names = []
103
+ let content = null
104
+ if (row.data) {
105
+ try {
106
+ const parsed = JSON.parse(row.data)
107
+ content = Array.isArray(parsed?.content) ? parsed.content : null
108
+ } catch {
109
+ content = null
110
+ }
111
+ }
112
+ if (!content) {
113
+ let sections = []
114
+ try {
115
+ const raw = typeof row.components === 'string' ? JSON.parse(row.components) : row.components
116
+ if (Array.isArray(raw)) {
117
+ sections = raw.filter((e) => e && typeof e === 'object' && typeof e.component === 'string')
118
+ }
119
+ } catch {
120
+ sections = []
121
+ }
122
+ content = sections.map((s) => ({ type: s.component }))
123
+ }
124
+ for (const block of content) {
125
+ const type = block && typeof block === 'object' ? block.type : null
126
+ if (typeof type === 'string') names.push(type)
127
+ }
128
+ return names
129
+ }
130
+
131
+ function computeUsage(rows, registered) {
132
+ const pagesUsing = new Map()
133
+ const instances = new Map()
134
+ for (const name of registered) {
135
+ pagesUsing.set(name, 0)
136
+ instances.set(name, 0)
137
+ }
138
+ for (const row of rows) {
139
+ const names = resolveBlockNames(row)
140
+ const seenOnThisPage = new Set()
141
+ for (const name of names) {
142
+ if (!instances.has(name)) continue // not a registered block (dead name, alias, etc.)
143
+ instances.set(name, instances.get(name) + 1)
144
+ seenOnThisPage.add(name)
145
+ }
146
+ for (const name of seenOnThisPage) {
147
+ pagesUsing.set(name, pagesUsing.get(name) + 1)
148
+ }
149
+ }
150
+ return { pagesUsing, instances }
151
+ }
152
+
153
+ // nonPageRefs: how many files OUTSIDE lib/puck/ mention this component name
154
+ // (social composer, mail renderer, etc.) — a block reachable from there is
155
+ // CHOSEN even at zero page instances.
156
+ function computeNonPageRefs(registered) {
157
+ const result = new Map()
158
+ for (const name of registered) result.set(name, 0)
159
+ let out
160
+ try {
161
+ out = execFileSync(
162
+ 'grep',
163
+ ['-rl', '-E', `\\b(${registered.map(escapeRe).join('|')})\\b`, path.join(WEB_DIR, 'src')],
164
+ { encoding: 'utf8', maxBuffer: 1024 * 1024 * 64 },
165
+ )
166
+ } catch (err) {
167
+ // grep exits 1 when nothing matches — that's a valid empty result, not an error.
168
+ out = err.stdout ?? ''
169
+ }
170
+ const tracked = trackedSourceFiles()
171
+ const files = out
172
+ .split('\n')
173
+ .filter(Boolean)
174
+ .filter((f) => tracked.has(f))
175
+ .filter((f) => !f.startsWith(PUCK_LIB_DIR + path.sep) && f !== CONFIG_PATH && f !== PROMISES_JSON)
176
+ .filter((f) => !f.includes('.test.') && !f.includes('/tests/'))
177
+ for (const file of files) {
178
+ let text
179
+ try {
180
+ text = readFileSync(file, 'utf8')
181
+ } catch {
182
+ continue
183
+ }
184
+ for (const name of registered) {
185
+ const re = new RegExp(`\\b${escapeRe(name)}\\b`)
186
+ if (re.test(text)) result.set(name, result.get(name) + 1)
187
+ }
188
+ }
189
+ return result
190
+ }
191
+
192
+ function escapeRe(s) {
193
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
194
+ }
195
+
196
+ // The census is a claim about the REPO, so the scan must read what git tracks,
197
+ // not what happens to be sitting on disk. A regenerate run during a neighbour's
198
+ // half-finished work otherwise records references that do not exist in the
199
+ // repo — measured 2026-08-19: an uncommitted src/components/speed/SpeedMetrics.tsx
200
+ // from a concurrent session contributed Timeline and Stats refs to the committed
201
+ // census. Locally-MODIFIED tracked files still count: the bar is tracked, not
202
+ // clean, because a modified file is still a file the repo has.
203
+ function trackedSourceFiles() {
204
+ let out
205
+ try {
206
+ out = execFileSync('git', ['ls-files', '-z', '--', 'src'], {
207
+ cwd: WEB_DIR,
208
+ encoding: 'utf8',
209
+ maxBuffer: 1024 * 1024 * 64,
210
+ })
211
+ } catch (err) {
212
+ throw new Error(`blocks-usage: git ls-files failed — cannot tell tracked from untracked: ${err.message}`)
213
+ }
214
+ const files = new Set(
215
+ out
216
+ .split('\0')
217
+ .filter(Boolean)
218
+ .map((rel) => path.join(WEB_DIR, rel)),
219
+ )
220
+ // A broken sensor must never degrade quietly: an empty or implausibly small
221
+ // tracked set would zero every nonPageRefs and read as "archive everything".
222
+ // 2474 files were tracked under one.ie/web/src when this floor was measured.
223
+ if (files.size < 500) {
224
+ throw new Error(`blocks-usage: git ls-files returned only ${files.size} files under one.ie/web/src — refusing to census a repo this scan cannot see`)
225
+ }
226
+ return files
227
+ }
228
+
229
+ function buildCensus() {
230
+ const registered = readRegisteredNames().sort()
231
+ // registered ∪ retired-but-reachable — see readAliasNames above
232
+ const measured = [...new Set([...registered, ...readAliasNames()])].sort()
233
+ const rows = readPagesRows()
234
+ const { pagesUsing, instances } = computeUsage(rows, measured)
235
+ const nonPageRefs = computeNonPageRefs(measured)
236
+ const blocks = {}
237
+ let chosen = 0
238
+ for (const name of measured) {
239
+ const pu = pagesUsing.get(name) ?? 0
240
+ const inst = instances.get(name) ?? 0
241
+ const npr = nonPageRefs.get(name) ?? 0
242
+ blocks[name] = { pagesUsing: pu, instances: inst, nonPageRefs: npr }
243
+ if (pu > 0 || npr > 0) chosen++
244
+ }
245
+ return {
246
+ measuredAt: new Date().toISOString().slice(0, 10),
247
+ registered: registered.length,
248
+ measured: measured.length,
249
+ chosen,
250
+ unchosen: measured.length - chosen,
251
+ blocks,
252
+ }
253
+ }
254
+
255
+ function diffBlocks(committed, fresh) {
256
+ const lines = []
257
+ const names = new Set([...Object.keys(committed.blocks ?? {}), ...Object.keys(fresh.blocks)])
258
+ for (const name of [...names].sort()) {
259
+ const a = committed.blocks?.[name]
260
+ const b = fresh.blocks[name]
261
+ if (!a) {
262
+ lines.push(`+ ${name} ${JSON.stringify(b)}`)
263
+ continue
264
+ }
265
+ if (!b) {
266
+ lines.push(`- ${name} ${JSON.stringify(a)}`)
267
+ continue
268
+ }
269
+ if (a.pagesUsing !== b.pagesUsing || a.instances !== b.instances || a.nonPageRefs !== b.nonPageRefs) {
270
+ lines.push(`~ ${name} committed=${JSON.stringify(a)} fresh=${JSON.stringify(b)}`)
271
+ }
272
+ }
273
+ if (committed.registered !== fresh.registered) lines.push(`~ registered committed=${committed.registered} fresh=${fresh.registered}`)
274
+ if (committed.chosen !== fresh.chosen) lines.push(`~ chosen committed=${committed.chosen} fresh=${fresh.chosen}`)
275
+ if (committed.unchosen !== fresh.unchosen) lines.push(`~ unchosen committed=${committed.unchosen} fresh=${fresh.unchosen}`)
276
+ return lines
277
+ }
278
+
279
+ function main() {
280
+ if (CHECK) {
281
+ if (!existsSync(OUT_PATH)) {
282
+ console.error(`blocks-usage --check: ${OUT_PATH} does not exist — run without --check first to seed it.`)
283
+ process.exit(1)
284
+ }
285
+ const committed = JSON.parse(readFileSync(OUT_PATH, 'utf8'))
286
+ const fresh = buildCensusOffline(committed)
287
+ const diff = diffBlocks(committed, fresh)
288
+ if (diff.length > 0) {
289
+ console.error('blocks-usage --check: drift detected')
290
+ for (const line of diff) console.error(line)
291
+ process.exit(1)
292
+ }
293
+ console.log('blocks-usage --check: ok (no drift)')
294
+ process.exit(0)
295
+ }
296
+
297
+ const census = buildCensus()
298
+ writeFileSync(OUT_PATH, JSON.stringify(census, null, 2) + '\n')
299
+ console.log(`wrote ${OUT_PATH} — registered=${census.registered} chosen=${census.chosen} unchosen=${census.unchosen}`)
300
+ }
301
+
302
+ // In --check mode, re-derive registered names + nonPageRefs from the committed
303
+ // file's OWN key set. This lets --check run with zero D1/network access when
304
+ // the caller doesn't need a live page-usage recompute (i.e. it trusts the
305
+ // committed pagesUsing/instances numbers and only re-verifies name/ref shape).
306
+ // If a live D1 recheck is wanted, delete block-usage.json and re-run without
307
+ // --check first.
308
+ function buildCensusOffline(committed) {
309
+ // --check runs with no D1: it re-derives only the source-scanned half
310
+ // (nonPageRefs) and trusts the committed D1 half. The name set is therefore
311
+ // whatever the committed file measured — registered UNION retired — so it
312
+ // must NOT be reported as `registered`, which counts actual registrations.
313
+ const measuredNames = Object.keys(committed.blocks ?? {}).sort()
314
+ const nonPageRefs = computeNonPageRefs(measuredNames)
315
+ const blocks = {}
316
+ let chosen = 0
317
+ for (const name of measuredNames) {
318
+ const pu = committed.blocks[name]?.pagesUsing ?? 0
319
+ const inst = committed.blocks[name]?.instances ?? 0
320
+ const npr = nonPageRefs.get(name) ?? 0
321
+ blocks[name] = { pagesUsing: pu, instances: inst, nonPageRefs: npr }
322
+ if (pu > 0 || npr > 0) chosen++
323
+ }
324
+ return {
325
+ measuredAt: committed.measuredAt,
326
+ registered: committed.registered,
327
+ measured: measuredNames.length,
328
+ chosen,
329
+ unchosen: measuredNames.length - chosen,
330
+ blocks,
331
+ }
332
+ }
333
+
334
+ main()
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env bash
2
+ # manifest: monorepo-only
3
+ #
4
+ # chat-context-check.sh — the deep chat context reaches the prompt, and the
5
+ # checker that says so can go RED.
6
+ #
7
+ # WHAT IT GUARDS. Two fields carry the operator's ask: `contextPack.page.body`
8
+ # (the page read in full, not just its headings) and `contextPack.panel` (the
9
+ # tab open in the rail beside the chat, and its rows). Both cross four files —
10
+ # the sender (Chat.tsx), web's edge sanitizer (/api/chat), channels'
11
+ # sanitizer, and channels' renderer — and a field that any one of them does not
12
+ # name is DEAD while every other test stays green. That has happened four times
13
+ # on this exact path (`workflowId` twice, `focusBlockId`, `page`).
14
+ #
15
+ # WHY A SCRIPT AND NOT JUST THE SUITE. The suite proves the field arrives. This
16
+ # proves the SUITE would notice if it stopped: it cuts the copy line out of
17
+ # `sanitizeContextPack`, re-runs, and requires a failure. A green run against a
18
+ # gutted sanitizer would mean the assertions were reading something else.
19
+ #
20
+ # bash .claude/scripts/chat-context-check.sh # green half
21
+ # bash .claude/scripts/chat-context-check.sh --self-test # both halves
22
+ #
23
+ # Exit codes: 0 ok · 1 the suite is red on an untouched tree · 2 the RED PROOF
24
+ # failed (the suite stayed green with the wire cut) · 3 setup (dirty target).
25
+ set -uo pipefail
26
+
27
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
28
+ CTX="$ROOT/channels/src/context.ts"
29
+ SUITES=(test/context-pack-deep.test.ts test/context-pack-parity.test.ts)
30
+
31
+ run_suite() {
32
+ ( cd "$ROOT/channels" && bash "$ROOT/.claude/scripts/gate-run.sh" chat-ctx-check -- bunx vitest run "${SUITES[@]}" ) >"$1" 2>&1
33
+ }
34
+
35
+ TMP="$(mktemp -d)"
36
+ trap 'rm -rf "$TMP"' EXIT
37
+
38
+ echo "[chat-context] green half — the suite on an untouched tree"
39
+ if ! run_suite "$TMP/green.log"; then
40
+ echo "[chat-context] RED on an untouched tree — this is a real failure, not a proof:"
41
+ tail -30 "$TMP/green.log"
42
+ exit 1
43
+ fi
44
+ grep -E 'Tests +[0-9]+ passed' "$TMP/green.log" || true
45
+
46
+ if [ "${1:-}" != "--self-test" ]; then
47
+ echo "[chat-context] ok — pass --self-test for the red proof"
48
+ exit 0
49
+ fi
50
+
51
+ # ── RED PROOF ───────────────────────────────────────────────────────────────
52
+ # Cut the two copy lines out of channels' sanitizer, one at a time, and require
53
+ # the suite to notice. The file is restored on every exit path, including a
54
+ # SIGINT — a checker that can leave the tree mutated is worse than no checker.
55
+ if ! git -C "$ROOT" diff --quiet -- channels/src/context.ts; then
56
+ echo "[chat-context] refusing: channels/src/context.ts already has uncommitted edits."
57
+ echo " The red proof rewrites it and restores from THIS copy; it will not"
58
+ echo " step on work in progress."
59
+ exit 3
60
+ fi
61
+ cp "$CTX" "$TMP/context.ts.orig"
62
+ restore() { cp "$TMP/context.ts.orig" "$CTX"; }
63
+ trap 'restore; rm -rf "$TMP"' EXIT INT TERM
64
+
65
+ fail=0
66
+ cut_and_check() {
67
+ local label="$1" pattern="$2"
68
+ restore
69
+ if ! grep -q "$pattern" "$CTX"; then
70
+ echo "[chat-context] RED PROOF SETUP FAILED — no line matching: $pattern"
71
+ echo " The wire moved. Update this checker or it is guarding nothing."
72
+ fail=1
73
+ return
74
+ fi
75
+ grep -v "$pattern" "$TMP/context.ts.orig" > "$CTX"
76
+ if run_suite "$TMP/red-$label.log"; then
77
+ echo "[chat-context] RED PROOF FAILED ($label) — the suite stayed GREEN with the wire cut."
78
+ fail=1
79
+ else
80
+ echo "[chat-context] red proof ok: cutting $label turns the suite red"
81
+ fi
82
+ }
83
+
84
+ cut_and_check "page.body" "page.body = rawBody.slice"
85
+ cut_and_check "panel.tab" "pack.panel = panel"
86
+ restore
87
+
88
+ [ "$fail" -eq 0 ] || exit 2
89
+ echo "[chat-context] ok — the field arrives, and the checker can go red"