intentdocs 0.10.1 → 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.
@@ -1,19 +1,19 @@
1
+ import {
2
+ writeStoryContext
3
+ } from "./chunk-BNEU6TLJ.js";
1
4
  import {
2
5
  captureCli,
3
6
  shutdownCliAnalytics
4
7
  } from "./chunk-KPQ42YE6.js";
5
- import {
6
- writeStoryContext
7
- } from "./chunk-F52HPVNQ.js";
8
8
  import {
9
9
  apiFetch,
10
10
  getContext,
11
11
  listStories
12
- } from "./chunk-34JCN66H.js";
12
+ } from "./chunk-KTGQO3AE.js";
13
13
  import {
14
14
  hasConfig,
15
15
  requireConfig
16
- } from "./chunk-JX2RLGOM.js";
16
+ } from "./chunk-K5SNF6CG.js";
17
17
  import {
18
18
  choose,
19
19
  closePrompt
@@ -23,9 +23,8 @@ import {
23
23
  } from "./chunk-XZE2XP6O.js";
24
24
 
25
25
  // src/commands/setup.ts
26
- import { existsSync as existsSync2, mkdirSync, writeFileSync as writeFileSync2, readFileSync, unlinkSync } from "fs";
27
- import { join as join2, dirname } from "path";
28
- import { homedir } from "os";
26
+ import { existsSync as existsSync3, mkdirSync, writeFileSync as writeFileSync3, readFileSync as readFileSync2, unlinkSync } from "fs";
27
+ import { join as join3 } from "path";
29
28
 
30
29
  // src/commands/sync.ts
31
30
  import { writeFileSync, readdirSync, existsSync, statSync } from "fs";
@@ -120,6 +119,61 @@ async function sync(opts) {
120
119
  await shutdownCliAnalytics();
121
120
  }
122
121
 
122
+ // src/lib/claude-mcp-config.ts
123
+ import { existsSync as existsSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
124
+ import { join as join2 } from "path";
125
+ import { homedir } from "os";
126
+ var LOCAL_SERVER_NAME = "intent";
127
+ var LOCAL_SERVER_ENTRY = { type: "stdio", command: "npx", args: ["intentdocs", "mcp"], env: {} };
128
+ var CLAUDE_MCP_ADD_COMMAND = `claude mcp add --scope project ${LOCAL_SERVER_NAME} -- npx intentdocs mcp`;
129
+ function sameEntry(a, b) {
130
+ return JSON.stringify(a) === JSON.stringify(b);
131
+ }
132
+ function registerLocalServerInMcpJson(cwd = process.cwd()) {
133
+ const path = join2(cwd, ".mcp.json");
134
+ let config = {};
135
+ if (existsSync2(path)) {
136
+ try {
137
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
138
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { status: "unreadable", path };
139
+ config = parsed;
140
+ } catch {
141
+ return { status: "unreadable", path };
142
+ }
143
+ }
144
+ const rawServers = config.mcpServers;
145
+ if (rawServers !== void 0 && (typeof rawServers !== "object" || rawServers === null || Array.isArray(rawServers))) {
146
+ return { status: "unreadable", path };
147
+ }
148
+ const servers = { ...rawServers ?? {} };
149
+ const before = servers[LOCAL_SERVER_NAME];
150
+ if (sameEntry(before, LOCAL_SERVER_ENTRY)) return { status: "unchanged", path };
151
+ servers[LOCAL_SERVER_NAME] = { ...LOCAL_SERVER_ENTRY, args: [...LOCAL_SERVER_ENTRY.args], env: {} };
152
+ writeFileSync2(path, JSON.stringify({ ...config, mcpServers: servers }, null, 2) + "\n");
153
+ return { status: before === void 0 ? "added" : "updated", path };
154
+ }
155
+ var LEGACY_SETTINGS_ENTRY = { command: "npx", args: ["intentdocs", "mcp"] };
156
+ function removeLegacySettingsEntry(home = homedir()) {
157
+ const path = join2(home, ".claude", "settings.json");
158
+ if (!existsSync2(path)) return false;
159
+ let settings;
160
+ try {
161
+ settings = JSON.parse(readFileSync(path, "utf-8"));
162
+ } catch {
163
+ return false;
164
+ }
165
+ if (!settings || typeof settings !== "object" || Array.isArray(settings)) return false;
166
+ const servers = settings.mcpServers;
167
+ if (!servers || typeof servers !== "object" || !sameEntry(servers[LOCAL_SERVER_NAME], LEGACY_SETTINGS_ENTRY)) return false;
168
+ const rest = { ...servers };
169
+ delete rest[LOCAL_SERVER_NAME];
170
+ const next = { ...settings };
171
+ if (Object.keys(rest).length > 0) next.mcpServers = rest;
172
+ else delete next.mcpServers;
173
+ writeFileSync2(path, JSON.stringify(next, null, 2) + "\n");
174
+ return true;
175
+ }
176
+
123
177
  // src/commands/setup.ts
124
178
  async function setup() {
125
179
  await requireValidAuth();
@@ -174,7 +228,7 @@ async function setup() {
174
228
  }
175
229
  function setupClaudeCode() {
176
230
  const cwd = process.cwd();
177
- const claudeMdPath = join2(cwd, "CLAUDE.md");
231
+ const claudeMdPath = join3(cwd, "CLAUDE.md");
178
232
  const claudeBlock = `
179
233
  # intentdocs Integration
180
234
 
@@ -194,29 +248,29 @@ story map, decisions, examples, tools, and guardrails.
194
248
  | \`npx intentdocs sync --context\` | Regenerate this file from intentdocs |
195
249
  | \`npx intentdocs status\` | Show recent activity |
196
250
  `;
197
- if (existsSync2(claudeMdPath)) {
198
- const existing = readFileSync(claudeMdPath, "utf-8");
251
+ if (existsSync3(claudeMdPath)) {
252
+ const existing = readFileSync2(claudeMdPath, "utf-8");
199
253
  if (!existing.includes("intentdocs Integration")) {
200
- writeFileSync2(claudeMdPath, existing + "\n" + claudeBlock);
254
+ writeFileSync3(claudeMdPath, existing + "\n" + claudeBlock);
201
255
  console.log(" \u2713 Updated CLAUDE.md with intentdocs agent workflow");
202
256
  } else {
203
257
  console.log(" \u2713 CLAUDE.md already configured");
204
258
  }
205
259
  } else {
206
- writeFileSync2(claudeMdPath, claudeBlock.trim() + "\n");
260
+ writeFileSync3(claudeMdPath, claudeBlock.trim() + "\n");
207
261
  console.log(" \u2713 Created CLAUDE.md with intentdocs agent workflow");
208
262
  }
209
- const commandsDir = join2(cwd, ".claude", "commands");
210
- if (!existsSync2(commandsDir)) mkdirSync(commandsDir, { recursive: true });
263
+ const commandsDir = join3(cwd, ".claude", "commands");
264
+ if (!existsSync3(commandsDir)) mkdirSync(commandsDir, { recursive: true });
211
265
  const oldPbCommands = ["pb-next", "pb-implement", "pb-review", "pb-document", "pb-test", "pb-status", "pb-done", "pb-sync", "pb-propose", "pb-context"];
212
266
  for (const name of oldPbCommands) {
213
- const oldPath = join2(commandsDir, name + ".md");
214
- if (existsSync2(oldPath)) {
267
+ const oldPath = join3(commandsDir, name + ".md");
268
+ if (existsSync3(oldPath)) {
215
269
  unlinkSync(oldPath);
216
270
  }
217
271
  }
218
- writeFileSync2(
219
- join2(commandsDir, "intent-next.md"),
272
+ writeFileSync3(
273
+ join3(commandsDir, "intent-next.md"),
220
274
  `# Pick up the next story
221
275
 
222
276
  1. Run \`npx intentdocs next\` to get the highest-priority story
@@ -227,8 +281,8 @@ story map, decisions, examples, tools, and guardrails.
227
281
  6. Ask for confirmation before proceeding
228
282
  `
229
283
  );
230
- writeFileSync2(
231
- join2(commandsDir, "intent-implement.md"),
284
+ writeFileSync3(
285
+ join3(commandsDir, "intent-implement.md"),
232
286
  `# Implement a story
233
287
 
234
288
  ## Context to load first
@@ -275,8 +329,8 @@ story map, decisions, examples, tools, and guardrails.
275
329
  - Never mark a story done with a failing or unreported \`must\` criterion.
276
330
  `
277
331
  );
278
- writeFileSync2(
279
- join2(commandsDir, "intent-review.md"),
332
+ writeFileSync3(
333
+ join3(commandsDir, "intent-review.md"),
280
334
  `# Review against acceptance criteria
281
335
 
282
336
  1. Identify the story being worked on
@@ -287,8 +341,8 @@ story map, decisions, examples, tools, and guardrails.
287
341
  4. Summarise: what's done, what needs work, any concerns
288
342
  `
289
343
  );
290
- writeFileSync2(
291
- join2(commandsDir, "intent-document.md"),
344
+ writeFileSync3(
345
+ join3(commandsDir, "intent-document.md"),
292
346
  `# Document a feature
293
347
 
294
348
  1. Check which story was just completed
@@ -306,8 +360,8 @@ story map, decisions, examples, tools, and guardrails.
306
360
  7. Update CONVENTIONS.md if new patterns were introduced
307
361
  `
308
362
  );
309
- writeFileSync2(
310
- join2(commandsDir, "intent-test.md"),
363
+ writeFileSync3(
364
+ join3(commandsDir, "intent-test.md"),
311
365
  `# Write tests for a story
312
366
 
313
367
  1. Get the story details: \`npx intentdocs next\` or provided story ID
@@ -317,12 +371,12 @@ story map, decisions, examples, tools, and guardrails.
317
371
  5. Run the tests to verify they pass
318
372
  `
319
373
  );
320
- writeFileSync2(
321
- join2(commandsDir, "intent-status.md"),
374
+ writeFileSync3(
375
+ join3(commandsDir, "intent-status.md"),
322
376
  `Run \`npx intentdocs status\` and \`npx intentdocs stories --filter todo\` to show current progress and remaining backlog. Summarise what's done and what's left.`
323
377
  );
324
- writeFileSync2(
325
- join2(commandsDir, "intent-done.md"),
378
+ writeFileSync3(
379
+ join3(commandsDir, "intent-done.md"),
326
380
  `Ask which story was just completed, then run \`npx intentdocs done <id>\`.
327
381
  \`npx intentdocs done\` prints the acceptance-criteria table first. If any \`must\` criterion is still failing or was
328
382
  never reported via \`report_criteria\`, it will list them and ask "Mark done anyway?" \u2014 only confirm that
@@ -331,8 +385,8 @@ The CLI will prompt for an optional decision or lesson learned \u2014 encourage
331
385
  Also ask if there are implementation notes to add: \`npx intentdocs note <id> "..."\`
332
386
  Both decisions and notes flow back to the project page on intentdocs.com.`
333
387
  );
334
- writeFileSync2(
335
- join2(commandsDir, "intent-sync.md"),
388
+ writeFileSync3(
389
+ join3(commandsDir, "intent-sync.md"),
336
390
  `# Refresh context from intentdocs
337
391
 
338
392
  ## Refresh story map summary
@@ -349,8 +403,8 @@ Run \`npx intentdocs sync --context\` \u2014 rebuilds CLAUDE.md from your projec
349
403
 
350
404
  Run \`npx intentdocs sync --context\` after updating any section on intentdocs.com.`
351
405
  );
352
- writeFileSync2(
353
- join2(commandsDir, "intent-propose.md"),
406
+ writeFileSync3(
407
+ join3(commandsDir, "intent-propose.md"),
354
408
  `# Propose a new change
355
409
 
356
410
  Use this when you want to plan a new feature, improvement, or fix before writing code.
@@ -374,8 +428,8 @@ stories there AND saves a local spec for the AI to reference during implementati
374
428
  - If the change is already defined in the UI, just run \`npx intentdocs sync\` instead
375
429
  `
376
430
  );
377
- writeFileSync2(
378
- join2(commandsDir, "intent-context.md"),
431
+ writeFileSync3(
432
+ join3(commandsDir, "intent-context.md"),
379
433
  `# Load current story context
380
434
 
381
435
  Use this to focus on a single story without loading the full story map.
@@ -398,8 +452,8 @@ Use this to focus on a single story without loading the full story map.
398
452
  The file is at \`.intent/stories/<full-story-id>.md\` \u2014 read it directly.
399
453
  `
400
454
  );
401
- writeFileSync2(
402
- join2(commandsDir, "intent-decide.md"),
455
+ writeFileSync3(
456
+ join3(commandsDir, "intent-decide.md"),
403
457
  `# Log an architectural or product decision
404
458
 
405
459
  Use this when you've made a decision that future agents (or teammates) should know about.
@@ -417,8 +471,8 @@ Good decisions to log:
417
471
  - "Decided not to do Z because..."
418
472
  - "This pattern is used throughout because..."`
419
473
  );
420
- writeFileSync2(
421
- join2(commandsDir, "intent-example.md"),
474
+ writeFileSync3(
475
+ join3(commandsDir, "intent-example.md"),
422
476
  `# Log a reference pattern for this codebase
423
477
 
424
478
  Use this when you've written code that represents the right way to do something in this project.
@@ -437,24 +491,22 @@ and are included in CLAUDE.md as Reference Patterns the next time you run \`npx
437
491
  registerMcpWithClaudeCode();
438
492
  }
439
493
  function registerMcpWithClaudeCode() {
440
- const settingsPath = join2(homedir(), ".claude", "settings.json");
441
- let settings = {};
442
- if (existsSync2(settingsPath)) {
443
- try {
444
- settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
445
- } catch {
446
- }
494
+ const result = registerLocalServerInMcpJson(process.cwd());
495
+ if (result.status === "unreadable") {
496
+ console.log(` \u26A0 Couldn't read ${result.path}, so it was left as it is. Add the IntentDocs server yourself:`);
497
+ console.log(` ${CLAUDE_MCP_ADD_COMMAND}`);
498
+ } else if (result.status === "unchanged") {
499
+ console.log(` \u2713 MCP server "${LOCAL_SERVER_NAME}" already registered in .mcp.json`);
500
+ } else {
501
+ console.log(` \u2713 Registered MCP server "${LOCAL_SERVER_NAME}" in .mcp.json \u2014 restart Claude Code and approve it when asked`);
502
+ }
503
+ if (removeLegacySettingsEntry()) {
504
+ console.log(" \u2713 Removed the old entry from ~/.claude/settings.json, where Claude Code doesn't read MCP servers");
447
505
  }
448
- const servers = settings.mcpServers ?? {};
449
- servers.intent = { command: "npx", args: ["intentdocs", "mcp"] };
450
- settings.mcpServers = servers;
451
- mkdirSync(dirname(settingsPath), { recursive: true });
452
- writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
453
- console.log(" \u2713 Registered MCP server in ~/.claude/settings.json \u2014 restart Claude Code to activate");
454
506
  }
455
507
  function setupCursor() {
456
508
  const cwd = process.cwd();
457
- const cursorRulesPath = join2(cwd, ".cursorrules");
509
+ const cursorRulesPath = join3(cwd, ".cursorrules");
458
510
  const cursorBlock = `
459
511
  # intentdocs Integration
460
512
 
@@ -463,6 +515,11 @@ function setupCursor() {
463
515
  2. **ARCHITECTURE.md** \u2014 technical decisions and system structure
464
516
  3. **CONVENTIONS.md** \u2014 coding patterns, naming, file structure
465
517
 
518
+ ## First run
519
+ - No story started yet? The founder's next step is their first story:
520
+ claim it with \`npx intentdocs next\` (or \`start_story\` over MCP), build it,
521
+ report each criterion, then mark it done.
522
+
466
523
  ## Workflow
467
524
  - Next story: \`npx intentdocs next\`
468
525
  - Mark done: \`npx intentdocs done <id>\`
@@ -489,14 +546,14 @@ function setupCursor() {
489
546
  - Log decisions and trade-offs back to intentdocs
490
547
  - Follow patterns in CONVENTIONS.md
491
548
  `;
492
- if (existsSync2(cursorRulesPath)) {
493
- const existing = readFileSync(cursorRulesPath, "utf-8");
549
+ if (existsSync3(cursorRulesPath)) {
550
+ const existing = readFileSync2(cursorRulesPath, "utf-8");
494
551
  if (!existing.includes("intentdocs Integration")) {
495
- writeFileSync2(cursorRulesPath, existing + "\n" + cursorBlock);
552
+ writeFileSync3(cursorRulesPath, existing + "\n" + cursorBlock);
496
553
  console.log(" \u2713 Updated .cursorrules");
497
554
  }
498
555
  } else {
499
- writeFileSync2(cursorRulesPath, cursorBlock.trim() + "\n");
556
+ writeFileSync3(cursorRulesPath, cursorBlock.trim() + "\n");
500
557
  console.log(" \u2713 Created .cursorrules");
501
558
  }
502
559
  console.log(`
@@ -515,9 +572,9 @@ function setupCursor() {
515
572
  }
516
573
  function generateAgentDocs() {
517
574
  const cwd = process.cwd();
518
- const archPath = join2(cwd, "ARCHITECTURE.md");
519
- if (!existsSync2(archPath)) {
520
- writeFileSync2(
575
+ const archPath = join3(cwd, "ARCHITECTURE.md");
576
+ if (!existsSync3(archPath)) {
577
+ writeFileSync3(
521
578
  archPath,
522
579
  `# Architecture
523
580
 
@@ -544,9 +601,9 @@ _Record trade-offs and why._
544
601
  );
545
602
  console.log(" \u2713 Created ARCHITECTURE.md scaffold");
546
603
  }
547
- const convPath = join2(cwd, "CONVENTIONS.md");
548
- if (!existsSync2(convPath)) {
549
- writeFileSync2(
604
+ const convPath = join3(cwd, "CONVENTIONS.md");
605
+ if (!existsSync3(convPath)) {
606
+ writeFileSync3(
550
607
  convPath,
551
608
  `# Conventions
552
609
 
@@ -5,6 +5,16 @@ import {
5
5
  // src/lib/config.ts
6
6
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
7
7
  import { join, dirname } from "path";
8
+ function checkoutConfig(input) {
9
+ return {
10
+ apiBase: input.apiBase,
11
+ mapTitle: input.map.title,
12
+ projectId: input.project.id,
13
+ mapId: input.map.id,
14
+ workspace: input.project.workspace ? { orgId: input.project.workspace.orgId, name: input.project.workspace.name } : { orgId: null, name: null },
15
+ ...input.appUrl && { appUrl: input.appUrl }
16
+ };
17
+ }
8
18
  var CONFIG_DIR = ".intent";
9
19
  var CONFIG_FILE = "config.json";
10
20
  function configPath() {
@@ -52,6 +62,7 @@ function requireConfig() {
52
62
  }
53
63
 
54
64
  export {
65
+ checkoutConfig,
55
66
  hasConfig,
56
67
  readConfig,
57
68
  writeConfig,
@@ -9,6 +9,14 @@ var AuthError = class extends Error {
9
9
  this.name = "AuthError";
10
10
  }
11
11
  };
12
+ var ApiError = class extends Error {
13
+ constructor(message, status, code = null) {
14
+ super(message);
15
+ this.status = status;
16
+ this.code = code;
17
+ this.name = "ApiError";
18
+ }
19
+ };
12
20
  function checkoutMismatch(config, binding) {
13
21
  const checkout = config.workspace;
14
22
  if (!checkout || !checkout.orgId) return null;
@@ -42,6 +50,17 @@ async function refusalMessage(res) {
42
50
  }
43
51
  return null;
44
52
  }
53
+ async function errorCode(res) {
54
+ try {
55
+ const body = await res.clone().json();
56
+ if (typeof body.error === "string") return body.error;
57
+ if (body.error && typeof body.error === "object" && typeof body.error.code === "string") {
58
+ return body.error.code;
59
+ }
60
+ } catch {
61
+ }
62
+ return null;
63
+ }
45
64
  async function apiFetch(config, path, init) {
46
65
  const auth = getAuth();
47
66
  const mismatch = checkoutMismatch(config, auth?.binding);
@@ -65,7 +84,10 @@ async function apiFetch(config, path, init) {
65
84
  said ?? "Access denied \u2014 you may not have permission for this project. Run `npx intentdocs login` to re-authenticate."
66
85
  );
67
86
  }
68
- if (!res.ok) throw new Error(`API error: ${res.status}`);
87
+ if (!res.ok) {
88
+ const said = await refusalMessage(res);
89
+ throw new ApiError(said ?? `API error: ${res.status}`, res.status, await errorCode(res));
90
+ }
69
91
  return res;
70
92
  }
71
93
  function apiUrl(config, path) {
@@ -154,6 +176,7 @@ async function retractDecision(config, decisionId, reason) {
154
176
  }
155
177
 
156
178
  export {
179
+ checkoutMismatch,
157
180
  syncHeaders,
158
181
  apiFetch,
159
182
  apiUrl,
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  ci
3
- } from "./chunk-6X4BORMW.js";
3
+ } from "./chunk-65EDM7QH.js";
4
4
  import "./chunk-KPQ42YE6.js";
5
- import "./chunk-JX2RLGOM.js";
5
+ import "./chunk-K5SNF6CG.js";
6
6
  import "./chunk-WRRUCCVZ.js";
7
7
  import "./chunk-XZE2XP6O.js";
8
8
  import "./chunk-UYTVTYNB.js";
@@ -9,10 +9,10 @@ import {
9
9
  apiUrl,
10
10
  listStories,
11
11
  syncHeaders
12
- } from "./chunk-34JCN66H.js";
12
+ } from "./chunk-KTGQO3AE.js";
13
13
  import {
14
14
  requireConfig
15
- } from "./chunk-JX2RLGOM.js";
15
+ } from "./chunk-K5SNF6CG.js";
16
16
  import "./chunk-XZE2XP6O.js";
17
17
  import "./chunk-UYTVTYNB.js";
18
18
 
@@ -5,10 +5,10 @@ import {
5
5
  import {
6
6
  apiUrl,
7
7
  syncHeaders
8
- } from "./chunk-34JCN66H.js";
8
+ } from "./chunk-KTGQO3AE.js";
9
9
  import {
10
10
  requireConfig
11
- } from "./chunk-JX2RLGOM.js";
11
+ } from "./chunk-K5SNF6CG.js";
12
12
  import {
13
13
  closePrompt,
14
14
  confirm
@@ -19,8 +19,15 @@ import "./chunk-UYTVTYNB.js";
19
19
  // src/commands/import-prd.ts
20
20
  import { readFileSync, existsSync } from "fs";
21
21
  import ora from "ora";
22
- async function importPrd(filePath) {
22
+ async function importPrd(filePath, opts = {}) {
23
23
  const config = requireConfig();
24
+ if (!filePath && !opts.yes) {
25
+ console.error(" Reading the document from standard input leaves nothing to answer the confirmation with.");
26
+ console.error(" Pass a file to review before importing: npx intentdocs import prd.md");
27
+ console.error(" Or import without asking: npx intentdocs import --yes < prd.md");
28
+ process.exitCode = 1;
29
+ return;
30
+ }
24
31
  let prdText;
25
32
  let format = "PRD";
26
33
  if (filePath) {
@@ -32,7 +39,7 @@ async function importPrd(filePath) {
32
39
  if (filePath.endsWith(".md")) format = "Markdown";
33
40
  else if (filePath.endsWith(".txt")) format = "text";
34
41
  } else {
35
- console.log(" Paste your PRD below (press Ctrl+D when done):\n");
42
+ if (process.stdin.isTTY) console.log(" Paste your PRD below (press Ctrl+D when done):\n");
36
43
  const chunks = [];
37
44
  for await (const chunk of process.stdin) {
38
45
  chunks.push(chunk);
@@ -86,7 +93,7 @@ async function importPrd(filePath) {
86
93
  }
87
94
  console.log();
88
95
  }
89
- const doImport = await confirm(
96
+ const doImport = opts.yes ? true : await confirm(
90
97
  ` Import this story map structure?`
91
98
  );
92
99
  if (doImport) {