issue-flow 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/dist/{analyze-UZYJAWAP.js → analyze-7B5TORNK.js} +13 -8
- package/dist/analyze-7B5TORNK.js.map +1 -0
- package/dist/apply-WUJHDD7A.js +788 -0
- package/dist/apply-WUJHDD7A.js.map +1 -0
- package/dist/{chunk-UUY4PCWW.js → chunk-3BOHTH4V.js} +2 -2
- package/dist/chunk-3D4A4B46.js +2 -0
- package/dist/{policy-NP4ZAVF6.js → chunk-47VIN33V.js} +82 -112
- package/dist/chunk-47VIN33V.js.map +1 -0
- package/dist/{chunk-67ZO5ROQ.js → chunk-6N7LZZ5U.js} +19 -13
- package/dist/chunk-6N7LZZ5U.js.map +1 -0
- package/dist/{chunk-S67Q4I2O.js → chunk-AAY6KM7N.js} +6 -3
- package/dist/chunk-AAY6KM7N.js.map +1 -0
- package/dist/{chunk-652DJBCN.js → chunk-GANSDJV7.js} +4 -4
- package/dist/{chunk-V5OHFSW4.js → chunk-HC2MT7BX.js} +23 -3
- package/dist/chunk-HC2MT7BX.js.map +1 -0
- package/dist/chunk-HQYXVIH6.js +110 -0
- package/dist/chunk-HQYXVIH6.js.map +1 -0
- package/dist/{chunk-2LNQL4XH.js → chunk-JW7WLSZ6.js} +15 -11
- package/dist/chunk-JW7WLSZ6.js.map +1 -0
- package/dist/{chunk-VOKNTBOO.js → chunk-LVKQ2AKT.js} +3 -3
- package/dist/{chunk-EWA3OGCM.js → chunk-MKD2D7WE.js} +12 -8
- package/dist/chunk-MKD2D7WE.js.map +1 -0
- package/dist/{chunk-UH4KAJBJ.js → chunk-MSPUB635.js} +15 -11
- package/dist/chunk-MSPUB635.js.map +1 -0
- package/dist/chunk-OBLVLIUS.js +285 -0
- package/dist/chunk-OBLVLIUS.js.map +1 -0
- package/dist/{chunk-MXTGCD5C.js → chunk-PL6CNMVS.js} +2 -2
- package/dist/chunk-RKTEV6EG.js +246 -0
- package/dist/chunk-RKTEV6EG.js.map +1 -0
- package/dist/{chunk-SP7DD4ID.js → chunk-UBIQXCVE.js} +15 -11
- package/dist/chunk-UBIQXCVE.js.map +1 -0
- package/dist/{chunk-2Z7OEZZT.js → chunk-VCKX262Z.js} +12 -8
- package/dist/chunk-VCKX262Z.js.map +1 -0
- package/dist/{chunk-VYFKOUUR.js → chunk-VQ5VN7DF.js} +9 -5
- package/dist/chunk-VQ5VN7DF.js.map +1 -0
- package/dist/cli.js +26 -22
- package/dist/cli.js.map +1 -1
- package/dist/{config-YCF3HIKR.js → config-4THHOXNN.js} +3 -3
- package/dist/execute-5OEKLM5X.js +15 -0
- package/dist/{generate-TW6QKSW5.js → generate-6RA2MFJB.js} +99 -12
- package/dist/generate-6RA2MFJB.js.map +1 -0
- package/dist/init-PRMOTQ7N.js +9 -0
- package/dist/plan-KENR6MJ3.js +17 -0
- package/dist/policy-XQW5NUIE.js +117 -0
- package/dist/policy-XQW5NUIE.js.map +1 -0
- package/dist/pr-KDGKG4BP.js +21 -0
- package/dist/pr-review-CAMAB7KO.js +17 -0
- package/dist/prd-V3MWDIHK.js +17 -0
- package/dist/review-7LN47SAN.js +15 -0
- package/dist/review-7LN47SAN.js.map +1 -0
- package/dist/{run-DIBC3YKX.js → run-H75MD5BQ.js} +26 -25
- package/dist/{run-DIBC3YKX.js.map → run-H75MD5BQ.js.map} +1 -1
- package/dist/{web-O2FVOYZW.js → web-ANQQTTRD.js} +5 -5
- package/package.json +1 -1
- package/prompts/analyze.md +24 -0
- package/prompts/execute.md +29 -1
- package/prompts/generate.md +61 -7
- package/prompts/plan.md +24 -1
- package/prompts/pr-review.md +65 -4
- package/prompts/pr.md +37 -3
- package/prompts/prd.md +17 -0
- package/prompts/review.md +40 -0
- package/dist/analyze-UZYJAWAP.js.map +0 -1
- package/dist/chunk-2LNQL4XH.js.map +0 -1
- package/dist/chunk-2Z7OEZZT.js.map +0 -1
- package/dist/chunk-67ZO5ROQ.js.map +0 -1
- package/dist/chunk-EWA3OGCM.js.map +0 -1
- package/dist/chunk-EZMOEBLU.js +0 -135
- package/dist/chunk-EZMOEBLU.js.map +0 -1
- package/dist/chunk-PPLIXZI5.js +0 -101
- package/dist/chunk-PPLIXZI5.js.map +0 -1
- package/dist/chunk-S67Q4I2O.js.map +0 -1
- package/dist/chunk-SLHZPB3C.js +0 -50
- package/dist/chunk-SLHZPB3C.js.map +0 -1
- package/dist/chunk-SP7DD4ID.js.map +0 -1
- package/dist/chunk-UH4KAJBJ.js.map +0 -1
- package/dist/chunk-V5OHFSW4.js.map +0 -1
- package/dist/chunk-VYFKOUUR.js.map +0 -1
- package/dist/execute-QYXA225W.js +0 -14
- package/dist/generate-TW6QKSW5.js.map +0 -1
- package/dist/init-G57UXKDG.js +0 -9
- package/dist/plan-YJLGBYBQ.js +0 -16
- package/dist/policy-NP4ZAVF6.js.map +0 -1
- package/dist/pr-V5H65MRW.js +0 -20
- package/dist/pr-review-RCMXKXVP.js +0 -16
- package/dist/prd-3XR5KYJS.js +0 -16
- package/dist/review-ZZG3CSUO.js +0 -14
- /package/dist/{chunk-UUY4PCWW.js.map → chunk-3BOHTH4V.js.map} +0 -0
- /package/dist/{config-YCF3HIKR.js.map → chunk-3D4A4B46.js.map} +0 -0
- /package/dist/{chunk-652DJBCN.js.map → chunk-GANSDJV7.js.map} +0 -0
- /package/dist/{chunk-VOKNTBOO.js.map → chunk-LVKQ2AKT.js.map} +0 -0
- /package/dist/{chunk-MXTGCD5C.js.map → chunk-PL6CNMVS.js.map} +0 -0
- /package/dist/{execute-QYXA225W.js.map → config-4THHOXNN.js.map} +0 -0
- /package/dist/{init-G57UXKDG.js.map → execute-5OEKLM5X.js.map} +0 -0
- /package/dist/{plan-YJLGBYBQ.js.map → init-PRMOTQ7N.js.map} +0 -0
- /package/dist/{pr-V5H65MRW.js.map → plan-KENR6MJ3.js.map} +0 -0
- /package/dist/{pr-review-RCMXKXVP.js.map → pr-KDGKG4BP.js.map} +0 -0
- /package/dist/{prd-3XR5KYJS.js.map → pr-review-CAMAB7KO.js.map} +0 -0
- /package/dist/{review-ZZG3CSUO.js.map → prd-V3MWDIHK.js.map} +0 -0
- /package/dist/{web-O2FVOYZW.js.map → web-ANQQTTRD.js.map} +0 -0
- /package/web/{CLAUDE.md → AGENTS.md} +0 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
printError,
|
|
4
|
+
printInfo,
|
|
5
|
+
printSuccess,
|
|
6
|
+
printWarning
|
|
7
|
+
} from "./chunk-HC2MT7BX.js";
|
|
8
|
+
|
|
9
|
+
// src/commands/init.ts
|
|
10
|
+
import { execa } from "execa";
|
|
11
|
+
async function checkClaude() {
|
|
12
|
+
try {
|
|
13
|
+
const proc = await execa("claude", ["--version"], { reject: false, timeout: 1e4 });
|
|
14
|
+
if (proc.exitCode === 0) {
|
|
15
|
+
const version = proc.stdout?.toString().trim() ?? "unknown";
|
|
16
|
+
return { key: "claude", name: "claude CLI", passed: true, detail: version };
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
key: "claude",
|
|
20
|
+
name: "claude CLI",
|
|
21
|
+
passed: false,
|
|
22
|
+
detail: "claude command failed",
|
|
23
|
+
hint: "Install Claude Code: https://docs.anthropic.com/en/docs/claude-code"
|
|
24
|
+
};
|
|
25
|
+
} catch {
|
|
26
|
+
return {
|
|
27
|
+
key: "claude",
|
|
28
|
+
name: "claude CLI",
|
|
29
|
+
passed: false,
|
|
30
|
+
detail: "claude not found",
|
|
31
|
+
hint: "Install Claude Code: https://docs.anthropic.com/en/docs/claude-code"
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function checkGh() {
|
|
36
|
+
try {
|
|
37
|
+
const proc = await execa("gh", ["--version"], { reject: false, timeout: 1e4 });
|
|
38
|
+
if (proc.exitCode !== 0) {
|
|
39
|
+
return {
|
|
40
|
+
key: "gh",
|
|
41
|
+
name: "gh CLI",
|
|
42
|
+
passed: false,
|
|
43
|
+
detail: "gh command failed",
|
|
44
|
+
hint: "Install GitHub CLI: https://cli.github.com/"
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const version = proc.stdout?.toString().split("\n")[0]?.trim() ?? "unknown";
|
|
48
|
+
const auth = await execa("gh", ["auth", "status"], { reject: false, timeout: 1e4 });
|
|
49
|
+
if (auth.exitCode !== 0) {
|
|
50
|
+
return {
|
|
51
|
+
key: "gh",
|
|
52
|
+
name: "gh CLI",
|
|
53
|
+
passed: false,
|
|
54
|
+
detail: `${version} (not authenticated)`,
|
|
55
|
+
hint: "Run: gh auth login"
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return { key: "gh", name: "gh CLI", passed: true, detail: `${version} (authenticated)` };
|
|
59
|
+
} catch {
|
|
60
|
+
return {
|
|
61
|
+
key: "gh",
|
|
62
|
+
name: "gh CLI",
|
|
63
|
+
passed: false,
|
|
64
|
+
detail: "gh not found",
|
|
65
|
+
hint: "Install GitHub CLI: https://cli.github.com/"
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function checkGit() {
|
|
70
|
+
try {
|
|
71
|
+
const proc = await execa("git", ["--version"], { reject: false, timeout: 1e4 });
|
|
72
|
+
if (proc.exitCode !== 0) {
|
|
73
|
+
return {
|
|
74
|
+
key: "git",
|
|
75
|
+
name: "git",
|
|
76
|
+
passed: false,
|
|
77
|
+
detail: "git command failed",
|
|
78
|
+
hint: "Install git: https://git-scm.com/"
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const version = proc.stdout?.toString().trim() ?? "unknown";
|
|
82
|
+
const repo = await execa("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
83
|
+
reject: false,
|
|
84
|
+
timeout: 5e3
|
|
85
|
+
});
|
|
86
|
+
if (repo.exitCode !== 0) {
|
|
87
|
+
return {
|
|
88
|
+
key: "git",
|
|
89
|
+
name: "git",
|
|
90
|
+
passed: false,
|
|
91
|
+
detail: `${version} (not a git repository)`,
|
|
92
|
+
hint: "Run this command inside a git repository"
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return { key: "git", name: "git", passed: true, detail: `${version} (inside repo)` };
|
|
96
|
+
} catch {
|
|
97
|
+
return {
|
|
98
|
+
key: "git",
|
|
99
|
+
name: "git",
|
|
100
|
+
passed: false,
|
|
101
|
+
detail: "git not found",
|
|
102
|
+
hint: "Install git: https://git-scm.com/"
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
var ACTION_ICON = {
|
|
107
|
+
create: "+",
|
|
108
|
+
keep: "=",
|
|
109
|
+
review: "!"
|
|
110
|
+
};
|
|
111
|
+
function renderPlan(plan, apply) {
|
|
112
|
+
const creates = plan.actions.filter((a) => a.kind === "create");
|
|
113
|
+
const reviews = plan.actions.filter((a) => a.kind === "review");
|
|
114
|
+
console.log("");
|
|
115
|
+
printInfo(`Repository conventions (${plan.root})
|
|
116
|
+
`);
|
|
117
|
+
for (const item of plan.actions) {
|
|
118
|
+
console.log(` ${ACTION_ICON[item.kind]} ${item.path}`);
|
|
119
|
+
console.log(` ${item.reason}`);
|
|
120
|
+
}
|
|
121
|
+
if (plan.notes.length > 0) {
|
|
122
|
+
console.log("");
|
|
123
|
+
for (const note of plan.notes) {
|
|
124
|
+
printWarning(note);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
console.log("");
|
|
128
|
+
if (creates.length === 0 && reviews.length === 0) {
|
|
129
|
+
printSuccess("This repository already declares everything Issue Flow would add.");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (creates.length === 0) {
|
|
133
|
+
printSuccess("Nothing to create.");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (!apply) {
|
|
137
|
+
printInfo(`${creates.length} file(s) would be created. Re-run with --apply to write them.`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function runInit(source = "github", options = {}) {
|
|
141
|
+
const json = options.json === true;
|
|
142
|
+
if (!json) {
|
|
143
|
+
printInfo("Checking prerequisites...\n");
|
|
144
|
+
}
|
|
145
|
+
const results = await Promise.all([checkClaude(), checkGh(), checkGit()]);
|
|
146
|
+
const isBlocking = (r) => r.key !== "gh" || source === "github";
|
|
147
|
+
if (!json) {
|
|
148
|
+
for (const r of results) {
|
|
149
|
+
if (r.passed) {
|
|
150
|
+
printSuccess(`${r.name}: ${r.detail}`);
|
|
151
|
+
} else if (isBlocking(r)) {
|
|
152
|
+
printError(`${r.name}: ${r.detail}`);
|
|
153
|
+
if (r.hint) {
|
|
154
|
+
console.log(` ${r.hint}`);
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
printWarning(`${r.name}: ${r.detail} (not required for ${source} issues)`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const allPassed = results.filter(isBlocking).every((r) => r.passed);
|
|
162
|
+
if (!json) {
|
|
163
|
+
console.log("");
|
|
164
|
+
if (allPassed) {
|
|
165
|
+
printSuccess("All prerequisites met. Ready to run the pipeline.");
|
|
166
|
+
} else {
|
|
167
|
+
printError("Some prerequisites are missing. Please fix the issues above.");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (options.checkOnly === true) {
|
|
171
|
+
return allPassed ? 0 : 1;
|
|
172
|
+
}
|
|
173
|
+
let plan;
|
|
174
|
+
try {
|
|
175
|
+
const { planRepositoryScaffold } = await import("./apply-WUJHDD7A.js");
|
|
176
|
+
plan = await planRepositoryScaffold({ scope: options.scope ?? null });
|
|
177
|
+
} catch (err) {
|
|
178
|
+
if (json) {
|
|
179
|
+
console.log(
|
|
180
|
+
JSON.stringify(
|
|
181
|
+
{ schemaVersion: 1, error: err instanceof Error ? err.message : String(err) },
|
|
182
|
+
null,
|
|
183
|
+
2
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
return allPassed ? 0 : 1;
|
|
187
|
+
}
|
|
188
|
+
printWarning(
|
|
189
|
+
`Could not inspect the repository conventions: ${err instanceof Error ? err.message : String(err)}`
|
|
190
|
+
);
|
|
191
|
+
return allPassed ? 0 : 1;
|
|
192
|
+
}
|
|
193
|
+
let applied = null;
|
|
194
|
+
if (options.apply === true) {
|
|
195
|
+
const { applyScaffoldPlan } = await import("./apply-WUJHDD7A.js");
|
|
196
|
+
applied = await applyScaffoldPlan(plan);
|
|
197
|
+
}
|
|
198
|
+
if (json) {
|
|
199
|
+
console.log(
|
|
200
|
+
JSON.stringify(
|
|
201
|
+
{
|
|
202
|
+
schemaVersion: 1,
|
|
203
|
+
prerequisites: results.map((r) => ({
|
|
204
|
+
key: r.key,
|
|
205
|
+
passed: r.passed,
|
|
206
|
+
detail: r.detail,
|
|
207
|
+
blocking: isBlocking(r)
|
|
208
|
+
})),
|
|
209
|
+
root: plan.root,
|
|
210
|
+
// Content is omitted: a skill decides from the actions, and the bodies
|
|
211
|
+
// would make the payload unreadable for no gain.
|
|
212
|
+
actions: plan.actions.map(({ path, kind, tier, reason }) => ({
|
|
213
|
+
path,
|
|
214
|
+
kind,
|
|
215
|
+
tier,
|
|
216
|
+
reason
|
|
217
|
+
})),
|
|
218
|
+
notes: plan.notes,
|
|
219
|
+
applied
|
|
220
|
+
},
|
|
221
|
+
null,
|
|
222
|
+
2
|
|
223
|
+
)
|
|
224
|
+
);
|
|
225
|
+
return allPassed ? 0 : 1;
|
|
226
|
+
}
|
|
227
|
+
renderPlan(plan, options.apply === true);
|
|
228
|
+
if (applied !== null) {
|
|
229
|
+
console.log("");
|
|
230
|
+
for (const path of applied.written) {
|
|
231
|
+
printSuccess(`Created ${path}`);
|
|
232
|
+
}
|
|
233
|
+
for (const path of applied.skipped) {
|
|
234
|
+
printWarning(`Skipped ${path}: it already exists.`);
|
|
235
|
+
}
|
|
236
|
+
if (applied.written.length === 0) {
|
|
237
|
+
printInfo("Nothing was written \u2014 the repository already had everything.");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return allPassed ? 0 : 1;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export {
|
|
244
|
+
runInit
|
|
245
|
+
};
|
|
246
|
+
//# sourceMappingURL=chunk-RKTEV6EG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/init.ts"],"sourcesContent":["import { execa } from 'execa';\nimport type { IssueSource } from '../issues/types.js';\nimport type { ScaffoldActionKind, ScaffoldPlan } from '../scaffold/plan.js';\nimport { printError, printInfo, printSuccess, printWarning } from '../ui/logger.js';\n\n/** Stable identifier of a check, used to decide whether it blocks. */\ntype CheckKey = 'claude' | 'gh' | 'git';\n\ninterface CheckResult {\n key: CheckKey;\n name: string;\n passed: boolean;\n detail: string;\n hint?: string;\n}\n\nasync function checkClaude(): Promise<CheckResult> {\n try {\n const proc = await execa('claude', ['--version'], { reject: false, timeout: 10_000 });\n if (proc.exitCode === 0) {\n const version = proc.stdout?.toString().trim() ?? 'unknown';\n return { key: 'claude', name: 'claude CLI', passed: true, detail: version };\n }\n return {\n key: 'claude',\n name: 'claude CLI',\n passed: false,\n detail: 'claude command failed',\n hint: 'Install Claude Code: https://docs.anthropic.com/en/docs/claude-code',\n };\n } catch {\n return {\n key: 'claude',\n name: 'claude CLI',\n passed: false,\n detail: 'claude not found',\n hint: 'Install Claude Code: https://docs.anthropic.com/en/docs/claude-code',\n };\n }\n}\n\nasync function checkGh(): Promise<CheckResult> {\n try {\n const proc = await execa('gh', ['--version'], { reject: false, timeout: 10_000 });\n if (proc.exitCode !== 0) {\n return {\n key: 'gh',\n name: 'gh CLI',\n passed: false,\n detail: 'gh command failed',\n hint: 'Install GitHub CLI: https://cli.github.com/',\n };\n }\n const version = proc.stdout?.toString().split('\\n')[0]?.trim() ?? 'unknown';\n\n // Check auth status\n const auth = await execa('gh', ['auth', 'status'], { reject: false, timeout: 10_000 });\n if (auth.exitCode !== 0) {\n return {\n key: 'gh',\n name: 'gh CLI',\n passed: false,\n detail: `${version} (not authenticated)`,\n hint: 'Run: gh auth login',\n };\n }\n return { key: 'gh', name: 'gh CLI', passed: true, detail: `${version} (authenticated)` };\n } catch {\n return {\n key: 'gh',\n name: 'gh CLI',\n passed: false,\n detail: 'gh not found',\n hint: 'Install GitHub CLI: https://cli.github.com/',\n };\n }\n}\n\nasync function checkGit(): Promise<CheckResult> {\n try {\n const proc = await execa('git', ['--version'], { reject: false, timeout: 10_000 });\n if (proc.exitCode !== 0) {\n return {\n key: 'git',\n name: 'git',\n passed: false,\n detail: 'git command failed',\n hint: 'Install git: https://git-scm.com/',\n };\n }\n const version = proc.stdout?.toString().trim() ?? 'unknown';\n\n // Check if current directory is a git repo\n const repo = await execa('git', ['rev-parse', '--is-inside-work-tree'], {\n reject: false,\n timeout: 5_000,\n });\n if (repo.exitCode !== 0) {\n return {\n key: 'git',\n name: 'git',\n passed: false,\n detail: `${version} (not a git repository)`,\n hint: 'Run this command inside a git repository',\n };\n }\n return { key: 'git', name: 'git', passed: true, detail: `${version} (inside repo)` };\n } catch {\n return {\n key: 'git',\n name: 'git',\n passed: false,\n detail: 'git not found',\n hint: 'Install git: https://git-scm.com/',\n };\n }\n}\n\nexport interface InitOptions {\n /** Write the missing files instead of only reporting them. */\n apply?: boolean;\n /** Emit the plan as JSON — the bridge the initialization skill reads. */\n json?: boolean;\n /** Subdirectory the conventions apply to, in a monorepo. */\n scope?: string;\n /** Skip the convention report entirely and only check prerequisites. */\n checkOnly?: boolean;\n}\n\nconst ACTION_ICON: Record<ScaffoldActionKind, string> = {\n create: '+',\n keep: '=',\n review: '!',\n};\n\n/**\n * Render the plan for a human.\n *\n * `keep` lines are printed, not hidden: the most valuable thing this report\n * says is usually *what it is not going to touch*, and a list of only the\n * missing files would read as if the repository had nothing.\n */\nfunction renderPlan(plan: ScaffoldPlan, apply: boolean): void {\n const creates = plan.actions.filter((a) => a.kind === 'create');\n const reviews = plan.actions.filter((a) => a.kind === 'review');\n\n console.log('');\n printInfo(`Repository conventions (${plan.root})\\n`);\n\n for (const item of plan.actions) {\n console.log(` ${ACTION_ICON[item.kind]} ${item.path}`);\n console.log(` ${item.reason}`);\n }\n\n if (plan.notes.length > 0) {\n console.log('');\n for (const note of plan.notes) {\n printWarning(note);\n }\n }\n\n console.log('');\n if (creates.length === 0 && reviews.length === 0) {\n printSuccess('This repository already declares everything Issue Flow would add.');\n return;\n }\n if (creates.length === 0) {\n printSuccess('Nothing to create.');\n return;\n }\n if (!apply) {\n printInfo(`${creates.length} file(s) would be created. Re-run with --apply to write them.`);\n }\n}\n\n/**\n * Verify the prerequisites of the pipeline and report the repository's\n * conventions.\n *\n * `source` is the Issue origin the run is headed for. `gh` only blocks when\n * that origin is GitHub: no other origin shells out to it, so a missing or\n * unauthenticated gh is reported as a warning instead of failing the\n * environment. `claude` and `git` stay blocking for every origin, and the\n * default ('github') keeps the previous behaviour byte for byte.\n *\n * The convention report is additive: the prerequisite checks run first and still\n * decide the exit code, so an existing script calling `issue-flow init` sees the\n * same pass/fail it always did. Nothing is written without `--apply`.\n */\nexport async function runInit(\n source: IssueSource = 'github',\n options: InitOptions = {},\n): Promise<number> {\n const json = options.json === true;\n\n if (!json) {\n printInfo('Checking prerequisites...\\n');\n }\n\n const results = await Promise.all([checkClaude(), checkGh(), checkGit()]);\n const isBlocking = (r: CheckResult): boolean => r.key !== 'gh' || source === 'github';\n\n if (!json) {\n for (const r of results) {\n if (r.passed) {\n printSuccess(`${r.name}: ${r.detail}`);\n } else if (isBlocking(r)) {\n printError(`${r.name}: ${r.detail}`);\n if (r.hint) {\n console.log(` ${r.hint}`);\n }\n } else {\n printWarning(`${r.name}: ${r.detail} (not required for ${source} issues)`);\n }\n }\n }\n\n const allPassed = results.filter(isBlocking).every((r) => r.passed);\n\n if (!json) {\n console.log('');\n if (allPassed) {\n printSuccess('All prerequisites met. Ready to run the pipeline.');\n } else {\n printError('Some prerequisites are missing. Please fix the issues above.');\n }\n }\n\n if (options.checkOnly === true) {\n return allPassed ? 0 : 1;\n }\n\n // The conventions half. It never changes the exit code: a repository missing\n // a template is not a broken environment, and failing here would break every\n // script that treats `init` as a prerequisite gate.\n let plan: ScaffoldPlan;\n try {\n const { planRepositoryScaffold } = await import('../scaffold/apply.js');\n plan = await planRepositoryScaffold({ scope: options.scope ?? null });\n } catch (err) {\n if (json) {\n console.log(\n JSON.stringify(\n { schemaVersion: 1, error: err instanceof Error ? err.message : String(err) },\n null,\n 2,\n ),\n );\n return allPassed ? 0 : 1;\n }\n printWarning(\n `Could not inspect the repository conventions: ${err instanceof Error ? err.message : String(err)}`,\n );\n return allPassed ? 0 : 1;\n }\n\n let applied: { written: string[]; skipped: string[] } | null = null;\n if (options.apply === true) {\n const { applyScaffoldPlan } = await import('../scaffold/apply.js');\n applied = await applyScaffoldPlan(plan);\n }\n\n if (json) {\n console.log(\n JSON.stringify(\n {\n schemaVersion: 1,\n prerequisites: results.map((r) => ({\n key: r.key,\n passed: r.passed,\n detail: r.detail,\n blocking: isBlocking(r),\n })),\n root: plan.root,\n // Content is omitted: a skill decides from the actions, and the bodies\n // would make the payload unreadable for no gain.\n actions: plan.actions.map(({ path, kind, tier, reason }) => ({\n path,\n kind,\n tier,\n reason,\n })),\n notes: plan.notes,\n applied,\n },\n null,\n 2,\n ),\n );\n return allPassed ? 0 : 1;\n }\n\n renderPlan(plan, options.apply === true);\n\n if (applied !== null) {\n console.log('');\n for (const path of applied.written) {\n printSuccess(`Created ${path}`);\n }\n for (const path of applied.skipped) {\n printWarning(`Skipped ${path}: it already exists.`);\n }\n if (applied.written.length === 0) {\n printInfo('Nothing was written — the repository already had everything.');\n }\n }\n\n return allPassed ? 0 : 1;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,31 +2,32 @@
|
|
|
2
2
|
import {
|
|
3
3
|
readFileWithGrace,
|
|
4
4
|
runPhaseWithRetry
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-3BOHTH4V.js";
|
|
6
6
|
import {
|
|
7
7
|
issuePlaceholders,
|
|
8
8
|
resolveCommandIssue
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-AAY6KM7N.js";
|
|
10
|
+
import {
|
|
11
|
+
runHeadless
|
|
12
|
+
} from "./chunk-PL6CNMVS.js";
|
|
10
13
|
import {
|
|
11
14
|
isTransientFailure
|
|
12
15
|
} from "./chunk-HDV3YDTG.js";
|
|
13
16
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
publishPhaseMetrics
|
|
18
|
-
} from "./chunk-PPLIXZI5.js";
|
|
17
|
+
publishPhaseMetrics,
|
|
18
|
+
resolvePolicyPlaceholders
|
|
19
|
+
} from "./chunk-OBLVLIUS.js";
|
|
19
20
|
import {
|
|
20
21
|
applyPlaceholders,
|
|
21
22
|
loadPrompt
|
|
22
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-HQYXVIH6.js";
|
|
23
24
|
import {
|
|
24
25
|
getIssuePaths,
|
|
25
26
|
getProjectRoot,
|
|
26
27
|
recordUserStoryNumbering,
|
|
27
28
|
resolveIssuePaths,
|
|
28
29
|
resolveProjectPaths
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-VQ5VN7DF.js";
|
|
30
31
|
import {
|
|
31
32
|
getGlobalTimeout,
|
|
32
33
|
isoNow,
|
|
@@ -37,7 +38,7 @@ import {
|
|
|
37
38
|
printWarning,
|
|
38
39
|
saveTaskPlan,
|
|
39
40
|
taskPlanSchema
|
|
40
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-HC2MT7BX.js";
|
|
41
42
|
|
|
42
43
|
// src/commands/plan.ts
|
|
43
44
|
import { mkdir, readFile as readFile2 } from "fs/promises";
|
|
@@ -184,6 +185,9 @@ async function runPlan(issue, resolvedIssue, numbering) {
|
|
|
184
185
|
printInfo(numberingMessage);
|
|
185
186
|
const template = await loadPrompt("plan");
|
|
186
187
|
const prompt = applyPlaceholders(template, {
|
|
188
|
+
// The repository's own conventions. Empty when it declares none, which is
|
|
189
|
+
// what keeps the rendered prompt identical to the pre-policy one.
|
|
190
|
+
...await resolvePolicyPlaceholders(),
|
|
187
191
|
__ISSUE_NUMBER__: issueNumber,
|
|
188
192
|
__PRD_CONTENT__: prdContent,
|
|
189
193
|
__TASKS_PATH__: tasksPath,
|
|
@@ -262,4 +266,4 @@ ${issues}`
|
|
|
262
266
|
export {
|
|
263
267
|
runPlan
|
|
264
268
|
};
|
|
265
|
-
//# sourceMappingURL=chunk-
|
|
269
|
+
//# sourceMappingURL=chunk-UBIQXCVE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/plan.ts","../src/storage/user-story-numbering.ts"],"sourcesContent":["import { mkdir, readFile } from 'node:fs/promises';\nimport { runHeadless } from '../core/headless.js';\nimport { readFileWithGrace, runPhaseWithRetry } from '../core/phase-runner.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { ResolvedIssue } from '../issues/types.js';\nimport { resolvePolicyPlaceholders } from '../policy/placeholders.js';\nimport { taskPlanSchema } from '../schemas.js';\nimport { resolveIssuePaths } from '../storage/resolve.js';\nimport {\n determineUserStoryNumbering,\n formatUserStoryId,\n parseUserStoryNumber,\n} from '../storage/user-story-numbering.js';\nimport { printError, printInfo, printSuccess, printWarning } from '../ui/logger.js';\nimport { isTransientFailure } from '../utils/retry.js';\n\n/** `--continue` / `--start-us <n>` override of the numbering cascade (issue #36). */\nexport interface PlanUserStoryNumberingOptions {\n continueFlag?: boolean;\n startUs?: number;\n}\n\nexport async function runPlan(\n issue: string,\n resolvedIssue?: ResolvedIssue,\n numbering?: PlanUserStoryNumberingOptions,\n): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const paths = await resolveIssuePaths(issueNumber);\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n // Read the PRD\n const prdPath = paths.prdFile;\n let prdContent: string;\n try {\n prdContent = await readFile(prdPath, 'utf-8');\n } catch {\n printError(`PRD not found at ${prdPath}. Run 'issue-flow prd ${issueNumber}' first.`);\n return 1;\n }\n\n const tasksPath = paths.tasksFile;\n\n // Resolve the User Story numbering continuity (issue #36), log where the\n // decision came from — never silently — and persist it into the project's\n // metadata.json for audit before the prompt is even built, so the record on\n // disk always matches what the prompt was told.\n const {\n message: numberingMessage,\n nextUserStoryId,\n decision: { nextNumber },\n } = await determineUserStoryNumbering({\n issueNumber,\n continueFlag: numbering?.continueFlag,\n startUs: numbering?.startUs,\n });\n printInfo(numberingMessage);\n\n const template = await loadPrompt('plan');\n const prompt = applyPlaceholders(template, {\n // The repository's own conventions. Empty when it declares none, which is\n // what keeps the rendered prompt identical to the pre-policy one.\n ...(await resolvePolicyPlaceholders()),\n __ISSUE_NUMBER__: issueNumber,\n __PRD_CONTENT__: prdContent,\n __TASKS_PATH__: tasksPath,\n __NEXT_US_NUMBER__: nextUserStoryId,\n ...issuePlaceholders(resolution.resolved),\n });\n\n await mkdir(paths.issueDir, { recursive: true });\n\n const outcome = await runPhaseWithRetry({\n phase: 'plan',\n attempt: async () => {\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 25,\n timeout: getGlobalTimeout() ?? 300_000,\n // json (not text) so the CLI reports usage: the envelope's `result`\n // field carries the same assistant text this phase already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n addDirs: [paths.issueDir],\n statusMessage: `Converting PRD to task plan for issue #${issueNumber}...`,\n });\n // One event per attempt; the reducer sums them into the phase total.\n publishPhaseMetrics('plan', result.cost, startedAtMs);\n\n if (!result.success) {\n return {\n ok: false,\n transient: isTransientFailure(1, result.error ?? ''),\n error: `Task plan generation failed: ${result.error}`,\n };\n }\n\n // Verify the file was created, tolerating a brief FS-visibility lag.\n let rawContent: string;\n try {\n rawContent = await readFileWithGrace(tasksPath);\n } catch {\n return { ok: false, transient: true, error: `tasks.json was not created at ${tasksPath}` };\n }\n\n // Validate JSON structure — a content defect, not a timing issue, but\n // still worth a bounded retry since it's a fresh Claude invocation.\n let parsed: unknown;\n try {\n parsed = JSON.parse(rawContent);\n } catch {\n return { ok: false, transient: true, error: 'tasks.json contains invalid JSON' };\n }\n\n // Validate with zod schema\n const validation = taskPlanSchema.safeParse(parsed);\n if (!validation.success) {\n const issues = validation.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n');\n return {\n ok: false,\n transient: true,\n error: `tasks.json does not match expected schema:\\n${issues}`,\n };\n }\n\n return { ok: true };\n },\n });\n\n if (!outcome.ok) {\n printError(outcome.error ?? `Task plan generation failed for issue #${issueNumber}`);\n return 1;\n }\n\n // Ensure pipeline state reflects completion of this phase\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.jsonCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n\n // The numbering is a prompt instruction, not a programmatic rewrite, so the\n // generated plan can still ignore it. Say so out loud instead of leaving the\n // log and metadata.json claiming a numbering the file on disk contradicts.\n const generatedLowest = plan.userStories\n .map((story) => parseUserStoryNumber(story.id))\n .filter((value): value is number => value !== null)\n .reduce<number | null>(\n (lowest, value) => (lowest === null ? value : Math.min(lowest, value)),\n null,\n );\n if (generatedLowest !== null && generatedLowest < nextNumber) {\n printWarning(\n `The generated plan starts at ${formatUserStoryId(generatedLowest)}, not the requested ` +\n `${nextUserStoryId}. User Story ids may collide with earlier issues of this project — ` +\n `re-run 'issue-flow plan ${issueNumber} --start-us ${nextNumber}' if that matters.`,\n );\n }\n\n printSuccess(`Task plan saved to ${tasksPath} (${plan.userStories.length} stories)`);\n return 0;\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { isoNow } from '../core/state-manager.js';\nimport { getProjectRoot } from '../utils/git.js';\nimport { recordUserStoryNumbering } from './compat.js';\nimport { getIssuePaths } from './paths.js';\nimport { type ResolveIssuePathsOptions, resolveProjectPaths } from './resolve.js';\nimport type { UserStoryNumberingDecision } from './schemas.js';\n\n/**\n * Continuity of `US-NNN` numbering across `plan` executions of the same\n * project (issue #36).\n *\n * The User Story `id` field is free-form — `userStorySchema` in\n * `src/schemas.ts` only requires a non-empty string, no `US-NNN` regex is\n * enforced — so every reader here tolerates an id that does not carry a\n * trailing number instead of throwing on it.\n */\n\nconst USER_STORY_ID_PAD = 3;\n\n/** `16` -> `'US-016'`. Numbers past 999 are not padded further. */\nexport function formatUserStoryId(n: number): string {\n return `US-${String(n).padStart(USER_STORY_ID_PAD, '0')}`;\n}\n\n/**\n * Extract the last run of digits from a User Story id, or `null` when none is\n * found. Tolerant by design: an id like `US-001` yields `1`, but so does a\n * free-form one such as `story-1` or `v2-US-007` (the last digit run, `007`,\n * wins over the `2` version prefix) — only an id with no digits at all (e.g.\n * `add-auth`) is ignored.\n */\nexport function parseUserStoryNumber(id: string): number | null {\n const matches = id.match(/\\d+/g);\n if (!matches || matches.length === 0) return null;\n\n const value = Number.parseInt(matches[matches.length - 1], 10);\n return Number.isNaN(value) ? null : value;\n}\n\nexport interface HighestUserStoryNumberResult {\n number: number;\n /** Issue directory name the winning story came from. */\n issueId: string;\n /** Verbatim id of the winning story. */\n storyId: string;\n}\n\n/**\n * Loose read of one `tasks.json`: only cares about `userStories[].id` and\n * tolerates every failure (missing file, invalid JSON, unexpected shape) by\n * returning `null` — a sibling issue mid-migration, or with a corrupt plan,\n * must not abort the whole project scan.\n */\nasync function readUserStoryIds(tasksPath: string): Promise<string[] | null> {\n let raw: string;\n try {\n raw = await readFile(tasksPath, 'utf-8');\n } catch {\n return null;\n }\n\n try {\n const parsed: unknown = JSON.parse(raw);\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n !Array.isArray((parsed as { userStories?: unknown }).userStories)\n ) {\n return null;\n }\n\n return (parsed as { userStories: unknown[] }).userStories\n .map((story) =>\n typeof story === 'object' &&\n story !== null &&\n typeof (story as { id?: unknown }).id === 'string'\n ? (story as { id: string }).id\n : null,\n )\n .filter((id): id is string => id !== null);\n } catch {\n return null;\n }\n}\n\nexport interface FindHighestUserStoryNumberOptions extends ResolveIssuePathsOptions {\n /**\n * Issue directory to leave out of the scan — the issue the numbering is\n * being resolved for. Re-planning an issue overwrites its own `tasks.json`,\n * so counting the plan about to be replaced would push the numbering forward\n * on every `plan` re-run of the same issue.\n */\n excludeIssueId?: string;\n}\n\n/**\n * Highest User Story number already used anywhere in the project, scanning\n * every `<issuesDir>/<id>/tasks.json` regardless of which issue produced it —\n * that project-wide scope is what makes `US-NNN` unambiguous across issues.\n * The issue named by `excludeIssueId` is skipped, so re-planning is idempotent.\n *\n * `null` means no readable `tasks.json` was found (the project's first `plan`\n * run ever, or a brand-new project directory). A storage directory that exists\n * but cannot be read throws instead: silently restarting at `US-001` because\n * `~/.issue-flow` was momentarily unreadable is the worst outcome this feature\n * can produce.\n */\nexport async function findHighestUserStoryNumber(\n options: FindHighestUserStoryNumberOptions = {},\n): Promise<HighestUserStoryNumberResult | null> {\n const { excludeIssueId, ...rootOptions } = options;\n const { projectId, issuesDir } = await resolveProjectPaths(rootOptions);\n\n let entries: string[];\n try {\n entries = (await readdir(issuesDir, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name);\n } catch (error) {\n // ENOENT is the legitimate \"no history yet\" case. Any other failure\n // (EACCES, EIO, an unavailable mount) must not be reported as an empty\n // history, or the numbering would restart silently.\n if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') return null;\n throw new Error(\n `Could not scan the project's User Story history at ${issuesDir}: ` +\n `${(error as Error).message}. Fix the storage directory, or pass --start-us <n> ` +\n 'to set the numbering explicitly.',\n { cause: error },\n );\n }\n\n let best: HighestUserStoryNumberResult | null = null;\n\n for (const issueId of entries) {\n if (excludeIssueId !== undefined && issueId === excludeIssueId) continue;\n // Never hand-build an artifact path (see src/storage/AGENTS.md): asking\n // getIssuePaths() keeps a rename of `tasks.json` a one-file change instead\n // of silently making this scan find nothing.\n const { tasksFile } = getIssuePaths(projectId, issueId, { env: options.env });\n const ids = await readUserStoryIds(tasksFile);\n if (!ids) continue;\n\n for (const id of ids) {\n const value = parseUserStoryNumber(id);\n if (value === null) continue;\n if (best === null || value > best.number) {\n best = { number: value, issueId, storyId: id };\n }\n }\n }\n\n return best;\n}\n\nexport interface ResolveUserStoryNumberingOptions extends ResolveIssuePathsOptions {\n /** Issue the decision is being made for — carried into the audit record. */\n issueNumber: string;\n /** `--continue` was passed explicitly. Purely cosmetic on the log message:\n * the resolved number is identical to the automatic default. */\n continueFlag?: boolean;\n /** `--start-us <n>` was passed. Wins outright over any history. */\n startUs?: number;\n /** Timestamp source, injectable for tests. */\n now?: () => string;\n}\n\nexport interface UserStoryNumberingOutcome {\n decision: UserStoryNumberingDecision;\n /** Ready-to-print explanation of where the decision came from — the CLI\n * must never resolve this silently (issue #36). */\n message: string;\n}\n\n/**\n * Resolve the numbering cascade of #36:\n *\n * 1. `--start-us <n>` wins outright — history is not even scanned, since the\n * whole point is to ignore it.\n * 2. Otherwise the project's history is scanned for the highest number used\n * so far. This is what running with no flag at all does automatically,\n * and what `--continue` names explicitly (the two produce the same\n * number; the flag only changes the log message).\n * 3. No history found (the project's first `plan` ever) starts at `US-001`.\n *\n * Pure with respect to `metadata.json` — this only reads `tasks.json` files,\n * never writes. Persisting the decision is {@link determineUserStoryNumbering}\n * below, via `recordUserStoryNumbering()` in `compat.ts`.\n */\nexport async function resolveUserStoryNumbering(\n options: ResolveUserStoryNumberingOptions,\n): Promise<UserStoryNumberingOutcome> {\n const { issueNumber, continueFlag, startUs, now, ...rootOptions } = options;\n const decidedAt = (now ?? isoNow)();\n\n if (startUs !== undefined) {\n const decision: UserStoryNumberingDecision = {\n nextNumber: startUs,\n source: 'start-us',\n issueNumber,\n decidedAt,\n };\n return {\n decision,\n message: `User Story numbering forced to ${formatUserStoryId(startUs)} via --start-us.`,\n };\n }\n\n // The issue being planned is excluded from the scan: `plan` overwrites its\n // own `tasks.json`, so counting the plan it is about to replace would push\n // the numbering forward on every re-run of `plan` for the same issue.\n const highest = await findHighestUserStoryNumber({ ...rootOptions, excludeIssueId: issueNumber });\n\n if (highest === null) {\n const decision: UserStoryNumberingDecision = {\n nextNumber: 1,\n source: 'none',\n issueNumber,\n decidedAt,\n };\n return {\n decision,\n message: `Starting User Story numbering at ${formatUserStoryId(1)} (no previous history found for this project).`,\n };\n }\n\n const nextNumber = highest.number + 1;\n const decision: UserStoryNumberingDecision = {\n nextNumber,\n source: 'history',\n issueNumber,\n decidedAt,\n detail: `${highest.storyId} (issue #${highest.issueId})`,\n };\n const via = continueFlag ? ' via --continue' : '';\n return {\n decision,\n message:\n `Continuing User Story numbering from ${formatUserStoryId(nextNumber)}${via} ` +\n `— last used was ${highest.storyId} (issue #${highest.issueId}).`,\n };\n}\n\nexport interface DetermineUserStoryNumberingOptions extends ResolveUserStoryNumberingOptions {\n /** Repository root. Defaults to the git toplevel of the current directory. */\n projectRoot?: string;\n}\n\nexport interface UserStoryNumberingResult extends UserStoryNumberingOutcome {\n /** `decision.nextNumber` formatted as `US-NNN`, ready for the prompt. */\n nextUserStoryId: string;\n}\n\n/**\n * The entry point commands use: resolve the cascade and persist the decision\n * into `metadata.json` in one call, so no call site can log/prompt with one\n * decision and audit a different one.\n */\nexport async function determineUserStoryNumbering(\n options: DetermineUserStoryNumberingOptions,\n): Promise<UserStoryNumberingResult> {\n const { projectRoot: projectRootOption, ...rest } = options;\n const projectRoot = projectRootOption ?? (await getProjectRoot());\n\n const outcome = await resolveUserStoryNumbering({ ...rest, projectRoot });\n await recordUserStoryNumbering(projectRoot, outcome.decision, {\n env: options.env,\n now: options.now,\n });\n\n return { ...outcome, nextUserStoryId: formatUserStoryId(outcome.decision.nextNumber) };\n}\n"],"mappings":"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","names":["readFile","decision","readFile"]}
|
|
@@ -2,27 +2,28 @@
|
|
|
2
2
|
import {
|
|
3
3
|
issuePlaceholders,
|
|
4
4
|
resolveCommandIssue
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-AAY6KM7N.js";
|
|
6
6
|
import {
|
|
7
7
|
runHeadless
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-PL6CNMVS.js";
|
|
9
9
|
import {
|
|
10
|
-
publishPhaseMetrics
|
|
11
|
-
|
|
10
|
+
publishPhaseMetrics,
|
|
11
|
+
resolvePolicyPlaceholders
|
|
12
|
+
} from "./chunk-OBLVLIUS.js";
|
|
12
13
|
import {
|
|
13
14
|
applyPlaceholders,
|
|
14
15
|
loadPrompt
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-HQYXVIH6.js";
|
|
16
17
|
import {
|
|
17
18
|
resolveIssuePaths
|
|
18
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-VQ5VN7DF.js";
|
|
19
20
|
import {
|
|
20
21
|
getGlobalTimeout,
|
|
21
22
|
loadTaskPlan,
|
|
22
23
|
printError,
|
|
23
24
|
printSuccess,
|
|
24
25
|
saveTaskPlan
|
|
25
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-HC2MT7BX.js";
|
|
26
27
|
|
|
27
28
|
// src/commands/review.ts
|
|
28
29
|
function parseReviewResult(output) {
|
|
@@ -57,6 +58,9 @@ async function runReview(issue, resolvedIssue) {
|
|
|
57
58
|
}
|
|
58
59
|
const template = await loadPrompt("review");
|
|
59
60
|
const prompt = applyPlaceholders(template, {
|
|
61
|
+
// The repository's own conventions. Empty when it declares none, which is
|
|
62
|
+
// what keeps the rendered prompt identical to the pre-policy one.
|
|
63
|
+
...await resolvePolicyPlaceholders(),
|
|
60
64
|
__ISSUE_NUMBER__: issueNumber,
|
|
61
65
|
__TASKS_PATH__: tasksPath,
|
|
62
66
|
...issuePlaceholders(resolution.resolved)
|
|
@@ -107,4 +111,4 @@ async function runReview(issue, resolvedIssue) {
|
|
|
107
111
|
export {
|
|
108
112
|
runReview
|
|
109
113
|
};
|
|
110
|
-
//# sourceMappingURL=chunk-
|
|
114
|
+
//# sourceMappingURL=chunk-VCKX262Z.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/review.ts"],"sourcesContent":["import { runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { ResolvedIssue } from '../issues/types.js';\nimport { resolvePolicyPlaceholders } from '../policy/placeholders.js';\nimport { resolveIssuePaths } from '../storage/resolve.js';\nimport { printError, printSuccess } from '../ui/logger.js';\n\nexport interface ReviewResult {\n status: 'PASS' | 'FAIL';\n findings?: string;\n}\n\n/**\n * Parse the <review-result> block from headless output.\n */\nfunction parseReviewResult(output: string): ReviewResult {\n const match = output.match(/<review-result>([\\s\\S]*?)<\\/review-result>/);\n if (!match) {\n // Try to detect PASS/FAIL from the raw output\n if (/STATUS:\\s*PASS/i.test(output)) {\n return { status: 'PASS' };\n }\n if (/STATUS:\\s*FAIL/i.test(output)) {\n const findingsMatch = output.match(/FINDINGS:([\\s\\S]*?)(?:$|<\\/)/);\n return { status: 'FAIL', findings: findingsMatch?.[1]?.trim() ?? 'Unknown findings' };\n }\n // Default to PASS if no explicit status found and no errors\n return { status: 'PASS' };\n }\n\n const block = match[1];\n if (/STATUS:\\s*PASS/i.test(block)) {\n return { status: 'PASS' };\n }\n\n const findingsMatch = block.match(/FINDINGS:([\\s\\S]*)/);\n return {\n status: 'FAIL',\n findings: findingsMatch?.[1]?.trim() ?? 'Unknown findings',\n };\n}\n\nexport async function runReview(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const paths = await resolveIssuePaths(issueNumber);\n const tasksPath = paths.tasksFile;\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n const template = await loadPrompt('review');\n const prompt = applyPlaceholders(template, {\n // The repository's own conventions. Empty when it declares none, which is\n // what keeps the rendered prompt identical to the pre-policy one.\n ...(await resolvePolicyPlaceholders()),\n __ISSUE_NUMBER__: issueNumber,\n __TASKS_PATH__: tasksPath,\n ...issuePlaceholders(resolution.resolved),\n });\n\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 25,\n timeout: getGlobalTimeout() ?? 300_000,\n // json (not text) so the CLI reports usage: the envelope's `result` field\n // carries the same assistant text parseReviewResult() already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n addDirs: [paths.issueDir],\n statusMessage: `Reviewing issue #${issueNumber} resolution...`,\n });\n // Before the success check: the tokens were spent either way. A correction\n // cycle calls runReview() again and the reducer sums both invocations.\n publishPhaseMetrics('review', result.cost, startedAtMs);\n\n if (!result.success) {\n printError(`Review failed: ${result.error}`);\n return 1;\n }\n\n const review = parseReviewResult(result.result);\n\n if (review.status === 'PASS') {\n // Update pipeline state\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.reviewCompleted = true;\n plan.lastReviewFindings = null;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist\n }\n\n printSuccess('Review PASSED — implementation meets acceptance criteria');\n return 0;\n }\n\n printError('Review FAILED — issues found:');\n if (review.findings) {\n console.log(review.findings);\n }\n\n // Persist the findings so a correction cycle can act on them: without this,\n // a re-run of execute sees every userStories[].passes already true and\n // exits immediately without attempting any fix (see engine.ts's\n // \"already complete\" guards).\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.reviewCompleted = false;\n plan.lastReviewFindings = review.findings ?? 'Unknown findings';\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist\n }\n\n return 1;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBA,SAAS,kBAAkB,QAA8B;AACvD,QAAM,QAAQ,OAAO,MAAM,4CAA4C;AACvE,MAAI,CAAC,OAAO;AAEV,QAAI,kBAAkB,KAAK,MAAM,GAAG;AAClC,aAAO,EAAE,QAAQ,OAAO;AAAA,IAC1B;AACA,QAAI,kBAAkB,KAAK,MAAM,GAAG;AAClC,YAAMA,iBAAgB,OAAO,MAAM,8BAA8B;AACjE,aAAO,EAAE,QAAQ,QAAQ,UAAUA,iBAAgB,CAAC,GAAG,KAAK,KAAK,mBAAmB;AAAA,IACtF;AAEA,WAAO,EAAE,QAAQ,OAAO;AAAA,EAC1B;AAEA,QAAM,QAAQ,MAAM,CAAC;AACrB,MAAI,kBAAkB,KAAK,KAAK,GAAG;AACjC,WAAO,EAAE,QAAQ,OAAO;AAAA,EAC1B;AAEA,QAAM,gBAAgB,MAAM,MAAM,oBAAoB;AACtD,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,UAAU,gBAAgB,CAAC,GAAG,KAAK,KAAK;AAAA,EAC1C;AACF;AAEA,eAAsB,UAAU,OAAe,eAAgD;AAC7F,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,QAAQ,MAAM,kBAAkB,WAAW;AACjD,QAAM,YAAY,MAAM;AAExB,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAEA,QAAM,WAAW,MAAM,WAAW,QAAQ;AAC1C,QAAM,SAAS,kBAAkB,UAAU;AAAA;AAAA;AAAA,IAGzC,GAAI,MAAM,0BAA0B;AAAA,IACpC,kBAAkB;AAAA,IAClB,gBAAgB;AAAA,IAChB,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,cAAc,KAAK,IAAI;AAC7B,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,SAAS,iBAAiB,KAAK;AAAA;AAAA;AAAA,IAG/B,cAAc;AAAA,IACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,MAAM;AAAA,IAC7C,SAAS,CAAC,MAAM,QAAQ;AAAA,IACxB,eAAe,oBAAoB,WAAW;AAAA,EAChD,CAAC;AAGD,sBAAoB,UAAU,OAAO,MAAM,WAAW;AAEtD,MAAI,CAAC,OAAO,SAAS;AACnB,eAAW,kBAAkB,OAAO,KAAK,EAAE;AAC3C,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,kBAAkB,OAAO,MAAM;AAE9C,MAAI,OAAO,WAAW,QAAQ;AAE5B,QAAI;AACF,YAAM,OAAO,MAAM,aAAa,SAAS;AACzC,WAAK,SAAS,kBAAkB;AAChC,WAAK,qBAAqB;AAC1B,YAAM,aAAa,WAAW,IAAI;AAAA,IACpC,QAAQ;AAAA,IAER;AAEA,iBAAa,+DAA0D;AACvE,WAAO;AAAA,EACT;AAEA,aAAW,oCAA+B;AAC1C,MAAI,OAAO,UAAU;AACnB,YAAQ,IAAI,OAAO,QAAQ;AAAA,EAC7B;AAMA,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,kBAAkB;AAChC,SAAK,qBAAqB,OAAO,YAAY;AAC7C,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;","names":["findingsMatch"]}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
pullRequestRefSchema,
|
|
11
11
|
webConfigSchema,
|
|
12
12
|
writeFileAtomic
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-HC2MT7BX.js";
|
|
14
14
|
|
|
15
15
|
// src/config.ts
|
|
16
16
|
import { readFile as readFile2 } from "fs/promises";
|
|
@@ -886,6 +886,10 @@ function readPolicyConfigEnv(env, warn) {
|
|
|
886
886
|
if (env.ISSUE_FLOW_POLICY !== void 0) {
|
|
887
887
|
layer.enabled = parseBooleanEnv(env.ISSUE_FLOW_POLICY);
|
|
888
888
|
}
|
|
889
|
+
const budget = readNumberEnv(env, "ISSUE_FLOW_POLICY_CONTEXT_BUDGET", warn);
|
|
890
|
+
if (budget !== void 0) {
|
|
891
|
+
layer.contextBudget = budget;
|
|
892
|
+
}
|
|
889
893
|
const issues = {};
|
|
890
894
|
if (env.ISSUE_FLOW_POLICY_ISSUE_TITLE_CONVENTION !== void 0) {
|
|
891
895
|
issues.titleConvention = env.ISSUE_FLOW_POLICY_ISSUE_TITLE_CONVENTION;
|
|
@@ -938,9 +942,9 @@ async function loadPolicyConfig(options = {}) {
|
|
|
938
942
|
const envLayer = readPolicyConfigEnv(env, warn);
|
|
939
943
|
const merged = {
|
|
940
944
|
...mergeConfigLayers({
|
|
941
|
-
project:
|
|
942
|
-
env:
|
|
943
|
-
cli:
|
|
945
|
+
project: dropNullish({ enabled: fileLayer.enabled, contextBudget: fileLayer.contextBudget }),
|
|
946
|
+
env: dropNullish({ enabled: envLayer.enabled, contextBudget: envLayer.contextBudget }),
|
|
947
|
+
cli: dropNullish({ enabled: cli.enabled, contextBudget: cli.contextBudget })
|
|
944
948
|
}),
|
|
945
949
|
discovery: mergeConfigLayers({
|
|
946
950
|
project: dropNullish(fileLayer.discovery),
|
|
@@ -1011,4 +1015,4 @@ export {
|
|
|
1011
1015
|
setPolicyCliOverrides,
|
|
1012
1016
|
loadPolicyConfig
|
|
1013
1017
|
};
|
|
1014
|
-
//# sourceMappingURL=chunk-
|
|
1018
|
+
//# sourceMappingURL=chunk-VQ5VN7DF.js.map
|