intentdocs 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/chunk-64I7RLP3.js +171 -0
- package/dist/{chunk-IDNUBI2P.js → chunk-65EDM7QH.js} +6 -6
- package/dist/{chunk-NFIXRIHM.js → chunk-BNEU6TLJ.js} +55 -21
- package/dist/chunk-GFKZ5CRL.js +12 -0
- package/dist/{chunk-5USVG4DW.js → chunk-ID4B5RZ2.js} +42 -21
- package/dist/{chunk-EEKEF2FD.js → chunk-IORMKLAQ.js} +178 -121
- package/dist/{chunk-UF535HZL.js → chunk-K5SNF6CG.js} +13 -2
- package/dist/{chunk-HEHHSSUM.js → chunk-KPQ42YE6.js} +1 -1
- package/dist/{chunk-YUW3W43S.js → chunk-KTGQO3AE.js} +31 -8
- package/dist/{chunk-UR73WX5I.js → chunk-UQDMUKLP.js} +2 -2
- package/dist/{chunk-LXQXJULN.js → chunk-XZE2XP6O.js} +3 -3
- package/dist/ci-NOCV3XUV.js +11 -0
- package/dist/{complexity-ZOB3BPVD.js → complexity-OJVM37NF.js} +4 -4
- package/dist/{import-prd-MJUCIMHX.js → import-prd-5FCVXCYO.js} +14 -7
- package/dist/index.js +377 -359
- package/dist/{keys-5PA3D6CX.js → keys-2W5V56FS.js} +32 -11
- package/dist/{log-EVLWM64C.js → log-DFCCAAJN.js} +23 -7
- package/dist/{map-extract-W6WJLVOF.js → map-extract-YUCFLGJS.js} +2 -2
- package/dist/{map-import-LL4AIAQE.js → map-import-7RN2NEUZ.js} +7 -6
- package/dist/{map-schema-6GL2UKZQ.js → map-schema-5BEIJM2J.js} +1 -1
- package/dist/{projects-F7GMH66U.js → projects-AYAYVFKL.js} +2 -2
- package/dist/push-33V7ANJW.js +462 -0
- package/dist/{research-4ASCZSZN.js → research-SHO5SXVQ.js} +4 -4
- package/dist/scope-RRGJHS2G.js +13 -0
- package/dist/setup-UILQVYIB.js +14 -0
- package/package.json +6 -5
- package/dist/ci-LBOCMRKZ.js +0 -11
- package/dist/scope-S3LZZ3KB.js +0 -11
- package/dist/setup-YLUPBLK6.js +0 -14
package/dist/index.js
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
appendToNotes
|
|
4
|
+
} from "./chunk-GFKZ5CRL.js";
|
|
2
5
|
import {
|
|
3
6
|
UPGRADE_MESSAGE_PLAIN,
|
|
4
7
|
isPlanGated
|
|
5
|
-
} from "./chunk-
|
|
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-HEHHSSUM.js";
|
|
17
|
-
import {
|
|
18
|
-
scope
|
|
19
|
-
} from "./chunk-5USVG4DW.js";
|
|
15
|
+
} from "./chunk-IORMKLAQ.js";
|
|
20
16
|
import {
|
|
21
17
|
criterionPriority,
|
|
22
18
|
criterionStatusLabel,
|
|
@@ -24,11 +20,22 @@ import {
|
|
|
24
20
|
formatDesignRefLine,
|
|
25
21
|
storyContextPath,
|
|
26
22
|
writeStoryContext
|
|
27
|
-
} from "./chunk-
|
|
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,13 +67,9 @@ import {
|
|
|
59
67
|
} from "./chunk-WRRUCCVZ.js";
|
|
60
68
|
import {
|
|
61
69
|
authenticatedFetch,
|
|
62
|
-
bindingLabel,
|
|
63
|
-
clearAuth,
|
|
64
70
|
getAuth,
|
|
65
|
-
requireValidAuth
|
|
66
|
-
|
|
67
|
-
validateAuth
|
|
68
|
-
} from "./chunk-LXQXJULN.js";
|
|
71
|
+
requireValidAuth
|
|
72
|
+
} from "./chunk-XZE2XP6O.js";
|
|
69
73
|
import {
|
|
70
74
|
API_BASE
|
|
71
75
|
} from "./chunk-UYTVTYNB.js";
|
|
@@ -75,168 +79,96 @@ import { Command } from "commander";
|
|
|
75
79
|
import { createRequire as createRequire2 } from "module";
|
|
76
80
|
|
|
77
81
|
// src/commands/init.ts
|
|
78
|
-
import { writeFileSync, existsSync, readFileSync, appendFileSync, statSync } from "fs";
|
|
79
|
-
import { join } from "path";
|
|
82
|
+
import { writeFileSync as writeFileSync2, existsSync as existsSync2, readFileSync as readFileSync2, appendFileSync, statSync } from "fs";
|
|
83
|
+
import { join as join2 } from "path";
|
|
80
84
|
import { createInterface } from "readline";
|
|
81
85
|
|
|
82
|
-
// src/
|
|
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: intentdocs init");
|
|
101
|
-
console.log(
|
|
102
|
-
"Optional: add your Anthropic API key to unlock AI features on any plan \u2014 intentdocs keys set"
|
|
103
|
-
);
|
|
104
122
|
}
|
|
105
|
-
function
|
|
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 `intentdocs login` to start a new one.");
|
|
175
|
-
process.exit(1);
|
|
176
|
-
}
|
|
177
|
-
} catch {
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (phase === "initial") {
|
|
181
|
-
console.log("\n\nThe initial wait has ended. The browser approval hasn't come through yet.");
|
|
182
|
-
console.log("This can happen if you needed to create an account or got distracted.\n");
|
|
183
|
-
const rl = await import("readline");
|
|
184
|
-
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
185
|
-
const answer = await new Promise(
|
|
186
|
-
(resolve) => iface.question("Keep waiting? (y/n): ", resolve)
|
|
187
|
-
);
|
|
188
|
-
iface.close();
|
|
189
|
-
if (answer.trim().toLowerCase().startsWith("y")) {
|
|
190
|
-
phase = "extended";
|
|
191
|
-
timeoutS = totalWaitedS + EXTEND_TIMEOUT_S;
|
|
192
|
-
console.log(`
|
|
193
|
-
Waiting ${EXTEND_TIMEOUT_S / 60} more minutes...`);
|
|
194
|
-
while (totalWaitedS < timeoutS) {
|
|
195
|
-
const minutesLeft = Math.ceil((timeoutS - totalWaitedS) / 60);
|
|
196
|
-
if (Math.floor(totalWaitedS) % 30 === 0 && totalWaitedS > INITIAL_TIMEOUT_S) {
|
|
197
|
-
process.stdout.write(`
|
|
198
|
-
Still waiting (${minutesLeft}m remaining)...`);
|
|
199
|
-
} else {
|
|
200
|
-
process.stdout.write(".");
|
|
201
|
-
}
|
|
202
|
-
await sleep(POLL_INTERVAL_MS);
|
|
203
|
-
totalWaitedS += POLL_INTERVAL_MS / 1e3;
|
|
204
|
-
try {
|
|
205
|
-
const checkRes = await fetch(`${API_BASE}/api/cli/auth?code=${code}`);
|
|
206
|
-
if (!checkRes.ok) continue;
|
|
207
|
-
const data = await checkRes.json();
|
|
208
|
-
if (data.status === "approved" && data.token) {
|
|
209
|
-
finishLogin(data);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
if (data.status === "expired") {
|
|
213
|
-
console.error("\n\nThe server-side login request expired. Run `intentdocs login` to start a new one.");
|
|
214
|
-
process.exit(1);
|
|
215
|
-
}
|
|
216
|
-
} catch {
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
console.error("\n\nLogin timed out. Run `intentdocs login` to try again.");
|
|
222
|
-
console.error("If the browser page is still open, complete it and re-run the command \u2014 your approval will still work within 10 minutes.\n");
|
|
223
|
-
process.exit(1);
|
|
136
|
+
function allowRules(serverNames, tools = ALLOWED_TOOLS) {
|
|
137
|
+
return serverNames.flatMap((server) => tools.map((tool) => `mcp__${server}__${tool}`));
|
|
224
138
|
}
|
|
225
|
-
|
|
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 };
|
|
153
|
+
}
|
|
154
|
+
function describeAllowList(serverNames) {
|
|
155
|
+
return [
|
|
156
|
+
` Allow these IntentDocs tools without asking, in .claude/settings.json (server: ${serverNames.join(", ")})?`,
|
|
157
|
+
"",
|
|
158
|
+
` Reads: ${READ_TOOLS.join(", ")}`,
|
|
159
|
+
` Build loop: ${BUILD_LOOP_TOOLS.join(", ")}`,
|
|
160
|
+
"",
|
|
161
|
+
` Still asks: ${STILL_ASKS.join(", ")} \u2014 anything that changes the spec or cancels work.`
|
|
162
|
+
].join("\n");
|
|
236
163
|
}
|
|
164
|
+
var CURSOR_ALLOW_NOTE = [
|
|
165
|
+
" Cursor: in its settings, find the agent's auto-run options and allow these",
|
|
166
|
+
` intentdocs MCP tools: ${ALLOWED_TOOLS.join(", ")}.`,
|
|
167
|
+
` Leave ${STILL_ASKS.join(", ")} asking.`
|
|
168
|
+
].join("\n");
|
|
237
169
|
|
|
238
170
|
// src/commands/init.ts
|
|
239
|
-
var INTENTDOCS_CLAUDE_BLOCK = "\n\n# intentdocs\nRead INTENT.md for the product story map, personas, and backlog.\
|
|
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,24 +199,24 @@ function detectStack(cwd) {
|
|
|
267
199
|
if (deps["express"]) return "Node.js (Express, TypeScript)";
|
|
268
200
|
return "Node.js (TypeScript)";
|
|
269
201
|
}
|
|
270
|
-
if (
|
|
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 "";
|
|
280
212
|
}
|
|
281
213
|
async function init(tokenArg) {
|
|
282
214
|
if (hasConfig()) {
|
|
283
|
-
console.log("Already initialized. Run `intentdocs sync` to refresh.");
|
|
215
|
+
console.log("Already initialized. Run `npx intentdocs sync` to refresh.");
|
|
284
216
|
return;
|
|
285
217
|
}
|
|
286
218
|
if (tokenArg) {
|
|
287
|
-
console.error("Sync tokens have been retired \u2014 `intentdocs init` no longer takes one. Run `intentdocs login`, then `intentdocs init` and pick the project and map.");
|
|
219
|
+
console.error("Sync tokens have been retired \u2014 `npx intentdocs init` no longer takes one. Run `npx intentdocs login`, then `npx intentdocs init` and pick the project and map.");
|
|
288
220
|
process.exit(1);
|
|
289
221
|
}
|
|
290
222
|
let auth = getAuth();
|
|
@@ -332,14 +264,7 @@ ${whereIAm}`);
|
|
|
332
264
|
Story maps in "${project.name}":
|
|
333
265
|
`);
|
|
334
266
|
const map2 = await pickFromList(rl, maps, (m) => m.title, "Select story map");
|
|
335
|
-
const config = {
|
|
336
|
-
apiBase: auth.apiBase,
|
|
337
|
-
mapTitle: map2.title,
|
|
338
|
-
projectId: project.id,
|
|
339
|
-
mapId: map2.id,
|
|
340
|
-
workspace: project.workspace ? { orgId: project.workspace.orgId, name: project.workspace.name } : { orgId: null, name: null },
|
|
341
|
-
...appUrl && { appUrl }
|
|
342
|
-
};
|
|
267
|
+
const config = checkoutConfig({ apiBase: auth.apiBase, project, map: map2, appUrl });
|
|
343
268
|
console.log(`
|
|
344
269
|
Connecting to "${map2.title}"${project.workspace ? ` in ${project.workspace.name ?? "its workspace"}` : ""}...`);
|
|
345
270
|
try {
|
|
@@ -356,8 +281,8 @@ Connecting to "${map2.title}"${project.workspace ? ` in ${project.workspace.name
|
|
|
356
281
|
writeConfig(config);
|
|
357
282
|
await setupFiles(config);
|
|
358
283
|
await setupStack(rl);
|
|
359
|
-
const specPath =
|
|
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
|
{
|
|
@@ -370,28 +295,48 @@ Connecting to "${map2.title}"${project.workspace ? ` in ${project.workspace.name
|
|
|
370
295
|
console.log(`
|
|
371
296
|
\u2705 Ready! Next steps:
|
|
372
297
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
298
|
+
npx intentdocs status \u2014 see your story map
|
|
299
|
+
npx intentdocs next \u2014 pick up the first story
|
|
300
|
+
npx intentdocs setup \u2014 configure Claude Code / Cursor slash commands
|
|
301
|
+
npx intentdocs ci \u2014 report your tests' results from CI to each story
|
|
302
|
+
npx intentdocs propose \u2014 plan a new feature from the terminal
|
|
378
303
|
`);
|
|
304
|
+
await offerAllowList((q) => ask2(rl, q));
|
|
379
305
|
const setUpCi = await ask2(rl, " Set up CI test reporting now? It creates a key and adds the workflow. (y/N): ");
|
|
380
306
|
if (/^y(es)?$/i.test(setUpCi.trim())) {
|
|
381
|
-
const { ci: ci2 } = await import("./ci-
|
|
307
|
+
const { ci: ci2 } = await import("./ci-NOCV3XUV.js");
|
|
382
308
|
await ci2();
|
|
383
309
|
} else {
|
|
384
|
-
console.log(" You can run `
|
|
310
|
+
console.log(" You can run `npx intentdocs ci` any time to turn it on.\n");
|
|
385
311
|
}
|
|
386
312
|
} finally {
|
|
387
313
|
rl.close();
|
|
388
314
|
await shutdownCliAnalytics();
|
|
389
315
|
}
|
|
390
316
|
}
|
|
317
|
+
async function offerAllowList(askFn, cwd = process.cwd(), home) {
|
|
318
|
+
const servers = detectServerNames(cwd, home);
|
|
319
|
+
console.log(`
|
|
320
|
+
${describeAllowList(servers)}
|
|
321
|
+
`);
|
|
322
|
+
const answer = await askFn(" Add them? (y/N): ");
|
|
323
|
+
if (!/^y(es)?$/i.test(answer.trim())) {
|
|
324
|
+
console.log(" Left as is \u2014 Claude Code will keep asking before each call.\n");
|
|
325
|
+
console.log(`${CURSOR_ALLOW_NOTE}
|
|
326
|
+
`);
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
const { path, added } = writeProjectAllowList(cwd, allowRules(servers));
|
|
330
|
+
console.log(` \u2713 Allowed ${added} ${added === 1 ? "tool" : "tools"} in ${path.replace(cwd + "/", "")}
|
|
331
|
+
`);
|
|
332
|
+
console.log(`${CURSOR_ALLOW_NOTE}
|
|
333
|
+
`);
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
391
336
|
async function setupStack(rl) {
|
|
392
337
|
const cwd = process.cwd();
|
|
393
|
-
const archPath =
|
|
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) {
|
|
@@ -728,10 +668,10 @@ async function next(opts = {}) {
|
|
|
728
668
|
`);
|
|
729
669
|
for (const ip of inProgress) {
|
|
730
670
|
console.log(` \u{1F528} ${ip.title}`);
|
|
731
|
-
console.log(` ID: ${ip.id.slice(0, 8)} | Mark done:
|
|
671
|
+
console.log(` ID: ${ip.id.slice(0, 8)} | Mark done: npx intentdocs done ${ip.id.slice(0, 8)}`);
|
|
732
672
|
}
|
|
733
673
|
console.log(`
|
|
734
|
-
Finish those first, or run \`intentdocs next --skip-wip\` to pick the next todo anyway.
|
|
674
|
+
Finish those first, or run \`npx intentdocs next --skip-wip\` to pick the next todo anyway.
|
|
735
675
|
`);
|
|
736
676
|
return;
|
|
737
677
|
}
|
|
@@ -759,12 +699,12 @@ async function next(opts = {}) {
|
|
|
759
699
|
if (unsequencedCount > 0) {
|
|
760
700
|
console.log(`
|
|
761
701
|
\u{1F512} ${unsequencedCount} stor${unsequencedCount === 1 ? "y is" : "ies are"} unsequenced \u2014 agents only build stories you've deliberately ordered.`);
|
|
762
|
-
console.log(` Sequence ${unsequencedCount === 1 ? "it" : "them"} on the Plan view, then run \`intentdocs next\` again.
|
|
702
|
+
console.log(` Sequence ${unsequencedCount === 1 ? "it" : "them"} on the Plan view, then run \`npx intentdocs next\` again.
|
|
763
703
|
`);
|
|
764
704
|
} else if (blockedCount > 0) {
|
|
765
705
|
console.log(`
|
|
766
706
|
\u{1F512} ${blockedCount} stories remain but all have unfinished dependencies.`);
|
|
767
|
-
console.log(` Finish in-progress stories first, then run \`intentdocs next\` again.
|
|
707
|
+
console.log(` Finish in-progress stories first, then run \`npx intentdocs next\` again.
|
|
768
708
|
`);
|
|
769
709
|
} else {
|
|
770
710
|
console.log("\n \u{1F389} All stories are done! Ship it.\n");
|
|
@@ -794,14 +734,14 @@ async function next(opts = {}) {
|
|
|
794
734
|
console.log(` Plan: \u2705 Implementation plan available`);
|
|
795
735
|
}
|
|
796
736
|
console.log(`
|
|
797
|
-
Start building:
|
|
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 \`intentdocs next --stub\` \u2014 copy into the criterion's command field, or run \`intentdocs check\` once wired up._`, ``];
|
|
859
|
-
console.log();
|
|
860
|
-
for (const suggestion of suggestions) {
|
|
861
|
-
const ac = candidates[suggestion.index - 1];
|
|
862
|
-
if (!ac) continue;
|
|
863
|
-
if (suggestion.executable && suggestion.command) {
|
|
864
|
-
console.log(` \u2192 ${ac.text}`);
|
|
865
|
-
console.log(` command: ${suggestion.command}${suggestion.expected ? ` (expect: ${suggestion.expected})` : ""}`);
|
|
866
|
-
lines.push(`- **${ac.text}**`, ` - command: \`${suggestion.command}\``, ...suggestion.expected ? [` - expected: ${suggestion.expected}`] : [], ``);
|
|
867
|
-
} else {
|
|
868
|
-
console.log(` \u2192 ${ac.text}`);
|
|
869
|
-
console.log(` not executable${suggestion.reason ? `: ${suggestion.reason}` : ""}`);
|
|
870
|
-
lines.push(`- **${ac.text}**`, ` - not executable${suggestion.reason ? `: ${suggestion.reason}` : ""}`, ``);
|
|
871
|
-
}
|
|
861
|
+
return null;
|
|
872
862
|
}
|
|
873
|
-
|
|
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:
|
|
897
|
+
console.log(` Mark done: npx intentdocs done ${storyRef(s)}
|
|
914
898
|
`);
|
|
915
899
|
return;
|
|
916
900
|
}
|
|
917
|
-
if (s.build_sequence == null)
|
|
918
|
-
console.log(unsequencedRefusal(s, config.apiBase, data.map.id));
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
901
|
+
if (s.build_sequence == null) console.log(unsequencedNote(s, config.apiBase, data.map.id));
|
|
921
902
|
const doneIds = new Set(
|
|
922
903
|
data.stories.filter((st) => st.status === "done").map((st) => st.id)
|
|
923
904
|
);
|
|
@@ -952,7 +933,7 @@ async function implement(storyId, opts = {}) {
|
|
|
952
933
|
}
|
|
953
934
|
console.log(` \u2502 \u2502`);
|
|
954
935
|
console.log(` \u2502 Consider: \u2502`);
|
|
955
|
-
console.log(` \u2502
|
|
936
|
+
console.log(` \u2502 npx intentdocs sync --context (refresh CLAUDE.md) \u2502`);
|
|
956
937
|
console.log(` \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
957
938
|
`);
|
|
958
939
|
const proceed = isInteractive() ? await confirm(" Assign this story and mark in progress?") : false;
|
|
@@ -976,8 +957,18 @@ async function implement(storyId, opts = {}) {
|
|
|
976
957
|
`);
|
|
977
958
|
}
|
|
978
959
|
try {
|
|
979
|
-
await updateStories(config, [{ id: s.id, status: "in_progress", cancelled_at: null }]);
|
|
980
|
-
|
|
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:
|
|
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) {
|
|
@@ -1140,7 +1148,7 @@ async function done(idPrefix, opts = {}) {
|
|
|
1140
1148
|
console.error(
|
|
1141
1149
|
` \u2717 Not marked done: "${match.title}" has unresolved 'must' criteria and no TTY is available to confirm an override.`
|
|
1142
1150
|
);
|
|
1143
|
-
console.error(` Report the criteria (
|
|
1151
|
+
console.error(` Report the criteria (npx intentdocs check / report_criteria) or re-run interactively to force it.`);
|
|
1144
1152
|
process.exit(1);
|
|
1145
1153
|
}
|
|
1146
1154
|
return;
|
|
@@ -1161,6 +1169,11 @@ async function done(idPrefix, opts = {}) {
|
|
|
1161
1169
|
if (summary) update.implementation_summary = summary;
|
|
1162
1170
|
if (files.length > 0) update.implemented_files = files;
|
|
1163
1171
|
if (screenshotUrl) update.screenshot_url = screenshotUrl;
|
|
1172
|
+
if (prUrl) {
|
|
1173
|
+
update.pr_url = prUrl;
|
|
1174
|
+
const prNumber = prNumberFromUrl(prUrl);
|
|
1175
|
+
if (prNumber) update.pr_number = prNumber;
|
|
1176
|
+
}
|
|
1164
1177
|
const result = await updateStories(config, [update]);
|
|
1165
1178
|
const verify = await listStories(config);
|
|
1166
1179
|
const persisted = verify.stories.find((s) => s.id === match.id);
|
|
@@ -1168,7 +1181,7 @@ async function done(idPrefix, opts = {}) {
|
|
|
1168
1181
|
console.error(
|
|
1169
1182
|
` \u2717 Done-mark did not persist: server reports status="${persisted?.status ?? "not found"}" for "${match.title}" after the update.`
|
|
1170
1183
|
);
|
|
1171
|
-
console.error(` Not treating this run as successful \u2014 re-run \`
|
|
1184
|
+
console.error(` Not treating this run as successful \u2014 re-run \`npx intentdocs done ${idPrefix}\` or check the sync API.`);
|
|
1172
1185
|
await shutdownCliAnalytics();
|
|
1173
1186
|
closePrompt();
|
|
1174
1187
|
process.exit(1);
|
|
@@ -1190,6 +1203,11 @@ async function done(idPrefix, opts = {}) {
|
|
|
1190
1203
|
if (summary) console.log(` \u21B3 Implementation summary posted to story map`);
|
|
1191
1204
|
if (files.length > 0) console.log(` \u21B3 ${files.length} file(s) recorded`);
|
|
1192
1205
|
if (screenshotUrl) console.log(` \u21B3 Screenshot attached`);
|
|
1206
|
+
if (prUrl) {
|
|
1207
|
+
console.log(` \u21B3 PR linked: ${prUrl}`);
|
|
1208
|
+
const stamp = result.pr_stamps?.find((p) => p.id === match.id);
|
|
1209
|
+
if (stamp?.message) console.log(` ${stamp.message.replace(/\n/g, "\n ")}`);
|
|
1210
|
+
}
|
|
1193
1211
|
for (const release of result.readyReleases ?? []) {
|
|
1194
1212
|
console.log();
|
|
1195
1213
|
console.log(`\u{1F3C1} ${release.label} is ready to close (${release.storyCount} stories done) \u2014 close it on Validate \u203A Release`);
|
|
@@ -1247,13 +1265,12 @@ async function done(idPrefix, opts = {}) {
|
|
|
1247
1265
|
async function stop(storyId) {
|
|
1248
1266
|
const config = requireConfig();
|
|
1249
1267
|
const data = await listStories(config);
|
|
1250
|
-
const match = data.stories.find(
|
|
1251
|
-
(st) => st.id === storyId || st.id.startsWith(storyId)
|
|
1252
|
-
);
|
|
1268
|
+
const match = data.stories.find((st) => matchesStoryRef(st, storyId));
|
|
1253
1269
|
if (!match) {
|
|
1254
1270
|
console.log(`
|
|
1255
|
-
\u274C No story found matching
|
|
1271
|
+
\u274C No story found matching "${storyId}"
|
|
1256
1272
|
`);
|
|
1273
|
+
process.exitCode = 1;
|
|
1257
1274
|
return;
|
|
1258
1275
|
}
|
|
1259
1276
|
if (match.status !== "in_progress") {
|
|
@@ -1320,7 +1337,7 @@ function resolveStory(stories2, idPrefix) {
|
|
|
1320
1337
|
const inProgress = stories2.filter((s) => s.status === "in_progress");
|
|
1321
1338
|
if (inProgress.length === 1) return { story: inProgress[0] };
|
|
1322
1339
|
return {
|
|
1323
|
-
error: inProgress.length === 0 ? "No story in progress. Specify one:
|
|
1340
|
+
error: inProgress.length === 0 ? "No story in progress. Specify one: npx intentdocs check <idPrefix>" : `${inProgress.length} stories in progress \u2014 specify one: npx intentdocs check <idPrefix>`
|
|
1324
1341
|
};
|
|
1325
1342
|
}
|
|
1326
1343
|
async function check(idPrefix, opts = {}) {
|
|
@@ -1466,7 +1483,7 @@ async function start() {
|
|
|
1466
1483
|
} catch (err) {
|
|
1467
1484
|
spinner.fail(" Failed to connect");
|
|
1468
1485
|
console.error(` ${err.message}`);
|
|
1469
|
-
console.error(" Make sure you're logged in: intentdocs login");
|
|
1486
|
+
console.error(" Make sure you're logged in: npx intentdocs login");
|
|
1470
1487
|
closePrompt();
|
|
1471
1488
|
return;
|
|
1472
1489
|
}
|
|
@@ -1538,14 +1555,14 @@ async function start() {
|
|
|
1538
1555
|
Failed to create project: ${data.error ?? "unknown error"}`);
|
|
1539
1556
|
}
|
|
1540
1557
|
} else {
|
|
1541
|
-
console.error("\n Failed to create project. Try logging in again: intentdocs login");
|
|
1558
|
+
console.error("\n Failed to create project. Try logging in again: npx intentdocs login");
|
|
1542
1559
|
}
|
|
1543
1560
|
closePrompt();
|
|
1544
1561
|
return;
|
|
1545
1562
|
}
|
|
1546
1563
|
const createContentType = createRes.headers.get("content-type") ?? "";
|
|
1547
1564
|
if (!createContentType.includes("application/json")) {
|
|
1548
|
-
console.error("\n Unexpected response from server. Try logging in again: intentdocs login");
|
|
1565
|
+
console.error("\n Unexpected response from server. Try logging in again: npx intentdocs login");
|
|
1549
1566
|
closePrompt();
|
|
1550
1567
|
return;
|
|
1551
1568
|
}
|
|
@@ -1609,7 +1626,7 @@ async function start() {
|
|
|
1609
1626
|
if (!readyToScope) {
|
|
1610
1627
|
console.log(`
|
|
1611
1628
|
No worries! When you're ready:
|
|
1612
|
-
\u2022 Run:
|
|
1629
|
+
\u2022 Run: npx intentdocs scope ${projectId}
|
|
1613
1630
|
\u2022 Or visit: ${API_BASE}
|
|
1614
1631
|
|
|
1615
1632
|
Your project is saved and waiting for you.
|
|
@@ -1617,17 +1634,28 @@ async function start() {
|
|
|
1617
1634
|
closePrompt();
|
|
1618
1635
|
return;
|
|
1619
1636
|
}
|
|
1620
|
-
const { scope: scope2 } = await import("./scope-
|
|
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()) {
|
|
1630
|
-
console.log(" Not connected to a project. Run `intentdocs init` first.");
|
|
1658
|
+
console.log(" Not connected to a project. Run `npx intentdocs init` first.");
|
|
1631
1659
|
closePrompt();
|
|
1632
1660
|
return;
|
|
1633
1661
|
}
|
|
@@ -1716,8 +1744,8 @@ async function propose(changeName, opts = {}) {
|
|
|
1716
1744
|
\u2717 Failed to create stories: ${err instanceof Error ? err.message : String(err)}`);
|
|
1717
1745
|
console.log(" Saving proposal locally anyway...");
|
|
1718
1746
|
}
|
|
1719
|
-
const changesDir =
|
|
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
|
|
|
@@ -1769,14 +1790,14 @@ async function propose(changeName, opts = {}) {
|
|
|
1769
1790
|
Local spec saved at: intent-changes/${slug}/proposal.md
|
|
1770
1791
|
|
|
1771
1792
|
Next:
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1793
|
+
npx intentdocs next \u2014 pick up the first story
|
|
1794
|
+
npx intentdocs status \u2014 see full progress
|
|
1795
|
+
npx intentdocs sync \u2014 refresh INTENT.md
|
|
1775
1796
|
`);
|
|
1776
1797
|
closePrompt();
|
|
1777
1798
|
}
|
|
1778
1799
|
function parseProposalFile(filePath) {
|
|
1779
|
-
const raw =
|
|
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,10 +1939,11 @@ 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
|
-
\u2705 Done! Run \`intentdocs next\` to start building.
|
|
1946
|
+
\u2705 Done! Run \`npx intentdocs next\` to start building.
|
|
1925
1947
|
`);
|
|
1926
1948
|
}
|
|
1927
1949
|
|
|
@@ -1938,7 +1960,7 @@ async function show(idPrefix) {
|
|
|
1938
1960
|
if (!story) {
|
|
1939
1961
|
console.error(`
|
|
1940
1962
|
No story found matching "${idPrefix}"
|
|
1941
|
-
Run \`intentdocs stories\` to list IDs.
|
|
1963
|
+
Run \`npx intentdocs stories\` to list IDs.
|
|
1942
1964
|
`);
|
|
1943
1965
|
process.exit(1);
|
|
1944
1966
|
}
|
|
@@ -1992,7 +2014,7 @@ async function show(idPrefix) {
|
|
|
1992
2014
|
const relPath = contextPath.replace(process.cwd() + "/", "");
|
|
1993
2015
|
console.log(`
|
|
1994
2016
|
Context : ${relPath}`);
|
|
1995
|
-
console.log(` Mark done:
|
|
2017
|
+
console.log(` Mark done: npx intentdocs done ${storyRef(story)}
|
|
1996
2018
|
`);
|
|
1997
2019
|
}
|
|
1998
2020
|
|
|
@@ -2041,7 +2063,7 @@ async function example() {
|
|
|
2041
2063
|
console.log(`
|
|
2042
2064
|
\u2713 Example saved: "${data.title}"
|
|
2043
2065
|
`);
|
|
2044
|
-
console.log(" Run `intentdocs sync --context` to regenerate your CLAUDE.md with this pattern.\n");
|
|
2066
|
+
console.log(" Run `npx intentdocs sync --context` to regenerate your CLAUDE.md with this pattern.\n");
|
|
2045
2067
|
} catch {
|
|
2046
2068
|
rl.close();
|
|
2047
2069
|
throw new Error("Aborted");
|
|
@@ -2291,7 +2313,7 @@ var TOOLS = [
|
|
|
2291
2313
|
},
|
|
2292
2314
|
{
|
|
2293
2315
|
name: "add_note",
|
|
2294
|
-
description: "Add implementation
|
|
2316
|
+
description: "Add an implementation note to a story \u2014 appended to its existing notes, never replacing them. Use this to record how something was built, gotchas, or context for future developers.",
|
|
2295
2317
|
inputSchema: {
|
|
2296
2318
|
type: "object",
|
|
2297
2319
|
properties: {
|
|
@@ -2467,16 +2489,6 @@ async function handleToolCall(name, args, config) {
|
|
|
2467
2489
|
const data = await listStories(config);
|
|
2468
2490
|
const match = data.stories.find((s) => matchesStoryRef(s, storyId));
|
|
2469
2491
|
if (!match) return { content: [{ type: "text", text: `No story found matching "${storyId}"` }] };
|
|
2470
|
-
if (match.build_sequence == null) {
|
|
2471
|
-
return {
|
|
2472
|
-
content: [{
|
|
2473
|
-
type: "text",
|
|
2474
|
-
text: `\u{1F512} "${match.title}" is unsequenced \u2014 it hasn't been ordered on the Plan view yet.
|
|
2475
|
-
|
|
2476
|
-
Agents only build stories that have been deliberately sequenced. Sequence it on the Plan view (drag it into the plan or set its build order), then retry.`
|
|
2477
|
-
}]
|
|
2478
|
-
};
|
|
2479
|
-
}
|
|
2480
2492
|
const result = await updateStories(config, [{ id: match.id, status: "in_progress" }]);
|
|
2481
2493
|
if (result.missing?.includes(match.id)) {
|
|
2482
2494
|
return {
|
|
@@ -2486,8 +2498,11 @@ Agents only build stories that have been deliberately sequenced. Sequence it on
|
|
|
2486
2498
|
}]
|
|
2487
2499
|
};
|
|
2488
2500
|
}
|
|
2501
|
+
const placed = result.sequenced?.find((x) => x.id === match.id);
|
|
2502
|
+
const note = placed ? `
|
|
2503
|
+
It wasn't in the build order, so it's been placed at the end of the build order.` : "";
|
|
2489
2504
|
return {
|
|
2490
|
-
content: [{ type: "text", text: `\u2713 ${match.title} \u2192 in_progress` }]
|
|
2505
|
+
content: [{ type: "text", text: `\u2713 ${match.title} \u2192 in_progress${note}` }]
|
|
2491
2506
|
};
|
|
2492
2507
|
}
|
|
2493
2508
|
case "mark_done": {
|
|
@@ -2687,7 +2702,7 @@ Decision needed: ${decisionNeeded}`
|
|
|
2687
2702
|
await apiFetch(config, "/stories", {
|
|
2688
2703
|
method: "PATCH",
|
|
2689
2704
|
headers: { "Content-Type": "application/json" },
|
|
2690
|
-
body: JSON.stringify({ stories: [{ id: match.id, notes: note }] })
|
|
2705
|
+
body: JSON.stringify({ stories: [{ id: match.id, notes: appendToNotes(match.notes, note) }] })
|
|
2691
2706
|
});
|
|
2692
2707
|
return { content: [{ type: "text", text: `\u2713 Note added to: ${match.title}` }] };
|
|
2693
2708
|
}
|
|
@@ -2919,7 +2934,7 @@ async function startMcpServer(mode = "all") {
|
|
|
2919
2934
|
import { execSync as execSync3 } from "child_process";
|
|
2920
2935
|
async function openclawRegister() {
|
|
2921
2936
|
if (!hasConfig()) {
|
|
2922
|
-
console.error("Not initialised. Run:
|
|
2937
|
+
console.error("Not initialised. Run: npx intentdocs init");
|
|
2923
2938
|
process.exit(1);
|
|
2924
2939
|
}
|
|
2925
2940
|
const config = readConfig();
|
|
@@ -2961,15 +2976,15 @@ var program = new Command();
|
|
|
2961
2976
|
program.name("intentdocs").description("Write specs, map stories, ship with AI \u2014 from your terminal").version(version);
|
|
2962
2977
|
program.command("login").description("Log in to intentdocs (opens browser)").action(() => void login());
|
|
2963
2978
|
program.command("logout").description("Log out of intentdocs").action(() => void logout());
|
|
2964
|
-
var keys = program.command("keys").description("Manage your Anthropic API key \u2014 unlock AI features on any plan");
|
|
2965
|
-
keys.command("set [key]").description("Save
|
|
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));
|
|
@@ -3001,7 +3016,10 @@ program.command("templates [name]").description("Browse story map templates").ac
|
|
|
3001
3016
|
}
|
|
3002
3017
|
});
|
|
3003
3018
|
program.command("projects").description("List the projects your token can reach \u2014 the whole workspace it is bound to").action(() => {
|
|
3004
|
-
import("./projects-
|
|
3019
|
+
import("./projects-AYAYVFKL.js").then((m) => void m.projects());
|
|
3020
|
+
});
|
|
3021
|
+
program.command("push").description("Upload this repo's nikOS intent/ folder: log in, create the project, and link the repo").action(() => {
|
|
3022
|
+
import("./push-33V7ANJW.js").then((m) => m.push());
|
|
3005
3023
|
});
|
|
3006
3024
|
program.command("init [token]").description("Connect this project to a intentdocs story map").action((token) => void init(token));
|
|
3007
3025
|
program.command("sync").description("Refresh INTENT.md from the live story map").option("--context", "Refresh only the CLAUDE.md/AGENTS.md context file (not stories)").action((opts) => void sync(opts));
|
|
@@ -3009,46 +3027,46 @@ program.command("ci").description("Set up CI test reporting \u2014 create a key,
|
|
|
3009
3027
|
program.command("status").description("Show story map progress").action(() => void status());
|
|
3010
3028
|
program.command("stories").description("List all stories").option("-f, --filter <filter>", "Filter by status (todo/in-progress/done) or search term").option("-s, --status <status>", "Filter by exact status: todo, in-progress, wip, done").option("-k, --kanban", "View stories as a kanban board (todo | in-progress | done)").action((opts) => void stories(opts));
|
|
3011
3029
|
program.command("next").description("Show the next highest-priority story to work on").option("-o, --open", "Open the spec page in your browser").option("--skip-wip", "Show next todo even if stories are in progress").option("--story-id <id>", "Show a specific story by ID").action((opts) => void next(opts));
|
|
3012
|
-
program.command("implement <id>").description("Pick up a story and mark it in-progress").option("--stub", "Suggest verification commands for acceptance criteria (
|
|
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
|
-
import("./map-schema-
|
|
3069
|
+
import("./map-schema-5BEIJM2J.js").then((m) => void m.mapSchema());
|
|
3052
3070
|
});
|
|
3053
3071
|
program.command("mcp").description("Start MCP server for Claude Code / Cursor integration").option("-m, --mode <mode>", "Tool mode: core, standard, all", "all").action((opts) => void startMcpServer(opts.mode));
|
|
3054
3072
|
program.command("openclaw-register").description("Register this project with OpenClaw MCP \u2014 gives spawned coding agents access to your story map").action(() => void openclawRegister());
|