@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.
- package/agents/abm-strategist.md +89 -0
- package/agents/ads-meta.md +97 -0
- package/agents/analyst.md +173 -0
- package/agents/architect.md +280 -0
- package/agents/brand-guardian.md +88 -0
- package/agents/brand-strategist.md +92 -0
- package/agents/campaign-content.md +90 -0
- package/agents/campaign-email.md +88 -0
- package/agents/campaign-sms.md +87 -0
- package/agents/campaign-social.md +87 -0
- package/agents/cco.md +99 -0
- package/agents/ceo.md +106 -0
- package/agents/chairman.md +120 -0
- package/agents/cmo.md +101 -0
- package/agents/community-greeter.md +89 -0
- package/agents/community-moderator.md +92 -0
- package/agents/compliance.md +92 -0
- package/agents/copywriter.md +100 -0
- package/agents/creative-strategist.md +95 -0
- package/agents/cro.md +98 -0
- package/agents/cto.md +267 -0
- package/agents/customer-interviewer.md +93 -0
- package/agents/customer-researcher.md +89 -0
- package/agents/customer-success-manager.md +90 -0
- package/agents/customer-trainer.md +90 -0
- package/agents/cxo.md +100 -0
- package/agents/demand-creator.md +89 -0
- package/agents/demo-mover.md +83 -0
- package/agents/demo-specialist.md +89 -0
- package/agents/demo-thai-family-law.md +87 -0
- package/agents/designer.md +112 -0
- package/agents/discovery-caller.md +98 -0
- package/agents/doctor.md +139 -0
- package/agents/educate-coach.md +100 -0
- package/agents/elevate-tutor.md +83 -0
- package/agents/email-lifecycle-marketer.md +91 -0
- package/agents/engage-specialist.md +99 -0
- package/agents/events-coordinator.md +90 -0
- package/agents/foundation-builder.md +94 -0
- package/agents/funnel-architect.md +89 -0
- package/agents/gift-creator.md +100 -0
- package/agents/google-ads.md +103 -0
- package/agents/guide.md +292 -0
- package/agents/helpdesk-dispatcher.md +88 -0
- package/agents/hook-specialist.md +91 -0
- package/agents/identify-optimizer.md +101 -0
- package/agents/implementer.md +305 -0
- package/agents/incident-commander.md +120 -0
- package/agents/insights-lead.md +93 -0
- package/agents/journey-runner.md +113 -0
- package/agents/linkedin-ads.md +123 -0
- package/agents/live-sales-chat.md +90 -0
- package/agents/market-researcher.md +88 -0
- package/agents/media-buyer.md +96 -0
- package/agents/movers-customer-researcher.md +95 -0
- package/agents/movers-foundation-builder.md +96 -0
- package/agents/movers-market-researcher.md +97 -0
- package/agents/movers-pricing-strategist.md +171 -0
- package/agents/nurture-architect.md +99 -0
- package/agents/offer-architect.md +92 -0
- package/agents/onboarder.md +222 -0
- package/agents/onboarding-specialist.md +92 -0
- package/agents/operations-dashboard.md +98 -0
- package/agents/perf-engineer.md +348 -0
- package/agents/playbook-writer.md +71 -0
- package/agents/plg-strategist.md +91 -0
- package/agents/positioning-architect.md +88 -0
- package/agents/press-officer.md +89 -0
- package/agents/pricing-strategist.md +94 -0
- package/agents/privacy-officer.md +92 -0
- package/agents/referral-manager.md +91 -0
- package/agents/refine-analyst.md +102 -0
- package/agents/release-manager.md +261 -0
- package/agents/renewals-upsell-rep.md +90 -0
- package/agents/review-engineer.md +327 -0
- package/agents/rewards-steward.md +82 -0
- package/agents/sales-call-coach.md +94 -0
- package/agents/sales-closer.md +98 -0
- package/agents/security-auditor.md +313 -0
- package/agents/sell-closer.md +101 -0
- package/agents/share-amplifier.md +99 -0
- package/agents/social-media-manager.md +92 -0
- package/agents/storyteller.md +250 -0
- package/agents/strategist.md +83 -0
- package/agents/strategy-aligner.md +94 -0
- package/agents/support-agent.md +90 -0
- package/agents/tagger.md +245 -0
- package/agents/tech-writer.md +216 -0
- package/agents/test-engineer.md +243 -0
- package/agents/tiktok-ads.md +105 -0
- package/agents/tracking-engineer.md +92 -0
- package/agents/upsell-strategist.md +101 -0
- package/agents/voice.md +85 -0
- package/agents/w4-tools.md +153 -0
- package/agents/w4-verify.md +20 -138
- package/agents/workflow-optimiser.md +84 -0
- package/commands/close.md +814 -160
- package/commands/create.md +2 -2
- package/commands/deploy.md +554 -175
- package/commands/do.md +119 -109
- package/commands/fast.md +67 -0
- package/commands/improve.md +2 -2
- package/commands/one.md +418 -0
- package/commands/see.md +1 -1
- package/commands/sync.md +1 -1
- package/commands/tasks.md +222 -0
- package/commands/vespio.md +110 -0
- package/commands/vespio.remote.md +227 -0
- package/hooks/hooks.json +25 -79
- package/hooks/lib/governor-escape-match.sh +88 -0
- package/hooks/lib/hook.sh +4 -13
- package/hooks/lib/signal.sh +9 -2
- package/hooks/scripts/branch-pin.sh +6 -0
- package/hooks/scripts/config-protect.sh +6 -0
- package/hooks/scripts/do-outcome-gate.sh +19 -2
- package/hooks/scripts/git-add-guard.sh +43 -2
- package/hooks/scripts/governor-escape.sh +96 -0
- package/hooks/scripts/load-guard.sh +127 -0
- package/hooks/scripts/post-edit-check.sh +36 -29
- package/hooks/scripts/session-start.sh +34 -80
- package/hooks/scripts/task-complete-verify.sh +46 -40
- package/package.json +1 -1
- package/rules/documentation.md +9 -0
- package/scripts/ad-copy-lint.sh +656 -0
- package/scripts/agentverse-audit.sh +256 -0
- package/scripts/asi-walk.sh +435 -0
- package/scripts/astro-build-cached.sh +143 -0
- package/scripts/blocks-manifest-cached.sh +100 -0
- package/scripts/blocks-manifest.mjs +497 -0
- package/scripts/blocks-ratchet.sh +116 -0
- package/scripts/blocks-render-probe.mjs +529 -0
- package/scripts/blocks-usage.mjs +334 -0
- package/scripts/chat-context-check.sh +89 -0
- package/scripts/close-metrics.sh +558 -0
- package/scripts/close-owner.sh +268 -0
- package/scripts/db-sync-lock-check.sh +116 -0
- package/scripts/deploy-dev.sh +35 -0
- package/scripts/deploy-emit.sh +298 -0
- package/scripts/deploy-gate-check.sh +128 -0
- package/scripts/deploy-ready.sh +78 -0
- package/scripts/deploy-record.sh +605 -0
- package/scripts/deploy.sh +1273 -0
- package/scripts/do-auto.sh +598 -48
- package/scripts/do-brief.sh +113 -0
- package/scripts/do-close.sh +1137 -0
- package/scripts/do-commit.sh +75 -0
- package/scripts/do-consumer-sweep.sh +18 -1
- package/scripts/do-cycle-shape-check.sh +160 -0
- package/scripts/do-decide.sh +476 -0
- package/scripts/do-derive-check.sh +436 -0
- package/scripts/do-fleet.sh +106 -28
- package/scripts/do-folder.sh +10 -1
- package/scripts/do-next.sh +106 -0
- package/scripts/do-orchestrate.sh +17 -5
- package/scripts/do-plan-json.mjs +201 -0
- package/scripts/do-plan-json.sh +8 -0
- package/scripts/do-preflight.sh +117 -0
- package/scripts/do-project.sh +157 -0
- package/scripts/do-prove-selftest.sh +108 -0
- package/scripts/do-prove.sh +295 -23
- package/scripts/do-rank.py +31 -1
- package/scripts/do-recon-cache.sh +7 -1
- package/scripts/do-recon-pack.sh +196 -0
- package/scripts/do-reconcile.sh +121 -4
- package/scripts/do-signal.sh +280 -23
- package/scripts/do-smoke.sh +18 -1
- package/scripts/do-test-gate.sh +80 -0
- package/scripts/do-tick.sh +102 -0
- package/scripts/do-tier.sh +6 -0
- package/scripts/do-triage.sh +182 -0
- package/scripts/do-ui-gate.sh +1 -1
- package/scripts/do-w4-gates.sh +451 -0
- package/scripts/do-walk.sh +12 -1
- package/scripts/env-sync.sh +173 -0
- package/scripts/factory-brief-check.sh +330 -0
- package/scripts/factory-check.sh +68 -14
- package/scripts/factory-close-check.sh +257 -0
- package/scripts/factory-emit.sh +211 -0
- package/scripts/factory-executor-check.mjs +353 -0
- package/scripts/factory-peak.sh +301 -0
- package/scripts/factory-repo.sh +118 -3
- package/scripts/factory-review-check.mjs +61 -0
- package/scripts/factory-ship.sh +61 -0
- package/scripts/factory-tasks-check.sh +18 -1
- package/scripts/factory-turn.sh +326 -0
- package/scripts/factory-walk.sh +396 -0
- package/scripts/factory-width.sh +57 -0
- package/scripts/fade-toxic.sh +4 -3
- package/scripts/fixtures/factory-brief-real.md +44 -0
- package/scripts/fixtures/triage-dupe.md +5 -0
- package/scripts/fleet-manifest.mjs +108 -0
- package/scripts/fleet-status.sh +110 -0
- package/scripts/full-suite-paths-check.sh +144 -0
- package/scripts/gate-reaper-check.sh +98 -0
- package/scripts/gate-reaper.sh +125 -0
- package/scripts/gate-run.sh +73 -0
- package/scripts/gc-content-check.sh +140 -0
- package/scripts/gen-dev-config.py +20 -0
- package/scripts/govern-bound-check.sh +60 -0
- package/scripts/govern-claims-check.sh +233 -0
- package/scripts/govern-mem-check.sh +290 -0
- package/scripts/governor-doors-check.sh +362 -0
- package/scripts/governor-escape-check.sh +171 -0
- package/scripts/health.sh +413 -0
- package/scripts/id-inventory.mjs +418 -0
- package/scripts/land.sh +551 -0
- package/scripts/lib/gc-finished.sh +77 -0
- package/scripts/lib/govern.sh +361 -0
- package/scripts/lib/govern.ts +756 -0
- package/scripts/lighthouse-run.sh +187 -0
- package/scripts/livekit-live-check.sh +61 -0
- package/scripts/livekit-ratchet.sh +46 -0
- package/scripts/load-guard-check.sh +49 -0
- package/scripts/machine-check.sh +102 -0
- package/scripts/machine-watch.sh +177 -0
- package/scripts/one-agents.mjs +415 -0
- package/scripts/one-resume.sh +78 -0
- package/scripts/orphan-baseline.json +182 -0
- package/scripts/orphan-modules.mjs +179 -0
- package/scripts/pr-body.sh +335 -0
- package/scripts/preview-fd-check.sh +289 -0
- package/scripts/promise-manifest.mjs +24 -1
- package/scripts/release.sh +322 -0
- package/scripts/roles-check.sh +946 -0
- package/scripts/sdk-build-cached.sh +64 -0
- package/scripts/signal-watch.sh +241 -0
- package/scripts/skills-publish.sh +94 -0
- package/scripts/speed-cache-check.sh +173 -0
- package/scripts/speed-check.mjs +907 -0
- package/scripts/speed-parity-check.sh +648 -0
- package/scripts/speed-waterfall-check.sh +355 -0
- package/scripts/substrate-env-parity.mjs +156 -0
- package/scripts/tasks-claim-race.mjs +108 -0
- package/scripts/tasks-loop.sh +185 -0
- package/scripts/test-cached.sh +255 -0
- package/scripts/test-full.sh +87 -0
- package/scripts/test-honesty.mjs +137 -0
- package/scripts/test-lanes.sh +166 -0
- package/scripts/test-speed.sh +94 -0
- package/scripts/triage-shape-check.sh +149 -0
- package/scripts/tsc-cached.sh +179 -0
- package/scripts/typedb-flake-check.sh +197 -0
- package/scripts/urls-lint.sh +15 -0
- package/scripts/verify-fast.sh +445 -0
- package/scripts/vespio-sync.sh +149 -0
- package/scripts/wf-check.mjs +104 -1
- package/scripts/worktree-preview.sh +879 -0
- package/scripts/worktree-up.sh +208 -0
- package/skills/livekit-agents/SKILL.md +285 -0
- package/skills/livekit-agents/references/freshness-rules.md +168 -0
- package/skills/shadcn/SKILL.md +1 -1
- package/skills/signal/SKILL.md +0 -1
- package/skills/voice/SKILL.md +94 -6
- package/skills/voice/corpus-check.sh +87 -0
- package/hooks/scripts/compact-hint.sh +0 -35
- package/hooks/scripts/gate-guard.sh +0 -83
- package/hooks/scripts/read-tracker.sh +0 -26
- package/hooks/scripts/session-end-verify.sh +0 -51
- package/hooks/scripts/stop-reflect.sh +0 -140
- package/hooks/scripts/sync-priority-todo.sh +0 -57
- package/hooks/scripts/sync-todo-docs.sh +0 -46
- package/hooks/scripts/tool-signal.sh +0 -48
- 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"
|