issue-flow 0.5.1 → 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.
Files changed (75) hide show
  1. package/README.md +4 -4
  2. package/dist/{analyze-IJ2SYXVM.js → analyze-VVG7UABS.js} +15 -5
  3. package/dist/analyze-VVG7UABS.js.map +1 -0
  4. package/dist/chunk-2CSHLHYP.js +58 -0
  5. package/dist/chunk-2CSHLHYP.js.map +1 -0
  6. package/dist/{chunk-TYOFZ6DD.js → chunk-4HT4EVYV.js} +78 -32
  7. package/dist/chunk-4HT4EVYV.js.map +1 -0
  8. package/dist/{chunk-GYR4YHEB.js → chunk-7W5VGYXW.js} +12 -51
  9. package/dist/chunk-7W5VGYXW.js.map +1 -0
  10. package/dist/chunk-EHX4J5G5.js +91 -0
  11. package/dist/chunk-EHX4J5G5.js.map +1 -0
  12. package/dist/chunk-FPGHTTVV.js +111 -0
  13. package/dist/chunk-FPGHTTVV.js.map +1 -0
  14. package/dist/chunk-HDV3YDTG.js +52 -0
  15. package/dist/chunk-HDV3YDTG.js.map +1 -0
  16. package/dist/{chunk-XJJP4TSK.js → chunk-JTSAL3TI.js} +12 -5
  17. package/dist/chunk-JTSAL3TI.js.map +1 -0
  18. package/dist/chunk-KWGRBNUG.js +111 -0
  19. package/dist/chunk-KWGRBNUG.js.map +1 -0
  20. package/dist/chunk-SVOWWZQJ.js +982 -0
  21. package/dist/chunk-SVOWWZQJ.js.map +1 -0
  22. package/dist/{chunk-FNLXZXWP.js → chunk-WZDCTAWZ.js} +62 -23
  23. package/dist/chunk-WZDCTAWZ.js.map +1 -0
  24. package/dist/{chunk-Q2TQCJDN.js → chunk-YM3G5BLR.js} +21 -9
  25. package/dist/chunk-YM3G5BLR.js.map +1 -0
  26. package/dist/cli.js +131 -30
  27. package/dist/cli.js.map +1 -1
  28. package/dist/config-B564P45C.js +29 -0
  29. package/dist/execute-PNOFNLZG.js +12 -0
  30. package/dist/generate-IN62ARBS.js +143 -0
  31. package/dist/generate-IN62ARBS.js.map +1 -0
  32. package/dist/init-5242XSYC.js +9 -0
  33. package/dist/plan-6KLONWPR.js +14 -0
  34. package/dist/pr-JLJLMFN3.js +14 -0
  35. package/dist/prd-TVJDYZXH.js +14 -0
  36. package/dist/review-FRV4ZMS2.js +12 -0
  37. package/dist/review-FRV4ZMS2.js.map +1 -0
  38. package/dist/{run-UCSNYSQO.js → run-GPBICAD6.js} +45 -24
  39. package/dist/run-GPBICAD6.js.map +1 -0
  40. package/package.json +2 -1
  41. package/prompts/analyze.md +18 -5
  42. package/prompts/generate.md +27 -6
  43. package/prompts/plan.md +19 -2
  44. package/prompts/pr.md +23 -6
  45. package/prompts/prd.md +18 -5
  46. package/prompts/review.md +18 -6
  47. package/dist/analyze-IJ2SYXVM.js.map +0 -1
  48. package/dist/chunk-2XWXVWVD.js +0 -283
  49. package/dist/chunk-2XWXVWVD.js.map +0 -1
  50. package/dist/chunk-AY7UZAGX.js +0 -83
  51. package/dist/chunk-AY7UZAGX.js.map +0 -1
  52. package/dist/chunk-FNLXZXWP.js.map +0 -1
  53. package/dist/chunk-GYR4YHEB.js.map +0 -1
  54. package/dist/chunk-MKQIP7YW.js +0 -76
  55. package/dist/chunk-MKQIP7YW.js.map +0 -1
  56. package/dist/chunk-Q2TQCJDN.js.map +0 -1
  57. package/dist/chunk-SI4ALORB.js +0 -66
  58. package/dist/chunk-SI4ALORB.js.map +0 -1
  59. package/dist/chunk-TYOFZ6DD.js.map +0 -1
  60. package/dist/chunk-XJJP4TSK.js.map +0 -1
  61. package/dist/execute-DALVYZIH.js +0 -11
  62. package/dist/generate-5OWWNRIB.js +0 -48
  63. package/dist/generate-5OWWNRIB.js.map +0 -1
  64. package/dist/init-6AWQI3IE.js +0 -9
  65. package/dist/plan-RQXK2OCP.js +0 -11
  66. package/dist/pr-UU4H7ZZR.js +0 -11
  67. package/dist/prd-WB3QH6QF.js +0 -11
  68. package/dist/review-2WHYJG7M.js +0 -11
  69. package/dist/run-UCSNYSQO.js.map +0 -1
  70. /package/dist/{execute-DALVYZIH.js.map → config-B564P45C.js.map} +0 -0
  71. /package/dist/{init-6AWQI3IE.js.map → execute-PNOFNLZG.js.map} +0 -0
  72. /package/dist/{plan-RQXK2OCP.js.map → init-5242XSYC.js.map} +0 -0
  73. /package/dist/{pr-UU4H7ZZR.js.map → plan-6KLONWPR.js.map} +0 -0
  74. /package/dist/{prd-WB3QH6QF.js.map → pr-JLJLMFN3.js.map} +0 -0
  75. /package/dist/{review-2WHYJG7M.js.map → prd-TVJDYZXH.js.map} +0 -0
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ readFileWithGrace,
4
+ runPhaseWithRetry
5
+ } from "./chunk-2CSHLHYP.js";
6
+ import {
7
+ issuePlaceholders,
8
+ resolveCommandIssue,
9
+ runHeadless
10
+ } from "./chunk-SVOWWZQJ.js";
11
+ import {
12
+ isTransientFailure
13
+ } from "./chunk-HDV3YDTG.js";
14
+ import {
15
+ applyPlaceholders,
16
+ loadPrompt
17
+ } from "./chunk-SLHZPB3C.js";
18
+ import {
19
+ getGlobalTimeout,
20
+ loadTaskPlan,
21
+ printError,
22
+ printSuccess,
23
+ saveTaskPlan,
24
+ taskPlanSchema
25
+ } from "./chunk-WZDCTAWZ.js";
26
+
27
+ // src/commands/plan.ts
28
+ import { mkdir, readFile } from "fs/promises";
29
+ import { join } from "path";
30
+ async function runPlan(issue, resolvedIssue) {
31
+ const issueNumber = issue.replace(/^#/, "");
32
+ const issueDir = join("issues", issueNumber);
33
+ const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);
34
+ if (!resolution.ok) {
35
+ return resolution.code;
36
+ }
37
+ const prdPath = join(issueDir, "prd.md");
38
+ let prdContent;
39
+ try {
40
+ prdContent = await readFile(prdPath, "utf-8");
41
+ } catch {
42
+ printError(`PRD not found at ${prdPath}. Run 'issue-flow prd ${issueNumber}' first.`);
43
+ return 1;
44
+ }
45
+ const tasksPath = join(issueDir, "tasks.json");
46
+ const template = await loadPrompt("plan");
47
+ const prompt = applyPlaceholders(template, {
48
+ __ISSUE_NUMBER__: issueNumber,
49
+ __PRD_CONTENT__: prdContent,
50
+ __TASKS_PATH__: tasksPath,
51
+ ...issuePlaceholders(resolution.resolved)
52
+ });
53
+ await mkdir(issueDir, { recursive: true });
54
+ const outcome = await runPhaseWithRetry({
55
+ phase: "plan",
56
+ attempt: async () => {
57
+ const result = await runHeadless({
58
+ prompt,
59
+ maxTurns: 25,
60
+ timeout: getGlobalTimeout() ?? 3e5,
61
+ outputFormat: "text",
62
+ allowedTools: ["Bash", "Read", "Glob", "Grep", "Write"],
63
+ statusMessage: `Converting PRD to task plan for issue #${issueNumber}...`
64
+ });
65
+ if (!result.success) {
66
+ return {
67
+ ok: false,
68
+ transient: isTransientFailure(1, result.error ?? ""),
69
+ error: `Task plan generation failed: ${result.error}`
70
+ };
71
+ }
72
+ let rawContent;
73
+ try {
74
+ rawContent = await readFileWithGrace(tasksPath);
75
+ } catch {
76
+ return { ok: false, transient: true, error: `tasks.json was not created at ${tasksPath}` };
77
+ }
78
+ let parsed;
79
+ try {
80
+ parsed = JSON.parse(rawContent);
81
+ } catch {
82
+ return { ok: false, transient: true, error: "tasks.json contains invalid JSON" };
83
+ }
84
+ const validation = taskPlanSchema.safeParse(parsed);
85
+ if (!validation.success) {
86
+ const issues = validation.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
87
+ return {
88
+ ok: false,
89
+ transient: true,
90
+ error: `tasks.json does not match expected schema:
91
+ ${issues}`
92
+ };
93
+ }
94
+ return { ok: true };
95
+ }
96
+ });
97
+ if (!outcome.ok) {
98
+ printError(outcome.error ?? `Task plan generation failed for issue #${issueNumber}`);
99
+ return 1;
100
+ }
101
+ const plan = await loadTaskPlan(tasksPath);
102
+ plan.pipeline.jsonCompleted = true;
103
+ await saveTaskPlan(tasksPath, plan);
104
+ printSuccess(`Task plan saved to ${tasksPath} (${plan.userStories.length} stories)`);
105
+ return 0;
106
+ }
107
+
108
+ export {
109
+ runPlan
110
+ };
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":[]}
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/utils/retry.ts
4
+ var TRANSIENT_PATTERNS = [
5
+ "timed out",
6
+ "timeout",
7
+ "connection reset",
8
+ "connection refused",
9
+ "connection aborted",
10
+ "network error",
11
+ "network unavailable",
12
+ "temporary failure",
13
+ "temporarily unavailable",
14
+ "service unavailable",
15
+ "overloaded",
16
+ "rate limit",
17
+ "too many requests",
18
+ "bad gateway",
19
+ "gateway timeout",
20
+ "internal server error",
21
+ "http 429",
22
+ "http 500",
23
+ "http 502",
24
+ "http 503",
25
+ "http 504",
26
+ "econnreset",
27
+ "econnrefused",
28
+ "enotfound",
29
+ "etimedout",
30
+ "socket hang up"
31
+ ];
32
+ function isTransientFailure(exitCode, output) {
33
+ if (exitCode === 75) {
34
+ return true;
35
+ }
36
+ const lowered = output.toLowerCase();
37
+ return TRANSIENT_PATTERNS.some((pattern) => lowered.includes(pattern));
38
+ }
39
+ function retryDelaySeconds(attempt, baseSeconds = 30, maxSeconds = 900) {
40
+ const delay = baseSeconds * 2 ** (attempt - 1);
41
+ return Math.min(delay, maxSeconds);
42
+ }
43
+ function sleep(seconds) {
44
+ return new Promise((resolve) => setTimeout(resolve, seconds * 1e3));
45
+ }
46
+
47
+ export {
48
+ isTransientFailure,
49
+ retryDelaySeconds,
50
+ sleep
51
+ };
52
+ //# sourceMappingURL=chunk-HDV3YDTG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/retry.ts"],"sourcesContent":["/**\n * Transient failure patterns — matching the Bash script's detection heuristics.\n * These are checked case-insensitively against the combined output.\n */\nconst TRANSIENT_PATTERNS = [\n 'timed out',\n 'timeout',\n 'connection reset',\n 'connection refused',\n 'connection aborted',\n 'network error',\n 'network unavailable',\n 'temporary failure',\n 'temporarily unavailable',\n 'service unavailable',\n 'overloaded',\n 'rate limit',\n 'too many requests',\n 'bad gateway',\n 'gateway timeout',\n 'internal server error',\n 'http 429',\n 'http 500',\n 'http 502',\n 'http 503',\n 'http 504',\n 'econnreset',\n 'econnrefused',\n 'enotfound',\n 'etimedout',\n 'socket hang up',\n];\n\n/**\n * Determine if a Claude CLI failure is transient (retryable).\n *\n * A failure is considered transient if:\n * - Exit code is 75 (EX_TEMPFAIL)\n * - Output contains known transient error patterns\n */\nexport function isTransientFailure(exitCode: number, output: string): boolean {\n // Exit code 75 = EX_TEMPFAIL\n if (exitCode === 75) {\n return true;\n }\n\n const lowered = output.toLowerCase();\n return TRANSIENT_PATTERNS.some((pattern) => lowered.includes(pattern));\n}\n\n/**\n * Calculate retry delay using exponential backoff.\n *\n * delay = baseSeconds * 2^(attempt-1), capped at maxSeconds\n *\n * @param attempt - The retry attempt number (1-based)\n * @param baseSeconds - Base delay in seconds (default: 30)\n * @param maxSeconds - Maximum delay in seconds (default: 900)\n */\nexport function retryDelaySeconds(\n attempt: number,\n baseSeconds: number = 30,\n maxSeconds: number = 900,\n): number {\n const delay = baseSeconds * 2 ** (attempt - 1);\n return Math.min(delay, maxSeconds);\n}\n\n/**\n * Sleep for a given number of seconds.\n */\nexport function sleep(seconds: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, seconds * 1000));\n}\n"],"mappings":";;;AAIA,IAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AASO,SAAS,mBAAmB,UAAkB,QAAyB;AAE5E,MAAI,aAAa,IAAI;AACnB,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,OAAO,YAAY;AACnC,SAAO,mBAAmB,KAAK,CAAC,YAAY,QAAQ,SAAS,OAAO,CAAC;AACvE;AAWO,SAAS,kBACd,SACA,cAAsB,IACtB,aAAqB,KACb;AACR,QAAM,QAAQ,cAAc,MAAM,UAAU;AAC5C,SAAO,KAAK,IAAI,OAAO,UAAU;AACnC;AAKO,SAAS,MAAM,SAAgC;AACpD,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,UAAU,GAAI,CAAC;AACrE;","names":[]}
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ issuePlaceholders,
4
+ resolveCommandIssue,
3
5
  runHeadless
