@oneie/claude 0.6.0 → 0.7.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 (111) hide show
  1. package/agents/w1-recon.md +9 -4
  2. package/agents/w2-decide.md +51 -28
  3. package/agents/w3-edit.md +70 -78
  4. package/agents/w4-verify.md +164 -69
  5. package/commands/browser.md +98 -35
  6. package/commands/cc-connect.md +6 -0
  7. package/commands/chat.md +13 -1
  8. package/commands/close.md +45 -19
  9. package/commands/create.md +20 -10
  10. package/commands/db-sync.md +5 -1
  11. package/commands/deploy.md +174 -87
  12. package/commands/do-autonomous.md +31 -20
  13. package/commands/do-improve.md +4 -4
  14. package/commands/do-show.md +4 -3
  15. package/commands/do.md +53 -13
  16. package/commands/go.md +75 -0
  17. package/commands/improve.md +4 -2
  18. package/commands/kill.md +6 -8
  19. package/commands/notify.md +15 -2
  20. package/commands/oo-push.md +24 -9
  21. package/commands/rag.md +6 -0
  22. package/commands/release.md +46 -25
  23. package/commands/restart.md +5 -4
  24. package/commands/see.md +45 -35
  25. package/commands/skill-create.md +38 -5
  26. package/commands/sync.md +21 -11
  27. package/hooks/scripts/auto-continue.sh +70 -0
  28. package/hooks/scripts/branch-pin.sh +85 -0
  29. package/hooks/scripts/git-add-guard.sh +16 -5
  30. package/hooks/scripts/post-edit-check.sh +16 -0
  31. package/hooks/scripts/session-start.sh +13 -8
  32. package/hooks/scripts/sync-priority-todo.sh +5 -0
  33. package/package.json +1 -1
  34. package/rules/api.md +9 -0
  35. package/rules/design.md +13 -3
  36. package/rules/documentation.md +30 -71
  37. package/rules/engine.md +8 -1
  38. package/rules/ui.md +5 -1
  39. package/scripts/__pycache__/do-rank.cpython-313.pyc +0 -0
  40. package/scripts/browser-check.mjs +44 -103
  41. package/scripts/cc-events-proof.sh +13 -12
  42. package/scripts/chrome.mjs +381 -0
  43. package/scripts/do-analyze.sh +109 -4
  44. package/scripts/do-auto.sh +350 -43
  45. package/scripts/do-consumer-sweep.sh +72 -0
  46. package/scripts/do-derives-check.sh +88 -0
  47. package/scripts/do-fleet.sh +50 -1
  48. package/scripts/do-killswitch-audit.py +33 -14
  49. package/scripts/do-promise-lint.sh +150 -4
  50. package/scripts/do-promise-settle.sh +131 -9
  51. package/scripts/do-prove.sh +197 -17
  52. package/scripts/do-rank.py +606 -20
  53. package/scripts/do-rubric.py +21 -2
  54. package/scripts/do-signal.sh +157 -17
  55. package/scripts/do-smoke.sh +9 -0
  56. package/scripts/do-substrate-check.sh +49 -0
  57. package/scripts/do-tasks-wire-check.sh +81 -0
  58. package/scripts/do-tier.sh +90 -5
  59. package/scripts/do-ui-gate.sh +332 -0
  60. package/scripts/do-untracked-gate.sh +231 -0
  61. package/scripts/do-walk.sh +344 -0
  62. package/scripts/factory-check.sh +747 -0
  63. package/scripts/factory-repo.sh +1361 -0
  64. package/scripts/factory-tasks-check.sh +85 -0
  65. package/scripts/outcome-pull.ts +279 -0
  66. package/scripts/promise-manifest.mjs +167 -0
  67. package/scripts/reconcile-allow.txt +5 -0
  68. package/scripts/rubric-weights.json +64 -0
  69. package/scripts/spine-canary.sh +20 -0
  70. package/scripts/typedb-cluster-status.sh +128 -0
  71. package/scripts/typedb-env.sh +182 -0
  72. package/scripts/typedb-flap-recorder.sh +85 -0
  73. package/scripts/typedb-probes/containment-probe.py +88 -0
  74. package/scripts/typedb-probes/panic-probe-version.py +92 -0
  75. package/scripts/typedb-probes/panic-probe.py +77 -0
  76. package/scripts/typedb-scratch.sh +73 -0
  77. package/scripts/urls-lint.sh +58 -0
  78. package/scripts/w4-rubric.ts +96 -12
  79. package/scripts/wf-check.mjs +75 -0
  80. package/skills/ai-ui/SKILL.md +1 -1
  81. package/skills/astro/SKILL.md +177 -132
  82. package/skills/astro/add-content-collection.md +29 -13
  83. package/skills/astro/check-build.md +19 -13
  84. package/skills/astro/create-component.md +36 -9
  85. package/skills/astro/create-page.md +39 -10
  86. package/skills/astro/optimize-performance.md +36 -8
  87. package/skills/cli/SKILL.md +254 -220
  88. package/skills/dev/SKILL.md +1 -1
  89. package/skills/directory-autofill/SKILL.md +68 -0
  90. package/skills/docs/SKILL.md +4 -4
  91. package/skills/mcp/SKILL.md +186 -156
  92. package/skills/promise-make/SKILL.md +2 -2
  93. package/skills/puck/SKILL.md +465 -250
  94. package/skills/react19/SKILL.md +196 -296
  95. package/skills/reactflow/SKILL.md +387 -412
  96. package/skills/sdk/SKILL.md +167 -106
  97. package/skills/shadcn/SKILL.md +245 -201
  98. package/skills/signal/SKILL.md +5 -0
  99. package/skills/sui/SKILL.md +6 -1
  100. package/skills/tutorial/SKILL.md +15 -0
  101. package/skills/typedb/SKILL.md +427 -217
  102. package/skills/typedb/examples/python-patterns.py +11 -4
  103. package/skills/typedb/examples/query-patterns.tql +8 -3
  104. package/skills/typedb/examples/schema-patterns.tql +4 -2
  105. package/skills/typedb/reference/migration-2x-3x.md +19 -1
  106. package/skills/typedb/reference/python-driver.md +17 -1
  107. package/skills/voice/SKILL.md +117 -0
  108. package/skills/writer/SKILL.md +29 -12
  109. package/templates/template-feature.md +26 -1
  110. package/templates/template-tests.md +4 -0
  111. package/templates/template-todo.md +65 -10
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: directory-autofill
3
+ description: Fill directory-submission forms in the operator's own Chrome via claude-in-chrome, one queued row at a time, with the human approving every submit. Use whenever an operator says "file with agent", "file these directories", "run the directory autofill", or opens the File-with-agent door on the /u/[slug]/directories board. Never clicks submit, never solves a challenge, always stamps filedBy — see HARD RULES.
4
+ ---
5
+
6
+ # directory-autofill — the browser hand for directory submissions
7
+
8
+ **Purpose:** Turn a run's queued directory rows into filled, human-approved submissions inside the operator's own logged-in Chrome, using the `claude-in-chrome` MCP tools — `tabs_create_mcp` / `navigate` to open the submit page, `read_page` + `find` to locate inputs, `form_input` to fill them, `computer` for a screenshot (there is no `screenshot` tool; screenshots are a `computer` action). Design + data model: `text/directory-submission-plan.md` § The browser hand. The one page this drives: `/u/[slug]/directories`.
9
+
10
+ ## HARD RULES
11
+
12
+ - **The human owns the submit.** This skill fills a form and stops. It never clicks the directory's submit button, never confirms a modal that finalizes a listing, and never creates an account on the operator's behalf. The operator reviews the filled form and approves the submit themselves.
13
+ - **Never solves a challenge.** Captcha, email verification, phone verification, or any anti-bot step hands control straight back to the human — this skill does not attempt to defeat them. Resume the loop on the next queued row once the human clears it (or skip and move on).
14
+ - **Provenance always.** Every `POST /api/directories/update` call this skill makes carries `filedBy: "agent"`. If a human takes over mid-fill and finishes the submit themselves, the row is recorded `filedBy: "human"` instead — never claim agent credit for a human-finished row.
15
+ - **One generic matcher, zero per-site scripts.** The `DirectoryField` key set is closed — seven shared templates in `registry.ts` (`F_LOCAL`, `F_REVIEW`, `F_B2B`, `F_STARTUP`, `F_AITOOL`, `F_EMAIL`, `F_VERTICAL`) supply every key, and no directory declares fields inline — and packs are deterministic; the only judgment this skill exercises is "which input on this page is *phone*", via label/placeholder/autocomplete-attribute matching. All 306 directories share this one code path. A site that defeats matching gets filed by hand instead — never write a bespoke per-site scraper.
16
+ - **No stored credentials.** Auth to `/api/directories/*` rides the operator's own session, the same way other operator-side skills call the ONE API (`cc-connect`/do-signal pattern). This skill holds no secrets of its own and never asks for or stores a directory's login.
17
+ - **Watched, paced batches.** Work 10–20 queued rows per sitting, not the whole run unattended — the desktop session is the throttle. Directory sites are exactly the sites with captchas and bot detection; a human present in their own browser dissolves both without evading anything.
18
+
19
+ ## The loop
20
+
21
+ ```
22
+ GET /api/directories/run?slug=<slug>&runId=<runId>
23
+ │ read the frozen packs for queued rows (batch 10–20)
24
+
25
+ open directory.submitUrl in a new Chrome tab
26
+
27
+ read the page, match each pack.fields[key] onto the
28
+ closest form input by label / placeholder / autocomplete
29
+
30
+ human checkpoint — the operator reviews the filled
31
+ form and clicks/approves the actual submit
32
+
33
+ capture evidence: confirmation text, listing URL if
34
+ shown, a screenshot
35
+
36
+ POST /api/directories/update
37
+ { slug, id, status: "submitted", filedBy: "agent" }
38
+ (and, once the listing appears, a later
39
+ { status: "live", listingUrl } call — evidence, not
40
+ assertion, per the status walk)
41
+
42
+ next queued row · challenge encountered → hand to the
43
+ human, resume the loop once cleared (or move on)
44
+ ```
45
+
46
+ ## Steps
47
+
48
+ 1. **Get the run.** Ask the operator (or read from the board context) which workspace `slug` and `runId` to work. `GET /api/directories/run?slug=<slug>&runId=<runId>` returns `{ submissions: [{ id, directorySlug, status, pack, listingUrl }] }`, where `pack` is the frozen `{ fields, missing }`. Keep each row's `id` — it is the handle every `update` call needs. Only act on rows with `status: "queued"`.
49
+ 2. **Look up the directory.** Cross-reference `directorySlug` against `one.ie/web/src/lib/directories/registry.ts`'s `DIRECTORIES` for the `submitUrl`, `method`, and `fields` list — the pack's `fields` map already carries the values, keyed the same as the registry's `DirectoryField.key`s.
50
+ 3. **Open and fill.** Navigate to `submitUrl` in the operator's Chrome. For a `method: 'form'` directory, read the page and match each `pack.fields` entry onto the input whose label/placeholder/autocomplete most plausibly matches that key. The twelve keys the templates can emit: `name`, `company`, `address`, `city`, `phone`, `email`, `website`, `url`, `category`, `tagline`, `description`, `pricing`. For a `method: 'email'` directory, draft the email in the operator's mail client instead of a web form, still stopping before send.
51
+ 4. **Stop at the human checkpoint.** Once every matchable field is filled, stop and tell the operator plainly: "Form filled for {directory.name} — review and submit when ready." Do not proceed until the operator confirms they submitted it (or tells you to skip).
52
+ 5. **Record the evidence.** After the operator confirms, `POST /api/directories/update` with `{ slug, id, status: "submitted", filedBy: "agent" }`. If the directory's confirmation page shows a live listing URL immediately, follow up once confirmed live with `{ status: "live", listingUrl }` — `listingUrl` is required to mark live, and the route 400s without it. If a listing takes longer to appear, leave the row `submitted` — the operator marks it live later from the board.
53
+ 6. **Handle challenges and mismatches.** A captcha, email/phone verification loop, or a form the matcher can't confidently map → stop, tell the operator this row needs a by-hand fill from the pack drawer, and move to the next queued row. Never guess a field mapping you're not confident about — an empty field the operator notices beats a wrong value they don't.
54
+ 7. **Repeat, paced.** Work through the batch (10–20 rows), then stop and summarize: how many submitted, how many handed to the human, how many skipped and why.
55
+
56
+ ## Failure modes (designed in, not edge cases)
57
+
58
+ - **No Chrome session / extension not available** — refuse at startup with a plain message; tell the operator the pack drawer's by-hand path (copy fields, open submit page, mark submitted) is always the fallback.
59
+ - **Field mapping fails** — leave the row `queued`, note which fields couldn't be matched, move on. Never invent a value for a field the pack doesn't supply.
60
+ - **Directory site redesigned / submitUrl dead** — stop and leave the row `queued` with a note saying why. The status walk (`ALLOWED_TRANSITIONS` in `update.ts`) only allows `queued → submitted` and `submitted → live | rejected`, so a queued row **cannot** be marked `rejected` — that call 400s with `invalid_transition`. `rejected` is the verdict on a submission the directory turned down, not on a directory that can't be reached. A note on the stuck row is the signal that routes the registry fix and future runs away from this directory.
61
+
62
+ ## See also
63
+
64
+ - `text/directory-submission.md` — the promise
65
+ - `text/directory-submission-plan.md` — data model, API shapes, § The browser hand
66
+ - `text/directory-submission-ui.md` — the File-with-agent door on the board
67
+ - `one.ie/web/src/lib/directories/registry.ts` — `DIRECTORIES`, `Directory`, `DirectoryField`
68
+ - `one.ie/web/src/pages/api/directories/run.ts` · `update.ts` — the two guarded routes this skill calls
@@ -9,7 +9,7 @@ This skill is the **middle layer** of three. It does not write prose (that's the
9
9
 
10
10
  A generic writing skill can make any paragraph clearer. It cannot tell you that the paragraph doesn't belong in a `-reference.md` at all. That judgment is this skill.
11
11
 
12
- **The order:** invoke `docs` (pick type + rules) → copy the matching `template-*.md` (shape) → invoke `writer` (craft the prose) → check `.claude/product-marketing.md` (voice).
12
+ **The order:** invoke `docs` (pick type + rules) → copy the matching `template-*.md` (shape) → invoke `writer` (craft the prose) → invoke `voice` (pick + apply the register) → check `.claude/product-marketing.md` (product context).
13
13
 
14
14
  The full taxonomy lives in [`text/docs.md`](../../../text/docs.md); the template map in [`text/templates.md`](../../../text/templates.md). This skill is how you *apply* them.
15
15
 
@@ -119,9 +119,9 @@ A dense, token-lean briefing for an AI agent that must *act*: the signal chain,
119
119
  ## How it composes with the other layers
120
120
 
121
121
  1. **Pick the type** (this skill) — reader + question → type. If the content spans two types, split before writing.
122
- 2. **Copy the template** — `cp text/template-<type>.md text/<slug>-<suffix>.md`. The template is the shape; never write a doc-spine artifact from scratch.
122
+ 2. **Copy the template** — the type → template map is the table in [`text/templates.md`](../../../text/templates.md); most types are `template-<type>.md`, with two exceptions worth knowing before you `cp` the wrong file: the **promise** copies `template-feature.md` (`template-promise.md` is a FOLDED stub — do not copy it) and the **explanation** copies `template-teach.md` (there is no `template-docs.md`). The template is the shape; never write a doc-spine artifact from scratch.
123
123
  3. **Write with `writer`** — apply the craft loop (cut, show-don't-tell, structure, sentence polish). This skill says *what goes where*; `writer` makes each sentence land.
124
- 4. **Check voice** — `.claude/product-marketing.md`: ONE's audience (CEOs + engineers), simple English, banned words. Run `do-reconcile.sh dictionary` on the file before committing (no dead names, no new synonyms).
124
+ 4. **Check voice** — `.claude/product-marketing.md`: ONE's audience (CEOs + engineers), simple English, banned words. Run `bash .claude/scripts/do-reconcile.sh dictionary text/<file>.md` before committing (no dead names, no new synonyms) — the scripts are not on PATH.
125
125
 
126
126
  ---
127
127
 
@@ -150,4 +150,4 @@ A dense, token-lean briefing for an AI agent that must *act*: the signal chain,
150
150
 
151
151
  ---
152
152
 
153
- *Taxonomy: [`text/docs.md`](../../../text/docs.md). Templates: [`text/templates.md`](../../../text/templates.md). Craft: `writer` skill. Voice: [`.claude/product-marketing.md`](../../product-marketing.md).*
153
+ *Taxonomy: [`text/docs.md`](../../../text/docs.md). Templates: [`text/templates.md`](../../../text/templates.md). Craft: `writer` skill. Register: `voice` skill → [`text/voice-and-tone.md`](../../../text/voice-and-tone.md) (senior) + [`text/writing-style-guide.md`](../../../text/writing-style-guide.md). Product context: [`.claude/product-marketing.md`](../../product-marketing.md).*
@@ -1,242 +1,272 @@
1
+ ---
2
+ name: mcp
3
+ description: Add or change a tool in @oneie/mcp — the MCP server exposing ONE to Claude Desktop, Cursor, and any MCP client. Use when adding an MCP tool, editing anything under packages/mcp/src/tools/, writing a tool inputSchema, fixing MCP_TOOLS manifest drift when `bun run check:tools` fails, or deciding whether a new capability needs a tool at all (usually it does not — signal/ask already reach every receiver). Triggers — "add an MCP tool", "new tool for the MCP server", "check:tools is failing", "MCP_TOOLS drift", "expose X to Claude Desktop", "wire the oneie MCP server".
4
+ ---
5
+
1
6
  # MCP tools — Model Context Protocol for agents
2
7
 
3
- MCP tools give agents programmatic access to ONE capabilities. They're scaffolded, typed, and auto-discovered.
8
+ MCP tools give agents programmatic access to ONE capabilities. Each tool is a
9
+ plain object registered on a router; the router is served over stdio.
4
10
 
5
11
  ## Structure
6
12
 
7
13
  ```
8
14
  packages/mcp/
9
15
  ├── src/
10
- │ ├── index.ts # MCP server entrypoint
11
- │ ├── tools/ # tool implementations
12
- ├── index.ts # tool registry + re-exports
13
- ├── chat/ # chat-related tools
14
- │ │ ├── index.ts
15
- │ │ └── memory.ts
16
- │ └── {domain}/
17
- ├── index.ts
18
- └── {action}.ts
19
- └── schema.ts # Zod schemas for tool inputs
16
+ │ ├── index.ts # createOneRouter() + MCP_TOOLS + bin
17
+ │ ├── serve.ts # McpTool, McpRouter, apiCall(), serve()
18
+ │ ├── env.ts # readEnv() { baseUrl, apiKey }
19
+ │ ├── telemetry.ts
20
+ └── tools/ # one file per group: <group>Tools()
21
+ ├── substrate.ts # 6 verbs + dims (locked)
22
+ ├── lifecycle.ts # agents, skills, pay
23
+ │ ├── observability.ts # stats, health, export
24
+ ├── discovery · social · video · broadcast
25
+ ├── messaging · seo · views · workflow · tasks
26
+ │ └── fn.ts # generated per allowlisted fn
20
27
  ```
21
28
 
29
+ There is no `src/schema.ts` and no per-domain subdirectory — a tool group is one
30
+ flat file exporting one `<group>Tools(): McpTool[]` function.
31
+
22
32
  ## Pattern: Adding an MCP tool
23
33
 
24
- ### 1. Define the schema (input + output types)
34
+ **First ask whether you need one.** `signal` and `ask` address any receiver by
35
+ name — the registry declares 273 of them today. (The registry is the capability
36
+ *catalog*; a receiver is only reachable once a handler is bound in
37
+ `world-receivers.ts` or a channels tool module. A declared-but-unbound name
38
+ type-checks and 404s.) A new product action needs a receiver, not a tool.
39
+ The documented exceptions (`views.ts`, `workflow.ts`, `tasks.ts`) exist because
40
+ those are first-class product objects other clients address by name — and even
41
+ they add no new receiver, they route through the `/api/ask` door.
25
42
 
26
- ```typescript
27
- // packages/mcp/src/schema.ts
28
- import { z } from 'zod';
43
+ ### 1. Define the input schema (raw JSON Schema, not Zod)
29
44
 
30
- export const memoryToolSchema = z.object({
31
- groupId: z.string().describe('Group ID'),
32
- content: z.string().describe('Memory content to store'),
33
- });
45
+ `McpTool.inputSchema` is `Record<string, unknown>` and is handed to the MCP
46
+ client verbatim. Zod is an SDK dependency, not an MCP one.
34
47
 
35
- export type MemoryToolInput = z.infer<typeof memoryToolSchema>;
48
+ ```typescript
49
+ // inside packages/mcp/src/tools/broadcast.ts
50
+ const inputSchema = {
51
+ type: "object",
52
+ properties: {
53
+ broadcastId: { type: "string" },
54
+ },
55
+ required: ["broadcastId"],
56
+ };
36
57
  ```
37
58
 
38
59
  ### 2. Implement the tool
39
60
 
40
61
  ```typescript
41
- // packages/mcp/src/tools/chat/memory.ts
42
- import { SubstrateClient } from '@oneie/sdk';
43
- import { memoryToolSchema, MemoryToolInput } from '../schema';
44
-
45
- export async function memoryTool(input: MemoryToolInput): Promise<string> {
46
- const client = new SubstrateClient();
47
- const memory = await client.memory(input.groupId);
48
-
49
- if (!memory) {
50
- return 'No memory found for this group.';
51
- }
52
-
53
- return JSON.stringify(memory);
62
+ // packages/mcp/src/tools/broadcast.ts
63
+ import { apiCall, type McpTool } from "../serve.js";
64
+
65
+ function ask(env: { baseUrl: string; apiKey?: string }, receiver: string, data: unknown) {
66
+ return apiCall(env.baseUrl, env.apiKey, `/api/ask/${encodeURIComponent(receiver)}`, {
67
+ method: "POST",
68
+ body: JSON.stringify({ data }),
69
+ });
54
70
  }
55
71
 
56
- export const MEMORY_TOOL = {
57
- name: 'memory',
58
- description: 'Read or write group memory',
59
- inputSchema: memoryToolSchema,
60
- execute: memoryTool,
61
- };
72
+ export function broadcastTools(): McpTool[] {
73
+ return [
74
+ {
75
+ name: "broadcast_send",
76
+ description: "Send a broadcast now (inline drain, suppression-checked).",
77
+ inputSchema: {
78
+ type: "object",
79
+ properties: { broadcastId: { type: "string" } },
80
+ required: ["broadcastId"],
81
+ },
82
+ handler: async (args, env) => ask(env, "broadcast:send", args),
83
+ },
84
+ ];
85
+ }
62
86
  ```
63
87
 
64
88
  **Rules:**
65
- - Schema first (using Zod)
66
- - Function receives typed input
67
- - Function returns string (MCP protocol requirement)
68
- - Tool object has name, description, inputSchema, execute
69
- - Tool calls SDK methods, never direct HTTP/DB
70
-
71
- ### 3. Register the tool
89
+ - The tool shape is exactly `{ name, description, inputSchema, handler }` —
90
+ the field is `handler`, not `execute`
91
+ - `handler(args, env)` receives `Record<string, unknown>` and the `readEnv()`
92
+ result; it returns `Promise<unknown>` **not** a string. Return the parsed
93
+ JSON body; the transport serialises it
94
+ - Tools call `apiCall(env.baseUrl, env.apiKey, path, init)` from `serve.ts`.
95
+ They do **not** construct a `SubstrateClient` — the SDK import in this package
96
+ is for the `RECIPES` contract catalog, not for transport
97
+ - Every receiver a tool wraps must be a real key in `@oneie/sdk/receivers`
98
+ - `apiCall` throws with the response body on a non-2xx, so the API's structured
99
+ error (`{error, field, expected, got, hint}`) reaches the agent. Let it throw
100
+
101
+ ### 3. Register the tool — in two places
72
102
 
73
103
  ```typescript
74
- // packages/mcp/src/tools/chat/index.ts
75
- export { MEMORY_TOOL } from './memory';
76
- export * as chatTools from './index';
77
-
78
- // packages/mcp/src/tools/index.ts
79
- import * as chatTools from './chat';
80
- export const TOOLS = [
81
- chatTools.MEMORY_TOOL,
82
- // other tools
83
- ] as const;
84
-
85
104
  // packages/mcp/src/index.ts
86
- import { TOOLS } from './tools';
105
+ export function createOneRouter() {
106
+ const router = createRouter();
107
+ for (const tool of [...substrateTools(), ...broadcastTools(), /* … */]) {
108
+ router.register(tool);
109
+ }
110
+ return router;
111
+ }
87
112
 
88
- export const mcpServer = {
89
- tools: TOOLS,
90
- // other MCP config
113
+ export const MCP_TOOLS = {
114
+ broadcast: ["broadcast_create", "broadcast_list", "broadcast_get",
115
+ "broadcast_send", /* */] as const,
91
116
  };
92
117
  ```
93
118
 
94
- ### 4. Test the tool
119
+ `MCP_TOOLS` is hand-maintained and drifts. `toolManifestDrift()` diffs the
120
+ manifest against the live router; `fn_*` is excluded by design because those are
121
+ generated at runtime from `FN_MAP`.
95
122
 
96
- ```typescript
97
- // packages/mcp/test/chat.memory.test.ts
98
- describe('Memory tool', () => {
99
- it('returns memory for a group', async () => {
100
- const result = await memoryTool({ groupId: 'group-123', content: 'test' });
101
- expect(result).toContain('memory');
102
- });
123
+ ### 4. Check and test
103
124
 
104
- it('returns error message when no memory', async () => {
105
- const result = await memoryTool({ groupId: 'nonexistent', content: 'test' });
106
- expect(result).toContain('No memory');
107
- });
125
+ ```bash
126
+ cd packages/mcp && bun run check:tools
127
+ # builds, then reports "MCP_TOOLS matches the router" or exits 1 with the diff
128
+ ```
108
129
 
109
- it('validates input schema', () => {
110
- expect(() => {
111
- memoryToolSchema.parse({ groupId: 'g1' }); // missing content
112
- }).toThrow();
113
- });
114
- });
130
+ There is no vitest suite in `packages/mcp` — `check:tools` and `bun run
131
+ typecheck` are the gates. Exercise a tool end-to-end through a live client:
132
+
133
+ ```bash
134
+ claude mcp add oneie -e ONEIE_API_KEY=one-<key> -- npx -y @oneie/mcp
115
135
  ```
116
136
 
117
137
  ## Naming conventions
118
138
 
119
- **Tool names:** kebab-case, verb-noun
120
- - `read-memory`, `create-task`, `list-actors`
121
- - `Memory`, `CREATE_TASK`, `list_actors` (inconsistent)
139
+ **Tool names:** snake_case, `group_verb`
140
+ - Correct: `broadcast_send`, `tasks_list`, `chat_send`, `workflow_run`
141
+ - Wrong: `send-broadcast`, `Memory`, `CREATE_TASK` (nothing shipped uses kebab or caps)
142
+ - The universal verbs are the bare exception — `signal`, `ask`, `mark`, `warn`,
143
+ `fade`, `follow` keep the locked verb name with no prefix
122
144
 
123
- **Tool descriptions:** one sentence, agent-perspective
124
- - "Read or write group memory" (what agent can do)
125
- - "Calls the memory API" (implementation detail)
145
+ **Tool descriptions:** one sentence, agent-perspective, and say what comes back
146
+ - Correct: "Send a broadcast now (inline drain, suppression-checked)."
147
+ - Correct: "Get one broadcast by id. Returns { broadcast }."
148
+ - Wrong: "Calls the broadcast API" (implementation detail, no return shape)
126
149
 
127
- **Input fields:** snake_case, self-documenting
128
- - `groupId`, `taskTitle`, `memberEmails`
129
- - `gid`, `t`, `e` (unclear)
150
+ **Input fields:** match the receiver's zod request field-for-field
151
+ - The handler forwards `args` straight through, so a renamed field is a silent
152
+ 400. `broadcast:send` takes `broadcastId`; the tool takes `broadcastId`
130
153
 
131
154
  ## Tool categories
132
155
 
133
156
  | Category | Purpose | Example |
134
157
  |----------|---------|---------|
135
- | **Read** | Fetch data without side effects | `read-memory`, `list-tasks` |
136
- | **Create** | Add new entity | `create-task`, `create-group` |
137
- | **Update** | Modify existing entity | `update-memory`, `complete-task` |
138
- | **Delete** | Remove entity (rare) | `delete-task` |
139
- | **Execute** | Run an action with side effects | `send-message`, `approve-request` |
158
+ | **Universal** | Reach any receiver by name | `signal`, `ask` |
159
+ | **Read** | Fetch data without side effects | `broadcast_list`, `tasks_list`, `stats` |
160
+ | **Create** | Add new entity | `broadcast_create`, `tasks_create`, `create_view` |
161
+ | **Update** | Modify existing entity | `newsletter_update`, `tasks_status` |
162
+ | **Delete** | Remove entity (rare) | `delete_room`, `unpublish_agent` |
163
+ | **Execute** | Run an action with side effects | `broadcast_send`, `chat_send`, `workflow_run` |
140
164
 
141
165
  ## Anti-patterns
142
166
 
143
- **Tool with no schema**
167
+ **A new tool for a new product action**
144
168
  ```typescript
145
- // WRONG
146
- export const memoryTool = async (input: any) => { ... };
169
+ // WRONG — a receiver already reaches this
170
+ { name: "commend_agent", handler: (a, e) => ask(e, "agents:commend", a) }
171
+ ```
172
+ **Fix:** Let the agent call `signal` with `receiver: "agents:commend"`. Only add
173
+ a tool for a first-class object clients address by name, and document why.
174
+
175
+ **Zod in inputSchema**
176
+ ```typescript
177
+ // WRONG — inputSchema is raw JSON Schema
178
+ inputSchema: z.object({ broadcastId: z.string() })
147
179
  ```
148
- **Fix:** Define Zod schema, use typed input.
180
+ **Fix:** Write the JSON Schema object literal, or derive it from the receiver
181
+ with `ask("meta:schema", { receiver })`.
149
182
 
150
- **Tool calls HTTP directly**
183
+ **Returning a hand-serialised string**
151
184
  ```typescript
152
185
  // WRONG
153
- async function memoryTool(input: MemoryToolInput) {
154
- const res = await fetch('/api/memory', { body: JSON.stringify(input) });
155
- return res.text();
156
- }
186
+ handler: async (args, env) => JSON.stringify(await ask(env, "broadcast:get", args))
157
187
  ```
158
- **Fix:** Use SDK client: `const client = new SubstrateClient(); client.memory(...)`
188
+ **Fix:** Return the value. `handler` is `Promise<unknown>`; double-encoding
189
+ gives the agent a string it has to re-parse.
159
190
 
160
- **Tool without description**
191
+ **Tool builds its own fetch**
161
192
  ```typescript
162
193
  // WRONG
163
- const MEMORY_TOOL = {
164
- name: 'memory',
165
- inputSchema: memoryToolSchema,
166
- execute: memoryTool,
167
- };
194
+ const res = await fetch(`${base}/api/ask/broadcast:send`, { … });
195
+ return res.json();
196
+ ```
197
+ **Fix:** Use `apiCall()` — it sets Accept/Content-Type/Authorization, handles
198
+ 204, and surfaces the structured error body on failure.
199
+
200
+ **Registered in the router but not in MCP_TOOLS**
201
+ ```typescript
202
+ // WRONG — check:tools exits 1 with "missing: [ 'broadcast_send' ]"
203
+ router.register(broadcastSendTool); // and nothing added to the manifest
168
204
  ```
169
- **Fix:** Add description for agent context.
205
+ **Fix:** Add the name to its `MCP_TOOLS` group in the same edit.
170
206
 
171
- **Tool that modifies global state**
207
+ **Tool that modifies global state**
172
208
  ```typescript
173
209
  // WRONG
174
- let cachedMemory: any;
175
- export async function memoryTool(input: MemoryToolInput) {
176
- cachedMemory = await client.memory(input.groupId); // cache is a bug
177
- return JSON.stringify(cachedMemory);
178
- }
210
+ let cached: unknown;
211
+ handler: async (args, env) => (cached ??= await ask(env, "broadcast:list", args))
179
212
  ```
180
213
  **Fix:** No caching in tools. Each invocation is independent.
181
214
 
182
215
  ## Composability rules
183
216
 
184
- **New tool composes SDK methods:**
217
+ **New tool wraps an existing receiver:**
185
218
  ```typescript
186
- // GOOD — reuses existing SDK
187
- async function createTaskTool(input: CreateTaskInput) {
188
- const client = new SubstrateClient();
189
- const task = await client.createTask(input); // SDK handles DB, validation
190
- return JSON.stringify(task);
219
+ // GOOD — the receiver owns validation, authority, and persistence
220
+ {
221
+ name: "segment_preview",
222
+ description:
223
+ "Preview an audience segment definition — live count + up to 10 sample " +
224
+ "addresses (read-only). Pass a SegmentDef ({all,any?}), not an id.",
225
+ inputSchema: {
226
+ type: "object",
227
+ properties: {
228
+ definition: { type: "object", description: "SegmentDef {all: SegmentRule[], any?: SegmentRule[]}" },
229
+ channel: { type: "string", enum: ["email", "sms", "whatsapp"] },
230
+ },
231
+ required: ["definition"],
232
+ },
233
+ handler: async (args, env) => ask(env, "segment:preview", args),
191
234
  }
192
235
  ```
193
236
 
194
- **New tool creates novel capability:**
237
+ **New tool is generated from a catalog:**
195
238
  ```typescript
196
- // GOOD — when no SDK method exists yet
197
- async function analyzeMemoryTool(input: AnalyzeMemoryInput) {
198
- const client = new SubstrateClient();
199
- const memory = await client.memory(input.groupId);
200
- // Analysis logic (not in SDK, because it's agent-specific)
201
- const analysis = analyzeForSentiment(memory.content);
202
- return JSON.stringify(analysis);
203
- }
239
+ // GOOD — fn.ts builds one tool per FN_ALLOWLIST entry (20 today) from
240
+ // @oneie/sdk/generated/fn-map, all routing through ask("fn:run"). Extending the
241
+ // surface means allowlisting the fun in @oneie/sdk/fn-allowlist — one edit
242
+ // reaches web, MCP, CLI, and channels. Never hand-write an fn_* tool.
204
243
  ```
205
244
 
206
245
  ## Error handling
207
246
 
208
247
  ```typescript
209
- export async function memoryTool(input: MemoryToolInput): Promise<string> {
210
- try {
211
- const client = new SubstrateClient();
212
- const memory = await client.memory(input.groupId);
213
-
214
- if (!memory) {
215
- return 'No memory found for this group. Create one first.';
216
- }
217
-
218
- return JSON.stringify(memory);
219
- } catch (error) {
220
- if (error instanceof ValidationError) {
221
- return `Invalid input: ${error.message}`;
222
- }
223
- if (error instanceof AuthenticationError) {
224
- return 'You do not have permission to access this memory.';
225
- }
226
- throw error; // Let parent handle unexpected errors
248
+ handler: async (args, env) => {
249
+ const res = await ask(env, "broadcast:get", args) as { broadcast?: unknown };
250
+ if (!res.broadcast) {
251
+ return { ok: false, error: "not_found", hint: "Call broadcast_list for valid ids." };
227
252
  }
253
+ return res;
228
254
  }
229
255
  ```
230
256
 
231
257
  **Rules:**
232
- - Validate input (Zod does this for you)
233
- - Return user-facing error messages (not stack traces)
234
- - Don't catch unexpected errors (let them propagate)
235
- - Never expose sensitive data (API keys, DB queries) in error messages
258
+ - Let `apiCall` throw on a non-2xx — its message already carries the API's
259
+ structured detail, which is what an agent self-corrects from
260
+ - Return an error *shape* (`{ ok: false, error, hint }`) for domain-empty cases
261
+ the API considers a legitimate 200
262
+ - Never expose the API key, raw TypeQL, or a stack trace in a returned value
263
+ - Don't validate what the receiver already validates — the zod contract runs
264
+ server-side before dispatch
236
265
 
237
266
  ## See also
238
267
 
239
- - `packages/mcp/CLAUDE.md` — MCP development guide
240
- - `packages/sdk/src/receivers.ts` — receiver registry (every tool calls a receiver)
241
- - `text/receiver-names-shipped-vs-spec.md` — which receivers exist
268
+ - `packages/mcp/CLAUDE.md` — the tool-file map, wiring, and the base-URL trap
269
+ - `packages/mcp/src/serve.ts` — `McpTool`, `McpRouter`, `apiCall`
270
+ - `packages/sdk/src/receivers.ts` — receiver registry (every tool wraps a receiver)
271
+ - `text/signals-catalog.md` — the generated namespace map
242
272
  - `@modelcontextprotocol/sdk` — MCP protocol docs
@@ -50,7 +50,7 @@ What the resolver does (so you don't re-do it): inserts the D1 `promises` row (s
50
50
  ## Process
51
51
 
52
52
  1. **Extract the terms.** One sentence of what will be true. If the ask is fuzzy, tighten it with the maker before emitting — frozen fuzz is worse than no promise.
53
- 2. **Extract the ONE proof.** Ask: "what single check settles this?" If the answer is "well, A and B" → two promises. If the answer is "you'll know it when you see it" → refuse, with the reason stated.
53
+ 2. **Extract the ONE proof.** Ask: "what single check settles this?" If the answer is "well, A and B" → two promises. If the answer is "you'll know it when you see it" → refuse, with the reason stated. The check should be *false right now* (covenant clause 3, red before green) — a proof that already passes means the thing is already built, or the proof is too weak to gate anything. The resolver does not enforce this; you do.
54
54
  3. **Classify the proof rail.** Shell-shaped → tell the maker it settles at PROVE via CLI, not via `promise:settle`. Runtime-checkable → `promise:settle` works.
55
55
  4. **Pick the slug.** Kebab, stable, matches `^[a-zA-Z0-9_:/-]{1,128}$`. It becomes the path endpoint `promise:<slug>→proof` — choose like it's permanent, because it is.
56
56
  5. **Emit** `signal("promise:make", { slug, terms, proof })` — authenticated (the session's `ctx.ownerSlug` becomes the maker who alone may settle).
@@ -90,4 +90,4 @@ Counter-example (must refuse): *"Promise we'll make the client happy."* → No o
90
90
  - Receiver source: `one.ie/web/src/lib/resolvers/promises.ts` (`make`, lines 36–67)
91
91
  - Agent briefing (payloads, errors, recovery): `text/promise-signals-agents-docs.md`
92
92
  - The contract this skill serves: `text/promise-signals.md` (`world.skills` names this skill)
93
- - Build-time twin: `/do` PROMISE stage + `text/template-feature.md` (contract: block)
93
+ - Build-time twin: `/do` PROMISE stage + `text/template-feature.md` (contract: block). "Never update the terms" is this rail's rule — a D1 row inert after `ON CONFLICT DO NOTHING`. The build-time rail differs: a *kept* `text/<slug>.md` promise grows, appending a follow-on deliverable whose `accept:` is already green and extending the `proof:` join (`.claude/rules/documentation.md` § "Promises grow with delivery"). Green-only and additive there; frozen here.