issue-flow 0.5.2 → 0.6.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-QVQ4CTCU.js → analyze-VVG7UABS.js} +15 -5
- package/dist/analyze-VVG7UABS.js.map +1 -0
- package/dist/{chunk-3ULIAC2J.js → chunk-2CSHLHYP.js} +2 -2
- package/dist/{chunk-PA3GI7LR.js → chunk-4HT4EVYV.js} +78 -32
- package/dist/chunk-4HT4EVYV.js.map +1 -0
- package/dist/{chunk-DLWRUTRL.js → chunk-7W5VGYXW.js} +11 -11
- package/dist/{chunk-JMSRJ43G.js → chunk-EHX4J5G5.js} +15 -8
- package/dist/chunk-EHX4J5G5.js.map +1 -0
- package/dist/{chunk-KMX3DUJN.js → chunk-FPGHTTVV.js} +15 -8
- package/dist/chunk-FPGHTTVV.js.map +1 -0
- package/dist/{chunk-OEJSMZMO.js → chunk-JTSAL3TI.js} +12 -5
- package/dist/chunk-JTSAL3TI.js.map +1 -0
- package/dist/{chunk-LJI45O5B.js → chunk-KWGRBNUG.js} +22 -8
- package/dist/chunk-KWGRBNUG.js.map +1 -0
- package/dist/chunk-SVOWWZQJ.js +982 -0
- package/dist/chunk-SVOWWZQJ.js.map +1 -0
- package/dist/{chunk-O5XI2CFF.js → chunk-WZDCTAWZ.js} +55 -20
- package/dist/chunk-WZDCTAWZ.js.map +1 -0
- package/dist/{chunk-VM7C7L3A.js → chunk-YM3G5BLR.js} +21 -9
- package/dist/chunk-YM3G5BLR.js.map +1 -0
- package/dist/cli.js +130 -29
- package/dist/cli.js.map +1 -1
- package/dist/config-B564P45C.js +29 -0
- package/dist/execute-PNOFNLZG.js +12 -0
- package/dist/generate-IN62ARBS.js +143 -0
- package/dist/generate-IN62ARBS.js.map +1 -0
- package/dist/init-5242XSYC.js +9 -0
- package/dist/plan-6KLONWPR.js +14 -0
- package/dist/pr-JLJLMFN3.js +14 -0
- package/dist/prd-TVJDYZXH.js +14 -0
- package/dist/review-FRV4ZMS2.js +12 -0
- package/dist/review-FRV4ZMS2.js.map +1 -0
- package/dist/{run-3GJC54DB.js → run-GPBICAD6.js} +45 -26
- package/dist/run-GPBICAD6.js.map +1 -0
- package/package.json +2 -1
- package/prompts/analyze.md +18 -5
- package/prompts/generate.md +27 -6
- package/prompts/plan.md +19 -2
- package/prompts/pr.md +23 -6
- package/prompts/prd.md +18 -5
- package/prompts/review.md +18 -6
- package/dist/analyze-QVQ4CTCU.js.map +0 -1
- package/dist/chunk-JMSRJ43G.js.map +0 -1
- package/dist/chunk-KLBAHTXX.js +0 -283
- package/dist/chunk-KLBAHTXX.js.map +0 -1
- package/dist/chunk-KMX3DUJN.js.map +0 -1
- package/dist/chunk-LJI45O5B.js.map +0 -1
- package/dist/chunk-O5XI2CFF.js.map +0 -1
- package/dist/chunk-OEJSMZMO.js.map +0 -1
- package/dist/chunk-PA3GI7LR.js.map +0 -1
- package/dist/chunk-VM7C7L3A.js.map +0 -1
- package/dist/execute-TEJ26L4O.js +0 -12
- package/dist/generate-KBQ4KW4T.js +0 -48
- package/dist/generate-KBQ4KW4T.js.map +0 -1
- package/dist/init-4VMPBRMA.js +0 -9
- package/dist/plan-PWYZP5AN.js +0 -13
- package/dist/pr-CJQWZUDR.js +0 -13
- package/dist/prd-4KLQY7BC.js +0 -13
- package/dist/review-YFCWBBKY.js +0 -11
- package/dist/run-3GJC54DB.js.map +0 -1
- /package/dist/{chunk-3ULIAC2J.js.map → chunk-2CSHLHYP.js.map} +0 -0
- /package/dist/{chunk-DLWRUTRL.js.map → chunk-7W5VGYXW.js.map} +0 -0
- /package/dist/{execute-TEJ26L4O.js.map → config-B564P45C.js.map} +0 -0
- /package/dist/{init-4VMPBRMA.js.map → execute-PNOFNLZG.js.map} +0 -0
- /package/dist/{plan-PWYZP5AN.js.map → init-5242XSYC.js.map} +0 -0
- /package/dist/{pr-CJQWZUDR.js.map → plan-6KLONWPR.js.map} +0 -0
- /package/dist/{prd-4KLQY7BC.js.map → pr-JLJLMFN3.js.map} +0 -0
- /package/dist/{review-YFCWBBKY.js.map → prd-TVJDYZXH.js.map} +0 -0
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
issuePlaceholders,
|
|
4
|
+
resolveCommandIssue,
|
|
3
5
|
runHeadless
|
|
4
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SVOWWZQJ.js";
|
|
5
7
|
import {
|
|
6
8
|
applyPlaceholders,
|
|
7
9
|
loadPrompt
|
|
8
10
|
} from "./chunk-SLHZPB3C.js";
|
|
11
|
+
import "./chunk-4HT4EVYV.js";
|
|
9
12
|
import {
|
|
10
13
|
getGlobalTimeout,
|
|
11
14
|
loadTaskPlan,
|
|
@@ -13,20 +16,27 @@ import {
|
|
|
13
16
|
printInfo,
|
|
14
17
|
printSuccess,
|
|
15
18
|
saveTaskPlan
|
|
16
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-WZDCTAWZ.js";
|
|
17
20
|
|
|
18
21
|
// src/commands/analyze.ts
|
|
19
22
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
20
23
|
import { join } from "path";
|
|
21
|
-
async function runAnalyze(issue) {
|
|
24
|
+
async function runAnalyze(issue, resolvedIssue) {
|
|
22
25
|
const issueNumber = issue.replace(/^#/, "");
|
|
23
26
|
const issueDir = join("issues", issueNumber);
|
|
24
27
|
const analysisPath = join(issueDir, "analysis.md");
|
|
28
|
+
const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);
|
|
29
|
+
if (!resolution.ok) {
|
|
30
|
+
return resolution.code;
|
|
31
|
+
}
|
|
25
32
|
await mkdir(issueDir, { recursive: true });
|
|
26
33
|
const template = await loadPrompt("analyze");
|
|
27
34
|
const prompt = applyPlaceholders(template, {
|
|
28
35
|
__ISSUE_NUMBER__: issueNumber,
|
|
29
|
-
__ANALYSIS_PATH__: analysisPath
|
|
36
|
+
__ANALYSIS_PATH__: analysisPath,
|
|
37
|
+
// Last: the Issue content is substituted in but never scanned again, so a
|
|
38
|
+
// body that happens to contain a placeholder is left untouched.
|
|
39
|
+
...issuePlaceholders(resolution.resolved)
|
|
30
40
|
});
|
|
31
41
|
const result = await runHeadless({
|
|
32
42
|
prompt,
|
|
@@ -63,4 +73,4 @@ async function runAnalyze(issue) {
|
|
|
63
73
|
export {
|
|
64
74
|
runAnalyze
|
|
65
75
|
};
|
|
66
|
-
//# sourceMappingURL=analyze-
|
|
76
|
+
//# sourceMappingURL=analyze-VVG7UABS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/analyze.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.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 { printError, printInfo, printSuccess } from '../ui/logger.js';\n\nexport async function runAnalyze(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const analysisPath = join(issueDir, 'analysis.md');\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n await mkdir(issueDir, { recursive: true });\n\n const template = await loadPrompt('analyze');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __ANALYSIS_PATH__: analysisPath,\n // Last: the Issue content is substituted in but never scanned again, so a\n // body that happens to contain a placeholder is left untouched.\n ...issuePlaceholders(resolution.resolved),\n });\n\n const result = await runHeadless({\n prompt,\n maxTurns: 30,\n timeout: getGlobalTimeout() ?? 300_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n statusMessage: `Analyzing issue #${issueNumber}...`,\n });\n\n if (!result.success) {\n printError(`Analysis failed: ${result.error}`);\n return 1;\n }\n\n // Verify the file was created\n try {\n const content = await readFile(analysisPath, 'utf-8');\n if (content.length < 10) {\n printError('Analysis file was created but appears empty');\n return 1;\n }\n } catch {\n // File wasn't created by headless — save the result as analysis\n printInfo('Headless did not create analysis file; saving output directly');\n await writeFile(analysisPath, result.result, 'utf-8');\n }\n\n // Update pipeline state\n const tasksPath = join(issueDir, 'tasks.json');\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.analyzeCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist yet — that's OK for standalone analyze\n }\n\n printSuccess(`Analysis saved to ${analysisPath}`);\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,OAAO,UAAU,iBAAiB;AAC3C,SAAS,YAAY;AASrB,eAAsB,WAAW,OAAe,eAAgD;AAC9F,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,eAAe,KAAK,UAAU,aAAa;AAEjD,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAEA,QAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAEzC,QAAM,WAAW,MAAM,WAAW,SAAS;AAC3C,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,mBAAmB;AAAA;AAAA;AAAA,IAGnB,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,SAAS,iBAAiB,KAAK;AAAA,IAC/B,cAAc;AAAA,IACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,IACtD,eAAe,oBAAoB,WAAW;AAAA,EAChD,CAAC;AAED,MAAI,CAAC,OAAO,SAAS;AACnB,eAAW,oBAAoB,OAAO,KAAK,EAAE;AAC7C,WAAO;AAAA,EACT;AAGA,MAAI;AACF,UAAM,UAAU,MAAM,SAAS,cAAc,OAAO;AACpD,QAAI,QAAQ,SAAS,IAAI;AACvB,iBAAW,6CAA6C;AACxD,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAEN,cAAU,+DAA+D;AACzE,UAAM,UAAU,cAAc,OAAO,QAAQ,OAAO;AAAA,EACtD;AAGA,QAAM,YAAY,KAAK,UAAU,YAAY;AAC7C,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,mBAAmB;AACjC,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,eAAa,qBAAqB,YAAY,EAAE;AAChD,SAAO;AACT;","names":[]}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
getSessionPublisher,
|
|
8
8
|
isoNow,
|
|
9
9
|
printRetry
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-WZDCTAWZ.js";
|
|
11
11
|
|
|
12
12
|
// src/core/phase-runner.ts
|
|
13
13
|
import { readFile } from "fs/promises";
|
|
@@ -55,4 +55,4 @@ export {
|
|
|
55
55
|
runPhaseWithRetry,
|
|
56
56
|
readFileWithGrace
|
|
57
57
|
};
|
|
58
|
-
//# sourceMappingURL=chunk-
|
|
58
|
+
//# sourceMappingURL=chunk-2CSHLHYP.js.map
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
issuesConfigSchema,
|
|
3
4
|
printWarning,
|
|
4
5
|
webConfigSchema
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-WZDCTAWZ.js";
|
|
6
7
|
|
|
7
8
|
// src/config.ts
|
|
8
9
|
import { readFile } from "fs/promises";
|
|
@@ -126,7 +127,37 @@ async function validateDependencies() {
|
|
|
126
127
|
}
|
|
127
128
|
return errors;
|
|
128
129
|
}
|
|
129
|
-
var
|
|
130
|
+
var PROJECT_CONFIG_FILENAME = ".issue-flow.json";
|
|
131
|
+
var WEB_CONFIG_FILENAME = PROJECT_CONFIG_FILENAME;
|
|
132
|
+
async function readProjectConfigFile(projectRoot, warn) {
|
|
133
|
+
let root = projectRoot;
|
|
134
|
+
if (root === void 0) {
|
|
135
|
+
try {
|
|
136
|
+
root = await getProjectRoot();
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const filePath = join(root, PROJECT_CONFIG_FILENAME);
|
|
142
|
+
let raw;
|
|
143
|
+
try {
|
|
144
|
+
raw = await readFile(filePath, "utf-8");
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
let parsed;
|
|
149
|
+
try {
|
|
150
|
+
parsed = JSON.parse(raw);
|
|
151
|
+
} catch {
|
|
152
|
+
warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: invalid JSON.`);
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
156
|
+
warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: expected a JSON object.`);
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return parsed;
|
|
160
|
+
}
|
|
130
161
|
var webCliOverrides = {};
|
|
131
162
|
function setWebCliOverrides(overrides) {
|
|
132
163
|
webCliOverrides = overrides;
|
|
@@ -170,40 +201,15 @@ function readWebConfigEnv(env, warn) {
|
|
|
170
201
|
return layer;
|
|
171
202
|
}
|
|
172
203
|
async function readWebConfigFile(projectRoot, warn) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
root = await getProjectRoot();
|
|
177
|
-
} catch {
|
|
178
|
-
return {};
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
const filePath = join(root, WEB_CONFIG_FILENAME);
|
|
182
|
-
let raw;
|
|
183
|
-
try {
|
|
184
|
-
raw = await readFile(filePath, "utf-8");
|
|
185
|
-
} catch {
|
|
186
|
-
return {};
|
|
187
|
-
}
|
|
188
|
-
let parsed;
|
|
189
|
-
try {
|
|
190
|
-
parsed = JSON.parse(raw);
|
|
191
|
-
} catch {
|
|
192
|
-
warn(`Ignoring ${WEB_CONFIG_FILENAME}: invalid JSON.`);
|
|
193
|
-
return {};
|
|
194
|
-
}
|
|
195
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
196
|
-
warn(`Ignoring ${WEB_CONFIG_FILENAME}: expected a JSON object.`);
|
|
197
|
-
return {};
|
|
198
|
-
}
|
|
199
|
-
const web = parsed.web;
|
|
204
|
+
const file = await readProjectConfigFile(projectRoot, warn);
|
|
205
|
+
const web = file?.web;
|
|
200
206
|
if (web === void 0) {
|
|
201
207
|
return {};
|
|
202
208
|
}
|
|
203
209
|
const result = webConfigSchema.partial().safeParse(web);
|
|
204
210
|
if (!result.success) {
|
|
205
211
|
warn(
|
|
206
|
-
`Ignoring "web" key of ${
|
|
212
|
+
`Ignoring "web" key of ${PROJECT_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? "invalid value"}.`
|
|
207
213
|
);
|
|
208
214
|
return {};
|
|
209
215
|
}
|
|
@@ -225,16 +231,56 @@ async function loadWebConfig(options = {}) {
|
|
|
225
231
|
);
|
|
226
232
|
return webConfigSchema.parse({});
|
|
227
233
|
}
|
|
234
|
+
var issuesCliOverrides = {};
|
|
235
|
+
function setIssuesCliOverrides(overrides) {
|
|
236
|
+
issuesCliOverrides = overrides;
|
|
237
|
+
}
|
|
238
|
+
async function readIssuesConfigFile(projectRoot, warn) {
|
|
239
|
+
const file = await readProjectConfigFile(projectRoot, warn);
|
|
240
|
+
const issues = file?.issues;
|
|
241
|
+
if (issues === void 0) {
|
|
242
|
+
return {};
|
|
243
|
+
}
|
|
244
|
+
const result = issuesConfigSchema.partial().safeParse(issues);
|
|
245
|
+
if (!result.success) {
|
|
246
|
+
warn(
|
|
247
|
+
`Ignoring "issues" key of ${PROJECT_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? "invalid value"}.`
|
|
248
|
+
);
|
|
249
|
+
return {};
|
|
250
|
+
}
|
|
251
|
+
return result.data;
|
|
252
|
+
}
|
|
253
|
+
async function loadIssuesConfig(options = {}) {
|
|
254
|
+
const warn = options.warn ?? printWarning;
|
|
255
|
+
const cli = options.cli ?? issuesCliOverrides;
|
|
256
|
+
const fileLayer = await readIssuesConfigFile(options.projectRoot, warn);
|
|
257
|
+
const merged = { ...fileLayer, ...cli };
|
|
258
|
+
const result = issuesConfigSchema.safeParse(merged);
|
|
259
|
+
if (result.success) {
|
|
260
|
+
return result.data;
|
|
261
|
+
}
|
|
262
|
+
warn(
|
|
263
|
+
`Invalid Issue provider configuration (${result.error.issues[0]?.message ?? "invalid value"}); using defaults.`
|
|
264
|
+
);
|
|
265
|
+
return issuesConfigSchema.parse({});
|
|
266
|
+
}
|
|
228
267
|
|
|
229
268
|
export {
|
|
230
269
|
run,
|
|
270
|
+
getProjectRoot,
|
|
231
271
|
getCurrentBranch,
|
|
232
272
|
getBaseBranch,
|
|
233
273
|
getCommitsSince,
|
|
274
|
+
DEFAULTS,
|
|
234
275
|
createConfig,
|
|
235
276
|
resolvePaths,
|
|
277
|
+
getInstallHint,
|
|
236
278
|
validateDependencies,
|
|
279
|
+
PROJECT_CONFIG_FILENAME,
|
|
280
|
+
WEB_CONFIG_FILENAME,
|
|
237
281
|
setWebCliOverrides,
|
|
238
|
-
loadWebConfig
|
|
282
|
+
loadWebConfig,
|
|
283
|
+
setIssuesCliOverrides,
|
|
284
|
+
loadIssuesConfig
|
|
239
285
|
};
|
|
240
|
-
//# sourceMappingURL=chunk-
|
|
286
|
+
//# sourceMappingURL=chunk-4HT4EVYV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/utils/shell.ts","../src/utils/git.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { platform } from 'node:os';\nimport { join } from 'node:path';\nimport type { IssuesConfig } from './issues/types.js';\nimport { issuesConfigSchema, type WebConfig, webConfigSchema } from './schemas.js';\nimport type { EngineConfig, ResolvedPaths } from './types.js';\nimport { printWarning } from './ui/logger.js';\nimport { getProjectRoot } from './utils/git.js';\nimport { run } from './utils/shell.js';\n\n/**\n * Default configuration values — matching the Bash script exactly.\n */\nexport const DEFAULTS = {\n retryLimit: 10,\n retryForever: false,\n backoffBaseSeconds: 30,\n backoffMaxSeconds: 900,\n} as const;\n\n/**\n * Create a EngineConfig with defaults merged with provided options.\n */\nexport function createConfig(options: Partial<EngineConfig>): EngineConfig {\n return {\n issueNumber: options.issueNumber,\n maxIterations: options.maxIterations,\n retryLimit: options.retryLimit ?? DEFAULTS.retryLimit,\n retryForever: options.retryForever ?? DEFAULTS.retryForever,\n backoffBaseSeconds: options.backoffBaseSeconds ?? DEFAULTS.backoffBaseSeconds,\n backoffMaxSeconds: options.backoffMaxSeconds ?? DEFAULTS.backoffMaxSeconds,\n };\n}\n\n/**\n * Resolve file paths based on issue number and project root.\n *\n * With --issue N:\n * prdFile = {projectRoot}/issues/{N}/tasks.json\n * progressFile = {projectRoot}/issues/{N}/progress.txt\n *\n * Standalone:\n * prdFile = {projectRoot}/prd.json\n * progressFile = {projectRoot}/progress.txt\n */\nexport async function resolvePaths(\n config: EngineConfig,\n scriptDir?: string,\n): Promise<ResolvedPaths> {\n const projectRoot = await getProjectRoot();\n\n if (config.issueNumber) {\n const issueDir = join(projectRoot, 'issues', config.issueNumber);\n return {\n prdFile: join(issueDir, 'tasks.json'),\n progressFile: join(issueDir, 'progress.txt'),\n archiveDir: join(issueDir, 'archive'),\n lastBranchFile: join(issueDir, '.last-branch'),\n projectRoot,\n };\n }\n\n // Standalone mode — use scriptDir if available, otherwise projectRoot\n const base = scriptDir ?? projectRoot;\n return {\n prdFile: join(base, 'prd.json'),\n progressFile: join(base, 'progress.txt'),\n archiveDir: join(base, 'archive'),\n lastBranchFile: join(base, '.last-branch'),\n projectRoot,\n };\n}\n\n/**\n * Return a platform-appropriate install hint for a given package.\n */\nexport function getInstallHint(pkg: string): string {\n const os = platform();\n\n if (os === 'darwin') {\n return `brew install ${pkg}`;\n }\n if (os === 'linux') {\n return `apt install ${pkg} (or your distro's package manager)`;\n }\n if (os === 'win32') {\n return `winget install ${pkg} (or choco install ${pkg})`;\n }\n\n return `install ${pkg} using your system package manager`;\n}\n\n/**\n * Validate that required external dependencies are available.\n * Returns an array of error messages (empty if all deps are found).\n */\nexport async function validateDependencies(): Promise<string[]> {\n const errors: string[] = [];\n\n // Check git\n const gitResult = await run('git', ['--version']);\n if (gitResult.exitCode !== 0) {\n errors.push(` - git (install with: ${getInstallHint('git')})`);\n }\n\n // Check claude\n const claudeResult = await run('claude', ['--version']);\n if (claudeResult.exitCode !== 0) {\n errors.push(' - claude (install with: npm install -g @anthropic-ai/claude-code)');\n }\n\n // Note: jq is NOT required — the TypeScript CLI handles JSON natively\n\n return errors;\n}\n\n// ── Per-project configuration file ──────────────────────────────────────────\n\n/** Optional per-project configuration file, read from the project root. */\nexport const PROJECT_CONFIG_FILENAME = '.issue-flow.json';\n\n/** Historical alias kept for the web monitoring call sites. */\nexport const WEB_CONFIG_FILENAME = PROJECT_CONFIG_FILENAME;\n\n/**\n * Read and parse .issue-flow.json from the project root.\n *\n * Never throws: an absent file, unreadable path, invalid JSON or a non-object\n * root all degrade to `null` (with a warning for the malformed cases), so every\n * consumer falls back to its own defaults.\n */\nasync function readProjectConfigFile(\n projectRoot: string | undefined,\n warn: (message: string) => void,\n): Promise<Record<string, unknown> | null> {\n let root = projectRoot;\n if (root === undefined) {\n try {\n root = await getProjectRoot();\n } catch {\n return null;\n }\n }\n\n const filePath = join(root, PROJECT_CONFIG_FILENAME);\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf-8');\n } catch {\n // The file is entirely optional — absence is the common case.\n return null;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: invalid JSON.`);\n return null;\n }\n\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: expected a JSON object.`);\n return null;\n }\n\n return parsed as Record<string, unknown>;\n}\n\n// ── Web monitoring configuration ────────────────────────────────────────────\n\n/**\n * CLI overrides captured by the preAction hook in cli.ts. Highest-precedence\n * source consumed by loadWebConfig().\n */\nlet webCliOverrides: Partial<WebConfig> = {};\n\nexport function setWebCliOverrides(overrides: Partial<WebConfig>): void {\n webCliOverrides = overrides;\n}\n\nexport interface LoadWebConfigOptions {\n /** CLI flag overrides. Defaults to the values set via setWebCliOverrides(). */\n cli?: Partial<WebConfig>;\n /** Environment source. Defaults to process.env. */\n env?: NodeJS.ProcessEnv;\n /** Directory containing .issue-flow.json. Defaults to the git project root. */\n projectRoot?: string;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n}\n\nconst FALSY_ENV_VALUES = new Set(['', '0', 'false', 'no', 'off']);\n\nfunction parseBooleanEnv(value: string): boolean {\n return !FALSY_ENV_VALUES.has(value.trim().toLowerCase());\n}\n\nfunction readNumberEnv(\n env: NodeJS.ProcessEnv,\n name: string,\n warn: (message: string) => void,\n): number | undefined {\n const raw = env[name];\n if (raw === undefined) {\n return undefined;\n }\n const parsed = Number(raw);\n if (!Number.isFinite(parsed)) {\n warn(`Ignoring ${name}=\"${raw}\": not a number.`);\n return undefined;\n }\n return parsed;\n}\n\nfunction readWebConfigEnv(\n env: NodeJS.ProcessEnv,\n warn: (message: string) => void,\n): Partial<WebConfig> {\n const layer: Partial<WebConfig> = {};\n if (env.ISSUE_FLOW_WEB !== undefined) {\n layer.enabled = parseBooleanEnv(env.ISSUE_FLOW_WEB);\n }\n const port = readNumberEnv(env, 'ISSUE_FLOW_WEB_PORT', warn);\n if (port !== undefined) {\n layer.port = port;\n }\n if (env.ISSUE_FLOW_WEB_HOST !== undefined) {\n layer.host = env.ISSUE_FLOW_WEB_HOST;\n }\n const refresh = readNumberEnv(env, 'ISSUE_FLOW_WEB_REFRESH', warn);\n if (refresh !== undefined) {\n layer.refreshSeconds = refresh;\n }\n const logLimit = readNumberEnv(env, 'ISSUE_FLOW_WEB_LOG_LIMIT', warn);\n if (logLimit !== undefined) {\n layer.logLimit = logLimit;\n }\n return layer;\n}\n\nasync function readWebConfigFile(\n projectRoot: string | undefined,\n warn: (message: string) => void,\n): Promise<Partial<WebConfig>> {\n const file = await readProjectConfigFile(projectRoot, warn);\n const web = file?.web;\n if (web === undefined) {\n return {};\n }\n\n const result = webConfigSchema.partial().safeParse(web);\n if (!result.success) {\n warn(\n `Ignoring \"web\" key of ${PROJECT_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? 'invalid value'}.`,\n );\n return {};\n }\n return result.data;\n}\n\n/**\n * Resolve the web monitoring configuration with the documented precedence:\n * CLI flag > environment variable > .issue-flow.json > defaults.\n *\n * Never throws: missing or invalid sources degrade to the defaults with a\n * warning.\n */\nexport async function loadWebConfig(options: LoadWebConfigOptions = {}): Promise<WebConfig> {\n const warn = options.warn ?? printWarning;\n const cli = options.cli ?? webCliOverrides;\n const env = options.env ?? process.env;\n\n const fileLayer = await readWebConfigFile(options.projectRoot, warn);\n const envLayer = readWebConfigEnv(env, warn);\n const merged = { ...fileLayer, ...envLayer, ...cli };\n\n const result = webConfigSchema.safeParse(merged);\n if (result.success) {\n return result.data;\n }\n warn(\n `Invalid web monitoring configuration (${result.error.issues[0]?.message ?? 'invalid value'}); using defaults.`,\n );\n return webConfigSchema.parse({});\n}\n\n// ── Issue provider configuration ────────────────────────────────────────────\n\n/**\n * CLI overrides captured by the preAction hook in cli.ts (--local, --github,\n * --prefer-local, --prefer-github, --ask). Highest-precedence source consumed\n * by loadIssuesConfig().\n */\nlet issuesCliOverrides: Partial<IssuesConfig> = {};\n\nexport function setIssuesCliOverrides(overrides: Partial<IssuesConfig>): void {\n issuesCliOverrides = overrides;\n}\n\nexport interface LoadIssuesConfigOptions {\n /** CLI flag overrides. Defaults to the values set via setIssuesCliOverrides(). */\n cli?: Partial<IssuesConfig>;\n /** Directory containing .issue-flow.json. Defaults to the git project root. */\n projectRoot?: string;\n /** Warning sink. Defaults to printWarning. */\n warn?: (message: string) => void;\n}\n\nasync function readIssuesConfigFile(\n projectRoot: string | undefined,\n warn: (message: string) => void,\n): Promise<Partial<IssuesConfig>> {\n const file = await readProjectConfigFile(projectRoot, warn);\n const issues = file?.issues;\n if (issues === undefined) {\n return {};\n }\n\n const result = issuesConfigSchema.partial().safeParse(issues);\n if (!result.success) {\n warn(\n `Ignoring \"issues\" key of ${PROJECT_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? 'invalid value'}.`,\n );\n return {};\n }\n return result.data;\n}\n\n/**\n * Resolve the Issue provider configuration with the documented precedence:\n * CLI flag > .issue-flow.json > defaults.\n *\n * Never throws: missing or invalid sources degrade to the defaults with a\n * warning, and the defaults reproduce the GitHub-only behaviour.\n */\nexport async function loadIssuesConfig(\n options: LoadIssuesConfigOptions = {},\n): Promise<IssuesConfig> {\n const warn = options.warn ?? printWarning;\n const cli = options.cli ?? issuesCliOverrides;\n\n const fileLayer = await readIssuesConfigFile(options.projectRoot, warn);\n const merged = { ...fileLayer, ...cli };\n\n const result = issuesConfigSchema.safeParse(merged);\n if (result.success) {\n return result.data;\n }\n warn(\n `Invalid Issue provider configuration (${result.error.issues[0]?.message ?? 'invalid value'}); using defaults.`,\n );\n return issuesConfigSchema.parse({});\n}\n","import { type Options as ExecaOptions, execa } from 'execa';\n\nexport interface ExecResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Execute a command with arguments and capture its output.\n * Uses execa which calls execFile internally (no shell injection risk).\n * Does not throw on non-zero exit codes.\n */\nexport async function run(\n command: string,\n args: string[] = [],\n options?: ExecaOptions,\n): Promise<ExecResult> {\n const result = await execa(command, args, {\n reject: false,\n ...options,\n });\n\n return {\n stdout: result.stdout?.toString() ?? '',\n stderr: result.stderr?.toString() ?? '',\n exitCode: result.exitCode ?? 1,\n };\n}\n","import { run } from './shell.js';\n\n/**\n * Get the root directory of the current git repository.\n * Throws if not inside a git repository.\n */\nexport async function getProjectRoot(): Promise<string> {\n const result = await run('git', ['rev-parse', '--show-toplevel']);\n\n if (result.exitCode !== 0) {\n throw new Error(\n 'Not inside a git repository. Please run issue-flow from within a git project.',\n );\n }\n\n return result.stdout.trim();\n}\n\n/**\n * Get the current git branch name.\n * Returns an empty string if in detached HEAD state.\n */\nexport async function getCurrentBranch(): Promise<string> {\n const result = await run('git', ['branch', '--show-current']);\n\n if (result.exitCode !== 0) {\n throw new Error(\n 'Failed to detect git branch. Ensure git is installed and you are inside a repository.',\n );\n }\n\n return result.stdout.trim();\n}\n\nexport interface CommitInfo {\n hash: string;\n subject: string;\n}\n\n/**\n * Get the base branch of the repository: the remote HEAD (origin/HEAD) when\n * available, otherwise the first of main/master that exists locally.\n * Never throws; defaults to 'main'.\n */\nexport async function getBaseBranch(): Promise<string> {\n const remoteHead = await run('git', ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD']);\n if (remoteHead.exitCode === 0) {\n const name = remoteHead.stdout.trim().replace(/^origin\\//, '');\n if (name) return name;\n }\n\n for (const candidate of ['main', 'master']) {\n const check = await run('git', ['rev-parse', '--verify', '--quiet', `refs/heads/${candidate}`]);\n if (check.exitCode === 0) return candidate;\n }\n\n return 'main';\n}\n\n/**\n * List commits on HEAD that are not on the given base branch (base..HEAD),\n * most recent first. Never throws; returns [] when the range cannot be\n * resolved (e.g. unknown base branch or shallow clone).\n */\nexport async function getCommitsSince(base: string): Promise<CommitInfo[]> {\n const result = await run('git', ['log', '--pretty=format:%h%x09%s', `${base}..HEAD`]);\n if (result.exitCode !== 0) return [];\n\n return result.stdout\n .split('\\n')\n .filter((line) => line.trim() !== '')\n .map((line) => {\n const tab = line.indexOf('\\t');\n return tab === -1\n ? { hash: line.trim(), subject: '' }\n : { hash: line.slice(0, tab), subject: line.slice(tab + 1) };\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
createConfig,
|
|
4
|
-
getBaseBranch,
|
|
5
|
-
getCommitsSince,
|
|
6
|
-
getCurrentBranch,
|
|
7
|
-
resolvePaths,
|
|
8
|
-
run,
|
|
9
|
-
validateDependencies
|
|
10
|
-
} from "./chunk-PA3GI7LR.js";
|
|
11
2
|
import {
|
|
12
3
|
isTransientFailure,
|
|
13
4
|
retryDelaySeconds,
|
|
@@ -17,6 +8,15 @@ import {
|
|
|
17
8
|
applyPlaceholders,
|
|
18
9
|
loadPrompt
|
|
19
10
|
} from "./chunk-SLHZPB3C.js";
|
|
11
|
+
import {
|
|
12
|
+
createConfig,
|
|
13
|
+
getBaseBranch,
|
|
14
|
+
getCommitsSince,
|
|
15
|
+
getCurrentBranch,
|
|
16
|
+
resolvePaths,
|
|
17
|
+
run,
|
|
18
|
+
validateDependencies
|
|
19
|
+
} from "./chunk-4HT4EVYV.js";
|
|
20
20
|
import {
|
|
21
21
|
NullPublisher,
|
|
22
22
|
allStoriesPass,
|
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
trimErrorMessage,
|
|
44
44
|
useColor,
|
|
45
45
|
useUnicode
|
|
46
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-WZDCTAWZ.js";
|
|
47
47
|
|
|
48
48
|
// src/core/engine.ts
|
|
49
49
|
import { existsSync } from "fs";
|
|
@@ -565,4 +565,4 @@ export {
|
|
|
565
565
|
publishGitState,
|
|
566
566
|
runExecute
|
|
567
567
|
};
|
|
568
|
-
//# sourceMappingURL=chunk-
|
|
568
|
+
//# sourceMappingURL=chunk-7W5VGYXW.js.map
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
import {
|
|
3
3
|
readFileWithGrace,
|
|
4
4
|
runPhaseWithRetry
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2CSHLHYP.js";
|
|
6
|
+
import {
|
|
7
|
+
issuePlaceholders,
|
|
8
|
+
resolveCommandIssue,
|
|
9
|
+
runHeadless
|
|
10
|
+
} from "./chunk-SVOWWZQJ.js";
|
|
6
11
|
import {
|
|
7
12
|
isTransientFailure
|
|
8
13
|
} from "./chunk-HDV3YDTG.js";
|
|
9
|
-
import {
|
|
10
|
-
runHeadless
|
|
11
|
-
} from "./chunk-KLBAHTXX.js";
|
|
12
14
|
import {
|
|
13
15
|
applyPlaceholders,
|
|
14
16
|
loadPrompt
|
|
@@ -19,20 +21,25 @@ import {
|
|
|
19
21
|
printError,
|
|
20
22
|
printSuccess,
|
|
21
23
|
saveTaskPlan
|
|
22
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-WZDCTAWZ.js";
|
|
23
25
|
|
|
24
26
|
// src/commands/prd.ts
|
|
25
27
|
import { mkdir } from "fs/promises";
|
|
26
28
|
import { join } from "path";
|
|
27
|
-
async function runPrd(issue) {
|
|
29
|
+
async function runPrd(issue, resolvedIssue) {
|
|
28
30
|
const issueNumber = issue.replace(/^#/, "");
|
|
29
31
|
const issueDir = join("issues", issueNumber);
|
|
30
32
|
const prdPath = join(issueDir, "prd.md");
|
|
33
|
+
const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);
|
|
34
|
+
if (!resolution.ok) {
|
|
35
|
+
return resolution.code;
|
|
36
|
+
}
|
|
31
37
|
await mkdir(issueDir, { recursive: true });
|
|
32
38
|
const template = await loadPrompt("prd");
|
|
33
39
|
const prompt = applyPlaceholders(template, {
|
|
34
40
|
__ISSUE_NUMBER__: issueNumber,
|
|
35
|
-
__PRD_PATH__: prdPath
|
|
41
|
+
__PRD_PATH__: prdPath,
|
|
42
|
+
...issuePlaceholders(resolution.resolved)
|
|
36
43
|
});
|
|
37
44
|
const outcome = await runPhaseWithRetry({
|
|
38
45
|
phase: "prd",
|
|
@@ -81,4 +88,4 @@ async function runPrd(issue) {
|
|
|
81
88
|
export {
|
|
82
89
|
runPrd
|
|
83
90
|
};
|
|
84
|
-
//# sourceMappingURL=chunk-
|
|
91
|
+
//# sourceMappingURL=chunk-EHX4J5G5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/prd.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { runHeadless } from '../core/headless.js';\nimport { readFileWithGrace, runPhaseWithRetry } from '../core/phase-runner.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.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 { printError, printSuccess } from '../ui/logger.js';\nimport { isTransientFailure } from '../utils/retry.js';\n\nexport async function runPrd(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const prdPath = join(issueDir, 'prd.md');\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n await mkdir(issueDir, { recursive: true });\n\n const template = await loadPrompt('prd');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __PRD_PATH__: prdPath,\n ...issuePlaceholders(resolution.resolved),\n });\n\n const outcome = await runPhaseWithRetry({\n phase: 'prd',\n attempt: async () => {\n const result = await runHeadless({\n prompt,\n maxTurns: 25,\n timeout: getGlobalTimeout() ?? 300_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n statusMessage: `Generating PRD for issue #${issueNumber}...`,\n });\n\n if (!result.success) {\n return {\n ok: false,\n transient: isTransientFailure(1, result.error ?? ''),\n error: `PRD generation failed: ${result.error}`,\n };\n }\n\n // Verify the file was created. A brief grace period absorbs a lag\n // between the Write tool completing and this process seeing it; if it\n // still isn't there, treat it as recoverable and let the phase retry.\n try {\n const content = await readFileWithGrace(prdPath);\n if (content.length < 10) {\n return { ok: false, transient: true, error: 'PRD file was created but appears empty' };\n }\n } catch {\n return { ok: false, transient: true, error: `PRD file was not created at ${prdPath}` };\n }\n\n return { ok: true };\n },\n });\n\n if (!outcome.ok) {\n printError(outcome.error ?? `PRD generation failed for issue #${issueNumber}`);\n return 1;\n }\n\n // Update pipeline state\n const tasksPath = join(issueDir, 'tasks.json');\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.prdCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist yet\n }\n\n printSuccess(`PRD saved to ${prdPath}`);\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa;AACtB,SAAS,YAAY;AAWrB,eAAsB,OAAO,OAAe,eAAgD;AAC1F,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,UAAU,KAAK,UAAU,QAAQ;AAEvC,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAEA,QAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAEzC,QAAM,WAAW,MAAM,WAAW,KAAK;AACvC,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,cAAc;AAAA,IACd,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,UAAU,MAAM,kBAAkB;AAAA,IACtC,OAAO;AAAA,IACP,SAAS,YAAY;AACnB,YAAM,SAAS,MAAM,YAAY;AAAA,QAC/B;AAAA,QACA,UAAU;AAAA,QACV,SAAS,iBAAiB,KAAK;AAAA,QAC/B,cAAc;AAAA,QACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,QACtD,eAAe,6BAA6B,WAAW;AAAA,MACzD,CAAC;AAED,UAAI,CAAC,OAAO,SAAS;AACnB,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,WAAW,mBAAmB,GAAG,OAAO,SAAS,EAAE;AAAA,UACnD,OAAO,0BAA0B,OAAO,KAAK;AAAA,QAC/C;AAAA,MACF;AAKA,UAAI;AACF,cAAM,UAAU,MAAM,kBAAkB,OAAO;AAC/C,YAAI,QAAQ,SAAS,IAAI;AACvB,iBAAO,EAAE,IAAI,OAAO,WAAW,MAAM,OAAO,yCAAyC;AAAA,QACvF;AAAA,MACF,QAAQ;AACN,eAAO,EAAE,IAAI,OAAO,WAAW,MAAM,OAAO,+BAA+B,OAAO,GAAG;AAAA,MACvF;AAEA,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB;AAAA,EACF,CAAC;AAED,MAAI,CAAC,QAAQ,IAAI;AACf,eAAW,QAAQ,SAAS,oCAAoC,WAAW,EAAE;AAC7E,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,KAAK,UAAU,YAAY;AAC7C,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,eAAe;AAC7B,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,eAAa,gBAAgB,OAAO,EAAE;AACtC,SAAO;AACT;","names":[]}
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
import {
|
|
3
3
|
readFileWithGrace,
|
|
4
4
|
runPhaseWithRetry
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2CSHLHYP.js";
|
|
6
|
+
import {
|
|
7
|
+
issuePlaceholders,
|
|
8
|
+
resolveCommandIssue,
|
|
9
|
+
runHeadless
|
|
10
|
+
} from "./chunk-SVOWWZQJ.js";
|
|
6
11
|
import {
|
|
7
12
|
isTransientFailure
|
|
8
13
|
} from "./chunk-HDV3YDTG.js";
|
|
9
|
-
import {
|
|
10
|
-
runHeadless
|
|
11
|
-
} from "./chunk-KLBAHTXX.js";
|
|
12
14
|
import {
|
|
13
15
|
applyPlaceholders,
|
|
14
16
|
loadPrompt
|
|
@@ -20,14 +22,18 @@ import {
|
|
|
20
22
|
printSuccess,
|
|
21
23
|
saveTaskPlan,
|
|
22
24
|
taskPlanSchema
|
|
23
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-WZDCTAWZ.js";
|
|
24
26
|
|
|
25
27
|
// src/commands/plan.ts
|
|
26
28
|
import { mkdir, readFile } from "fs/promises";
|
|
27
29
|
import { join } from "path";
|
|
28
|
-
async function runPlan(issue) {
|
|
30
|
+
async function runPlan(issue, resolvedIssue) {
|
|
29
31
|
const issueNumber = issue.replace(/^#/, "");
|
|
30
32
|
const issueDir = join("issues", issueNumber);
|
|
33
|
+
const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);
|
|
34
|
+
if (!resolution.ok) {
|
|
35
|
+
return resolution.code;
|
|
36
|
+
}
|
|
31
37
|
const prdPath = join(issueDir, "prd.md");
|
|
32
38
|
let prdContent;
|
|
33
39
|
try {
|
|
@@ -41,7 +47,8 @@ async function runPlan(issue) {
|
|
|
41
47
|
const prompt = applyPlaceholders(template, {
|
|
42
48
|
__ISSUE_NUMBER__: issueNumber,
|
|
43
49
|
__PRD_CONTENT__: prdContent,
|
|
44
|
-
__TASKS_PATH__: tasksPath
|
|
50
|
+
__TASKS_PATH__: tasksPath,
|
|
51
|
+
...issuePlaceholders(resolution.resolved)
|
|
45
52
|
});
|
|
46
53
|
await mkdir(issueDir, { recursive: true });
|
|
47
54
|
const outcome = await runPhaseWithRetry({
|
|
@@ -101,4 +108,4 @@ ${issues}`
|
|
|
101
108
|
export {
|
|
102
109
|
runPlan
|
|
103
110
|
};
|
|
104
|
-
//# sourceMappingURL=chunk-
|
|
111
|
+
//# sourceMappingURL=chunk-FPGHTTVV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/plan.ts"],"sourcesContent":["import { mkdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { runHeadless } from '../core/headless.js';\nimport { readFileWithGrace, runPhaseWithRetry } from '../core/phase-runner.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.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 { taskPlanSchema } from '../schemas.js';\nimport { printError, printSuccess } from '../ui/logger.js';\nimport { isTransientFailure } from '../utils/retry.js';\n\nexport async function runPlan(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', 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 = join(issueDir, 'prd.md');\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 = join(issueDir, 'tasks.json');\n\n const template = await loadPrompt('plan');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __PRD_CONTENT__: prdContent,\n __TASKS_PATH__: tasksPath,\n ...issuePlaceholders(resolution.resolved),\n });\n\n await mkdir(issueDir, { recursive: true });\n\n const outcome = await runPhaseWithRetry({\n phase: 'plan',\n attempt: async () => {\n const result = await runHeadless({\n prompt,\n maxTurns: 25,\n timeout: getGlobalTimeout() ?? 300_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n statusMessage: `Converting PRD to task plan for issue #${issueNumber}...`,\n });\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 printSuccess(`Task plan saved to ${tasksPath} (${plan.userStories.length} stories)`);\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,OAAO,gBAAgB;AAChC,SAAS,YAAY;AAYrB,eAAsB,QAAQ,OAAe,eAAgD;AAC3F,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAE3C,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAGA,QAAM,UAAU,KAAK,UAAU,QAAQ;AACvC,MAAI;AACJ,MAAI;AACF,iBAAa,MAAM,SAAS,SAAS,OAAO;AAAA,EAC9C,QAAQ;AACN,eAAW,oBAAoB,OAAO,yBAAyB,WAAW,UAAU;AACpF,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,KAAK,UAAU,YAAY;AAE7C,QAAM,WAAW,MAAM,WAAW,MAAM;AACxC,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAEzC,QAAM,UAAU,MAAM,kBAAkB;AAAA,IACtC,OAAO;AAAA,IACP,SAAS,YAAY;AACnB,YAAM,SAAS,MAAM,YAAY;AAAA,QAC/B;AAAA,QACA,UAAU;AAAA,QACV,SAAS,iBAAiB,KAAK;AAAA,QAC/B,cAAc;AAAA,QACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,QACtD,eAAe,0CAA0C,WAAW;AAAA,MACtE,CAAC;AAED,UAAI,CAAC,OAAO,SAAS;AACnB,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,WAAW,mBAAmB,GAAG,OAAO,SAAS,EAAE;AAAA,UACnD,OAAO,gCAAgC,OAAO,KAAK;AAAA,QACrD;AAAA,MACF;AAGA,UAAI;AACJ,UAAI;AACF,qBAAa,MAAM,kBAAkB,SAAS;AAAA,MAChD,QAAQ;AACN,eAAO,EAAE,IAAI,OAAO,WAAW,MAAM,OAAO,iCAAiC,SAAS,GAAG;AAAA,MAC3F;AAIA,UAAI;AACJ,UAAI;AACF,iBAAS,KAAK,MAAM,UAAU;AAAA,MAChC,QAAQ;AACN,eAAO,EAAE,IAAI,OAAO,WAAW,MAAM,OAAO,mCAAmC;AAAA,MACjF;AAGA,YAAM,aAAa,eAAe,UAAU,MAAM;AAClD,UAAI,CAAC,WAAW,SAAS;AACvB,cAAM,SAAS,WAAW,MAAM,OAC7B,IAAI,CAAC,MAAM,OAAO,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAClD,KAAK,IAAI;AACZ,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,WAAW;AAAA,UACX,OAAO;AAAA,EAA+C,MAAM;AAAA,QAC9D;AAAA,MACF;AAEA,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB;AAAA,EACF,CAAC;AAED,MAAI,CAAC,QAAQ,IAAI;AACf,eAAW,QAAQ,SAAS,0CAA0C,WAAW,EAAE;AACnF,WAAO;AAAA,EACT;AAGA,QAAM,OAAO,MAAM,aAAa,SAAS;AACzC,OAAK,SAAS,gBAAgB;AAC9B,QAAM,aAAa,WAAW,IAAI;AAElC,eAAa,sBAAsB,SAAS,KAAK,KAAK,YAAY,MAAM,WAAW;AACnF,SAAO;AACT;","names":[]}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
issuePlaceholders,
|
|
4
|
+
resolveCommandIssue,
|
|
3
5
|
runHeadless
|
|
4
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SVOWWZQJ.js";
|
|
5
7
|
import {
|
|
6
8
|
applyPlaceholders,
|
|
7
9
|
loadPrompt
|
|
@@ -12,7 +14,7 @@ import {
|
|
|
12
14
|
printError,
|
|
13
15
|
printSuccess,
|
|
14
16
|
saveTaskPlan
|
|
15
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-WZDCTAWZ.js";
|
|
16
18
|
|
|
17
19
|
// src/commands/review.ts
|
|
18
20
|
import { join } from "path";
|
|
@@ -38,14 +40,19 @@ function parseReviewResult(output) {
|
|
|
38
40
|
findings: findingsMatch?.[1]?.trim() ?? "Unknown findings"
|
|
39
41
|
};
|
|
40
42
|
}
|
|
41
|
-
async function runReview(issue) {
|
|
43
|
+
async function runReview(issue, resolvedIssue) {
|
|
42
44
|
const issueNumber = issue.replace(/^#/, "");
|
|
43
45
|
const issueDir = join("issues", issueNumber);
|
|
44
46
|
const tasksPath = join(issueDir, "tasks.json");
|
|
47
|
+
const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);
|
|
48
|
+
if (!resolution.ok) {
|
|
49
|
+
return resolution.code;
|
|
50
|
+
}
|
|
45
51
|
const template = await loadPrompt("review");
|
|
46
52
|
const prompt = applyPlaceholders(template, {
|
|
47
53
|
__ISSUE_NUMBER__: issueNumber,
|
|
48
|
-
__TASKS_PATH__: tasksPath
|
|
54
|
+
__TASKS_PATH__: tasksPath,
|
|
55
|
+
...issuePlaceholders(resolution.resolved)
|
|
49
56
|
});
|
|
50
57
|
const result = await runHeadless({
|
|
51
58
|
prompt,
|
|
@@ -80,4 +87,4 @@ async function runReview(issue) {
|
|
|
80
87
|
export {
|
|
81
88
|
runReview
|
|
82
89
|
};
|
|
83
|
-
//# sourceMappingURL=chunk-
|
|
90
|
+
//# sourceMappingURL=chunk-JTSAL3TI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/review.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.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 { 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 issueDir = join('issues', issueNumber);\n const tasksPath = join(issueDir, 'tasks.json');\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 __ISSUE_NUMBER__: issueNumber,\n __TASKS_PATH__: tasksPath,\n ...issuePlaceholders(resolution.resolved),\n });\n\n const result = await runHeadless({\n prompt,\n maxTurns: 25,\n timeout: getGlobalTimeout() ?? 300_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n statusMessage: `Reviewing issue #${issueNumber} resolution...`,\n });\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 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 return 1;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA,SAAS,YAAY;AAiBrB,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,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,YAAY,KAAK,UAAU,YAAY;AAE7C,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,IACzC,kBAAkB;AAAA,IAClB,gBAAgB;AAAA,IAChB,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,SAAS,iBAAiB,KAAK;AAAA,IAC/B,cAAc;AAAA,IACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,MAAM;AAAA,IAC7C,eAAe,oBAAoB,WAAW;AAAA,EAChD,CAAC;AAED,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,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;AACA,SAAO;AACT;","names":["findingsMatch"]}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runPhaseWithRetry
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2CSHLHYP.js";
|
|
5
|
+
import {
|
|
6
|
+
issuePlaceholders,
|
|
7
|
+
resolveCommandIssue,
|
|
8
|
+
runHeadless
|
|
9
|
+
} from "./chunk-SVOWWZQJ.js";
|
|
5
10
|
import {
|
|
6
11
|
isTransientFailure
|
|
7
12
|
} from "./chunk-HDV3YDTG.js";
|
|
8
|
-
import {
|
|
9
|
-
runHeadless
|
|
10
|
-
} from "./chunk-KLBAHTXX.js";
|
|
11
13
|
import {
|
|
12
14
|
applyPlaceholders,
|
|
13
15
|
loadPrompt
|
|
@@ -18,7 +20,7 @@ import {
|
|
|
18
20
|
printError,
|
|
19
21
|
printSuccess,
|
|
20
22
|
saveTaskPlan
|
|
21
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-WZDCTAWZ.js";
|
|
22
24
|
|
|
23
25
|
// src/commands/pr.ts
|
|
24
26
|
import { join } from "path";
|
|
@@ -27,10 +29,20 @@ function parsePrUrl(output) {
|
|
|
27
29
|
const match = output.match(/(https:\/\/github\.com\/[^\s]+\/pull\/\d+)/);
|
|
28
30
|
return match?.[1] ?? null;
|
|
29
31
|
}
|
|
30
|
-
|
|
32
|
+
function issueClosesLine(issue, fallbackId) {
|
|
33
|
+
if (issue.remoteRef === null) {
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
return `Closes #${issue.number ?? fallbackId}`;
|
|
37
|
+
}
|
|
38
|
+
async function runPr(issue, resolvedIssue) {
|
|
31
39
|
const issueNumber = issue.replace(/^#/, "");
|
|
32
40
|
const issueDir = join("issues", issueNumber);
|
|
33
41
|
const tasksPath = join(issueDir, "tasks.json");
|
|
42
|
+
const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);
|
|
43
|
+
if (!resolution.ok) {
|
|
44
|
+
return resolution.code;
|
|
45
|
+
}
|
|
34
46
|
let branchName;
|
|
35
47
|
try {
|
|
36
48
|
const proc = await execa("git", ["branch", "--show-current"], { reject: false });
|
|
@@ -47,7 +59,9 @@ async function runPr(issue) {
|
|
|
47
59
|
const prompt = applyPlaceholders(template, {
|
|
48
60
|
__ISSUE_NUMBER__: issueNumber,
|
|
49
61
|
__BRANCH_NAME__: branchName,
|
|
50
|
-
__TASKS_PATH__: tasksPath
|
|
62
|
+
__TASKS_PATH__: tasksPath,
|
|
63
|
+
__ISSUE_CLOSES__: issueClosesLine(resolution.resolved.issue, issueNumber),
|
|
64
|
+
...issuePlaceholders(resolution.resolved)
|
|
51
65
|
});
|
|
52
66
|
let headlessOutput = "";
|
|
53
67
|
const outcome = await runPhaseWithRetry({
|
|
@@ -94,4 +108,4 @@ async function runPr(issue) {
|
|
|
94
108
|
export {
|
|
95
109
|
runPr
|
|
96
110
|
};
|
|
97
|
-
//# sourceMappingURL=chunk-
|
|
111
|
+
//# sourceMappingURL=chunk-KWGRBNUG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/pr.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { execa } from 'execa';\nimport { runHeadless } from '../core/headless.js';\nimport { runPhaseWithRetry } from '../core/phase-runner.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { Issue, ResolvedIssue } from '../issues/types.js';\nimport { printError, printSuccess } from '../ui/logger.js';\nimport { isTransientFailure } from '../utils/retry.js';\n\n/**\n * Extract a PR URL from headless output.\n */\nfunction parsePrUrl(output: string): string | null {\n const match = output.match(/(https:\\/\\/github\\.com\\/[^\\s]+\\/pull\\/\\d+)/);\n return match?.[1] ?? null;\n}\n\n/**\n * The `Closes #N` line for the PR body, empty when the Issue has no remote\n * counterpart: GitHub only understands the reference for Issues it hosts, and\n * an invented `#N` would silently point at an unrelated Issue.\n */\nfunction issueClosesLine(issue: Issue, fallbackId: string): string {\n if (issue.remoteRef === null) {\n return '';\n }\n return `Closes #${issue.number ?? fallbackId}`;\n}\n\nexport async function runPr(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const issueDir = join('issues', issueNumber);\n const tasksPath = join(issueDir, 'tasks.json');\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n // Get current branch\n let branchName: string;\n try {\n const proc = await execa('git', ['branch', '--show-current'], { reject: false });\n branchName = proc.stdout?.toString().trim() ?? '';\n if (!branchName) {\n printError('Could not determine current branch');\n return 1;\n }\n } catch {\n printError('Failed to get current branch');\n return 1;\n }\n\n const template = await loadPrompt('pr');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __BRANCH_NAME__: branchName,\n __TASKS_PATH__: tasksPath,\n __ISSUE_CLOSES__: issueClosesLine(resolution.resolved.issue, issueNumber),\n ...issuePlaceholders(resolution.resolved),\n });\n\n let headlessOutput = '';\n\n const outcome = await runPhaseWithRetry({\n phase: 'pr',\n attempt: async () => {\n const result = await runHeadless({\n prompt,\n maxTurns: 15,\n timeout: getGlobalTimeout() ?? 300_000,\n outputFormat: 'text',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n statusMessage: `Creating PR for issue #${issueNumber}...`,\n });\n\n if (!result.success) {\n return {\n ok: false,\n transient: isTransientFailure(1, result.error ?? ''),\n error: `PR creation failed: ${result.error}`,\n };\n }\n\n headlessOutput = result.result;\n return { ok: true };\n },\n });\n\n if (!outcome.ok) {\n printError(outcome.error ?? `PR creation failed for issue #${issueNumber}`);\n return 1;\n }\n\n const prUrl = parsePrUrl(headlessOutput);\n\n // Update pipeline state\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.prCreated = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist\n }\n\n if (prUrl) {\n printSuccess(`PR created: ${prUrl}`);\n } else {\n printSuccess('PR creation completed');\n }\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,YAAY;AACrB,SAAS,aAAa;AActB,SAAS,WAAW,QAA+B;AACjD,QAAM,QAAQ,OAAO,MAAM,4CAA4C;AACvE,SAAO,QAAQ,CAAC,KAAK;AACvB;AAOA,SAAS,gBAAgB,OAAc,YAA4B;AACjE,MAAI,MAAM,cAAc,MAAM;AAC5B,WAAO;AAAA,EACT;AACA,SAAO,WAAW,MAAM,UAAU,UAAU;AAC9C;AAEA,eAAsB,MAAM,OAAe,eAAgD;AACzF,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,WAAW,KAAK,UAAU,WAAW;AAC3C,QAAM,YAAY,KAAK,UAAU,YAAY;AAE7C,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAGA,MAAI;AACJ,MAAI;AACF,UAAM,OAAO,MAAM,MAAM,OAAO,CAAC,UAAU,gBAAgB,GAAG,EAAE,QAAQ,MAAM,CAAC;AAC/E,iBAAa,KAAK,QAAQ,SAAS,EAAE,KAAK,KAAK;AAC/C,QAAI,CAAC,YAAY;AACf,iBAAW,oCAAoC;AAC/C,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AACN,eAAW,8BAA8B;AACzC,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,MAAM,WAAW,IAAI;AACtC,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,kBAAkB,gBAAgB,WAAW,SAAS,OAAO,WAAW;AAAA,IACxE,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,MAAI,iBAAiB;AAErB,QAAM,UAAU,MAAM,kBAAkB;AAAA,IACtC,OAAO;AAAA,IACP,SAAS,YAAY;AACnB,YAAM,SAAS,MAAM,YAAY;AAAA,QAC/B;AAAA,QACA,UAAU;AAAA,QACV,SAAS,iBAAiB,KAAK;AAAA,QAC/B,cAAc;AAAA,QACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,MAAM;AAAA,QAC7C,eAAe,0BAA0B,WAAW;AAAA,MACtD,CAAC;AAED,UAAI,CAAC,OAAO,SAAS;AACnB,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,WAAW,mBAAmB,GAAG,OAAO,SAAS,EAAE;AAAA,UACnD,OAAO,uBAAuB,OAAO,KAAK;AAAA,QAC5C;AAAA,MACF;AAEA,uBAAiB,OAAO;AACxB,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB;AAAA,EACF,CAAC;AAED,MAAI,CAAC,QAAQ,IAAI;AACf,eAAW,QAAQ,SAAS,iCAAiC,WAAW,EAAE;AAC1E,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,WAAW,cAAc;AAGvC,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,YAAY;AAC1B,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,MAAI,OAAO;AACT,iBAAa,eAAe,KAAK,EAAE;AAAA,EACrC,OAAO;AACL,iBAAa,uBAAuB;AAAA,EACtC;AACA,SAAO;AACT;","names":[]}
|