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.
Files changed (239) hide show
  1. package/README.md +34 -100
  2. package/dist/agent-EWKA7357.js +32 -0
  3. package/dist/{analyze-6M3KEOX7.js → analyze-Z7ODC27F.js} +42 -41
  4. package/dist/analyze-Z7ODC27F.js.map +1 -0
  5. package/dist/{apply-ADB3ZK7W.js → apply-VNSWQS6O.js} +22 -24
  6. package/dist/apply-VNSWQS6O.js.map +1 -0
  7. package/dist/artifacts-AMJQJFFV.js +82 -0
  8. package/dist/artifacts-AMJQJFFV.js.map +1 -0
  9. package/dist/{bench-FM7I7EIV.js → bench-ZJEIACUK.js} +42 -42
  10. package/dist/{chunk-RYOUZ6EK.js → chunk-23VHVVKW.js} +3 -3
  11. package/dist/chunk-2EYLOS5I.js +99 -0
  12. package/dist/chunk-2EYLOS5I.js.map +1 -0
  13. package/dist/{chunk-ANI2NTR4.js → chunk-2EZW4X57.js} +28 -25
  14. package/dist/chunk-2EZW4X57.js.map +1 -0
  15. package/dist/{chunk-E6KXCV3T.js → chunk-2Q6PG3MT.js} +15 -15
  16. package/dist/chunk-2Q6PG3MT.js.map +1 -0
  17. package/dist/{chunk-C5TL7T4V.js → chunk-2YM2VH6R.js} +2 -2
  18. package/dist/{chunk-LPWPFIIB.js → chunk-2YYP7AE6.js} +14 -16
  19. package/dist/{chunk-LPWPFIIB.js.map → chunk-2YYP7AE6.js.map} +1 -1
  20. package/dist/chunk-3JD25HHJ.js +278 -0
  21. package/dist/chunk-3JD25HHJ.js.map +1 -0
  22. package/dist/{chunk-ISZ54SVE.js → chunk-46S7APLQ.js} +41 -17
  23. package/dist/chunk-46S7APLQ.js.map +1 -0
  24. package/dist/chunk-6AOZCWN2.js +21 -0
  25. package/dist/chunk-6AOZCWN2.js.map +1 -0
  26. package/dist/{chunk-QPGEFLMY.js → chunk-7CMVL4X6.js} +7 -7
  27. package/dist/chunk-7MJGYPHF.js +1571 -0
  28. package/dist/chunk-7MJGYPHF.js.map +1 -0
  29. package/dist/chunk-7ZZUSMBJ.js +35 -0
  30. package/dist/chunk-7ZZUSMBJ.js.map +1 -0
  31. package/dist/{chunk-DUS5ZNKL.js → chunk-DIMDAYZS.js} +7 -6
  32. package/dist/{chunk-DGRTJD5R.js → chunk-DXH4TQRB.js} +7 -7
  33. package/dist/{chunk-CRGIQFUQ.js → chunk-E2H3JV2E.js} +3 -1
  34. package/dist/chunk-E2H3JV2E.js.map +1 -0
  35. package/dist/{chunk-YUNBOUQH.js → chunk-ERAG5IQB.js} +8 -8
  36. package/dist/{chunk-D6ZBGJ3L.js → chunk-F2NFJ27Z.js} +130 -36
  37. package/dist/chunk-F2NFJ27Z.js.map +1 -0
  38. package/dist/{chunk-SOM4QA6C.js → chunk-GD7JZMSJ.js} +93 -18
  39. package/dist/chunk-GD7JZMSJ.js.map +1 -0
  40. package/dist/{chunk-YII4U7ZD.js → chunk-HEHHQU4G.js} +70 -81
  41. package/dist/chunk-HEHHQU4G.js.map +1 -0
  42. package/dist/{chunk-KZ5EY5XX.js → chunk-HHXWAWU6.js} +159 -114
  43. package/dist/chunk-HHXWAWU6.js.map +1 -0
  44. package/dist/{chunk-SGTTR4XW.js → chunk-HWWB22HC.js} +152 -100
  45. package/dist/chunk-HWWB22HC.js.map +1 -0
  46. package/dist/chunk-IMB6FA2F.js +673 -0
  47. package/dist/chunk-IMB6FA2F.js.map +1 -0
  48. package/dist/{chunk-6YQT3SSM.js → chunk-IQYXXFRC.js} +22 -6
  49. package/dist/chunk-IQYXXFRC.js.map +1 -0
  50. package/dist/chunk-J6VJ5M2U.js +1201 -0
  51. package/dist/chunk-J6VJ5M2U.js.map +1 -0
  52. package/dist/{chunk-B3TSS7BT.js → chunk-KDQRNVBZ.js} +3 -3
  53. package/dist/{chunk-A4H7P7T7.js → chunk-LRWIP7QS.js} +41 -26
  54. package/dist/chunk-LRWIP7QS.js.map +1 -0
  55. package/dist/{chunk-DV7HUC4Z.js → chunk-LYC57OXF.js} +20 -117
  56. package/dist/chunk-LYC57OXF.js.map +1 -0
  57. package/dist/{chunk-HAKDLR6N.js → chunk-MAJ6QDRL.js} +65 -78
  58. package/dist/chunk-MAJ6QDRL.js.map +1 -0
  59. package/dist/{chunk-HJBF4WD2.js → chunk-MHA7Q3KU.js} +288 -108
  60. package/dist/chunk-MHA7Q3KU.js.map +1 -0
  61. package/dist/{chunk-UK4E2FUA.js → chunk-MQTVMB7W.js} +103 -76
  62. package/dist/chunk-MQTVMB7W.js.map +1 -0
  63. package/dist/chunk-NP47HRX2.js +136 -0
  64. package/dist/chunk-NP47HRX2.js.map +1 -0
  65. package/dist/{chunk-MI5TREN3.js → chunk-NYDAHE3Y.js} +52 -46
  66. package/dist/chunk-NYDAHE3Y.js.map +1 -0
  67. package/dist/{chunk-UWDCIFTP.js → chunk-P7ZYOC23.js} +41 -57
  68. package/dist/chunk-P7ZYOC23.js.map +1 -0
  69. package/dist/{chunk-3R27MJ5R.js → chunk-PFWHHYEO.js} +3 -3
  70. package/dist/{chunk-2SUFAE6X.js → chunk-PHPAK2L6.js} +44 -33
  71. package/dist/{chunk-2SUFAE6X.js.map → chunk-PHPAK2L6.js.map} +1 -1
  72. package/dist/{chunk-PPZ3K4YM.js → chunk-PRGPSNGA.js} +2 -2
  73. package/dist/{chunk-32JSYXEK.js → chunk-RGFMQFX7.js} +9 -23
  74. package/dist/{chunk-32JSYXEK.js.map → chunk-RGFMQFX7.js.map} +1 -1
  75. package/dist/{chunk-YVPZUN6X.js → chunk-TJVX42KY.js} +335 -38
  76. package/dist/chunk-TJVX42KY.js.map +1 -0
  77. package/dist/{chunk-RCZJQUTC.js → chunk-UDRAD7MF.js} +60 -27
  78. package/dist/chunk-UDRAD7MF.js.map +1 -0
  79. package/dist/{chunk-HY5HLFDU.js → chunk-UHDXXLYH.js} +148 -135
  80. package/dist/chunk-UHDXXLYH.js.map +1 -0
  81. package/dist/{chunk-RNFUVKC3.js → chunk-UPAAZU4W.js} +30 -13
  82. package/dist/chunk-UPAAZU4W.js.map +1 -0
  83. package/dist/{chunk-L6HSKTQE.js → chunk-W7LZLJAF.js} +4 -4
  84. package/dist/{chunk-HLAPOUEM.js → chunk-WB2FVIUS.js} +44 -12
  85. package/dist/chunk-WB2FVIUS.js.map +1 -0
  86. package/dist/{chunk-VE3IYNDN.js → chunk-WPHC6JBR.js} +9 -9
  87. package/dist/chunk-WPHC6JBR.js.map +1 -0
  88. package/dist/{chunk-KZXQWC7U.js → chunk-XHCP6ZNO.js} +201 -64
  89. package/dist/chunk-XHCP6ZNO.js.map +1 -0
  90. package/dist/{chunk-7F7LGMVO.js → chunk-XKR3VD2Z.js} +12 -12
  91. package/dist/{chunk-7F7LGMVO.js.map → chunk-XKR3VD2Z.js.map} +1 -1
  92. package/dist/{chunk-ICM463EO.js → chunk-XNK5UN2D.js} +5 -649
  93. package/dist/chunk-XNK5UN2D.js.map +1 -0
  94. package/dist/{chunk-Z5O2XBWA.js → chunk-XQLNZW7I.js} +40 -30
  95. package/dist/chunk-XQLNZW7I.js.map +1 -0
  96. package/dist/{chunk-QMIQR7EE.js → chunk-ZAYATXJD.js} +32 -316
  97. package/dist/chunk-ZAYATXJD.js.map +1 -0
  98. package/dist/cli.js +128 -60
  99. package/dist/cli.js.map +1 -1
  100. package/dist/compat-YPDZB34U.js +24 -0
  101. package/dist/{config-KGZUPAUP.js → config-56B4OHZE.js} +20 -17
  102. package/dist/{contract-JH2WBIO6.js → contract-HWLFFL3F.js} +2 -2
  103. package/dist/{conventions-RYN7OXJN.js → conventions-DW2CM5I3.js} +20 -16
  104. package/dist/{conventions-RYN7OXJN.js.map → conventions-DW2CM5I3.js.map} +1 -1
  105. package/dist/db-5NU77PJA.js +264 -0
  106. package/dist/db-5NU77PJA.js.map +1 -0
  107. package/dist/diagnostics-UCTS2VUF.js +25 -0
  108. package/dist/execute-R7AVOLNG.js +38 -0
  109. package/dist/{generate-CX6KNHZS.js → generate-O7GUXLN4.js} +46 -43
  110. package/dist/generate-O7GUXLN4.js.map +1 -0
  111. package/dist/{git-A2IP32PQ.js → git-LCG45YXD.js} +2 -3
  112. package/dist/history-6GSURU3H.js +101 -0
  113. package/dist/history-6GSURU3H.js.map +1 -0
  114. package/dist/init-DD6DBRC5.js +28 -0
  115. package/dist/{operations-CAKIT2BO.js → operations-HU5ZFEBA.js} +78 -52
  116. package/dist/operations-HU5ZFEBA.js.map +1 -0
  117. package/dist/{permissions-G4XCAESF.js → permissions-NNNSRC4P.js} +5 -6
  118. package/dist/plan-XR6B7EM3.js +38 -0
  119. package/dist/{policy-WX6O2JID.js → policy-RFJOJQL6.js} +17 -15
  120. package/dist/{policy-WX6O2JID.js.map → policy-RFJOJQL6.js.map} +1 -1
  121. package/dist/pr-OSCNJCOO.js +42 -0
  122. package/dist/pr-review-IKIYHFBO.js +33 -0
  123. package/dist/prd-HFDZJJQR.js +37 -0
  124. package/dist/{ps-76L6ZAZL.js → ps-AKRHGPSR.js} +26 -15
  125. package/dist/ps-AKRHGPSR.js.map +1 -0
  126. package/dist/{recorder-N7U66HCO.js → recorder-T7426CKV.js} +11 -7
  127. package/dist/registry-DWRKD65A.js +17 -0
  128. package/dist/repository-TABBRW2C.js +80 -0
  129. package/dist/resume-GDXKK5XP.js +278 -0
  130. package/dist/resume-GDXKK5XP.js.map +1 -0
  131. package/dist/review-JJHMQA2O.js +38 -0
  132. package/dist/routing-FPNUVE7T.js +35 -0
  133. package/dist/run-RLCURLLL.js +61 -0
  134. package/dist/{session-publisher-KAJQRHNZ.js → session-publisher-Q5TNEZPX.js} +3 -3
  135. package/dist/session-publisher-Q5TNEZPX.js.map +1 -0
  136. package/dist/{state-manager-UPHO2EA2.js → state-manager-IGNTD4AB.js} +9 -4
  137. package/dist/state-manager-IGNTD4AB.js.map +1 -0
  138. package/dist/{usage-U7QNGCPK.js → usage-XW27AL5K.js} +69 -63
  139. package/dist/usage-XW27AL5K.js.map +1 -0
  140. package/dist/web-NCDBVZ5Y.js +205 -0
  141. package/dist/web-NCDBVZ5Y.js.map +1 -0
  142. package/package.json +20 -7
  143. package/prompts/analyze.md +16 -19
  144. package/prompts/execute.md +73 -144
  145. package/prompts/generate.md +23 -10
  146. package/prompts/plan.md +32 -60
  147. package/prompts/pr-review.md +58 -40
  148. package/prompts/pr.md +50 -16
  149. package/prompts/prd.md +26 -19
  150. package/prompts/review.md +41 -18
  151. package/web/AGENTS.md +115 -7
  152. package/web/public/app.css +393 -245
  153. package/web/public/app.js +212 -143
  154. package/web/public/index.html +106 -78
  155. package/dist/agent-TB6W7PT7.js +0 -30
  156. package/dist/analyze-6M3KEOX7.js.map +0 -1
  157. package/dist/apply-ADB3ZK7W.js.map +0 -1
  158. package/dist/chunk-6YQT3SSM.js.map +0 -1
  159. package/dist/chunk-A4H7P7T7.js.map +0 -1
  160. package/dist/chunk-ANI2NTR4.js.map +0 -1
  161. package/dist/chunk-CRGIQFUQ.js.map +0 -1
  162. package/dist/chunk-D6ZBGJ3L.js.map +0 -1
  163. package/dist/chunk-DV7HUC4Z.js.map +0 -1
  164. package/dist/chunk-E6KXCV3T.js.map +0 -1
  165. package/dist/chunk-HAKDLR6N.js.map +0 -1
  166. package/dist/chunk-HJBF4WD2.js.map +0 -1
  167. package/dist/chunk-HLAPOUEM.js.map +0 -1
  168. package/dist/chunk-HY5HLFDU.js.map +0 -1
  169. package/dist/chunk-ICM463EO.js.map +0 -1
  170. package/dist/chunk-ISZ54SVE.js.map +0 -1
  171. package/dist/chunk-KZ5EY5XX.js.map +0 -1
  172. package/dist/chunk-KZXQWC7U.js.map +0 -1
  173. package/dist/chunk-MI5TREN3.js.map +0 -1
  174. package/dist/chunk-QMIQR7EE.js.map +0 -1
  175. package/dist/chunk-RCZJQUTC.js.map +0 -1
  176. package/dist/chunk-RNFUVKC3.js.map +0 -1
  177. package/dist/chunk-SGTTR4XW.js.map +0 -1
  178. package/dist/chunk-SOM4QA6C.js.map +0 -1
  179. package/dist/chunk-UK4E2FUA.js.map +0 -1
  180. package/dist/chunk-UWDCIFTP.js.map +0 -1
  181. package/dist/chunk-VE3IYNDN.js.map +0 -1
  182. package/dist/chunk-WCCZJL7W.js +0 -141
  183. package/dist/chunk-WCCZJL7W.js.map +0 -1
  184. package/dist/chunk-XEYH6ZZI.js +0 -289
  185. package/dist/chunk-XEYH6ZZI.js.map +0 -1
  186. package/dist/chunk-XZUEFTUP.js +0 -243
  187. package/dist/chunk-XZUEFTUP.js.map +0 -1
  188. package/dist/chunk-YII4U7ZD.js.map +0 -1
  189. package/dist/chunk-YVPZUN6X.js.map +0 -1
  190. package/dist/chunk-Z5O2XBWA.js.map +0 -1
  191. package/dist/diagnostics-HTCWF7F4.js +0 -23
  192. package/dist/execute-QL3ZQIAP.js +0 -37
  193. package/dist/generate-CX6KNHZS.js.map +0 -1
  194. package/dist/init-PDVQDRS6.js +0 -24
  195. package/dist/operations-CAKIT2BO.js.map +0 -1
  196. package/dist/plan-PRORGS3L.js +0 -34
  197. package/dist/pr-27RMQI5F.js +0 -39
  198. package/dist/pr-review-VNESMAMT.js +0 -32
  199. package/dist/prd-ZNC4T5BS.js +0 -33
  200. package/dist/ps-76L6ZAZL.js.map +0 -1
  201. package/dist/registry-XLHHYLTR.js +0 -15
  202. package/dist/resume-VLWIE264.js +0 -244
  203. package/dist/resume-VLWIE264.js.map +0 -1
  204. package/dist/review-7CREJDKB.js +0 -35
  205. package/dist/routing-VCDYHSXA.js +0 -33
  206. package/dist/run-3MPORLF5.js +0 -56
  207. package/dist/usage-U7QNGCPK.js.map +0 -1
  208. package/dist/web-HUNDHCJ4.js +0 -172
  209. package/dist/web-HUNDHCJ4.js.map +0 -1
  210. /package/dist/{agent-TB6W7PT7.js.map → agent-EWKA7357.js.map} +0 -0
  211. /package/dist/{bench-FM7I7EIV.js.map → bench-ZJEIACUK.js.map} +0 -0
  212. /package/dist/{chunk-RYOUZ6EK.js.map → chunk-23VHVVKW.js.map} +0 -0
  213. /package/dist/{chunk-C5TL7T4V.js.map → chunk-2YM2VH6R.js.map} +0 -0
  214. /package/dist/{chunk-QPGEFLMY.js.map → chunk-7CMVL4X6.js.map} +0 -0
  215. /package/dist/{chunk-DUS5ZNKL.js.map → chunk-DIMDAYZS.js.map} +0 -0
  216. /package/dist/{chunk-DGRTJD5R.js.map → chunk-DXH4TQRB.js.map} +0 -0
  217. /package/dist/{chunk-YUNBOUQH.js.map → chunk-ERAG5IQB.js.map} +0 -0
  218. /package/dist/{chunk-B3TSS7BT.js.map → chunk-KDQRNVBZ.js.map} +0 -0
  219. /package/dist/{chunk-3R27MJ5R.js.map → chunk-PFWHHYEO.js.map} +0 -0
  220. /package/dist/{chunk-PPZ3K4YM.js.map → chunk-PRGPSNGA.js.map} +0 -0
  221. /package/dist/{chunk-L6HSKTQE.js.map → chunk-W7LZLJAF.js.map} +0 -0
  222. /package/dist/{config-KGZUPAUP.js.map → compat-YPDZB34U.js.map} +0 -0
  223. /package/dist/{contract-JH2WBIO6.js.map → config-56B4OHZE.js.map} +0 -0
  224. /package/dist/{diagnostics-HTCWF7F4.js.map → contract-HWLFFL3F.js.map} +0 -0
  225. /package/dist/{execute-QL3ZQIAP.js.map → diagnostics-UCTS2VUF.js.map} +0 -0
  226. /package/dist/{git-A2IP32PQ.js.map → execute-R7AVOLNG.js.map} +0 -0
  227. /package/dist/{init-PDVQDRS6.js.map → git-LCG45YXD.js.map} +0 -0
  228. /package/dist/{permissions-G4XCAESF.js.map → init-DD6DBRC5.js.map} +0 -0
  229. /package/dist/{plan-PRORGS3L.js.map → permissions-NNNSRC4P.js.map} +0 -0
  230. /package/dist/{pr-27RMQI5F.js.map → plan-XR6B7EM3.js.map} +0 -0
  231. /package/dist/{pr-review-VNESMAMT.js.map → pr-OSCNJCOO.js.map} +0 -0
  232. /package/dist/{prd-ZNC4T5BS.js.map → pr-review-IKIYHFBO.js.map} +0 -0
  233. /package/dist/{recorder-N7U66HCO.js.map → prd-HFDZJJQR.js.map} +0 -0
  234. /package/dist/{registry-XLHHYLTR.js.map → recorder-T7426CKV.js.map} +0 -0
  235. /package/dist/{review-7CREJDKB.js.map → registry-DWRKD65A.js.map} +0 -0
  236. /package/dist/{routing-VCDYHSXA.js.map → repository-TABBRW2C.js.map} +0 -0
  237. /package/dist/{run-3MPORLF5.js.map → review-JJHMQA2O.js.map} +0 -0
  238. /package/dist/{session-publisher-KAJQRHNZ.js.map → routing-FPNUVE7T.js.map} +0 -0
  239. /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-ICM463EO.js";
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 = "1";
45
+ var RECOMMENDED_POLICY_VERSION = "2";
46
46
  var RECOMMENDED_POLICY = {
47
- analyze: { tier: "fast", preferHarness: "codex-cli" },
48
- generate: { tier: "mid", preferHarness: "claude-code" },
49
- prd: { tier: "mid", preferHarness: "claude-code" },
50
- plan: { tier: "fast", preferHarness: "codex-cli" },
51
- execute: { tier: "strong", preferHarness: "claude-code" },
52
- review: { tier: "strong", preferHarness: "codex-cli" },
53
- pr: { tier: "fast", preferHarness: "codex-cli" },
54
- "pr-review": { tier: "strong", preferHarness: "codex-cli" }
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
- if (entry.preferHarness === void 0) return null;
68
- const model = modelForTier(entry.preferHarness, entry.tier);
69
- const provider = PROVIDERS[entry.preferHarness];
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: entry.preferHarness, provider, model: model.id, tier: entry.tier };
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: reasons.length === 0, reasonCodes: reasons };
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 score = input.eligible ? (prior + learned) * weights.quality + costScore * weights.cost + latencyScore * weights.latency : 0;
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-D6ZBGJ3L.js.map
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
- isProcessAlive,
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-ICM463EO.js";
15
+ } from "./chunk-XNK5UN2D.js";
6
16
  import {
7
17
  writeFileAtomic
8
- } from "./chunk-3R27MJ5R.js";
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 content = await readFile(path, "utf-8");
146
- const raw = JSON.parse(content);
205
+ const repository = getPlanRepository(path);
147
206
  try {
148
- const parsed = taskPlanSchema.parse(raw);
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 ?? 3,
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 [...stories].filter((story) => !story.passes).sort((a, b) => a.priority - b.priority)[0];
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-SOM4QA6C.js.map
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":";;;;;;;;;;;;;;;;;;;;AAAA,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;;;ACDzB,SAAS,gBAAgB;AAIzB,SAAS,SAAiB;AACxB,UAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,aAAa,GAAG;AAC1D;AAQO,SAAS,+BAA+B,MAA0B;AACvE,MAAI,KAAK,eAAe,UAAa,KAAK,WAAW,WAAW,EAAG,QAAO;AAC1E,MAAI,UAAU;AACd,QAAM,aAAa,KAAK,WAAW,IAAI,CAAC,WAAW;AACjD,QAAI,OAAO,WAAW,aAAa,OAAO,eAAe,KAAM,QAAO;AACtE,UAAM,QAAQ,OAAO;AACrB,QAAI,UAAU,UAAa,UAAU,MAAM;AACzC,gBAAU;AACV,aAAO,EAAE,GAAG,QAAQ,QAAQ,eAAwB,YAAY,OAAO,GAAG,OAAO,KAAK;AAAA,IACxF;AACA,QAAI,MAAM,SAAS,SAAS,EAAG,QAAO;AACtC,QAAI,eAAe,MAAM,GAAG,EAAG,QAAO;AACtC,cAAU;AACV,WAAO,EAAE,GAAG,QAAQ,QAAQ,eAAwB,YAAY,OAAO,GAAG,OAAO,KAAK;AAAA,EACxF,CAAC;AACD,SAAO,UAAU,EAAE,GAAG,MAAM,WAAW,IAAI;AAC7C;;;ACLA,IAAM,eAAe;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAMA,SAAS,IAAI,OAAoC;AAC/C,SAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ;AACvE;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAmBO,SAAS,gBAAgB,SAAsC;AACpE,MAAI,CAAC,SAAS,OAAO,EAAG,QAAO;AAE/B,QAAM,QAAqB,CAAC;AAC5B,QAAM,cAAc,IAAI,QAAQ,YAAY;AAC5C,QAAM,cAAc,IAAI,QAAQ,mBAAmB;AACnD,QAAM,aAAa,IAAI,QAAQ,wBAAwB;AACvD,QAAM,eAAe,IAAI,QAAQ,aAAa;AAE9C,MAAI,gBAAgB,QAAW;AAC7B,UAAM,cACJ,gBAAgB,SAAY,KAAK,IAAI,GAAG,cAAc,WAAW,IAAI;AAAA,EACzE;AACA,MAAI,iBAAiB,OAAW,OAAM,eAAe;AACrD,MAAI,gBAAgB,OAAW,OAAM,kBAAkB;AACvD,MAAI,eAAe,OAAW,OAAM,sBAAsB;AAE1D,SAAO,OAAO,KAAK,KAAK,EAAE,SAAS,IAAI,QAAQ;AACjD;AAEO,SAAS,WAAW,SAAsC;AAC/D,MAAI,CAAC,SAAS,OAAO,EAAG,QAAO;AAE/B,QAAM,WAAW,SAAS,QAAQ,KAAK,IAAI,QAAQ,QAAQ;AAE3D,QAAM,QAAqB,CAAC;AAE5B,QAAM,cAAc,WAAW,IAAI,SAAS,YAAY,IAAI;AAC5D,QAAM,eAAe,WAAW,IAAI,SAAS,aAAa,IAAI;AAC9D,QAAM,kBAAkB,WAAW,IAAI,SAAS,uBAAuB,IAAI;AAC3E,QAAM,sBAAsB,WAAW,IAAI,SAAS,2BAA2B,IAAI;AACnF,QAAM,UAAU,IAAI,QAAQ,cAAc;AAG1C,QAAM,cAAc,IAAI,QAAQ,gBAAgB;AAChD,QAAM,eAAe,IAAI,QAAQ,iBAAiB;AAClD,QAAM,aAAa,IAAI,QAAQ,QAAQ;AAEvC,QAAM,gBAAgB,eAAe;AACrC,QAAM,iBAAiB,gBAAgB;AACvC,QAAM,eAAe,WAAW;AAEhC,MAAI,kBAAkB,OAAW,OAAM,cAAc;AACrD,MAAI,mBAAmB,OAAW,OAAM,eAAe;AACvD,MAAI,oBAAoB,OAAW,OAAM,kBAAkB;AAC3D,MAAI,wBAAwB,OAAW,OAAM,sBAAsB;AACnE,MAAI,iBAAiB,OAAW,OAAM,UAAU;AAEhD,QAAM,gBAAgB,IAAI,QAAQ,WAAW;AAC7C,QAAM,gBAAgB,IAAI,QAAQ,eAAe;AACjD,QAAM,SAAS,IAAI,QAAQ,OAAO,KAAK,IAAI,QAAQ,sBAAsB;AACzE,QAAM,WAAW,IAAI,QAAQ,SAAS;AACtC,MAAI,kBAAkB,OAAW,OAAM,gBAAgB;AACvD,MAAI,kBAAkB,OAAW,OAAM,gBAAgB;AACvD,MAAI,WAAW,OAAW,OAAM,SAAS;AACzC,MAAI,aAAa,OAAW,OAAM,WAAW;AAE7C,SAAO,OAAO,KAAK,KAAK,EAAE,SAAS,IAAI,QAAQ;AACjD;AASO,SAAS,SACd,GACA,GACa;AACb,QAAM,SAAsB,CAAC;AAE7B,aAAW,SAAS,cAAc;AAChC,UAAM,OAAO,IAAI,KAAK;AACtB,UAAM,QAAQ,IAAI,KAAK;AACvB,QAAI,SAAS,UAAa,UAAU,OAAW;AAC/C,WAAO,KAAK,KAAK,QAAQ,MAAM,SAAS;AAAA,EAC1C;AAEA,SAAO;AACT;AAcO,SAAS,YAAY,OAAuC,OAA4B;AAC7F,QAAM,SAAsB,CAAC;AAC7B,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,UAAU,OAAO,SAAS,KAAK,KAAK,QAAQ,IAAI,QAAQ;AAE9D,aAAW,SAAS,cAAc;AAChC,UAAM,QAAQ,MAAM,KAAK;AACzB,QAAI,UAAU,OAAW;AACzB,WAAO,KAAK,IAAI,UAAU,YAAY,QAAQ,UAAU,KAAK,MAAM,QAAQ,OAAO;AAAA,EACpF;AAEA,SAAO;AACT;AAKA,SAAS,cAAc,OAAuB;AAC5C,QAAM,MAAM,KAAK,IAAI,KAAK;AAC1B,MAAI,OAAO,IAAW,QAAO,IAAI,QAAQ,KAAW,QAAQ,CAAC,CAAC;AAC9D,MAAI,OAAO,IAAO,QAAO,IAAI,QAAQ,KAAO,QAAQ,CAAC,CAAC;AACtD,SAAO,OAAO,KAAK,MAAM,KAAK,CAAC;AACjC;AASO,SAAS,aAAa,OAA+C;AAC1E,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,WAAqB,CAAC;AAE5B,QAAM,KAAe,CAAC;AACtB,MAAI,MAAM,gBAAgB,OAAW,IAAG,KAAK,GAAG,cAAc,MAAM,WAAW,CAAC,KAAK;AACrF,MAAI,MAAM,iBAAiB,OAAW,IAAG,KAAK,GAAG,cAAc,MAAM,YAAY,CAAC,MAAM;AACxF,MAAI,GAAG,SAAS,EAAG,UAAS,KAAK,GAAG,KAAK,KAAK,CAAC;AAE/C,QAAM,QACJ,MAAM,oBAAoB,UAAa,MAAM,wBAAwB,UAChE,MAAM,mBAAmB,MAAM,MAAM,uBAAuB,KAC7D;AACN,MAAI,UAAU,OAAW,UAAS,KAAK,GAAG,cAAc,KAAK,CAAC,QAAQ;AAEtE,MAAI,MAAM,YAAY,OAAW,UAAS,KAAK,KAAK,MAAM,QAAQ,QAAQ,CAAC,CAAC,EAAE;AAE9E,SAAO,SAAS,KAAK,QAAK;AAC5B;AAMO,SAAS,aAAa,OAAgD;AAC3E,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,aAAa,KAAK,CAAC,UAAU,MAAM,KAAK,MAAM,MAAS;AAChE;;;ACtNO,IAAM,kBAAkB;AAAA,EAC7B,EAAE,OAAO,OAAO,OAAO,gBAAgB,UAAU,oBAAoB,UAAU,aAAa;AAAA,EAC5F;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,EACZ;AACF;AAOO,IAAM,gCAAgC;AACtC,IAAM,yBAAyB;AAAA,EACpC,cAAc;AAAA,EACd,eAAe;AAAA,EACf,oBAAoB;AAAA,EACpB,iBAAiB;AAAA,EACjB,WAAW;AACb;AAEO,SAAS,oBAEd,MAAS,YAAwB;AACjC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAI,aAAa,EAAE,aAAa,eAAe,aAAa,KAAK,IAAI,CAAC;AAAA,IACtE,UAAU,EAAE,GAAG,KAAK,UAAU,oBAAoB,KAAK;AAAA,EACzD;AACF;;;AH7CO,SAAS,SAAiB;AAC/B,UAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,aAAa,GAAG;AAC1D;AAMA,eAAsB,aAAa,MAAiC;AAClE,QAAM,aAAa,kBAAkB,IAAI;AAEzC,MAAI;AACF,QAAI;AACJ,QAAI,eAAe,QAAW;AAC5B,eAAS,eAAe,MAAM,KAAK,MAAM,MAAM,SAAS,MAAM,OAAO,CAAC,CAAC;AAAA,IACzE,OAAO;AACL,UAAI;AACF,iBAAS,MAAM,eAAe,UAAU;AAAA,MAC1C,SAAS,OAAO;AAMd,YAAI,EAAE,iBAAiB,UAAU,CAAC,MAAM,QAAQ,WAAW,4BAA4B,GAAG;AACxF,gBAAM;AAAA,QACR;AACA,iBAAS,eAAe,MAAM,KAAK,MAAM,MAAM,SAAS,MAAM,OAAO,CAAC,CAAC;AACvE,cAAM,eAAe,YAAY,MAAM;AAAA,MACzC;AAAA,IACF;AACA,UAAM,aAAa,+BAA+B,MAAM;AACxD,QAAI,eAAe,QAAQ;AACzB,UAAI;AACF,cAAM,aAAa,MAAM,UAAU;AAAA,MACrC,QAAQ;AAAA,MAGR;AAAA,IACF;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAI,eAAe,UAAU;AAC3B,YAAM,SAAS,IAAI,OAAO,IAAI,CAAC,MAAM,OAAO,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI;AACvF,YAAM,IAAI,MAAM,yBAAyB,IAAI;AAAA,EAAM,MAAM,EAAE;AAAA,IAC7D;AACA,UAAM;AAAA,EACR;AACF;AAMA,eAAsB,aAAa,MAAc,MAA+B;AAC9E,QAAM,aAAa,kBAAkB,IAAI;AACzC,MAAI,eAAe,QAAW;AAC5B,UAAM,eAAe,YAAY,IAAI;AACrC;AAAA,EACF;AACA,QAAM,gBAAgB,MAAM,GAAG,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,CAAI;AAClE;AAUO,SAAS,gBAAgB,MAA0B;AACxD,QAAM,kBAAiC;AAEvC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,aAAa,KAAK,eAAe;AAAA,IACjC,aAAa,KAAK,eAAe;AAAA,IACjC,eAAe,KAAK,iBAAiB;AAAA,IACrC,WAAW,KAAK,aAAa;AAAA,IAC7B,iBAAiB,KAAK,mBAAmB;AAAA,IACzC,qBAAqB,KAAK,uBAAuB;AAAA,IACjD,oBAAoB,KAAK,sBAAsB;AAAA,IAC/C,UAAU,KAAK,WAAW,EAAE,GAAG,iBAAiB,GAAG,KAAK,SAAS,IAAI;AAAA,EACvE;AACF;AAMO,SAAS,qBAAqB,MAAyB;AAC5D,SAAO,QAAQ,KAAK,kBAAkB;AACxC;AAMO,SAAS,eAAe,MAAyB;AACtD,MAAI,KAAK,YAAY,WAAW,GAAG;AACjC,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,YAAY,MAAM,CAAC,UAAU,MAAM,WAAW,IAAI;AAChE;AAaO,SAAS,kBAAkB,SAAsD;AACtF,SAAO,gBAAgB,OAAO,EAAE,CAAC;AACnC;AAKO,SAAS,iBAAiB,MAAgB,SAA2B;AAC1E,SAAO;AAAA,IACL,GAAG;AAAA,IACH,aAAa,KAAK,YAAY;AAAA,MAAI,CAAC,UACjC,MAAM,OAAO,UAAU,EAAE,GAAG,OAAO,QAAQ,KAAK,IAAI;AAAA,IACtD;AAAA,EACF;AACF;AAaO,SAAS,kBACd,MACA,UACA,OACA,iBACU;AACV,MAAI,SAAS,WAAW,EAAG,QAAO;AAElC,QAAM,UAAU,IAAI,IAAI,QAAQ;AAEhC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,aAAa,KAAK,YAAY,IAAI,CAAC,UAAU;AAC3C,UAAI,CAAC,QAAQ,IAAI,MAAM,EAAE,EAAG,QAAO;AAEnC,YAAM,UAAqB,EAAE,GAAG,OAAO,GAAG,SAAS,OAAO,KAAK,EAAE;AACjE,UAAI,MAAM,oBAAoB,UAAa,oBAAoB,QAAW;AACxE,gBAAQ,mBAAmB,MAAM,mBAAmB,MAAM,mBAAmB;AAAA,MAC/E;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;AAKO,SAAS,oBAAoB,MAAgB,WAA8B;AAChF,QAAM,KAAK,aAAa,OAAO;AAC/B,SAAO;AAAA,IACL,GAAG;AAAA,IACH,aAAa;AAAA,IACb,aAAa;AAAA,IACb,eAAe;AAAA,EACjB;AACF;AAKO,SAAS,mBAAmB,MAA0B;AAC3D,QAAM,KAAK,OAAO;AAClB,SAAO;AAAA,IACL,GAAG;AAAA,IACH,aAAa;AAAA,IACb,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,UAAU,KAAK,WAAW,EAAE,GAAG,KAAK,UAAU,oBAAoB,KAAK,IAAI,KAAK;AAAA,EAClF;AACF;AAKO,SAAS,aAAa,MAAgB,UAAkB,SAA2B;AACxF,QAAM,KAAK,OAAO;AAClB,QAAM,QAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,IAAI;AAAA,EACN;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,eAAe;AAAA,IACf,WAAW;AAAA,EACb;AACF;AAMO,SAAS,eAAe,MAAgB,kBAAoC;AACjF,QAAM,KAAK,OAAO;AAGlB,MAAI,KAAK,aAAa,KAAK,UAAU,KAAK,kBAAkB;AAC1D,WAAO;AAAA,MACL,GAAG;AAAA,MACH,eAAe;AAAA,IACjB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,eAAe;AAAA,IACf,WAAW;AAAA,EACb;AACF;AAKO,SAAS,iBAAiB,SAAyB;AACxD,SAAO,QACJ,MAAM,IAAI,EACV,OAAO,CAAC,SAAS,KAAK,KAAK,EAAE,SAAS,CAAC,EACvC,MAAM,GAAG,CAAC,EACV,KAAK,IAAI;AACd;","names":[]}