@orderful/droid 0.51.0 → 0.52.1

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.
@@ -28,6 +28,7 @@
28
28
  "./src/tools/pii/skills/pii/SKILL.md",
29
29
  "./src/tools/plan/skills/plan/SKILL.md",
30
30
  "./src/tools/project/skills/project/SKILL.md",
31
+ "./src/tools/propose-plan/skills/propose-plan/SKILL.md",
31
32
  "./src/tools/release/skills/release/SKILL.md",
32
33
  "./src/tools/share/skills/share/SKILL.md",
33
34
  "./src/tools/status-update/skills/status-update/SKILL.md",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @orderful/droid
2
2
 
3
+ ## 0.52.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#321](https://github.com/Orderful/droid/pull/321) [`235a4b3`](https://github.com/Orderful/droid/commit/235a4b32d735136c79b66aacca6097f42559c94c) Thanks [@frytyler](https://github.com/frytyler)! - Align propose-plan skill with Zod v4 structured output constraint: payload and sourceRaw as JSON-stringified strings
8
+
9
+ ## 0.52.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#320](https://github.com/Orderful/droid/pull/320) [`89e37e0`](https://github.com/Orderful/droid/commit/89e37e03899ce34d01de09ed43ad2fff2c171af8) Thanks [@frytyler](https://github.com/frytyler)! - Add `propose-plan` tool — structured plans with evidence and actions for human-in-the-loop approval
14
+
15
+ ### Patch Changes
16
+
17
+ - [#318](https://github.com/Orderful/droid/pull/318) [`87bb797`](https://github.com/Orderful/droid/commit/87bb797bb1cd53d89c4a946401abdb9210542eab) Thanks [@frytyler](https://github.com/frytyler)! - Add `--tool` flag to `droid pack` for single-tool downloads
18
+
3
19
  ## 0.51.0
4
20
 
5
21
  ### Minor Changes
package/dist/bin/droid.js CHANGED
@@ -5215,7 +5215,7 @@ function collectToolArtifacts(tool) {
5215
5215
  }
5216
5216
  const refsDir = join14(skillDir, "references");
5217
5217
  if (existsSync12(refsDir)) {
5218
- const refFiles = readdirSync7(refsDir).filter((f) => f.endsWith(".md"));
5218
+ const refFiles = readdirSync7(refsDir).filter((f) => !f.startsWith("."));
5219
5219
  for (const file of refFiles) {
5220
5220
  artifacts.push({
5221
5221
  sourcePath: join14(refsDir, file),
@@ -5269,9 +5269,9 @@ function generateClaudeMd(tools) {
5269
5269
  lines.push("");
5270
5270
  return lines.join("\n");
5271
5271
  }
5272
- function generateReadme(audience, tools) {
5272
+ function generateReadme(label, tools) {
5273
5273
  const lines = [
5274
- `# Droid Pack: ${audience}`,
5274
+ `# Droid Pack: ${label}`,
5275
5275
  "",
5276
5276
  "This pack contains AI skills, commands, and agents for Claude Desktop.",
5277
5277
  "",
@@ -5317,6 +5317,49 @@ function checkDependencies(tools) {
5317
5317
  }
5318
5318
  return warnings;
5319
5319
  }
5320
+ async function buildToolPack(options) {
5321
+ const { toolName, outputDir } = options;
5322
+ const allTools = getBundledTools();
5323
+ const tool = allTools.find((t) => t.name === toolName);
5324
+ if (!tool) {
5325
+ return {
5326
+ success: false,
5327
+ message: `Tool '${toolName}' not found`
5328
+ };
5329
+ }
5330
+ const tools = [tool];
5331
+ const warnings = checkDependencies(tools);
5332
+ const filename = `droid-${toolName}.zip`;
5333
+ const outputPath = join14(outputDir, filename);
5334
+ return new Promise((resolve) => {
5335
+ const output = createWriteStream(outputPath);
5336
+ const archive = archiver("zip", { zlib: { level: 9 } });
5337
+ output.on("close", () => {
5338
+ resolve({
5339
+ success: true,
5340
+ message: `Pack created: ${filename}`,
5341
+ outputPath,
5342
+ toolCount: 1,
5343
+ warnings: warnings.length > 0 ? warnings : void 0
5344
+ });
5345
+ });
5346
+ archive.on("error", (err) => {
5347
+ resolve({
5348
+ success: false,
5349
+ message: `Failed to create pack: ${err.message}`
5350
+ });
5351
+ });
5352
+ archive.pipe(output);
5353
+ const artifacts = collectToolArtifacts(tool);
5354
+ for (const artifact of artifacts) {
5355
+ const content = readFileSync11(artifact.sourcePath, "utf-8");
5356
+ archive.append(content, { name: artifact.zipPath });
5357
+ }
5358
+ archive.append(generateClaudeMd(tools), { name: "CLAUDE.md" });
5359
+ archive.append(generateReadme(toolName, tools), { name: "README.md" });
5360
+ archive.finalize();
5361
+ });
5362
+ }
5320
5363
  async function buildPack(options) {
5321
5364
  const { audience, outputDir } = options;
5322
5365
  const tools = getToolsForAudience(audience);
@@ -5381,9 +5424,34 @@ async function packCommand(audience, options) {
5381
5424
  console.log("");
5382
5425
  return;
5383
5426
  }
5427
+ if (options.tool) {
5428
+ const outputDir2 = options.output || process.cwd();
5429
+ console.log(
5430
+ chalk10.bold(`
5431
+ Packing tool ${chalk10.cyan(options.tool)}...`)
5432
+ );
5433
+ const result2 = await buildToolPack({ toolName: options.tool, outputDir: outputDir2 });
5434
+ if (!result2.success) {
5435
+ console.error(chalk10.red(`
5436
+ ${result2.message}`));
5437
+ process.exit(1);
5438
+ }
5439
+ console.log(chalk10.green(`
5440
+ ${result2.message}`));
5441
+ console.log(chalk10.gray(` Output: ${result2.outputPath}`));
5442
+ if (result2.warnings && result2.warnings.length > 0) {
5443
+ console.log(chalk10.yellow("\nWarnings:"));
5444
+ for (const warning of result2.warnings) {
5445
+ console.log(chalk10.yellow(` - ${warning}`));
5446
+ }
5447
+ }
5448
+ console.log("");
5449
+ return;
5450
+ }
5384
5451
  if (!audience) {
5385
5452
  console.error(chalk10.red("\nError: audience argument required"));
5386
5453
  console.log(chalk10.gray("Usage: droid pack <audience>"));
5454
+ console.log(chalk10.gray(" droid pack --tool <name>"));
5387
5455
  console.log(chalk10.gray(" droid pack --list"));
5388
5456
  process.exit(1);
5389
5457
  }
@@ -6051,7 +6119,7 @@ program.command("install <tool>").description("Install a tool and run its setup
6051
6119
  program.command("uninstall <tool>").description("Uninstall a tool").action(uninstallCommand);
6052
6120
  program.command("update").description("Update droid and installed tools").option("--tools", "Only update tools").option("--cli", "Only update the CLI").argument("[tool]", "Update a specific tool").action(updateCommand);
6053
6121
  program.command("tui").description("Launch interactive TUI dashboard").action(tuiCommand);
6054
- program.command("pack").description("Create audience-filtered zip packs for distribution").argument("[audience]", "Target audience (e.g., engineering, customer-support)").option("-l, --list", "List available audiences and tool counts").option("-o, --output <dir>", "Output directory (default: cwd)").action(packCommand);
6122
+ program.command("pack").description("Create zip packs for distribution").argument("[audience]", "Target audience (e.g., engineering, customer-support)").option("-l, --list", "List available audiences and tool counts").option("-t, --tool <name>", "Pack a single tool by name").option("-o, --output <dir>", "Output directory (default: cwd)").action(packCommand);
6055
6123
  program.command("exec <tool> <script>").description("Execute a tool script").argument("[args...]", "Arguments to pass to the script").allowUnknownOption().action(execCommand);
6056
6124
  var integrations = program.command("integrations").description("Manage external service integrations");
6057
6125
  var integrationsSetup = integrations.command("setup").description("Set up an integration");
@@ -1,5 +1,6 @@
1
1
  export declare function packCommand(audience: string | undefined, options: {
2
2
  list?: boolean;
3
3
  output?: string;
4
+ tool?: string;
4
5
  }): Promise<void>;
5
6
  //# sourceMappingURL=pack.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../../src/commands/pack.ts"],"names":[],"mappings":"AAQA,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3C,OAAO,CAAC,IAAI,CAAC,CAiEf"}
1
+ {"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../../src/commands/pack.ts"],"names":[],"mappings":"AAQA,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1D,OAAO,CAAC,IAAI,CAAC,CA+Ff"}
@@ -8,6 +8,10 @@ export interface PackOptions {
8
8
  audience: ToolAudience;
9
9
  outputDir: string;
10
10
  }
11
+ export interface ToolPackOptions {
12
+ toolName: string;
13
+ outputDir: string;
14
+ }
11
15
  export interface PackResult {
12
16
  success: boolean;
13
17
  message: string;
@@ -24,6 +28,10 @@ export declare function getToolsForAudience(audience: ToolAudience): ToolManifes
24
28
  * Get audience info for all audiences that have tools
25
29
  */
26
30
  export declare function getAudienceInfo(): AudienceInfo[];
31
+ /**
32
+ * Build a zip pack for a single tool
33
+ */
34
+ export declare function buildToolPack(options: ToolPackOptions): Promise<PackResult>;
27
35
  /**
28
36
  * Build a zip pack for the given audience
29
37
  */
@@ -1 +1 @@
1
- {"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../../src/lib/pack.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAE1D,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,YAAY,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,YAAY,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,GACrB,YAAY,EAAE,CAehB;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,YAAY,EAAE,CAsBhD;AA4JD;;GAEG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CA4DzE"}
1
+ {"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../../src/lib/pack.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAE1D,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,YAAY,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,YAAY,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,GACrB,YAAY,EAAE,CAehB;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,YAAY,EAAE,CAsBhD;AA4JD;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAqDjF;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CA4DzE"}
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "droid-propose-plan",
3
+ "version": "0.1.0",
4
+ "description": "Produce structured plans with evidence and actions for human approval in Orderful Agents. Use when an agent must propose write operations that require human-in-the-loop review before execution.",
5
+ "author": {
6
+ "name": "Orderful",
7
+ "url": "https://github.com/orderful"
8
+ },
9
+ "repository": "https://github.com/orderful/droid",
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "droid",
13
+ "ai",
14
+ "propose-plan"
15
+ ],
16
+ "skills": [
17
+ "./skills/propose-plan/SKILL.md"
18
+ ]
19
+ }
@@ -0,0 +1,17 @@
1
+ name: propose-plan
2
+ description: "Produce structured plans with evidence and actions for human approval in Orderful Agents. Use when an agent must propose write operations that require human-in-the-loop review before execution."
3
+ version: 0.1.0
4
+ status: beta
5
+ audience:
6
+ - all
7
+
8
+ includes:
9
+ skills:
10
+ - name: propose-plan
11
+ required: true
12
+ commands: []
13
+ agents: []
14
+
15
+ dependencies: []
16
+
17
+ config_schema: {}
@@ -0,0 +1,172 @@
1
+ ---
2
+ name: propose-plan
3
+ description: "Produce a structured plan with evidence and actions for human approval. Use when an agent must propose write operations, research current state before making changes, or produce actions that require human-in-the-loop review before execution."
4
+ globs: []
5
+ alwaysApply: false
6
+ allowed-tools: []
7
+ ---
8
+
9
+ # Propose Plan
10
+
11
+ Structure your findings into a plan for human approval. Use this skill when you've researched the current state, understand what needs to happen, and are ready to propose the specific actions to take.
12
+
13
+ This skill teaches you how to package what you've learned into structured evidence and discrete actions that a reviewer can approve, modify, or reject before anything is executed.
14
+
15
+ Never call write tools directly. Each proposed write is described as an **action** in your plan output. Approved actions are executed separately after human review.
16
+
17
+ ## Plan Structure
18
+
19
+ A plan has two parts:
20
+
21
+ 1. **Evidence** — your analysis of the gathered context: what it shows, why changes are needed, and what will happen if the plan is approved
22
+ 2. **Actions** — an ordered list of discrete operations to execute, each mapped to a specific tool call
23
+
24
+ The human reviewer sees your evidence first (the reasoning and impact), then reviews each action. Clear evidence builds trust; vague evidence gets rejected.
25
+
26
+ ## Output Format
27
+
28
+ Produce a single JSON object matching the schema in `references/output-schema.json`. The shape:
29
+
30
+ ```json
31
+ {
32
+ "name": "string — short human-readable plan name (optional)",
33
+ "category": "string — grouping category, e.g. org-provisioning (optional)",
34
+ "evidence": {
35
+ "reasoning": "string — your analysis of what needs to happen and why",
36
+ "sourceSummary": "string — concise summary of what you found during research",
37
+ "sourceRaw": "string — JSON-stringified raw source data from research (optional)",
38
+ "impactSummary": "string — what will change if this plan is approved",
39
+ "caveats": ["string — anything uncertain, auto-generated, or requiring follow-up"]
40
+ },
41
+ "actions": [
42
+ {
43
+ "type": "mcp_call",
44
+ "config": {
45
+ "tool": "string — the tool name exactly as you see it in your available tools"
46
+ },
47
+ "description": "string — human-readable description of what this action does",
48
+ "payload": "string — JSON-stringified arguments to pass to the tool"
49
+ }
50
+ ]
51
+ }
52
+ ```
53
+
54
+ ### Evidence Fields
55
+
56
+ | Field | Purpose | Reviewer Experience |
57
+ | --------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
58
+ | `reasoning` | Your analysis — what needs to happen and why. Cite actual data from your research, not assumptions. | First thing they read. Establishes trust. |
59
+ | `sourceSummary` | Concise summary of what you found. Include entity names, IDs, and states. | Quick scan to verify you looked at the right things. |
60
+ | `sourceRaw` | Optional. JSON-stringified raw API responses or data snapshots. | Expandable detail for reviewers who want to verify. |
61
+ | `impactSummary` | What changes if the plan is approved. Be specific — "creates org X with ISA ID Y" not "creates an org". | The core decision point. Reviewer approves based on this. |
62
+ | `caveats` | Optional. Flag anything uncertain, auto-generated, or needing manual follow-up. | Prevents surprises. Reviewers appreciate honesty about gaps. |
63
+
64
+ ### Action Fields
65
+
66
+ | Field | Purpose |
67
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
68
+ | `type` | Action category. Use `mcp_call` for MCP tool invocations. |
69
+ | `config` | Identifies which tool to call. For `mcp_call`: `{ "tool": "create_org" }`. Use the tool name exactly as it appears in your available tools. |
70
+ | `description` | Human-readable summary. The reviewer sees this before expanding the payload. Write it for someone who won't read the raw payload. |
71
+ | `payload` | JSON-stringified arguments passed to the tool. Stringify the object — `JSON.stringify({...})` — don't nest it raw. |
72
+
73
+ ## Building Evidence From What You Know
74
+
75
+ By the time you're writing a plan, you've already done the research — queried tools, read documents, collected the data you need. Your job now is to analyse what you found and structure it into a plan.
76
+
77
+ If read tools are still available, use them to **verify and fill gaps** — check for conflicts, confirm preconditions, resolve ambiguities. But the research phase is behind you. Focus on structuring what you know into clear evidence and precise actions.
78
+
79
+ When writing evidence:
80
+
81
+ 1. **Cite the gathered data** — reference specific entities, IDs, and values. A reviewer can't verify "the account exists" but they can verify "Salesforce account SF-001234 (Acme Corp) has no OrgID".
82
+ 2. **Flag conflicts** — if the gathered data reveals duplicates, collisions, or unexpected state, surface that prominently.
83
+ 3. **Note gaps** — if data is missing or ambiguous, say so in caveats rather than guessing. A plan with honest gaps is more useful than one with invented data.
84
+
85
+ ## Writing Actions
86
+
87
+ Each action represents one tool call. `config.tool` identifies which tool to call, and `payload` carries the arguments — both must be precise.
88
+
89
+ **Order matters.** List actions in dependency order — if action B depends on the result of action A, action A comes first. The execution step processes them sequentially.
90
+
91
+ **One action per tool call.** Don't batch multiple operations into a single action. If provisioning requires creating an org, then creating a billing customer, those are two separate actions.
92
+
93
+ **Only propose tools from your available list.** You've been given a set of write tools you can propose actions for. If a required operation isn't in that list, note it as a caveat — don't invent actions for tools that don't exist.
94
+
95
+ **Payloads must be complete.** Include every required field the tool expects. Don't leave placeholders or TODOs in payloads. For values that don't exist yet because they come from a prior action, use a template reference (see below). If you can't determine a value at all, flag it in caveats and omit the action.
96
+
97
+ ### Action Dependencies (Template References)
98
+
99
+ When an action needs a value produced by a prior action — like an org ID that doesn't exist until the org is created — use a Handlebars template reference in the payload value.
100
+
101
+ **Syntax:** `{{ actions.N.result.path }}` where `N` is the zero-based action index and `path` navigates the result object.
102
+
103
+ At execution time, the system resolves these references using the actual results from completed actions. The human reviewer sees the template expression and understands the data flow — they're approving the intent ("use the org ID from step 1"), not the literal runtime value.
104
+
105
+ **Rules:**
106
+
107
+ - Only reference actions that come _before_ the current action (lower index)
108
+ - Use dot-path navigation to reach nested values: `{{ actions.0.result.org.id }}`
109
+ - Static values and template references can coexist in the same payload
110
+ - Every other payload field should still use concrete values you already know
111
+
112
+ **Example:**
113
+
114
+ ```json
115
+ {
116
+ "type": "mcp_call",
117
+ "config": { "tool": "create_billing_customer" },
118
+ "description": "Create ChargeBee billing customer for Acme Corp using new org ID",
119
+ "payload": "{\"orgId\":\"{{ actions.0.result.id }}\",\"orgName\":\"Acme Corp\",\"contactEmail\":\"jane@acme.com\"}"
120
+ }
121
+ ```
122
+
123
+ Here `orgId` will be resolved at runtime from the result of action 0 (e.g., `create_org`). The reviewer sees that billing depends on the org creation step. Note the payload is JSON-stringified — template references work inside the string and are resolved before parsing.
124
+
125
+ ## Example
126
+
127
+ Say you've been asked to provision an Orderful org for a new customer. You've queried Salesforce, searched for existing orgs, and gathered the relevant data. Your plan output might look like:
128
+
129
+ ```json
130
+ {
131
+ "evidence": {
132
+ "reasoning": "Salesforce account Acme Corp (SF-001234) was signed on 2026-03-01 and has no Orderful OrgID. No existing Orderful org matches by name or ISA ID. The account's primary contact is jane@acme.com. ISA ID derived from Customer_ISA_ID__c field: ACME001.",
133
+ "sourceSummary": "Salesforce: Acme Corp (SF-001234), signed 2026-03-01, no OrgID. Orderful: no org named 'Acme Corp', no ISA ID 'ACME001' in use.",
134
+ "sourceRaw": "{\"salesforce_account\":{\"Id\":\"SF-001234\",\"Name\":\"Acme Corp\",\"OrgID__c\":null,\"Customer_ISA_ID__c\":\"ACME001\"},\"orderful_org_search\":{\"results\":[]}}",
135
+ "impactSummary": "Creates Orderful org 'Acme Corp' with ISA ID 'ACME001', creates billing customer in ChargeBee, and sends welcome email to jane@acme.com.",
136
+ "caveats": ["ARR not found in Salesforce — billing customer will be created without revenue data. Manual update may be needed."]
137
+ },
138
+ "actions": [
139
+ {
140
+ "type": "mcp_call",
141
+ "config": { "tool": "create_org" },
142
+ "description": "Create Orderful org 'Acme Corp' with ISA ID 'ACME001'",
143
+ "payload": "{\"name\":\"Acme Corp\",\"isaId\":\"ACME001\",\"contactEmail\":\"jane@acme.com\"}"
144
+ },
145
+ {
146
+ "type": "mcp_call",
147
+ "config": { "tool": "create_billing_customer" },
148
+ "description": "Create ChargeBee billing customer for Acme Corp using new org ID",
149
+ "payload": "{\"orgId\":\"{{ actions.0.result.id }}\",\"orgName\":\"Acme Corp\",\"contactEmail\":\"jane@acme.com\"}"
150
+ },
151
+ {
152
+ "type": "mcp_call",
153
+ "config": { "tool": "invite_user" },
154
+ "description": "Send welcome email to jane@acme.com for Acme Corp org",
155
+ "payload": "{\"email\":\"jane@acme.com\",\"orgId\":\"{{ actions.0.result.id }}\"}"
156
+ }
157
+ ]
158
+ }
159
+ ```
160
+
161
+ ## What Not To Do
162
+
163
+ These are hard rules. Violating any of them will cause the plan to be rejected or produce incorrect results.
164
+
165
+ - **Don't call write tools directly.** Your job is to propose, not execute. Every write operation must be described as an action in your plan output. If you call a write tool yourself, the approval flow is bypassed and there is no audit trail.
166
+ - **Don't include read operations as actions.** Actions are for writes only — things that change state and need human approval. Reading data is research, not an action. If you need to look something up, use read tools directly during your research phase.
167
+ - **Don't fabricate data.** If a value wasn't in your research and you can't verify it with read tools, say so in caveats. Never guess IDs, names, emails, or configuration values. A plan with honest gaps gets sent back for more research. A plan with invented data gets approved and breaks things.
168
+ - **Don't produce actions for tools outside your available list.** You can only propose actions for tools you've been given. If an operation requires a tool you don't have, note it as a caveat — don't invent an action for it. The execution step will reject unknown tools.
169
+ - **Don't skip evidence.** A plan without evidence will be rejected. The reviewer needs to see what you found, what it means, and what will change. Evidence is not optional filler — it is how the reviewer decides whether to approve.
170
+ - **Don't combine multiple operations into one action.** Each action is one tool call. "Create org and set up billing" is two actions, not one. The execution step calls each action individually — a combined action will fail.
171
+ - **Don't hardcode values from actions that haven't run yet.** If action 2 needs the org ID from action 1, don't guess or invent a value — use a template reference: `"orgId": "{{ actions.0.result.id }}"`. The execution step resolves these at runtime. Never put a placeholder like `"TBD"` or `"<org-id>"` in a payload.
172
+ - **Don't use vague descriptions.** "Create org" tells the reviewer nothing. "Create Orderful org 'Acme Corp' with ISA ID 'ACME001'" tells them exactly what will happen. The description is what the reviewer reads before deciding whether to expand the payload.
@@ -0,0 +1,83 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "PlanOutput",
4
+ "description": "Structured plan output produced by an LLM agent for human review and approval",
5
+ "type": "object",
6
+ "required": ["evidence", "actions"],
7
+ "properties": {
8
+ "name": {
9
+ "type": "string",
10
+ "description": "Short human-readable name for the plan"
11
+ },
12
+ "category": {
13
+ "type": "string",
14
+ "description": "Grouping category for the plan (e.g. org-provisioning)"
15
+ },
16
+ "evidence": {
17
+ "type": "object",
18
+ "description": "Research findings and rationale supporting the plan",
19
+ "required": ["reasoning", "sourceSummary", "impactSummary"],
20
+ "properties": {
21
+ "reasoning": {
22
+ "type": "string",
23
+ "description": "Analysis of what needs to happen and why"
24
+ },
25
+ "sourceSummary": {
26
+ "type": "string",
27
+ "description": "Concise summary of research findings with entity names and IDs"
28
+ },
29
+ "sourceRaw": {
30
+ "type": "string",
31
+ "description": "JSON-stringified raw source data from research"
32
+ },
33
+ "impactSummary": {
34
+ "type": "string",
35
+ "description": "What changes if the plan is approved"
36
+ },
37
+ "caveats": {
38
+ "type": "array",
39
+ "items": { "type": "string" },
40
+ "description": "Risks or caveats to flag for review"
41
+ }
42
+ },
43
+ "additionalProperties": false
44
+ },
45
+ "actions": {
46
+ "type": "array",
47
+ "description": "Ordered list of MCP tool calls to execute",
48
+ "items": {
49
+ "type": "object",
50
+ "required": ["type", "config", "description", "payload"],
51
+ "properties": {
52
+ "type": {
53
+ "type": "string",
54
+ "const": "mcp_call",
55
+ "description": "Action category — v0 only supports mcp_call"
56
+ },
57
+ "config": {
58
+ "type": "object",
59
+ "description": "Configuration specifying which MCP tool to invoke",
60
+ "required": ["tool"],
61
+ "properties": {
62
+ "tool": {
63
+ "type": "string",
64
+ "description": "Tool name as it appears in the available tools"
65
+ }
66
+ },
67
+ "additionalProperties": false
68
+ },
69
+ "description": {
70
+ "type": "string",
71
+ "description": "Human-readable description of what this action does"
72
+ },
73
+ "payload": {
74
+ "type": "string",
75
+ "description": "JSON-stringified arguments to pass to the MCP tool"
76
+ }
77
+ },
78
+ "additionalProperties": false
79
+ }
80
+ }
81
+ },
82
+ "additionalProperties": false
83
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orderful/droid",
3
- "version": "0.51.0",
3
+ "version": "0.52.1",
4
4
  "description": "AI workflow toolkit for sharing skills, commands, and agents across the team",
5
5
  "type": "module",
6
6
  "bin": {
package/src/bin/droid.ts CHANGED
@@ -106,9 +106,10 @@ program
106
106
 
107
107
  program
108
108
  .command('pack')
109
- .description('Create audience-filtered zip packs for distribution')
109
+ .description('Create zip packs for distribution')
110
110
  .argument('[audience]', 'Target audience (e.g., engineering, customer-support)')
111
111
  .option('-l, --list', 'List available audiences and tool counts')
112
+ .option('-t, --tool <name>', 'Pack a single tool by name')
112
113
  .option('-o, --output <dir>', 'Output directory (default: cwd)')
113
114
  .action(packCommand);
114
115
 
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { ToolAudience } from '../lib/types';
3
- import { getAudienceInfo, buildPack } from '../lib/pack';
3
+ import { getAudienceInfo, buildPack, buildToolPack } from '../lib/pack';
4
4
 
5
5
  function isValidAudience(value: string): value is ToolAudience {
6
6
  return Object.values(ToolAudience).includes(value as ToolAudience);
@@ -8,7 +8,7 @@ function isValidAudience(value: string): value is ToolAudience {
8
8
 
9
9
  export async function packCommand(
10
10
  audience: string | undefined,
11
- options: { list?: boolean; output?: string },
11
+ options: { list?: boolean; output?: string; tool?: string },
12
12
  ): Promise<void> {
13
13
  // --list mode: show audiences and tool counts
14
14
  if (options.list) {
@@ -29,10 +29,40 @@ export async function packCommand(
29
29
  return;
30
30
  }
31
31
 
32
+ // --tool mode: pack a single tool
33
+ if (options.tool) {
34
+ const outputDir = options.output || process.cwd();
35
+
36
+ console.log(
37
+ chalk.bold(`\nPacking tool ${chalk.cyan(options.tool)}...`),
38
+ );
39
+
40
+ const result = await buildToolPack({ toolName: options.tool, outputDir });
41
+
42
+ if (!result.success) {
43
+ console.error(chalk.red(`\n${result.message}`));
44
+ process.exit(1);
45
+ }
46
+
47
+ console.log(chalk.green(`\n${result.message}`));
48
+ console.log(chalk.gray(` Output: ${result.outputPath}`));
49
+
50
+ if (result.warnings && result.warnings.length > 0) {
51
+ console.log(chalk.yellow('\nWarnings:'));
52
+ for (const warning of result.warnings) {
53
+ console.log(chalk.yellow(` - ${warning}`));
54
+ }
55
+ }
56
+
57
+ console.log('');
58
+ return;
59
+ }
60
+
32
61
  // Require audience argument
33
62
  if (!audience) {
34
63
  console.error(chalk.red('\nError: audience argument required'));
35
64
  console.log(chalk.gray('Usage: droid pack <audience>'));
65
+ console.log(chalk.gray(' droid pack --tool <name>'));
36
66
  console.log(chalk.gray(' droid pack --list'));
37
67
  process.exit(1);
38
68
  }
@@ -1,6 +1,9 @@
1
1
  import { describe, it, expect } from 'bun:test';
2
+ import { existsSync, unlinkSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
2
5
  import { ToolAudience } from './types';
3
- import { getToolsForAudience, getAudienceInfo } from './pack';
6
+ import { getToolsForAudience, getAudienceInfo, buildToolPack } from './pack';
4
7
 
5
8
  // Uses real bundled tools for integration-style tests
6
9
 
@@ -83,3 +86,25 @@ describe('getAudienceInfo', () => {
83
86
  expect(engineering!.toolNames).toContain('brain');
84
87
  });
85
88
  });
89
+
90
+ describe('buildToolPack', () => {
91
+ it('should create a zip for a single tool', async () => {
92
+ const outputDir = tmpdir();
93
+ const result = await buildToolPack({ toolName: 'brain', outputDir });
94
+
95
+ expect(result.success).toBe(true);
96
+ expect(result.toolCount).toBe(1);
97
+ expect(result.outputPath).toContain('droid-brain.zip');
98
+ expect(existsSync(result.outputPath!)).toBe(true);
99
+
100
+ // Cleanup
101
+ unlinkSync(result.outputPath!);
102
+ });
103
+
104
+ it('should return error for unknown tool', async () => {
105
+ const result = await buildToolPack({ toolName: 'nonexistent', outputDir: tmpdir() });
106
+
107
+ expect(result.success).toBe(false);
108
+ expect(result.message).toContain('not found');
109
+ });
110
+ });
package/src/lib/pack.ts CHANGED
@@ -15,6 +15,11 @@ export interface PackOptions {
15
15
  outputDir: string;
16
16
  }
17
17
 
18
+ export interface ToolPackOptions {
19
+ toolName: string;
20
+ outputDir: string;
21
+ }
22
+
18
23
  export interface PackResult {
19
24
  success: boolean;
20
25
  message: string;
@@ -100,7 +105,7 @@ function collectToolArtifacts(
100
105
  // references/
101
106
  const refsDir = join(skillDir, 'references');
102
107
  if (existsSync(refsDir)) {
103
- const refFiles = readdirSync(refsDir).filter((f) => f.endsWith('.md'));
108
+ const refFiles = readdirSync(refsDir).filter((f) => !f.startsWith('.'));
104
109
  for (const file of refFiles) {
105
110
  artifacts.push({
106
111
  sourcePath: join(refsDir, file),
@@ -170,9 +175,9 @@ function generateClaudeMd(tools: ToolManifest[]): string {
170
175
  /**
171
176
  * Generate README.md with install instructions
172
177
  */
173
- function generateReadme(audience: ToolAudience, tools: ToolManifest[]): string {
178
+ function generateReadme(label: string, tools: ToolManifest[]): string {
174
179
  const lines = [
175
- `# Droid Pack: ${audience}`,
180
+ `# Droid Pack: ${label}`,
176
181
  '',
177
182
  'This pack contains AI skills, commands, and agents for Claude Desktop.',
178
183
  '',
@@ -227,6 +232,64 @@ function checkDependencies(tools: ToolManifest[]): string[] {
227
232
  return warnings;
228
233
  }
229
234
 
235
+ /**
236
+ * Build a zip pack for a single tool
237
+ */
238
+ export async function buildToolPack(options: ToolPackOptions): Promise<PackResult> {
239
+ const { toolName, outputDir } = options;
240
+
241
+ const allTools = getBundledTools();
242
+ const tool = allTools.find((t) => t.name === toolName);
243
+
244
+ if (!tool) {
245
+ return {
246
+ success: false,
247
+ message: `Tool '${toolName}' not found`,
248
+ };
249
+ }
250
+
251
+ const tools = [tool];
252
+ const warnings = checkDependencies(tools);
253
+
254
+ const filename = `droid-${toolName}.zip`;
255
+ const outputPath = join(outputDir, filename);
256
+
257
+ return new Promise((resolve) => {
258
+ const output = createWriteStream(outputPath);
259
+ const archive = archiver('zip', { zlib: { level: 9 } });
260
+
261
+ output.on('close', () => {
262
+ resolve({
263
+ success: true,
264
+ message: `Pack created: ${filename}`,
265
+ outputPath,
266
+ toolCount: 1,
267
+ warnings: warnings.length > 0 ? warnings : undefined,
268
+ });
269
+ });
270
+
271
+ archive.on('error', (err: Error) => {
272
+ resolve({
273
+ success: false,
274
+ message: `Failed to create pack: ${err.message}`,
275
+ });
276
+ });
277
+
278
+ archive.pipe(output);
279
+
280
+ const artifacts = collectToolArtifacts(tool);
281
+ for (const artifact of artifacts) {
282
+ const content = readFileSync(artifact.sourcePath, 'utf-8');
283
+ archive.append(content, { name: artifact.zipPath });
284
+ }
285
+
286
+ archive.append(generateClaudeMd(tools), { name: 'CLAUDE.md' });
287
+ archive.append(generateReadme(toolName, tools), { name: 'README.md' });
288
+
289
+ archive.finalize();
290
+ });
291
+ }
292
+
230
293
  /**
231
294
  * Build a zip pack for the given audience
232
295
  */
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "droid-propose-plan",
3
+ "version": "0.1.0",
4
+ "description": "Produce structured plans with evidence and actions for human approval in Orderful Agents. Use when an agent must propose write operations that require human-in-the-loop review before execution.",
5
+ "author": {
6
+ "name": "Orderful",
7
+ "url": "https://github.com/orderful"
8
+ },
9
+ "repository": "https://github.com/orderful/droid",
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "droid",
13
+ "ai",
14
+ "propose-plan"
15
+ ],
16
+ "skills": [
17
+ "./skills/propose-plan/SKILL.md"
18
+ ]
19
+ }
@@ -0,0 +1,17 @@
1
+ name: propose-plan
2
+ description: "Produce structured plans with evidence and actions for human approval in Orderful Agents. Use when an agent must propose write operations that require human-in-the-loop review before execution."
3
+ version: 0.1.0
4
+ status: beta
5
+ audience:
6
+ - all
7
+
8
+ includes:
9
+ skills:
10
+ - name: propose-plan
11
+ required: true
12
+ commands: []
13
+ agents: []
14
+
15
+ dependencies: []
16
+
17
+ config_schema: {}
@@ -0,0 +1,172 @@
1
+ ---
2
+ name: propose-plan
3
+ description: "Produce a structured plan with evidence and actions for human approval. Use when an agent must propose write operations, research current state before making changes, or produce actions that require human-in-the-loop review before execution."
4
+ globs: []
5
+ alwaysApply: false
6
+ allowed-tools: []
7
+ ---
8
+
9
+ # Propose Plan
10
+
11
+ Structure your findings into a plan for human approval. Use this skill when you've researched the current state, understand what needs to happen, and are ready to propose the specific actions to take.
12
+
13
+ This skill teaches you how to package what you've learned into structured evidence and discrete actions that a reviewer can approve, modify, or reject before anything is executed.
14
+
15
+ Never call write tools directly. Each proposed write is described as an **action** in your plan output. Approved actions are executed separately after human review.
16
+
17
+ ## Plan Structure
18
+
19
+ A plan has two parts:
20
+
21
+ 1. **Evidence** — your analysis of the gathered context: what it shows, why changes are needed, and what will happen if the plan is approved
22
+ 2. **Actions** — an ordered list of discrete operations to execute, each mapped to a specific tool call
23
+
24
+ The human reviewer sees your evidence first (the reasoning and impact), then reviews each action. Clear evidence builds trust; vague evidence gets rejected.
25
+
26
+ ## Output Format
27
+
28
+ Produce a single JSON object matching the schema in `references/output-schema.json`. The shape:
29
+
30
+ ```json
31
+ {
32
+ "name": "string — short human-readable plan name (optional)",
33
+ "category": "string — grouping category, e.g. org-provisioning (optional)",
34
+ "evidence": {
35
+ "reasoning": "string — your analysis of what needs to happen and why",
36
+ "sourceSummary": "string — concise summary of what you found during research",
37
+ "sourceRaw": "string — JSON-stringified raw source data from research (optional)",
38
+ "impactSummary": "string — what will change if this plan is approved",
39
+ "caveats": ["string — anything uncertain, auto-generated, or requiring follow-up"]
40
+ },
41
+ "actions": [
42
+ {
43
+ "type": "mcp_call",
44
+ "config": {
45
+ "tool": "string — the tool name exactly as you see it in your available tools"
46
+ },
47
+ "description": "string — human-readable description of what this action does",
48
+ "payload": "string — JSON-stringified arguments to pass to the tool"
49
+ }
50
+ ]
51
+ }
52
+ ```
53
+
54
+ ### Evidence Fields
55
+
56
+ | Field | Purpose | Reviewer Experience |
57
+ | --------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
58
+ | `reasoning` | Your analysis — what needs to happen and why. Cite actual data from your research, not assumptions. | First thing they read. Establishes trust. |
59
+ | `sourceSummary` | Concise summary of what you found. Include entity names, IDs, and states. | Quick scan to verify you looked at the right things. |
60
+ | `sourceRaw` | Optional. JSON-stringified raw API responses or data snapshots. | Expandable detail for reviewers who want to verify. |
61
+ | `impactSummary` | What changes if the plan is approved. Be specific — "creates org X with ISA ID Y" not "creates an org". | The core decision point. Reviewer approves based on this. |
62
+ | `caveats` | Optional. Flag anything uncertain, auto-generated, or needing manual follow-up. | Prevents surprises. Reviewers appreciate honesty about gaps. |
63
+
64
+ ### Action Fields
65
+
66
+ | Field | Purpose |
67
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
68
+ | `type` | Action category. Use `mcp_call` for MCP tool invocations. |
69
+ | `config` | Identifies which tool to call. For `mcp_call`: `{ "tool": "create_org" }`. Use the tool name exactly as it appears in your available tools. |
70
+ | `description` | Human-readable summary. The reviewer sees this before expanding the payload. Write it for someone who won't read the raw payload. |
71
+ | `payload` | JSON-stringified arguments passed to the tool. Stringify the object — `JSON.stringify({...})` — don't nest it raw. |
72
+
73
+ ## Building Evidence From What You Know
74
+
75
+ By the time you're writing a plan, you've already done the research — queried tools, read documents, collected the data you need. Your job now is to analyse what you found and structure it into a plan.
76
+
77
+ If read tools are still available, use them to **verify and fill gaps** — check for conflicts, confirm preconditions, resolve ambiguities. But the research phase is behind you. Focus on structuring what you know into clear evidence and precise actions.
78
+
79
+ When writing evidence:
80
+
81
+ 1. **Cite the gathered data** — reference specific entities, IDs, and values. A reviewer can't verify "the account exists" but they can verify "Salesforce account SF-001234 (Acme Corp) has no OrgID".
82
+ 2. **Flag conflicts** — if the gathered data reveals duplicates, collisions, or unexpected state, surface that prominently.
83
+ 3. **Note gaps** — if data is missing or ambiguous, say so in caveats rather than guessing. A plan with honest gaps is more useful than one with invented data.
84
+
85
+ ## Writing Actions
86
+
87
+ Each action represents one tool call. `config.tool` identifies which tool to call, and `payload` carries the arguments — both must be precise.
88
+
89
+ **Order matters.** List actions in dependency order — if action B depends on the result of action A, action A comes first. The execution step processes them sequentially.
90
+
91
+ **One action per tool call.** Don't batch multiple operations into a single action. If provisioning requires creating an org, then creating a billing customer, those are two separate actions.
92
+
93
+ **Only propose tools from your available list.** You've been given a set of write tools you can propose actions for. If a required operation isn't in that list, note it as a caveat — don't invent actions for tools that don't exist.
94
+
95
+ **Payloads must be complete.** Include every required field the tool expects. Don't leave placeholders or TODOs in payloads. For values that don't exist yet because they come from a prior action, use a template reference (see below). If you can't determine a value at all, flag it in caveats and omit the action.
96
+
97
+ ### Action Dependencies (Template References)
98
+
99
+ When an action needs a value produced by a prior action — like an org ID that doesn't exist until the org is created — use a Handlebars template reference in the payload value.
100
+
101
+ **Syntax:** `{{ actions.N.result.path }}` where `N` is the zero-based action index and `path` navigates the result object.
102
+
103
+ At execution time, the system resolves these references using the actual results from completed actions. The human reviewer sees the template expression and understands the data flow — they're approving the intent ("use the org ID from step 1"), not the literal runtime value.
104
+
105
+ **Rules:**
106
+
107
+ - Only reference actions that come _before_ the current action (lower index)
108
+ - Use dot-path navigation to reach nested values: `{{ actions.0.result.org.id }}`
109
+ - Static values and template references can coexist in the same payload
110
+ - Every other payload field should still use concrete values you already know
111
+
112
+ **Example:**
113
+
114
+ ```json
115
+ {
116
+ "type": "mcp_call",
117
+ "config": { "tool": "create_billing_customer" },
118
+ "description": "Create ChargeBee billing customer for Acme Corp using new org ID",
119
+ "payload": "{\"orgId\":\"{{ actions.0.result.id }}\",\"orgName\":\"Acme Corp\",\"contactEmail\":\"jane@acme.com\"}"
120
+ }
121
+ ```
122
+
123
+ Here `orgId` will be resolved at runtime from the result of action 0 (e.g., `create_org`). The reviewer sees that billing depends on the org creation step. Note the payload is JSON-stringified — template references work inside the string and are resolved before parsing.
124
+
125
+ ## Example
126
+
127
+ Say you've been asked to provision an Orderful org for a new customer. You've queried Salesforce, searched for existing orgs, and gathered the relevant data. Your plan output might look like:
128
+
129
+ ```json
130
+ {
131
+ "evidence": {
132
+ "reasoning": "Salesforce account Acme Corp (SF-001234) was signed on 2026-03-01 and has no Orderful OrgID. No existing Orderful org matches by name or ISA ID. The account's primary contact is jane@acme.com. ISA ID derived from Customer_ISA_ID__c field: ACME001.",
133
+ "sourceSummary": "Salesforce: Acme Corp (SF-001234), signed 2026-03-01, no OrgID. Orderful: no org named 'Acme Corp', no ISA ID 'ACME001' in use.",
134
+ "sourceRaw": "{\"salesforce_account\":{\"Id\":\"SF-001234\",\"Name\":\"Acme Corp\",\"OrgID__c\":null,\"Customer_ISA_ID__c\":\"ACME001\"},\"orderful_org_search\":{\"results\":[]}}",
135
+ "impactSummary": "Creates Orderful org 'Acme Corp' with ISA ID 'ACME001', creates billing customer in ChargeBee, and sends welcome email to jane@acme.com.",
136
+ "caveats": ["ARR not found in Salesforce — billing customer will be created without revenue data. Manual update may be needed."]
137
+ },
138
+ "actions": [
139
+ {
140
+ "type": "mcp_call",
141
+ "config": { "tool": "create_org" },
142
+ "description": "Create Orderful org 'Acme Corp' with ISA ID 'ACME001'",
143
+ "payload": "{\"name\":\"Acme Corp\",\"isaId\":\"ACME001\",\"contactEmail\":\"jane@acme.com\"}"
144
+ },
145
+ {
146
+ "type": "mcp_call",
147
+ "config": { "tool": "create_billing_customer" },
148
+ "description": "Create ChargeBee billing customer for Acme Corp using new org ID",
149
+ "payload": "{\"orgId\":\"{{ actions.0.result.id }}\",\"orgName\":\"Acme Corp\",\"contactEmail\":\"jane@acme.com\"}"
150
+ },
151
+ {
152
+ "type": "mcp_call",
153
+ "config": { "tool": "invite_user" },
154
+ "description": "Send welcome email to jane@acme.com for Acme Corp org",
155
+ "payload": "{\"email\":\"jane@acme.com\",\"orgId\":\"{{ actions.0.result.id }}\"}"
156
+ }
157
+ ]
158
+ }
159
+ ```
160
+
161
+ ## What Not To Do
162
+
163
+ These are hard rules. Violating any of them will cause the plan to be rejected or produce incorrect results.
164
+
165
+ - **Don't call write tools directly.** Your job is to propose, not execute. Every write operation must be described as an action in your plan output. If you call a write tool yourself, the approval flow is bypassed and there is no audit trail.
166
+ - **Don't include read operations as actions.** Actions are for writes only — things that change state and need human approval. Reading data is research, not an action. If you need to look something up, use read tools directly during your research phase.
167
+ - **Don't fabricate data.** If a value wasn't in your research and you can't verify it with read tools, say so in caveats. Never guess IDs, names, emails, or configuration values. A plan with honest gaps gets sent back for more research. A plan with invented data gets approved and breaks things.
168
+ - **Don't produce actions for tools outside your available list.** You can only propose actions for tools you've been given. If an operation requires a tool you don't have, note it as a caveat — don't invent an action for it. The execution step will reject unknown tools.
169
+ - **Don't skip evidence.** A plan without evidence will be rejected. The reviewer needs to see what you found, what it means, and what will change. Evidence is not optional filler — it is how the reviewer decides whether to approve.
170
+ - **Don't combine multiple operations into one action.** Each action is one tool call. "Create org and set up billing" is two actions, not one. The execution step calls each action individually — a combined action will fail.
171
+ - **Don't hardcode values from actions that haven't run yet.** If action 2 needs the org ID from action 1, don't guess or invent a value — use a template reference: `"orgId": "{{ actions.0.result.id }}"`. The execution step resolves these at runtime. Never put a placeholder like `"TBD"` or `"<org-id>"` in a payload.
172
+ - **Don't use vague descriptions.** "Create org" tells the reviewer nothing. "Create Orderful org 'Acme Corp' with ISA ID 'ACME001'" tells them exactly what will happen. The description is what the reviewer reads before deciding whether to expand the payload.
@@ -0,0 +1,83 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "PlanOutput",
4
+ "description": "Structured plan output produced by an LLM agent for human review and approval",
5
+ "type": "object",
6
+ "required": ["evidence", "actions"],
7
+ "properties": {
8
+ "name": {
9
+ "type": "string",
10
+ "description": "Short human-readable name for the plan"
11
+ },
12
+ "category": {
13
+ "type": "string",
14
+ "description": "Grouping category for the plan (e.g. org-provisioning)"
15
+ },
16
+ "evidence": {
17
+ "type": "object",
18
+ "description": "Research findings and rationale supporting the plan",
19
+ "required": ["reasoning", "sourceSummary", "impactSummary"],
20
+ "properties": {
21
+ "reasoning": {
22
+ "type": "string",
23
+ "description": "Analysis of what needs to happen and why"
24
+ },
25
+ "sourceSummary": {
26
+ "type": "string",
27
+ "description": "Concise summary of research findings with entity names and IDs"
28
+ },
29
+ "sourceRaw": {
30
+ "type": "string",
31
+ "description": "JSON-stringified raw source data from research"
32
+ },
33
+ "impactSummary": {
34
+ "type": "string",
35
+ "description": "What changes if the plan is approved"
36
+ },
37
+ "caveats": {
38
+ "type": "array",
39
+ "items": { "type": "string" },
40
+ "description": "Risks or caveats to flag for review"
41
+ }
42
+ },
43
+ "additionalProperties": false
44
+ },
45
+ "actions": {
46
+ "type": "array",
47
+ "description": "Ordered list of MCP tool calls to execute",
48
+ "items": {
49
+ "type": "object",
50
+ "required": ["type", "config", "description", "payload"],
51
+ "properties": {
52
+ "type": {
53
+ "type": "string",
54
+ "const": "mcp_call",
55
+ "description": "Action category — v0 only supports mcp_call"
56
+ },
57
+ "config": {
58
+ "type": "object",
59
+ "description": "Configuration specifying which MCP tool to invoke",
60
+ "required": ["tool"],
61
+ "properties": {
62
+ "tool": {
63
+ "type": "string",
64
+ "description": "Tool name as it appears in the available tools"
65
+ }
66
+ },
67
+ "additionalProperties": false
68
+ },
69
+ "description": {
70
+ "type": "string",
71
+ "description": "Human-readable description of what this action does"
72
+ },
73
+ "payload": {
74
+ "type": "string",
75
+ "description": "JSON-stringified arguments to pass to the MCP tool"
76
+ }
77
+ },
78
+ "additionalProperties": false
79
+ }
80
+ }
81
+ },
82
+ "additionalProperties": false
83
+ }