intentdocs 0.10.0 → 0.11.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 (30) hide show
  1. package/README.md +6 -1
  2. package/dist/chunk-64I7RLP3.js +171 -0
  3. package/dist/{chunk-IDNUBI2P.js → chunk-65EDM7QH.js} +6 -6
  4. package/dist/{chunk-NFIXRIHM.js → chunk-BNEU6TLJ.js} +55 -21
  5. package/dist/chunk-GFKZ5CRL.js +12 -0
  6. package/dist/{chunk-5USVG4DW.js → chunk-ID4B5RZ2.js} +42 -21
  7. package/dist/{chunk-EEKEF2FD.js → chunk-IORMKLAQ.js} +178 -121
  8. package/dist/{chunk-UF535HZL.js → chunk-K5SNF6CG.js} +13 -2
  9. package/dist/{chunk-HEHHSSUM.js → chunk-KPQ42YE6.js} +1 -1
  10. package/dist/{chunk-YUW3W43S.js → chunk-KTGQO3AE.js} +31 -8
  11. package/dist/{chunk-UR73WX5I.js → chunk-UQDMUKLP.js} +2 -2
  12. package/dist/{chunk-LXQXJULN.js → chunk-XZE2XP6O.js} +3 -3
  13. package/dist/ci-NOCV3XUV.js +11 -0
  14. package/dist/{complexity-ZOB3BPVD.js → complexity-OJVM37NF.js} +4 -4
  15. package/dist/{import-prd-MJUCIMHX.js → import-prd-5FCVXCYO.js} +14 -7
  16. package/dist/index.js +377 -359
  17. package/dist/{keys-5PA3D6CX.js → keys-2W5V56FS.js} +32 -11
  18. package/dist/{log-EVLWM64C.js → log-DFCCAAJN.js} +23 -7
  19. package/dist/{map-extract-W6WJLVOF.js → map-extract-YUCFLGJS.js} +2 -2
  20. package/dist/{map-import-LL4AIAQE.js → map-import-7RN2NEUZ.js} +7 -6
  21. package/dist/{map-schema-6GL2UKZQ.js → map-schema-5BEIJM2J.js} +1 -1
  22. package/dist/{projects-F7GMH66U.js → projects-AYAYVFKL.js} +2 -2
  23. package/dist/push-33V7ANJW.js +462 -0
  24. package/dist/{research-4ASCZSZN.js → research-SHO5SXVQ.js} +4 -4
  25. package/dist/scope-RRGJHS2G.js +13 -0
  26. package/dist/setup-UILQVYIB.js +14 -0
  27. package/package.json +6 -5
  28. package/dist/ci-LBOCMRKZ.js +0 -11
  29. package/dist/scope-S3LZZ3KB.js +0 -11
  30. package/dist/setup-YLUPBLK6.js +0 -14
package/dist/index.js CHANGED
@@ -1,22 +1,18 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ appendToNotes
4
+ } from "./chunk-GFKZ5CRL.js";
2
5
  import {
3
6
  UPGRADE_MESSAGE_PLAIN,
4
7
  isPlanGated
5
- } from "./chunk-UR73WX5I.js";
8
+ } from "./chunk-UQDMUKLP.js";
6
9
  import {
7
10
  ci
8
- } from "./chunk-IDNUBI2P.js";
11
+ } from "./chunk-65EDM7QH.js";
9
12
  import {
10
13
  setup,
11
14
  sync
12
- } from "./chunk-EEKEF2FD.js";
13
- import {
14
- captureCli,
15
- shutdownCliAnalytics
16
- } from "./chunk-HEHHSSUM.js";
17
- import {
18
- scope
19
- } from "./chunk-5USVG4DW.js";
15
+ } from "./chunk-IORMKLAQ.js";
20
16
  import {
21
17
  criterionPriority,
22
18
  criterionStatusLabel,
@@ -24,11 +20,22 @@ import {
24
20
  formatDesignRefLine,
25
21
  storyContextPath,
26
22
  writeStoryContext
27
- } from "./chunk-NFIXRIHM.js";
23
+ } from "./chunk-BNEU6TLJ.js";
28
24
  import {
29
25
  matchesStoryRef,
30
26
  storyRef
31
27
  } from "./chunk-LQTPZL2Y.js";
28
+ import {
29
+ captureCli,
30
+ shutdownCliAnalytics
31
+ } from "./chunk-KPQ42YE6.js";
32
+ import {
33
+ scope
34
+ } from "./chunk-ID4B5RZ2.js";
35
+ import {
36
+ login,
37
+ logout
38
+ } from "./chunk-64I7RLP3.js";
32
39
  import {
33
40
  amendDecision,
34
41
  apiFetch,
@@ -43,13 +50,14 @@ import {
43
50
  syncHeaders,
44
51
  updateStories,
45
52
  uploadScreenshot
46
- } from "./chunk-YUW3W43S.js";
53
+ } from "./chunk-KTGQO3AE.js";
47
54
  import {
55
+ checkoutConfig,
48
56
  hasConfig,
49
57
  readConfig,
50
58
  requireConfig,
51
59
  writeConfig
52
- } from "./chunk-UF535HZL.js";
60
+ } from "./chunk-K5SNF6CG.js";
53
61
  import {
54
62
  ask,
55
63
  closePrompt,
@@ -59,13 +67,9 @@ import {
59
67
  } from "./chunk-WRRUCCVZ.js";
60
68
  import {
61
69
  authenticatedFetch,
62
- bindingLabel,
63
- clearAuth,
64
70
  getAuth,
65
- requireValidAuth,
66
- saveAuth,
67
- validateAuth
68
- } from "./chunk-LXQXJULN.js";
71
+ requireValidAuth
72
+ } from "./chunk-XZE2XP6O.js";
69
73
  import {
70
74
  API_BASE
71
75
  } from "./chunk-UYTVTYNB.js";
@@ -75,168 +79,96 @@ import { Command } from "commander";
75
79
  import { createRequire as createRequire2 } from "module";
76
80
 
77
81
  // src/commands/init.ts
78
- import { writeFileSync, existsSync, readFileSync, appendFileSync, statSync } from "fs";
79
- import { join } from "path";
82
+ import { writeFileSync as writeFileSync2, existsSync as existsSync2, readFileSync as readFileSync2, appendFileSync, statSync } from "fs";
83
+ import { join as join2 } from "path";
80
84
  import { createInterface } from "readline";
81
85
 
