@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.
- package/agents/w1-recon.md +9 -4
- package/agents/w2-decide.md +51 -28
- package/agents/w3-edit.md +70 -78
- package/agents/w4-verify.md +164 -69
- package/commands/browser.md +98 -35
- package/commands/cc-connect.md +6 -0
- package/commands/chat.md +13 -1
- package/commands/close.md +45 -19
- package/commands/create.md +20 -10
- package/commands/db-sync.md +5 -1
- package/commands/deploy.md +174 -87
- package/commands/do-autonomous.md +31 -20
- package/commands/do-improve.md +4 -4
- package/commands/do-show.md +4 -3
- package/commands/do.md +53 -13
- package/commands/go.md +75 -0
- package/commands/improve.md +4 -2
- package/commands/kill.md +6 -8
- package/commands/notify.md +15 -2
- package/commands/oo-push.md +24 -9
- package/commands/rag.md +6 -0
- package/commands/release.md +46 -25
- package/commands/restart.md +5 -4
- package/commands/see.md +45 -35
- package/commands/skill-create.md +38 -5
- package/commands/sync.md +21 -11
- package/hooks/scripts/auto-continue.sh +70 -0
- package/hooks/scripts/branch-pin.sh +85 -0
- package/hooks/scripts/git-add-guard.sh +16 -5
- package/hooks/scripts/post-edit-check.sh +16 -0
- package/hooks/scripts/session-start.sh +13 -8
- package/hooks/scripts/sync-priority-todo.sh +5 -0
- package/package.json +1 -1
- package/rules/api.md +9 -0
- package/rules/design.md +13 -3
- package/rules/documentation.md +30 -71
- package/rules/engine.md +8 -1
- package/rules/ui.md +5 -1
- package/scripts/__pycache__/do-rank.cpython-313.pyc +0 -0
- package/scripts/browser-check.mjs +44 -103
- package/scripts/cc-events-proof.sh +13 -12
- package/scripts/chrome.mjs +381 -0
- package/scripts/do-analyze.sh +109 -4
- package/scripts/do-auto.sh +350 -43
- package/scripts/do-consumer-sweep.sh +72 -0
- package/scripts/do-derives-check.sh +88 -0
- package/scripts/do-fleet.sh +50 -1
- package/scripts/do-killswitch-audit.py +33 -14
- package/scripts/do-promise-lint.sh +150 -4
- package/scripts/do-promise-settle.sh +131 -9
- package/scripts/do-prove.sh +197 -17
- package/scripts/do-rank.py +606 -20
- package/scripts/do-rubric.py +21 -2
- package/scripts/do-signal.sh +157 -17
- package/scripts/do-smoke.sh +9 -0
- package/scripts/do-substrate-check.sh +49 -0
- package/scripts/do-tasks-wire-check.sh +81 -0
- package/scripts/do-tier.sh +90 -5
- package/scripts/do-ui-gate.sh +332 -0
- package/scripts/do-untracked-gate.sh +231 -0
- package/scripts/do-walk.sh +344 -0
- package/scripts/factory-check.sh +747 -0
- package/scripts/factory-repo.sh +1361 -0
- package/scripts/factory-tasks-check.sh +85 -0
- package/scripts/outcome-pull.ts +279 -0
- package/scripts/promise-manifest.mjs +167 -0
- package/scripts/reconcile-allow.txt +5 -0
- package/scripts/rubric-weights.json +64 -0
- package/scripts/spine-canary.sh +20 -0
- package/scripts/typedb-cluster-status.sh +128 -0
- package/scripts/typedb-env.sh +182 -0
- package/scripts/typedb-flap-recorder.sh +85 -0
- package/scripts/typedb-probes/containment-probe.py +88 -0
- package/scripts/typedb-probes/panic-probe-version.py +92 -0
- package/scripts/typedb-probes/panic-probe.py +77 -0
- package/scripts/typedb-scratch.sh +73 -0
- package/scripts/urls-lint.sh +58 -0
- package/scripts/w4-rubric.ts +96 -12
- package/scripts/wf-check.mjs +75 -0
- package/skills/ai-ui/SKILL.md +1 -1
- package/skills/astro/SKILL.md +177 -132
- package/skills/astro/add-content-collection.md +29 -13
- package/skills/astro/check-build.md +19 -13
- package/skills/astro/create-component.md +36 -9
- package/skills/astro/create-page.md +39 -10
- package/skills/astro/optimize-performance.md +36 -8
- package/skills/cli/SKILL.md +254 -220
- package/skills/dev/SKILL.md +1 -1
- package/skills/directory-autofill/SKILL.md +68 -0
- package/skills/docs/SKILL.md +4 -4
- package/skills/mcp/SKILL.md +186 -156
- package/skills/promise-make/SKILL.md +2 -2
- package/skills/puck/SKILL.md +465 -250
- package/skills/react19/SKILL.md +196 -296
- package/skills/reactflow/SKILL.md +387 -412
- package/skills/sdk/SKILL.md +167 -106
- package/skills/shadcn/SKILL.md +245 -201
- package/skills/signal/SKILL.md +5 -0
- package/skills/sui/SKILL.md +6 -1
- package/skills/tutorial/SKILL.md +15 -0
- package/skills/typedb/SKILL.md +427 -217
- package/skills/typedb/examples/python-patterns.py +11 -4
- package/skills/typedb/examples/query-patterns.tql +8 -3
- package/skills/typedb/examples/schema-patterns.tql +4 -2
- package/skills/typedb/reference/migration-2x-3x.md +19 -1
- package/skills/typedb/reference/python-driver.md +17 -1
- package/skills/voice/SKILL.md +117 -0
- package/skills/writer/SKILL.md +29 -12
- package/templates/template-feature.md +26 -1
- package/templates/template-tests.md +4 -0
- 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
|
package/skills/docs/SKILL.md
CHANGED
|
@@ -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` (
|
|
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** — `
|
|
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
|
|
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.
|
|
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).*
|
package/skills/mcp/SKILL.md
CHANGED
|
@@ -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.
|
|
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
|
|
11
|
-
│ ├──
|
|
12
|
-
│
|
|
13
|
-
│
|
|
14
|
-
│
|
|
15
|
-
│
|
|
16
|
-
│
|
|
17
|
-
│
|
|
18
|
-
│
|
|
19
|
-
│
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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/
|
|
42
|
-
import {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
-
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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:**
|
|
120
|
-
-
|
|
121
|
-
-
|
|
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
|
-
-
|
|
125
|
-
-
|
|
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:**
|
|
128
|
-
-
|
|
129
|
-
|
|
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
|
-
| **
|
|
136
|
-
| **
|
|
137
|
-
| **
|
|
138
|
-
| **
|
|
139
|
-
| **
|
|
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
|
-
|
|
167
|
+
**A new tool for a new product action**
|
|
144
168
|
```typescript
|
|
145
|
-
// WRONG
|
|
146
|
-
|
|
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:**
|
|
180
|
+
**Fix:** Write the JSON Schema object literal, or derive it from the receiver
|
|
181
|
+
with `ask("meta:schema", { receiver })`.
|
|
149
182
|
|
|
150
|
-
|
|
183
|
+
**Returning a hand-serialised string**
|
|
151
184
|
```typescript
|
|
152
185
|
// WRONG
|
|
153
|
-
async
|
|
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:**
|
|
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
|
-
|
|
191
|
+
**Tool builds its own fetch**
|
|
161
192
|
```typescript
|
|
162
193
|
// WRONG
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
205
|
+
**Fix:** Add the name to its `MCP_TOOLS` group in the same edit.
|
|
170
206
|
|
|
171
|
-
|
|
207
|
+
**Tool that modifies global state**
|
|
172
208
|
```typescript
|
|
173
209
|
// WRONG
|
|
174
|
-
let
|
|
175
|
-
|
|
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
|
|
217
|
+
**New tool wraps an existing receiver:**
|
|
185
218
|
```typescript
|
|
186
|
-
// GOOD —
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
237
|
+
**New tool is generated from a catalog:**
|
|
195
238
|
```typescript
|
|
196
|
-
// GOOD —
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
-
|
|
233
|
-
|
|
234
|
-
-
|
|
235
|
-
|
|
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` —
|
|
240
|
-
- `packages/
|
|
241
|
-
- `
|
|
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.
|