intentdocs 0.8.0 → 0.10.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.
- package/README.md +7 -1
- package/bin/intentdocs.js +2 -0
- package/dist/chunk-34JCN66H.js +170 -0
- package/dist/chunk-6X4BORMW.js +317 -0
- package/dist/{chunk-6FGYJ3W2.js → chunk-F52HPVNQ.js} +43 -4
- package/dist/{chunk-ICOMMHKG.js → chunk-JX2RLGOM.js} +13 -2
- package/dist/chunk-KPQ42YE6.js +43 -0
- package/dist/chunk-LQTPZL2Y.js +15 -0
- package/dist/{chunk-4OWPN2RE.js → chunk-NHNBCNND.js} +28 -47
- package/dist/{chunk-IKF3ZLHK.js → chunk-PUGYRSMK.js} +72 -108
- package/dist/{chunk-ITQUQYWB.js → chunk-UQDMUKLP.js} +2 -2
- package/dist/chunk-UYTVTYNB.js +11 -0
- package/dist/{chunk-WPVDURTJ.js → chunk-WRRUCCVZ.js} +27 -3
- package/dist/chunk-XZE2XP6O.js +105 -0
- package/dist/ci-XCOXFTLB.js +11 -0
- package/dist/{complexity-NSSYVT54.js → complexity-X3IJKNNV.js} +12 -7
- package/dist/{import-prd-CKV3R67G.js → import-prd-IJTM6FZE.js} +13 -11
- package/dist/index.js +435 -194
- package/dist/{keys-EDWET3WG.js → keys-UVXCCWCS.js} +8 -7
- package/dist/log-XIH4BGFL.js +206 -0
- package/dist/{map-extract-I3AACTGL.js → map-extract-V6D27I3H.js} +3 -2
- package/dist/{map-import-IYUMRASO.js → map-import-LORTI4K4.js} +12 -10
- package/dist/{map-schema-JVL3O2TJ.js → map-schema-5BEIJM2J.js} +1 -1
- package/dist/projects-AYAYVFKL.js +37 -0
- package/dist/{prompt-WAWCRGHN.js → prompt-GOAFIKBZ.js} +3 -1
- package/dist/{research-6SXJ37TI.js → research-CO3W7PLC.js} +12 -10
- package/dist/scope-ZZLKQEF5.js +11 -0
- package/dist/setup-WSYIG2CR.js +14 -0
- package/package.json +4 -2
- package/dist/chunk-6XKB6RTU.js +0 -123
- package/dist/chunk-PCNDP5SX.js +0 -34
- package/dist/log-TT2UCJP7.js +0 -94
- package/dist/scope-OGKQ472D.js +0 -10
- package/dist/setup-SOI2HQAL.js +0 -11
package/dist/index.js
CHANGED
|
@@ -1,53 +1,74 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
UPGRADE_MESSAGE_PLAIN,
|
|
4
|
+
isPlanGated
|
|
5
|
+
} from "./chunk-UQDMUKLP.js";
|
|
6
|
+
import {
|
|
7
|
+
ci
|
|
8
|
+
} from "./chunk-6X4BORMW.js";
|
|
9
|
+
import {
|
|
4
10
|
setup,
|
|
5
|
-
shutdownCliAnalytics,
|
|
6
11
|
sync
|
|
7
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-PUGYRSMK.js";
|
|
13
|
+
import {
|
|
14
|
+
captureCli,
|
|
15
|
+
shutdownCliAnalytics
|
|
16
|
+
} from "./chunk-KPQ42YE6.js";
|
|
8
17
|
import {
|
|
9
18
|
scope
|
|
10
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-NHNBCNND.js";
|
|
11
20
|
import {
|
|
12
21
|
criterionPriority,
|
|
13
22
|
criterionStatusLabel,
|
|
14
23
|
findBlockingMusts,
|
|
24
|
+
formatDesignRefLine,
|
|
15
25
|
storyContextPath,
|
|
16
26
|
writeStoryContext
|
|
17
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-F52HPVNQ.js";
|
|
18
28
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
pausePrompt
|
|
23
|
-
} from "./chunk-WPVDURTJ.js";
|
|
24
|
-
import {
|
|
25
|
-
UPGRADE_MESSAGE_PLAIN,
|
|
26
|
-
isPlanGated
|
|
27
|
-
} from "./chunk-ITQUQYWB.js";
|
|
29
|
+
matchesStoryRef,
|
|
30
|
+
storyRef
|
|
31
|
+
} from "./chunk-LQTPZL2Y.js";
|
|
28
32
|
import {
|
|
33
|
+
amendDecision,
|
|
29
34
|
apiFetch,
|
|
30
35
|
apiUrl,
|
|
31
36
|
createStories,
|
|
32
37
|
getContext,
|
|
38
|
+
listDecisions,
|
|
33
39
|
listStories,
|
|
34
40
|
raiseBlocker,
|
|
35
41
|
reportCriteria,
|
|
42
|
+
retractDecision,
|
|
43
|
+
syncHeaders,
|
|
36
44
|
updateStories,
|
|
37
45
|
uploadScreenshot
|
|
38
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-34JCN66H.js";
|
|
39
47
|
import {
|
|
40
48
|
hasConfig,
|
|
41
49
|
readConfig,
|
|
42
50
|
requireConfig,
|
|
43
51
|
writeConfig
|
|
44
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-JX2RLGOM.js";
|
|
53
|
+
import {
|
|
54
|
+
ask,
|
|
55
|
+
closePrompt,
|
|
56
|
+
confirm,
|
|
57
|
+
isInteractive,
|
|
58
|
+
pausePrompt
|
|
59
|
+
} from "./chunk-WRRUCCVZ.js";
|
|
45
60
|
import {
|
|
46
|
-
|
|
61
|
+
authenticatedFetch,
|
|
62
|
+
bindingLabel,
|
|
63
|
+
clearAuth,
|
|
47
64
|
getAuth,
|
|
48
|
-
|
|
49
|
-
saveAuth
|
|
50
|
-
|
|
65
|
+
requireValidAuth,
|
|
66
|
+
saveAuth,
|
|
67
|
+
validateAuth
|
|
68
|
+
} from "./chunk-XZE2XP6O.js";
|
|
69
|
+
import {
|
|
70
|
+
API_BASE
|
|
71
|
+
} from "./chunk-UYTVTYNB.js";
|
|
51
72
|
|
|
52
73
|
// src/index.ts
|
|
53
74
|
import { Command } from "commander";
|
|
@@ -60,6 +81,27 @@ import { createInterface } from "readline";
|
|
|
60
81
|
|
|
61
82
|
// src/commands/login.ts
|
|
62
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");
|
|
99
|
+
}
|
|
100
|
+
console.log("You can now run: npx intentdocs init");
|
|
101
|
+
console.log(
|
|
102
|
+
"Optional: add your Anthropic API key to unlock AI features on any plan \u2014 npx intentdocs keys set"
|
|
103
|
+
);
|
|
104
|
+
}
|
|
63
105
|
function openBrowser(url) {
|
|
64
106
|
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "${url}"` : `xdg-open "${url}"`;
|
|
65
107
|
exec(cmd);
|
|
@@ -70,8 +112,24 @@ function sleep(ms) {
|
|
|
70
112
|
async function login() {
|
|
71
113
|
const existing = getAuth();
|
|
72
114
|
if (existing) {
|
|
73
|
-
|
|
74
|
-
|
|
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();
|
|
75
133
|
}
|
|
76
134
|
console.log("Logging in to intentdocs...\n");
|
|
77
135
|
const res = await fetch(`${API_BASE}/api/cli/auth`, { method: "POST" });
|
|
@@ -85,38 +143,83 @@ async function login() {
|
|
|
85
143
|
console.log(`If it doesn't open, visit: ${authUrl}
|
|
86
144
|
`);
|
|
87
145
|
openBrowser(authUrl);
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
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;
|
|
91
165
|
try {
|
|
92
166
|
const checkRes = await fetch(`${API_BASE}/api/cli/auth?code=${code}`);
|
|
93
167
|
if (!checkRes.ok) continue;
|
|
94
168
|
const data = await checkRes.json();
|
|
95
169
|
if (data.status === "approved" && data.token) {
|
|
96
|
-
|
|
97
|
-
token: data.token,
|
|
98
|
-
apiBase: API_BASE,
|
|
99
|
-
...data.userId && { userId: data.userId },
|
|
100
|
-
...data.posthogKey && { posthogKey: data.posthogKey },
|
|
101
|
-
...data.posthogHost && { posthogHost: data.posthogHost }
|
|
102
|
-
});
|
|
103
|
-
console.log("\u2705 Logged in to intentdocs!\n");
|
|
104
|
-
console.log("You can now run: intent init");
|
|
105
|
-
console.log(
|
|
106
|
-
"Optional: add your Anthropic API key to unlock AI features on any plan \u2014 intent keys set"
|
|
107
|
-
);
|
|
170
|
+
finishLogin(data);
|
|
108
171
|
return;
|
|
109
172
|
}
|
|
110
173
|
if (data.status === "expired") {
|
|
111
|
-
console.error("
|
|
174
|
+
console.error("\n\nThe server-side login request expired. Run `npx intentdocs login` to start a new one.");
|
|
112
175
|
process.exit(1);
|
|
113
176
|
}
|
|
114
|
-
process.stdout.write(".");
|
|
115
177
|
} catch {
|
|
116
|
-
process.stdout.write(".");
|
|
117
178
|
}
|
|
118
179
|
}
|
|
119
|
-
|
|
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 `npx 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 `npx 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");
|
|
120
223
|
process.exit(1);
|
|
121
224
|
}
|
|
122
225
|
async function logout() {
|
|
@@ -133,6 +236,7 @@ async function logout() {
|
|
|
133
236
|
}
|
|
134
237
|
|
|
135
238
|
// 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 `npx intentdocs 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";
|
|
136
240
|
function ask2(rl, question) {
|
|
137
241
|
return new Promise((resolve) => rl.question(question, resolve));
|
|
138
242
|
}
|
|
@@ -148,9 +252,6 @@ ${prompt} [1-${items.length}]: `);
|
|
|
148
252
|
console.log(` Please enter a number between 1 and ${items.length}.`);
|
|
149
253
|
}
|
|
150
254
|
}
|
|
151
|
-
function authHeaders(token) {
|
|
152
|
-
return { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
|
|
153
|
-
}
|
|
154
255
|
function detectStack(cwd) {
|
|
155
256
|
try {
|
|
156
257
|
if (existsSync(join(cwd, "package.json"))) {
|
|
@@ -179,12 +280,12 @@ function detectStack(cwd) {
|
|
|
179
280
|
}
|
|
180
281
|
async function init(tokenArg) {
|
|
181
282
|
if (hasConfig()) {
|
|
182
|
-
console.log("Already initialized. Run `
|
|
283
|
+
console.log("Already initialized. Run `npx intentdocs sync` to refresh.");
|
|
183
284
|
return;
|
|
184
285
|
}
|
|
185
286
|
if (tokenArg) {
|
|
186
|
-
|
|
187
|
-
|
|
287
|
+
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
|
+
process.exit(1);
|
|
188
289
|
}
|
|
189
290
|
let auth = getAuth();
|
|
190
291
|
if (!auth) {
|
|
@@ -200,24 +301,23 @@ async function init(tokenArg) {
|
|
|
200
301
|
const initStart = Date.now();
|
|
201
302
|
try {
|
|
202
303
|
console.log("\nFetching your projects...");
|
|
203
|
-
const projectsRes = await
|
|
204
|
-
headers: authHeaders(auth.token)
|
|
205
|
-
});
|
|
304
|
+
const projectsRes = await authenticatedFetch("/api/cli/projects");
|
|
206
305
|
if (!projectsRes.ok) {
|
|
207
|
-
console.error("Failed to fetch projects.
|
|
306
|
+
console.error("Failed to fetch projects. The server returned an error \u2014 try again or check intentdocs.com for status.");
|
|
208
307
|
process.exit(1);
|
|
209
308
|
}
|
|
210
|
-
const { projects } = await projectsRes.json();
|
|
309
|
+
const { projects, workspace: whereIAm } = await projectsRes.json();
|
|
211
310
|
if (projects.length === 0) {
|
|
212
311
|
console.log("\nNo projects found. Create one at intentdocs.com first.");
|
|
213
312
|
process.exit(0);
|
|
214
313
|
}
|
|
314
|
+
if (whereIAm) console.log(`
|
|
315
|
+
${whereIAm}`);
|
|
215
316
|
console.log("\nYour projects:\n");
|
|
216
317
|
const project = await pickFromList(rl, projects, (p) => p.name, "Select project");
|
|
217
318
|
console.log("\nFetching story maps...");
|
|
218
|
-
const mapsRes = await
|
|
219
|
-
|
|
220
|
-
{ headers: authHeaders(auth.token) }
|
|
319
|
+
const mapsRes = await authenticatedFetch(
|
|
320
|
+
`/api/cli/projects/${project.id}/maps`
|
|
221
321
|
);
|
|
222
322
|
if (!mapsRes.ok) {
|
|
223
323
|
console.error("Failed to fetch story maps.");
|
|
@@ -233,15 +333,15 @@ Story maps in "${project.name}":
|
|
|
233
333
|
`);
|
|
234
334
|
const map2 = await pickFromList(rl, maps, (m) => m.title, "Select story map");
|
|
235
335
|
const config = {
|
|
236
|
-
syncToken: map2.syncToken,
|
|
237
336
|
apiBase: auth.apiBase,
|
|
238
337
|
mapTitle: map2.title,
|
|
239
338
|
projectId: project.id,
|
|
240
339
|
mapId: map2.id,
|
|
340
|
+
workspace: project.workspace ? { orgId: project.workspace.orgId, name: project.workspace.name } : { orgId: null, name: null },
|
|
241
341
|
...appUrl && { appUrl }
|
|
242
342
|
};
|
|
243
343
|
console.log(`
|
|
244
|
-
Connecting to "${map2.title}"...`);
|
|
344
|
+
Connecting to "${map2.title}"${project.workspace ? ` in ${project.workspace.name ?? "its workspace"}` : ""}...`);
|
|
245
345
|
try {
|
|
246
346
|
const data = await listStories(config);
|
|
247
347
|
console.log(`\u2713 Connected to "${data.map.title}"`);
|
|
@@ -270,11 +370,19 @@ Connecting to "${map2.title}"...`);
|
|
|
270
370
|
console.log(`
|
|
271
371
|
\u2705 Ready! Next steps:
|
|
272
372
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
373
|
+
npx intentdocs status \u2014 see your story map
|
|
374
|
+
npx intentdocs next \u2014 pick up the first story
|
|
375
|
+
npx intentdocs setup \u2014 configure Claude Code / Cursor slash commands
|
|
376
|
+
npx intentdocs ci \u2014 report your tests' results from CI to each story
|
|
377
|
+
npx intentdocs propose \u2014 plan a new feature from the terminal
|
|
277
378
|
`);
|
|
379
|
+
const setUpCi = await ask2(rl, " Set up CI test reporting now? It creates a key and adds the workflow. (y/N): ");
|
|
380
|
+
if (/^y(es)?$/i.test(setUpCi.trim())) {
|
|
381
|
+
const { ci: ci2 } = await import("./ci-XCOXFTLB.js");
|
|
382
|
+
await ci2();
|
|
383
|
+
} else {
|
|
384
|
+
console.log(" You can run `npx intentdocs ci` any time to turn it on.\n");
|
|
385
|
+
}
|
|
278
386
|
} finally {
|
|
279
387
|
rl.close();
|
|
280
388
|
await shutdownCliAnalytics();
|
|
@@ -327,34 +435,6 @@ _Add important files and what they do as you build._
|
|
|
327
435
|
writeFileSync(archPath, archContent);
|
|
328
436
|
console.log(` \u2713 Created ARCHITECTURE.md with your stack (edit it as you build)`);
|
|
329
437
|
}
|
|
330
|
-
async function initWithSyncToken(syncToken) {
|
|
331
|
-
if (!syncToken.startsWith("intent_sync_") && !syncToken.startsWith("pb_sync_") && !syncToken.startsWith("sc_sync_")) {
|
|
332
|
-
console.error("Invalid token. It should start with intent_sync_");
|
|
333
|
-
process.exit(1);
|
|
334
|
-
}
|
|
335
|
-
console.log("\nConnecting to intentdocs...");
|
|
336
|
-
const config = { syncToken, apiBase: API_BASE };
|
|
337
|
-
try {
|
|
338
|
-
const data = await listStories(config);
|
|
339
|
-
console.log(`\u2713 Connected to "${data.map.title}"`);
|
|
340
|
-
console.log(
|
|
341
|
-
` ${data.summary.total} stories \u2014 ${data.summary.done} done, ${data.summary.todo} todo
|
|
342
|
-
`
|
|
343
|
-
);
|
|
344
|
-
writeConfig({ ...config, mapTitle: data.map.title });
|
|
345
|
-
} catch {
|
|
346
|
-
console.error("Failed to connect. Check your sync token.");
|
|
347
|
-
process.exit(1);
|
|
348
|
-
}
|
|
349
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
350
|
-
try {
|
|
351
|
-
await setupFiles(config);
|
|
352
|
-
await setupStack(rl);
|
|
353
|
-
} finally {
|
|
354
|
-
rl.close();
|
|
355
|
-
}
|
|
356
|
-
console.log("\n\u2705 Ready! Try: intent status");
|
|
357
|
-
}
|
|
358
438
|
async function setupFiles(config) {
|
|
359
439
|
try {
|
|
360
440
|
const md = await getContext(config);
|
|
@@ -375,10 +455,7 @@ async function setupFiles(config) {
|
|
|
375
455
|
if (existsSync(join(cwd, "CLAUDE.md"))) {
|
|
376
456
|
const claudeMd = readFileSync(join(cwd, "CLAUDE.md"), "utf-8");
|
|
377
457
|
if (!claudeMd.includes("INTENT.md")) {
|
|
378
|
-
appendFileSync(
|
|
379
|
-
join(cwd, "CLAUDE.md"),
|
|
380
|
-
"\n\n# intentdocs\nRead INTENT.md for the product story map, personas, and backlog.\n"
|
|
381
|
-
);
|
|
458
|
+
appendFileSync(join(cwd, "CLAUDE.md"), INTENTDOCS_CLAUDE_BLOCK);
|
|
382
459
|
console.log("\u2713 Added INTENT.md reference to CLAUDE.md");
|
|
383
460
|
}
|
|
384
461
|
}
|
|
@@ -508,7 +585,7 @@ function listView(stories2) {
|
|
|
508
585
|
${activity} (${done2}/${actStories.length})`);
|
|
509
586
|
for (const s of actStories) {
|
|
510
587
|
const icon = STATUS_ICON[s.status] ?? "\u2B1C";
|
|
511
|
-
const id = s
|
|
588
|
+
const id = storyRef(s);
|
|
512
589
|
console.log(` ${icon} ${s.title.slice(0, 70)} (${id})`);
|
|
513
590
|
}
|
|
514
591
|
}
|
|
@@ -556,7 +633,7 @@ function kanbanView(stories2) {
|
|
|
556
633
|
row(
|
|
557
634
|
cols.map((c) => {
|
|
558
635
|
const s = c.stories[i];
|
|
559
|
-
return s ? cell(s
|
|
636
|
+
return s ? cell(storyRef(s)) : emptyCell();
|
|
560
637
|
})
|
|
561
638
|
)
|
|
562
639
|
);
|
|
@@ -615,18 +692,32 @@ function parseVerificationSuggestions(raw) {
|
|
|
615
692
|
// src/commands/next.ts
|
|
616
693
|
var PRIORITY_ORDER = { must: 0, should: 1, could: 2 };
|
|
617
694
|
var EFFORT_ORDER = { S: 0, M: 1, L: 2, XL: 3 };
|
|
695
|
+
function unsequencedRefusal(story, apiBase, mapId) {
|
|
696
|
+
const planUrl = `${apiBase}/storymapper/${mapId}?phase=plan&story=${story.id}`;
|
|
697
|
+
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:
|
|
702
|
+
\u2192 ${planUrl}
|
|
703
|
+
`;
|
|
704
|
+
}
|
|
618
705
|
async function next(opts = {}) {
|
|
619
706
|
const config = requireConfig();
|
|
620
707
|
const data = await listStories(config);
|
|
621
708
|
let s;
|
|
622
709
|
if (opts.storyId) {
|
|
623
|
-
const match = data.stories.find((st) => st
|
|
710
|
+
const match = data.stories.find((st) => matchesStoryRef(st, opts.storyId));
|
|
624
711
|
if (!match) {
|
|
625
712
|
console.log(`
|
|
626
713
|
\u274C No story found matching ID: ${opts.storyId}
|
|
627
714
|
`);
|
|
628
715
|
return;
|
|
629
716
|
}
|
|
717
|
+
if (match.build_sequence == null) {
|
|
718
|
+
console.log(unsequencedRefusal(match, config.apiBase, data.map.id));
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
630
721
|
s = match;
|
|
631
722
|
} else {
|
|
632
723
|
if (!opts.skipWip) {
|
|
@@ -637,10 +728,10 @@ async function next(opts = {}) {
|
|
|
637
728
|
`);
|
|
638
729
|
for (const ip of inProgress) {
|
|
639
730
|
console.log(` \u{1F528} ${ip.title}`);
|
|
640
|
-
console.log(` ID: ${ip.id.slice(0, 8)} | Mark done:
|
|
731
|
+
console.log(` ID: ${ip.id.slice(0, 8)} | Mark done: npx intentdocs done ${ip.id.slice(0, 8)}`);
|
|
641
732
|
}
|
|
642
733
|
console.log(`
|
|
643
|
-
Finish those first, or run \`
|
|
734
|
+
Finish those first, or run \`npx intentdocs next --skip-wip\` to pick the next todo anyway.
|
|
644
735
|
`);
|
|
645
736
|
return;
|
|
646
737
|
}
|
|
@@ -648,12 +739,13 @@ async function next(opts = {}) {
|
|
|
648
739
|
const doneIds = new Set(
|
|
649
740
|
data.stories.filter((st) => st.status === "done").map((st) => st.id)
|
|
650
741
|
);
|
|
651
|
-
const
|
|
742
|
+
const allTodos = data.stories.filter((st) => st.status === "todo");
|
|
743
|
+
const todos = allTodos.filter((st) => st.build_sequence != null).filter((st) => {
|
|
652
744
|
if (!st.depends_on?.length) return true;
|
|
653
745
|
return st.depends_on.every((depId) => doneIds.has(depId));
|
|
654
746
|
}).sort((a, b) => {
|
|
655
|
-
const aSeq = a.build_sequence
|
|
656
|
-
const bSeq = b.build_sequence
|
|
747
|
+
const aSeq = a.build_sequence;
|
|
748
|
+
const bSeq = b.build_sequence;
|
|
657
749
|
if (aSeq !== bSeq) return aSeq - bSeq;
|
|
658
750
|
if (a.release !== b.release) return a.release - b.release;
|
|
659
751
|
const pa = PRIORITY_ORDER[a.priority] ?? 9;
|
|
@@ -662,11 +754,17 @@ async function next(opts = {}) {
|
|
|
662
754
|
return (EFFORT_ORDER[a.effort] ?? 9) - (EFFORT_ORDER[b.effort] ?? 9);
|
|
663
755
|
});
|
|
664
756
|
if (todos.length === 0) {
|
|
665
|
-
const
|
|
666
|
-
|
|
757
|
+
const unsequencedCount = allTodos.filter((st) => st.build_sequence == null).length;
|
|
758
|
+
const blockedCount = allTodos.length - unsequencedCount;
|
|
759
|
+
if (unsequencedCount > 0) {
|
|
667
760
|
console.log(`
|
|
668
|
-
\u{1F512} ${
|
|
669
|
-
console.log(`
|
|
761
|
+
\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 \`npx intentdocs next\` again.
|
|
763
|
+
`);
|
|
764
|
+
} else if (blockedCount > 0) {
|
|
765
|
+
console.log(`
|
|
766
|
+
\u{1F512} ${blockedCount} stories remain but all have unfinished dependencies.`);
|
|
767
|
+
console.log(` Finish in-progress stories first, then run \`npx intentdocs next\` again.
|
|
670
768
|
`);
|
|
671
769
|
} else {
|
|
672
770
|
console.log("\n \u{1F389} All stories are done! Ship it.\n");
|
|
@@ -696,7 +794,7 @@ async function next(opts = {}) {
|
|
|
696
794
|
console.log(` Plan: \u2705 Implementation plan available`);
|
|
697
795
|
}
|
|
698
796
|
console.log(`
|
|
699
|
-
Start building:
|
|
797
|
+
Start building: npx intentdocs implement ${s.id.slice(0, 8)}
|
|
700
798
|
`);
|
|
701
799
|
if (opts.open) {
|
|
702
800
|
openBrowser2(specUrl);
|
|
@@ -757,7 +855,7 @@ async function generateVerificationSuggestions(story, contextPath) {
|
|
|
757
855
|
`);
|
|
758
856
|
return;
|
|
759
857
|
}
|
|
760
|
-
const lines = [`# Suggested verification commands: ${story.title}`, ``, `_Generated by \`
|
|
858
|
+
const lines = [`# Suggested verification commands: ${story.title}`, ``, `_Generated by \`npx intentdocs next --stub\` \u2014 copy into the criterion's command field, or run \`npx intentdocs check\` once wired up._`, ``];
|
|
761
859
|
console.log();
|
|
762
860
|
for (const suggestion of suggestions) {
|
|
763
861
|
const ac = candidates[suggestion.index - 1];
|
|
@@ -812,10 +910,14 @@ async function implement(storyId, opts = {}) {
|
|
|
812
910
|
if (s.status === "in_progress") {
|
|
813
911
|
console.log(`
|
|
814
912
|
\u{1F528} "${s.title}" is already in progress.`);
|
|
815
|
-
console.log(` Mark done:
|
|
913
|
+
console.log(` Mark done: npx intentdocs done ${s.id.slice(0, 8)}
|
|
816
914
|
`);
|
|
817
915
|
return;
|
|
818
916
|
}
|
|
917
|
+
if (s.build_sequence == null) {
|
|
918
|
+
console.log(unsequencedRefusal(s, config.apiBase, data.map.id));
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
819
921
|
const doneIds = new Set(
|
|
820
922
|
data.stories.filter((st) => st.status === "done").map((st) => st.id)
|
|
821
923
|
);
|
|
@@ -850,10 +952,10 @@ async function implement(storyId, opts = {}) {
|
|
|
850
952
|
}
|
|
851
953
|
console.log(` \u2502 \u2502`);
|
|
852
954
|
console.log(` \u2502 Consider: \u2502`);
|
|
853
|
-
console.log(` \u2502
|
|
955
|
+
console.log(` \u2502 npx intentdocs sync --context (refresh CLAUDE.md) \u2502`);
|
|
854
956
|
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
|
|
855
957
|
`);
|
|
856
|
-
const proceed = await confirm(" Assign this story and mark in progress?");
|
|
958
|
+
const proceed = isInteractive() ? await confirm(" Assign this story and mark in progress?") : false;
|
|
857
959
|
if (!proceed) {
|
|
858
960
|
console.log(`
|
|
859
961
|
Skipped.
|
|
@@ -897,7 +999,7 @@ async function implement(storyId, opts = {}) {
|
|
|
897
999
|
console.log(` Priority: ${s.priority} | Effort: ${s.effort} | Release: ${s.release}${s.build_sequence ? ` | Build step: ${s.build_sequence}` : ""}`);
|
|
898
1000
|
console.log(` Context: ${relPath}`);
|
|
899
1001
|
console.log(` Spec: ${specUrl}`);
|
|
900
|
-
console.log(` Mark done:
|
|
1002
|
+
console.log(` Mark done: npx intentdocs done ${s.id.slice(0, 8)}
|
|
901
1003
|
`);
|
|
902
1004
|
if (opts.stub) {
|
|
903
1005
|
await generateVerificationSuggestions(s, contextPath);
|
|
@@ -999,27 +1101,30 @@ function extractImplementationContext(storyId) {
|
|
|
999
1101
|
return { summary, files };
|
|
1000
1102
|
}
|
|
1001
1103
|
async function passesCriteriaGuard(story) {
|
|
1002
|
-
if (story.acceptance_criteria.length === 0) return { proceed: true, forced: false };
|
|
1104
|
+
if (story.acceptance_criteria.length === 0) return { proceed: true, forced: false, blockedNonInteractive: false };
|
|
1003
1105
|
console.log();
|
|
1004
1106
|
console.log(" Acceptance criteria:");
|
|
1005
1107
|
for (const ac of story.acceptance_criteria) {
|
|
1006
1108
|
console.log(` [${criterionPriority(ac)}] [${criterionStatusLabel(ac)}] ${ac.text}`);
|
|
1007
1109
|
}
|
|
1008
1110
|
const blocking = findBlockingMusts(story.acceptance_criteria);
|
|
1009
|
-
if (blocking.length === 0) return { proceed: true, forced: false };
|
|
1111
|
+
if (blocking.length === 0) return { proceed: true, forced: false, blockedNonInteractive: false };
|
|
1010
1112
|
console.log();
|
|
1011
1113
|
console.log(` ${blocking.length} 'must' criterion/criteria still failing or unreported:`);
|
|
1012
1114
|
for (const ac of blocking) {
|
|
1013
1115
|
console.log(` - [${criterionStatusLabel(ac)}] ${ac.text}`);
|
|
1014
1116
|
}
|
|
1015
1117
|
console.log();
|
|
1118
|
+
if (!isInteractive()) {
|
|
1119
|
+
return { proceed: false, forced: false, blockedNonInteractive: true };
|
|
1120
|
+
}
|
|
1016
1121
|
const proceed = await confirm(" Mark done anyway?");
|
|
1017
|
-
return { proceed, forced: proceed };
|
|
1122
|
+
return { proceed, forced: proceed, blockedNonInteractive: false };
|
|
1018
1123
|
}
|
|
1019
1124
|
async function done(idPrefix, opts = {}) {
|
|
1020
1125
|
const config = requireConfig();
|
|
1021
1126
|
const data = await listStories(config);
|
|
1022
|
-
const match = data.stories.find((s) => s
|
|
1127
|
+
const match = data.stories.find((s) => matchesStoryRef(s, idPrefix));
|
|
1023
1128
|
if (!match) {
|
|
1024
1129
|
console.error(`No story found matching "${idPrefix}"`);
|
|
1025
1130
|
process.exit(1);
|
|
@@ -1031,6 +1136,13 @@ async function done(idPrefix, opts = {}) {
|
|
|
1031
1136
|
const guard = await passesCriteriaGuard(match);
|
|
1032
1137
|
if (!guard.proceed) {
|
|
1033
1138
|
closePrompt();
|
|
1139
|
+
if (guard.blockedNonInteractive) {
|
|
1140
|
+
console.error(
|
|
1141
|
+
` \u2717 Not marked done: "${match.title}" has unresolved 'must' criteria and no TTY is available to confirm an override.`
|
|
1142
|
+
);
|
|
1143
|
+
console.error(` Report the criteria (npx intentdocs check / report_criteria) or re-run interactively to force it.`);
|
|
1144
|
+
process.exit(1);
|
|
1145
|
+
}
|
|
1034
1146
|
return;
|
|
1035
1147
|
}
|
|
1036
1148
|
const { summary, files } = extractImplementationContext(match.id);
|
|
@@ -1050,6 +1162,17 @@ async function done(idPrefix, opts = {}) {
|
|
|
1050
1162
|
if (files.length > 0) update.implemented_files = files;
|
|
1051
1163
|
if (screenshotUrl) update.screenshot_url = screenshotUrl;
|
|
1052
1164
|
const result = await updateStories(config, [update]);
|
|
1165
|
+
const verify = await listStories(config);
|
|
1166
|
+
const persisted = verify.stories.find((s) => s.id === match.id);
|
|
1167
|
+
if (persisted?.status !== "done") {
|
|
1168
|
+
console.error(
|
|
1169
|
+
` \u2717 Done-mark did not persist: server reports status="${persisted?.status ?? "not found"}" for "${match.title}" after the update.`
|
|
1170
|
+
);
|
|
1171
|
+
console.error(` Not treating this run as successful \u2014 re-run \`npx intentdocs done ${idPrefix}\` or check the sync API.`);
|
|
1172
|
+
await shutdownCliAnalytics();
|
|
1173
|
+
closePrompt();
|
|
1174
|
+
process.exit(1);
|
|
1175
|
+
}
|
|
1053
1176
|
const effortToComplexity = (e) => e === "XL" || e === "L" ? "high" : e === "M" ? "med" : "low";
|
|
1054
1177
|
captureCli(
|
|
1055
1178
|
"story_shipped",
|
|
@@ -1067,12 +1190,12 @@ async function done(idPrefix, opts = {}) {
|
|
|
1067
1190
|
if (summary) console.log(` \u21B3 Implementation summary posted to story map`);
|
|
1068
1191
|
if (files.length > 0) console.log(` \u21B3 ${files.length} file(s) recorded`);
|
|
1069
1192
|
if (screenshotUrl) console.log(` \u21B3 Screenshot attached`);
|
|
1070
|
-
for (const release of result.
|
|
1193
|
+
for (const release of result.readyReleases ?? []) {
|
|
1071
1194
|
console.log();
|
|
1072
|
-
console.log(`\u{
|
|
1195
|
+
console.log(`\u{1F3C1} ${release.label} is ready to close (${release.storyCount} stories done) \u2014 close it on Validate \u203A Release`);
|
|
1073
1196
|
}
|
|
1074
1197
|
console.log();
|
|
1075
|
-
const wantsDecision = await confirm(" Log a decision or lesson from this story?");
|
|
1198
|
+
const wantsDecision = isInteractive() ? await confirm(" Log a decision or lesson from this story?") : false;
|
|
1076
1199
|
if (wantsDecision) {
|
|
1077
1200
|
const title = await ask(" Decision title: ");
|
|
1078
1201
|
const description = await ask(" What was decided / learned: ");
|
|
@@ -1103,16 +1226,19 @@ async function done(idPrefix, opts = {}) {
|
|
|
1103
1226
|
}
|
|
1104
1227
|
}
|
|
1105
1228
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1229
|
+
try {
|
|
1230
|
+
await apiFetch(config, "/activity", {
|
|
1231
|
+
method: "POST",
|
|
1232
|
+
headers: { "Content-Type": "application/json" },
|
|
1233
|
+
body: JSON.stringify({
|
|
1234
|
+
event_type: "story_done",
|
|
1235
|
+
summary: `Marked done: ${match.title}`,
|
|
1236
|
+
metadata: { story_id: match.id, ...screenshotUrl ? { screenshot_url: screenshotUrl } : {} },
|
|
1237
|
+
source: "cli"
|
|
1238
|
+
})
|
|
1239
|
+
});
|
|
1240
|
+
} catch {
|
|
1241
|
+
}
|
|
1116
1242
|
await shutdownCliAnalytics();
|
|
1117
1243
|
closePrompt();
|
|
1118
1244
|
}
|
|
@@ -1188,13 +1314,13 @@ function isRunnable(ac) {
|
|
|
1188
1314
|
}
|
|
1189
1315
|
function resolveStory(stories2, idPrefix) {
|
|
1190
1316
|
if (idPrefix) {
|
|
1191
|
-
const story = stories2.find((s) => s
|
|
1317
|
+
const story = stories2.find((s) => matchesStoryRef(s, idPrefix));
|
|
1192
1318
|
return story ? { story } : { error: `No story found matching "${idPrefix}"` };
|
|
1193
1319
|
}
|
|
1194
1320
|
const inProgress = stories2.filter((s) => s.status === "in_progress");
|
|
1195
1321
|
if (inProgress.length === 1) return { story: inProgress[0] };
|
|
1196
1322
|
return {
|
|
1197
|
-
error: inProgress.length === 0 ? "No story in progress. Specify one:
|
|
1323
|
+
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>`
|
|
1198
1324
|
};
|
|
1199
1325
|
}
|
|
1200
1326
|
async function check(idPrefix, opts = {}) {
|
|
@@ -1292,18 +1418,8 @@ function printBanner(subtitle) {
|
|
|
1292
1418
|
|
|
1293
1419
|
// src/commands/start.ts
|
|
1294
1420
|
import ora from "ora";
|
|
1295
|
-
async function
|
|
1296
|
-
|
|
1297
|
-
...opts,
|
|
1298
|
-
headers: {
|
|
1299
|
-
"Content-Type": "application/json",
|
|
1300
|
-
Authorization: `Bearer ${token}`,
|
|
1301
|
-
...opts.headers ?? {}
|
|
1302
|
-
}
|
|
1303
|
-
});
|
|
1304
|
-
}
|
|
1305
|
-
async function streamResponse(path, token, body, options) {
|
|
1306
|
-
const res = await apiFetch2(path, token, {
|
|
1421
|
+
async function streamResponse(path, body, options) {
|
|
1422
|
+
const res = await authenticatedFetch(path, {
|
|
1307
1423
|
method: "POST",
|
|
1308
1424
|
body: JSON.stringify(body)
|
|
1309
1425
|
});
|
|
@@ -1331,8 +1447,7 @@ async function streamResponse(path, token, body, options) {
|
|
|
1331
1447
|
return full;
|
|
1332
1448
|
}
|
|
1333
1449
|
async function start() {
|
|
1334
|
-
|
|
1335
|
-
const { token } = auth;
|
|
1450
|
+
await requireValidAuth();
|
|
1336
1451
|
printBanner("Start a new project");
|
|
1337
1452
|
console.log(" Let's start with the problem you want to solve.\n");
|
|
1338
1453
|
const problem = await ask(" What problem are you trying to solve?\n > ");
|
|
@@ -1345,13 +1460,13 @@ async function start() {
|
|
|
1345
1460
|
const spinner = ora(" Thinking about your idea...").start();
|
|
1346
1461
|
let discoveryText;
|
|
1347
1462
|
try {
|
|
1348
|
-
discoveryText = await streamResponse("/api/blueprint/discovery",
|
|
1463
|
+
discoveryText = await streamResponse("/api/blueprint/discovery", {
|
|
1349
1464
|
idea: problem
|
|
1350
1465
|
}, { silent: true });
|
|
1351
1466
|
} catch (err) {
|
|
1352
1467
|
spinner.fail(" Failed to connect");
|
|
1353
1468
|
console.error(` ${err.message}`);
|
|
1354
|
-
console.error(" Make sure you're logged in:
|
|
1469
|
+
console.error(" Make sure you're logged in: npx intentdocs login");
|
|
1355
1470
|
closePrompt();
|
|
1356
1471
|
return;
|
|
1357
1472
|
}
|
|
@@ -1368,7 +1483,7 @@ async function start() {
|
|
|
1368
1483
|
}
|
|
1369
1484
|
pausePrompt();
|
|
1370
1485
|
const pitchSpinner = ora(" Synthesising your pitch...").start();
|
|
1371
|
-
const pitchRes = await
|
|
1486
|
+
const pitchRes = await authenticatedFetch("/api/blueprint/synthesize-pitch", {
|
|
1372
1487
|
method: "POST",
|
|
1373
1488
|
body: JSON.stringify({ problem, qa })
|
|
1374
1489
|
});
|
|
@@ -1404,7 +1519,7 @@ async function start() {
|
|
|
1404
1519
|
Great \u2014 "${productName}" it is.
|
|
1405
1520
|
`);
|
|
1406
1521
|
console.log(" Creating project...");
|
|
1407
|
-
const createRes = await
|
|
1522
|
+
const createRes = await authenticatedFetch("/api/blueprint/create", {
|
|
1408
1523
|
method: "POST",
|
|
1409
1524
|
body: JSON.stringify({
|
|
1410
1525
|
messages: [{ role: "user", content: problem }],
|
|
@@ -1423,14 +1538,14 @@ async function start() {
|
|
|
1423
1538
|
Failed to create project: ${data.error ?? "unknown error"}`);
|
|
1424
1539
|
}
|
|
1425
1540
|
} else {
|
|
1426
|
-
console.error("\n Failed to create project. Try logging in again:
|
|
1541
|
+
console.error("\n Failed to create project. Try logging in again: npx intentdocs login");
|
|
1427
1542
|
}
|
|
1428
1543
|
closePrompt();
|
|
1429
1544
|
return;
|
|
1430
1545
|
}
|
|
1431
1546
|
const createContentType = createRes.headers.get("content-type") ?? "";
|
|
1432
1547
|
if (!createContentType.includes("application/json")) {
|
|
1433
|
-
console.error("\n Unexpected response from server. Try logging in again:
|
|
1548
|
+
console.error("\n Unexpected response from server. Try logging in again: npx intentdocs login");
|
|
1434
1549
|
closePrompt();
|
|
1435
1550
|
return;
|
|
1436
1551
|
}
|
|
@@ -1461,9 +1576,8 @@ async function start() {
|
|
|
1461
1576
|
console.log(" \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\n");
|
|
1462
1577
|
const confirmPublish = await confirm(" Publish this to Explore?");
|
|
1463
1578
|
if (confirmPublish) {
|
|
1464
|
-
const pubRes = await
|
|
1579
|
+
const pubRes = await authenticatedFetch(
|
|
1465
1580
|
`/api/projects/${projectId}/publish`,
|
|
1466
|
-
token,
|
|
1467
1581
|
{
|
|
1468
1582
|
method: "POST",
|
|
1469
1583
|
body: JSON.stringify({
|
|
@@ -1495,7 +1609,7 @@ async function start() {
|
|
|
1495
1609
|
if (!readyToScope) {
|
|
1496
1610
|
console.log(`
|
|
1497
1611
|
No worries! When you're ready:
|
|
1498
|
-
\u2022 Run:
|
|
1612
|
+
\u2022 Run: npx intentdocs scope ${projectId}
|
|
1499
1613
|
\u2022 Or visit: ${API_BASE}
|
|
1500
1614
|
|
|
1501
1615
|
Your project is saved and waiting for you.
|
|
@@ -1503,7 +1617,7 @@ async function start() {
|
|
|
1503
1617
|
closePrompt();
|
|
1504
1618
|
return;
|
|
1505
1619
|
}
|
|
1506
|
-
const { scope: scope2 } = await import("./scope-
|
|
1620
|
+
const { scope: scope2 } = await import("./scope-ZZLKQEF5.js");
|
|
1507
1621
|
await scope2(projectId);
|
|
1508
1622
|
}
|
|
1509
1623
|
|
|
@@ -1511,9 +1625,9 @@ async function start() {
|
|
|
1511
1625
|
import { mkdirSync, writeFileSync as writeFileSync3, readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
|
|
1512
1626
|
import { join as join4 } from "path";
|
|
1513
1627
|
async function propose(changeName, opts = {}) {
|
|
1514
|
-
|
|
1628
|
+
await requireValidAuth();
|
|
1515
1629
|
if (!hasConfig()) {
|
|
1516
|
-
console.log(" Not connected to a project. Run `
|
|
1630
|
+
console.log(" Not connected to a project. Run `npx intentdocs init` first.");
|
|
1517
1631
|
closePrompt();
|
|
1518
1632
|
return;
|
|
1519
1633
|
}
|
|
@@ -1655,9 +1769,9 @@ async function propose(changeName, opts = {}) {
|
|
|
1655
1769
|
Local spec saved at: intent-changes/${slug}/proposal.md
|
|
1656
1770
|
|
|
1657
1771
|
Next:
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1772
|
+
npx intentdocs next \u2014 pick up the first story
|
|
1773
|
+
npx intentdocs status \u2014 see full progress
|
|
1774
|
+
npx intentdocs sync \u2014 refresh INTENT.md
|
|
1661
1775
|
`);
|
|
1662
1776
|
closePrompt();
|
|
1663
1777
|
}
|
|
@@ -1807,7 +1921,7 @@ async function proposeFromFile(filePath, config, nameOverride) {
|
|
|
1807
1921
|
writeFileSync3(join4(changesDir, "proposal.md"), proposalMd);
|
|
1808
1922
|
console.log(` \u2713 Saved intent-changes/${slug}/proposal.md`);
|
|
1809
1923
|
console.log(`
|
|
1810
|
-
\u2705 Done! Run \`
|
|
1924
|
+
\u2705 Done! Run \`npx intentdocs next\` to start building.
|
|
1811
1925
|
`);
|
|
1812
1926
|
}
|
|
1813
1927
|
|
|
@@ -1820,11 +1934,11 @@ var STATUS_ICON2 = {
|
|
|
1820
1934
|
async function show(idPrefix) {
|
|
1821
1935
|
const config = requireConfig();
|
|
1822
1936
|
const data = await listStories(config);
|
|
1823
|
-
const story = data.stories.find((s) => s
|
|
1937
|
+
const story = data.stories.find((s) => matchesStoryRef(s, idPrefix));
|
|
1824
1938
|
if (!story) {
|
|
1825
1939
|
console.error(`
|
|
1826
1940
|
No story found matching "${idPrefix}"
|
|
1827
|
-
Run \`
|
|
1941
|
+
Run \`npx intentdocs stories\` to list IDs.
|
|
1828
1942
|
`);
|
|
1829
1943
|
process.exit(1);
|
|
1830
1944
|
}
|
|
@@ -1853,6 +1967,13 @@ async function show(idPrefix) {
|
|
|
1853
1967
|
console.log(` ${check2} ${ac.text}`);
|
|
1854
1968
|
}
|
|
1855
1969
|
}
|
|
1970
|
+
if (story.design_refs && story.design_refs.length > 0) {
|
|
1971
|
+
console.log(`
|
|
1972
|
+
Design references:`);
|
|
1973
|
+
for (const ref of story.design_refs) {
|
|
1974
|
+
console.log(` ${formatDesignRefLine(ref)}`);
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1856
1977
|
if (story.notes) {
|
|
1857
1978
|
console.log(`
|
|
1858
1979
|
Notes:`);
|
|
@@ -1871,7 +1992,7 @@ async function show(idPrefix) {
|
|
|
1871
1992
|
const relPath = contextPath.replace(process.cwd() + "/", "");
|
|
1872
1993
|
console.log(`
|
|
1873
1994
|
Context : ${relPath}`);
|
|
1874
|
-
console.log(` Mark done:
|
|
1995
|
+
console.log(` Mark done: npx intentdocs done ${storyRef(story)}
|
|
1875
1996
|
`);
|
|
1876
1997
|
}
|
|
1877
1998
|
|
|
@@ -1920,7 +2041,7 @@ async function example() {
|
|
|
1920
2041
|
console.log(`
|
|
1921
2042
|
\u2713 Example saved: "${data.title}"
|
|
1922
2043
|
`);
|
|
1923
|
-
console.log(" Run `
|
|
2044
|
+
console.log(" Run `npx intentdocs sync --context` to regenerate your CLAUDE.md with this pattern.\n");
|
|
1924
2045
|
} catch {
|
|
1925
2046
|
rl.close();
|
|
1926
2047
|
throw new Error("Aborted");
|
|
@@ -2222,6 +2343,38 @@ var TOOLS = [
|
|
|
2222
2343
|
},
|
|
2223
2344
|
mode: "standard"
|
|
2224
2345
|
},
|
|
2346
|
+
{
|
|
2347
|
+
name: "amend_decision",
|
|
2348
|
+
description: "Amend a previously logged decision's title, description, or category. Use when you got a decision wrong or need to update it after new information. Cannot amend a decision that's been retracted.",
|
|
2349
|
+
inputSchema: {
|
|
2350
|
+
type: "object",
|
|
2351
|
+
properties: {
|
|
2352
|
+
decision_id: { type: "string", description: "Decision ID (full UUID or prefix)" },
|
|
2353
|
+
title: { type: "string", description: "New title" },
|
|
2354
|
+
description: { type: "string", description: "New description" },
|
|
2355
|
+
category: {
|
|
2356
|
+
type: "string",
|
|
2357
|
+
enum: ["architecture", "product", "technical", "trade-off"],
|
|
2358
|
+
description: "New category"
|
|
2359
|
+
}
|
|
2360
|
+
},
|
|
2361
|
+
required: ["decision_id"]
|
|
2362
|
+
},
|
|
2363
|
+
mode: "standard"
|
|
2364
|
+
},
|
|
2365
|
+
{
|
|
2366
|
+
name: "retract_decision",
|
|
2367
|
+
description: "Retract a previously logged decision \u2014 a soft, audited removal (marked retracted with a timestamp and optional reason), never a hard delete. Use when a decision was made in error or no longer applies.",
|
|
2368
|
+
inputSchema: {
|
|
2369
|
+
type: "object",
|
|
2370
|
+
properties: {
|
|
2371
|
+
decision_id: { type: "string", description: "Decision ID (full UUID or prefix)" },
|
|
2372
|
+
reason: { type: "string", description: "Why this decision is being retracted" }
|
|
2373
|
+
},
|
|
2374
|
+
required: ["decision_id"]
|
|
2375
|
+
},
|
|
2376
|
+
mode: "standard"
|
|
2377
|
+
},
|
|
2225
2378
|
// ── All tools (~15k tokens) ─────────────────────────────────────────────
|
|
2226
2379
|
{
|
|
2227
2380
|
name: "analyze_complexity",
|
|
@@ -2275,7 +2428,7 @@ async function handleToolCall(name, args, config) {
|
|
|
2275
2428
|
const storyId = args?.story_id;
|
|
2276
2429
|
if (storyId) {
|
|
2277
2430
|
const data = await listStories(config);
|
|
2278
|
-
const match = data.stories.find((s) => s
|
|
2431
|
+
const match = data.stories.find((s) => matchesStoryRef(s, storyId));
|
|
2279
2432
|
if (match?.cancelled_at) {
|
|
2280
2433
|
return {
|
|
2281
2434
|
content: [{
|
|
@@ -2312,9 +2465,27 @@ async function handleToolCall(name, args, config) {
|
|
|
2312
2465
|
const storyId = args?.story_id;
|
|
2313
2466
|
if (!storyId) return { content: [{ type: "text", text: "story_id is required" }] };
|
|
2314
2467
|
const data = await listStories(config);
|
|
2315
|
-
const match = data.stories.find((s) => s
|
|
2468
|
+
const match = data.stories.find((s) => matchesStoryRef(s, storyId));
|
|
2316
2469
|
if (!match) return { content: [{ type: "text", text: `No story found matching "${storyId}"` }] };
|
|
2317
|
-
|
|
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
|
+
const result = await updateStories(config, [{ id: match.id, status: "in_progress" }]);
|
|
2481
|
+
if (result.missing?.includes(match.id)) {
|
|
2482
|
+
return {
|
|
2483
|
+
content: [{
|
|
2484
|
+
type: "text",
|
|
2485
|
+
text: `\u2717 "${match.title}" was not updated \u2014 the server reports the id matched no row. Re-run list_stories and retry.`
|
|
2486
|
+
}]
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2318
2489
|
return {
|
|
2319
2490
|
content: [{ type: "text", text: `\u2713 ${match.title} \u2192 in_progress` }]
|
|
2320
2491
|
};
|
|
@@ -2324,7 +2495,7 @@ async function handleToolCall(name, args, config) {
|
|
|
2324
2495
|
if (!storyId) return { content: [{ type: "text", text: "story_id is required" }] };
|
|
2325
2496
|
const force = args?.force === true;
|
|
2326
2497
|
const data = await listStories(config);
|
|
2327
|
-
const match = data.stories.find((s) => s
|
|
2498
|
+
const match = data.stories.find((s) => matchesStoryRef(s, storyId));
|
|
2328
2499
|
if (!match) return { content: [{ type: "text", text: `No story found matching "${storyId}"` }] };
|
|
2329
2500
|
const blocking = findBlockingMusts(match.acceptance_criteria);
|
|
2330
2501
|
if (!force && blocking.length > 0) {
|
|
@@ -2340,7 +2511,15 @@ Fix and call report_criteria, or call mark_done again with force:true to overrid
|
|
|
2340
2511
|
};
|
|
2341
2512
|
}
|
|
2342
2513
|
const forced = force && blocking.length > 0;
|
|
2343
|
-
await updateStories(config, [{ id: match.id, status: "done", ...forced ? { forced: true } : {} }]);
|
|
2514
|
+
const result = await updateStories(config, [{ id: match.id, status: "done", ...forced ? { forced: true } : {} }]);
|
|
2515
|
+
if (result.missing?.includes(match.id)) {
|
|
2516
|
+
return {
|
|
2517
|
+
content: [{
|
|
2518
|
+
type: "text",
|
|
2519
|
+
text: `\u2717 "${match.title}" was not marked done \u2014 the server reports the id matched no row. Re-run list_stories and retry.`
|
|
2520
|
+
}]
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2344
2523
|
return {
|
|
2345
2524
|
content: [{ type: "text", text: `\u2713 ${match.title} \u2192 done` }]
|
|
2346
2525
|
};
|
|
@@ -2352,7 +2531,7 @@ Fix and call report_criteria, or call mark_done again with force:true to overrid
|
|
|
2352
2531
|
return { content: [{ type: "text", text: "story_id and results are required" }] };
|
|
2353
2532
|
}
|
|
2354
2533
|
const data = await listStories(config);
|
|
2355
|
-
const match = data.stories.find((s) => s
|
|
2534
|
+
const match = data.stories.find((s) => matchesStoryRef(s, storyId));
|
|
2356
2535
|
if (!match) return { content: [{ type: "text", text: `No story found matching "${storyId}"` }] };
|
|
2357
2536
|
const result = await reportCriteria(
|
|
2358
2537
|
config,
|
|
@@ -2384,7 +2563,7 @@ Fix and call report_criteria, or call mark_done again with force:true to overrid
|
|
|
2384
2563
|
};
|
|
2385
2564
|
}
|
|
2386
2565
|
const data = await listStories(config);
|
|
2387
|
-
const match = data.stories.find((s) => s
|
|
2566
|
+
const match = data.stories.find((s) => matchesStoryRef(s, storyId));
|
|
2388
2567
|
if (!match) return { content: [{ type: "text", text: `No story found matching "${storyId}"` }] };
|
|
2389
2568
|
await raiseBlocker(config, {
|
|
2390
2569
|
story_id: match.id,
|
|
@@ -2452,6 +2631,48 @@ Decision needed: ${decisionNeeded}`
|
|
|
2452
2631
|
}
|
|
2453
2632
|
return { content: [{ type: "text", text: "Failed to log decision" }] };
|
|
2454
2633
|
}
|
|
2634
|
+
case "amend_decision": {
|
|
2635
|
+
const decisionId = args?.decision_id;
|
|
2636
|
+
if (!decisionId) return { content: [{ type: "text", text: "decision_id is required" }] };
|
|
2637
|
+
const title = args?.title;
|
|
2638
|
+
const description = args?.description;
|
|
2639
|
+
const category = args?.category;
|
|
2640
|
+
if (!title?.trim() && !description?.trim() && !category) {
|
|
2641
|
+
return { content: [{ type: "text", text: "Nothing to amend \u2014 pass title, description, and/or category" }] };
|
|
2642
|
+
}
|
|
2643
|
+
const { decisions } = await listDecisions(config);
|
|
2644
|
+
const match = decisions.find((d) => d.id.startsWith(decisionId));
|
|
2645
|
+
if (!match) return { content: [{ type: "text", text: `No decision found matching "${decisionId}"` }] };
|
|
2646
|
+
if (match.retracted_at) {
|
|
2647
|
+
return { content: [{ type: "text", text: `"${match.title}" has been retracted and can't be amended.` }] };
|
|
2648
|
+
}
|
|
2649
|
+
try {
|
|
2650
|
+
const updated = await amendDecision(config, match.id, {
|
|
2651
|
+
...title?.trim() ? { title: title.trim() } : {},
|
|
2652
|
+
...description?.trim() ? { description: description.trim() } : {},
|
|
2653
|
+
...category ? { category } : {}
|
|
2654
|
+
});
|
|
2655
|
+
return { content: [{ type: "text", text: `\u2713 Decision amended: ${updated.title}` }] };
|
|
2656
|
+
} catch {
|
|
2657
|
+
return { content: [{ type: "text", text: "Failed to amend decision" }] };
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
case "retract_decision": {
|
|
2661
|
+
const decisionId = args?.decision_id;
|
|
2662
|
+
if (!decisionId) return { content: [{ type: "text", text: "decision_id is required" }] };
|
|
2663
|
+
const { decisions } = await listDecisions(config);
|
|
2664
|
+
const match = decisions.find((d) => d.id.startsWith(decisionId));
|
|
2665
|
+
if (!match) return { content: [{ type: "text", text: `No decision found matching "${decisionId}"` }] };
|
|
2666
|
+
if (match.retracted_at) {
|
|
2667
|
+
return { content: [{ type: "text", text: `"${match.title}" is already retracted.` }] };
|
|
2668
|
+
}
|
|
2669
|
+
try {
|
|
2670
|
+
await retractDecision(config, match.id, args?.reason);
|
|
2671
|
+
return { content: [{ type: "text", text: `\u2713 Decision retracted: ${match.title}` }] };
|
|
2672
|
+
} catch {
|
|
2673
|
+
return { content: [{ type: "text", text: "Failed to retract decision" }] };
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2455
2676
|
case "add_note": {
|
|
2456
2677
|
const storyId = args?.story_id;
|
|
2457
2678
|
const note = args?.note;
|
|
@@ -2459,7 +2680,7 @@ Decision needed: ${decisionNeeded}`
|
|
|
2459
2680
|
return { content: [{ type: "text", text: "story_id and note required" }] };
|
|
2460
2681
|
}
|
|
2461
2682
|
const data = await listStories(config);
|
|
2462
|
-
const match = data.stories.find((s) => s
|
|
2683
|
+
const match = data.stories.find((s) => matchesStoryRef(s, storyId));
|
|
2463
2684
|
if (!match) {
|
|
2464
2685
|
return { content: [{ type: "text", text: `No story found matching "${storyId}"` }] };
|
|
2465
2686
|
}
|
|
@@ -2481,7 +2702,7 @@ Decision needed: ${decisionNeeded}`
|
|
|
2481
2702
|
}
|
|
2482
2703
|
const res = await fetch(apiUrl(config, "/complexity"), {
|
|
2483
2704
|
method: "POST",
|
|
2484
|
-
headers: { "Content-Type": "application/json" },
|
|
2705
|
+
headers: syncHeaders({ "Content-Type": "application/json" }),
|
|
2485
2706
|
body: JSON.stringify(body)
|
|
2486
2707
|
});
|
|
2487
2708
|
if (!res.ok) {
|
|
@@ -2498,7 +2719,7 @@ Decision needed: ${decisionNeeded}`
|
|
|
2498
2719
|
if (!query) return { content: [{ type: "text", text: "query is required" }] };
|
|
2499
2720
|
const res = await fetch(apiUrl(config, "/research"), {
|
|
2500
2721
|
method: "POST",
|
|
2501
|
-
headers: { "Content-Type": "application/json" },
|
|
2722
|
+
headers: syncHeaders({ "Content-Type": "application/json" }),
|
|
2502
2723
|
body: JSON.stringify({ query })
|
|
2503
2724
|
});
|
|
2504
2725
|
if (!res.ok) {
|
|
@@ -2515,7 +2736,7 @@ Decision needed: ${decisionNeeded}`
|
|
|
2515
2736
|
if (!prd) return { content: [{ type: "text", text: "prd text is required" }] };
|
|
2516
2737
|
const res = await fetch(apiUrl(config, "/import"), {
|
|
2517
2738
|
method: "POST",
|
|
2518
|
-
headers: { "Content-Type": "application/json" },
|
|
2739
|
+
headers: syncHeaders({ "Content-Type": "application/json" }),
|
|
2519
2740
|
body: JSON.stringify({ prd, format: args?.format ?? "PRD" })
|
|
2520
2741
|
});
|
|
2521
2742
|
if (!res.ok) {
|
|
@@ -2625,7 +2846,7 @@ async function startMcpServer(mode = "all") {
|
|
|
2625
2846
|
};
|
|
2626
2847
|
}
|
|
2627
2848
|
if (uri === "intentdocs://project/decisions") {
|
|
2628
|
-
const res = await fetch(apiUrl(config, "/decisions"));
|
|
2849
|
+
const res = await fetch(apiUrl(config, "/decisions"), { headers: syncHeaders() });
|
|
2629
2850
|
if (!res.ok) {
|
|
2630
2851
|
const text2 = JSON.stringify({ note: "Failed to fetch decisions" });
|
|
2631
2852
|
return { contents: [{ uri, mimeType: "application/json", text: text2 }] };
|
|
@@ -2637,7 +2858,7 @@ async function startMcpServer(mode = "all") {
|
|
|
2637
2858
|
};
|
|
2638
2859
|
}
|
|
2639
2860
|
if (uri === "intentdocs://project/data-model") {
|
|
2640
|
-
const res = await fetch(apiUrl(config, "/data-model"));
|
|
2861
|
+
const res = await fetch(apiUrl(config, "/data-model"), { headers: syncHeaders() });
|
|
2641
2862
|
let data;
|
|
2642
2863
|
if (res.status === 404) {
|
|
2643
2864
|
data = { note: "No data model defined yet. Run the interview or add one via the web UI." };
|
|
@@ -2652,7 +2873,7 @@ async function startMcpServer(mode = "all") {
|
|
|
2652
2873
|
};
|
|
2653
2874
|
}
|
|
2654
2875
|
if (uri === "intentdocs://project/build-order") {
|
|
2655
|
-
const res = await fetch(apiUrl(config, "/build-order"));
|
|
2876
|
+
const res = await fetch(apiUrl(config, "/build-order"), { headers: syncHeaders() });
|
|
2656
2877
|
let data;
|
|
2657
2878
|
if (res.status === 404) {
|
|
2658
2879
|
data = { note: "No build order defined yet." };
|
|
@@ -2698,7 +2919,7 @@ async function startMcpServer(mode = "all") {
|
|
|
2698
2919
|
import { execSync as execSync3 } from "child_process";
|
|
2699
2920
|
async function openclawRegister() {
|
|
2700
2921
|
if (!hasConfig()) {
|
|
2701
|
-
console.error("Not initialised. Run:
|
|
2922
|
+
console.error("Not initialised. Run: npx intentdocs init");
|
|
2702
2923
|
process.exit(1);
|
|
2703
2924
|
}
|
|
2704
2925
|
const config = readConfig();
|
|
@@ -2737,18 +2958,18 @@ Registering intentdocs MCP with OpenClaw...`);
|
|
|
2737
2958
|
var require2 = createRequire2(import.meta.url);
|
|
2738
2959
|
var { version } = require2("../package.json");
|
|
2739
2960
|
var program = new Command();
|
|
2740
|
-
program.name("
|
|
2961
|
+
program.name("intentdocs").description("Write specs, map stories, ship with AI \u2014 from your terminal").version(version);
|
|
2741
2962
|
program.command("login").description("Log in to intentdocs (opens browser)").action(() => void login());
|
|
2742
2963
|
program.command("logout").description("Log out of intentdocs").action(() => void logout());
|
|
2743
2964
|
var keys = program.command("keys").description("Manage your Anthropic API key \u2014 unlock AI features on any plan");
|
|
2744
2965
|
keys.command("set [key]").description("Save your Anthropic API key (prompts if omitted)").action((key) => {
|
|
2745
|
-
import("./keys-
|
|
2966
|
+
import("./keys-UVXCCWCS.js").then((m) => void m.keysSet(key));
|
|
2746
2967
|
});
|
|
2747
2968
|
keys.command("status").description("Show whether a key is set (masked)").action(() => {
|
|
2748
|
-
import("./keys-
|
|
2969
|
+
import("./keys-UVXCCWCS.js").then((m) => void m.keysStatus());
|
|
2749
2970
|
});
|
|
2750
2971
|
keys.command("remove").description("Remove your stored key").action(() => {
|
|
2751
|
-
import("./keys-
|
|
2972
|
+
import("./keys-UVXCCWCS.js").then((m) => void m.keysRemove());
|
|
2752
2973
|
});
|
|
2753
2974
|
program.command("start").description("Start a new project \u2014 guided flow from problem to pitch").action(() => void start());
|
|
2754
2975
|
program.command("scope [projectId]").description("Scope a project \u2014 generate blueprint, story map, and mockup").action((id) => void scope(id));
|
|
@@ -2761,12 +2982,12 @@ program.command("templates [name]").description("Browse story map templates").ac
|
|
|
2761
2982
|
console.log(` ${key.padEnd(20)} ${tmpl.description}`);
|
|
2762
2983
|
}
|
|
2763
2984
|
console.log(`
|
|
2764
|
-
Usage:
|
|
2985
|
+
Usage: intentdocs templates <name>
|
|
2765
2986
|
`);
|
|
2766
2987
|
} else {
|
|
2767
2988
|
const tmpl = templates[name];
|
|
2768
2989
|
if (!tmpl) {
|
|
2769
|
-
console.error(`Unknown template: "${name}". Run \`
|
|
2990
|
+
console.error(`Unknown template: "${name}". Run \`intentdocs templates\` to list.`);
|
|
2770
2991
|
} else {
|
|
2771
2992
|
console.log(`
|
|
2772
2993
|
${tmpl.name}: ${tmpl.description}`);
|
|
@@ -2779,8 +3000,12 @@ program.command("templates [name]").description("Browse story map templates").ac
|
|
|
2779
3000
|
}
|
|
2780
3001
|
}
|
|
2781
3002
|
});
|
|
3003
|
+
program.command("projects").description("List the projects your token can reach \u2014 the whole workspace it is bound to").action(() => {
|
|
3004
|
+
import("./projects-AYAYVFKL.js").then((m) => void m.projects());
|
|
3005
|
+
});
|
|
2782
3006
|
program.command("init [token]").description("Connect this project to a intentdocs story map").action((token) => void init(token));
|
|
2783
3007
|
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));
|
|
3008
|
+
program.command("ci").description("Set up CI test reporting \u2014 create a key, add the workflow, store the secret").option("--status", "Show how the last CI run's results matched your saved tests").option("--new", "Create a new CI key even if one already exists (rotate)").action((opts) => void ci(opts));
|
|
2784
3009
|
program.command("status").description("Show story map progress").action(() => void status());
|
|
2785
3010
|
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));
|
|
2786
3011
|
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));
|
|
@@ -2790,32 +3015,48 @@ program.command("show <id>").description("Show the full spec for a story (accept
|
|
|
2790
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));
|
|
2791
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));
|
|
2792
3017
|
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));
|
|
2793
|
-
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) => {
|
|
2794
|
-
import("./log-
|
|
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-XIH4BGFL.js").then((m) => void m.logDecision(title, opts));
|
|
3020
|
+
});
|
|
3021
|
+
decide.command("list").description("List logged decisions (id, category, title)").action(() => {
|
|
3022
|
+
import("./log-XIH4BGFL.js").then((m) => void m.listLoggedDecisions());
|
|
3023
|
+
});
|
|
3024
|
+
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-XIH4BGFL.js").then((m) => void m.amendDecision(id, opts));
|
|
3026
|
+
});
|
|
3027
|
+
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-XIH4BGFL.js").then((m) => void m.deleteDecision(id, opts));
|
|
2795
3029
|
});
|
|
2796
3030
|
program.command("note <storyId> [note]").description("Add implementation notes to a story").action((storyId, note) => {
|
|
2797
|
-
import("./log-
|
|
3031
|
+
import("./log-XIH4BGFL.js").then((m) => void m.logNote(storyId, note));
|
|
2798
3032
|
});
|
|
2799
3033
|
program.command("example").description("Add a few-shot reference pattern (before/after code) to the project").action(() => void example());
|
|
2800
3034
|
program.command("complexity [storyId]").description("Analyze story complexity with AI \u2014 scores, risks, effort estimates").action((id) => {
|
|
2801
|
-
import("./complexity-
|
|
3035
|
+
import("./complexity-X3IJKNNV.js").then((m) => void m.complexity(id));
|
|
2802
3036
|
});
|
|
2803
3037
|
program.command("research [query]").description("Research a topic with AI using your project context").action((query) => {
|
|
2804
|
-
import("./research-
|
|
3038
|
+
import("./research-CO3W7PLC.js").then((m) => void m.research(query));
|
|
2805
3039
|
});
|
|
2806
3040
|
program.command("import [file]").description("Import a PRD or spec into your story map as stories").action((file) => {
|
|
2807
|
-
import("./import-prd-
|
|
3041
|
+
import("./import-prd-IJTM6FZE.js").then((m) => void m.importPrd(file));
|
|
2808
3042
|
});
|
|
2809
3043
|
var map = program.command("map").description("Import or extract story maps from codebases");
|
|
2810
3044
|
map.command("extract").description("Output a prompt for your coding agent to extract a story map from an existing codebase").action(() => {
|
|
2811
|
-
import("./map-extract-
|
|
3045
|
+
import("./map-extract-V6D27I3H.js").then((m) => void m.mapExtract());
|
|
2812
3046
|
});
|
|
2813
3047
|
map.command("import <file>").description("Import a storymap.json file generated by your coding agent").option("--yes", "Skip confirmation prompt").action((file, opts) => {
|
|
2814
|
-
import("./map-import-
|
|
3048
|
+
import("./map-import-LORTI4K4.js").then((m) => void m.mapImport(file, opts));
|
|
2815
3049
|
});
|
|
2816
3050
|
map.command("schema").description("Print the JSON Schema for storymap.json").action(() => {
|
|
2817
|
-
import("./map-schema-
|
|
3051
|
+
import("./map-schema-5BEIJM2J.js").then((m) => void m.mapSchema());
|
|
2818
3052
|
});
|
|
2819
3053
|
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));
|
|
2820
3054
|
program.command("openclaw-register").description("Register this project with OpenClaw MCP \u2014 gives spawned coding agents access to your story map").action(() => void openclawRegister());
|
|
3055
|
+
var invokedAs = process.argv[1]?.split("/").pop()?.replace(/\.js$/, "") ?? "";
|
|
3056
|
+
if (invokedAs === "intent" && !process.argv.includes("--quiet")) {
|
|
3057
|
+
const args = process.argv.slice(2);
|
|
3058
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
3059
|
+
console.log("\n Tip: the canonical command is `intentdocs`. `intent` also works but may collide with other tools.\n");
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
2821
3062
|
program.parse();
|