82
- // src/commands/login.ts
83
- import { exec } from "child_process";
84
- function finishLogin(data) {
85
- saveAuth({
86
- token: data.token,
87
- apiBase: API_BASE,
88
- ...data.userId && { userId: data.userId },
89
- binding: data.binding ?? null,
90
- ...data.posthogKey && { posthogKey: data.posthogKey },
91
- ...data.posthogHost && { posthogHost: data.posthogHost }
92
- });
93
- console.log(`
94
-
95
- \u2705 Logged in as ${data.userId ?? "unknown user"} \u2014 token ${bindingLabel(data.binding)}
96
- `);
97
- if (data.binding?.approvalStatus === "pending") {
98
- console.log("An admin has been asked to approve it. Until they do, this token reads your personal projects only.\n");
86
+ // src/lib/agent-permissions.ts
87
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
88
+ import { homedir } from "os";
89
+ import { dirname, join } from "path";
90
+ var READ_TOOLS = [
91
+ "list_projects",
92
+ "get_context",
93
+ "get_session_context",
94
+ "list_stories",
95
+ "get_story",
96
+ "get_status",
97
+ "get_build_order",
98
+ "get_vision",
99
+ "get_design",
100
+ "get_run"
101
+ ];
102
+ var BUILD_LOOP_TOOLS = [
103
+ "next_in_run",
104
+ "update_run",
105
+ "start_story",
106
+ "report_criteria",
107
+ "save_tests",
108
+ "complete_story",
109
+ "link_pr",
110
+ "add_note"
111
+ ];
112
+ var STILL_ASKS = ["replace_criteria", "update_stories", "create_stories", "cancel_story"];
113
+ var ALLOWED_TOOLS = [...READ_TOOLS, ...BUILD_LOOP_TOOLS];
114
+ var DEFAULT_SERVER_NAME = "intentdocs";
115
+ function readJson(path) {
116
+ if (!existsSync(path)) return null;
117
+ try {
118
+ return JSON.parse(readFileSync(path, "utf-8"));
119
+ } catch {
120
+ return null;
99
121
  }
100
- console.log("You can now run: intentdocs init");
101
- console.log(
102
- "Optional: add your Anthropic API key to unlock AI features on any plan \u2014 intentdocs keys set"
103
- );
104
122
  }
105
- function openBrowser(url) {
106
- const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "${url}"` : `xdg-open "${url}"`;
107
- exec(cmd);
123
+ function intentdocsServers(servers) {
124
+ if (!servers || typeof servers !== "object") return [];
125
+ return Object.entries(servers).filter(([, s]) => (s?.url ?? "").includes("intentdocs.com/api/mcp") || (s?.args ?? []).includes("intentdocs")).map(([name]) => name);
108
126
  }
109
- function sleep(ms) {
110
- return new Promise((r) => setTimeout(r, ms));
127
+ function detectServerNames(cwd = process.cwd(), home = homedir()) {
128
+ const names = /* @__PURE__ */ new Set();
129
+ for (const n of intentdocsServers(readJson(join(cwd, ".mcp.json"))?.mcpServers)) names.add(n);
130
+ const user = readJson(join(home, ".claude.json"));
131
+ for (const n of intentdocsServers(user?.mcpServers)) names.add(n);
132
+ const projects = user?.projects ?? {};
133
+ for (const n of intentdocsServers(projects[cwd]?.mcpServers)) names.add(n);
134
+ return names.size > 0 ? [...names] : [DEFAULT_SERVER_NAME];
111
135
  }
112
- async function login() {
113
- const existing = getAuth();
114
- if (existing) {
115
- const result = await validateAuth();
116
- if (result.valid) {
117
- console.log(`Already logged in (user: ${result.userId}). Run \`intentdocs logout\` to switch accounts.`);
118
- return;
119
- }
120
- switch (result.reason) {
121
- case "revoked":
122
- console.log("Your stored token has been revoked. Starting a fresh login...\n");
123
- break;
124
- case "not_found":
125
- console.log("Your stored token is no longer valid. Starting a fresh login...\n");
126
- break;
127
- case "network_error":
128
- console.error("Could not reach intentdocs.com to verify your token. Check your internet connection and try again.");
129
- process.exit(1);
130
- break;
131
- }
132
- clearAuth();
133
- }
134
- console.log("Logging in to intentdocs...\n");
135
- const res = await fetch(`${API_BASE}/api/cli/auth`, { method: "POST" });
136
- if (!res.ok) {
137
- console.error("Failed to start login flow. Try again.");
138
- process.exit(1);
139
- }
140
- const { code } = await res.json();
141
- const authUrl = `${API_BASE}/cli/auth?code=${code}`;
142
- console.log("Opening browser to authorize...");
143
- console.log(`If it doesn't open, visit: ${authUrl}
144
- `);
145
- openBrowser(authUrl);
146
- const POLL_INTERVAL_MS = 3e3;
147
- const INITIAL_TIMEOUT_S = 300;
148
- const EXTEND_TIMEOUT_S = 120;
149
- let totalWaitedS = 0;
150
- let phase = "initial";
151
- let timeoutS = INITIAL_TIMEOUT_S;
152
- while (totalWaitedS < timeoutS) {
153
- const elapsed = totalWaitedS;
154
- const minutesLeft = Math.ceil((timeoutS - elapsed) / 60);
155
- if (elapsed === 0) {
156
- process.stdout.write("Waiting for browser approval...");
157
- } else if (elapsed % 30 === 0) {
158
- process.stdout.write(`
159
- Still waiting for approval (${minutesLeft}m remaining)...`);
160
- } else {
161
- process.stdout.write(".");
162
- }
163
- await sleep(POLL_INTERVAL_MS);
164
- totalWaitedS += POLL_INTERVAL_MS / 1e3;
165
- try {
166
- const checkRes = await fetch(`${API_BASE}/api/cli/auth?code=${code}`);
167
- if (!checkRes.ok) continue;
168
- const data = await checkRes.json();
169
- if (data.status === "approved" && data.token) {
170
- finishLogin(data);
171
- return;
172
- }
173
- if (data.status === "expired") {
174
- console.error("\n\nThe server-side login request expired. Run `intentdocs login` to start a new one.");
175
- process.exit(1);
176
- }
177
- } catch {
178
- }
179
- }
180
- if (phase === "initial") {
181
- console.log("\n\nThe initial wait has ended. The browser approval hasn't come through yet.");
182
- console.log("This can happen if you needed to create an account or got distracted.\n");
183
- const rl = await import("readline");
184
- const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
185
- const answer = await new Promise(
186
- (resolve) => iface.question("Keep waiting? (y/n): ", resolve)
187
- );
188
- iface.close();
189
- if (answer.trim().toLowerCase().startsWith("y")) {
190
- phase = "extended";
191
- timeoutS = totalWaitedS + EXTEND_TIMEOUT_S;
192
- console.log(`
193
- Waiting ${EXTEND_TIMEOUT_S / 60} more minutes...`);
194
- while (totalWaitedS < timeoutS) {
195
- const minutesLeft = Math.ceil((timeoutS - totalWaitedS) / 60);
196
- if (Math.floor(totalWaitedS) % 30 === 0 && totalWaitedS > INITIAL_TIMEOUT_S) {
197
- process.stdout.write(`
198
- Still waiting (${minutesLeft}m remaining)...`);
199
- } else {
200
- process.stdout.write(".");
201
- }
202
- await sleep(POLL_INTERVAL_MS);
203
- totalWaitedS += POLL_INTERVAL_MS / 1e3;
204
- try {
205
- const checkRes = await fetch(`${API_BASE}/api/cli/auth?code=${code}`);
206
- if (!checkRes.ok) continue;
207
- const data = await checkRes.json();
208
- if (data.status === "approved" && data.token) {
209
- finishLogin(data);
210
- return;
211
- }
212
- if (data.status === "expired") {
213
- console.error("\n\nThe server-side login request expired. Run `intentdocs login` to start a new one.");
214
- process.exit(1);
215
- }
216
- } catch {
217
- }
218
- }
219
- }
220
- }
221
- console.error("\n\nLogin timed out. Run `intentdocs login` to try again.");
222
- console.error("If the browser page is still open, complete it and re-run the command \u2014 your approval will still work within 10 minutes.\n");
223
- process.exit(1);
136
+ function allowRules(serverNames, tools = ALLOWED_TOOLS) {
137
+ return serverNames.flatMap((server) => tools.map((tool) => `mcp__${server}__${tool}`));
224
138
  }
225
- async function logout() {
226
- const { existsSync: existsSync5, unlinkSync } = await import("fs");
227
- const { join: join5 } = await import("path");
228
- const { homedir } = await import("os");
229
- const authFile = join5(homedir(), ".intent", "auth.json");
230
- if (existsSync5(authFile)) {
231
- unlinkSync(authFile);
232
- console.log("Logged out.");
233
- } else {
234
- console.log("Not logged in.");
235
- }
139
+ function withAllowRules(settings, rules) {
140
+ const permissions = { ...settings.permissions ?? {} };
141
+ const allow = Array.isArray(permissions.allow) ? permissions.allow : [];
142
+ permissions.allow = [...allow, ...rules.filter((r) => !allow.includes(r))];
143
+ return { ...settings, permissions };
144
+ }
145
+ function writeProjectAllowList(cwd, rules) {
146
+ const path = join(cwd, ".claude", "settings.json");
147
+ const before = readJson(path) ?? {};
148
+ const existing = new Set(before.permissions?.allow ?? []);
149
+ const next2 = withAllowRules(before, rules);
150
+ mkdirSync(dirname(path), { recursive: true });
151
+ writeFileSync(path, JSON.stringify(next2, null, 2) + "\n");
152
+ return { path, added: rules.filter((r) => !existing.has(r)).length };
153
+ }
154
+ function describeAllowList(serverNames) {
155
+ return [
156
+ ` Allow these IntentDocs tools without asking, in .claude/settings.json (server: ${serverNames.join(", ")})?`,
157
+ "",
158
+ ` Reads: ${READ_TOOLS.join(", ")}`,
159
+ ` Build loop: ${BUILD_LOOP_TOOLS.join(", ")}`,
160
+ "",
161
+ ` Still asks: ${STILL_ASKS.join(", ")} \u2014 anything that changes the spec or cancels work.`
162
+ ].join("\n");
236
163
  }
164
+ var CURSOR_ALLOW_NOTE = [
165
+ " Cursor: in its settings, find the agent's auto-run options and allow these",
166
+ ` intentdocs MCP tools: ${ALLOWED_TOOLS.join(", ")}.`,
167
+ ` Leave ${STILL_ASKS.join(", ")} asking.`
168
+ ].join("\n");
237
169
 
238
170
  // src/commands/init.ts
239
- var INTENTDOCS_CLAUDE_BLOCK = "\n\n# intentdocs\nRead INTENT.md for the product story map, personas, and backlog.\nWhen you call `save_tests`, pass the `file` and the `full_name` (the full test name, describe blocks included) for every test, so CI can report each test's result back to its story.\nWhen you open a pull request for a story, pass its URL to `intent done` or `complete_story`; intentdocs stamps the PR with a 'Spec \u2192 Code via intentdocs.com' footer linking back to the story when GitHub is connected. If it is not, end the PR body with:\n---\nSpec \u2192 Code via [intentdocs.com](https://intentdocs.com?utm_source=github&utm_medium=pr-footer) \xB7 [story <story-id>](<story-url>)\n";
171
+ var INTENTDOCS_CLAUDE_BLOCK = "\n\n# intentdocs\nRead INTENT.md for the product story map, personas, and backlog.\nSave every test you write for a story with `save_tests`, linked to the acceptance criteria it proves, and pass the `file` and the `full_name` (the full test name, describe blocks included) for each, so CI can report each test's result back to its story.\nWhen you open a pull request for a story, pass its URL to `npx intentdocs done <story> --pr <url>` or to `complete_story` (or `link_pr`); intentdocs stamps the PR with a 'Spec \u2192 Code via intentdocs.com' footer linking back to the story when GitHub is connected. Add the footer yourself only when that reply asks you to (it says nothing when the project turned the line off); it will look like:\n---\nSpec \u2192 Code via [intentdocs.com](https://intentdocs.com?utm_source=github&utm_medium=pr-footer) \xB7 [story <story-id>](<story-url>)\n";
240
172
  function ask2(rl, question) {
241
173
  return new Promise((resolve) => rl.question(question, resolve));
242
174
  }
@@ -254,8 +186,8 @@ ${prompt} [1-${items.length}]: `);
254
186
  }
255
187
  function detectStack(cwd) {
256
188
  try {
257
- if (existsSync(join(cwd, "package.json"))) {
258
- const raw = readFileSync(join(cwd, "package.json"), "utf-8");
189
+ if (existsSync2(join2(cwd, "package.json"))) {
190
+ const raw = readFileSync2(join2(cwd, "package.json"), "utf-8");
259
191
  const pkg = JSON.parse(raw);
260
192
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
261
193
  if (deps["next"]) return "Next.js (React, TypeScript)";
@@ -267,24 +199,24 @@ function detectStack(cwd) {
267
199
  if (deps["express"]) return "Node.js (Express, TypeScript)";
268
200
  return "Node.js (TypeScript)";
269
201
  }
270
- if (existsSync(join(cwd, "pyproject.toml")) || existsSync(join(cwd, "requirements.txt"))) {
271
- const hasFastapi = existsSync(join(cwd, "pyproject.toml")) && readFileSync(join(cwd, "pyproject.toml"), "utf-8").includes("fastapi");
202
+ if (existsSync2(join2(cwd, "pyproject.toml")) || existsSync2(join2(cwd, "requirements.txt"))) {
203
+ const hasFastapi = existsSync2(join2(cwd, "pyproject.toml")) && readFileSync2(join2(cwd, "pyproject.toml"), "utf-8").includes("fastapi");
272
204
  return hasFastapi ? "Python (FastAPI)" : "Python";
273
205
  }
274
- if (existsSync(join(cwd, "Cargo.toml"))) return "Rust";
275
- if (existsSync(join(cwd, "go.mod"))) return "Go";
276
- if (existsSync(join(cwd, "pubspec.yaml"))) return "Flutter (Dart)";
206
+ if (existsSync2(join2(cwd, "Cargo.toml"))) return "Rust";
207
+ if (existsSync2(join2(cwd, "go.mod"))) return "Go";
208
+ if (existsSync2(join2(cwd, "pubspec.yaml"))) return "Flutter (Dart)";
277
209
  } catch {
278
210
  }
279
211
  return "";
280
212
  }
281
213
  async function init(tokenArg) {
282
214
  if (hasConfig()) {
283
- console.log("Already initialized. Run `intentdocs sync` to refresh.");
215
+ console.log("Already initialized. Run `npx intentdocs sync` to refresh.");
284
216
  return;
285
217
  }
286
218
  if (tokenArg) {
287
- console.error("Sync tokens have been retired \u2014 `intentdocs init` no longer takes one. Run `intentdocs login`, then `intentdocs init` and pick the project and map.");
219
+ console.error("Sync tokens have been retired \u2014 `npx intentdocs init` no longer takes one. Run `npx intentdocs login`, then `npx intentdocs init` and pick the project and map.");
288
220
  process.exit(1);
289
221
  }
290
222
  let auth = getAuth();
@@ -332,14 +264,7 @@ ${whereIAm}`);
332
264
  Story maps in "${project.name}":
