issue-flow 0.11.0 → 0.12.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 (118) hide show
  1. package/README.md +1 -1
  2. package/dist/{analyze-7B5TORNK.js → analyze-OEET7KGE.js} +17 -11
  3. package/dist/analyze-OEET7KGE.js.map +1 -0
  4. package/dist/{apply-WUJHDD7A.js → apply-ZIA3C52S.js} +6 -4
  5. package/dist/{apply-WUJHDD7A.js.map → apply-ZIA3C52S.js.map} +1 -1
  6. package/dist/{chunk-6N7LZZ5U.js → chunk-3QHK4YQA.js} +204 -107
  7. package/dist/chunk-3QHK4YQA.js.map +1 -0
  8. package/dist/chunk-42PJFMAR.js +605 -0
  9. package/dist/chunk-42PJFMAR.js.map +1 -0
  10. package/dist/{chunk-PL6CNMVS.js → chunk-4JSOYE4G.js} +105 -19
  11. package/dist/chunk-4JSOYE4G.js.map +1 -0
  12. package/dist/chunk-56KS3TFQ.js +703 -0
  13. package/dist/chunk-56KS3TFQ.js.map +1 -0
  14. package/dist/{chunk-AAY6KM7N.js → chunk-73ATPIYX.js} +160 -154
  15. package/dist/chunk-73ATPIYX.js.map +1 -0
  16. package/dist/{chunk-MKD2D7WE.js → chunk-A4SLHOW7.js} +70 -13
  17. package/dist/chunk-A4SLHOW7.js.map +1 -0
  18. package/dist/chunk-BAUPQXPE.js +173 -0
  19. package/dist/chunk-BAUPQXPE.js.map +1 -0
  20. package/dist/{chunk-HC2MT7BX.js → chunk-BYZDPR7N.js} +14 -581
  21. package/dist/chunk-BYZDPR7N.js.map +1 -0
  22. package/dist/chunk-CD5ULRVC.js +615 -0
  23. package/dist/chunk-CD5ULRVC.js.map +1 -0
  24. package/dist/chunk-DIDUVL6O.js +1365 -0
  25. package/dist/chunk-DIDUVL6O.js.map +1 -0
  26. package/dist/{chunk-47VIN33V.js → chunk-EZATO3DW.js} +7 -5
  27. package/dist/{chunk-47VIN33V.js.map → chunk-EZATO3DW.js.map} +1 -1
  28. package/dist/{chunk-JW7WLSZ6.js → chunk-FADTCQGM.js} +48 -12
  29. package/dist/chunk-FADTCQGM.js.map +1 -0
  30. package/dist/{chunk-GANSDJV7.js → chunk-FJP3UQGD.js} +15 -13
  31. package/dist/chunk-FJP3UQGD.js.map +1 -0
  32. package/dist/chunk-KMMASOA6.js +134 -0
  33. package/dist/chunk-KMMASOA6.js.map +1 -0
  34. package/dist/{chunk-HQYXVIH6.js → chunk-LAFNLLQP.js} +3 -3
  35. package/dist/chunk-LWRSMTTZ.js +18 -0
  36. package/dist/chunk-LWRSMTTZ.js.map +1 -0
  37. package/dist/{chunk-MSPUB635.js → chunk-M5UCXTIT.js} +15 -12
  38. package/dist/chunk-M5UCXTIT.js.map +1 -0
  39. package/dist/chunk-M66SJHQQ.js +206 -0
  40. package/dist/chunk-M66SJHQQ.js.map +1 -0
  41. package/dist/{chunk-OBLVLIUS.js → chunk-MJULIKQU.js} +42 -6
  42. package/dist/chunk-MJULIKQU.js.map +1 -0
  43. package/dist/{chunk-RKTEV6EG.js → chunk-NS6R37II.js} +4 -4
  44. package/dist/{run-H75MD5BQ.js → chunk-NTMMF5UD.js} +515 -544
  45. package/dist/chunk-NTMMF5UD.js.map +1 -0
  46. package/dist/{chunk-UBIQXCVE.js → chunk-QCPGHBSZ.js} +16 -13
  47. package/dist/chunk-QCPGHBSZ.js.map +1 -0
  48. package/dist/{chunk-LVKQ2AKT.js → chunk-TSOLWK7Y.js} +8 -6
  49. package/dist/chunk-TSOLWK7Y.js.map +1 -0
  50. package/dist/{chunk-VCKX262Z.js → chunk-XQUSIEKA.js} +13 -10
  51. package/dist/chunk-XQUSIEKA.js.map +1 -0
  52. package/dist/chunk-Y5ZDJ4PJ.js +76 -0
  53. package/dist/chunk-Y5ZDJ4PJ.js.map +1 -0
  54. package/dist/cli.js +156 -26
  55. package/dist/cli.js.map +1 -1
  56. package/dist/{config-4THHOXNN.js → config-PRJGWW7J.js} +15 -3
  57. package/dist/execute-XBNVL753.js +18 -0
  58. package/dist/{generate-6RA2MFJB.js → generate-EGB4KV2B.js} +18 -11
  59. package/dist/generate-EGB4KV2B.js.map +1 -0
  60. package/dist/init-AJHCXJBP.js +10 -0
  61. package/dist/operations-BYT626KQ.js +365 -0
  62. package/dist/operations-BYT626KQ.js.map +1 -0
  63. package/dist/plan-QOZ3RIO6.js +21 -0
  64. package/dist/{policy-XQW5NUIE.js → policy-6S4GFC76.js} +6 -4
  65. package/dist/{policy-XQW5NUIE.js.map → policy-6S4GFC76.js.map} +1 -1
  66. package/dist/pr-EBBHD2ZX.js +26 -0
  67. package/dist/pr-review-VVMW5PO4.js +20 -0
  68. package/dist/prd-JQSZF7PR.js +21 -0
  69. package/dist/resume-PPCNQVVV.js +221 -0
  70. package/dist/resume-PPCNQVVV.js.map +1 -0
  71. package/dist/review-3F6MARWH.js +19 -0
  72. package/dist/run-RXE5GDQO.js +36 -0
  73. package/dist/run-RXE5GDQO.js.map +1 -0
  74. package/dist/state-manager-2EDVDBAR.js +34 -0
  75. package/dist/state-manager-2EDVDBAR.js.map +1 -0
  76. package/dist/{web-ANQQTTRD.js → web-EF5WIIGG.js} +13 -8
  77. package/dist/{web-ANQQTTRD.js.map → web-EF5WIIGG.js.map} +1 -1
  78. package/package.json +1 -1
  79. package/prompts/pr.md +11 -4
  80. package/web/public/app.js +12 -0
  81. package/dist/analyze-7B5TORNK.js.map +0 -1
  82. package/dist/chunk-3BOHTH4V.js +0 -58
  83. package/dist/chunk-3BOHTH4V.js.map +0 -1
  84. package/dist/chunk-6N7LZZ5U.js.map +0 -1
  85. package/dist/chunk-AAY6KM7N.js.map +0 -1
  86. package/dist/chunk-GANSDJV7.js.map +0 -1
  87. package/dist/chunk-HC2MT7BX.js.map +0 -1
  88. package/dist/chunk-HDV3YDTG.js +0 -52
  89. package/dist/chunk-HDV3YDTG.js.map +0 -1
  90. package/dist/chunk-JW7WLSZ6.js.map +0 -1
  91. package/dist/chunk-LVKQ2AKT.js.map +0 -1
  92. package/dist/chunk-MKD2D7WE.js.map +0 -1
  93. package/dist/chunk-MSPUB635.js.map +0 -1
  94. package/dist/chunk-OBLVLIUS.js.map +0 -1
  95. package/dist/chunk-PL6CNMVS.js.map +0 -1
  96. package/dist/chunk-UBIQXCVE.js.map +0 -1
  97. package/dist/chunk-VCKX262Z.js.map +0 -1
  98. package/dist/chunk-VQ5VN7DF.js +0 -1018
  99. package/dist/chunk-VQ5VN7DF.js.map +0 -1
  100. package/dist/execute-5OEKLM5X.js +0 -15
  101. package/dist/generate-6RA2MFJB.js.map +0 -1
  102. package/dist/init-PRMOTQ7N.js +0 -9
  103. package/dist/plan-KENR6MJ3.js +0 -17
  104. package/dist/pr-KDGKG4BP.js +0 -21
  105. package/dist/pr-review-CAMAB7KO.js +0 -17
  106. package/dist/prd-V3MWDIHK.js +0 -17
  107. package/dist/review-7LN47SAN.js +0 -15
  108. package/dist/run-H75MD5BQ.js.map +0 -1
  109. /package/dist/{chunk-HQYXVIH6.js.map → chunk-LAFNLLQP.js.map} +0 -0
  110. /package/dist/{chunk-RKTEV6EG.js.map → chunk-NS6R37II.js.map} +0 -0
  111. /package/dist/{config-4THHOXNN.js.map → config-PRJGWW7J.js.map} +0 -0
  112. /package/dist/{execute-5OEKLM5X.js.map → execute-XBNVL753.js.map} +0 -0
  113. /package/dist/{init-PRMOTQ7N.js.map → init-AJHCXJBP.js.map} +0 -0
  114. /package/dist/{plan-KENR6MJ3.js.map → plan-QOZ3RIO6.js.map} +0 -0
  115. /package/dist/{pr-KDGKG4BP.js.map → pr-EBBHD2ZX.js.map} +0 -0
  116. /package/dist/{pr-review-CAMAB7KO.js.map → pr-review-VVMW5PO4.js.map} +0 -0
  117. /package/dist/{prd-V3MWDIHK.js.map → prd-JQSZF7PR.js.map} +0 -0
  118. /package/dist/{review-7LN47SAN.js.map → review-3F6MARWH.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/review.ts"],"sourcesContent":["import { DEFAULT_HEADLESS_TIMEOUT_MS, 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() ?? DEFAULT_HEADLESS_TIMEOUT_MS,\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"]}
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getShutdownSignal
4
+ } from "./chunk-M66SJHQQ.js";
5
+ import {
6
+ classify,
7
+ fixedBackoffPolicy,
8
+ withRetry
9
+ } from "./chunk-DIDUVL6O.js";
10
+ import {
11
+ getSessionPublisher,
12
+ printRetry
13
+ } from "./chunk-BYZDPR7N.js";
14
+ import {
15
+ isoNow
16
+ } from "./chunk-CD5ULRVC.js";
17
+
18
+ // src/core/phase-runner.ts
19
+ import { readFile } from "fs/promises";
20
+ var DEFAULT_PHASE_RETRY_LIMIT = 3;
21
+ var DEFAULT_BACKOFF_BASE_SECONDS = 15;
22
+ var DEFAULT_BACKOFF_MAX_SECONDS = 120;
23
+ async function runPhaseWithRetry(options) {
24
+ const retryLimit = options.retryLimit ?? DEFAULT_PHASE_RETRY_LIMIT;
25
+ const backoffBaseSeconds = options.backoffBaseSeconds ?? DEFAULT_BACKOFF_BASE_SECONDS;
26
+ const backoffMaxSeconds = options.backoffMaxSeconds ?? DEFAULT_BACKOFF_MAX_SECONDS;
27
+ if (retryLimit < 1) return { ok: false, error: "Phase never attempted" };
28
+ const outcome = await withRetry((attempt) => options.attempt(attempt), {
29
+ policy: fixedBackoffPolicy(retryLimit, backoffBaseSeconds, backoffMaxSeconds),
30
+ // The caller's own signal when it has one, and the process-wide shutdown
31
+ // otherwise: a backoff nobody can interrupt is a `Ctrl+C` that appears to
32
+ // do nothing for two minutes.
33
+ signal: options.signal ?? getShutdownSignal(),
34
+ evaluate: (result) => result.ok ? null : phaseFailure(result),
35
+ onAttempt: ({ attempt, failure, willRetry, delayMs }) => {
36
+ if (failure === null || !willRetry) return;
37
+ const delaySeconds = delayMs / 1e3;
38
+ getSessionPublisher().publish({
39
+ type: "retry",
40
+ at: isoNow(),
41
+ attempt,
42
+ delaySeconds,
43
+ reason: failure.message,
44
+ kind: failure.kind
45
+ });
46
+ printRetry(
47
+ `Phase ${options.phase} hit a recoverable failure (attempt ${attempt}/${retryLimit}): ${failure.message}. Retrying in ${delaySeconds}s.`
48
+ );
49
+ }
50
+ });
51
+ return outcome.value;
52
+ }
53
+ function phaseFailure(result) {
54
+ const message = result.error ?? "Phase failed";
55
+ return {
56
+ ...classify({ source: "agent", stdout: message }),
57
+ message,
58
+ retryable: result.transient === true
59
+ };
60
+ }
61
+ async function readFileWithGrace(path, delaysMs = [300, 800]) {
62
+ for (let i = 0; ; i++) {
63
+ try {
64
+ return await readFile(path, "utf-8");
65
+ } catch (err) {
66
+ if (i >= delaysMs.length) throw err;
67
+ await new Promise((resolve) => setTimeout(resolve, delaysMs[i]));
68
+ }
69
+ }
70
+ }
71
+
72
+ export {
73
+ runPhaseWithRetry,
74
+ readFileWithGrace
75
+ };
76
+ //# sourceMappingURL=chunk-Y5ZDJ4PJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/phase-runner.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { type ClassifiedFailure, classify } from '../resilience/errors.js';\nimport { fixedBackoffPolicy, withRetry } from '../resilience/retry.js';\nimport { printRetry } from '../ui/logger.js';\nimport { getSessionPublisher } from './session-publisher.js';\nimport { getShutdownSignal } from './shutdown.js';\nimport { isoNow } from './state-manager.js';\n\nexport interface PhaseAttemptResult {\n ok: boolean;\n /** Whether a failed attempt is worth retrying. Ignored when ok is true. */\n transient?: boolean;\n error?: string;\n}\n\nexport interface PhaseRetryOptions {\n phase: string;\n attempt: (attemptNumber: number) => Promise<PhaseAttemptResult>;\n retryLimit?: number;\n backoffBaseSeconds?: number;\n backoffMaxSeconds?: number;\n /** Cuts a pending backoff short, so a phase stops waiting on shutdown. */\n signal?: AbortSignal;\n}\n\nconst DEFAULT_PHASE_RETRY_LIMIT = 3;\nconst DEFAULT_BACKOFF_BASE_SECONDS = 15;\nconst DEFAULT_BACKOFF_MAX_SECONDS = 120;\n\n/**\n * Bounded retry wrapper for single-shot phases (prd, plan, pr) that invoke\n * the Claude CLI once and validate an artifact. Applied to phases that\n * previously aborted the whole pipeline on the first failure with no chance\n * to recover. Non-transient failures (or exhausting the retry budget) still\n * end the phase — this bounds recovery attempts, it never retries forever.\n *\n * The loop itself is `resilience/retry.ts:withRetry`, shared with the\n * `execute` loop of `core/engine.ts`. What stays here is what is specific to a\n * phase: the caller's `transient` verdict, the `retry` event and the printed\n * line. With no options supplied the effective numbers are the ones this\n * module has always used — 3 attempts, 15s doubling to 120s, no jitter.\n */\nexport async function runPhaseWithRetry(options: PhaseRetryOptions): Promise<PhaseAttemptResult> {\n const retryLimit = options.retryLimit ?? DEFAULT_PHASE_RETRY_LIMIT;\n const backoffBaseSeconds = options.backoffBaseSeconds ?? DEFAULT_BACKOFF_BASE_SECONDS;\n const backoffMaxSeconds = options.backoffMaxSeconds ?? DEFAULT_BACKOFF_MAX_SECONDS;\n\n if (retryLimit < 1) return { ok: false, error: 'Phase never attempted' };\n\n const outcome = await withRetry<PhaseAttemptResult>((attempt) => options.attempt(attempt), {\n policy: fixedBackoffPolicy(retryLimit, backoffBaseSeconds, backoffMaxSeconds),\n // The caller's own signal when it has one, and the process-wide shutdown\n // otherwise: a backoff nobody can interrupt is a `Ctrl+C` that appears to\n // do nothing for two minutes.\n signal: options.signal ?? getShutdownSignal(),\n evaluate: (result) => (result.ok ? null : phaseFailure(result)),\n onAttempt: ({ attempt, failure, willRetry, delayMs }) => {\n if (failure === null || !willRetry) return;\n\n const delaySeconds = delayMs / 1000;\n getSessionPublisher().publish({\n type: 'retry',\n at: isoNow(),\n attempt,\n delaySeconds,\n reason: failure.message,\n kind: failure.kind,\n });\n printRetry(\n `Phase ${options.phase} hit a recoverable failure (attempt ${attempt}/${retryLimit}): ${failure.message}. Retrying in ${delaySeconds}s.`,\n );\n },\n });\n\n return outcome.value;\n}\n\n/**\n * Turn a phase's own verdict into the classified failure `withRetry` decides\n * on.\n *\n * `transient` stays authoritative: the phases build it with\n * `isTransientFailure()` over the CLI's output, but a phase can also fail on\n * something it alone can see — a PRD file that never appeared — and that\n * judgement is not recoverable from the error text. `classify()` is consulted\n * only to name the *kind*, which is what the `retry` event now reports.\n */\nfunction phaseFailure(result: PhaseAttemptResult): ClassifiedFailure {\n const message = result.error ?? 'Phase failed';\n return {\n ...classify({ source: 'agent', stdout: message }),\n message,\n retryable: result.transient === true,\n };\n}\n\n/**\n * Re-read a file a few times with short delays before giving up. Absorbs a\n * brief filesystem-visibility lag between the Claude CLI's Write tool\n * completing and this process observing the result, without spending a full\n * phase retry (and a new Claude invocation) on what may just be a timing gap.\n */\nexport async function readFileWithGrace(\n path: string,\n delaysMs: number[] = [300, 800],\n): Promise<string> {\n for (let i = 0; ; i++) {\n try {\n return await readFile(path, 'utf-8');\n } catch (err) {\n if (i >= delaysMs.length) throw err;\n await new Promise((resolve) => setTimeout(resolve, delaysMs[i]));\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA,SAAS,gBAAgB;AAyBzB,IAAM,4BAA4B;AAClC,IAAM,+BAA+B;AACrC,IAAM,8BAA8B;AAepC,eAAsB,kBAAkB,SAAyD;AAC/F,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,qBAAqB,QAAQ,sBAAsB;AACzD,QAAM,oBAAoB,QAAQ,qBAAqB;AAEvD,MAAI,aAAa,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO,wBAAwB;AAEvE,QAAM,UAAU,MAAM,UAA8B,CAAC,YAAY,QAAQ,QAAQ,OAAO,GAAG;AAAA,IACzF,QAAQ,mBAAmB,YAAY,oBAAoB,iBAAiB;AAAA;AAAA;AAAA;AAAA,IAI5E,QAAQ,QAAQ,UAAU,kBAAkB;AAAA,IAC5C,UAAU,CAAC,WAAY,OAAO,KAAK,OAAO,aAAa,MAAM;AAAA,IAC7D,WAAW,CAAC,EAAE,SAAS,SAAS,WAAW,QAAQ,MAAM;AACvD,UAAI,YAAY,QAAQ,CAAC,UAAW;AAEpC,YAAM,eAAe,UAAU;AAC/B,0BAAoB,EAAE,QAAQ;AAAA,QAC5B,MAAM;AAAA,QACN,IAAI,OAAO;AAAA,QACX;AAAA,QACA;AAAA,QACA,QAAQ,QAAQ;AAAA,QAChB,MAAM,QAAQ;AAAA,MAChB,CAAC;AACD;AAAA,QACE,SAAS,QAAQ,KAAK,uCAAuC,OAAO,IAAI,UAAU,MAAM,QAAQ,OAAO,iBAAiB,YAAY;AAAA,MACtI;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,QAAQ;AACjB;AAYA,SAAS,aAAa,QAA+C;AACnE,QAAM,UAAU,OAAO,SAAS;AAChC,SAAO;AAAA,IACL,GAAG,SAAS,EAAE,QAAQ,SAAS,QAAQ,QAAQ,CAAC;AAAA,IAChD;AAAA,IACA,WAAW,OAAO,cAAc;AAAA,EAClC;AACF;AAQA,eAAsB,kBACpB,MACA,WAAqB,CAAC,KAAK,GAAG,GACb;AACjB,WAAS,IAAI,KAAK,KAAK;AACrB,QAAI;AACF,aAAO,MAAM,SAAS,MAAM,OAAO;AAAA,IACrC,SAAS,KAAK;AACZ,UAAI,KAAK,SAAS,OAAQ,OAAM;AAChC,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,SAAS,CAAC,CAAC,CAAC;AAAA,IACjE;AAAA,EACF;AACF;","names":[]}
package/dist/cli.js CHANGED
@@ -1,13 +1,21 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_INACTIVITY_TIMEOUT_MS,
4
+ installShutdownHandlers
5
+ } from "./chunk-M66SJHQQ.js";
2
6
  import {
3
7
  setIssuesCliOverrides,
8
+ setResilienceCliOverrides,
4
9
  setWebCliOverrides
5
- } from "./chunk-VQ5VN7DF.js";
10
+ } from "./chunk-42PJFMAR.js";
11
+ import "./chunk-DIDUVL6O.js";
6
12
  import {
7
13
  printError,
8
14
  setGlobalTimeout,
15
+ setInactivityTimeout,
9
16
  setVerbose
10
- } from "./chunk-HC2MT7BX.js";
17
+ } from "./chunk-BYZDPR7N.js";
18
+ import "./chunk-CD5ULRVC.js";
11
19
 
