issue-flow 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -97
- package/dist/agent-4DHRKZ2Z.js +32 -0
- package/dist/{analyze-Q4ONIWRZ.js → analyze-UEJ2R3H5.js} +42 -41
- package/dist/analyze-UEJ2R3H5.js.map +1 -0
- package/dist/{apply-EEFPY2W4.js → apply-QDYNQMUX.js} +22 -24
- package/dist/apply-QDYNQMUX.js.map +1 -0
- package/dist/artifacts-AMJQJFFV.js +82 -0
- package/dist/artifacts-AMJQJFFV.js.map +1 -0
- package/dist/{bench-WWWXLTFH.js → bench-RNRXVEFK.js} +42 -42
- package/dist/{chunk-RYOUZ6EK.js → chunk-23VHVVKW.js} +3 -3
- package/dist/chunk-2EYLOS5I.js +99 -0
- package/dist/chunk-2EYLOS5I.js.map +1 -0
- package/dist/{chunk-C5TL7T4V.js → chunk-2YM2VH6R.js} +2 -2
- package/dist/{chunk-XUEMATVY.js → chunk-37O2FMAN.js} +28 -25
- package/dist/chunk-37O2FMAN.js.map +1 -0
- package/dist/chunk-44RF3VFG.js +717 -0
- package/dist/chunk-44RF3VFG.js.map +1 -0
- package/dist/chunk-6AOZCWN2.js +21 -0
- package/dist/chunk-6AOZCWN2.js.map +1 -0
- package/dist/chunk-7MJGYPHF.js +1571 -0
- package/dist/chunk-7MJGYPHF.js.map +1 -0
- package/dist/{chunk-SA6HXL4Y.js → chunk-7WBUEBD5.js} +9 -9
- package/dist/chunk-7WBUEBD5.js.map +1 -0
- package/dist/chunk-7ZZUSMBJ.js +35 -0
- package/dist/chunk-7ZZUSMBJ.js.map +1 -0
- package/dist/{chunk-QJ44VG5G.js → chunk-ADH6RJNX.js} +3 -3
- package/dist/{chunk-34OCSKOB.js → chunk-AKL5UPR6.js} +159 -114
- package/dist/chunk-AKL5UPR6.js.map +1 -0
- package/dist/{chunk-7F7LGMVO.js → chunk-BTBJ5UOO.js} +12 -12
- package/dist/{chunk-7F7LGMVO.js.map → chunk-BTBJ5UOO.js.map} +1 -1
- package/dist/{chunk-HOAHMERN.js → chunk-CLZ6RRZL.js} +21 -21
- package/dist/chunk-DBLXLKGU.js +1201 -0
- package/dist/chunk-DBLXLKGU.js.map +1 -0
- package/dist/{chunk-DUS5ZNKL.js → chunk-DIMDAYZS.js} +7 -6
- package/dist/chunk-DZ3DZ3EN.js +278 -0
- package/dist/chunk-DZ3DZ3EN.js.map +1 -0
- package/dist/{chunk-CRGIQFUQ.js → chunk-E2H3JV2E.js} +3 -1
- package/dist/chunk-E2H3JV2E.js.map +1 -0
- package/dist/{chunk-45WZ6GMI.js → chunk-EORDSKWZ.js} +17 -17
- package/dist/chunk-EORDSKWZ.js.map +1 -0
- package/dist/{chunk-LTH6XYZ3.js → chunk-EYKPWHT7.js} +40 -30
- package/dist/chunk-EYKPWHT7.js.map +1 -0
- package/dist/{chunk-OODXFCQV.js → chunk-FU2FQ4JE.js} +14 -16
- package/dist/{chunk-OODXFCQV.js.map → chunk-FU2FQ4JE.js.map} +1 -1
- package/dist/{chunk-V7QOIQYC.js → chunk-G442RDFX.js} +9 -23
- package/dist/{chunk-V7QOIQYC.js.map → chunk-G442RDFX.js.map} +1 -1
- package/dist/{chunk-QPGEFLMY.js → chunk-I64N6RL6.js} +7 -7
- package/dist/{chunk-KRAGAUKD.js → chunk-JBYXJOJJ.js} +201 -64
- package/dist/chunk-JBYXJOJJ.js.map +1 -0
- package/dist/{chunk-SOM4QA6C.js → chunk-JCR5C5A7.js} +93 -18
- package/dist/chunk-JCR5C5A7.js.map +1 -0
- package/dist/{chunk-RNFUVKC3.js → chunk-K5NM26NV.js} +30 -13
- package/dist/chunk-K5NM26NV.js.map +1 -0
- package/dist/{chunk-YVPZUN6X.js → chunk-KUA6KBBD.js} +335 -38
- package/dist/chunk-KUA6KBBD.js.map +1 -0
- package/dist/{chunk-KMCS76ZW.js → chunk-KUL64XUF.js} +44 -12
- package/dist/chunk-KUL64XUF.js.map +1 -0
- package/dist/{chunk-HAKDLR6N.js → chunk-MAJ6QDRL.js} +65 -78
- package/dist/chunk-MAJ6QDRL.js.map +1 -0
- package/dist/{chunk-DGRTJD5R.js → chunk-MML2G7IC.js} +7 -7
- package/dist/chunk-NP47HRX2.js +136 -0
- package/dist/chunk-NP47HRX2.js.map +1 -0
- package/dist/{chunk-3R27MJ5R.js → chunk-PFWHHYEO.js} +3 -3
- package/dist/{chunk-ECU7YO7S.js → chunk-PM5YWJYO.js} +22 -6
- package/dist/chunk-PM5YWJYO.js.map +1 -0
- package/dist/{chunk-E4I6MMHD.js → chunk-PXPTIXJF.js} +154 -101
- package/dist/chunk-PXPTIXJF.js.map +1 -0
- package/dist/{chunk-CWSQVHVV.js → chunk-QJHZ3HYJ.js} +248 -102
- package/dist/chunk-QJHZ3HYJ.js.map +1 -0
- package/dist/{chunk-LI35PIIS.js → chunk-RDPK7LYK.js} +20 -117
- package/dist/chunk-RDPK7LYK.js.map +1 -0
- package/dist/{chunk-PMLE7KAQ.js → chunk-RMIBLFG3.js} +30 -14
- package/dist/chunk-RMIBLFG3.js.map +1 -0
- package/dist/{chunk-62GVVZQO.js → chunk-T736DSZI.js} +32 -316
- package/dist/chunk-T736DSZI.js.map +1 -0
- package/dist/{chunk-TZYINKCC.js → chunk-UDUUUFQF.js} +57 -29
- package/dist/chunk-UDUUUFQF.js.map +1 -0
- package/dist/{chunk-NEWNXZ65.js → chunk-V7GETODQ.js} +36 -28
- package/dist/{chunk-NEWNXZ65.js.map → chunk-V7GETODQ.js.map} +1 -1
- package/dist/{chunk-PPZ3K4YM.js → chunk-VHQMMY2D.js} +2 -2
- package/dist/{chunk-ZVZMWNCG.js → chunk-VJ3PF6B7.js} +50 -16
- package/dist/chunk-VJ3PF6B7.js.map +1 -0
- package/dist/{chunk-L6HSKTQE.js → chunk-W7LZLJAF.js} +4 -4
- package/dist/{chunk-6K2LCFG6.js → chunk-WB4FA2EH.js} +70 -81
- package/dist/chunk-WB4FA2EH.js.map +1 -0
- package/dist/{chunk-LGHND47K.js → chunk-WQSGL5LX.js} +22 -16
- package/dist/chunk-WQSGL5LX.js.map +1 -0
- package/dist/{chunk-ICM463EO.js → chunk-XNK5UN2D.js} +5 -649
- package/dist/chunk-XNK5UN2D.js.map +1 -0
- package/dist/{chunk-7UMMFOL2.js → chunk-Y2KOWABB.js} +39 -24
- package/dist/chunk-Y2KOWABB.js.map +1 -0
- package/dist/{chunk-QF7VEL3M.js → chunk-YC24E66P.js} +568 -158
- package/dist/chunk-YC24E66P.js.map +1 -0
- package/dist/{chunk-EG2MGDP5.js → chunk-Z6RCKCVB.js} +52 -46
- package/dist/chunk-Z6RCKCVB.js.map +1 -0
- package/dist/{chunk-YUNBOUQH.js → chunk-ZPGNVBWY.js} +8 -8
- package/dist/cli.js +132 -62
- package/dist/cli.js.map +1 -1
- package/dist/compat-OJG76J4I.js +24 -0
- package/dist/{config-LEBZZCJP.js → config-6YTWPDBK.js} +20 -17
- package/dist/{contract-JH2WBIO6.js → contract-HWLFFL3F.js} +2 -2
- package/dist/{conventions-XOT6YW5L.js → conventions-5UPOTU42.js} +20 -16
- package/dist/{conventions-XOT6YW5L.js.map → conventions-5UPOTU42.js.map} +1 -1
- package/dist/db-QAZG4LQD.js +264 -0
- package/dist/db-QAZG4LQD.js.map +1 -0
- package/dist/diagnostics-YEFB42E5.js +25 -0
- package/dist/execute-QCT64D37.js +38 -0
- package/dist/{generate-JGW5JSKB.js → generate-QATMDKOK.js} +46 -43
- package/dist/generate-QATMDKOK.js.map +1 -0
- package/dist/{git-A2IP32PQ.js → git-XGOECXWG.js} +2 -3
- package/dist/history-NBPUZVGQ.js +101 -0
- package/dist/history-NBPUZVGQ.js.map +1 -0
- package/dist/init-4LB2YKWP.js +28 -0
- package/dist/{operations-O4FORFIH.js → operations-RUQP2Y2F.js} +78 -52
- package/dist/operations-RUQP2Y2F.js.map +1 -0
- package/dist/{permissions-G4XCAESF.js → permissions-QZPUUBSO.js} +5 -6
- package/dist/plan-2NMSHTMD.js +38 -0
- package/dist/{policy-U5NBFAO5.js → policy-RKKFF5A2.js} +17 -15
- package/dist/{policy-U5NBFAO5.js.map → policy-RKKFF5A2.js.map} +1 -1
- package/dist/pr-QKFO5HI3.js +42 -0
- package/dist/pr-review-H2BFD6F2.js +33 -0
- package/dist/prd-ET6XKYRS.js +37 -0
- package/dist/{ps-76L6ZAZL.js → ps-UIKSOUAQ.js} +26 -15
- package/dist/ps-UIKSOUAQ.js.map +1 -0
- package/dist/{recorder-2BAKPBRA.js → recorder-LOU3PTIE.js} +11 -7
- package/dist/registry-HV7M6MAH.js +17 -0
- package/dist/repository-RCRR6RDE.js +80 -0
- package/dist/resume-BWJZGBLZ.js +278 -0
- package/dist/resume-BWJZGBLZ.js.map +1 -0
- package/dist/review-2MTZLNSS.js +38 -0
- package/dist/routing-RM5ABHHD.js +35 -0
- package/dist/run-XMACQBRJ.js +61 -0
- package/dist/{session-publisher-KAJQRHNZ.js → session-publisher-Q5TNEZPX.js} +3 -3
- package/dist/session-publisher-Q5TNEZPX.js.map +1 -0
- package/dist/{state-manager-UPHO2EA2.js → state-manager-SRN4VJSX.js} +9 -4
- package/dist/state-manager-SRN4VJSX.js.map +1 -0
- package/dist/{usage-KTMYXAPF.js → usage-JOODVE5O.js} +69 -63
- package/dist/usage-JOODVE5O.js.map +1 -0
- package/dist/web-TLMWJ2BM.js +205 -0
- package/dist/web-TLMWJ2BM.js.map +1 -0
- package/package.json +22 -8
- package/prompts/analyze.md +16 -19
- package/prompts/execute.md +73 -144
- package/prompts/generate.md +23 -10
- package/prompts/plan.md +32 -60
- package/prompts/pr-review.md +58 -40
- package/prompts/pr.md +50 -16
- package/prompts/prd.md +26 -19
- package/prompts/review.md +41 -18
- package/web/public/app.js +111 -112
- package/dist/agent-HMZTXCRT.js +0 -30
- package/dist/analyze-Q4ONIWRZ.js.map +0 -1
- package/dist/apply-EEFPY2W4.js.map +0 -1
- package/dist/chunk-34OCSKOB.js.map +0 -1
- package/dist/chunk-45WZ6GMI.js.map +0 -1
- package/dist/chunk-62GVVZQO.js.map +0 -1
- package/dist/chunk-6K2LCFG6.js.map +0 -1
- package/dist/chunk-7UMMFOL2.js.map +0 -1
- package/dist/chunk-CRGIQFUQ.js.map +0 -1
- package/dist/chunk-CWSQVHVV.js.map +0 -1
- package/dist/chunk-E4I6MMHD.js.map +0 -1
- package/dist/chunk-ECU7YO7S.js.map +0 -1
- package/dist/chunk-EG2MGDP5.js.map +0 -1
- package/dist/chunk-HAKDLR6N.js.map +0 -1
- package/dist/chunk-ICM463EO.js.map +0 -1
- package/dist/chunk-KMCS76ZW.js.map +0 -1
- package/dist/chunk-KRAGAUKD.js.map +0 -1
- package/dist/chunk-LGHND47K.js.map +0 -1
- package/dist/chunk-LI35PIIS.js.map +0 -1
- package/dist/chunk-LTH6XYZ3.js.map +0 -1
- package/dist/chunk-PMLE7KAQ.js.map +0 -1
- package/dist/chunk-QF7VEL3M.js.map +0 -1
- package/dist/chunk-RNFUVKC3.js.map +0 -1
- package/dist/chunk-SA6HXL4Y.js.map +0 -1
- package/dist/chunk-SOM4QA6C.js.map +0 -1
- package/dist/chunk-TZYINKCC.js.map +0 -1
- package/dist/chunk-WCCZJL7W.js +0 -141
- package/dist/chunk-WCCZJL7W.js.map +0 -1
- package/dist/chunk-XEYH6ZZI.js +0 -289
- package/dist/chunk-XEYH6ZZI.js.map +0 -1
- package/dist/chunk-XUEMATVY.js.map +0 -1
- package/dist/chunk-XZUEFTUP.js +0 -243
- package/dist/chunk-XZUEFTUP.js.map +0 -1
- package/dist/chunk-YVPZUN6X.js.map +0 -1
- package/dist/chunk-ZVZMWNCG.js.map +0 -1
- package/dist/diagnostics-HTCWF7F4.js +0 -23
- package/dist/execute-LELOSR5T.js +0 -37
- package/dist/generate-JGW5JSKB.js.map +0 -1
- package/dist/init-TO6MUXO4.js +0 -26
- package/dist/operations-O4FORFIH.js.map +0 -1
- package/dist/plan-OXAT344Q.js +0 -34
- package/dist/pr-UZHNKKZC.js +0 -39
- package/dist/pr-review-Q4ACAWDM.js +0 -32
- package/dist/prd-6CYEMLDY.js +0 -33
- package/dist/ps-76L6ZAZL.js.map +0 -1
- package/dist/registry-XLHHYLTR.js +0 -15
- package/dist/resume-URCU6CIJ.js +0 -244
- package/dist/resume-URCU6CIJ.js.map +0 -1
- package/dist/review-LVZT2TMW.js +0 -35
- package/dist/routing-EK6RBZM4.js +0 -33
- package/dist/run-2RXJCJKT.js +0 -56
- package/dist/usage-KTMYXAPF.js.map +0 -1
- package/dist/web-B5UPHHR5.js +0 -172
- package/dist/web-B5UPHHR5.js.map +0 -1
- /package/dist/{agent-HMZTXCRT.js.map → agent-4DHRKZ2Z.js.map} +0 -0
- /package/dist/{bench-WWWXLTFH.js.map → bench-RNRXVEFK.js.map} +0 -0
- /package/dist/{chunk-RYOUZ6EK.js.map → chunk-23VHVVKW.js.map} +0 -0
- /package/dist/{chunk-C5TL7T4V.js.map → chunk-2YM2VH6R.js.map} +0 -0
- /package/dist/{chunk-QJ44VG5G.js.map → chunk-ADH6RJNX.js.map} +0 -0
- /package/dist/{chunk-HOAHMERN.js.map → chunk-CLZ6RRZL.js.map} +0 -0
- /package/dist/{chunk-DUS5ZNKL.js.map → chunk-DIMDAYZS.js.map} +0 -0
- /package/dist/{chunk-QPGEFLMY.js.map → chunk-I64N6RL6.js.map} +0 -0
- /package/dist/{chunk-DGRTJD5R.js.map → chunk-MML2G7IC.js.map} +0 -0
- /package/dist/{chunk-3R27MJ5R.js.map → chunk-PFWHHYEO.js.map} +0 -0
- /package/dist/{chunk-PPZ3K4YM.js.map → chunk-VHQMMY2D.js.map} +0 -0
- /package/dist/{chunk-L6HSKTQE.js.map → chunk-W7LZLJAF.js.map} +0 -0
- /package/dist/{chunk-YUNBOUQH.js.map → chunk-ZPGNVBWY.js.map} +0 -0
- /package/dist/{config-LEBZZCJP.js.map → compat-OJG76J4I.js.map} +0 -0
- /package/dist/{contract-JH2WBIO6.js.map → config-6YTWPDBK.js.map} +0 -0
- /package/dist/{diagnostics-HTCWF7F4.js.map → contract-HWLFFL3F.js.map} +0 -0
- /package/dist/{execute-LELOSR5T.js.map → diagnostics-YEFB42E5.js.map} +0 -0
- /package/dist/{git-A2IP32PQ.js.map → execute-QCT64D37.js.map} +0 -0
- /package/dist/{init-TO6MUXO4.js.map → git-XGOECXWG.js.map} +0 -0
- /package/dist/{permissions-G4XCAESF.js.map → init-4LB2YKWP.js.map} +0 -0
- /package/dist/{plan-OXAT344Q.js.map → permissions-QZPUUBSO.js.map} +0 -0
- /package/dist/{pr-UZHNKKZC.js.map → plan-2NMSHTMD.js.map} +0 -0
- /package/dist/{pr-review-Q4ACAWDM.js.map → pr-QKFO5HI3.js.map} +0 -0
- /package/dist/{prd-6CYEMLDY.js.map → pr-review-H2BFD6F2.js.map} +0 -0
- /package/dist/{recorder-2BAKPBRA.js.map → prd-ET6XKYRS.js.map} +0 -0
- /package/dist/{registry-XLHHYLTR.js.map → recorder-LOU3PTIE.js.map} +0 -0
- /package/dist/{review-LVZT2TMW.js.map → registry-HV7M6MAH.js.map} +0 -0
- /package/dist/{routing-EK6RBZM4.js.map → repository-RCRR6RDE.js.map} +0 -0
- /package/dist/{run-2RXJCJKT.js.map → review-2MTZLNSS.js.map} +0 -0
- /package/dist/{session-publisher-KAJQRHNZ.js.map → routing-RM5ABHHD.js.map} +0 -0
- /package/dist/{state-manager-UPHO2EA2.js.map → run-XMACQBRJ.js.map} +0 -0
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
getShutdownSignal
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-YC24E66P.js";
|
|
5
5
|
import {
|
|
6
6
|
printRetry
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import {
|
|
9
|
-
fixedBackoffPolicy,
|
|
10
|
-
withRetry
|
|
11
|
-
} from "./chunk-YVPZUN6X.js";
|
|
7
|
+
} from "./chunk-DBLXLKGU.js";
|
|
12
8
|
import {
|
|
13
9
|
getSessionPublisher
|
|
14
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-DIMDAYZS.js";
|
|
15
11
|
import {
|
|
16
12
|
isoNow
|
|
17
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-JCR5C5A7.js";
|
|
18
14
|
import {
|
|
19
|
-
classify
|
|
20
|
-
|
|
15
|
+
classify,
|
|
16
|
+
fixedBackoffPolicy,
|
|
17
|
+
withRetry
|
|
18
|
+
} from "./chunk-KUA6KBBD.js";
|
|
21
19
|
|
|
22
20
|
// src/core/phase-runner.ts
|
|
23
|
-
import { readFile } from "fs/promises";
|
|
24
21
|
var DEFAULT_PHASE_RETRY_LIMIT = 3;
|
|
25
22
|
var DEFAULT_BACKOFF_BASE_SECONDS = 15;
|
|
26
23
|
var DEFAULT_BACKOFF_MAX_SECONDS = 120;
|
|
@@ -62,16 +59,6 @@ function phaseFailure(result) {
|
|
|
62
59
|
retryable: result.transient === true
|
|
63
60
|
};
|
|
64
61
|
}
|
|
65
|
-
async function readFileWithGrace(path, delaysMs = [300, 800]) {
|
|
66
|
-
for (let i = 0; ; i++) {
|
|
67
|
-
try {
|
|
68
|
-
return await readFile(path, "utf-8");
|
|
69
|
-
} catch (err) {
|
|
70
|
-
if (i >= delaysMs.length) throw err;
|
|
71
|
-
await new Promise((resolve) => setTimeout(resolve, delaysMs[i]));
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
62
|
|
|
76
63
|
// src/utils/retry.ts
|
|
77
64
|
function isTransientFailure(exitCode, output) {
|
|
@@ -80,7 +67,6 @@ function isTransientFailure(exitCode, output) {
|
|
|
80
67
|
|
|
81
68
|
export {
|
|
82
69
|
runPhaseWithRetry,
|
|
83
|
-
readFileWithGrace,
|
|
84
70
|
isTransientFailure
|
|
85
71
|
};
|
|
86
|
-
//# sourceMappingURL=chunk-
|
|
72
|
+
//# sourceMappingURL=chunk-G442RDFX.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/phase-runner.ts","../src/utils/retry.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","import { classify } from '../resilience/errors.js';\n\n/**\n * Determine if an agent invocation failure is transient (retryable).\n *\n * This is a thin adapter over `resilience/errors.ts:classify()` — the taxonomy\n * is the single source of truth, and this signature survives only because\n * `core/engine.ts` and the phase commands are written against it. A caller\n * that has more evidence than an exit code and a blob of text (an `errno`, an\n * HTTP status, `timedOut`) should call `classify()` directly and keep it.\n */\nexport function isTransientFailure(exitCode: number, output: string): boolean {\n return classify({ source: 'agent', exitCode, stdout: output }).retryable;\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":"
|
|
1
|
+
{"version":3,"sources":["../src/core/phase-runner.ts","../src/utils/retry.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","import { classify } from '../resilience/errors.js';\n\n/**\n * Determine if an agent invocation failure is transient (retryable).\n *\n * This is a thin adapter over `resilience/errors.ts:classify()` — the taxonomy\n * is the single source of truth, and this signature survives only because\n * `core/engine.ts` and the phase commands are written against it. A caller\n * that has more evidence than an exit code and a blob of text (an `errno`, an\n * HTTP status, `timedOut`) should call `classify()` directly and keep it.\n */\nexport function isTransientFailure(exitCode: number, output: string): boolean {\n return classify({ source: 'agent', exitCode, stdout: output }).retryable;\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":";;;;;;;;;;;;;;;;;;;;AAyBA,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;;;ACnFO,SAAS,mBAAmB,UAAkB,QAAyB;AAC5E,SAAO,SAAS,EAAE,QAAQ,SAAS,UAAU,QAAQ,OAAO,CAAC,EAAE;AACjE;","names":[]}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
getDiagnosticsDir
|
|
4
|
-
} from "./chunk-HAKDLR6N.js";
|
|
5
2
|
import {
|
|
6
3
|
redactSecrets
|
|
7
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VHQMMY2D.js";
|
|
5
|
+
import {
|
|
6
|
+
getDiagnosticsDir
|
|
7
|
+
} from "./chunk-MAJ6QDRL.js";
|
|
8
8
|
|
|
9
9
|
// src/storage/diagnostics.ts
|
|
10
|
-
import { appendFile, mkdir, readdir, readFile, rename, stat, unlink } from "fs/promises";
|
|
11
|
-
import { join } from "path";
|
|
10
|
+
import { appendFile, mkdir, readdir, readFile, rename, stat, unlink } from "node:fs/promises";
|
|
11
|
+
import { join } from "node:path";
|
|
12
12
|
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
13
13
|
var MAX_GENERATIONS = 5;
|
|
14
14
|
var RETENTION_DAYS = 30;
|
|
@@ -160,4 +160,4 @@ export {
|
|
|
160
160
|
resetDiagnosticsState,
|
|
161
161
|
readDiagnostics
|
|
162
162
|
};
|
|
163
|
-
//# sourceMappingURL=chunk-
|
|
163
|
+
//# sourceMappingURL=chunk-I64N6RL6.js.map
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ensureWebMonitor
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-V7GETODQ.js";
|
|
5
|
+
import {
|
|
6
|
+
groupBy,
|
|
7
|
+
summarize
|
|
8
|
+
} from "./chunk-E2H3JV2E.js";
|
|
5
9
|
import {
|
|
6
10
|
applyAcceptanceToPlan,
|
|
7
11
|
resolveIssueDir,
|
|
8
12
|
runAcceptanceGate
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import {
|
|
11
|
-
groupBy,
|
|
12
|
-
summarize
|
|
13
|
-
} from "./chunk-CRGIQFUQ.js";
|
|
13
|
+
} from "./chunk-PM5YWJYO.js";
|
|
14
14
|
import {
|
|
15
15
|
EXECUTE_PHASE,
|
|
16
16
|
elapsedSecondsSince,
|
|
@@ -18,35 +18,32 @@ import {
|
|
|
18
18
|
publishIterationMetrics,
|
|
19
19
|
publishStoryMetrics,
|
|
20
20
|
resolvePolicyPlaceholders
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-KUL64XUF.js";
|
|
22
22
|
import {
|
|
23
23
|
applyPlaceholders,
|
|
24
24
|
loadPrompt
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-BTBJ5UOO.js";
|
|
26
26
|
import {
|
|
27
27
|
invokeSelectedAgent
|
|
28
|
-
} from "./chunk-
|
|
29
|
-
import {
|
|
30
|
-
getPackageVersion
|
|
31
|
-
} from "./chunk-C5TL7T4V.js";
|
|
28
|
+
} from "./chunk-UDUUUFQF.js";
|
|
32
29
|
import {
|
|
33
30
|
archiveFolderName,
|
|
34
31
|
commitMessage
|
|
35
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-WQSGL5LX.js";
|
|
33
|
+
import {
|
|
34
|
+
getPackageVersion
|
|
35
|
+
} from "./chunk-2YM2VH6R.js";
|
|
36
36
|
import {
|
|
37
37
|
createConfig,
|
|
38
38
|
getActiveResilienceConfig,
|
|
39
39
|
getShutdownSignal,
|
|
40
|
-
loadGlobalConfig,
|
|
41
40
|
loadWebConfig,
|
|
42
41
|
resolveAgentFor,
|
|
43
42
|
resolvePaths,
|
|
44
43
|
validateDependencies
|
|
45
|
-
} from "./chunk-
|
|
46
|
-
import {
|
|
47
|
-
resolveIssuePaths
|
|
48
|
-
} from "./chunk-LI35PIIS.js";
|
|
44
|
+
} from "./chunk-YC24E66P.js";
|
|
49
45
|
import {
|
|
46
|
+
MultiPublisher,
|
|
50
47
|
formatDuration,
|
|
51
48
|
getIcons,
|
|
52
49
|
getInactivityTimeout,
|
|
@@ -55,33 +52,30 @@ import {
|
|
|
55
52
|
getStoryUpdateCallback,
|
|
56
53
|
getTermWidth,
|
|
57
54
|
isVerbose,
|
|
55
|
+
loadGlobalConfig,
|
|
58
56
|
printError,
|
|
59
57
|
printInfo,
|
|
60
58
|
printRetry,
|
|
61
59
|
printSuccess,
|
|
62
60
|
printWarning,
|
|
61
|
+
resolveIssuePaths,
|
|
63
62
|
useColor,
|
|
64
63
|
useUnicode
|
|
65
|
-
} from "./chunk-
|
|
66
|
-
import {
|
|
67
|
-
committedStoryIds,
|
|
68
|
-
fixedBackoffPolicy,
|
|
69
|
-
getBaseBranch,
|
|
70
|
-
isWorkingTreeClean,
|
|
71
|
-
resolvePolicy,
|
|
72
|
-
withRetry
|
|
73
|
-
} from "./chunk-YVPZUN6X.js";
|
|
64
|
+
} from "./chunk-DBLXLKGU.js";
|
|
74
65
|
import {
|
|
66
|
+
DEFAULT_SESSION_HEARTBEAT_MS,
|
|
75
67
|
FilePublisher,
|
|
68
|
+
MemoryPublisher,
|
|
76
69
|
NullPublisher,
|
|
77
70
|
getSessionPublisher,
|
|
78
71
|
setSessionPublisher
|
|
79
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-DIMDAYZS.js";
|
|
80
73
|
import {
|
|
81
74
|
allStoriesPass,
|
|
82
75
|
applyStoryMetrics,
|
|
83
76
|
clearLastError,
|
|
84
77
|
divideUsage,
|
|
78
|
+
executionCompletion,
|
|
85
79
|
formatTokens,
|
|
86
80
|
hasPendingCorrection,
|
|
87
81
|
initializeState,
|
|
@@ -93,18 +87,39 @@ import {
|
|
|
93
87
|
selectActiveStory,
|
|
94
88
|
setLastError,
|
|
95
89
|
trimErrorMessage
|
|
96
|
-
} from "./chunk-
|
|
90
|
+
} from "./chunk-JCR5C5A7.js";
|
|
97
91
|
import {
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
executionContext,
|
|
93
|
+
inspectTaskPlan
|
|
94
|
+
} from "./chunk-NP47HRX2.js";
|
|
95
|
+
import {
|
|
96
|
+
getLastBranchRepository,
|
|
97
|
+
getPlanRepository,
|
|
98
|
+
ingestAgentPlan,
|
|
99
|
+
loadStoredLastBranch,
|
|
100
|
+
saveSessionEvent,
|
|
101
|
+
saveStoredLastBranch,
|
|
102
|
+
setAgentProjectionWindow,
|
|
103
|
+
touchStoredSession
|
|
104
|
+
} from "./chunk-7MJGYPHF.js";
|
|
105
|
+
import {
|
|
106
|
+
classify,
|
|
107
|
+
committedStoryIds,
|
|
108
|
+
fixedBackoffPolicy,
|
|
109
|
+
getBaseBranch,
|
|
110
|
+
getCurrentBranch,
|
|
111
|
+
isWorkingTreeClean,
|
|
112
|
+
resolvePolicy,
|
|
113
|
+
withRetry
|
|
114
|
+
} from "./chunk-KUA6KBBD.js";
|
|
100
115
|
|
|
101
116
|
// src/commands/execute.ts
|
|
102
|
-
import { randomUUID } from "crypto";
|
|
117
|
+
import { randomUUID } from "node:crypto";
|
|
103
118
|
|
|
104
119
|
// src/core/engine.ts
|
|
105
|
-
import { existsSync } from "fs";
|
|
106
|
-
import { cp, mkdir, readFile, writeFile } from "fs/promises";
|
|
107
|
-
import { join } from "path";
|
|
120
|
+
import { existsSync } from "node:fs";
|
|
121
|
+
import { cp, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
122
|
+
import { join } from "node:path";
|
|
108
123
|
|
|
109
124
|
// src/ui/progress.ts
|
|
110
125
|
import chalk from "chalk";
|
|
@@ -602,16 +617,17 @@ Started: ${(/* @__PURE__ */ new Date()).toString()}
|
|
|
602
617
|
}
|
|
603
618
|
async function archiveIfBranchChanged(plan, paths) {
|
|
604
619
|
const { lastBranchFile, archiveDir, prdFile, progressFile } = paths;
|
|
605
|
-
if (!existsSync(lastBranchFile)) {
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
620
|
const currentBranch = plan.branchName ?? "";
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
621
|
+
const repository = getLastBranchRepository(lastBranchFile);
|
|
622
|
+
let lastBranch = repository === void 0 ? null : await loadStoredLastBranch(repository);
|
|
623
|
+
if (lastBranch === null && existsSync(lastBranchFile)) {
|
|
624
|
+
try {
|
|
625
|
+
lastBranch = (await readFile(lastBranchFile, "utf-8")).trim() || null;
|
|
626
|
+
} catch {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
614
629
|
}
|
|
630
|
+
if (lastBranch === null) return;
|
|
615
631
|
if (currentBranch && lastBranch && currentBranch !== lastBranch) {
|
|
616
632
|
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
617
633
|
const folderName = archiveFolderName(lastBranch);
|
|
@@ -638,10 +654,26 @@ Started: ${(/* @__PURE__ */ new Date()).toString()}
|
|
|
638
654
|
async function trackBranch(plan, lastBranchFile) {
|
|
639
655
|
const branch = plan.branchName ?? "";
|
|
640
656
|
if (branch) {
|
|
657
|
+
const repository = getLastBranchRepository(lastBranchFile);
|
|
658
|
+
if (repository !== void 0) {
|
|
659
|
+
await saveStoredLastBranch(repository, branch);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
641
662
|
await writeFile(lastBranchFile, `${branch}
|
|
642
663
|
`, "utf-8");
|
|
643
664
|
}
|
|
644
665
|
}
|
|
666
|
+
async function validateExecutionInput(plan) {
|
|
667
|
+
const result = inspectTaskPlan(plan);
|
|
668
|
+
if (!result.ok) return result.errors.map((error) => `${error.code}: ${error.message}`).join("\n");
|
|
669
|
+
if (plan.userStories.length === 0) return "Task plan has no stories";
|
|
670
|
+
if (plan.noBranch === true) {
|
|
671
|
+
const branch = await getCurrentBranch().catch(() => "");
|
|
672
|
+
if (!branch || branch !== plan.branchName)
|
|
673
|
+
return `Current branch must match ${plan.branchName}; refusing to switch in no-branch mode`;
|
|
674
|
+
}
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
645
677
|
async function runEngine(config, paths) {
|
|
646
678
|
if (!existsSync(paths.prdFile)) {
|
|
647
679
|
printError(`PRD file not found at ${paths.prdFile}`);
|
|
@@ -651,11 +683,17 @@ async function runEngine(config, paths) {
|
|
|
651
683
|
return 1;
|
|
652
684
|
}
|
|
653
685
|
let plan = await loadTaskPlan(paths.prdFile);
|
|
686
|
+
const closure = { closeIssue: plan.closeIssue, issueClosedAt: plan.issueClosedAt };
|
|
687
|
+
const inputError = await validateExecutionInput(plan);
|
|
688
|
+
if (inputError) {
|
|
689
|
+
printError(inputError);
|
|
690
|
+
return 1;
|
|
691
|
+
}
|
|
654
692
|
plan = initializeState(plan);
|
|
655
693
|
await saveTaskPlan(paths.prdFile, plan);
|
|
656
|
-
if (plan.issueStatus === "completed" && allStoriesPass(plan) && !hasPendingCorrection(plan)) {
|
|
694
|
+
if (plan.issueStatus === "completed" && allStoriesPass(plan) && !hasPendingCorrection(plan) && !plan.lastError) {
|
|
657
695
|
emitLog(`Issue already marked complete in ${paths.prdFile}`);
|
|
658
|
-
return
|
|
696
|
+
return finishWithAcceptance(config, paths, plan);
|
|
659
697
|
}
|
|
660
698
|
if (plan.issueStatus === "completed" && !allStoriesPass(plan)) {
|
|
661
699
|
printWarning(
|
|
@@ -669,9 +707,9 @@ async function runEngine(config, paths) {
|
|
|
669
707
|
);
|
|
670
708
|
await saveTaskPlan(paths.prdFile, plan);
|
|
671
709
|
}
|
|
672
|
-
if (allStoriesPass(plan) && !hasPendingCorrection(plan)) {
|
|
710
|
+
if (allStoriesPass(plan) && !hasPendingCorrection(plan) && !plan.lastError) {
|
|
673
711
|
emitLog("All user stories already pass. Marking issue as completed.");
|
|
674
|
-
plan = markIssueCompleted(plan);
|
|
712
|
+
plan = executionCompletion(markIssueCompleted(plan), config.inPipeline === true);
|
|
675
713
|
await saveTaskPlan(paths.prdFile, plan);
|
|
676
714
|
return finishWithAcceptance(config, paths, plan);
|
|
677
715
|
}
|
|
@@ -679,7 +717,7 @@ async function runEngine(config, paths) {
|
|
|
679
717
|
await trackBranch(plan, paths.lastBranchFile);
|
|
680
718
|
await ensureProgressFile(paths.progressFile);
|
|
681
719
|
const promptTemplate = await loadPrompt("execute", { projectRoot: paths.projectRoot });
|
|
682
|
-
const policy = await resolvePolicyPlaceholders({ root: paths.projectRoot });
|
|
720
|
+
const policy = await resolvePolicyPlaceholders({ root: paths.projectRoot, phase: "execute" });
|
|
683
721
|
const signoff = (await loadGlobalConfig()).commit?.signoff === true;
|
|
684
722
|
const executeAgent = await resolveAgentFor("execute");
|
|
685
723
|
printStartupHeader(config, plan, {
|
|
@@ -701,13 +739,15 @@ async function runEngine(config, paths) {
|
|
|
701
739
|
`Adopting ${adopted.join(", ")}: already committed on this branch with a clean tree.`
|
|
702
740
|
);
|
|
703
741
|
plan = await loadTaskPlan(paths.prdFile);
|
|
704
|
-
if (plan.userStories.every((story) => story.passes) && plan.lastReviewFindings === null) {
|
|
742
|
+
if (plan.userStories.every((story) => story.passes) && plan.lastReviewFindings === null && !plan.lastError) {
|
|
705
743
|
break;
|
|
706
744
|
}
|
|
707
745
|
}
|
|
708
746
|
const attempted = await withRetry(
|
|
709
747
|
async () => {
|
|
710
748
|
plan = await loadTaskPlan(paths.prdFile);
|
|
749
|
+
const inputError2 = await validateExecutionInput(plan);
|
|
750
|
+
if (inputError2) throw new Error(`Invalid task plan: ${inputError2}`);
|
|
711
751
|
if (plan.userStories.length > 0) {
|
|
712
752
|
getSessionPublisher().publish({
|
|
713
753
|
type: "stories:update",
|
|
@@ -716,7 +756,8 @@ async function runEngine(config, paths) {
|
|
|
716
756
|
});
|
|
717
757
|
}
|
|
718
758
|
const storiesBefore = plan.userStories;
|
|
719
|
-
const
|
|
759
|
+
const lastErrorBefore = plan.lastError;
|
|
760
|
+
const activeStoryId = hasPendingCorrection(plan) ? void 0 : selectActiveStory(plan.userStories)?.id;
|
|
720
761
|
getSessionPublisher().publish({
|
|
721
762
|
type: "iteration:start",
|
|
722
763
|
at: isoNow(),
|
|
@@ -728,6 +769,10 @@ async function runEngine(config, paths) {
|
|
|
728
769
|
printIterationHeader(i, config.maxIterations, plan.userStories, activeStoryId);
|
|
729
770
|
}
|
|
730
771
|
const prompt = applyPlaceholders(promptTemplate, {
|
|
772
|
+
__EXECUTION_CONTEXT__: JSON.stringify(executionContext(plan)),
|
|
773
|
+
__CORRECTION_MODE__: hasPendingCorrection(plan) ? "enabled" : "",
|
|
774
|
+
__ACTIVE_STORY__: activeStoryId ?? "Address pending review findings",
|
|
775
|
+
__EXECUTION_SCOPE__: config.inPipeline ? "This is one pipeline phase: keep issueStatus=in_progress and completedAt=null." : "Standalone execution may mark the issue completed after verification.",
|
|
731
776
|
__PRD_FILE__: paths.prdFile,
|
|
732
777
|
__PROGRESS_FILE__: paths.progressFile,
|
|
733
778
|
...policy,
|
|
@@ -743,17 +788,30 @@ async function runEngine(config, paths) {
|
|
|
743
788
|
plan = markIssueInProgress(plan, startedAt);
|
|
744
789
|
await saveTaskPlan(paths.prdFile, plan);
|
|
745
790
|
const startedAtMs = Date.now();
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
791
|
+
setAgentProjectionWindow(paths.prdFile, true);
|
|
792
|
+
let result2;
|
|
793
|
+
try {
|
|
794
|
+
result2 = await executeClaude(prompt, {
|
|
795
|
+
...inactivityOptions(),
|
|
796
|
+
iteration: i,
|
|
797
|
+
correctionCycle: plan.correctionCycle,
|
|
798
|
+
...activeStoryId === void 0 ? {} : { storyIds: [activeStoryId] }
|
|
799
|
+
});
|
|
800
|
+
} finally {
|
|
801
|
+
setAgentProjectionWindow(paths.prdFile, false);
|
|
802
|
+
}
|
|
803
|
+
const repository = getPlanRepository(paths.prdFile);
|
|
804
|
+
if (repository !== void 0) {
|
|
805
|
+
plan = await ingestAgentPlan(repository, plan);
|
|
806
|
+
} else {
|
|
807
|
+
plan = { ...await loadTaskPlan(paths.prdFile), ...closure };
|
|
808
|
+
await saveTaskPlan(paths.prdFile, plan);
|
|
809
|
+
}
|
|
752
810
|
const seconds = elapsedSecondsSince(startedAtMs);
|
|
753
811
|
if (result2.exitCode !== 0) {
|
|
754
812
|
publishIterationMetrics(i, result2.cost, seconds);
|
|
755
813
|
}
|
|
756
|
-
return { result: result2, startedAt, seconds, storiesBefore };
|
|
814
|
+
return { result: result2, startedAt, seconds, storiesBefore, lastErrorBefore };
|
|
757
815
|
},
|
|
758
816
|
{
|
|
759
817
|
policy: executeRetryPolicy(config),
|
|
@@ -831,6 +889,22 @@ async function runEngine(config, paths) {
|
|
|
831
889
|
return result.exitCode;
|
|
832
890
|
}
|
|
833
891
|
plan = await loadTaskPlan(paths.prdFile);
|
|
892
|
+
const outputError = await validateExecutionInput(plan);
|
|
893
|
+
if (outputError) {
|
|
894
|
+
printError(`Invalid execution result: ${outputError}`);
|
|
895
|
+
return 1;
|
|
896
|
+
}
|
|
897
|
+
if (plan.lastError && (JSON.stringify(plan.lastError) !== JSON.stringify(attempted.value.lastErrorBefore) || ![
|
|
898
|
+
"fatal_claude_failure",
|
|
899
|
+
"transient_claude_failure",
|
|
900
|
+
"invalid_completion_signal",
|
|
901
|
+
"task_execution"
|
|
902
|
+
].includes(plan.lastError.category))) {
|
|
903
|
+
publishIterationMetrics(i, result.cost, iterationSeconds);
|
|
904
|
+
getSessionPublisher().publish({ type: "iteration:end", at: isoNow(), iteration: i });
|
|
905
|
+
printError(`Execution blocked: ${plan.lastError.message}`);
|
|
906
|
+
return 1;
|
|
907
|
+
}
|
|
834
908
|
plan = clearLastError(plan, iterationStartedAt);
|
|
835
909
|
await saveTaskPlan(paths.prdFile, plan);
|
|
836
910
|
const storyUpdateCb = getStoryUpdateCallback();
|
|
@@ -861,7 +935,7 @@ async function runEngine(config, paths) {
|
|
|
861
935
|
if (result.output.includes("<promise>COMPLETE</promise>")) {
|
|
862
936
|
plan = await loadTaskPlan(paths.prdFile);
|
|
863
937
|
if (allStoriesPass(plan) && !hasPendingCorrection(plan)) {
|
|
864
|
-
plan = markIssueCompleted(plan);
|
|
938
|
+
plan = executionCompletion(markIssueCompleted(plan), config.inPipeline === true);
|
|
865
939
|
await saveTaskPlan(paths.prdFile, plan);
|
|
866
940
|
const elapsed2 = Math.floor((Date.now() - startTime) / 1e3);
|
|
867
941
|
printSummaryBox(
|
|
@@ -916,9 +990,61 @@ async function finishWithAcceptance(config, paths, plan) {
|
|
|
916
990
|
await saveTaskPlan(paths.prdFile, applied.plan);
|
|
917
991
|
return 1;
|
|
918
992
|
}
|
|
993
|
+
await saveTaskPlan(
|
|
994
|
+
paths.prdFile,
|
|
995
|
+
executionCompletion(markIssueCompleted(applied.plan), config.inPipeline === true)
|
|
996
|
+
);
|
|
919
997
|
return 0;
|
|
920
998
|
}
|
|
921
999
|
|
|
1000
|
+
// src/storage/db/session-publisher.ts
|
|
1001
|
+
var SqliteSessionPublisher = class extends MemoryPublisher {
|
|
1002
|
+
context;
|
|
1003
|
+
writeChain = Promise.resolve();
|
|
1004
|
+
heartbeatTimer;
|
|
1005
|
+
closed = false;
|
|
1006
|
+
constructor(context, options = {}) {
|
|
1007
|
+
super(options);
|
|
1008
|
+
this.context = context;
|
|
1009
|
+
this.heartbeatTimer = setInterval(() => this.enqueueHeartbeat(), DEFAULT_SESSION_HEARTBEAT_MS);
|
|
1010
|
+
this.heartbeatTimer.unref();
|
|
1011
|
+
}
|
|
1012
|
+
afterPublish(event) {
|
|
1013
|
+
if (this.closed) return;
|
|
1014
|
+
const sessionId = this.snapshot().sessionId;
|
|
1015
|
+
if (sessionId === null) return;
|
|
1016
|
+
const snapshot = this.snapshot();
|
|
1017
|
+
const sequence = this.version();
|
|
1018
|
+
this.writeChain = this.writeChain.then(async () => {
|
|
1019
|
+
try {
|
|
1020
|
+
await saveSessionEvent(this.context, { sessionId, sequence, event, snapshot });
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
this.warnOnce(error);
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
async flush() {
|
|
1027
|
+
await this.writeChain;
|
|
1028
|
+
}
|
|
1029
|
+
async close() {
|
|
1030
|
+
this.closed = true;
|
|
1031
|
+
clearInterval(this.heartbeatTimer);
|
|
1032
|
+
await this.flush();
|
|
1033
|
+
}
|
|
1034
|
+
enqueueHeartbeat() {
|
|
1035
|
+
if (this.closed) return;
|
|
1036
|
+
const sessionId = this.snapshot().sessionId;
|
|
1037
|
+
if (sessionId === null || this.snapshot().status !== "running") return;
|
|
1038
|
+
this.writeChain = this.writeChain.then(async () => {
|
|
1039
|
+
try {
|
|
1040
|
+
await touchStoredSession(this.context, sessionId);
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
this.warnOnce(error);
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
|
|
922
1048
|
// src/commands/execute.ts
|
|
923
1049
|
async function runExecute(positionalMaxIter, options) {
|
|
924
1050
|
const errors = await validateDependencies();
|
|
@@ -932,6 +1058,7 @@ async function runExecute(positionalMaxIter, options) {
|
|
|
932
1058
|
const maxIterations = options.maxIterations ?? positionalMaxIter;
|
|
933
1059
|
const config = createConfig({
|
|
934
1060
|
issueNumber: options.issue,
|
|
1061
|
+
inPipeline: options.inPipeline,
|
|
935
1062
|
maxIterations,
|
|
936
1063
|
retryLimit: options.retryLimit,
|
|
937
1064
|
retryForever: options.retryForever,
|
|
@@ -945,13 +1072,22 @@ async function runExecute(positionalMaxIter, options) {
|
|
|
945
1072
|
const issuePaths = await resolveIssuePaths(config.issueNumber, {
|
|
946
1073
|
projectRoot: paths.projectRoot
|
|
947
1074
|
});
|
|
948
|
-
|
|
1075
|
+
const filePublisher = new FilePublisher(issuePaths.sessionFile, {
|
|
949
1076
|
logLimit: webConfig.logLimit,
|
|
950
1077
|
includeLogs: webConfig.includeLogs
|
|
951
1078
|
});
|
|
952
|
-
|
|
1079
|
+
const repository = getPlanRepository(issuePaths.tasksFile);
|
|
1080
|
+
const publisher = repository === void 0 ? filePublisher : new MultiPublisher([
|
|
1081
|
+
new SqliteSessionPublisher(repository, {
|
|
1082
|
+
logLimit: webConfig.logLimit,
|
|
1083
|
+
includeLogs: webConfig.includeLogs
|
|
1084
|
+
}),
|
|
1085
|
+
filePublisher
|
|
1086
|
+
]);
|
|
1087
|
+
standalonePublisher = publisher;
|
|
1088
|
+
setSessionPublisher(publisher);
|
|
953
1089
|
const numericIssue2 = /^\d+$/.test(config.issueNumber) ? Number(config.issueNumber) : null;
|
|
954
|
-
|
|
1090
|
+
publisher.publish({
|
|
955
1091
|
type: "session:start",
|
|
956
1092
|
at: isoNow(),
|
|
957
1093
|
sessionId: randomUUID(),
|
|
@@ -967,10 +1103,10 @@ async function runExecute(positionalMaxIter, options) {
|
|
|
967
1103
|
cliVersion: getPackageVersion()
|
|
968
1104
|
}
|
|
969
1105
|
});
|
|
970
|
-
|
|
1106
|
+
publisher.publish({ type: "phase:start", at: isoNow(), phase: "execute" });
|
|
971
1107
|
await ensureWebMonitor(
|
|
972
1108
|
{
|
|
973
|
-
publisher
|
|
1109
|
+
publisher,
|
|
974
1110
|
port: webConfig.port,
|
|
975
1111
|
host: webConfig.host,
|
|
976
1112
|
refreshSeconds: webConfig.refreshSeconds
|
|
@@ -1017,6 +1153,7 @@ export {
|
|
|
1017
1153
|
printQueueSummary,
|
|
1018
1154
|
printRunSummary,
|
|
1019
1155
|
stripMarkdown,
|
|
1156
|
+
SqliteSessionPublisher,
|
|
1020
1157
|
runExecute
|
|
1021
1158
|
};
|
|
1022
|
-
//# sourceMappingURL=chunk-
|
|
1159
|
+
//# sourceMappingURL=chunk-JBYXJOJJ.js.map
|