333
265
  `);
334
266
  const map2 = await pickFromList(rl, maps, (m) => m.title, "Select story map");
335
- const config = {
336
- apiBase: auth.apiBase,
337
- mapTitle: map2.title,
338
- projectId: project.id,
339
- mapId: map2.id,
340
- workspace: project.workspace ? { orgId: project.workspace.orgId, name: project.workspace.name } : { orgId: null, name: null },
341
- ...appUrl && { appUrl }
342
- };
267
+ const config = checkoutConfig({ apiBase: auth.apiBase, project, map: map2, appUrl });
343
268
  console.log(`
344
269
  Connecting to "${map2.title}"${project.workspace ? ` in ${project.workspace.name ?? "its workspace"}` : ""}...`);
345
270
  try {
@@ -356,8 +281,8 @@ Connecting to "${map2.title}"${project.workspace ? ` in ${project.workspace.name
356
281
  writeConfig(config);
357
282
  await setupFiles(config);
358
283
  await setupStack(rl);
359
- const specPath = join(process.cwd(), "INTENT.md");
360
- const claudeMdBytes = existsSync(specPath) ? statSync(specPath).size : 0;
284
+ const specPath = join2(process.cwd(), "INTENT.md");
285
+ const claudeMdBytes = existsSync2(specPath) ? statSync(specPath).size : 0;
361
286
  captureCli(
362
287
  "harness_initialized",
363
288
  {
@@ -370,28 +295,48 @@ Connecting to "${map2.title}"${project.workspace ? ` in ${project.workspace.name
370
295
  console.log(`
371
296
  \u2705 Ready! Next steps:
372
297
 
373
- intent status \u2014 see your story map
374
- intent next \u2014 pick up the first story
375
- intent setup \u2014 configure Claude Code / Cursor slash commands
376
- intent ci \u2014 report your tests' results from CI to each story
377
- intent propose \u2014 plan a new feature from the terminal
298
+ npx intentdocs status \u2014 see your story map
299
+ npx intentdocs next \u2014 pick up the first story
300
+ npx intentdocs setup \u2014 configure Claude Code / Cursor slash commands
301
+ npx intentdocs ci \u2014 report your tests' results from CI to each story
302
+ npx intentdocs propose \u2014 plan a new feature from the terminal
378
303
  `);
304
+ await offerAllowList((q) => ask2(rl, q));
379
305
  const setUpCi = await ask2(rl, " Set up CI test reporting now? It creates a key and adds the workflow. (y/N): ");
380
306
  if (/^y(es)?$/i.test(setUpCi.trim())) {
381
- const { ci: ci2 } = await import("./ci-LBOCMRKZ.js");
307
+ const { ci: ci2 } = await import("./ci-NOCV3XUV.js");
382
308
  await ci2();
383
309
  } else {
384
- console.log(" You can run `intent ci` any time to turn it on.\n");
310
+ console.log(" You can run `npx intentdocs ci` any time to turn it on.\n");
385
311
  }
386
312
  } finally {
387
313
  rl.close();
388
314
  await shutdownCliAnalytics();
389
315
  }
390
316
  }
317
+ async function offerAllowList(askFn, cwd = process.cwd(), home) {
318
+ const servers = detectServerNames(cwd, home);
319
+ console.log(`
320
+ ${describeAllowList(servers)}
321
+ `);
322
+ const answer = await askFn(" Add them? (y/N): ");
323
+ if (!/^y(es)?$/i.test(answer.trim())) {
324
+ console.log(" Left as is \u2014 Claude Code will keep asking before each call.\n");
325
+ console.log(`${CURSOR_ALLOW_NOTE}
326
+ `);
327
+ return false;
328
+ }
329
+ const { path, added } = writeProjectAllowList(cwd, allowRules(servers));
330
+ console.log(` \u2713 Allowed ${added} ${added === 1 ? "tool" : "tools"} in ${path.replace(cwd + "/", "")}
331
+ `);
332
+ console.log(`${CURSOR_ALLOW_NOTE}
333
+ `);
334
+ return true;
335
+ }
391
336
  async function setupStack(rl) {
392
337
  const cwd = process.cwd();
393
- const archPath = join(cwd, "ARCHITECTURE.md");
394
- if (existsSync(archPath)) return;
338
+ const archPath = join2(cwd, "ARCHITECTURE.md");
339
+ if (existsSync2(archPath)) return;
395
340
  const detected = detectStack(cwd);
396
341
  let stack;
397
342
  if (detected) {
@@ -432,38 +377,38 @@ _Record trade-offs and why._
432
377
 
433
378
  _Add important files and what they do as you build._
434
379
  `;
435
- writeFileSync(archPath, archContent);
380
+ writeFileSync2(archPath, archContent);
436
381
  console.log(` \u2713 Created ARCHITECTURE.md with your stack (edit it as you build)`);
437
382
  }
438
383
  async function setupFiles(config) {
439
384
  try {
440
385
  const md = await getContext(config);
441
- writeFileSync(join(process.cwd(), "INTENT.md"), md);
386
+ writeFileSync2(join2(process.cwd(), "INTENT.md"), md);
442
387
  console.log("\u2713 Downloaded INTENT.md");
443
388
  } catch {
444
389
  console.warn("\u26A0 Could not download INTENT.md");
445
390
  }
446
- const gitignore = join(process.cwd(), ".gitignore");
447
- if (existsSync(gitignore)) {
448
- const content = readFileSync(gitignore, "utf-8");
391
+ const gitignore = join2(process.cwd(), ".gitignore");
392
+ if (existsSync2(gitignore)) {
393
+ const content = readFileSync2(gitignore, "utf-8");
449
394
  if (!content.includes(".intent/")) {
450
395
  appendFileSync(gitignore, "\n# intentdocs\n.intent/\n");
451
396
  console.log("\u2713 Added .intent/ to .gitignore");
452
397
  }
453
398
  }
454
399
  const cwd = process.cwd();
455
- if (existsSync(join(cwd, "CLAUDE.md"))) {
456
- const claudeMd = readFileSync(join(cwd, "CLAUDE.md"), "utf-8");
400
+ if (existsSync2(join2(cwd, "CLAUDE.md"))) {
401
+ const claudeMd = readFileSync2(join2(cwd, "CLAUDE.md"), "utf-8");
457
402
  if (!claudeMd.includes("INTENT.md")) {
458
- appendFileSync(join(cwd, "CLAUDE.md"), INTENTDOCS_CLAUDE_BLOCK);
403
+ appendFileSync(join2(cwd, "CLAUDE.md"), INTENTDOCS_CLAUDE_BLOCK);
459
404
  console.log("\u2713 Added INTENT.md reference to CLAUDE.md");
460
405
  }
461
406
  }
462
- if (existsSync(join(cwd, ".cursorrules"))) {
463
- const rules = readFileSync(join(cwd, ".cursorrules"), "utf-8");
407
+ if (existsSync2(join2(cwd, ".cursorrules"))) {
408
+ const rules = readFileSync2(join2(cwd, ".cursorrules"), "utf-8");
464
409
  if (!rules.includes("INTENT.md")) {
465
410
  appendFileSync(
466
- join(cwd, ".cursorrules"),
411
+ join2(cwd, ".cursorrules"),
467
412
  "\n\nRead INTENT.md for the product story map, personas, and backlog.\n"
468
413
  );
469
414
  console.log("\u2713 Added INTENT.md reference to .cursorrules");
@@ -646,8 +591,8 @@ function kanbanView(stories2) {
646
591
 
647
592
  // src/commands/next.ts
648
593
  import { execSync } from "child_process";
649
- import { writeFileSync as writeFileSync2 } from "fs";
650
- import { join as join2 } from "path";
594
+ import { writeFileSync as writeFileSync3 } from "fs";
595
+ import { join as join3 } from "path";
651
596
 
652
597
  // src/lib/verification-suggestions.ts
653
598
  function needsVerificationSuggestion(ac) {
@@ -692,13 +637,11 @@ function parseVerificationSuggestions(raw) {
692
637
  // src/commands/next.ts
693
638
  var PRIORITY_ORDER = { must: 0, should: 1, could: 2 };
694
639
  var EFFORT_ORDER = { S: 0, M: 1, L: 2, XL: 3 };
695
- function unsequencedRefusal(story, apiBase, mapId) {
640
+ function unsequencedNote(story, apiBase, mapId) {
696
641
  const planUrl = `${apiBase}/storymapper/${mapId}?phase=plan&story=${story.id}`;
697
642
  return `
698
- \u{1F512} "${story.title}" is unsequenced \u2014 it hasn't been ordered on the Plan view yet.
699
-
700
- Agents only build stories you've deliberately sequenced. Drag it into the
701
- plan (or set its build order) here, then retry:
643
+ \u2139\uFE0F "${story.title}" isn't in the build order yet. Starting it adds it to the end;
644
+ reorder it on the Plan view if it belongs elsewhere:
702
645
  \u2192 ${planUrl}
703
646
  `;
704
647
  }
@@ -714,10 +657,7 @@ async function next(opts = {}) {
714
657
  `);
715
658
  return;
716
659
  }
717
- if (match.build_sequence == null) {
718
- console.log(unsequencedRefusal(match, config.apiBase, data.map.id));
719
- return;
720
- }
660
+ if (match.build_sequence == null) console.log(unsequencedNote(match, config.apiBase, data.map.id));
721
661
  s = match;
722
662
  } else {
723
663
  if (!opts.skipWip) {
@@ -728,10 +668,10 @@ async function next(opts = {}) {
728
668
  `);
729
669
  for (const ip of inProgress) {
730
670
  console.log(` \u{1F528} ${ip.title}`);
731
- console.log(` ID: ${ip.id.slice(0, 8)} | Mark done: intent done ${ip.id.slice(0, 8)}`);
671
+ console.log(` ID: ${ip.id.slice(0, 8)} | Mark done: npx intentdocs done ${ip.id.slice(0, 8)}`);
732
672
  }
733
673
  console.log(`
734
- Finish those first, or run \`intentdocs next --skip-wip\` to pick the next todo anyway.
674
+ Finish those first, or run \`npx intentdocs next --skip-wip\` to pick the next todo anyway.
735
675
  `);
736
676
  return;
737
677
  }
@@ -759,12 +699,12 @@ async function next(opts = {}) {
759
699
  if (unsequencedCount > 0) {
760
700
  console.log(`
761
701
  \u{1F512} ${unsequencedCount} stor${unsequencedCount === 1 ? "y is" : "ies are"} unsequenced \u2014 agents only build stories you've deliberately ordered.`);
762
- console.log(` Sequence ${unsequencedCount === 1 ? "it" : "them"} on the Plan view, then run \`intentdocs next\` again.
702
+ console.log(` Sequence ${unsequencedCount === 1 ? "it" : "them"} on the Plan view, then run \`npx intentdocs next\` again.
763
703
  `);
764
704
  } else if (blockedCount > 0) {
765
705
  console.log(`
766
706
  \u{1F512} ${blockedCount} stories remain but all have unfinished dependencies.`);
767
- console.log(` Finish in-progress stories first, then run \`intentdocs next\` again.
707
+ console.log(` Finish in-progress stories first, then run \`npx intentdocs next\` again.
768
708
  `);
769
709
  } else {
770
710
  console.log("\n \u{1F389} All stories are done! Ship it.\n");
@@ -794,14 +734,14 @@ async function next(opts = {}) {
794
734
  console.log(` Plan: \u2705 Implementation plan available`);
795
735
  }
796
736
  console.log(`
797
- Start building: intent implement ${s.id.slice(0, 8)}
737
+ Start building: npx intentdocs implement ${storyRef(s)}
798
738
  `);
799
739
  if (opts.open) {
800
- openBrowser2(specUrl);
740
+ openBrowser(specUrl);
801
741
  }
802
742
  await shutdownCliAnalytics();
803
743
  }
804
- async function generateVerificationSuggestions(story, contextPath) {
744
+ async function generateVerificationSuggestions(story, contextPath, config) {
805
745
  const acs = story.acceptance_criteria;
806
746
  const candidates = acs.filter(needsVerificationSuggestion);
807
747
  if (acs.length === 0) {
@@ -814,13 +754,84 @@ async function generateVerificationSuggestions(story, contextPath) {
814
754
  `);
815
755
  return;
816
756
  }
817
- const apiKey = process.env.ANTHROPIC_API_KEY;
818
- if (!apiKey) {
819
- console.log(` \u26A0\uFE0F ANTHROPIC_API_KEY not set \u2014 skipping verification suggestions.
757
+ console.log(` Suggesting verification commands for ${candidates.length} criteri${candidates.length !== 1 ? "a" : "on"}\u2026`);
758
+ let found = null;
759
+ let serverRefusal = null;
760
+ if (config?.mapId) {
761
+ try {
762
+ const res = await fetch(apiUrl(config, "/verification-suggestions"), {
763
+ method: "POST",
764
+ headers: syncHeaders({ "Content-Type": "application/json" }),
765
+ body: JSON.stringify({ story_id: story.id })
766
+ });
767
+ if (res.ok) {
768
+ const json = await res.json();
769
+ const byId = new Map(acs.map((ac) => [ac.id, ac]));
770
+ found = {
771
+ criteria: json.criteria.map((c) => byId.get(c.id) ?? { id: c.id, text: c.text, done: false }),
772
+ suggestions: json.suggestions
773
+ };
774
+ } else if (isPlanGated(res.status)) {
775
+ serverRefusal = "plan";
776
+ } else if (res.status === 404 || res.status === 405) {
777
+ serverRefusal = "unsupported";
778
+ } else {
779
+ console.log(` \u26A0\uFE0F intentdocs couldn't suggest commands (${res.status}) \u2014 suggestion generation skipped.
780
+ `);
781
+ return;
782
+ }
783
+ } catch {
784
+ console.log(` \u26A0\uFE0F Network error \u2014 suggestion generation skipped.
785
+ `);
786
+ return;
787
+ }
788
+ } else {
789
+ serverRefusal = "unsupported";
790
+ }
791
+ if (!found) {
792
+ const envKey = process.env.ANTHROPIC_API_KEY;
793
+ if (!envKey) {
794
+ if (serverRefusal === "plan") {
795
+ console.log(` \u26A0\uFE0F Verification suggestions need a paid plan or your own Anthropic key \u2014 run \`npx intentdocs keys set\`. Skipped.
796
+ `);
797
+ } else {
798
+ console.log(` \u26A0\uFE0F No saved Anthropic key reached the server and ANTHROPIC_API_KEY is not set \u2014 skipping verification suggestions.
799
+ `);
800
+ }
801
+ return;
802
+ }
803
+ const suggestions = await suggestWithKey(envKey, story, candidates);
804
+ if (!suggestions) return;
805
+ found = { criteria: candidates, suggestions };
806
+ }
807
+ if (found.suggestions.length === 0) {
808
+ console.log(` \u26A0\uFE0F Couldn't parse a usable response from Anthropic \u2014 suggestion generation skipped.
820
809
  `);
821
810
  return;
822
811
  }
823
- console.log(` Suggesting verification commands for ${candidates.length} criteri${candidates.length !== 1 ? "a" : "on"}\u2026`);
812
+ const lines = [`# Suggested verification commands: ${story.title}`, ``, `_Generated by \`npx intentdocs implement --stub\` \u2014 copy into the criterion's command field, or run \`npx intentdocs check\` once wired up._`, ``];
813
+ console.log();
814
+ for (const suggestion of found.suggestions) {
815
+ const ac = found.criteria[suggestion.index - 1];
816
+ if (!ac) continue;
817
+ if (suggestion.executable && suggestion.command) {
818
+ console.log(` \u2192 ${ac.text}`);
819
+ console.log(` command: ${suggestion.command}${suggestion.expected ? ` (expect: ${suggestion.expected})` : ""}`);
820
+ lines.push(`- **${ac.text}**`, ` - command: \`${suggestion.command}\``, ...suggestion.expected ? [` - expected: ${suggestion.expected}`] : [], ``);
821
+ } else {
822
+ console.log(` \u2192 ${ac.text}`);
823
+ console.log(` not executable${suggestion.reason ? `: ${suggestion.reason}` : ""}`);
824
+ lines.push(`- **${ac.text}**`, ` - not executable${suggestion.reason ? `: ${suggestion.reason}` : ""}`, ``);
825
+ }
826
+ }
827
+ const suggestionsPath = contextPath.replace(/\.md$/, ".checks.md");
828
+ writeFileSync3(suggestionsPath, lines.join("\n"));
829
+ const relPath = suggestionsPath.replace(join3(process.cwd(), "/"), "");
830
+ console.log(`
831
+ \u2713 Suggestions written: ${relPath}
832
+ `);
833
+ }
834
+ async function suggestWithKey(apiKey, story, candidates) {
824
835
  const prompt = buildVerificationSuggestionPrompt(story.title, story.user_story, candidates);
825
836
  let raw;
826
837
  try {
@@ -840,44 +851,18 @@ async function generateVerificationSuggestions(story, contextPath) {
840
851
  if (!res.ok) {
841
852
  console.log(` \u26A0\uFE0F Anthropic API error (${res.status}) \u2014 suggestion generation skipped.
842
853
  `);
843
- return;
854
+ return null;
844
855
  }
845
856
  const json = await res.json();
846
857
  raw = json.content.find((c) => c.type === "text")?.text ?? "";
847
858
  } catch {
848
859
  console.log(` \u26A0\uFE0F Network error \u2014 suggestion generation skipped.
849
860
  `);
850
- return;
851
- }
852
- const suggestions = parseVerificationSuggestions(raw);
853
- if (suggestions.length === 0) {
854
- console.log(` \u26A0\uFE0F Couldn't parse a usable response from Anthropic \u2014 suggestion generation skipped.
855
- `);
856
- return;
857
- }
858
- const lines = [`# Suggested verification commands: ${story.title}`, ``, `_Generated by \`intentdocs next --stub\` \u2014 copy into the criterion's command field, or run \`intentdocs check\` once wired up._`, ``];
859
- console.log();
860
- for (const suggestion of suggestions) {
861
- const ac = candidates[suggestion.index - 1];
862
- if (!ac) continue;
863
- if (suggestion.executable && suggestion.command) {
864
- console.log(` \u2192 ${ac.text}`);
865
- console.log(` command: ${suggestion.command}${suggestion.expected ? ` (expect: ${suggestion.expected})` : ""}`);
866
- lines.push(`- **${ac.text}**`, ` - command: \`${suggestion.command}\``, ...suggestion.expected ? [` - expected: ${suggestion.expected}`] : [], ``);
867
- } else {
868
- console.log(` \u2192 ${ac.text}`);
869
- console.log(` not executable${suggestion.reason ? `: ${suggestion.reason}` : ""}`);
870
- lines.push(`- **${ac.text}**`, ` - not executable${suggestion.reason ? `: ${suggestion.reason}` : ""}`, ``);
871
- }
861
+ return null;
872
862
  }
873
- const suggestionsPath = contextPath.replace(/\.md$/, ".checks.md");
874
- writeFileSync2(suggestionsPath, lines.join("\n"));
875
- const relPath = suggestionsPath.replace(join2(process.cwd(), "/"), "");
876
- console.log(`
877
- \u2713 Suggestions written: ${relPath}
878
- `);
863
+ return parseVerificationSuggestions(raw);
879
864
  }
880
- function openBrowser2(url) {
865
+ function openBrowser(url) {
881
866
  try {
882
867
  const platform = process.platform;
883
868
  const cmd = platform === "darwin" ? `open "${url}"` : platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
@@ -890,13 +875,12 @@ function openBrowser2(url) {
890
875
  async function implement(storyId, opts = {}) {
891
876
  const config = requireConfig();
892
877
  const data = await listStories(config);
893
- const match = data.stories.find(
894
- (st) => st.id === storyId || st.id.startsWith(storyId)
895
- );
878
+ const match = data.stories.find((st) => matchesStoryRef(st, storyId));
896
879
  if (!match) {
897
880
  console.log(`
898
- \u274C No story found matching ID: ${storyId}
881
+ \u274C No story found matching "${storyId}"
899
882
  `);
883
+ process.exitCode = 1;
900
884
  return;
901
885
  }
902
886
  const s = match;
@@ -910,14 +894,11 @@ async function implement(storyId, opts = {}) {
910
894
  if (s.status === "in_progress") {
911
895
  console.log(`
912
896
  \u{1F528} "${s.title}" is already in progress.`);
913
- console.log(` Mark done: intent done ${s.id.slice(0, 8)}
897
+ console.log(` Mark done: npx intentdocs done ${storyRef(s)}
914
898
  `);
915
899
  return;
916
900
  }
917
- if (s.build_sequence == null) {
918
- console.log(unsequencedRefusal(s, config.apiBase, data.map.id));
919
- return;
920
- }
901
+ if (s.build_sequence == null) console.log(unsequencedNote(s, config.apiBase, data.map.id));
921
902
  const doneIds = new Set(
922
903
  data.stories.filter((st) => st.status === "done").map((st) => st.id)
923
904
  );
@@ -952,7 +933,7 @@ async function implement(storyId, opts = {}) {
952
933
  }
953
934
  console.log(` \u2502 \u2502`);
954
935
  console.log(` \u2502 Consider: \u2502`);
955
- console.log(` \u2502 intent sync --context refresh CLAUDE.md first \u2502`);
936
+ console.log(` \u2502 npx intentdocs sync --context (refresh CLAUDE.md) \u2502`);
956
937
  console.log(` \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
957
938
  `);
958
939
  const proceed = isInteractive() ? await confirm(" Assign this story and mark in progress?") : false;
@@ -976,8 +957,18 @@ async function implement(storyId, opts = {}) {
976
957
  `);
977
958
  }
978
959
  try {
979
- await updateStories(config, [{ id: s.id, status: "in_progress", cancelled_at: null }]);
980
- } catch {
960
+ const result = await updateStories(config, [{ id: s.id, status: "in_progress", cancelled_at: null }]);
961
+ if (result.missing?.includes(s.id)) {
962
+ throw new Error("the server reports the story matched no row \u2014 run `npx intentdocs stories` and try again");
963
+ }
964
+ } catch (err) {
965
+ console.error(`
966
+ \u2717 Not picked up: "${s.title}"`);
967
+ console.error(` ${err instanceof Error ? err.message : String(err)}
968
+ `);
969
+ process.exitCode = 1;
970
+ await shutdownCliAnalytics();
971
+ return;
981
972
  }
982
973
  const contextPath = writeStoryContext(s);
983
974
  const relPath = contextPath.replace(process.cwd() + "/", "");
@@ -999,22 +990,22 @@ async function implement(storyId, opts = {}) {
999
990
  console.log(` Priority: ${s.priority} | Effort: ${s.effort} | Release: ${s.release}${s.build_sequence ? ` | Build step: ${s.build_sequence}` : ""}`);
1000
991
  console.log(` Context: ${relPath}`);
1001
992
  console.log(` Spec: ${specUrl}`);
1002
- console.log(` Mark done: intent done ${s.id.slice(0, 8)}
993
+ console.log(` Mark done: npx intentdocs done ${storyRef(s)}
1003
994
  `);
1004
995
  if (opts.stub) {
1005
- await generateVerificationSuggestions(s, contextPath);
996
+ await generateVerificationSuggestions(s, contextPath, config);
1006
997
  }
1007
998
  await shutdownCliAnalytics();
1008
999
  }
1009
1000
 
1010
1001
  // src/commands/done.ts
1011
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
1002
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
1012
1003
 
1013
1004
  // src/lib/screenshot.ts
1014
1005
  import { createRequire } from "module";
1015
1006
  import { pathToFileURL } from "url";
1016
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
1017
- import { extname, join as join3 } from "path";
1007
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
1008
+ import { extname, join as join4 } from "path";
1018
1009
  var MAX_BYTES = 5 * 1024 * 1024;
1019
1010
  var EXT_MIME = {
1020
1011
  ".png": "image/png",
@@ -1024,7 +1015,7 @@ var EXT_MIME = {
1024
1015
  };
1025
1016
  async function loadPlaywright() {
1026
1017
  try {
1027
- const req = createRequire(join3(process.cwd(), "package.json"));
1018
+ const req = createRequire(join4(process.cwd(), "package.json"));
1028
1019
  const resolved = req.resolve("playwright");
1029
1020
  return await import(pathToFileURL(resolved).href);
1030
1021
  } catch {
@@ -1032,7 +1023,7 @@ async function loadPlaywright() {
1032
1023
  }
1033
1024
  }
1034
1025
  function readScreenshotFile(path) {
1035
- if (!existsSync2(path)) {
1026
+ if (!existsSync3(path)) {
1036
1027
  console.error(` Screenshot file not found: ${path}`);
1037
1028
  return null;
1038
1029
  }
@@ -1042,7 +1033,7 @@ function readScreenshotFile(path) {
1042
1033
  console.error(` Unsupported screenshot type "${ext}" \u2014 use .png, .jpg, or .webp`);
1043
1034
  return null;
1044
1035
  }
1045
- const buffer = readFileSync2(path);
1036
+ const buffer = readFileSync3(path);
1046
1037
  if (buffer.length > MAX_BYTES) {
1047
1038
  console.error(` Screenshot too large (${(buffer.length / 1024 / 1024).toFixed(1)}MB, max 5MB)`);
1048
1039
  return null;
@@ -1080,8 +1071,8 @@ async function captureScreenshot(appUrl) {
1080
1071
  // src/commands/done.ts
1081
1072
  function extractImplementationContext(storyId) {
1082
1073
  const path = storyContextPath(storyId);
1083
- if (!existsSync3(path)) return { summary: null, files: [] };
1084
- const content = readFileSync3(path, "utf-8");
1074
+ if (!existsSync4(path)) return { summary: null, files: [] };
1075
+ const content = readFileSync4(path, "utf-8");
1085
1076
  const lines = content.split("\n");
1086
1077
  const notesStart = lines.findIndex((l) => l.trim() === "## Implementation notes");
1087
1078
  const decisionsStart = lines.findIndex((l) => l.trim() === "## Decisions");
@@ -1121,8 +1112,25 @@ async function passesCriteriaGuard(story) {
1121
1112
  const proceed = await confirm(" Mark done anyway?");
1122
1113
  return { proceed, forced: proceed, blockedNonInteractive: false };
1123
1114
  }
1115
+ function prNumberFromUrl(url) {
1116
+ const m = url.match(/\/pull\/(\d+)\/?(?:[?#].*)?$/);
1117
+ return m ? Number(m[1]) : null;
1118
+ }
1124
1119
  async function done(idPrefix, opts = {}) {
1125
1120
  const config = requireConfig();
1121
+ const prUrl = opts.pr?.trim();
1122
+ if (prUrl !== void 0) {
1123
+ let valid = false;
1124
+ try {
1125
+ valid = /^https?:$/.test(new URL(prUrl).protocol);
1126
+ } catch {
1127
+ valid = false;
1128
+ }
1129
+ if (!valid) {
1130
+ console.error(` \u2717 --pr needs the pull request's URL, e.g. https://github.com/acme/app/pull/12 (got "${opts.pr}").`);
1131
+ process.exit(1);
1132
+ }
1133
+ }
1126
1134
  const data = await listStories(config);
1127
1135
  const match = data.stories.find((s) => matchesStoryRef(s, idPrefix));
1128
1136
  if (!match) {
@@ -1140,7 +1148,7 @@ async function done(idPrefix, opts = {}) {
1140
1148
  console.error(
1141
1149
  ` \u2717 Not marked done: "${match.title}" has unresolved 'must' criteria and no TTY is available to confirm an override.`
1142
1150
  );
1143
- console.error(` Report the criteria (intent check / report_criteria) or re-run interactively to force it.`);
1151
+ console.error(` Report the criteria (npx intentdocs check / report_criteria) or re-run interactively to force it.`);
1144
1152
  process.exit(1);
1145
1153
  }
1146
1154
  return;
@@ -1161,6 +1169,11 @@ async function done(idPrefix, opts = {}) {
1161
1169
  if (summary) update.implementation_summary = summary;
1162
1170
  if (files.length > 0) update.implemented_files = files;
1163
1171
  if (screenshotUrl) update.screenshot_url = screenshotUrl;
1172
+ if (prUrl) {
1173
+ update.pr_url = prUrl;
1174
+ const prNumber = prNumberFromUrl(prUrl);
1175
+ if (prNumber) update.pr_number = prNumber;
1176
+ }
1164
1177
  const result = await updateStories(config, [update]);
1165
1178
  const verify = await listStories(config);
1166
1179
  const persisted = verify.stories.find((s) => s.id === match.id);
@@ -1168,7 +1181,7 @@ async function done(idPrefix, opts = {}) {
1168
1181
  console.error(
1169
1182
  ` \u2717 Done-mark did not persist: server reports status="${persisted?.status ?? "not found"}" for "${match.title}" after the update.`
1170
1183
  );
1171
- console.error(` Not treating this run as successful \u2014 re-run \`intent done ${idPrefix}\` or check the sync API.`);
1184
+ console.error(` Not treating this run as successful \u2014 re-run \`npx intentdocs done ${idPrefix}\` or check the sync API.`);
1172
1185
  await shutdownCliAnalytics();
1173
1186
  closePrompt();
1174
1187
  process.exit(1);
@@ -1190,6 +1203,11 @@ async function done(idPrefix, opts = {}) {
1190
1203
  if (summary) console.log(` \u21B3 Implementation summary posted to story map`);
1191
1204
  if (files.length > 0) console.log(` \u21B3 ${files.length} file(s) recorded`);
1192
1205
  if (screenshotUrl) console.log(` \u21B3 Screenshot attached`);
1206
+ if (prUrl) {
1207
+ console.log(` \u21B3 PR linked: ${prUrl}`);
1208
+ const stamp = result.pr_stamps?.find((p) => p.id === match.id);
1209
+ if (stamp?.message) console.log(` ${stamp.message.replace(/\n/g, "\n ")}`);
1210
+ }
1193
1211
  for (const release of result.readyReleases ?? []) {
1194
1212
  console.log();
1195
1213
  console.log(`\u{1F3C1} ${release.label} is ready to close (${release.storyCount} stories done) \u2014 close it on Validate \u203A Release`);
@@ -1247,13 +1265,12 @@ async function done(idPrefix, opts = {}) {
1247
1265
  async function stop(storyId) {
1248
1266
  const config = requireConfig();
1249
1267
  const data = await listStories(config);
1250
- const match = data.stories.find(
1251
- (st) => st.id === storyId || st.id.startsWith(storyId)
1252
- );
1268
+ const match = data.stories.find((st) => matchesStoryRef(st, storyId));
1253
1269
  if (!match) {
1254
1270
  console.log(`
1255
- \u274C No story found matching ID: ${storyId}
1271
+ \u274C No story found matching "${storyId}"
1256
1272
  `);
1273
+ process.exitCode = 1;
1257
1274
  return;
1258
1275
  }
1259
1276
  if (match.status !== "in_progress") {
@@ -1320,7 +1337,7 @@ function resolveStory(stories2, idPrefix) {
1320
1337
  const inProgress = stories2.filter((s) => s.status === "in_progress");
1321
1338
  if (inProgress.length === 1) return { story: inProgress[0] };
1322
1339
  return {
1323
- error: inProgress.length === 0 ? "No story in progress. Specify one: intent check <idPrefix>" : `${inProgress.length} stories in progress \u2014 specify one: intent check <idPrefix>`
1340
+ error: inProgress.length === 0 ? "No story in progress. Specify one: npx intentdocs check <idPrefix>" : `${inProgress.length} stories in progress \u2014 specify one: npx intentdocs check <idPrefix>`
1324
1341
  };
1325
1342
  }
1326
1343
  async function check(idPrefix, opts = {}) {
@@ -1466,7 +1483,7 @@ async function start() {
1466
1483
  } catch (err) {
1467
1484
  spinner.fail(" Failed to connect");
1468
1485
  console.error(` ${err.message}`);
1469
- console.error(" Make sure you're logged in: intentdocs login");
1486
+ console.error(" Make sure you're logged in: npx intentdocs login");
1470
1487
  closePrompt();
1471
1488
  return;
1472
1489
  }
@@ -1538,14 +1555,14 @@ async function start() {
1538
1555
  Failed to create project: ${data.error ?? "unknown error"}`);
1539
1556
  }
1540
1557
  } else {
1541
- console.error("\n Failed to create project. Try logging in again: intentdocs login");
1558
+ console.error("\n Failed to create project. Try logging in again: npx intentdocs login");
1542
1559
  }
1543
1560
  closePrompt();
1544
1561
  return;
1545
1562
  }
1546
1563
  const createContentType = createRes.headers.get("content-type") ?? "";
1547
1564
  if (!createContentType.includes("application/json")) {
1548
- console.error("\n Unexpected response from server. Try logging in again: intentdocs login");
1565
+ console.error("\n Unexpected response from server. Try logging in again: npx intentdocs login");
1549
1566
  closePrompt();
1550
1567
  return;
1551
1568
  }
@@ -1609,7 +1626,7 @@ async function start() {
1609
1626
  if (!readyToScope) {
1610
1627
  console.log(`
1611
1628
  No worries! When you're ready:
1612
- \u2022 Run: intent scope ${projectId}
1629
+ \u2022 Run: npx intentdocs scope ${projectId}
1613
1630
  \u2022 Or visit: ${API_BASE}
1614
1631
 
1615
1632
  Your project is saved and waiting for you.
@@ -1617,17 +1634,28 @@ async function start() {
1617
1634
  closePrompt();
1618
1635
  return;
1619
1636
  }
1620
- const { scope: scope2 } = await import("./scope-S3LZZ3KB.js");
1637
+ const { scope: scope2 } = await import("./scope-RRGJHS2G.js");
1621
1638
  await scope2(projectId);
1622
1639
  }
1623
1640
 
1624
1641
  // src/commands/propose.ts
1625
- import { mkdirSync, writeFileSync as writeFileSync3, readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
1626
- import { join as join4 } from "path";
1642
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync4, readFileSync as readFileSync5, existsSync as existsSync5, appendFileSync as appendFileSync2 } from "fs";
1643
+ import { join as join5 } from "path";
1644
+ function ensureChangesIgnored(cwd = process.cwd()) {
1645
+ const gitignorePath = join5(cwd, ".gitignore");
1646
+ if (!existsSync5(gitignorePath)) return;
1647
+ const covered = readFileSync5(gitignorePath, "utf-8").split(/\r?\n/).some((line) => /^\/?intent-changes\/?(\*\*?)?$/.test(line.trim()));
1648
+ if (covered) return;
1649
+ const gi = readFileSync5(gitignorePath, "utf-8");
1650
+ appendFileSync2(gitignorePath, `${gi.endsWith("\n") || gi === "" ? "" : "\n"}
1651
+ # intentdocs local change proposals
1652
+ intent-changes/
1653
+ `);
1654
+ }
1627
1655
  async function propose(changeName, opts = {}) {
1628
1656
  await requireValidAuth();
1629
1657
  if (!hasConfig()) {
1630
- console.log(" Not connected to a project. Run `intentdocs init` first.");
1658
+ console.log(" Not connected to a project. Run `npx intentdocs init` first.");
1631
1659
  closePrompt();
1632
1660
  return;
1633
1661
  }
@@ -1716,8 +1744,8 @@ async function propose(changeName, opts = {}) {
1716
1744
  \u2717 Failed to create stories: ${err instanceof Error ? err.message : String(err)}`);
1717
1745
  console.log(" Saving proposal locally anyway...");
1718
1746
  }
1719
- const changesDir = join4(process.cwd(), "intent-changes", slug);
1720
- if (!existsSync4(changesDir)) mkdirSync(changesDir, { recursive: true });
1747
+ const changesDir = join5(process.cwd(), "intent-changes", slug);
1748
+ if (!existsSync5(changesDir)) mkdirSync2(changesDir, { recursive: true });
1721
1749
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1722
1750
  const proposalMd = [
1723
1751
  `# Proposal: ${changeName}`,
@@ -1752,16 +1780,9 @@ async function propose(changeName, opts = {}) {
1752
1780
  `_Add notes here as you build._`,
1753
1781
  ``
1754
1782
  ].join("\n");
1755
- writeFileSync3(join4(changesDir, "proposal.md"), proposalMd);
1783
+ writeFileSync4(join5(changesDir, "proposal.md"), proposalMd);
1756
1784
  console.log(` \u2713 Saved intent-changes/${slug}/proposal.md`);
1757
- const gitignorePath = join4(process.cwd(), ".gitignore");
1758
- if (existsSync4(gitignorePath)) {
1759
- const { readFileSync: readFileSync5, appendFileSync: appendFileSync2 } = await import("fs");
1760
- const gi = readFileSync5(gitignorePath, "utf-8");
1761
- if (!gi.includes("intent-changes")) {
1762
- appendFileSync2(gitignorePath, "\n# intentdocs local change proposals\nstc-changes/\n");
1763
- }
1764
- }
1785
+ ensureChangesIgnored();
1765
1786
  console.log(`
1766
1787
  \u2705 Proposal "${changeName}" ready!
1767
1788
 
@@ -1769,14 +1790,14 @@ async function propose(changeName, opts = {}) {
1769
1790
  Local spec saved at: intent-changes/${slug}/proposal.md
1770
1791
 
1771
1792
  Next:
1772
- intent next \u2014 pick up the first story
1773
- intent status \u2014 see full progress
1774
- intent sync \u2014 refresh INTENT.md
1793
+ npx intentdocs next \u2014 pick up the first story
1794
+ npx intentdocs status \u2014 see full progress
1795
+ npx intentdocs sync \u2014 refresh INTENT.md
1775
1796
  `);
1776
1797
  closePrompt();
1777
1798
  }
1778
1799
  function parseProposalFile(filePath) {
1779
- const raw = readFileSync4(filePath, "utf-8");
1800
+ const raw = readFileSync5(filePath, "utf-8");
1780
1801
  const lines = raw.split("\n");
1781
1802
  let changeName = "";
1782
1803
  let problem = "";
@@ -1850,7 +1871,7 @@ function parseProposalFile(filePath) {
1850
1871
  return { changeName, problem, solution, activity, priority, stories: stories2 };
1851
1872
  }
1852
1873
  async function proposeFromFile(filePath, config, nameOverride) {
1853
- if (!existsSync4(filePath)) {
1874
+ if (!existsSync5(filePath)) {
1854
1875
  console.error(`
1855
1876
  File not found: ${filePath}
1856
1877
  `);
@@ -1886,8 +1907,8 @@ async function proposeFromFile(filePath, config, nameOverride) {
1886
1907
  \u2717 Failed to create stories: ${err instanceof Error ? err.message : String(err)}`);
1887
1908
  console.log(" Saving proposal locally anyway...");
1888
1909
  }
1889
- const changesDir = join4(process.cwd(), "intent-changes", slug);
1890
- if (!existsSync4(changesDir)) mkdirSync(changesDir, { recursive: true });
1910
+ const changesDir = join5(process.cwd(), "intent-changes", slug);
1911
+ if (!existsSync5(changesDir)) mkdirSync2(changesDir, { recursive: true });
1891
1912
  const proposalMd = [
1892
1913
  `# Proposal: ${changeName}`,
1893
1914
  ``,
@@ -1918,10 +1939,11 @@ async function proposeFromFile(filePath, config, nameOverride) {
1918
1939
  ].join("\n");
1919
1940
  })
1920
1941
  ].join("\n");
1921
- writeFileSync3(join4(changesDir, "proposal.md"), proposalMd);
1942
+ writeFileSync4(join5(changesDir, "proposal.md"), proposalMd);
1922
1943
  console.log(` \u2713 Saved intent-changes/${slug}/proposal.md`);
1944
+ ensureChangesIgnored();
1923
1945
  console.log(`
1924
- \u2705 Done! Run \`intentdocs next\` to start building.
1946
+ \u2705 Done! Run \`npx intentdocs next\` to start building.
1925
1947
  `);
1926
1948
  }
1927
1949
 
@@ -1938,7 +1960,7 @@ async function show(idPrefix) {
1938
1960
  if (!story) {
1939
1961
  console.error(`
1940
1962
  No story found matching "${idPrefix}"
1941
- Run \`intentdocs stories\` to list IDs.
1963
+ Run \`npx intentdocs stories\` to list IDs.
1942
1964
  `);
1943
1965
  process.exit(1);
1944
1966
  }
@@ -1992,7 +2014,7 @@ async function show(idPrefix) {
1992
2014
  const relPath = contextPath.replace(process.cwd() + "/", "");
1993
2015
  console.log(`
1994
2016
  Context : ${relPath}`);
1995
- console.log(` Mark done: intent done ${storyRef(story)}
2017
+ console.log(` Mark done: npx intentdocs done ${storyRef(story)}
1996
2018
  `);
1997
2019
  }
1998
2020
 
@@ -2041,7 +2063,7 @@ async function example() {
2041
2063
  console.log(`
2042
2064
  \u2713 Example saved: "${data.title}"
2043
2065
  `);
2044
- console.log(" Run `intentdocs sync --context` to regenerate your CLAUDE.md with this pattern.\n");
2066
+ console.log(" Run `npx intentdocs sync --context` to regenerate your CLAUDE.md with this pattern.\n");
2045
2067
  } catch {
2046
2068
  rl.close();
2047
2069
  throw new Error("Aborted");
@@ -2291,7 +2313,7 @@ var TOOLS = [
2291
2313
  },
2292
2314
  {
2293
2315
  name: "add_note",
2294
- description: "Add implementation notes to a story. Use this to record how something was built, gotchas, or context for future developers.",
2316
+ description: "Add an implementation note to a story \u2014 appended to its existing notes, never replacing them. Use this to record how something was built, gotchas, or context for future developers.",
2295
2317
  inputSchema: {
2296
2318
  type: "object",
2297
2319
  properties: {
@@ -2467,16 +2489,6 @@ async function handleToolCall(name, args, config) {
2467
2489
  const data = await listStories(config);
2468
2490
  const match = data.stories.find((s) => matchesStoryRef(s, storyId));
2469
2491
  if (!match) return { content: [{ type: "text", text: `No story found matching "${storyId}"` }] };
2470
- if (match.build_sequence == null) {
2471
- return {
2472
- content: [{
2473
- type: "text",
2474
- text: `\u{1F512} "${match.title}" is unsequenced \u2014 it hasn't been ordered on the Plan view yet.
2475
-
2476
- Agents only build stories that have been deliberately sequenced. Sequence it on the Plan view (drag it into the plan or set its build order), then retry.`
2477
- }]
2478
- };
2479
- }
2480
2492
  const result = await updateStories(config, [{ id: match.id, status: "in_progress" }]);
2481
2493
  if (result.missing?.includes(match.id)) {
2482
2494
  return {
@@ -2486,8 +2498,11 @@ Agents only build stories that have been deliberately sequenced. Sequence it on
2486
2498
  }]
2487
2499
  };
2488
2500
  }
2501
+ const placed = result.sequenced?.find((x) => x.id === match.id);
2502
+ const note = placed ? `
2503
+ It wasn't in the build order, so it's been placed at the end of the build order.` : "";
2489
2504
  return {
2490
- content: [{ type: "text", text: `\u2713 ${match.title} \u2192 in_progress` }]
2505
+ content: [{ type: "text", text: `\u2713 ${match.title} \u2192 in_progress${note}` }]
2491
2506
  };
2492
2507
  }
2493
2508
  case "mark_done": {
@@ -2687,7 +2702,7 @@ Decision needed: ${decisionNeeded}`
2687
2702
  await apiFetch(config, "/stories", {
2688
2703
  method: "PATCH",
2689
2704
  headers: { "Content-Type": "application/json" },
2690
- body: JSON.stringify({ stories: [{ id: match.id, notes: note }] })
2705
+ body: JSON.stringify({ stories: [{ id: match.id, notes: appendToNotes(match.notes, note) }] })
2691
2706
  });
2692
2707
  return { content: [{ type: "text", text: `\u2713 Note added to: ${match.title}` }] };
2693
2708
  }
@@ -2919,7 +2934,7 @@ async function startMcpServer(mode = "all") {
2919
2934
  import { execSync as execSync3 } from "child_process";
2920
2935
  async function openclawRegister() {
2921
2936
  if (!hasConfig()) {
2922
- console.error("Not initialised. Run: intent init");
2937
+ console.error("Not initialised. Run: npx intentdocs init");
2923
2938
  process.exit(1);
2924
2939
  }
2925
2940
  const config = readConfig();
@@ -2961,15 +2976,15 @@ var program = new Command();
2961
2976
  program.name("intentdocs").description("Write specs, map stories, ship with AI \u2014 from your terminal").version(version);
2962
2977
  program.command("login").description("Log in to intentdocs (opens browser)").action(() => void login());
2963
2978
  program.command("logout").description("Log out of intentdocs").action(() => void logout());
2964
- var keys = program.command("keys").description("Manage your Anthropic API key \u2014 unlock AI features on any plan");
2965
- keys.command("set [key]").description("Save your Anthropic API key (prompts if omitted)").action((key) => {
2966
- import("./keys-5PA3D6CX.js").then((m) => void m.keysSet(key));
2979
+ var keys = program.command("keys").description("Manage your Anthropic (sk-ant-), xAI (xai-), or OpenAI (sk- / sk-proj-) API key \u2014 unlock AI features on any plan");
2980
+ keys.command("set [key]").description("Save an Anthropic (sk-ant-), xAI (xai-), or OpenAI (sk- / sk-proj-) API key (prompts if omitted)").action((key) => {
2981
+ import("./keys-2W5V56FS.js").then((m) => void m.keysSet(key));
2967
2982
  });
2968
2983
  keys.command("status").description("Show whether a key is set (masked)").action(() => {
2969
- import("./keys-5PA3D6CX.js").then((m) => void m.keysStatus());
2984
+ import("./keys-2W5V56FS.js").then((m) => void m.keysStatus());
2970
2985
  });
2971
2986
  keys.command("remove").description("Remove your stored key").action(() => {
2972
- import("./keys-5PA3D6CX.js").then((m) => void m.keysRemove());
2987
+ import("./keys-2W5V56FS.js").then((m) => void m.keysRemove());
2973
2988
  });
2974
2989
  program.command("start").description("Start a new project \u2014 guided flow from problem to pitch").action(() => void start());
2975
2990
  program.command("scope [projectId]").description("Scope a project \u2014 generate blueprint, story map, and mockup").action((id) => void scope(id));
@@ -3001,7 +3016,10 @@ program.command("templates [name]").description("Browse story map templates").ac
3001
3016
  }
3002
3017
  });
3003
3018
  program.command("projects").description("List the projects your token can reach \u2014 the whole workspace it is bound to").action(() => {
3004
- import("./projects-F7GMH66U.js").then((m) => void m.projects());
3019
+ import("./projects-AYAYVFKL.js").then((m) => void m.projects());
3020
+ });
3021
+ program.command("push").description("Upload this repo's nikOS intent/ folder: log in, create the project, and link the repo").action(() => {
3022
+ import("./push-33V7ANJW.js").then((m) => m.push());
3005
3023
  });
3006
3024
  program.command("init [token]").description("Connect this project to a intentdocs story map").action((token) => void init(token));
3007
3025
  program.command("sync").description("Refresh INTENT.md from the live story map").option("--context", "Refresh only the CLAUDE.md/AGENTS.md context file (not stories)").action((opts) => void sync(opts));
@@ -3009,46 +3027,46 @@ program.command("ci").description("Set up CI test reporting \u2014 create a key,
3009
3027
  program.command("status").description("Show story map progress").action(() => void status());
3010
3028
  program.command("stories").description("List all stories").option("-f, --filter <filter>", "Filter by status (todo/in-progress/done) or search term").option("-s, --status <status>", "Filter by exact status: todo, in-progress, wip, done").option("-k, --kanban", "View stories as a kanban board (todo | in-progress | done)").action((opts) => void stories(opts));
3011
3029
  program.command("next").description("Show the next highest-priority story to work on").option("-o, --open", "Open the spec page in your browser").option("--skip-wip", "Show next todo even if stories are in progress").option("--story-id <id>", "Show a specific story by ID").action((opts) => void next(opts));
3012
- program.command("implement <id>").description("Pick up a story and mark it in-progress").option("--stub", "Suggest verification commands for acceptance criteria (requires ANTHROPIC_API_KEY)").action((id, opts) => void implement(id, opts));
3030
+ program.command("implement <id>").description("Pick up a story and mark it in-progress (accepts its key or an ID prefix)").option("--stub", "Suggest verification commands for acceptance criteria (uses your saved key or plan; ANTHROPIC_API_KEY as a fallback)").action((id, opts) => void implement(id, opts));
3013
3031
  program.command("check [idPrefix]").description("Run executable acceptance criteria locally and print pass/fail (defaults to the in-progress story)").option("--report", "Report results back to the story map (reported_by: human)").action((idPrefix, opts) => void check(idPrefix, opts));
3014
3032
  program.command("show <id>").description("Show the full spec for a story (accepts ID prefix)").action((id) => void show(id));
3015
- program.command("done <id>").description("Mark a story as done (accepts ID prefix)").option("--screenshot <path>", "Attach a screenshot of the product from an image file").action((id, opts) => void done(id, opts));
3016
- program.command("stop <id>").description("Stop development of an in-progress story (resets to todo, signals agents to stop)").action((id) => void stop(id));
3033
+ program.command("done <id>").description("Mark a story as done (accepts its key or an ID prefix)").option("--screenshot <path>", "Attach a screenshot of the product from an image file").option("--pr <url>", "Link the story's pull request (stamped with the intentdocs footer when GitHub is connected)").action((id, opts) => void done(id, opts));
3034
+ program.command("stop <id>").description("Stop development of an in-progress story (resets to todo, signals agents to stop; accepts its key or an ID prefix)").action((id) => void stop(id));
3017
3035
  program.command("add <title>").description("Create a new story").option("-a, --activity <activity>", "Activity to add the story under").option("-p, --priority <priority>", "Priority: must, should, could", "should").option("-e, --effort <effort>", "Effort: S, M, L, XL", "M").action((title, opts) => void add(title, opts));
3018
- var decide = program.command("decide [title]").description("Log an architectural or product decision").option("-d, --description <desc>", "Decision description").option("-c, --category <cat>", "Category: architecture, product, technical, trade-off", "technical").option("-s, --story <id>", "Link to a story ID").action((title, opts) => {
3019
- import("./log-EVLWM64C.js").then((m) => void m.logDecision(title, opts));
3036
+ var decide = program.command("decide [title]").description("Log an architectural or product decision").option("-d, --description <desc>", "Decision description").option("-c, --category <cat>", "Category: architecture, product, technical, trade-off", "technical").option("-s, --story <id>", "Link to a story (its key like PB-42, or an ID prefix)").action((title, opts) => {
3037
+ import("./log-DFCCAAJN.js").then((m) => void m.logDecision(title, opts));
3020
3038
  });
3021
3039
  decide.command("list").description("List logged decisions (id, category, title)").action(() => {
3022
- import("./log-EVLWM64C.js").then((m) => void m.listLoggedDecisions());
3040
+ import("./log-DFCCAAJN.js").then((m) => void m.listLoggedDecisions());
3023
3041
  });
3024
3042
  decide.command("amend <id>").description("Amend a logged decision's title, description, or category (accepts an ID prefix)").option("-t, --title <title>", "New title").option("-d, --description <desc>", "New description").option("-c, --category <cat>", "New category: architecture, product, technical, trade-off").action((id, opts) => {
3025
- import("./log-EVLWM64C.js").then((m) => void m.amendDecision(id, opts));
3043
+ import("./log-DFCCAAJN.js").then((m) => void m.amendDecision(id, opts));
3026
3044
  });
3027
3045
  decide.command("delete <id>").alias("retract").description("Retract a logged decision \u2014 soft-deleted and audited, not removed (accepts an ID prefix)").option("-r, --reason <reason>", "Why this decision is being retracted").option("-y, --yes", "Skip the confirmation prompt (required when there's no TTY)").action((id, opts) => {
3028
- import("./log-EVLWM64C.js").then((m) => void m.deleteDecision(id, opts));
3046
+ import("./log-DFCCAAJN.js").then((m) => void m.deleteDecision(id, opts));
3029
3047
  });
3030
- program.command("note <storyId> [note]").description("Add implementation notes to a story").action((storyId, note) => {
3031
- import("./log-EVLWM64C.js").then((m) => void m.logNote(storyId, note));
3048
+ program.command("note <storyId> [note]").description("Add an implementation note to a story (appended to its notes)").action((storyId, note) => {
3049
+ import("./log-DFCCAAJN.js").then((m) => void m.logNote(storyId, note));
3032
3050
  });
3033
3051
  program.command("example").description("Add a few-shot reference pattern (before/after code) to the project").action(() => void example());
3034
3052
  program.command("complexity [storyId]").description("Analyze story complexity with AI \u2014 scores, risks, effort estimates").action((id) => {
3035
- import("./complexity-ZOB3BPVD.js").then((m) => void m.complexity(id));
3053
+ import("./complexity-OJVM37NF.js").then((m) => void m.complexity(id));
3036
3054
  });
3037
3055
  program.command("research [query]").description("Research a topic with AI using your project context").action((query) => {
3038
- import("./research-4ASCZSZN.js").then((m) => void m.research(query));
3056
+ import("./research-SHO5SXVQ.js").then((m) => void m.research(query));
3039
3057
  });
3040
- program.command("import [file]").description("Import a PRD or spec into your story map as stories").action((file) => {
3041
- import("./import-prd-MJUCIMHX.js").then((m) => void m.importPrd(file));
3058
+ program.command("import [file]").description("Import a PRD or spec into your story map as stories (reads standard input when no file is given; needs --yes then)").option("-y, --yes", "Import without asking for confirmation (required when reading from standard input)").action((file, opts) => {
3059
+ import("./import-prd-5FCVXCYO.js").then((m) => void m.importPrd(file, opts));
3042
3060
  });
3043
3061
  var map = program.command("map").description("Import or extract story maps from codebases");
3044
3062
  map.command("extract").description("Output a prompt for your coding agent to extract a story map from an existing codebase").action(() => {
3045
- import("./map-extract-W6WJLVOF.js").then((m) => void m.mapExtract());
3063
+ import("./map-extract-YUCFLGJS.js").then((m) => void m.mapExtract());
3046
3064
  });
3047
3065
  map.command("import <file>").description("Import a storymap.json file generated by your coding agent").option("--yes", "Skip confirmation prompt").action((file, opts) => {
3048
- import("./map-import-LL4AIAQE.js").then((m) => void m.mapImport(file, opts));
3066
+ import("./map-import-7RN2NEUZ.js").then((m) => void m.mapImport(file, opts));
3049
3067
  });
3050
3068
  map.command("schema").description("Print the JSON Schema for storymap.json").action(() => {
3051
- import("./map-schema-6GL2UKZQ.js").then((m) => void m.mapSchema());
3069
+ import("./map-schema-5BEIJM2J.js").then((m) => void m.mapSchema());
3052
3070
  });
3053
3071
  program.command("mcp").description("Start MCP server for Claude Code / Cursor integration").option("-m, --mode <mode>", "Tool mode: core, standard, all", "all").action((opts) => void startMcpServer(opts.mode));
3054
3072
  program.command("openclaw-register").description("Register this project with OpenClaw MCP \u2014 gives spawned coding agents access to your story map").action(() => void openclawRegister());