12
20
  // src/cli.ts
13
21
  import { createRequire } from "module";
@@ -107,6 +115,37 @@ function resolveGenerateTarget(opts) {
107
115
  return selected[0];
108
116
  }
109
117
 
118
+ // src/resilience/cli-flags.ts
119
+ function continuousProfile() {
120
+ return {
121
+ profile: "continuous",
122
+ providers: { failover: true },
123
+ queue: { onIssueFailure: "skip" },
124
+ journal: { enabled: true },
125
+ watchdog: { inactivityTimeoutMs: DEFAULT_INACTIVITY_TIMEOUT_MS }
126
+ };
127
+ }
128
+ function resolveResilienceOverrides(flags) {
129
+ const continuous = flags.continuous === true || flags.resilient === true;
130
+ const overrides = continuous ? continuousProfile() : {};
131
+ if (flags.failover === false) {
132
+ overrides.providers = { ...overrides.providers, failover: false };
133
+ }
134
+ if (flags.autoDecompose === true) {
135
+ overrides.decompose = { ...overrides.decompose, auto: true };
136
+ }
137
+ if (flags.inactivityTimeout !== void 0) {
138
+ overrides.watchdog = {
139
+ ...overrides.watchdog,
140
+ inactivityTimeoutMs: flags.inactivityTimeout * 1e3
141
+ };
142
+ }
143
+ if (flags.onIssueFailure !== void 0) {
144
+ overrides.queue = { ...overrides.queue, onIssueFailure: flags.onIssueFailure };
145
+ }
146
+ return overrides;
147
+ }
148
+
110
149
  // src/cli.ts
