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.
- package/README.md +1 -1
- package/dist/chunk-64I7RLP3.js +171 -0
- package/dist/{chunk-6X4BORMW.js → chunk-65EDM7QH.js} +1 -1
- package/dist/{chunk-F52HPVNQ.js → chunk-BNEU6TLJ.js} +54 -20
- package/dist/chunk-GFKZ5CRL.js +12 -0
- package/dist/{chunk-NHNBCNND.js → chunk-ID4B5RZ2.js} +37 -16
- package/dist/{chunk-PUGYRSMK.js → chunk-IORMKLAQ.js} +122 -65
- package/dist/{chunk-JX2RLGOM.js → chunk-K5SNF6CG.js} +11 -0
- package/dist/{chunk-34JCN66H.js → chunk-KTGQO3AE.js} +24 -1
- package/dist/{ci-XCOXFTLB.js → ci-NOCV3XUV.js} +2 -2
- package/dist/{complexity-X3IJKNNV.js → complexity-OJVM37NF.js} +2 -2
- package/dist/{import-prd-IJTM6FZE.js → import-prd-5FCVXCYO.js} +12 -5
- package/dist/index.js +344 -326
- package/dist/{keys-UVXCCWCS.js → keys-2W5V56FS.js} +30 -9
- package/dist/{log-XIH4BGFL.js → log-DFCCAAJN.js} +21 -5
- package/dist/{map-extract-V6D27I3H.js → map-extract-YUCFLGJS.js} +1 -1
- package/dist/{map-import-LORTI4K4.js → map-import-7RN2NEUZ.js} +5 -4
- package/dist/push-33V7ANJW.js +462 -0
- package/dist/{research-CO3W7PLC.js → research-SHO5SXVQ.js} +2 -2
- package/dist/scope-RRGJHS2G.js +13 -0
- package/dist/{setup-WSYIG2CR.js → setup-UILQVYIB.js} +5 -5
- package/package.json +6 -5
- package/dist/scope-ZZLKQEF5.js +0 -11
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
8
|
} from "./chunk-UQDMUKLP.js";
|
|
6
9
|
import {
|
|
7
10
|
ci
|
|
8
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-65EDM7QH.js";
|
|
9
12
|
import {
|
|
10
13
|
setup,
|
|
11
14
|
sync
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import {
|
|
14
|
-
captureCli,
|
|
15
|
-
shutdownCliAnalytics
|
|
16
|
-
} from "./chunk-KPQ42YE6.js";
|
|
17
|
-
import {
|
|
18
|
-
scope
|
|
19
|
-
} from "./chunk-NHNBCNND.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-
|
|
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-
|
|
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-
|
|
60
|
+
} from "./chunk-K5SNF6CG.js";
|
|
53
61
|
import {
|
|
54
62
|
ask,
|
|
55
63
|
closePrompt,
|
|
@@ -59,12 +67,8 @@ 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
|
|
71
|
+
requireValidAuth
|
|
68
72
|
} from "./chunk-XZE2XP6O.js";
|
|
69
73
|
import {
|
|
70
74
|
API_BASE
|
|
@@ -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/
|
|
83
|
-
import {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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: 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
122
|
}
|
|
105
|
-
function
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
110
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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 `npx 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 `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");
|
|
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
|
-
|
|
226
|
-
const {
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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 };
|
|
236
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");
|
|
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.\
|
|
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 (
|
|
258
|
-
const raw =
|
|
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,13 +199,13 @@ function detectStack(cwd) {
|
|
|
267
199
|
if (deps["express"]) return "Node.js (Express, TypeScript)";
|
|
268
200
|
return "Node.js (TypeScript)";
|
|
269
201
|
}
|
|
270
|
-
if (
|
|
271
|
-
const hasFastapi =
|
|
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 (
|
|
275
|
-
if (
|
|
276
|
-
if (
|
|
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 "";
|
|
@@ -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 =
|
|
360
|
-
const claudeMdBytes =
|
|
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
|
{
|
|
@@ -376,9 +301,10 @@ Connecting to "${map2.title}"${project.workspace ? ` in ${project.workspace.name
|
|
|
376
301
|
npx intentdocs ci \u2014 report your tests' results from CI to each story
|
|
377
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-
|
|
307
|
+
const { ci: ci2 } = await import("./ci-NOCV3XUV.js");
|
|
382
308
|
await ci2();
|
|
383
309
|
} else {
|
|
384
310
|
console.log(" You can run `npx intentdocs ci` any time to turn it on.\n");
|
|
@@ -388,10 +314,29 @@ Connecting to "${map2.title}"${project.workspace ? ` in ${project.workspace.name
|
|
|
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 =
|
|
394
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
447
|
-
if (
|
|
448
|
-
const content =
|
|
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 (
|
|
456
|
-
const claudeMd =
|
|
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(
|
|
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 (
|
|
463
|
-
const rules =
|
|
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
|
-
|
|
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
|
|
650
|
-
import { join as
|
|
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
|
|
640
|
+
function unsequencedNote(story, apiBase, mapId) {
|
|
696
641
|
const planUrl = `${apiBase}/storymapper/${mapId}?phase=plan&story=${story.id}`;
|
|
697
642
|
return `
|
|
698
|
-
\
|
|
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) {
|
|
@@ -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: npx intentdocs implement ${s
|
|
737
|
+
Start building: npx intentdocs implement ${storyRef(s)}
|
|
798
738
|
`);
|
|
799
739
|
if (opts.open) {
|
|
800
|
-
|
|
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
|
-
|
|
818
|
-
|
|
819
|
-
|
|
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
|
-
|
|
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 \`npx intentdocs next --stub\` \u2014 copy into the criterion's command field, or run \`npx 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
|
-
|
|
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
|
|
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
|
|
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: npx intentdocs done ${s
|
|
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
|
);
|
|
@@ -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
|
-
|
|
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: npx intentdocs done ${s
|
|
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
|
|
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
|
|
1017
|
-
import { extname, join as
|
|
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(
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
1084
|
-
const content =
|
|
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) {
|
|
@@ -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);
|
|
@@ -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
|
|
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") {
|
|
@@ -1617,13 +1634,24 @@ async function start() {
|
|
|
1617
1634
|
closePrompt();
|
|
1618
1635
|
return;
|
|
1619
1636
|
}
|
|
1620
|
-
const { scope: scope2 } = await import("./scope-
|
|
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
|
|
1626
|
-
import { join as
|
|
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()) {
|
|
@@ -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 =
|
|
1720
|
-
if (!
|
|
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
|
-
|
|
1783
|
+
writeFileSync4(join5(changesDir, "proposal.md"), proposalMd);
|
|
1756
1784
|
console.log(` \u2713 Saved intent-changes/${slug}/proposal.md`);
|
|
1757
|
-
|
|
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
|
|
|
@@ -1776,7 +1797,7 @@ async function propose(changeName, opts = {}) {
|
|
|
1776
1797
|
closePrompt();
|
|
1777
1798
|
}
|
|
1778
1799
|
function parseProposalFile(filePath) {
|
|
1779
|
-
const raw =
|
|
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 (!
|
|
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 =
|
|
1890
|
-
if (!
|
|
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,8 +1939,9 @@ async function proposeFromFile(filePath, config, nameOverride) {
|
|
|
1918
1939
|
].join("\n");
|
|
1919
1940
|
})
|
|
1920
1941
|
].join("\n");
|
|
1921
|
-
|
|
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
1946
|
\u2705 Done! Run \`npx intentdocs next\` to start building.
|
|
1925
1947
|
`);
|
|
@@ -2291,7 +2313,7 @@ var TOOLS = [
|
|
|
2291
2313
|
},
|
|
2292
2314
|
{
|
|
2293
2315
|
name: "add_note",
|
|
2294
|
-
description: "Add implementation
|
|
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
|
}
|
|
@@ -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
|
|
2966
|
-
import("./keys-
|
|
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-
|
|
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-
|
|
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));
|
|
@@ -3003,49 +3018,52 @@ program.command("templates [name]").description("Browse story map templates").ac
|
|
|
3003
3018
|
program.command("projects").description("List the projects your token can reach \u2014 the whole workspace it is bound to").action(() => {
|
|
3004
3019
|
import("./projects-AYAYVFKL.js").then((m) => void m.projects());
|
|
3005
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());
|
|
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));
|
|
3008
3026
|
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));
|
|
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 (
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
3046
|
+
import("./log-DFCCAAJN.js").then((m) => void m.deleteDecision(id, opts));
|
|
3029
3047
|
});
|
|
3030
|
-
program.command("note <storyId> [note]").description("Add implementation
|
|
3031
|
-
import("./log-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
3069
|
import("./map-schema-5BEIJM2J.js").then((m) => void m.mapSchema());
|