@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,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
|