issue-flow 0.17.0 → 0.19.1
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 +34 -100
- package/dist/agent-EWKA7357.js +32 -0
- package/dist/{analyze-6M3KEOX7.js → analyze-Z7ODC27F.js} +42 -41
- package/dist/analyze-Z7ODC27F.js.map +1 -0
- package/dist/{apply-ADB3ZK7W.js → apply-VNSWQS6O.js} +22 -24
- package/dist/apply-VNSWQS6O.js.map +1 -0
- package/dist/artifacts-AMJQJFFV.js +82 -0
- package/dist/artifacts-AMJQJFFV.js.map +1 -0
- package/dist/{bench-FM7I7EIV.js → bench-ZJEIACUK.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-ANI2NTR4.js → chunk-2EZW4X57.js} +28 -25
- package/dist/chunk-2EZW4X57.js.map +1 -0
- package/dist/{chunk-E6KXCV3T.js → chunk-2Q6PG3MT.js} +15 -15
- package/dist/chunk-2Q6PG3MT.js.map +1 -0
- package/dist/{chunk-C5TL7T4V.js → chunk-2YM2VH6R.js} +2 -2
- package/dist/{chunk-LPWPFIIB.js → chunk-2YYP7AE6.js} +14 -16
- package/dist/{chunk-LPWPFIIB.js.map → chunk-2YYP7AE6.js.map} +1 -1
- package/dist/chunk-3JD25HHJ.js +278 -0
- package/dist/chunk-3JD25HHJ.js.map +1 -0
- package/dist/{chunk-ISZ54SVE.js → chunk-46S7APLQ.js} +41 -17
- package/dist/chunk-46S7APLQ.js.map +1 -0
- package/dist/chunk-6AOZCWN2.js +21 -0
- package/dist/chunk-6AOZCWN2.js.map +1 -0
- package/dist/{chunk-QPGEFLMY.js → chunk-7CMVL4X6.js} +7 -7
- package/dist/chunk-7MJGYPHF.js +1571 -0
- package/dist/chunk-7MJGYPHF.js.map +1 -0
- package/dist/chunk-7ZZUSMBJ.js +35 -0
- package/dist/chunk-7ZZUSMBJ.js.map +1 -0
- package/dist/{chunk-DUS5ZNKL.js → chunk-DIMDAYZS.js} +7 -6
- package/dist/{chunk-DGRTJD5R.js → chunk-DXH4TQRB.js} +7 -7
- package/dist/{chunk-CRGIQFUQ.js → chunk-E2H3JV2E.js} +3 -1
- package/dist/chunk-E2H3JV2E.js.map +1 -0
- package/dist/{chunk-YUNBOUQH.js → chunk-ERAG5IQB.js} +8 -8
- package/dist/{chunk-D6ZBGJ3L.js → chunk-F2NFJ27Z.js} +130 -36
- package/dist/chunk-F2NFJ27Z.js.map +1 -0
- package/dist/{chunk-SOM4QA6C.js → chunk-GD7JZMSJ.js} +93 -18
- package/dist/chunk-GD7JZMSJ.js.map +1 -0
- package/dist/{chunk-YII4U7ZD.js → chunk-HEHHQU4G.js} +70 -81
- package/dist/chunk-HEHHQU4G.js.map +1 -0
- package/dist/{chunk-KZ5EY5XX.js → chunk-HHXWAWU6.js} +159 -114
- package/dist/chunk-HHXWAWU6.js.map +1 -0
- package/dist/{chunk-SGTTR4XW.js → chunk-HWWB22HC.js} +152 -100
- package/dist/chunk-HWWB22HC.js.map +1 -0
- package/dist/chunk-IMB6FA2F.js +673 -0
- package/dist/chunk-IMB6FA2F.js.map +1 -0
- package/dist/{chunk-6YQT3SSM.js → chunk-IQYXXFRC.js} +22 -6
- package/dist/chunk-IQYXXFRC.js.map +1 -0
- package/dist/chunk-J6VJ5M2U.js +1201 -0
- package/dist/chunk-J6VJ5M2U.js.map +1 -0
- package/dist/{chunk-B3TSS7BT.js → chunk-KDQRNVBZ.js} +3 -3
- package/dist/{chunk-A4H7P7T7.js → chunk-LRWIP7QS.js} +41 -26
- package/dist/chunk-LRWIP7QS.js.map +1 -0
- package/dist/{chunk-DV7HUC4Z.js → chunk-LYC57OXF.js} +20 -117
- package/dist/chunk-LYC57OXF.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-HJBF4WD2.js → chunk-MHA7Q3KU.js} +288 -108
- package/dist/chunk-MHA7Q3KU.js.map +1 -0
- package/dist/{chunk-UK4E2FUA.js → chunk-MQTVMB7W.js} +103 -76
- package/dist/chunk-MQTVMB7W.js.map +1 -0
- package/dist/chunk-NP47HRX2.js +136 -0
- package/dist/chunk-NP47HRX2.js.map +1 -0
- package/dist/{chunk-MI5TREN3.js → chunk-NYDAHE3Y.js} +52 -46
- package/dist/chunk-NYDAHE3Y.js.map +1 -0
- package/dist/{chunk-UWDCIFTP.js → chunk-P7ZYOC23.js} +41 -57
- package/dist/chunk-P7ZYOC23.js.map +1 -0
- package/dist/{chunk-3R27MJ5R.js → chunk-PFWHHYEO.js} +3 -3
- package/dist/{chunk-2SUFAE6X.js → chunk-PHPAK2L6.js} +44 -33
- package/dist/{chunk-2SUFAE6X.js.map → chunk-PHPAK2L6.js.map} +1 -1
- package/dist/{chunk-PPZ3K4YM.js → chunk-PRGPSNGA.js} +2 -2
- package/dist/{chunk-32JSYXEK.js → chunk-RGFMQFX7.js} +9 -23
- package/dist/{chunk-32JSYXEK.js.map → chunk-RGFMQFX7.js.map} +1 -1
- package/dist/{chunk-YVPZUN6X.js → chunk-TJVX42KY.js} +335 -38
- package/dist/chunk-TJVX42KY.js.map +1 -0
- package/dist/{chunk-RCZJQUTC.js → chunk-UDRAD7MF.js} +60 -27
- package/dist/chunk-UDRAD7MF.js.map +1 -0
- package/dist/{chunk-HY5HLFDU.js → chunk-UHDXXLYH.js} +148 -135
- package/dist/chunk-UHDXXLYH.js.map +1 -0
- package/dist/{chunk-RNFUVKC3.js → chunk-UPAAZU4W.js} +30 -13
- package/dist/chunk-UPAAZU4W.js.map +1 -0
- package/dist/{chunk-L6HSKTQE.js → chunk-W7LZLJAF.js} +4 -4
- package/dist/{chunk-HLAPOUEM.js → chunk-WB2FVIUS.js} +44 -12
- package/dist/chunk-WB2FVIUS.js.map +1 -0
- package/dist/{chunk-VE3IYNDN.js → chunk-WPHC6JBR.js} +9 -9
- package/dist/chunk-WPHC6JBR.js.map +1 -0
- package/dist/{chunk-KZXQWC7U.js → chunk-XHCP6ZNO.js} +201 -64
- package/dist/chunk-XHCP6ZNO.js.map +1 -0
- package/dist/{chunk-7F7LGMVO.js → chunk-XKR3VD2Z.js} +12 -12
- package/dist/{chunk-7F7LGMVO.js.map → chunk-XKR3VD2Z.js.map} +1 -1
- package/dist/{chunk-ICM463EO.js → chunk-XNK5UN2D.js} +5 -649
- package/dist/chunk-XNK5UN2D.js.map +1 -0
- package/dist/{chunk-Z5O2XBWA.js → chunk-XQLNZW7I.js} +40 -30
- package/dist/chunk-XQLNZW7I.js.map +1 -0
- package/dist/{chunk-QMIQR7EE.js → chunk-ZAYATXJD.js} +32 -316
- package/dist/chunk-ZAYATXJD.js.map +1 -0
- package/dist/cli.js +128 -60
- package/dist/cli.js.map +1 -1
- package/dist/compat-YPDZB34U.js +24 -0
- package/dist/{config-KGZUPAUP.js → config-56B4OHZE.js} +20 -17
- package/dist/{contract-JH2WBIO6.js → contract-HWLFFL3F.js} +2 -2
- package/dist/{conventions-RYN7OXJN.js → conventions-DW2CM5I3.js} +20 -16
- package/dist/{conventions-RYN7OXJN.js.map → conventions-DW2CM5I3.js.map} +1 -1
- package/dist/db-5NU77PJA.js +264 -0
- package/dist/db-5NU77PJA.js.map +1 -0
- package/dist/diagnostics-UCTS2VUF.js +25 -0
- package/dist/execute-R7AVOLNG.js +38 -0
- package/dist/{generate-CX6KNHZS.js → generate-O7GUXLN4.js} +46 -43
- package/dist/generate-O7GUXLN4.js.map +1 -0
- package/dist/{git-A2IP32PQ.js → git-LCG45YXD.js} +2 -3
- package/dist/history-6GSURU3H.js +101 -0
- package/dist/history-6GSURU3H.js.map +1 -0
- package/dist/init-DD6DBRC5.js +28 -0
- package/dist/{operations-CAKIT2BO.js → operations-HU5ZFEBA.js} +78 -52
- package/dist/operations-HU5ZFEBA.js.map +1 -0
- package/dist/{permissions-G4XCAESF.js → permissions-NNNSRC4P.js} +5 -6
- package/dist/plan-XR6B7EM3.js +38 -0
- package/dist/{policy-WX6O2JID.js → policy-RFJOJQL6.js} +17 -15
- package/dist/{policy-WX6O2JID.js.map → policy-RFJOJQL6.js.map} +1 -1
- package/dist/pr-OSCNJCOO.js +42 -0
- package/dist/pr-review-IKIYHFBO.js +33 -0
- package/dist/prd-HFDZJJQR.js +37 -0
- package/dist/{ps-76L6ZAZL.js → ps-AKRHGPSR.js} +26 -15
- package/dist/ps-AKRHGPSR.js.map +1 -0
- package/dist/{recorder-N7U66HCO.js → recorder-T7426CKV.js} +11 -7
- package/dist/registry-DWRKD65A.js +17 -0
- package/dist/repository-TABBRW2C.js +80 -0
- package/dist/resume-GDXKK5XP.js +278 -0
- package/dist/resume-GDXKK5XP.js.map +1 -0
- package/dist/review-JJHMQA2O.js +38 -0
- package/dist/routing-FPNUVE7T.js +35 -0
- package/dist/run-RLCURLLL.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-IGNTD4AB.js} +9 -4
- package/dist/state-manager-IGNTD4AB.js.map +1 -0
- package/dist/{usage-U7QNGCPK.js → usage-XW27AL5K.js} +69 -63
- package/dist/usage-XW27AL5K.js.map +1 -0
- package/dist/web-NCDBVZ5Y.js +205 -0
- package/dist/web-NCDBVZ5Y.js.map +1 -0
- package/package.json +20 -7
- 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/AGENTS.md +115 -7
- package/web/public/app.css +393 -245
- package/web/public/app.js +212 -143
- package/web/public/index.html +106 -78
- package/dist/agent-TB6W7PT7.js +0 -30
- package/dist/analyze-6M3KEOX7.js.map +0 -1
- package/dist/apply-ADB3ZK7W.js.map +0 -1
- package/dist/chunk-6YQT3SSM.js.map +0 -1
- package/dist/chunk-A4H7P7T7.js.map +0 -1
- package/dist/chunk-ANI2NTR4.js.map +0 -1
- package/dist/chunk-CRGIQFUQ.js.map +0 -1
- package/dist/chunk-D6ZBGJ3L.js.map +0 -1
- package/dist/chunk-DV7HUC4Z.js.map +0 -1
- package/dist/chunk-E6KXCV3T.js.map +0 -1
- package/dist/chunk-HAKDLR6N.js.map +0 -1
- package/dist/chunk-HJBF4WD2.js.map +0 -1
- package/dist/chunk-HLAPOUEM.js.map +0 -1
- package/dist/chunk-HY5HLFDU.js.map +0 -1
- package/dist/chunk-ICM463EO.js.map +0 -1
- package/dist/chunk-ISZ54SVE.js.map +0 -1
- package/dist/chunk-KZ5EY5XX.js.map +0 -1
- package/dist/chunk-KZXQWC7U.js.map +0 -1
- package/dist/chunk-MI5TREN3.js.map +0 -1
- package/dist/chunk-QMIQR7EE.js.map +0 -1
- package/dist/chunk-RCZJQUTC.js.map +0 -1
- package/dist/chunk-RNFUVKC3.js.map +0 -1
- package/dist/chunk-SGTTR4XW.js.map +0 -1
- package/dist/chunk-SOM4QA6C.js.map +0 -1
- package/dist/chunk-UK4E2FUA.js.map +0 -1
- package/dist/chunk-UWDCIFTP.js.map +0 -1
- package/dist/chunk-VE3IYNDN.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-XZUEFTUP.js +0 -243
- package/dist/chunk-XZUEFTUP.js.map +0 -1
- package/dist/chunk-YII4U7ZD.js.map +0 -1
- package/dist/chunk-YVPZUN6X.js.map +0 -1
- package/dist/chunk-Z5O2XBWA.js.map +0 -1
- package/dist/diagnostics-HTCWF7F4.js +0 -23
- package/dist/execute-QL3ZQIAP.js +0 -37
- package/dist/generate-CX6KNHZS.js.map +0 -1
- package/dist/init-PDVQDRS6.js +0 -24
- package/dist/operations-CAKIT2BO.js.map +0 -1
- package/dist/plan-PRORGS3L.js +0 -34
- package/dist/pr-27RMQI5F.js +0 -39
- package/dist/pr-review-VNESMAMT.js +0 -32
- package/dist/prd-ZNC4T5BS.js +0 -33
- package/dist/ps-76L6ZAZL.js.map +0 -1
- package/dist/registry-XLHHYLTR.js +0 -15
- package/dist/resume-VLWIE264.js +0 -244
- package/dist/resume-VLWIE264.js.map +0 -1
- package/dist/review-7CREJDKB.js +0 -35
- package/dist/routing-VCDYHSXA.js +0 -33
- package/dist/run-3MPORLF5.js +0 -56
- package/dist/usage-U7QNGCPK.js.map +0 -1
- package/dist/web-HUNDHCJ4.js +0 -172
- package/dist/web-HUNDHCJ4.js.map +0 -1
- /package/dist/{agent-TB6W7PT7.js.map → agent-EWKA7357.js.map} +0 -0
- /package/dist/{bench-FM7I7EIV.js.map → bench-ZJEIACUK.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-QPGEFLMY.js.map → chunk-7CMVL4X6.js.map} +0 -0
- /package/dist/{chunk-DUS5ZNKL.js.map → chunk-DIMDAYZS.js.map} +0 -0
- /package/dist/{chunk-DGRTJD5R.js.map → chunk-DXH4TQRB.js.map} +0 -0
- /package/dist/{chunk-YUNBOUQH.js.map → chunk-ERAG5IQB.js.map} +0 -0
- /package/dist/{chunk-B3TSS7BT.js.map → chunk-KDQRNVBZ.js.map} +0 -0
- /package/dist/{chunk-3R27MJ5R.js.map → chunk-PFWHHYEO.js.map} +0 -0
- /package/dist/{chunk-PPZ3K4YM.js.map → chunk-PRGPSNGA.js.map} +0 -0
- /package/dist/{chunk-L6HSKTQE.js.map → chunk-W7LZLJAF.js.map} +0 -0
- /package/dist/{config-KGZUPAUP.js.map → compat-YPDZB34U.js.map} +0 -0
- /package/dist/{contract-JH2WBIO6.js.map → config-56B4OHZE.js.map} +0 -0
- /package/dist/{diagnostics-HTCWF7F4.js.map → contract-HWLFFL3F.js.map} +0 -0
- /package/dist/{execute-QL3ZQIAP.js.map → diagnostics-UCTS2VUF.js.map} +0 -0
- /package/dist/{git-A2IP32PQ.js.map → execute-R7AVOLNG.js.map} +0 -0
- /package/dist/{init-PDVQDRS6.js.map → git-LCG45YXD.js.map} +0 -0
- /package/dist/{permissions-G4XCAESF.js.map → init-DD6DBRC5.js.map} +0 -0
- /package/dist/{plan-PRORGS3L.js.map → permissions-NNNSRC4P.js.map} +0 -0
- /package/dist/{pr-27RMQI5F.js.map → plan-XR6B7EM3.js.map} +0 -0
- /package/dist/{pr-review-VNESMAMT.js.map → pr-OSCNJCOO.js.map} +0 -0
- /package/dist/{prd-ZNC4T5BS.js.map → pr-review-IKIYHFBO.js.map} +0 -0
- /package/dist/{recorder-N7U66HCO.js.map → prd-HFDZJJQR.js.map} +0 -0
- /package/dist/{registry-XLHHYLTR.js.map → recorder-T7426CKV.js.map} +0 -0
- /package/dist/{review-7CREJDKB.js.map → registry-DWRKD65A.js.map} +0 -0
- /package/dist/{routing-VCDYHSXA.js.map → repository-TABBRW2C.js.map} +0 -0
- /package/dist/{run-3MPORLF5.js.map → review-JJHMQA2O.js.map} +0 -0
- /package/dist/{session-publisher-KAJQRHNZ.js.map → routing-FPNUVE7T.js.map} +0 -0
- /package/dist/{state-manager-UPHO2EA2.js.map → run-RLCURLLL.js.map} +0 -0
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
CLAUDE_CAPABILITIES,
|
|
5
5
|
CODEX_CAPABILITIES,
|
|
6
6
|
CURSOR_CAPABILITIES
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IMB6FA2F.js";
|
|
8
8
|
|
|
9
9
|
// src/routing/models.ts
|
|
10
10
|
var MODEL_CATALOG_VERSION = "1";
|
|
@@ -42,16 +42,50 @@ function modelForTier(harness, tier) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// src/routing/policy.ts
|
|
45
|
-
var RECOMMENDED_POLICY_VERSION = "
|
|
45
|
+
var RECOMMENDED_POLICY_VERSION = "2";
|
|
46
46
|
var RECOMMENDED_POLICY = {
|
|
47
|
-
analyze: {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
analyze: {
|
|
48
|
+
preferredTier: "fast",
|
|
49
|
+
optimizeFor: "cost-speed",
|
|
50
|
+
affinityHarness: "codex-cli"
|
|
51
|
+
},
|
|
52
|
+
generate: {
|
|
53
|
+
preferredTier: "mid",
|
|
54
|
+
optimizeFor: "balanced",
|
|
55
|
+
affinityHarness: "claude-code"
|
|
56
|
+
},
|
|
57
|
+
prd: {
|
|
58
|
+
preferredTier: "mid",
|
|
59
|
+
optimizeFor: "balanced",
|
|
60
|
+
affinityHarness: "claude-code"
|
|
61
|
+
},
|
|
62
|
+
plan: {
|
|
63
|
+
preferredTier: "fast",
|
|
64
|
+
optimizeFor: "cost-speed",
|
|
65
|
+
affinityHarness: "codex-cli"
|
|
66
|
+
},
|
|
67
|
+
execute: {
|
|
68
|
+
preferredTier: "mid",
|
|
69
|
+
escalateTo: "strong",
|
|
70
|
+
optimizeFor: "time-to-correct",
|
|
71
|
+
affinityHarness: "claude-code"
|
|
72
|
+
},
|
|
73
|
+
review: {
|
|
74
|
+
preferredTier: "strong",
|
|
75
|
+
optimizeFor: "quality",
|
|
76
|
+
independentFrom: "execute",
|
|
77
|
+
affinityHarness: "codex-cli"
|
|
78
|
+
},
|
|
79
|
+
pr: {
|
|
80
|
+
preferredTier: "fast",
|
|
81
|
+
optimizeFor: "cost-speed",
|
|
82
|
+
affinityHarness: "codex-cli"
|
|
83
|
+
},
|
|
84
|
+
"pr-review": {
|
|
85
|
+
preferredTier: "strong",
|
|
86
|
+
optimizeFor: "quality",
|
|
87
|
+
affinityHarness: "codex-cli"
|
|
88
|
+
}
|
|
55
89
|
};
|
|
56
90
|
var PROVIDERS = {
|
|
57
91
|
"claude-code": "claude",
|
|
@@ -64,11 +98,12 @@ function recommendedFor(phase) {
|
|
|
64
98
|
}
|
|
65
99
|
function recommendedTarget(phase) {
|
|
66
100
|
const entry = recommendedFor(phase);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
101
|
+
const harness = entry.affinityHarness;
|
|
102
|
+
if (harness === void 0) return null;
|
|
103
|
+
const model = modelForTier(harness, entry.preferredTier);
|
|
104
|
+
const provider = PROVIDERS[harness];
|
|
70
105
|
if (model === null || provider === void 0) return null;
|
|
71
|
-
return { harness
|
|
106
|
+
return { harness, provider, model: model.id, tier: entry.preferredTier };
|
|
72
107
|
}
|
|
73
108
|
|
|
74
109
|
// src/routing/priors.ts
|
|
@@ -158,10 +193,46 @@ function classify(hay, signals) {
|
|
|
158
193
|
// src/routing/capabilities.ts
|
|
159
194
|
function filterEligible(input) {
|
|
160
195
|
const reasons = [];
|
|
196
|
+
let eligible = true;
|
|
161
197
|
if (input.requiresExtraDirectories && input.capabilities.extraDirectories === "none") {
|
|
162
198
|
reasons.push("MISSING_CAPABILITY:extraDirectories");
|
|
199
|
+
reasons.push("CAPABILITY_MISMATCH");
|
|
200
|
+
eligible = false;
|
|
201
|
+
}
|
|
202
|
+
const readiness = input.readiness;
|
|
203
|
+
if (readiness !== void 0 && readiness !== null) {
|
|
204
|
+
if (!readiness.installed) {
|
|
205
|
+
reasons.push("PROVIDER_NOT_INSTALLED");
|
|
206
|
+
eligible = false;
|
|
207
|
+
} else if (readiness.cooldownUntil !== null && Date.parse(readiness.cooldownUntil) > Date.parse(readiness.observedAt)) {
|
|
208
|
+
reasons.push("PROVIDER_COOLDOWN");
|
|
209
|
+
eligible = false;
|
|
210
|
+
} else if (readiness.authentication === "failed") {
|
|
211
|
+
reasons.push("AUTHENTICATION_FAILED");
|
|
212
|
+
reasons.push("PROVIDER_UNAVAILABLE");
|
|
213
|
+
eligible = false;
|
|
214
|
+
} else if (readiness.state === "unavailable") {
|
|
215
|
+
reasons.push("PROVIDER_UNAVAILABLE");
|
|
216
|
+
eligible = false;
|
|
217
|
+
} else if (readiness.authentication === "unverified" || readiness.state === "conditional") {
|
|
218
|
+
reasons.push("AUTHENTICATION_UNVERIFIED");
|
|
219
|
+
reasons.push("READINESS_CONDITIONAL");
|
|
220
|
+
if (input.allowConditional === false) {
|
|
221
|
+
reasons.push("PROVIDER_UNAVAILABLE");
|
|
222
|
+
eligible = false;
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
reasons.push("READINESS_CONFIRMED");
|
|
226
|
+
}
|
|
227
|
+
const modelAccess = readiness.models[0]?.access;
|
|
228
|
+
if (modelAccess === "denied") {
|
|
229
|
+
reasons.push("MODEL_NOT_ACCESSIBLE");
|
|
230
|
+
eligible = false;
|
|
231
|
+
} else if (modelAccess === "unverified") {
|
|
232
|
+
reasons.push("MODEL_ACCESS_UNVERIFIED");
|
|
233
|
+
}
|
|
163
234
|
}
|
|
164
|
-
return { eligible
|
|
235
|
+
return { eligible, reasonCodes: reasons };
|
|
165
236
|
}
|
|
166
237
|
|
|
167
238
|
// src/routing/score.ts
|
|
@@ -176,6 +247,12 @@ var PROFILE_WEIGHTS = {
|
|
|
176
247
|
function clampLearned(value) {
|
|
177
248
|
return Math.max(-DELTA, Math.min(DELTA, value));
|
|
178
249
|
}
|
|
250
|
+
function readinessMultiplier(state) {
|
|
251
|
+
if (state === "ready") return 1;
|
|
252
|
+
if (state === "conditional") return 0.72;
|
|
253
|
+
if (state === "unavailable") return 0;
|
|
254
|
+
return 1;
|
|
255
|
+
}
|
|
179
256
|
function scoreCandidates(inputs) {
|
|
180
257
|
const profile = inputs[0]?.profile ?? "balanced";
|
|
181
258
|
const scored = inputs.map((input) => {
|
|
@@ -186,7 +263,8 @@ function scoreCandidates(inputs) {
|
|
|
186
263
|
const weights = PROFILE_WEIGHTS[profile];
|
|
187
264
|
const costScore = 1 / input.relativeCost;
|
|
188
265
|
const latencyScore = 1 / input.relativeLatency;
|
|
189
|
-
const
|
|
266
|
+
const preferenceBonus = (input.preferredTierMatch === true ? 0.12 : 0) + (input.affinityMatch === true ? 0.08 : 0);
|
|
267
|
+
const score = input.eligible ? ((prior + learned) * weights.quality + costScore * weights.cost + latencyScore * weights.latency + preferenceBonus) * readinessMultiplier(input.readinessState) : 0;
|
|
190
268
|
const reasonCodes = [...input.reasonCodes];
|
|
191
269
|
if (input.eligible && samples === 0) reasonCodes.push("COLD_START");
|
|
192
270
|
if (input.eligible && learned > 0) reasonCodes.push("HIGH_HISTORICAL_SUCCESS");
|
|
@@ -259,32 +337,48 @@ function decideRouting(input) {
|
|
|
259
337
|
reasonCodes: ["EXPLICIT_CONFIG"]
|
|
260
338
|
};
|
|
261
339
|
}
|
|
340
|
+
const recommendation = input.policy === "recommended" ? recommendedFor(input.phase) : null;
|
|
341
|
+
const allowConditional = recommendation?.allowConditional !== false;
|
|
262
342
|
const scored = scoreCandidates(
|
|
263
343
|
Object.keys(CAPS).flatMap((harness) => {
|
|
344
|
+
const providerId = PROVIDER[harness];
|
|
345
|
+
const readiness = input.readiness === void 0 || input.readiness === null ? null : input.readiness.providers[providerId] ?? null;
|
|
264
346
|
const eligibility = filterEligible({
|
|
265
347
|
harness,
|
|
266
348
|
capabilities: CAPS[harness],
|
|
267
349
|
phase: input.phase,
|
|
268
|
-
requiresExtraDirectories: input.requiresExtraDirectories === true
|
|
350
|
+
requiresExtraDirectories: input.requiresExtraDirectories === true,
|
|
351
|
+
readiness,
|
|
352
|
+
allowConditional
|
|
353
|
+
});
|
|
354
|
+
return modelsFor(harness, CAPS[harness]).map((model) => {
|
|
355
|
+
const reasonCodes = [...eligibility.reasonCodes];
|
|
356
|
+
const preferredTierMatch = recommendation !== null && model.tier === recommendation.preferredTier;
|
|
357
|
+
const affinityMatch = recommendation !== null && recommendation.affinityHarness === harness;
|
|
358
|
+
if (preferredTierMatch) reasonCodes.push("RECOMMENDED_POLICY");
|
|
359
|
+
if (affinityMatch) reasonCodes.push("HARNESS_AFFINITY");
|
|
360
|
+
if (model.tier === "fast" && analyzed.risk !== "high") {
|
|
361
|
+
reasonCodes.push("CHEAPER_TIER_SUFFICIENT");
|
|
362
|
+
}
|
|
363
|
+
if (model.tier === "strong" && (analyzed.risk === "high" || profile === "quality")) {
|
|
364
|
+
reasonCodes.push("STRONGER_TIER_FOR_RISK");
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
harness,
|
|
368
|
+
provider: providerId,
|
|
369
|
+
model: model.id,
|
|
370
|
+
tier: model.tier,
|
|
371
|
+
relativeCost: model.relativeCost,
|
|
372
|
+
relativeLatency: model.relativeLatency,
|
|
373
|
+
eligible: eligibility.eligible,
|
|
374
|
+
reasonCodes,
|
|
375
|
+
taskClass: analyzed.taskClass,
|
|
376
|
+
profile,
|
|
377
|
+
readinessState: readiness?.state ?? null,
|
|
378
|
+
preferredTierMatch,
|
|
379
|
+
affinityMatch
|
|
380
|
+
};
|
|
269
381
|
});
|
|
270
|
-
const recommendation = input.policy === "recommended" ? recommendedFor(input.phase) : null;
|
|
271
|
-
return modelsFor(harness, CAPS[harness]).map((model) => ({
|
|
272
|
-
harness,
|
|
273
|
-
provider: PROVIDER[harness] ?? harness,
|
|
274
|
-
model: model.id,
|
|
275
|
-
tier: model.tier,
|
|
276
|
-
relativeCost: model.relativeCost,
|
|
277
|
-
relativeLatency: model.relativeLatency,
|
|
278
|
-
eligible: eligibility.eligible && (recommendation === null || model.tier === recommendation.tier && (recommendation.preferHarness === void 0 || recommendation.preferHarness === harness)),
|
|
279
|
-
reasonCodes: [
|
|
280
|
-
...eligibility.reasonCodes,
|
|
281
|
-
...recommendation !== null && model.tier === recommendation.tier && (recommendation.preferHarness === void 0 || recommendation.preferHarness === harness) ? ["RECOMMENDED_POLICY"] : [],
|
|
282
|
-
...model.tier === "fast" && analyzed.risk !== "high" ? ["CHEAPER_TIER_SUFFICIENT"] : [],
|
|
283
|
-
...model.tier === "strong" && (analyzed.risk === "high" || profile === "quality") ? ["STRONGER_TIER_FOR_RISK"] : []
|
|
284
|
-
],
|
|
285
|
-
taskClass: analyzed.taskClass,
|
|
286
|
-
profile
|
|
287
|
-
}));
|
|
288
382
|
})
|
|
289
383
|
);
|
|
290
384
|
const selected = pickSelected(scored);
|
|
@@ -318,4 +412,4 @@ export {
|
|
|
318
412
|
PRIORS_VERSION,
|
|
319
413
|
decideRouting
|
|
320
414
|
};
|
|
321
|
-
//# sourceMappingURL=chunk-
|
|
415
|
+
//# sourceMappingURL=chunk-F2NFJ27Z.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/routing/models.ts","../src/routing/policy.ts","../src/routing/priors.ts","../src/routing/analyze.ts","../src/routing/capabilities.ts","../src/routing/score.ts","../src/routing/decide.ts"],"sourcesContent":["import type { AgentCapabilities } from '../agents/types.js';\n\nexport const MODEL_CATALOG_VERSION = '1';\n\nexport type ModelTier = 'fast' | 'mid' | 'strong';\n\nexport interface ModelEntry {\n id: string | null;\n tier: ModelTier;\n relativeCost: number;\n relativeLatency: number;\n}\n\n/**\n * Deliberately hand-maintained. Harnesses do not expose a stable price/tier API,\n * so relative values are more durable than USD prices and keep the router pure.\n */\nexport const MODEL_CATALOG: Record<string, readonly ModelEntry[]> = {\n 'claude-code': [\n { id: 'haiku', tier: 'fast', relativeCost: 1, relativeLatency: 1 },\n { id: 'sonnet', tier: 'mid', relativeCost: 3.5, relativeLatency: 1.5 },\n { id: 'opus', tier: 'strong', relativeCost: 8, relativeLatency: 2.2 },\n ],\n 'codex-cli': [\n { id: 'gpt-5.6-luna', tier: 'fast', relativeCost: 1, relativeLatency: 1 },\n { id: 'gpt-5.6-terra', tier: 'mid', relativeCost: 3, relativeLatency: 1.45 },\n { id: 'gpt-5.6-sol', tier: 'strong', relativeCost: 7, relativeLatency: 2.1 },\n ],\n 'cursor-cli': [\n { id: 'auto-fast', tier: 'fast', relativeCost: 1, relativeLatency: 1 },\n { id: 'auto', tier: 'mid', relativeCost: 3, relativeLatency: 1.5 },\n { id: 'auto-strong', tier: 'strong', relativeCost: 7, relativeLatency: 2.1 },\n ],\n 'antigravity-cli': [\n { id: 'gemini-3.5-flash-low', tier: 'fast', relativeCost: 1, relativeLatency: 1 },\n { id: 'gemini-3.5-flash-medium', tier: 'mid', relativeCost: 2.5, relativeLatency: 1.4 },\n { id: 'gemini-3.5-pro-high', tier: 'strong', relativeCost: 6, relativeLatency: 2 },\n ],\n};\n\nconst DEFAULT_ONLY: readonly ModelEntry[] = [\n { id: null, tier: 'mid', relativeCost: 1, relativeLatency: 1 },\n];\n\nexport function modelsFor(harness: string, capabilities: AgentCapabilities): readonly ModelEntry[] {\n if (!capabilities.modelSelection) return DEFAULT_ONLY;\n return MODEL_CATALOG[harness] ?? DEFAULT_ONLY;\n}\n\nexport function modelForTier(harness: string, tier: ModelTier): ModelEntry | null {\n return MODEL_CATALOG[harness]?.find((entry) => entry.tier === tier) ?? null;\n}\n","import type { AgentPhase, AgentProviderId } from '../agents/types.js';\nimport { type ModelTier, modelForTier } from './models.js';\nimport type { ExecutionTarget } from './types.js';\n\nexport const RECOMMENDED_POLICY_VERSION = '2';\n\nexport type OptimizeFor = 'cost-speed' | 'time-to-correct' | 'quality' | 'balanced';\n\n/**\n * Phase objectives for the opinionated policy. Affinity is a soft prior /\n * tie-break — never an eligibility filter that eliminates other harnesses.\n */\nexport interface RecommendedPolicyEntry {\n preferredTier: ModelTier;\n optimizeFor: OptimizeFor;\n escalateTo?: ModelTier;\n independentFrom?: AgentPhase;\n /** When true (default), conditional providers may win if no ready peer exists. */\n allowConditional?: boolean;\n /** Soft harness affinity for scoring — not a hard pin. */\n affinityHarness?: string;\n}\n\n/** The token-economy guidance from docs/agents.md, made executable and reviewable. */\nexport const RECOMMENDED_POLICY: Record<AgentPhase, RecommendedPolicyEntry> = {\n analyze: {\n preferredTier: 'fast',\n optimizeFor: 'cost-speed',\n affinityHarness: 'codex-cli',\n },\n generate: {\n preferredTier: 'mid',\n optimizeFor: 'balanced',\n affinityHarness: 'claude-code',\n },\n prd: {\n preferredTier: 'mid',\n optimizeFor: 'balanced',\n affinityHarness: 'claude-code',\n },\n plan: {\n preferredTier: 'fast',\n optimizeFor: 'cost-speed',\n affinityHarness: 'codex-cli',\n },\n execute: {\n preferredTier: 'mid',\n escalateTo: 'strong',\n optimizeFor: 'time-to-correct',\n affinityHarness: 'claude-code',\n },\n review: {\n preferredTier: 'strong',\n optimizeFor: 'quality',\n independentFrom: 'execute',\n affinityHarness: 'codex-cli',\n },\n pr: {\n preferredTier: 'fast',\n optimizeFor: 'cost-speed',\n affinityHarness: 'codex-cli',\n },\n 'pr-review': {\n preferredTier: 'strong',\n optimizeFor: 'quality',\n affinityHarness: 'codex-cli',\n },\n};\n\nconst PROVIDERS: Record<string, AgentProviderId> = {\n 'claude-code': 'claude',\n 'codex-cli': 'codex',\n 'cursor-cli': 'cursor',\n 'antigravity-cli': 'antigravity',\n};\n\nexport function recommendedFor(phase: AgentPhase): RecommendedPolicyEntry {\n return RECOMMENDED_POLICY[phase];\n}\n\n/**\n * Illustrative affinity target for `routing explain`. The live decision may\n * pick a different harness when readiness or score says so.\n */\nexport function recommendedTarget(phase: AgentPhase): ExecutionTarget | null {\n const entry = recommendedFor(phase);\n const harness = entry.affinityHarness;\n if (harness === undefined) return null;\n const model = modelForTier(harness, entry.preferredTier);\n const provider = PROVIDERS[harness];\n if (model === null || provider === undefined) return null;\n return { harness, provider, model: model.id, tier: entry.preferredTier };\n}\n","import type { ModelTier } from './models.js';\nimport type { TaskClass } from './types.js';\n\n/** Bump when a cell's provenance changes. */\nexport const PRIORS_VERSION = '2';\n\n/**\n * Cold-start priors. Provenance: author judgement on 2026-08-30 from the\n * #79 baseline and the #76 capability matrix — not measured affinity.\n */\ntype HarnessPriors = Record<string, Record<ModelTier, number>>;\n\nfunction tiers(mid: number): Record<ModelTier, number> {\n return { fast: Math.max(0, mid - 0.25), mid, strong: Math.min(1, mid + 0.12) };\n}\n\nfunction harnesses(values: Record<string, number>): HarnessPriors {\n return Object.fromEntries(Object.entries(values).map(([harness, mid]) => [harness, tiers(mid)]));\n}\n\nexport const PRIORS: Record<TaskClass, HarnessPriors> = {\n bugfix: harnesses({\n 'claude-code': 0.7,\n 'codex-cli': 0.95,\n 'cursor-cli': 0.65,\n 'antigravity-cli': 0.6,\n }),\n feature: harnesses({\n 'claude-code': 0.85,\n 'codex-cli': 0.8,\n 'cursor-cli': 0.7,\n 'antigravity-cli': 0.65,\n }),\n refactor: harnesses({\n 'claude-code': 0.75,\n 'codex-cli': 0.9,\n 'cursor-cli': 0.65,\n 'antigravity-cli': 0.6,\n }),\n docs: harnesses({\n 'claude-code': 0.8,\n 'codex-cli': 0.7,\n 'cursor-cli': 0.75,\n 'antigravity-cli': 0.7,\n }),\n test: harnesses({\n 'claude-code': 0.7,\n 'codex-cli': 0.9,\n 'cursor-cli': 0.65,\n 'antigravity-cli': 0.6,\n }),\n infra: harnesses({\n 'claude-code': 0.8,\n 'codex-cli': 0.75,\n 'cursor-cli': 0.6,\n 'antigravity-cli': 0.55,\n }),\n analysis: harnesses({\n 'claude-code': 0.95,\n 'codex-cli': 0.6,\n 'cursor-cli': 0.7,\n 'antigravity-cli': 0.65,\n }),\n};\n\nexport function priorFor(taskClass: TaskClass, harness: string, tier: ModelTier): number {\n return PRIORS[taskClass][harness]?.[tier] ?? 0.5;\n}\n","import type { RiskLevel, TaskClass, TaskSignals } from './types.js';\n\nconst RISK_PATHS = [/auth/i, /migrat/i, /infra/i, /\\.github\\//i, /permission/i, /secret/i];\n\nexport interface AnalyzedTask {\n taskClass: TaskClass;\n risk: RiskLevel;\n}\n\n/** Deterministic. No model, no I/O. Every class is reachable. */\nexport function analyzeTask(signals: TaskSignals = {}): AnalyzedTask {\n const hay = [\n signals.title ?? '',\n signals.body ?? '',\n ...(signals.labels ?? []),\n ...(signals.paths ?? []),\n ]\n .join(' ')\n .toLowerCase();\n\n const taskClass = classify(hay, signals);\n const risk = RISK_PATHS.some((pattern) =>\n (signals.paths ?? []).some((path) => pattern.test(path)),\n )\n ? 'high'\n : /security|auth|migrat/.test(hay)\n ? 'high'\n : /refactor|large|epic/.test(hay)\n ? 'medium'\n : 'low';\n\n return { taskClass, risk };\n}\n\nfunction classify(hay: string, signals: TaskSignals): TaskClass {\n if (/\\bdocs?\\b|readme|changelog/.test(hay)) return 'docs';\n if (/\\btest|spec|coverage/.test(hay)) return 'test';\n if (/\\bcie?|deploy|infra|workflow|docker/.test(hay)) return 'infra';\n if (/\\banaly[sz]e|investigat|research/.test(hay)) return 'analysis';\n if (/\\bbug|fix|regres+ion|crash/.test(hay)) return 'bugfix';\n if (/\\brefactor|cleanup|extract/.test(hay)) return 'refactor';\n if (/\\bfeat|feature|add |implement/.test(hay)) return 'feature';\n if ((signals.labels ?? []).some((label) => /bug/i.test(label))) return 'bugfix';\n if ((signals.labels ?? []).some((label) => /enhancement|feature/i.test(label))) return 'feature';\n return 'feature';\n}\n","import type { ProviderReadiness } from '../agents/availability.js';\nimport type { AgentCapabilities, AgentPhase } from '../agents/types.js';\n\nexport interface EligibilityInput {\n harness: string;\n capabilities: AgentCapabilities;\n phase: AgentPhase;\n requiresExtraDirectories: boolean;\n readiness?: ProviderReadiness | null;\n /** Default true. When false, conditional/unverified providers are ineligible. */\n allowConditional?: boolean;\n}\n\nexport interface Eligibility {\n eligible: boolean;\n reasonCodes: string[];\n}\n\n/**\n * Restriction missing (`toolAllowlist`) is ignored. Enablement missing\n * (`extraDirectories`) is ineligible when the phase needs addDirs.\n * Readiness is injected by the caller — this module stays pure.\n */\nexport function filterEligible(input: EligibilityInput): Eligibility {\n const reasons: string[] = [];\n let eligible = true;\n\n if (input.requiresExtraDirectories && input.capabilities.extraDirectories === 'none') {\n reasons.push('MISSING_CAPABILITY:extraDirectories');\n reasons.push('CAPABILITY_MISMATCH');\n eligible = false;\n }\n\n const readiness = input.readiness;\n if (readiness !== undefined && readiness !== null) {\n if (!readiness.installed) {\n reasons.push('PROVIDER_NOT_INSTALLED');\n eligible = false;\n } else if (\n readiness.cooldownUntil !== null &&\n Date.parse(readiness.cooldownUntil) > Date.parse(readiness.observedAt)\n ) {\n reasons.push('PROVIDER_COOLDOWN');\n eligible = false;\n } else if (readiness.authentication === 'failed') {\n reasons.push('AUTHENTICATION_FAILED');\n reasons.push('PROVIDER_UNAVAILABLE');\n eligible = false;\n } else if (readiness.state === 'unavailable') {\n reasons.push('PROVIDER_UNAVAILABLE');\n eligible = false;\n } else if (readiness.authentication === 'unverified' || readiness.state === 'conditional') {\n reasons.push('AUTHENTICATION_UNVERIFIED');\n reasons.push('READINESS_CONDITIONAL');\n if (input.allowConditional === false) {\n reasons.push('PROVIDER_UNAVAILABLE');\n eligible = false;\n }\n } else {\n reasons.push('READINESS_CONFIRMED');\n }\n\n const modelAccess = readiness.models[0]?.access;\n if (modelAccess === 'denied') {\n reasons.push('MODEL_NOT_ACCESSIBLE');\n eligible = false;\n } else if (modelAccess === 'unverified') {\n reasons.push('MODEL_ACCESS_UNVERIFIED');\n }\n }\n\n return { eligible, reasonCodes: reasons };\n}\n","import type { ModelTier } from './models.js';\nimport { priorFor } from './priors.js';\nimport type { Candidate, RoutingProfile, TaskClass } from './types.js';\n\nexport const DELTA = 0.2;\nexport const MIN_SAMPLE_SIZE = 8;\n\nexport const PROFILE_WEIGHTS: Record<\n RoutingProfile,\n { quality: number; cost: number; latency: number }\n> = {\n economy: { quality: 0.5, cost: 0.4, latency: 0.1 },\n balanced: { quality: 0.6, cost: 0.25, latency: 0.15 },\n quality: { quality: 0.85, cost: 0.05, latency: 0.1 },\n speed: { quality: 0.45, cost: 0.1, latency: 0.45 },\n};\n\nexport interface ScoreInput {\n harness: string;\n provider: string;\n model?: string | null;\n tier: ModelTier;\n relativeCost: number;\n relativeLatency: number;\n eligible: boolean;\n reasonCodes: string[];\n taskClass: TaskClass;\n learned?: number;\n samples?: number;\n profile?: RoutingProfile;\n /** Observed cost status from #78; catalog-relative cost is always available. */\n costStatus?: 'reported' | 'estimated' | 'unknown';\n readinessState?: 'ready' | 'conditional' | 'unavailable' | null;\n preferredTierMatch?: boolean;\n affinityMatch?: boolean;\n}\n\nexport function clampLearned(value: number): number {\n return Math.max(-DELTA, Math.min(DELTA, value));\n}\n\nfunction readinessMultiplier(state: ScoreInput['readinessState']): number {\n if (state === 'ready') return 1;\n if (state === 'conditional') return 0.72;\n if (state === 'unavailable') return 0;\n return 1;\n}\n\nexport function scoreCandidates(inputs: readonly ScoreInput[]): Candidate[] {\n const profile = inputs[0]?.profile ?? 'balanced';\n const scored: Candidate[] = inputs.map((input) => {\n const prior = priorFor(input.taskClass, input.harness, input.tier);\n const samples = input.samples ?? 0;\n const rawLearned = samples >= MIN_SAMPLE_SIZE ? (input.learned ?? 0) : 0;\n const learned = clampLearned(rawLearned);\n const weights = PROFILE_WEIGHTS[profile];\n // This is catalog-relative ordering, not observed USD. An unknown reported\n // cost still contributes no measured-cost signal; the declared tier order\n // remains available to economy/balanced profiles.\n const costScore = 1 / input.relativeCost;\n const latencyScore = 1 / input.relativeLatency;\n const preferenceBonus =\n (input.preferredTierMatch === true ? 0.12 : 0) + (input.affinityMatch === true ? 0.08 : 0);\n const score = input.eligible\n ? ((prior + learned) * weights.quality +\n costScore * weights.cost +\n latencyScore * weights.latency +\n preferenceBonus) *\n readinessMultiplier(input.readinessState)\n : 0;\n const reasonCodes = [...input.reasonCodes];\n if (input.eligible && samples === 0) reasonCodes.push('COLD_START');\n if (input.eligible && learned > 0) reasonCodes.push('HIGH_HISTORICAL_SUCCESS');\n return {\n harness: input.harness,\n provider: input.provider,\n model: input.model ?? null,\n tier: input.tier,\n relativeCost: input.relativeCost,\n relativeLatency: input.relativeLatency,\n eligible: input.eligible,\n prior,\n learned,\n samples,\n score,\n reasonCodes,\n };\n });\n\n return scored.sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score;\n return a.harness.localeCompare(b.harness);\n });\n}\n\nexport function pickSelected(candidates: readonly Candidate[]): Candidate {\n const eligible = candidates.filter((candidate) => candidate.eligible);\n if (eligible.length === 0) {\n throw new Error(\n `No eligible routing candidate: ${candidates.map((c) => `${c.harness} (${c.reasonCodes.join(',')})`).join('; ')}`,\n );\n }\n return eligible[0] as Candidate;\n}\n","import type { ReadinessSnapshot } from '../agents/availability.js';\nimport type { AgentPhase } from '../agents/types.js';\nimport {\n ANTIGRAVITY_CAPABILITIES,\n CLAUDE_CAPABILITIES,\n CODEX_CAPABILITIES,\n CURSOR_CAPABILITIES,\n} from '../agents/types.js';\nimport { analyzeTask } from './analyze.js';\nimport { filterEligible } from './capabilities.js';\nimport { MODEL_CATALOG_VERSION, modelsFor } from './models.js';\nimport { RECOMMENDED_POLICY_VERSION, recommendedFor } from './policy.js';\nimport { PRIORS_VERSION } from './priors.js';\nimport { pickSelected, scoreCandidates } from './score.js';\nimport type { RoutingDecision, RoutingMode, RoutingProfile, TaskSignals } from './types.js';\n\nconst CAPS = {\n 'claude-code': CLAUDE_CAPABILITIES,\n 'codex-cli': CODEX_CAPABILITIES,\n 'cursor-cli': CURSOR_CAPABILITIES,\n 'antigravity-cli': ANTIGRAVITY_CAPABILITIES,\n} as const;\n\nconst PROVIDER: Record<string, string> = {\n 'claude-code': 'claude',\n 'codex-cli': 'codex',\n 'cursor-cli': 'cursor',\n 'antigravity-cli': 'antigravity',\n};\n\nexport function decideRouting(input: {\n signals?: TaskSignals;\n phase: AgentPhase;\n actualHarness: string;\n actualProvider?: string;\n actualModel?: string | null;\n mode: RoutingMode;\n profile?: RoutingProfile;\n requiresExtraDirectories?: boolean;\n skipScore?: boolean;\n policy?: 'recommended';\n /** Injected inventory — router stays pure (no probes). */\n readiness?: ReadinessSnapshot | null;\n}): RoutingDecision | null {\n if (input.mode === 'off') return null;\n const analyzed = analyzeTask(input.signals ?? {});\n const profile = input.profile ?? 'balanced';\n const policyVersion = `${PRIORS_VERSION}:${MODEL_CATALOG_VERSION}:${input.policy === 'recommended' ? RECOMMENDED_POLICY_VERSION : 'adaptive'}`;\n\n if (input.skipScore) {\n return {\n policyVersion,\n profile,\n taskClass: analyzed.taskClass,\n risk: analyzed.risk,\n mode: input.mode,\n candidates: [],\n selected: {\n harness: input.actualHarness,\n provider: input.actualProvider ?? PROVIDER[input.actualHarness] ?? input.actualHarness,\n model: input.actualModel ?? null,\n },\n actual: {\n harness: input.actualHarness,\n provider: input.actualProvider ?? PROVIDER[input.actualHarness] ?? input.actualHarness,\n model: input.actualModel ?? null,\n },\n reasonCodes: ['EXPLICIT_CONFIG'],\n };\n }\n\n const recommendation = input.policy === 'recommended' ? recommendedFor(input.phase) : null;\n const allowConditional = recommendation?.allowConditional !== false;\n\n const scored = scoreCandidates(\n (Object.keys(CAPS) as (keyof typeof CAPS)[]).flatMap((harness) => {\n const providerId = PROVIDER[harness] as 'claude' | 'codex' | 'cursor' | 'antigravity';\n const readiness =\n input.readiness === undefined || input.readiness === null\n ? null\n : (input.readiness.providers[providerId] ?? null);\n const eligibility = filterEligible({\n harness,\n capabilities: CAPS[harness],\n phase: input.phase,\n requiresExtraDirectories: input.requiresExtraDirectories === true,\n readiness,\n allowConditional,\n });\n return modelsFor(harness, CAPS[harness]).map((model) => {\n const reasonCodes = [...eligibility.reasonCodes];\n const preferredTierMatch =\n recommendation !== null && model.tier === recommendation.preferredTier;\n const affinityMatch = recommendation !== null && recommendation.affinityHarness === harness;\n if (preferredTierMatch) reasonCodes.push('RECOMMENDED_POLICY');\n if (affinityMatch) reasonCodes.push('HARNESS_AFFINITY');\n if (model.tier === 'fast' && analyzed.risk !== 'high') {\n reasonCodes.push('CHEAPER_TIER_SUFFICIENT');\n }\n if (model.tier === 'strong' && (analyzed.risk === 'high' || profile === 'quality')) {\n reasonCodes.push('STRONGER_TIER_FOR_RISK');\n }\n return {\n harness,\n provider: providerId,\n model: model.id,\n tier: model.tier,\n relativeCost: model.relativeCost,\n relativeLatency: model.relativeLatency,\n eligible: eligibility.eligible,\n reasonCodes,\n taskClass: analyzed.taskClass,\n profile,\n readinessState: readiness?.state ?? null,\n preferredTierMatch,\n affinityMatch,\n };\n });\n }),\n );\n const selected = pickSelected(scored);\n return {\n policyVersion,\n profile,\n taskClass: analyzed.taskClass,\n risk: analyzed.risk,\n mode: input.mode,\n candidates: scored,\n selected: {\n harness: selected.harness,\n provider: selected.provider,\n model: selected.model ?? null,\n tier: selected.tier,\n },\n actual: {\n harness: input.actualHarness,\n provider: input.actualProvider ?? PROVIDER[input.actualHarness] ?? input.actualHarness,\n model: input.actualModel ?? null,\n },\n reasonCodes: selected.reasonCodes.includes('COLD_START')\n ? ['HIGH_PRIOR', ...selected.reasonCodes]\n : selected.reasonCodes,\n };\n}\n"],"mappings":";;;;;;;;;AAEO,IAAM,wBAAwB;AAe9B,IAAM,gBAAuD;AAAA,EAClE,eAAe;AAAA,IACb,EAAE,IAAI,SAAS,MAAM,QAAQ,cAAc,GAAG,iBAAiB,EAAE;AAAA,IACjE,EAAE,IAAI,UAAU,MAAM,OAAO,cAAc,KAAK,iBAAiB,IAAI;AAAA,IACrE,EAAE,IAAI,QAAQ,MAAM,UAAU,cAAc,GAAG,iBAAiB,IAAI;AAAA,EACtE;AAAA,EACA,aAAa;AAAA,IACX,EAAE,IAAI,gBAAgB,MAAM,QAAQ,cAAc,GAAG,iBAAiB,EAAE;AAAA,IACxE,EAAE,IAAI,iBAAiB,MAAM,OAAO,cAAc,GAAG,iBAAiB,KAAK;AAAA,IAC3E,EAAE,IAAI,eAAe,MAAM,UAAU,cAAc,GAAG,iBAAiB,IAAI;AAAA,EAC7E;AAAA,EACA,cAAc;AAAA,IACZ,EAAE,IAAI,aAAa,MAAM,QAAQ,cAAc,GAAG,iBAAiB,EAAE;AAAA,IACrE,EAAE,IAAI,QAAQ,MAAM,OAAO,cAAc,GAAG,iBAAiB,IAAI;AAAA,IACjE,EAAE,IAAI,eAAe,MAAM,UAAU,cAAc,GAAG,iBAAiB,IAAI;AAAA,EAC7E;AAAA,EACA,mBAAmB;AAAA,IACjB,EAAE,IAAI,wBAAwB,MAAM,QAAQ,cAAc,GAAG,iBAAiB,EAAE;AAAA,IAChF,EAAE,IAAI,2BAA2B,MAAM,OAAO,cAAc,KAAK,iBAAiB,IAAI;AAAA,IACtF,EAAE,IAAI,uBAAuB,MAAM,UAAU,cAAc,GAAG,iBAAiB,EAAE;AAAA,EACnF;AACF;AAEA,IAAM,eAAsC;AAAA,EAC1C,EAAE,IAAI,MAAM,MAAM,OAAO,cAAc,GAAG,iBAAiB,EAAE;AAC/D;AAEO,SAAS,UAAU,SAAiB,cAAwD;AACjG,MAAI,CAAC,aAAa,eAAgB,QAAO;AACzC,SAAO,cAAc,OAAO,KAAK;AACnC;AAEO,SAAS,aAAa,SAAiB,MAAoC;AAChF,SAAO,cAAc,OAAO,GAAG,KAAK,CAAC,UAAU,MAAM,SAAS,IAAI,KAAK;AACzE;;;AC/CO,IAAM,6BAA6B;AAoBnC,IAAM,qBAAiE;AAAA,EAC5E,SAAS;AAAA,IACP,eAAe;AAAA,IACf,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,UAAU;AAAA,IACR,eAAe;AAAA,IACf,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,KAAK;AAAA,IACH,eAAe;AAAA,IACf,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,MAAM;AAAA,IACJ,eAAe;AAAA,IACf,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,SAAS;AAAA,IACP,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,IACN,eAAe;AAAA,IACf,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,EACnB;AAAA,EACA,IAAI;AAAA,IACF,eAAe;AAAA,IACf,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,aAAa;AAAA,IACX,eAAe;AAAA,IACf,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AACF;AAEA,IAAM,YAA6C;AAAA,EACjD,eAAe;AAAA,EACf,aAAa;AAAA,EACb,cAAc;AAAA,EACd,mBAAmB;AACrB;AAEO,SAAS,eAAe,OAA2C;AACxE,SAAO,mBAAmB,KAAK;AACjC;AAMO,SAAS,kBAAkB,OAA2C;AAC3E,QAAM,QAAQ,eAAe,KAAK;AAClC,QAAM,UAAU,MAAM;AACtB,MAAI,YAAY,OAAW,QAAO;AAClC,QAAM,QAAQ,aAAa,SAAS,MAAM,aAAa;AACvD,QAAM,WAAW,UAAU,OAAO;AAClC,MAAI,UAAU,QAAQ,aAAa,OAAW,QAAO;AACrD,SAAO,EAAE,SAAS,UAAU,OAAO,MAAM,IAAI,MAAM,MAAM,cAAc;AACzE;;;ACxFO,IAAM,iBAAiB;AAQ9B,SAAS,MAAM,KAAwC;AACrD,SAAO,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,GAAG,KAAK,QAAQ,KAAK,IAAI,GAAG,MAAM,IAAI,EAAE;AAC/E;AAEA,SAAS,UAAU,QAA+C;AAChE,SAAO,OAAO,YAAY,OAAO,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,MAAM,GAAG,CAAC,CAAC,CAAC;AACjG;AAEO,IAAM,SAA2C;AAAA,EACtD,QAAQ,UAAU;AAAA,IAChB,eAAe;AAAA,IACf,aAAa;AAAA,IACb,cAAc;AAAA,IACd,mBAAmB;AAAA,EACrB,CAAC;AAAA,EACD,SAAS,UAAU;AAAA,IACjB,eAAe;AAAA,IACf,aAAa;AAAA,IACb,cAAc;AAAA,IACd,mBAAmB;AAAA,EACrB,CAAC;AAAA,EACD,UAAU,UAAU;AAAA,IAClB,eAAe;AAAA,IACf,aAAa;AAAA,IACb,cAAc;AAAA,IACd,mBAAmB;AAAA,EACrB,CAAC;AAAA,EACD,MAAM,UAAU;AAAA,IACd,eAAe;AAAA,IACf,aAAa;AAAA,IACb,cAAc;AAAA,IACd,mBAAmB;AAAA,EACrB,CAAC;AAAA,EACD,MAAM,UAAU;AAAA,IACd,eAAe;AAAA,IACf,aAAa;AAAA,IACb,cAAc;AAAA,IACd,mBAAmB;AAAA,EACrB,CAAC;AAAA,EACD,OAAO,UAAU;AAAA,IACf,eAAe;AAAA,IACf,aAAa;AAAA,IACb,cAAc;AAAA,IACd,mBAAmB;AAAA,EACrB,CAAC;AAAA,EACD,UAAU,UAAU;AAAA,IAClB,eAAe;AAAA,IACf,aAAa;AAAA,IACb,cAAc;AAAA,IACd,mBAAmB;AAAA,EACrB,CAAC;AACH;AAEO,SAAS,SAAS,WAAsB,SAAiB,MAAyB;AACvF,SAAO,OAAO,SAAS,EAAE,OAAO,IAAI,IAAI,KAAK;AAC/C;;;ACjEA,IAAM,aAAa,CAAC,SAAS,WAAW,UAAU,eAAe,eAAe,SAAS;AAQlF,SAAS,YAAY,UAAuB,CAAC,GAAiB;AACnE,QAAM,MAAM;AAAA,IACV,QAAQ,SAAS;AAAA,IACjB,QAAQ,QAAQ;AAAA,IAChB,GAAI,QAAQ,UAAU,CAAC;AAAA,IACvB,GAAI,QAAQ,SAAS,CAAC;AAAA,EACxB,EACG,KAAK,GAAG,EACR,YAAY;AAEf,QAAM,YAAY,SAAS,KAAK,OAAO;AACvC,QAAM,OAAO,WAAW;AAAA,IAAK,CAAC,aAC3B,QAAQ,SAAS,CAAC,GAAG,KAAK,CAAC,SAAS,QAAQ,KAAK,IAAI,CAAC;AAAA,EACzD,IACI,SACA,uBAAuB,KAAK,GAAG,IAC7B,SACA,sBAAsB,KAAK,GAAG,IAC5B,WACA;AAER,SAAO,EAAE,WAAW,KAAK;AAC3B;AAEA,SAAS,SAAS,KAAa,SAAiC;AAC9D,MAAI,6BAA6B,KAAK,GAAG,EAAG,QAAO;AACnD,MAAI,uBAAuB,KAAK,GAAG,EAAG,QAAO;AAC7C,MAAI,sCAAsC,KAAK,GAAG,EAAG,QAAO;AAC5D,MAAI,mCAAmC,KAAK,GAAG,EAAG,QAAO;AACzD,MAAI,6BAA6B,KAAK,GAAG,EAAG,QAAO;AACnD,MAAI,6BAA6B,KAAK,GAAG,EAAG,QAAO;AACnD,MAAI,gCAAgC,KAAK,GAAG,EAAG,QAAO;AACtD,OAAK,QAAQ,UAAU,CAAC,GAAG,KAAK,CAAC,UAAU,OAAO,KAAK,KAAK,CAAC,EAAG,QAAO;AACvE,OAAK,QAAQ,UAAU,CAAC,GAAG,KAAK,CAAC,UAAU,uBAAuB,KAAK,KAAK,CAAC,EAAG,QAAO;AACvF,SAAO;AACT;;;ACtBO,SAAS,eAAe,OAAsC;AACnE,QAAM,UAAoB,CAAC;AAC3B,MAAI,WAAW;AAEf,MAAI,MAAM,4BAA4B,MAAM,aAAa,qBAAqB,QAAQ;AACpF,YAAQ,KAAK,qCAAqC;AAClD,YAAQ,KAAK,qBAAqB;AAClC,eAAW;AAAA,EACb;AAEA,QAAM,YAAY,MAAM;AACxB,MAAI,cAAc,UAAa,cAAc,MAAM;AACjD,QAAI,CAAC,UAAU,WAAW;AACxB,cAAQ,KAAK,wBAAwB;AACrC,iBAAW;AAAA,IACb,WACE,UAAU,kBAAkB,QAC5B,KAAK,MAAM,UAAU,aAAa,IAAI,KAAK,MAAM,UAAU,UAAU,GACrE;AACA,cAAQ,KAAK,mBAAmB;AAChC,iBAAW;AAAA,IACb,WAAW,UAAU,mBAAmB,UAAU;AAChD,cAAQ,KAAK,uBAAuB;AACpC,cAAQ,KAAK,sBAAsB;AACnC,iBAAW;AAAA,IACb,WAAW,UAAU,UAAU,eAAe;AAC5C,cAAQ,KAAK,sBAAsB;AACnC,iBAAW;AAAA,IACb,WAAW,UAAU,mBAAmB,gBAAgB,UAAU,UAAU,eAAe;AACzF,cAAQ,KAAK,2BAA2B;AACxC,cAAQ,KAAK,uBAAuB;AACpC,UAAI,MAAM,qBAAqB,OAAO;AACpC,gBAAQ,KAAK,sBAAsB;AACnC,mBAAW;AAAA,MACb;AAAA,IACF,OAAO;AACL,cAAQ,KAAK,qBAAqB;AAAA,IACpC;AAEA,UAAM,cAAc,UAAU,OAAO,CAAC,GAAG;AACzC,QAAI,gBAAgB,UAAU;AAC5B,cAAQ,KAAK,sBAAsB;AACnC,iBAAW;AAAA,IACb,WAAW,gBAAgB,cAAc;AACvC,cAAQ,KAAK,yBAAyB;AAAA,IACxC;AAAA,EACF;AAEA,SAAO,EAAE,UAAU,aAAa,QAAQ;AAC1C;;;ACpEO,IAAM,QAAQ;AACd,IAAM,kBAAkB;AAExB,IAAM,kBAGT;AAAA,EACF,SAAS,EAAE,SAAS,KAAK,MAAM,KAAK,SAAS,IAAI;AAAA,EACjD,UAAU,EAAE,SAAS,KAAK,MAAM,MAAM,SAAS,KAAK;AAAA,EACpD,SAAS,EAAE,SAAS,MAAM,MAAM,MAAM,SAAS,IAAI;AAAA,EACnD,OAAO,EAAE,SAAS,MAAM,MAAM,KAAK,SAAS,KAAK;AACnD;AAsBO,SAAS,aAAa,OAAuB;AAClD,SAAO,KAAK,IAAI,CAAC,OAAO,KAAK,IAAI,OAAO,KAAK,CAAC;AAChD;AAEA,SAAS,oBAAoB,OAA6C;AACxE,MAAI,UAAU,QAAS,QAAO;AAC9B,MAAI,UAAU,cAAe,QAAO;AACpC,MAAI,UAAU,cAAe,QAAO;AACpC,SAAO;AACT;AAEO,SAAS,gBAAgB,QAA4C;AAC1E,QAAM,UAAU,OAAO,CAAC,GAAG,WAAW;AACtC,QAAM,SAAsB,OAAO,IAAI,CAAC,UAAU;AAChD,UAAM,QAAQ,SAAS,MAAM,WAAW,MAAM,SAAS,MAAM,IAAI;AACjE,UAAM,UAAU,MAAM,WAAW;AACjC,UAAM,aAAa,WAAW,kBAAmB,MAAM,WAAW,IAAK;AACvE,UAAM,UAAU,aAAa,UAAU;AACvC,UAAM,UAAU,gBAAgB,OAAO;AAIvC,UAAM,YAAY,IAAI,MAAM;AAC5B,UAAM,eAAe,IAAI,MAAM;AAC/B,UAAM,mBACH,MAAM,uBAAuB,OAAO,OAAO,MAAM,MAAM,kBAAkB,OAAO,OAAO;AAC1F,UAAM,QAAQ,MAAM,aACd,QAAQ,WAAW,QAAQ,UAC3B,YAAY,QAAQ,OACpB,eAAe,QAAQ,UACvB,mBACF,oBAAoB,MAAM,cAAc,IACxC;AACJ,UAAM,cAAc,CAAC,GAAG,MAAM,WAAW;AACzC,QAAI,MAAM,YAAY,YAAY,EAAG,aAAY,KAAK,YAAY;AAClE,QAAI,MAAM,YAAY,UAAU,EAAG,aAAY,KAAK,yBAAyB;AAC7E,WAAO;AAAA,MACL,SAAS,MAAM;AAAA,MACf,UAAU,MAAM;AAAA,MAChB,OAAO,MAAM,SAAS;AAAA,MACtB,MAAM,MAAM;AAAA,MACZ,cAAc,MAAM;AAAA,MACpB,iBAAiB,MAAM;AAAA,MACvB,UAAU,MAAM;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,OAAO,KAAK,CAAC,GAAG,MAAM;AAC3B,QAAI,EAAE,UAAU,EAAE,MAAO,QAAO,EAAE,QAAQ,EAAE;AAC5C,WAAO,EAAE,QAAQ,cAAc,EAAE,OAAO;AAAA,EAC1C,CAAC;AACH;AAEO,SAAS,aAAa,YAA6C;AACxE,QAAM,WAAW,WAAW,OAAO,CAAC,cAAc,UAAU,QAAQ;AACpE,MAAI,SAAS,WAAW,GAAG;AACzB,UAAM,IAAI;AAAA,MACR,kCAAkC,WAAW,IAAI,CAAC,MAAM,GAAG,EAAE,OAAO,KAAK,EAAE,YAAY,KAAK,GAAG,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA,IACjH;AAAA,EACF;AACA,SAAO,SAAS,CAAC;AACnB;;;ACvFA,IAAM,OAAO;AAAA,EACX,eAAe;AAAA,EACf,aAAa;AAAA,EACb,cAAc;AAAA,EACd,mBAAmB;AACrB;AAEA,IAAM,WAAmC;AAAA,EACvC,eAAe;AAAA,EACf,aAAa;AAAA,EACb,cAAc;AAAA,EACd,mBAAmB;AACrB;AAEO,SAAS,cAAc,OAaH;AACzB,MAAI,MAAM,SAAS,MAAO,QAAO;AACjC,QAAM,WAAW,YAAY,MAAM,WAAW,CAAC,CAAC;AAChD,QAAM,UAAU,MAAM,WAAW;AACjC,QAAM,gBAAgB,GAAG,cAAc,IAAI,qBAAqB,IAAI,MAAM,WAAW,gBAAgB,6BAA6B,UAAU;AAE5I,MAAI,MAAM,WAAW;AACnB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,WAAW,SAAS;AAAA,MACpB,MAAM,SAAS;AAAA,MACf,MAAM,MAAM;AAAA,MACZ,YAAY,CAAC;AAAA,MACb,UAAU;AAAA,QACR,SAAS,MAAM;AAAA,QACf,UAAU,MAAM,kBAAkB,SAAS,MAAM,aAAa,KAAK,MAAM;AAAA,QACzE,OAAO,MAAM,eAAe;AAAA,MAC9B;AAAA,MACA,QAAQ;AAAA,QACN,SAAS,MAAM;AAAA,QACf,UAAU,MAAM,kBAAkB,SAAS,MAAM,aAAa,KAAK,MAAM;AAAA,QACzE,OAAO,MAAM,eAAe;AAAA,MAC9B;AAAA,MACA,aAAa,CAAC,iBAAiB;AAAA,IACjC;AAAA,EACF;AAEA,QAAM,iBAAiB,MAAM,WAAW,gBAAgB,eAAe,MAAM,KAAK,IAAI;AACtF,QAAM,mBAAmB,gBAAgB,qBAAqB;AAE9D,QAAM,SAAS;AAAA,IACZ,OAAO,KAAK,IAAI,EAA4B,QAAQ,CAAC,YAAY;AAChE,YAAM,aAAa,SAAS,OAAO;AACnC,YAAM,YACJ,MAAM,cAAc,UAAa,MAAM,cAAc,OACjD,OACC,MAAM,UAAU,UAAU,UAAU,KAAK;AAChD,YAAM,cAAc,eAAe;AAAA,QACjC;AAAA,QACA,cAAc,KAAK,OAAO;AAAA,QAC1B,OAAO,MAAM;AAAA,QACb,0BAA0B,MAAM,6BAA6B;AAAA,QAC7D;AAAA,QACA;AAAA,MACF,CAAC;AACD,aAAO,UAAU,SAAS,KAAK,OAAO,CAAC,EAAE,IAAI,CAAC,UAAU;AACtD,cAAM,cAAc,CAAC,GAAG,YAAY,WAAW;AAC/C,cAAM,qBACJ,mBAAmB,QAAQ,MAAM,SAAS,eAAe;AAC3D,cAAM,gBAAgB,mBAAmB,QAAQ,eAAe,oBAAoB;AACpF,YAAI,mBAAoB,aAAY,KAAK,oBAAoB;AAC7D,YAAI,cAAe,aAAY,KAAK,kBAAkB;AACtD,YAAI,MAAM,SAAS,UAAU,SAAS,SAAS,QAAQ;AACrD,sBAAY,KAAK,yBAAyB;AAAA,QAC5C;AACA,YAAI,MAAM,SAAS,aAAa,SAAS,SAAS,UAAU,YAAY,YAAY;AAClF,sBAAY,KAAK,wBAAwB;AAAA,QAC3C;AACA,eAAO;AAAA,UACL;AAAA,UACA,UAAU;AAAA,UACV,OAAO,MAAM;AAAA,UACb,MAAM,MAAM;AAAA,UACZ,cAAc,MAAM;AAAA,UACpB,iBAAiB,MAAM;AAAA,UACvB,UAAU,YAAY;AAAA,UACtB;AAAA,UACA,WAAW,SAAS;AAAA,UACpB;AAAA,UACA,gBAAgB,WAAW,SAAS;AAAA,UACpC;AAAA,UACA;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACA,QAAM,WAAW,aAAa,MAAM;AACpC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,WAAW,SAAS;AAAA,IACpB,MAAM,SAAS;AAAA,IACf,MAAM,MAAM;AAAA,IACZ,YAAY;AAAA,IACZ,UAAU;AAAA,MACR,SAAS,SAAS;AAAA,MAClB,UAAU,SAAS;AAAA,MACnB,OAAO,SAAS,SAAS;AAAA,MACzB,MAAM,SAAS;AAAA,IACjB;AAAA,IACA,QAAQ;AAAA,MACN,SAAS,MAAM;AAAA,MACf,UAAU,MAAM,kBAAkB,SAAS,MAAM,aAAa,KAAK,MAAM;AAAA,MACzE,OAAO,MAAM,eAAe;AAAA,IAC9B;AAAA,IACA,aAAa,SAAS,YAAY,SAAS,YAAY,IACnD,CAAC,cAAc,GAAG,SAAS,WAAW,IACtC,SAAS;AAAA,EACf;AACF;","names":[]}
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
eligibleStories
|
|
4
|
+
} from "./chunk-NP47HRX2.js";
|
|
5
|
+
import {
|
|
6
|
+
isProcessAlive
|
|
7
|
+
} from "./chunk-IMB6FA2F.js";
|
|
8
|
+
import {
|
|
9
|
+
getPlanRepository,
|
|
10
|
+
loadStoredPlan,
|
|
11
|
+
saveStoredPlan
|
|
12
|
+
} from "./chunk-7MJGYPHF.js";
|
|
13
|
+
import {
|
|
4
14
|
taskPlanSchema
|
|
5
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-XNK5UN2D.js";
|
|
6
16
|
import {
|
|
7
17
|
writeFileAtomic
|
|
8
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-PFWHHYEO.js";
|
|
9
19
|
|
|
10
20
|
// src/core/state-manager.ts
|
|
11
|
-
import { readFile } from "fs/promises";
|
|
21
|
+
import { readFile } from "node:fs/promises";
|
|
12
22
|
import { ZodError } from "zod";
|
|
13
23
|
|
|
14
24
|
// src/telemetry/reconcile.ts
|
|
15
|
-
import { hostname } from "os";
|
|
25
|
+
import { hostname } from "node:os";
|
|
16
26
|
function nowIso() {
|
|
17
27
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
18
28
|
}
|
|
@@ -137,15 +147,77 @@ function hasUsageData(usage) {
|
|
|
137
147
|
return USAGE_FIELDS.some((field) => usage[field] !== void 0);
|
|
138
148
|
}
|
|
139
149
|
|
|
150
|
+
// src/core/workflow-contract.ts
|
|
151
|
+
var WORKFLOW_PHASES = [
|
|
152
|
+
{ phase: "prd", field: "prdCompleted", requires: "Available demand", produces: "PRD exists" },
|
|
153
|
+
{
|
|
154
|
+
phase: "plan",
|
|
155
|
+
field: "jsonCompleted",
|
|
156
|
+
requires: "PRD exists",
|
|
157
|
+
produces: "Valid task plan and dependency graph"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
phase: "execute",
|
|
161
|
+
field: "executionCompleted",
|
|
162
|
+
requires: "Valid plan; eligible stories or pending findings",
|
|
163
|
+
produces: "Verified stories; no unresolved findings"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
phase: "review",
|
|
167
|
+
field: "reviewCompleted",
|
|
168
|
+
requires: "Implementation available",
|
|
169
|
+
produces: "Valid PASS with acceptance evidence"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
phase: "pr",
|
|
173
|
+
field: "prCreated",
|
|
174
|
+
requires: "Review passed; PR delivery authorized",
|
|
175
|
+
produces: "Confirmed pull request"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
phase: "pr-review",
|
|
179
|
+
field: "prReviewCompleted",
|
|
180
|
+
requires: "Confirmed PR; review requested",
|
|
181
|
+
produces: "Valid approving review"
|
|
182
|
+
}
|
|
183
|
+
];
|
|
184
|
+
var DEFAULT_MAX_CORRECTION_CYCLES = 3;
|
|
185
|
+
var DEFAULT_PIPELINE_STATE = {
|
|
186
|
+
prdCompleted: false,
|
|
187
|
+
jsonCompleted: false,
|
|
188
|
+
executionCompleted: false,
|
|
189
|
+
reviewCompleted: false,
|
|
190
|
+
prCreated: false
|
|
191
|
+
};
|
|
192
|
+
function executionCompletion(plan, inPipeline) {
|
|
193
|
+
return {
|
|
194
|
+
...plan,
|
|
195
|
+
...inPipeline ? { issueStatus: "in_progress", completedAt: null } : {},
|
|
196
|
+
pipeline: { ...plan.pipeline, executionCompleted: true }
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
140
200
|
// src/core/state-manager.ts
|
|
141
201
|
function isoNow() {
|
|
142
202
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
143
203
|
}
|
|
144
204
|
async function loadTaskPlan(path) {
|
|
145
|
-
const
|
|
146
|
-
const raw = JSON.parse(content);
|
|
205
|
+
const repository = getPlanRepository(path);
|
|
147
206
|
try {
|
|
148
|
-
|
|
207
|
+
let parsed;
|
|
208
|
+
if (repository === void 0) {
|
|
209
|
+
parsed = taskPlanSchema.parse(JSON.parse(await readFile(path, "utf-8")));
|
|
210
|
+
} else {
|
|
211
|
+
try {
|
|
212
|
+
parsed = await loadStoredPlan(repository);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
if (!(error instanceof Error) || !error.message.startsWith("No SQLite task plan exists")) {
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
parsed = taskPlanSchema.parse(JSON.parse(await readFile(path, "utf-8")));
|
|
218
|
+
await saveStoredPlan(repository, parsed);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
149
221
|
const reconciled = reconcileInterruptedExecutions(parsed);
|
|
150
222
|
if (reconciled !== parsed) {
|
|
151
223
|
try {
|
|
@@ -164,17 +236,16 @@ ${issues}`);
|
|
|
164
236
|
}
|
|
165
237
|
}
|
|
166
238
|
async function saveTaskPlan(path, plan) {
|
|
239
|
+
const repository = getPlanRepository(path);
|
|
240
|
+
if (repository !== void 0) {
|
|
241
|
+
await saveStoredPlan(repository, plan);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
167
244
|
await writeFileAtomic(path, `${JSON.stringify(plan, null, 2)}
|
|
168
245
|
`);
|
|
169
246
|
}
|
|
170
247
|
function initializeState(plan) {
|
|
171
|
-
const defaultPipeline =
|
|
172
|
-
prdCompleted: false,
|
|
173
|
-
jsonCompleted: false,
|
|
174
|
-
executionCompleted: false,
|
|
175
|
-
reviewCompleted: false,
|
|
176
|
-
prCreated: false
|
|
177
|
-
};
|
|
248
|
+
const defaultPipeline = DEFAULT_PIPELINE_STATE;
|
|
178
249
|
return {
|
|
179
250
|
...plan,
|
|
180
251
|
issueStatus: plan.issueStatus ?? "pending",
|
|
@@ -182,7 +253,7 @@ function initializeState(plan) {
|
|
|
182
253
|
lastAttemptAt: plan.lastAttemptAt ?? null,
|
|
183
254
|
lastError: plan.lastError ?? null,
|
|
184
255
|
correctionCycle: plan.correctionCycle ?? 0,
|
|
185
|
-
maxCorrectionCycles: plan.maxCorrectionCycles ??
|
|
256
|
+
maxCorrectionCycles: plan.maxCorrectionCycles ?? DEFAULT_MAX_CORRECTION_CYCLES,
|
|
186
257
|
lastReviewFindings: plan.lastReviewFindings ?? null,
|
|
187
258
|
pipeline: plan.pipeline ? { ...defaultPipeline, ...plan.pipeline } : defaultPipeline
|
|
188
259
|
};
|
|
@@ -197,7 +268,7 @@ function allStoriesPass(plan) {
|
|
|
197
268
|
return plan.userStories.every((story) => story.passes === true);
|
|
198
269
|
}
|
|
199
270
|
function selectActiveStory(stories) {
|
|
200
|
-
return
|
|
271
|
+
return eligibleStories(stories)[0];
|
|
201
272
|
}
|
|
202
273
|
function markStoryPassing(plan, storyId) {
|
|
203
274
|
return {
|
|
@@ -280,6 +351,10 @@ export {
|
|
|
280
351
|
divideUsage,
|
|
281
352
|
formatTokens,
|
|
282
353
|
hasUsageData,
|
|
354
|
+
WORKFLOW_PHASES,
|
|
355
|
+
DEFAULT_MAX_CORRECTION_CYCLES,
|
|
356
|
+
DEFAULT_PIPELINE_STATE,
|
|
357
|
+
executionCompletion,
|
|
283
358
|
isoNow,
|
|
284
359
|
loadTaskPlan,
|
|
285
360
|
saveTaskPlan,
|
|
@@ -295,4 +370,4 @@ export {
|
|
|
295
370
|
clearLastError,
|
|
296
371
|
trimErrorMessage
|
|
297
372
|
};
|
|
298
|
-
//# sourceMappingURL=chunk-
|
|
373
|
+
//# sourceMappingURL=chunk-GD7JZMSJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/state-manager.ts","../src/telemetry/reconcile.ts","../src/core/metrics.ts","../src/core/workflow-contract.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { ZodError } from 'zod';\nimport { taskPlanSchema } from '../schemas.js';\nimport { getPlanRepository, loadStoredPlan, saveStoredPlan } from '../storage/db/repository.js';\nimport { reconcileInterruptedExecutions } from '../telemetry/reconcile.js';\nimport type { LastError, PipelineState, TaskPlan, UserStory } from '../types.js';\nimport { writeFileAtomic } from '../utils/fs.js';\nimport { type ClaudeUsage, sumUsage } from './metrics.js';\nimport { eligibleStories } from './task-plan.js';\nimport { DEFAULT_MAX_CORRECTION_CYCLES, DEFAULT_PIPELINE_STATE } from './workflow-contract.js';\n\n/**\n * Get the current ISO timestamp.\n */\nexport function isoNow(): string {\n return new Date().toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\n/**\n * Load and parse a tasks.json file.\n * Validates that it has the expected structure.\n */\nexport async function loadTaskPlan(path: string): Promise<TaskPlan> {\n const repository = getPlanRepository(path);\n\n try {\n let parsed: TaskPlan;\n if (repository === undefined) {\n parsed = taskPlanSchema.parse(JSON.parse(await readFile(path, 'utf-8'))) as TaskPlan;\n } else {\n try {\n parsed = await loadStoredPlan(repository);\n } catch (error) {\n // The plan phase (and compatible test/embedding callers) writes the\n // agent-facing projection after resolution. Until it is promoted there\n // is deliberately no pipeline row, so bootstrap it exactly once from\n // that validated phase output instead of treating SQLite as an empty\n // plan that masks the file.\n if (!(error instanceof Error) || !error.message.startsWith('No SQLite task plan exists')) {\n throw error;\n }\n parsed = taskPlanSchema.parse(JSON.parse(await readFile(path, 'utf-8'))) as TaskPlan;\n await saveStoredPlan(repository, parsed);\n }\n }\n const reconciled = reconcileInterruptedExecutions(parsed);\n if (reconciled !== parsed) {\n try {\n await saveTaskPlan(path, reconciled);\n } catch {\n // Observational: a failed persist of interrupted status still returns\n // the reconciled plan to this reader.\n }\n }\n return reconciled;\n } catch (err) {\n if (err instanceof ZodError) {\n const issues = err.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\\n');\n throw new Error(`Invalid tasks.json at ${path}:\\n${issues}`);\n }\n throw err;\n }\n}\n\n/**\n * Save a TaskPlan to disk atomically (write-to-temp + rename).\n * This prevents corruption if the process is interrupted during write.\n */\nexport async function saveTaskPlan(path: string, plan: TaskPlan): Promise<void> {\n const repository = getPlanRepository(path);\n if (repository !== undefined) {\n await saveStoredPlan(repository, plan);\n return;\n }\n await writeFileAtomic(path, `${JSON.stringify(plan, null, 2)}\\n`);\n}\n\n/**\n * Initialize default state fields on a TaskPlan.\n * Fills in missing fields with defaults, matching the Bash script behavior.\n *\n * The opt-in fields (`pullRequest`, `prReview`, `pipeline.prReviewCompleted`)\n * are deliberately absent from the defaults: they ride along on the spread when\n * present and stay out of the file entirely when the phases never ran.\n */\nexport function initializeState(plan: TaskPlan): TaskPlan {\n const defaultPipeline: PipelineState = DEFAULT_PIPELINE_STATE;\n\n return {\n ...plan,\n issueStatus: plan.issueStatus ?? 'pending',\n completedAt: plan.completedAt ?? null,\n lastAttemptAt: plan.lastAttemptAt ?? null,\n lastError: plan.lastError ?? null,\n correctionCycle: plan.correctionCycle ?? 0,\n maxCorrectionCycles: plan.maxCorrectionCycles ?? DEFAULT_MAX_CORRECTION_CYCLES,\n lastReviewFindings: plan.lastReviewFindings ?? null,\n pipeline: plan.pipeline ? { ...defaultPipeline, ...plan.pipeline } : defaultPipeline,\n };\n}\n\n/**\n * Whether the issue has a review failure that execute must still address,\n * even if every userStories[].passes is already true.\n */\nexport function hasPendingCorrection(plan: TaskPlan): boolean {\n return Boolean(plan.lastReviewFindings);\n}\n\n/**\n * Check if all user stories have passes=true.\n * Returns false if there are no stories.\n */\nexport function allStoriesPass(plan: TaskPlan): boolean {\n if (plan.userStories.length === 0) {\n return false;\n }\n\n return plan.userStories.every((story) => story.passes === true);\n}\n\n/**\n * The story `execute` should work on next: the highest-priority (lowest\n * `priority` number) story with `passes: false` whose dependencies passed. This is the exact same rule\n * `prompts/execute.md` gives the agent (\"work on the CLI-selected eligible story\") — kept here as a single, pure, exported\n * helper so the engine's `iteration:start` publication and the terminal\n * renderer read the same computed identity instead of two independent\n * heuristics.\n *\n * Pure: never mutates `stories`. Returns `undefined` when every story\n * already passes (or the plan has none).\n */\nexport function selectActiveStory(stories: readonly UserStory[]): UserStory | undefined {\n return eligibleStories(stories)[0];\n}\n\n/**\n * Mark a specific story as passing.\n */\nexport function markStoryPassing(plan: TaskPlan, storyId: string): TaskPlan {\n return {\n ...plan,\n userStories: plan.userStories.map((story) =>\n story.id === storyId ? { ...story, passes: true } : story,\n ),\n };\n}\n\n/**\n * Accumulate a usage share (and duration) onto the given stories.\n *\n * Pure, like the rest of this module: returns a new plan and never touches the\n * input. Accumulation is summing and treats `undefined` as \"not reported\": a\n * field neither the story nor the share carries stays absent, so a plan written\n * by a CLI that reports no usage never gains artificial zeros.\n *\n * The share is what `divideUsage()` already split across the stories that\n * completed in the same execute iteration — see core/engine.ts.\n */\nexport function applyStoryMetrics(\n plan: TaskPlan,\n storyIds: string[],\n share: ClaudeUsage,\n durationSeconds?: number,\n): TaskPlan {\n if (storyIds.length === 0) return plan;\n\n const targets = new Set(storyIds);\n\n return {\n ...plan,\n userStories: plan.userStories.map((story) => {\n if (!targets.has(story.id)) return story;\n\n const updated: UserStory = { ...story, ...sumUsage(story, share) };\n if (story.durationSeconds !== undefined || durationSeconds !== undefined) {\n updated.durationSeconds = (story.durationSeconds ?? 0) + (durationSeconds ?? 0);\n }\n return updated;\n }),\n };\n}\n\n/**\n * Mark the issue as in_progress.\n */\nexport function markIssueInProgress(plan: TaskPlan, timestamp?: string): TaskPlan {\n const ts = timestamp ?? isoNow();\n return {\n ...plan,\n issueStatus: 'in_progress',\n completedAt: null,\n lastAttemptAt: ts,\n };\n}\n\n/**\n * Mark the issue as completed.\n */\nexport function markIssueCompleted(plan: TaskPlan): TaskPlan {\n const ts = isoNow();\n return {\n ...plan,\n issueStatus: 'completed',\n completedAt: ts,\n lastAttemptAt: ts,\n lastError: null,\n pipeline: plan.pipeline ? { ...plan.pipeline, executionCompleted: true } : plan.pipeline,\n };\n}\n\n/**\n * Set the lastError field on the task plan.\n */\nexport function setLastError(plan: TaskPlan, category: string, message: string): TaskPlan {\n const ts = isoNow();\n const error: LastError = {\n category,\n message,\n at: ts,\n };\n\n return {\n ...plan,\n lastAttemptAt: ts,\n lastError: error,\n };\n}\n\n/**\n * Clear the lastError field, but only if it was set before the attempt started.\n * This prevents clearing errors that were set during the current attempt.\n */\nexport function clearLastError(plan: TaskPlan, attemptStartedAt: string): TaskPlan {\n const ts = isoNow();\n\n // If the error was set after the attempt started, keep it\n if (plan.lastError && plan.lastError.at > attemptStartedAt) {\n return {\n ...plan,\n lastAttemptAt: ts,\n };\n }\n\n return {\n ...plan,\n lastAttemptAt: ts,\n lastError: null,\n };\n}\n\n/**\n * Trim an error message to at most 8 non-empty lines.\n */\nexport function trimErrorMessage(message: string): string {\n return message\n .split('\\n')\n .filter((line) => line.trim().length > 0)\n .slice(0, 8)\n .join('\\n');\n}\n","import { hostname } from 'node:os';\nimport { isProcessAlive } from '../storage/lock.js';\nimport type { TaskPlan } from '../types.js';\n\nfunction nowIso(): string {\n return new Date().toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\n/**\n * Convert orphan `running` records to `interrupted` using the two-signal\n * rule from `storage/lock.ts`: a pid on this host that is gone.\n *\n * A record owned by another host is left `running` — we cannot see that pid.\n */\nexport function reconcileInterruptedExecutions(plan: TaskPlan): TaskPlan {\n if (plan.executions === undefined || plan.executions.length === 0) return plan;\n let changed = false;\n const executions = plan.executions.map((record) => {\n if (record.status !== 'running' || record.finishedAt !== null) return record;\n const owner = record.owner;\n if (owner === undefined || owner === null) {\n changed = true;\n return { ...record, status: 'interrupted' as const, finishedAt: nowIso(), owner: null };\n }\n if (owner.host !== hostname()) return record;\n if (isProcessAlive(owner.pid)) return record;\n changed = true;\n return { ...record, status: 'interrupted' as const, finishedAt: nowIso(), owner: null };\n });\n return changed ? { ...plan, executions } : plan;\n}\n","/**\n * Single source of truth for translating the `claude` CLI JSON output into\n * token/cost metrics.\n *\n * Three call sites consume this (headless json, headless stream-json and the\n * execute-phase executor); keeping the parsing here is what stops them from\n * diverging again the next time the CLI payload changes shape.\n */\n\nexport interface ClaudeUsage {\n inputTokens?: number;\n outputTokens?: number;\n cacheReadTokens?: number;\n cacheCreationTokens?: number;\n costUsd?: number;\n /** Envelope `duration_ms` — request onward, not process startup. */\n cliDurationMs?: number;\n /** Envelope `duration_api_ms` when the harness reports it. */\n apiDurationMs?: number;\n /** Time to first output, when the harness reports it. */\n ttftMs?: number;\n numTurns?: number;\n}\n\n/** Field names of {@link ClaudeUsage}, used by the summing helper. */\nconst USAGE_FIELDS = [\n 'inputTokens',\n 'outputTokens',\n 'cacheReadTokens',\n 'cacheCreationTokens',\n 'costUsd',\n] as const;\n\n/**\n * Coerce a value to a finite number, or undefined when it is absent/unusable.\n * Never throws — malformed payloads simply yield undefined.\n */\nfunction num(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Parse a `claude` CLI result payload into a {@link ClaudeUsage}.\n *\n * Reads the current format (`total_cost_usd` plus a nested `usage` object) and\n * falls back to the legacy flat keys (`cost_usd`, `num_input_tokens`,\n * `num_output_tokens`) when the newer ones are absent.\n *\n * Returns null when the payload carries no recognizable field at all, and a\n * partial object when only some fields are present. Never throws.\n */\n/**\n * Parse a Codex `turn.completed.usage` object.\n *\n * Codex reports `input_tokens` including the cache (Decision 6 of #62), so\n * `inputTokens` is `input_tokens − cached_input_tokens`, clamped at 0. Codex\n * never reports USD; `costUsd` stays absent — \"not reported\", never zero.\n */\nexport function parseCodexUsage(payload: unknown): ClaudeUsage | null {\n if (!isRecord(payload)) return null;\n\n const usage: ClaudeUsage = {};\n const inputTokens = num(payload.input_tokens);\n const cachedInput = num(payload.cached_input_tokens);\n const cacheWrite = num(payload.cache_write_input_tokens);\n const outputTokens = num(payload.output_tokens);\n\n if (inputTokens !== undefined) {\n usage.inputTokens =\n cachedInput !== undefined ? Math.max(0, inputTokens - cachedInput) : inputTokens;\n }\n if (outputTokens !== undefined) usage.outputTokens = outputTokens;\n if (cachedInput !== undefined) usage.cacheReadTokens = cachedInput;\n if (cacheWrite !== undefined) usage.cacheCreationTokens = cacheWrite;\n\n return Object.keys(usage).length > 0 ? usage : null;\n}\n\nexport function parseUsage(payload: unknown): ClaudeUsage | null {\n if (!isRecord(payload)) return null;\n\n const usageRaw = isRecord(payload.usage) ? payload.usage : undefined;\n\n const usage: ClaudeUsage = {};\n\n const inputTokens = usageRaw ? num(usageRaw.input_tokens) : undefined;\n const outputTokens = usageRaw ? num(usageRaw.output_tokens) : undefined;\n const cacheReadTokens = usageRaw ? num(usageRaw.cache_read_input_tokens) : undefined;\n const cacheCreationTokens = usageRaw ? num(usageRaw.cache_creation_input_tokens) : undefined;\n const costUsd = num(payload.total_cost_usd);\n\n // Legacy fallbacks — only consulted when the modern key is missing.\n const legacyInput = num(payload.num_input_tokens);\n const legacyOutput = num(payload.num_output_tokens);\n const legacyCost = num(payload.cost_usd);\n\n const resolvedInput = inputTokens ?? legacyInput;\n const resolvedOutput = outputTokens ?? legacyOutput;\n const resolvedCost = costUsd ?? legacyCost;\n\n if (resolvedInput !== undefined) usage.inputTokens = resolvedInput;\n if (resolvedOutput !== undefined) usage.outputTokens = resolvedOutput;\n if (cacheReadTokens !== undefined) usage.cacheReadTokens = cacheReadTokens;\n if (cacheCreationTokens !== undefined) usage.cacheCreationTokens = cacheCreationTokens;\n if (resolvedCost !== undefined) usage.costUsd = resolvedCost;\n\n const cliDurationMs = num(payload.duration_ms);\n const apiDurationMs = num(payload.duration_api_ms);\n const ttftMs = num(payload.ttft_ms) ?? num(payload.time_to_first_token_ms);\n const numTurns = num(payload.num_turns);\n if (cliDurationMs !== undefined) usage.cliDurationMs = cliDurationMs;\n if (apiDurationMs !== undefined) usage.apiDurationMs = apiDurationMs;\n if (ttftMs !== undefined) usage.ttftMs = ttftMs;\n if (numTurns !== undefined) usage.numTurns = numTurns;\n\n return Object.keys(usage).length > 0 ? usage : null;\n}\n\n/**\n * Sum two usages field by field.\n *\n * `undefined` means \"not reported\", not zero: a field absent on both sides\n * stays absent in the result, while a field present on a single side is\n * carried over untouched.\n */\nexport function sumUsage(\n a: ClaudeUsage | null | undefined,\n b: ClaudeUsage | null | undefined,\n): ClaudeUsage {\n const result: ClaudeUsage = {};\n\n for (const field of USAGE_FIELDS) {\n const left = a?.[field];\n const right = b?.[field];\n if (left === undefined && right === undefined) continue;\n result[field] = (left ?? 0) + (right ?? 0);\n }\n\n return result;\n}\n\n/**\n * Split a usage evenly across `parts`.\n *\n * This is the rateio used when several stories complete in the same execute\n * iteration: the iteration's tokens cannot be attributed to a single story, so\n * each one receives an equal share. Token counts are rounded to integers;\n * the cost keeps full precision.\n *\n * `parts <= 1` (or a non-finite value) returns the usage unchanged. Fields the\n * CLI never reported stay absent — dividing \"not reported\" still yields\n * \"not reported\", never zero.\n */\nexport function divideUsage(usage: ClaudeUsage | null | undefined, parts: number): ClaudeUsage {\n const result: ClaudeUsage = {};\n if (!usage) return result;\n\n const divisor = Number.isFinite(parts) && parts > 1 ? parts : 1;\n\n for (const field of USAGE_FIELDS) {\n const value = usage[field];\n if (value === undefined) continue;\n result[field] = field === 'costUsd' ? value / divisor : Math.round(value / divisor);\n }\n\n return result;\n}\n\n/**\n * Compact token count: `1523` → `1.5k`, `2_400_000` → `2.4M`.\n */\nfunction compactTokens(value: number): string {\n const abs = Math.abs(value);\n if (abs >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;\n if (abs >= 1_000) return `${(value / 1_000).toFixed(1)}k`;\n return String(Math.round(value));\n}\n\n/**\n * Human-readable one-liner, e.g. `2 in / 4 out · 15.5k cache · ~$0.1607`.\n *\n * Segments whose data is missing are omitted entirely — callers can rely on an\n * empty string meaning \"no metrics at all\" and skip the line altogether rather\n * than printing zeros or NaN.\n */\nexport function formatTokens(usage: ClaudeUsage | null | undefined): string {\n if (!usage) return '';\n\n const segments: string[] = [];\n\n const io: string[] = [];\n if (usage.inputTokens !== undefined) io.push(`${compactTokens(usage.inputTokens)} in`);\n if (usage.outputTokens !== undefined) io.push(`${compactTokens(usage.outputTokens)} out`);\n if (io.length > 0) segments.push(io.join(' / '));\n\n const cache =\n usage.cacheReadTokens !== undefined || usage.cacheCreationTokens !== undefined\n ? (usage.cacheReadTokens ?? 0) + (usage.cacheCreationTokens ?? 0)\n : undefined;\n if (cache !== undefined) segments.push(`${compactTokens(cache)} cache`);\n\n if (usage.costUsd !== undefined) segments.push(`~$${usage.costUsd.toFixed(4)}`);\n\n return segments.join(' · ');\n}\n\n/**\n * True when the usage carries at least one reported field. Handy for deciding\n * whether a metrics line or event is worth emitting at all.\n */\nexport function hasUsageData(usage: ClaudeUsage | null | undefined): boolean {\n if (!usage) return false;\n return USAGE_FIELDS.some((field) => usage[field] !== undefined);\n}\n","import type { PipelineState } from '../types.js';\n\n/** Methodology shared at build time; sessions and delivery choices remain consumer-owned. */\nexport const WORKFLOW_PHASES = [\n { phase: 'prd', field: 'prdCompleted', requires: 'Available demand', produces: 'PRD exists' },\n {\n phase: 'plan',\n field: 'jsonCompleted',\n requires: 'PRD exists',\n produces: 'Valid task plan and dependency graph',\n },\n {\n phase: 'execute',\n field: 'executionCompleted',\n requires: 'Valid plan; eligible stories or pending findings',\n produces: 'Verified stories; no unresolved findings',\n },\n {\n phase: 'review',\n field: 'reviewCompleted',\n requires: 'Implementation available',\n produces: 'Valid PASS with acceptance evidence',\n },\n {\n phase: 'pr',\n field: 'prCreated',\n requires: 'Review passed; PR delivery authorized',\n produces: 'Confirmed pull request',\n },\n {\n phase: 'pr-review',\n field: 'prReviewCompleted',\n requires: 'Confirmed PR; review requested',\n produces: 'Valid approving review',\n },\n] as const satisfies readonly {\n phase: string;\n field: keyof PipelineState;\n requires: string;\n produces: string;\n}[];\n\nexport const DEFAULT_MAX_CORRECTION_CYCLES = 3;\nexport const DEFAULT_PIPELINE_STATE = {\n prdCompleted: false,\n jsonCompleted: false,\n executionCompleted: false,\n reviewCompleted: false,\n prCreated: false,\n} satisfies PipelineState;\n\nexport function executionCompletion<\n T extends { issueStatus: string; completedAt: string | null; pipeline: PipelineState },\n>(plan: T, inPipeline: boolean): T {\n return {\n ...plan,\n ...(inPipeline ? { issueStatus: 'in_progress', completedAt: null } : {}),\n pipeline: { ...plan.pipeline, executionCompleted: true },\n };\n}\n"],"mappings":"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","names":[]}
|