4
- } from "./chunk-2XWXVWVD.js";
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-FNLXZXWP.js";
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-XJJP4TSK.js.map
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"]}
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runPhaseWithRetry
4
+ } from "./chunk-2CSHLHYP.js";
5
+ import {
6
+ issuePlaceholders,
7
+ resolveCommandIssue,
8
+ runHeadless
9
+ } from "./chunk-SVOWWZQJ.js";
10
+ import {
11
+ isTransientFailure
12
+ } from "./chunk-HDV3YDTG.js";
13
+ import {
14
+ applyPlaceholders,
15
+ loadPrompt
16
+ } from "./chunk-SLHZPB3C.js";
17
+ import {
18
+ getGlobalTimeout,
19
+ loadTaskPlan,
20
+ printError,
21
+ printSuccess,
22
+ saveTaskPlan
23
+ } from "./chunk-WZDCTAWZ.js";
24
+
25
+ // src/commands/pr.ts
26
+ import { join } from "path";
27
+ import { execa } from "execa";
28
+ function parsePrUrl(output) {
29
+ const match = output.match(/(https:\/\/github\.com\/[^\s]+\/pull\/\d+)/);
30
+ return match?.[1] ?? null;
31
+ }
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) {
39
+ const issueNumber = issue.replace(/^#/, "");
40
+ const issueDir = join("issues", issueNumber);
41
+ const tasksPath = join(issueDir, "tasks.json");
42
+ const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);
43
+ if (!resolution.ok) {
44
+ return resolution.code;
45
+ }
46
+ let branchName;
47
+ try {
48
+ const proc = await execa("git", ["branch", "--show-current"], { reject: false });
49
+ branchName = proc.stdout?.toString().trim() ?? "";
50
+ if (!branchName) {
51
+ printError("Could not determine current branch");
52
+ return 1;
53
+ }
54
+ } catch {
55
+ printError("Failed to get current branch");
56
+ return 1;
57
+ }
58
+ const template = await loadPrompt("pr");
59
+ const prompt = applyPlaceholders(template, {
60
+ __ISSUE_NUMBER__: issueNumber,
61
+ __BRANCH_NAME__: branchName,
62
+ __TASKS_PATH__: tasksPath,
63
+ __ISSUE_CLOSES__: issueClosesLine(resolution.resolved.issue, issueNumber),
64
+ ...issuePlaceholders(resolution.resolved)
65
+ });
66
+ let headlessOutput = "";
67
+ const outcome = await runPhaseWithRetry({
68
+ phase: "pr",
69
+ attempt: async () => {
70
+ const result = await runHeadless({
71
+ prompt,
72
+ maxTurns: 15,
73
+ timeout: getGlobalTimeout() ?? 3e5,
74
+ outputFormat: "text",
75
+ allowedTools: ["Bash", "Read", "Glob", "Grep"],
76
+ statusMessage: `Creating PR for issue #${issueNumber}...`
77
+ });
78
+ if (!result.success) {
79
+ return {
80
+ ok: false,
81
+ transient: isTransientFailure(1, result.error ?? ""),
82
+ error: `PR creation failed: ${result.error}`
83
+ };
84
+ }
85
+ headlessOutput = result.result;
86
+ return { ok: true };
87
+ }
88
+ });
89
+ if (!outcome.ok) {
90
+ printError(outcome.error ?? `PR creation failed for issue #${issueNumber}`);
91
+ return 1;
92
+ }
93
+ const prUrl = parsePrUrl(headlessOutput);
94
+ try {
95
+ const plan = await loadTaskPlan(tasksPath);
96
+ plan.pipeline.prCreated = true;
97
+ await saveTaskPlan(tasksPath, plan);
98
+ } catch {
99
+ }
100
+ if (prUrl) {
101
+ printSuccess(`PR created: ${prUrl}`);
102
+ } else {
103
+ printSuccess("PR creation completed");
104
+ }
105
+ return 0;
106
+ }
107
+
108
+ export {
109
+ runPr
110
+ };
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":[]}