@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,418 @@
1
+ #!/usr/bin/env node
2
+ // The generated id inventory — text/story-todo.md C1 deliverable, and the
3
+ // source `id:resolve` (C4) projects its metadata card from.
4
+ //
5
+ // Tony asked for seven id families. Seven hand-maintained registries is the
6
+ // measured 1% failure seven times over (text/story-factory-plan.md § 2), so
7
+ // there is ONE inventory with a generated `kind` discriminator. Every field
8
+ // here is READ from a source that already exists; nothing is hand-declared.
9
+ //
10
+ // Five kinds are counted from the repo. Two — `workflow` and `thing` — live in
11
+ // the graph, so they carry `provenance:"graph"`, `count:null` and a stated
12
+ // reason. `count:0` would be a lie: an unrun count is not a zero. The seventh
13
+ // kind is `thing`, the LOCKED dimension-3 word — never `object`
14
+ // (text/story-factory-plan.md § 2, § 0b).
15
+ //
16
+ // Two artifacts, one build, BOTH checked — the rule blocks-manifest.mjs states
17
+ // in its own header: an emitted file that nothing compares is a file that rots.
18
+ // one.ie/web/src/data/id-inventory.json the seven kinds and their entries
19
+ // one.ie/web/src/data/story.json the metadata card of the story family
20
+ //
21
+ // Usage:
22
+ // node .claude/scripts/id-inventory.mjs # write both files
23
+ // node .claude/scripts/id-inventory.mjs --check # diff, no write
24
+
25
+ import { execFileSync } from 'node:child_process'
26
+ import { existsSync, readFileSync, readdirSync, writeFileSync, mkdirSync } from 'node:fs'
27
+ import path from 'node:path'
28
+ import { fileURLToPath } from 'node:url'
29
+
30
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
31
+ const REPO_ROOT = path.resolve(__dirname, '../..')
32
+ const R = (...p) => path.join(REPO_ROOT, ...p)
33
+
34
+ const OUT_INVENTORY = R('one.ie/web/src/data/id-inventory.json')
35
+ const OUT_STORY = R('one.ie/web/src/data/story.json')
36
+ const STORY_MD = R('text/story.md')
37
+
38
+ const CHECK = process.argv.slice(2).includes('--check')
39
+
40
+ // ── the stamp ───────────────────────────────────────────────────────────────
41
+ // git HEAD, never wall-clock: a wall-clock stamp makes every regeneration a
42
+ // diff, so `--check` could never distinguish a stale artifact from a fresh one.
43
+ // It is DELIBERATELY excluded from the --check comparison (`stripStamp` below),
44
+ // the same way blocks-manifest.mjs's diffManifests never compares `measuredAt`.
45
+ // Comparing it would red this check on the very commit that lands the artifact:
46
+ // you generate at sha X, commit (minting sha Y), and X !== Y forever.
47
+ function headSha() {
48
+ try {
49
+ return execFileSync('git', ['rev-parse', 'HEAD'], { cwd: REPO_ROOT, encoding: 'utf8' }).trim()
50
+ } catch {
51
+ return 'unknown'
52
+ }
53
+ }
54
+
55
+ // ── frontmatter, the two fields every source here declares ──────────────────
56
+ // Deliberately not a YAML parser: `name` and `description` are single-line
57
+ // scalars in every SKILL.md and doc in this tree, and a dependency would put a
58
+ // build step in front of a script that must run from a bare checkout.
59
+ function frontmatter(file) {
60
+ const text = readFileSync(file, 'utf8')
61
+ if (!text.startsWith('---')) return {}
62
+ const end = text.indexOf('\n---', 3)
63
+ if (end === -1) return {}
64
+ const out = {}
65
+ for (const line of text.slice(4, end).split('\n')) {
66
+ const m = line.match(/^([a-zA-Z][a-zA-Z0-9_-]*):\s*(.*)$/)
67
+ if (!m) continue
68
+ let v = m[2].trim()
69
+ if (
70
+ (v.startsWith('"') && v.endsWith('"') && v.length > 1) ||
71
+ (v.startsWith("'") && v.endsWith("'") && v.length > 1)
72
+ ) {
73
+ v = v.slice(1, -1)
74
+ }
75
+ if (v) out[m[1]] = v
76
+ }
77
+ return out
78
+ }
79
+
80
+ // ── kind: signal ────────────────────────────────────────────────────────────
81
+ // Counted the way signals-parity.ts counts — `Object.keys(RECEIVERS).length`
82
+ // off the registry itself (packages/sdk/scripts/signals-parity.ts:21-22), not a
83
+ // grep. A grep over receivers.ts counts whatever the current formatting is; the
84
+ // import counts what the system actually ships.
85
+ function signals() {
86
+ const runner = [
87
+ `import { RECEIVERS } from ${JSON.stringify(R('packages/sdk/src/receivers.ts'))}`,
88
+ 'const out = Object.entries(RECEIVERS).map(([name, r]) => ({',
89
+ ' name, summary: typeof r?.summary === "string" ? r.summary : "",',
90
+ ' source: "packages/sdk/src/receivers.ts",',
91
+ '}))',
92
+ 'out.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))',
93
+ 'console.log(JSON.stringify(out))',
94
+ ].join('\n')
95
+ const raw = execFileSync('bun', ['-e', runner], {
96
+ cwd: REPO_ROOT,
97
+ encoding: 'utf8',
98
+ maxBuffer: 64 * 1024 * 1024,
99
+ })
100
+ return JSON.parse(raw.trim())
101
+ }
102
+
103
+ // ── kind: skill ─────────────────────────────────────────────────────────────
104
+ // `<dir>/<name>/SKILL.md`, ONE level deep, because that is what the running
105
+ // system treats as a skill: one.ie/web/src/lib/skill-catalog.ts:11 globs
106
+ // `../../ai/skills/*/SKILL.md`, skills-publish.sh:72 iterates `*/` and requires
107
+ // `$d/SKILL.md`, and asi-walk.sh:172 names the same shape. A recursive walk
108
+ // finds 325 SKILL.md under one.ie/ai/skills, but the 172 nested ones are not
109
+ // addressable as skill ids anywhere in the system — counting them would publish
110
+ // an id `id:resolve` could never resolve.
111
+ function skills() {
112
+ const out = []
113
+ for (const dir of ['one.ie/ai/skills', '.claude/skills']) {
114
+ const abs = R(dir)
115
+ if (!existsSync(abs)) continue
116
+ for (const name of readdirSync(abs).sort()) {
117
+ const file = path.join(abs, name, 'SKILL.md')
118
+ if (!existsSync(file)) continue
119
+ const fm = frontmatter(file)
120
+ out.push({
121
+ name: fm.name || name,
122
+ summary: fm.description || '',
123
+ source: `${dir}/${name}/SKILL.md`,
124
+ })
125
+ }
126
+ }
127
+ return out
128
+ }
129
+
130
+ // ── kind: component ─────────────────────────────────────────────────────────
131
+ // `jq '.count'`, NOT `jq 'length'` — the manifest is {measuredAt, count, blocks}
132
+ // so `length` is 3 and has been mistaken for the block count before
133
+ // (text/story-todo.md C1). The block vocabulary is itself generated, by
134
+ // .claude/scripts/blocks-manifest.mjs — this reads its artifact, never the
135
+ // Puck registry, so there is one generator per source.
136
+ function components() {
137
+ const file = R('one.ie/web/src/lib/puck/block-manifest.json')
138
+ const manifest = JSON.parse(readFileSync(file, 'utf8'))
139
+ const entries = Object.entries(manifest.blocks ?? {}).map(([name, b]) => ({
140
+ name,
141
+ summary: typeof b?.description === 'string' ? b.description : '',
142
+ source: 'one.ie/web/src/lib/puck/block-manifest.json',
143
+ }))
144
+ entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
145
+ // The manifest's own declared count is the oracle. If the blocks object and
146
+ // the count disagree, the manifest is stale and this inventory must not
147
+ // launder that into a confident number of its own.
148
+ if (typeof manifest.count === 'number' && manifest.count !== entries.length) {
149
+ throw new Error(
150
+ `block-manifest.json is stale: count=${manifest.count} but blocks=${entries.length}. Run: node .claude/scripts/blocks-manifest.mjs`,
151
+ )
152
+ }
153
+ return entries
154
+ }
155
+
156
+ // ── kind: doc ───────────────────────────────────────────────────────────────
157
+ // The `docs` CONTENT COLLECTION, not text/. text/ is the plan-and-promise
158
+ // surface (a `doc` id addresses a published page); the collection is what
159
+ // one.ie actually renders (text/story-factory-plan.md § 2).
160
+ function docs() {
161
+ const dir = R('one.ie/web/src/content/docs')
162
+ if (!existsSync(dir)) return []
163
+ const out = []
164
+ for (const name of readdirSync(dir).sort()) {
165
+ if (!/\.mdx?$/.test(name)) continue
166
+ const slug = name.replace(/\.mdx?$/, '')
167
+ const fm = frontmatter(path.join(dir, name))
168
+ out.push({
169
+ name: slug,
170
+ title: fm.title || slug,
171
+ summary: fm.description || '',
172
+ source: `one.ie/web/src/content/docs/${name}`,
173
+ })
174
+ }
175
+ return out
176
+ }
177
+
178
+ // ── kind: function ──────────────────────────────────────────────────────────
179
+ // `fn:`, never `fun:` — text/story-factory-plan.md § 0b, the sibling board's
180
+ // ruling, which this file is read through.
181
+ //
182
+ // The name charclass MUST carry `-`: TypeQL fun names are hyphenated
183
+ // (`fun strong-reach(...)`, roles.tql:39) and a `[a-zA-Z0-9_]*` pattern silently
184
+ // undercounts by 44 across schema/ — measured. The `^\s*fun\s+<name>\s*(` anchor
185
+ // also keeps `entity funnel`, `owns funnel-slug` and every `# fun ...` comment
186
+ // out; verified by inspecting every non-matching line containing "fun".
187
+ const FUN_RE = /^[ \t]*fun[ \t]+([a-zA-Z_][a-zA-Z0-9_-]*)[ \t]*\(/
188
+ function functions() {
189
+ const dir = R('schema')
190
+ const out = []
191
+ for (const name of readdirSync(dir).sort()) {
192
+ if (!name.endsWith('.tql')) continue
193
+ const lines = readFileSync(path.join(dir, name), 'utf8').split('\n')
194
+ lines.forEach((line, i) => {
195
+ const m = line.match(FUN_RE)
196
+ if (!m) return
197
+ out.push({
198
+ name: m[1],
199
+ summary: '',
200
+ source: `schema/${name}:${i + 1}`,
201
+ })
202
+ })
203
+ }
204
+ return out
205
+ }
206
+
207
+ // ── the inventory ───────────────────────────────────────────────────────────
208
+ // Seven kinds, always seven, in the order text/story-factory-plan.md § 2 lists
209
+ // them. Five carry a number read from the repo; two carry null and say why.
210
+ const GRAPH_REASON_WORKFLOW =
211
+ 'the graph, not the repo — workflows are rows the substrate holds, and a committed artifact cannot walk them. Count it through workflow:list, never here.'
212
+ const GRAPH_REASON_THING =
213
+ 'dimension 3, the graph — a substrate query. `thing` is the LOCKED word and the catch-all kind; `object` is refused.'
214
+
215
+ function buildInventory() {
216
+ const counted = {
217
+ signal: signals(),
218
+ skill: skills(),
219
+ component: components(),
220
+ doc: docs(),
221
+ function: functions(),
222
+ }
223
+ const sources = {
224
+ signal: 'packages/sdk/src/receivers.ts',
225
+ skill: 'one.ie/ai/skills/*/SKILL.md + .claude/skills/*/SKILL.md',
226
+ component: "one.ie/web/src/lib/puck/block-manifest.json (jq '.count')",
227
+ doc: 'one.ie/web/src/content/docs',
228
+ function: 'schema/*.tql',
229
+ }
230
+ // The address is COMPOSED, never stored. An entry carries `name`; the id is
231
+ // `<id_prefix>:<name>` — and `id_prefix` is published here, bare and without
232
+ // its colon, so the grammar travels with the data instead of being retyped in
233
+ // the resolver.
234
+ //
235
+ // Storing the composed string is what this artifact must NOT do, and the
236
+ // reason is a law, not a lint: this repo's tags are BARE words, and
237
+ // one.ie/web/tests/unit/in/namespace-parity.test.ts greps src/ for any
238
+ // literal starting `(fn|ticket|kind|state|status|topic|lifecycle):`. A stored
239
+ // `fn:<name>` is indistinguishable from a classification tag — to that grep
240
+ // and to a human reading the file — so materialising 213 of them in src/data
241
+ // published 213 tag-shaped strings into a tree whose central law forbids
242
+ // them. Measured: the gate went red on exactly those 213.
243
+ //
244
+ // `signal` has no prefix: a receiver's name IS its address (`factory:size`),
245
+ // which is why null is the honest value rather than an empty string.
246
+ const idPrefix = { signal: null, skill: 'skill', component: 'component', doc: 'doc', function: 'fn' }
247
+ const kinds = [
248
+ ...['signal', 'skill', 'component', 'doc', 'function'].map((kind) => ({
249
+ kind,
250
+ provenance: 'generated',
251
+ count: counted[kind].length,
252
+ id_prefix: idPrefix[kind],
253
+ source: sources[kind],
254
+ })),
255
+ { kind: 'workflow', provenance: 'graph', count: null, id_prefix: 'workflow', source: 'the substrate', reason: GRAPH_REASON_WORKFLOW },
256
+ { kind: 'thing', provenance: 'graph', count: null, id_prefix: 'thing', source: 'the substrate', reason: GRAPH_REASON_THING },
257
+ ]
258
+ return { generated_at: headSha(), kinds, entries: counted }
259
+ }
260
+
261
+ // ── the story family card (text/story.md's `family` deliverable) ────────────
262
+ // The eleven paths are PARSED OUT of text/story.md's own accept line, never
263
+ // re-typed here. That accept is `ls <paths…> 2>/dev/null | wc -l` compared
264
+ // `-eq 11`; reading the list from the promise is what makes `jq '.files |
265
+ // length'` and that `-eq` unable to disagree. A hand-copied list is exactly the
266
+ // hand-declared metadata this whole rung exists to replace.
267
+ function familyPaths() {
268
+ const md = readFileSync(STORY_MD, 'utf8')
269
+ const line = md.split('\n').find((l) => l.includes('accept:') && /ls\s+text\/story\.md/.test(l))
270
+ if (!line) throw new Error(`could not find the family accept line in ${STORY_MD}`)
271
+ const m = line.match(/ls\s+((?:\S+\s+)*?)2>\/dev\/null/)
272
+ if (!m) throw new Error(`the family accept line is not the expected \`ls … 2>/dev/null\` shape`)
273
+ const paths = m[1].trim().split(/\s+/).filter(Boolean)
274
+ const declared = line.match(/-eq\s+(\d+)/)
275
+ if (declared && Number(declared[1]) !== paths.length) {
276
+ throw new Error(
277
+ `text/story.md's family accept disagrees with itself: it lists ${paths.length} paths and asserts -eq ${declared[1]}`,
278
+ )
279
+ }
280
+ return paths
281
+ }
282
+
283
+ // One of the seven kinds for each family file, decided by a RULE, not by an
284
+ // eleven-row hand-written table — the same law the inventory above obeys.
285
+ //
286
+ // Nothing in the family maps to `signal`, and that is the honest answer rather
287
+ // than a gap: the family's signal side is `story:chain` and `id:resolve`, which
288
+ // live in packages/sdk/src/receivers.ts and are addressed as `story:chain`, not
289
+ // as a story-named file. Seven of the eleven land on `thing`, the dimension-3
290
+ // catch-all — a data artifact, a library, a page, a Move object, an agent and a
291
+ // test are all Things, and stretching one of them onto `component` or
292
+ // `function` would put a lie in the card `id:resolve` projects.
293
+ const KIND_RULES = [
294
+ [(p) => path.basename(p) === 'SKILL.md', 'skill', 'a SKILL.md is a skill'],
295
+ [(p) => p.endsWith('.tql') && /\/workflows\//.test(p), 'workflow', 'a .tql under */workflows/ is a workflow'],
296
+ [(p) => p.endsWith('.tql') && p.startsWith('schema/'), 'function', 'a .tql under schema/ declares funs'],
297
+ [(p) => p.endsWith('.tsx') && /\/components\//.test(p), 'component', 'a .tsx under */components/ is a component'],
298
+ [(p) => p.endsWith('.md') && p.startsWith('text/'), 'doc', 'a .md under text/ is a document'],
299
+ ]
300
+ function kindOf(p) {
301
+ for (const [test, kind, rule] of KIND_RULES) if (test(p)) return { kind, rule }
302
+ return { kind: 'thing', rule: 'the dimension-3 catch-all — never `object`' }
303
+ }
304
+
305
+ // This card is itself one of the eleven, and it is written LAST — so a plain
306
+ // existsSync said `exists:false` for `one.ie/web/src/data/story.json` at the
307
+ // instant that very file was being written, and the next --check went red
308
+ // reporting `committed=thing/false fresh=thing/true` forever (measured). The
309
+ // file's presence is what running this script guarantees, so the self-reference
310
+ // answers true. Every other path is probed on disk.
311
+ const SELF = path.relative(REPO_ROOT, OUT_STORY)
312
+
313
+ function buildStory() {
314
+ const paths = familyPaths()
315
+ const files = paths.map((p) => {
316
+ const { kind, rule } = kindOf(p)
317
+ return { path: p, kind, rule, exists: p === SELF ? true : existsSync(R(p)) }
318
+ })
319
+ return {
320
+ slug: 'story',
321
+ name: 'Story',
322
+ summary:
323
+ 'One slug, every technology — the eleven files text/story.md promises, each the best of its own technology. A told story generates this family for the teller\'s own slug.',
324
+ source: 'text/story.md',
325
+ generated_at: headSha(),
326
+ count: files.length,
327
+ present: files.filter((f) => f.exists).length,
328
+ files,
329
+ }
330
+ }
331
+
332
+ // ── check ───────────────────────────────────────────────────────────────────
333
+ const stripStamp = (o) => {
334
+ const { generated_at: _drop, ...rest } = o
335
+ return rest
336
+ }
337
+
338
+ function diffOne(label, outPath, fresh) {
339
+ if (!existsSync(outPath)) {
340
+ return [`- ${outPath} does not exist — run without --check first to seed it.`]
341
+ }
342
+ const committed = JSON.parse(readFileSync(outPath, 'utf8'))
343
+ if (JSON.stringify(stripStamp(committed)) === JSON.stringify(stripStamp(fresh))) return []
344
+ const lines = [`~ ${outPath} out of date — run: node .claude/scripts/id-inventory.mjs`]
345
+ // Say WHICH kind moved. A bare "out of date" makes the reader diff a 300KB
346
+ // file by hand, which is how a generated artifact stops being regenerated.
347
+ if (label === 'inventory') {
348
+ const byKind = (o) => Object.fromEntries((o.kinds ?? []).map((k) => [k.kind, k.count]))
349
+ const a = byKind(committed)
350
+ const b = byKind(fresh)
351
+ for (const kind of new Set([...Object.keys(a), ...Object.keys(b)])) {
352
+ if (a[kind] !== b[kind]) lines.push(` ~ ${kind}: committed=${a[kind]} fresh=${b[kind]}`)
353
+ }
354
+ // A kind whose COUNT is unchanged can still have drifted — a receiver
355
+ // whose summary was reworded moves no number. Report it, or the reader
356
+ // reads "out of date" against seven identical counts and assumes a bug.
357
+ for (const kind of Object.keys(fresh.entries ?? {})) {
358
+ const ca = (committed.entries ?? {})[kind]
359
+ const cb = (fresh.entries ?? {})[kind]
360
+ if (ca && cb && a[kind] === b[kind] && JSON.stringify(ca) !== JSON.stringify(cb)) {
361
+ lines.push(` ~ ${kind}: same count (${b[kind]}), different entries`)
362
+ }
363
+ }
364
+ } else {
365
+ const byPath = (o) => Object.fromEntries((o.files ?? []).map((f) => [f.path, `${f.kind}/${f.exists}`]))
366
+ const a = byPath(committed)
367
+ const b = byPath(fresh)
368
+ for (const p of new Set([...Object.keys(a), ...Object.keys(b)])) {
369
+ if (a[p] !== b[p]) lines.push(` ~ ${p}: committed=${a[p] ?? '(absent)'} fresh=${b[p] ?? '(absent)'}`)
370
+ }
371
+ }
372
+ // The per-item loops above key on a SUBSET of each file (a kind's count, a
373
+ // file's kind+exists). Anything outside that key — `rule`, `present`, `slug`,
374
+ // `summary`, `source`, `id_prefix` — differs without producing a line, and the
375
+ // reader gets a bare "out of date" header and diffs a 285KB file by hand.
376
+ // Whatever the comparator missed, name the top-level fields that moved.
377
+ if (lines.length === 1) {
378
+ const keys = new Set([...Object.keys(committed), ...Object.keys(fresh)])
379
+ const moved = [...keys]
380
+ .filter((k) => k !== 'generated_at')
381
+ .filter((k) => JSON.stringify(committed[k]) !== JSON.stringify(fresh[k]))
382
+ lines.push(
383
+ moved.length > 0
384
+ ? ` ~ fields differ (below the per-item comparator): ${moved.join(', ')}`
385
+ : ` ~ differs only in key ORDER or in generated_at handling — regenerate`,
386
+ )
387
+ }
388
+ return lines
389
+ }
390
+
391
+ function main() {
392
+ const inventory = buildInventory()
393
+ const story = buildStory()
394
+
395
+ if (CHECK) {
396
+ const diff = [
397
+ ...diffOne('inventory', OUT_INVENTORY, inventory),
398
+ ...diffOne('story', OUT_STORY, story),
399
+ ]
400
+ if (diff.length > 0) {
401
+ console.error('id-inventory --check: drift detected')
402
+ for (const line of diff) console.error(line)
403
+ process.exit(1)
404
+ }
405
+ const counts = inventory.kinds.map((k) => `${k.kind}=${k.count ?? 'graph'}`).join(' ')
406
+ console.log(`id-inventory --check: ok (no drift) — kinds=${inventory.kinds.length} ${counts} story.files=${story.count} present=${story.present}`)
407
+ process.exit(0)
408
+ }
409
+
410
+ mkdirSync(path.dirname(OUT_INVENTORY), { recursive: true })
411
+ writeFileSync(OUT_INVENTORY, JSON.stringify(inventory, null, 2) + '\n')
412
+ writeFileSync(OUT_STORY, JSON.stringify(story, null, 2) + '\n')
413
+ const counts = inventory.kinds.map((k) => `${k.kind}=${k.count ?? 'graph'}`).join(' ')
414
+ console.log(`wrote ${OUT_INVENTORY} — kinds=${inventory.kinds.length} ${counts}`)
415
+ console.log(`wrote ${OUT_STORY} — files=${story.count} present=${story.present}`)
416
+ }
417
+
418
+ main()