@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,415 @@
1
+ #!/usr/bin/env node
2
+ // one-agents.mjs — the BRIDGE: platform agents → Claude Code subagents.
3
+ //
4
+ // classification: monorepo-only (hard-asserts against one.ie/ai/agents/)
5
+ //
6
+ // Reads every `kind: agent` file under one.ie/ai/agents/**.md and emits a
7
+ // spawnable Claude Code subagent into .claude/agents/one/<name>.md.
8
+ //
9
+ // WHY THIS EXISTS — the whole point of /one's desk mode:
10
+ // A subagent starts with a FRESH context. That is exactly what a new tab
11
+ // buys, without losing the thread. The platform org chart already names 81
12
+ // agents with tier/reports_to/domain/subscribes; this makes them callable by
13
+ // the Agent tool so the desk can route to them instead of the operator
14
+ // opening another window.
15
+ //
16
+ // THE ONE HARD CONSTRAINT: a spawned subagent inherits NO parent CLAUDE.md
17
+ // (.claude/CLAUDE.md says so flatly). So the generated body INLINES the
18
+ // load-bearing substrate constants — otherwise the fresh context that solves
19
+ // the tab problem is also what makes every subagent violate the locked rules.
20
+ //
21
+ // Authority for the frontmatter shape: one.ie/web/src/lib/agent-md.ts
22
+ // (this script reads a deliberate SUBSET — name/tier/reports_to/domain/
23
+ // tagline/skills/subscribes — and never writes back).
24
+ //
25
+ // Usage:
26
+ // node .claude/scripts/one-agents.mjs # write .claude/agents/one/
27
+ // node .claude/scripts/one-agents.mjs --check # exit 1 if regeneration would change anything
28
+ // node .claude/scripts/one-agents.mjs --chart # print the routing table, emit nothing
29
+ // node .claude/scripts/one-agents.mjs --self-test
30
+
31
+ import { readFileSync, writeFileSync, readdirSync, mkdirSync, rmSync, existsSync } from 'node:fs'
32
+ import { join, relative, dirname } from 'node:path'
33
+ import { fileURLToPath } from 'node:url'
34
+
35
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..')
36
+ const SRC = join(ROOT, 'one.ie', 'ai', 'agents')
37
+ // FLAT, deliberately. Every agents/ directory on this machine — ten official
38
+ // plugins plus the harness itself — is flat, and not one of them nests. A
39
+ // subdirectory is unproven ground for the loader, so generated agents live
40
+ // beside w1-w4 and are told apart by the GENERATED marker, never by their path.
41
+ const OUT = join(ROOT, '.claude', 'agents')
42
+ const MARKER = '<!-- GENERATED by .claude/scripts/one-agents.mjs'
43
+
44
+ // Names the harness or the runtime already owns. Emitting one of these would
45
+ // shadow a real agent, so they are skipped loudly rather than silently.
46
+ const RESERVED = new Set([
47
+ 'claude', 'Explore', 'Plan', 'general-purpose', 'statusline-setup',
48
+ 'claude-code-guide', 'w1-recon', 'w2-decide', 'w3-edit', 'w4-verify', 'w4-tools',
49
+ ])
50
+
51
+ // The Agent tool accepts ONLY these. Platform agents declare `model:
52
+ // x-ai/grok-4.5` — an OpenRouter id that means nothing here, so it is mapped
53
+ // by tier and never copied through.
54
+ const MODEL_BY_TIER = {
55
+ chairman: 'opus', ceo: 'opus', director: 'opus',
56
+ specialist: 'sonnet', tool: 'haiku',
57
+ 1: 'sonnet', 2: 'sonnet', '1': 'sonnet', '2': 'sonnet',
58
+ }
59
+
60
+ // Engineering agents touch the repo; everyone else touches the world.
61
+ const TOOLS_ENGINEERING = 'Read, Grep, Glob, Bash, Edit, Write, WebFetch'
62
+ const TOOLS_WORLD = 'Read, Grep, Glob, Bash, Write, WebFetch, WebSearch'
63
+ const TOOLS_DIRECTOR = 'Read, Grep, Glob, Bash, WebFetch, WebSearch'
64
+
65
+ const COLOR_BY_DOMAIN = {
66
+ governance: 'purple', engineering: 'blue', marketing: 'orange',
67
+ sales: 'green', service: 'cyan', education: 'yellow',
68
+ finance: 'green', community: 'pink',
69
+ }
70
+
71
+ // ---------------------------------------------------------------- frontmatter
72
+
73
+ /** Minimal YAML-subset reader. Scalars, `[a, b]` inline lists, and `- item`
74
+ * block lists — which is everything these files actually use. Deliberately
75
+ * NOT a YAML parser: an unparseable value yields undefined and the agent is
76
+ * skipped, which is the safe direction. */
77
+ function frontmatter(text) {
78
+ if (!text.startsWith('---')) return null
79
+ const end = text.indexOf('\n---', 3)
80
+ if (end === -1) return null
81
+ const head = text.slice(4, end)
82
+ const body = text.slice(end + 4).replace(/^\n+/, '')
83
+ const meta = {}
84
+ let key = null
85
+ for (const raw of head.split('\n')) {
86
+ const line = raw.replace(/\s+$/, '')
87
+ if (!line || line.startsWith('#')) continue
88
+ const m = line.match(/^([A-Za-z_][\w-]*):\s*(.*)$/)
89
+ if (m) {
90
+ key = m[1]
91
+ const v = m[2].trim()
92
+ if (v === '') { meta[key] = []; continue }
93
+ if (v.startsWith('[') && v.endsWith(']')) {
94
+ meta[key] = v.slice(1, -1).split(',').map(s => s.trim().replace(/^["']|["']$/g, '')).filter(Boolean)
95
+ key = null
96
+ continue
97
+ }
98
+ meta[key] = v.replace(/^["']|["']$/g, '')
99
+ key = null
100
+ continue
101
+ }
102
+ const item = line.match(/^\s*-\s+(.*)$/)
103
+ if (item && key) {
104
+ if (!Array.isArray(meta[key])) meta[key] = []
105
+ // `- signal: campaign:brief` appears in the wild — keep the value half.
106
+ const val = item[1].replace(/^signal:\s*/, '').trim().replace(/^["']|["']$/g, '')
107
+ if (val) meta[key].push(val)
108
+ }
109
+ }
110
+ return { meta, body }
111
+ }
112
+
113
+ function walk(dir, acc = []) {
114
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
115
+ const p = join(dir, e.name)
116
+ if (e.isDirectory()) walk(p, acc)
117
+ else if (e.name.endsWith('.md')) acc.push(p)
118
+ }
119
+ return acc
120
+ }
121
+
122
+ // ------------------------------------------------------------------- collect
123
+
124
+ const SKIPPED = []
125
+
126
+ function collect() {
127
+ const out = []
128
+ const seen = new Map()
129
+ for (const path of walk(SRC).sort()) {
130
+ const rel = relative(ROOT, path)
131
+ let parsed
132
+ try { parsed = frontmatter(readFileSync(path, 'utf8')) } catch { parsed = null }
133
+ if (!parsed) { SKIPPED.push([rel, 'no frontmatter']); continue }
134
+ const { meta, body } = parsed
135
+
136
+ // `kind: agent` is the gate. It excludes AGENTS.md, README.md, the
137
+ // import-*/export-* connector stubs, and the doc files that live here.
138
+ if (meta.kind !== 'agent') { SKIPPED.push([rel, `kind=${meta.kind ?? '-'}`]); continue }
139
+
140
+ const name = String(meta.name ?? '').trim()
141
+ // The template file declares `name: <kebab-case>` with placeholder
142
+ // frontmatter. Without this guard the generator emits a subagent literally
143
+ // called `<kebab-case>`.
144
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) { SKIPPED.push([rel, `bad name: ${name || '(none)'}`]); continue }
145
+ if (RESERVED.has(name)) { SKIPPED.push([rel, 'reserved name']); continue }
146
+ if (meta.lifecycle && meta.lifecycle !== 'active') { SKIPPED.push([rel, `lifecycle=${meta.lifecycle}`]); continue }
147
+ if (seen.has(name)) { SKIPPED.push([rel, `duplicate of ${seen.get(name)}`]); continue }
148
+
149
+ seen.set(name, rel)
150
+ out.push({
151
+ name,
152
+ src: rel,
153
+ tier: String(meta.tier ?? 'specialist'),
154
+ reports_to: meta.reports_to ? String(meta.reports_to) : null,
155
+ domain: String(meta.domain ?? 'governance'),
156
+ title: meta.title ? String(meta.title) : name,
157
+ tagline: meta.tagline ? String(meta.tagline) : '',
158
+ skills: Array.isArray(meta.skills) ? meta.skills : (meta.skills ? [String(meta.skills)] : []),
159
+ subscribes: (Array.isArray(meta.subscribes) ? meta.subscribes : []).filter(t => t !== '*'),
160
+ body,
161
+ })
162
+ }
163
+ return out
164
+ }
165
+
166
+ // -------------------------------------------------------------------- render
167
+
168
+ /** The constants a subagent cannot look up, because it inherits no CLAUDE.md.
169
+ * Everything here is LOCKED in the substrate — a subagent that renames one of
170
+ * these breaks the schema, not just its own reply. */
171
+ const SUBSTRATE = `
172
+ ## Substrate constants (you inherit no CLAUDE.md — these are the locked facts)
173
+
174
+ You are running inside the ONE monorepo at \`/Users/toc/Server/one-ie\`.
175
+
176
+ **6 dimensions (LOCKED, never rename):** Groups · Actors · Things · Paths · Events · Learning.
177
+ Dead names that must never appear: knowledge, connections, people, node, scent, alarm, trail, colony.
178
+
179
+ **6 verbs (LOCKED):** \`signal\` · \`mark\` · \`warn\` · \`fade\` · \`follow\` · \`harden\`.
180
+ Every signal closes with \`mark()\`, \`warn()\`, or dissolve. **No silent returns.**
181
+
182
+ **Tags are BARE words** — \`marketing\`, \`lead\`, \`engineering\` — never \`lifecycle:marketing\`.
183
+ A namespaced subscribe tag matches zero signals.
184
+
185
+ **Structural time** — plan in tasks → waves → cycles. Never days/hours/weeks.
186
+
187
+ **Where things live:** web UI + API routes \`one.ie/web/\` · TypeDB schema \`schema/\` (never
188
+ duplicate .tql elsewhere) · specs and docs \`text/\` · payments \`pay/\` · agent runtime \`channels/\`.
189
+ Backend routes live ONLY in \`one.ie/web/src/pages/api/\`.
190
+
191
+ **Tests:** \`bun run verify:fast\` is the dev lane; the full \`bun run verify\` (~87s) is the review
192
+ gate. Say which lane you ran — a fast pass is never reported as a full pass. A test touching the
193
+ DOM needs \`// @vitest-environment jsdom\` on line 1.
194
+
195
+ **Where you collaborate, and where you plan.** \`/u/<slug>/in\` is the room. Its Spaces ARE
196
+ your \`subscribes:\` tags — \`FN_TAGS\` maps a department to bare words
197
+ (\`one.ie/web/src/lib/in/spaces.ts:28\`), so staking \`marketing\` IS having a desk in the
198
+ marketing Space. \`/u/<slug>/tasks\` is the same words as plannable rows.
199
+
200
+ **A task comment is a message in that room — one store, one renderer, one vocabulary.**
201
+ \`tasks:comment\` writes to the SAME D1 message store the inbox thread uses
202
+ (\`one.ie/web/src/lib/resolvers/tasks.ts:1639\`, thread id \`task:<tid>\` at \`:1665\`), and it
203
+ renders through the same \`Thread\` component the conversation uses
204
+ (\`one.ie/web/src/components/in/TaskDetail.tsx:13,859\`). An \`@mention\` in a comment body
205
+ INSERTs a \`follows\` row (\`resolvers/tasks.ts:1686-1693\`) — mentioning someone subscribes
206
+ them to that task. So: report where the work already is. Never open a second room, and never
207
+ invent a second vocabulary — the tag that routes your signal is the same word that names your
208
+ Space and tags the task.
209
+
210
+ **Proof discipline:** a presence grep is not proof. Probe the running thing. Report numbers you
211
+ actually measured, and say plainly when a check did not run — an unrun gate is not a pass.
212
+ `.trim()
213
+
214
+ /** How many agents stake each tag — used to drop the non-discriminating ones
215
+ * from descriptions. Rebuilt per render call set; cheap at this size. */
216
+ let STAKE_FREQ = new Map()
217
+ function indexStakes(roster) {
218
+ STAKE_FREQ = new Map()
219
+ for (const a of roster) for (const t of a.subscribes) STAKE_FREQ.set(t, (STAKE_FREQ.get(t) ?? 0) + 1)
220
+ }
221
+
222
+ function render(a, roster) {
223
+ if (STAKE_FREQ.size === 0) indexStakes(roster)
224
+ const model = MODEL_BY_TIER[a.tier] ?? 'sonnet'
225
+ const isDirector = a.tier === 'director' || a.tier === 'ceo' || a.tier === 'chairman'
226
+ const tools = isDirector ? TOOLS_DIRECTOR
227
+ : a.domain === 'engineering' ? TOOLS_ENGINEERING
228
+ : TOOLS_WORLD
229
+ const color = COLOR_BY_DOMAIN[a.domain] ?? 'blue'
230
+
231
+ // The description is what the Agent tool matches on when deciding whether to
232
+ // spawn this agent, so it must be TRIGGER-shaped — the tags it stakes on —
233
+ // not a job title.
234
+ //
235
+ // KEEP IT SHORT. Every description here is injected into the roster
236
+ // system-reminder on EVERY turn of EVERY session in this repo, desk or not.
237
+ // 80 agents is the difference between a few hundred tokens and several
238
+ // thousand of standing tax — and this design's whole thesis is that the tab
239
+ // stays light. So the description carries trigger words only. Tier, domain
240
+ // and reporting line are already in the body, where the spawned agent reads
241
+ // them, and none of the three helps the router decide to spawn this agent.
242
+ // Prefer DISCRIMINATING tags. `lead, mql, sql, marketing` is the department
243
+ // stake ~30 agents share — it costs bytes on every turn and tells the router
244
+ // nothing about which of the thirty to pick. `campaign:<id>:copy-needed`
245
+ // does. Fall back to the shared tags only when an agent has nothing else.
246
+ const own = a.subscribes.filter(t => (STAKE_FREQ.get(t) ?? 0) <= 5)
247
+ const stake = (own.length ? own : a.subscribes.length ? a.subscribes : [a.domain]).slice(0, 4).join(', ')
248
+ const desc = [
249
+ (a.tagline || `${a.title}.`).replace(/\s+/g, ' ').trim(),
250
+ `For: ${stake}.`,
251
+ ].join(' ').replace(/"/g, "'")
252
+
253
+ const reports = roster.filter(r => r.reports_to === a.name).map(r => r.name)
254
+ // The delegation line is tier-shaped. "You are a director, not a specialist"
255
+ // reads as a demotion on the top rung, whose whole function is that nobody is
256
+ // checking it — so the chairman gets the sentence its own job actually needs.
257
+ const delegation = a.tier === 'chairman'
258
+ ? `You are the top rung — nobody audits you, so ask what was MEASURED, never what was\nclaimed. Convene, press for the second answer, name disagreements. Do not do their work yourself.`
259
+ : `You are a director, not a specialist — decide which work is worth doing, name the agent\nthat owns the surface, and report back. Do not do their work yourself.`
260
+ const chain = reports.length
261
+ ? `\n## Your reports\n\nThese agents report to you and are spawnable by name: ${reports.join(' · ')}.\n${delegation}\n`
262
+ : ''
263
+
264
+ return `---
265
+ name: ${a.name}
266
+ description: "${desc}"
267
+ tools: "${tools}"
268
+ model: ${model}
269
+ color: ${color}
270
+ ---
271
+ ${MARKER} from ${a.src} — do not hand-edit.
272
+ Edit the platform agent, then re-run the generator. -->
273
+
274
+ ${SUBSTRATE}
275
+ ${chain}
276
+ ---
277
+
278
+ ${a.body.trim()}
279
+
280
+ ---
281
+
282
+ ## Closing the loop
283
+
284
+ Return a compact report: what you found or did, the file:line evidence, and one
285
+ sentence naming what you did NOT do. If you were asked for something you cannot
286
+ verify, say so — do not fill the gap with a plausible answer.
287
+ `
288
+ }
289
+
290
+ // ---------------------------------------------------------------------- main
291
+
292
+ const args = process.argv.slice(2)
293
+
294
+ if (args.includes('--self-test')) {
295
+ let fail = 0
296
+ const ok = (label, cond) => { console.log(`${cond ? 'ok ' : 'FAIL'} ${label}`); if (!cond) fail++ }
297
+
298
+ const fm = frontmatter('---\nname: x\nskills: [a, b]\nsubscribes:\n - lead\n - signal: route:to-x\n---\nbody here\n')
299
+ ok('scalar parsed', fm.meta.name === 'x')
300
+ ok('inline list parsed', JSON.stringify(fm.meta.skills) === '["a","b"]')
301
+ ok('block list parsed', JSON.stringify(fm.meta.subscribes) === '["lead","route:to-x"]')
302
+ ok('body split', fm.body.trim() === 'body here')
303
+ ok('no frontmatter -> null', frontmatter('# hello') === null)
304
+
305
+ // RED PROOF: the placeholder-name guard must actually bite. Without it the
306
+ // generator emits a subagent named `<kebab-case>`.
307
+ ok('placeholder name rejected', !/^[a-z][a-z0-9-]*$/.test('<kebab-case>'))
308
+ ok('reserved name held', RESERVED.has('w1-recon'))
309
+ // RED PROOF: an OpenRouter model id must never reach the Agent tool.
310
+ ok('grok id never mapped through', !Object.values(MODEL_BY_TIER).some(m => m.includes('/')))
311
+
312
+ const roster = collect()
313
+ ok(`roster non-empty (${roster.length})`, roster.length > 20)
314
+ ok('no placeholder in roster', !roster.some(r => r.name.includes('<')))
315
+ ok('every roster model is legal', roster.every(r => ['opus', 'sonnet', 'haiku', 'fable'].includes(MODEL_BY_TIER[r.tier] ?? 'sonnet')))
316
+ const ceo = roster.find(r => r.name === 'ceo')
317
+ ok('ceo present', !!ceo)
318
+ ok('ceo drops the "*" wildcard subscribe', ceo && !ceo.subscribes.includes('*'))
319
+ const rendered = ceo ? render(ceo, roster) : ''
320
+ ok('render inlines substrate constants', rendered.includes('6 verbs (LOCKED)'))
321
+ ok('render names the reports', rendered.includes('cto'))
322
+ ok('render carries the sweep marker', rendered.includes(MARKER))
323
+
324
+ // RED PROOF for the flat layout. The sweep deletes generated strays from a
325
+ // directory it SHARES with the hand-written harness agents, so the marker
326
+ // test is the only thing standing between a regenerate and losing w1-w4.
327
+ // Assert directly that they carry no marker and would therefore be invisible
328
+ // to it — and that a generated file WOULD be seen.
329
+ const HAND = ['w1-recon.md', 'w2-decide.md', 'w3-edit.md', 'w4-verify.md', 'w4-tools.md']
330
+ const survives = HAND.filter(f => {
331
+ const p = join(OUT, f)
332
+ return !existsSync(p) || !readFileSync(p, 'utf8').includes(MARKER)
333
+ })
334
+ ok(`sweep cannot see the ${HAND.length} hand-written harness agents`, survives.length === HAND.length)
335
+
336
+ // Every description is injected into the roster reminder on EVERY turn of
337
+ // EVERY session in this repo. This is the standing tax the design pays, so
338
+ // it is asserted, not assumed. ~90 chars x 80 agents ~= 7KB ~= 1.8k tokens.
339
+ const descBytes = roster.reduce((n, a) => n + render(a, roster).split('\n')[2].length, 0)
340
+ ok(`roster description budget ${(descBytes / 1024).toFixed(1)}KB < 14KB`, descBytes < 14 * 1024)
341
+
342
+ console.log(fail === 0 ? `\nPASS — ${roster.length} agents` : `\nFAIL — ${fail} check(s)`)
343
+ process.exit(fail === 0 ? 0 : 1)
344
+ }
345
+
346
+ const roster = collect()
347
+
348
+ if (args.includes('--chart')) {
349
+ const byDomain = {}
350
+ for (const a of roster) (byDomain[a.domain] ??= []).push(a)
351
+ console.log(`# ONE routing table — ${roster.length} spawnable agents\n`)
352
+ for (const d of Object.keys(byDomain).sort()) {
353
+ const list = byDomain[d]
354
+ const heads = list.filter(a => a.tier === 'director' || a.tier === 'ceo')
355
+ console.log(`## ${d} (${list.length})${heads.length ? ' — head: ' + heads.map(h => h.name).join(', ') : ''}`)
356
+ for (const a of list.sort((x, y) => x.name.localeCompare(y.name))) {
357
+ console.log(` ${a.name.padEnd(28)} ${a.tier.padEnd(11)} ${(a.subscribes.slice(0, 5).join(',') || '-')}`)
358
+ }
359
+ console.log('')
360
+ }
361
+ console.log(`skipped ${SKIPPED.length}:`)
362
+ for (const [f, why] of SKIPPED) console.log(` ${f} — ${why}`)
363
+ process.exit(0)
364
+ }
365
+
366
+ const files = new Map()
367
+ for (const a of roster) files.set(`${a.name}.md`, render(a, roster))
368
+
369
+ /** The files WE wrote, identified by the marker in their body and never by
370
+ * filename. This is the whole reason a flat directory is safe: w1-recon.md and
371
+ * the other hand-written harness agents carry no marker, so the sweep below
372
+ * cannot see them. */
373
+ function generatedOnDisk() {
374
+ if (!existsSync(OUT)) return []
375
+ return readdirSync(OUT)
376
+ .filter(f => f.endsWith('.md'))
377
+ .filter(f => { try { return readFileSync(join(OUT, f), 'utf8').includes(MARKER) } catch { return false } })
378
+ }
379
+
380
+ if (args.includes('--check')) {
381
+ let drift = 0
382
+ const existing = new Set(generatedOnDisk())
383
+ for (const [f, content] of files) {
384
+ const p = join(OUT, f)
385
+ const cur = existsSync(p) ? readFileSync(p, 'utf8') : null
386
+ if (cur !== content) { console.log(`DRIFT ${f}${cur === null ? ' (missing)' : ''}`); drift++ }
387
+ existing.delete(f)
388
+ }
389
+ for (const f of existing) { console.log(`STALE ${f}`); drift++ }
390
+ console.log(drift === 0 ? `ok — ${files.size} agents in sync` : `\n${drift} file(s) out of sync — run: node .claude/scripts/one-agents.mjs`)
391
+ process.exit(drift === 0 ? 0 : 1)
392
+ }
393
+
394
+ mkdirSync(OUT, { recursive: true })
395
+ let swept = 0
396
+ for (const f of generatedOnDisk()) if (!files.has(f)) { rmSync(join(OUT, f)); swept++ }
397
+
398
+ // Guard the WRITE the same way the delete is guarded. RESERVED only knows the
399
+ // five harness agents that exist today; a hand-written agent added tomorrow
400
+ // whose name collides with a platform agent would be silently overwritten —
401
+ // and would then carry the marker, so the next sweep would own it too. Refuse
402
+ // loudly instead.
403
+ let refused = 0
404
+ for (const [f, content] of files) {
405
+ const p = join(OUT, f)
406
+ if (existsSync(p) && !readFileSync(p, 'utf8').includes(MARKER)) {
407
+ console.log(`REFUSE ${f} — hand-written file, not overwriting (rename the platform agent)`)
408
+ refused++
409
+ continue
410
+ }
411
+ writeFileSync(p, content)
412
+ }
413
+ if (refused) console.log(`${refused} agent(s) refused`)
414
+ console.log(`wrote ${files.size} agents -> ${relative(ROOT, OUT)}/ (skipped ${SKIPPED.length}, swept ${swept})`)
415
+ console.log('NOTE: the roster is read at SESSION START — restart the session before spawning these.')
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bash
2
+ # one-resume.sh — the board, for a harness picking up someone else's fleets.
3
+ #
4
+ # bash .claude/scripts/one-resume.sh # print the board
5
+ # bash .claude/scripts/one-resume.sh --deploy # board, then land+ship what is ready
6
+ #
7
+ # Zero model tokens. It reads the workflow journals and git, and tells you which
8
+ # fleets finished, which are mid-flight, which branches are ready to land, and
9
+ # the exact Workflow({scriptPath, resumeFromRunId}) call to continue each one.
10
+ #
11
+ # WHY: a session that runs out of credits must not lose its fleets. Every run
12
+ # persists a script and a journal; a resume replays unchanged agents from CACHE
13
+ # for free and only re-runs what changed. The expensive mistake is re-launching a
14
+ # fleet whose work already landed — so this prints the branch state next to it.
15
+ set -uo pipefail
16
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
17
+ cd "$ROOT" || exit 1
18
+ DEPLOY=0
19
+ for a in "$@"; do case "$a" in --deploy) DEPLOY=1 ;; -h|--help) sed -n '2,18p' "${BASH_SOURCE[0]}"; exit 0 ;; esac; done
20
+
21
+ GRN=$'\033[32m'; YEL=$'\033[33m'; DIM=$'\033[2m'; OFF=$'\033[0m'
22
+ [[ -t 1 ]] || { GRN=""; YEL=""; DIM=""; OFF=""; }
23
+
24
+ PROJ="$HOME/.claude/projects"
25
+ echo "${DIM}══ fleets ══${OFF}"
26
+
27
+ # One journal per run: {"type":"started"} / {"type":"result"} lines.
28
+ found=0
29
+ while IFS= read -r j; do
30
+ [[ -f "$j" ]] || continue
31
+ run="$(basename "$(dirname "$j")")"
32
+ read -r done total < <(python3 - "$j" <<'PY'
33
+ import json,sys
34
+ s=e=0
35
+ for line in open(sys.argv[1]):
36
+ try: t=json.loads(line).get("type")
37
+ except Exception: continue
38
+ s += t=="started"; e += t=="result"
39
+ print(e, s)
40
+ PY
41
+ )
42
+ [[ "${total:-0}" == "0" ]] && continue
43
+ found=1
44
+ state="in flight"; mark="$YEL"
45
+ [[ "$done" == "$total" ]] && { state="DONE"; mark="$GRN"; }
46
+ script="$(ls -t "$PROJ"/*/*/workflows/scripts/*"${run#wf_}"* 2>/dev/null | head -1)"
47
+ printf '%s %-22s %s%s%s %s/%s agents\n' "$mark" "$run" "$mark" "$state" "$OFF" "$done" "$total"
48
+ [[ -n "$script" ]] && printf ' resume: Workflow({scriptPath: "%s", resumeFromRunId: "%s"})\n' "$script" "$run"
49
+ done < <(find "$PROJ" -path '*/subagents/workflows/wf_*/journal.jsonl' -mtime -3 2>/dev/null)
50
+ (( found )) || echo " (no workflow journals in the last 3 days)"
51
+
52
+ echo
53
+ echo "${DIM}══ branches ══${OFF}"
54
+ READY=()
55
+ while IFS= read -r br; do
56
+ [[ -z "$br" ]] && continue
57
+ ahead="$(git rev-list --count "main..$br" 2>/dev/null || echo 0)"
58
+ if [[ "$ahead" == "0" ]]; then
59
+ printf ' %-34s %sno commits beyond main%s\n' "$br" "$DIM" "$OFF"
60
+ else
61
+ printf ' %-34s %s%s commit(s) ready to land%s\n' "$br" "$GRN" "$ahead" "$OFF"
62
+ READY+=("$br")
63
+ fi
64
+ done < <(git for-each-ref --format='%(refname:short)' --sort=-committerdate refs/heads/feat 2>/dev/null | head -20)
65
+
66
+ echo
67
+ echo "${DIM}══ next ══${OFF}"
68
+ if (( ${#READY[@]} )); then
69
+ echo " bash .claude/scripts/land.sh ${READY[*]} --deploy"
70
+ else
71
+ echo " nothing to land yet — resume a fleet above, or ./deploy dev"
72
+ fi
73
+ echo " read text/HANDOFF.md BEFORE spawning anything new"
74
+
75
+ if (( DEPLOY )) && (( ${#READY[@]} )); then
76
+ echo
77
+ bash "$ROOT/.claude/scripts/land.sh" "${READY[@]}" --deploy
78
+ fi