111
150
  var require2 = createRequire(import.meta.url);
112
151
  var { version } = require2("../package.json");
@@ -117,6 +156,10 @@ function parseInteger(value) {
117
156
  }
118
157
  return parsed;
119
158
  }
159
+ function parseQueueFailureMode(value) {
160
+ if (value === "stop" || value === "skip" || value === "block") return value;
161
+ throw new InvalidArgumentError("Must be one of: stop, skip, block.");
162
+ }
120
163
  function parseStartUs(value) {
121
164
  const parsed = parseInteger(value);
122
165
  if (parsed < 1) {
@@ -136,8 +179,15 @@ function withGlobalOptions(cmd) {
136
179
  "-t, --timeout <seconds>",
137
180
  "Override headless timeout in seconds (0 = no limit)",
138
181
  parseInteger
182
+ ).option(
183
+ "--inactivity-timeout <seconds>",
184
+ "Stop the agent after this many seconds with no output (0 = no watchdog)",
185
+ parseInteger
139
186
  );
140
187
  }
188
+ function withResilienceOptions(cmd) {
189
+ return cmd.option("--continuous", "Long-running profile: keep going without supervision").option("--resilient", "Alias of --continuous").option("--no-failover", "Never migrate a phase to another agent provider").option("--auto-decompose", "Act on a decomposition report instead of only writing it");
190
+ }
141
191
  function withWebOptions(cmd) {
142
192
  return cmd.option("--web", "Enable the web monitoring server").option("--serve", "Alias for --web").option("--port <n>", "Web server port (default: 3737)", parseInteger).option("--host <h>", "Web server host (default: 0.0.0.0)").option("--refresh <s>", "Suggested UI polling interval in seconds", parseInteger).option("--web-log-limit <n>", "Max log entries kept in the snapshot", parseInteger).option("--web-no-logs", "Exclude logs from the published snapshot");
143
193
  }
@@ -171,6 +221,7 @@ program.name("issue-flow").description(
171
221
  "Unified CLI for orchestrating the full issue-flow pipeline via Claude Code Headless."
172
222
  ).version(version);
173
223
  program.hook("preAction", (_thisCommand, actionCommand) => {
224
+ installShutdownHandlers();
174
225
  const opts = actionCommand.opts();
175
226
  if (opts.verbose) {
176
227
  setVerbose(true);
@@ -178,7 +229,20 @@ program.hook("preAction", (_thisCommand, actionCommand) => {
178
229
  if (opts.timeout !== void 0) {
179
230
  setGlobalTimeout(opts.timeout * 1e3);
180
231
  }
232
+ if (opts.inactivityTimeout !== void 0) {
233
+ setInactivityTimeout(opts.inactivityTimeout * 1e3);
234
+ }
181
235
  setWebCliOverrides(resolveWebOverrides(opts));
236
+ setResilienceCliOverrides(
237
+ resolveResilienceOverrides({
238
+ continuous: opts.continuous,
239
+ resilient: opts.resilient,
240
+ failover: opts.failover,
241
+ autoDecompose: opts.autoDecompose,
242
+ inactivityTimeout: opts.inactivityTimeout,
243
+ onIssueFailure: opts.onIssueFailure
244
+ })
245
+ );
182
246
  try {
183
247
  setIssuesCliOverrides(resolveIssuesOverrides(opts));
184
248
  } catch (error) {
@@ -197,8 +261,8 @@ withIssueOptions(
197
261
  )
198
262
  ).action(
199
263
  async (options) => {
200
- const { loadIssuesConfig } = await import("./config-4THHOXNN.js");
201
- const { runInit } = await import("./init-PRMOTQ7N.js");
264
+ const { loadIssuesConfig } = await import("./config-PRJGWW7J.js");
265
+ const { runInit } = await import("./init-AJHCXJBP.js");
202
266
  const { preferredProvider } = await loadIssuesConfig();
203
267
  const code = await runInit(preferredProvider, options);
204
268
  process.exit(code);
@@ -217,20 +281,30 @@ withGlobalOptions(
217
281
  }
218
282
  throw error;
219
283
  }
220
- const { runGenerate } = await import("./generate-6RA2MFJB.js");
284
+ const { runGenerate } = await import("./generate-EGB4KV2B.js");
221
285
  const code = await runGenerate(options.prompt, target);
222
286
  process.exit(code);
223
287
  });
224
288
  withUserStoryNumberingOptions(
225
- withWebOptions(
226
- withIssueOptions(
227
- withGlobalOptions(
228
- program.command("run").description(
229
- "Execute the full pipeline: prd \u2192 plan \u2192 execute \u2192 review \u2192 pr (\u2192 pr-review, optional)"
230
- ).argument("<issues...>", 'Issue number(s): 42, "42,43" or 42 43').option("--mode <mode>", "Execution mode: auto | manual", "auto").option("--from <phase>", "Resume from a specific phase").option(
231
- "--no-branch",
232
- "Run pipeline on current branch without creating a new branch or PR"
233
- ).option("--pr-review", "Review the created Pull Request after the pr phase").option("-y, --yes", "Run the whole discovered hierarchy without confirmation").option("--only", "Run just the issues informed, without their hierarchy")
289
+ withResilienceOptions(
290
+ withWebOptions(
291
+ withIssueOptions(
292
+ withGlobalOptions(
293
+ program.command("run").description(
294
+ "Execute the full pipeline: prd \u2192 plan \u2192 execute \u2192 review \u2192 pr (\u2192 pr-review, optional)"
295
+ ).argument("<issues...>", 'Issue number(s): 42, "42,43" or 42 43').option("--mode <mode>", "Execution mode: auto | manual", "auto").option("--from <phase>", "Resume from a specific phase").option(
296
+ "--no-branch",
297
+ "Run pipeline on current branch without creating a new branch or PR"
298
+ ).option("--pr-review", "Review the created Pull Request after the pr phase").option("-y, --yes", "Run the whole discovered hierarchy without confirmation").option("--only", "Run just the issues informed, without their hierarchy").option(
299
+ "--retry-limit <number>",
300
+ "Retry transient Claude failures up to N consecutive times",
301
+ parseInteger
302
+ ).option("--retry-forever", "Retry transient Claude failures indefinitely").option(
303
+ "--on-issue-failure <mode>",
304
+ "In a queue, on a failing issue: stop | skip | block",
305
+ parseQueueFailureMode
306
+ )
307
+ )
234
308
  )
235
309
  )
236
310
  )
@@ -250,7 +324,7 @@ withUserStoryNumberingOptions(
250
324
  }
251
325
  throw error;
252
326
  }
253
- const { runPipeline } = await import("./run-H75MD5BQ.js");
327
+ const { runPipeline } = await import("./run-RXE5GDQO.js");
254
328
  const code = await runPipeline(
255
329
  issues,
256
330
  options.mode,
@@ -261,18 +335,74 @@ withUserStoryNumberingOptions(
261
335
  yes: scope.yes,
262
336
  only: scope.only,
263
337
  continueNumbering: numbering.continueFlag,
264
- startUs: numbering.startUs
338
+ startUs: numbering.startUs,
339
+ retryLimit: options.retryLimit,
340
+ retryForever: options.retryForever,
341
+ onIssueFailure: options.onIssueFailure
265
342
  }
266
343
  );
267
344
  process.exit(code);
268
345
  }
269
346
  );
347
+ withIssueOptions(
348
+ withGlobalOptions(
349
+ program.command("resume").description("Resume an interrupted pipeline from the phase it stopped at").argument("[issue]", "Issue to resume. Omitted: the most recently attempted one").option("--all", "Resume every unfinished issue of this project, in order").option("--mode <mode>", "Execution mode: auto | manual", "auto")
350
+ )
351
+ ).action(async (issue, options) => {
352
+ const { runResume } = await import("./resume-PPCNQVVV.js");
353
+ const code = await runResume(issue, {
354
+ ...options.all === void 0 ? {} : { all: options.all },
355
+ ...options.mode === void 0 ? {} : { mode: options.mode }
356
+ });
357
+ process.exit(code);
358
+ });
359
+ withGlobalOptions(
360
+ program.command("status").description("What is running right now, in which phase, and since when").argument("[issue]", "Restrict the report to one issue").option("--json", "Emit the assembled state as JSON")
361
+ ).action(async (issue, options) => {
362
+ const { runStatus } = await import("./operations-BYT626KQ.js");
363
+ process.exit(
364
+ await runStatus(issue, { ...options.json === void 0 ? {} : { json: options.json } })
365
+ );
366
+ });
367
+ withGlobalOptions(
368
+ program.command("runs").description("History of the runs of this project, with how each ended")
369
+ ).action(async () => {
370
+ const { runRuns } = await import("./operations-BYT626KQ.js");
371
+ process.exit(await runRuns());
372
+ });
373
+ withGlobalOptions(
374
+ program.command("logs").description("Read the execution journal (events.jsonl), filtered and readable").argument("[issue]", "Issue to read. Omitted: the most recently attempted one").option("--issue <issue>", "Same as the positional argument").option("--follow", "Keep reading as the journal grows").option("--tail <n>", "How many entries to show first (default 50)", parseInteger).option("--kind <kinds>", "Only these event types, comma separated (retry, phase:end, \u2026)")
375
+ ).action(
376
+ async (issue, options) => {
377
+ const { runLogs } = await import("./operations-BYT626KQ.js");
378
+ const kinds = options.kind === void 0 ? void 0 : options.kind.split(",").map((kind) => kind.trim()).filter((kind) => kind !== "");
379
+ process.exit(
380
+ await runLogs(issue ?? options.issue, {
381
+ ...kinds === void 0 ? {} : { kind: kinds },
382
+ ...options.follow === void 0 ? {} : { follow: options.follow },
383
+ ...options.tail === void 0 ? {} : { tail: options.tail }
384
+ })
385
+ );
386
+ }
387
+ );
388
+ withGlobalOptions(
389
+ program.command("pause").description("Ask the running pipeline to stop after writing a checkpoint")
390
+ ).action(async () => {
391
+ const { runPause } = await import("./operations-BYT626KQ.js");
392
+ process.exit(await runPause());
393
+ });
394
+ withGlobalOptions(
395
+ program.command("cancel").description("Stop the run and mark the issue so a resume does not pick it up").argument("[issue]", "Issue to cancel. Omitted: the most recently attempted one")
396
+ ).action(async (issue) => {
397
+ const { runCancel } = await import("./operations-BYT626KQ.js");
398
+ process.exit(await runCancel(issue));
399
+ });
270
400
  withIssueOptions(
271
401
  withGlobalOptions(
272
402
  program.command("analyze").description("Analyze an issue via Claude Code Headless").argument("<issue>", "Issue number")
273
403
  )
274
404
  ).action(async (issue) => {
275
- const { runAnalyze } = await import("./analyze-7B5TORNK.js");
405
+ const { runAnalyze } = await import("./analyze-OEET7KGE.js");
276
406
  const code = await runAnalyze(issue);
277
407
  process.exit(code);
278
408
  });
@@ -281,7 +411,7 @@ withIssueOptions(
281
411
  program.command("prd").description("Generate a PRD from an analyzed issue via Claude Code Headless").argument("<issue>", "Issue number")
282
412
  )
283
413
  ).action(async (issue) => {
284
- const { runPrd } = await import("./prd-V3MWDIHK.js");
414
+ const { runPrd } = await import("./prd-JQSZF7PR.js");
285
415
  const code = await runPrd(issue);
286
416
  process.exit(code);
287
417
  });
@@ -302,7 +432,7 @@ withUserStoryNumberingOptions(
302
432
  }
303
433
  throw error;
304
434
  }
305
- const { runPlan } = await import("./plan-KENR6MJ3.js");
435
+ const { runPlan } = await import("./plan-QOZ3RIO6.js");
306
436
  const code = await runPlan(issue, void 0, numbering);
307
437
  process.exit(code);
308
438
  });
@@ -321,7 +451,7 @@ withWebOptions(
321
451
  ).action(
322
452
  async (positionalMaxIter, options) => {
323
453
  try {
324
- const { runExecute } = await import("./execute-5OEKLM5X.js");
454
+ const { runExecute } = await import("./execute-XBNVL753.js");
325
455
  const code = await runExecute(positionalMaxIter, options);
326
456
  process.exit(code);
327
457
  } catch (error) {
@@ -335,7 +465,7 @@ withIssueOptions(
335
465
  program.command("review").description("Validate an issue resolution via Claude Code Headless").argument("<issue>", "Issue number")
336
466
  )
337
467
  ).action(async (issue) => {
338
- const { runReview } = await import("./review-7LN47SAN.js");
468
+ const { runReview } = await import("./review-3F6MARWH.js");
339
469
  const code = await runReview(issue);
340
470
  process.exit(code);
341
471
  });
@@ -344,7 +474,7 @@ withIssueOptions(
344
474
  program.command("pr").description("Create a pull request via Claude Code Headless").argument("<issue>", "Issue number")
345
475
  )
346
476
  ).action(async (issue) => {
347
- const { runPr } = await import("./pr-KDGKG4BP.js");
477
+ const { runPr } = await import("./pr-EBBHD2ZX.js");
348
478
  const code = await runPr(issue);
349
479
  process.exit(code);
350
480
  });
@@ -352,21 +482,21 @@ var webCommand = program.command("web").description("Manage the web monitoring s
352
482
  webCommand.command("serve").description(
353
483
  "Run the web monitor server in the foreground (internal \u2014 spawned detached by --web)"
354
484
  ).option("--port <n>", "Web server port (default: 3737)", parseInteger).option("--host <h>", "Web server host (default: 0.0.0.0)").option("--refresh <s>", "Suggested UI polling interval in seconds", parseInteger).action(async (options) => {
355
- const { runWebServe } = await import("./web-ANQQTTRD.js");
485
+ const { runWebServe } = await import("./web-EF5WIIGG.js");
356
486
  const code = await runWebServe(options);
357
487
  if (code !== 0) {
358
488
  process.exit(code);
359
489
  }
360
490
  });
361
491
  webCommand.command("stop").description("Stop the running web monitor server").action(async () => {
362
- const { runWebStop } = await import("./web-ANQQTTRD.js");
492
+ const { runWebStop } = await import("./web-EF5WIIGG.js");
363
493
  const code = await runWebStop();
364
494
  process.exit(code);
365
495
  });
366
496
  withGlobalOptions(
367
497
  program.command("policy").description("Inspect the policies discovered in this repository and their provenance").option("--scope <dir>", "Resolve the policy for a subdirectory (monorepo)").option("--json", "Emit the resolved policy as JSON")
368
498
  ).action(async (options) => {
369
- const { runPolicy } = await import("./policy-XQW5NUIE.js");
499
+ const { runPolicy } = await import("./policy-6S4GFC76.js");
370
500
  const code = await runPolicy(options);
371
501
  process.exit(code);
372
502
  });
@@ -377,7 +507,7 @@ withGlobalOptions(
377
507
  )
378
508
  ).action(
379
509
  async (pr, options) => {
380
- const { runPrReview } = await import("./pr-review-CAMAB7KO.js");
510
+ const { runPrReview } = await import("./pr-review-VVMW5PO4.js");
381
511
  const code = await runPrReview(pr, options);
382
512
  process.exit(code);
383
513
  }