issue-flow 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/{analyze-XMEULTT2.js → analyze-PJBY346H.js} +13 -8
  2. package/dist/analyze-PJBY346H.js.map +1 -0
  3. package/dist/{chunk-MWUF4LMF.js → chunk-3ISZ7T5M.js} +13 -8
  4. package/dist/chunk-3ISZ7T5M.js.map +1 -0
  5. package/dist/{chunk-EHVKXFFN.js → chunk-4M4EWFZZ.js} +2 -2
  6. package/dist/{chunk-JNEHVQH5.js → chunk-6HGZYTAT.js} +223 -11
  7. package/dist/chunk-6HGZYTAT.js.map +1 -0
  8. package/dist/chunk-7HX2RPFY.js +123 -0
  9. package/dist/chunk-7HX2RPFY.js.map +1 -0
  10. package/dist/{chunk-DKMBLNGO.js → chunk-7YS3J4A2.js} +3 -3
  11. package/dist/{chunk-WQ4SUZ4P.js → chunk-EBJM6G2D.js} +90 -23
  12. package/dist/chunk-EBJM6G2D.js.map +1 -0
  13. package/dist/{chunk-GOZT2YP5.js → chunk-IWBHBDRU.js} +5 -9
  14. package/dist/chunk-IWBHBDRU.js.map +1 -0
  15. package/dist/{chunk-6UBA7CSJ.js → chunk-NVSJYJ72.js} +14 -8
  16. package/dist/chunk-NVSJYJ72.js.map +1 -0
  17. package/dist/{chunk-G3TJHMAS.js → chunk-OCXHO7YP.js} +2 -2
  18. package/dist/{chunk-RGCU2KE6.js → chunk-R7X26MWK.js} +14 -9
  19. package/dist/chunk-R7X26MWK.js.map +1 -0
  20. package/dist/{chunk-YLXPLHEL.js → chunk-RKASM5FF.js} +2 -2
  21. package/dist/{chunk-WUITOU5B.js → chunk-SHG2PPN7.js} +14 -9
  22. package/dist/chunk-SHG2PPN7.js.map +1 -0
  23. package/dist/{chunk-CYU6RSDJ.js → chunk-WEXU4R3H.js} +14 -9
  24. package/dist/chunk-WEXU4R3H.js.map +1 -0
  25. package/dist/{chunk-PRVWJZV4.js → chunk-YPIIIDTT.js} +3 -3
  26. package/dist/cli.js +13 -13
  27. package/dist/{config-2NDV7Y72.js → config-7N7UJP32.js} +3 -3
  28. package/dist/execute-EZKQKKLO.js +13 -0
  29. package/dist/{generate-YEQRH36H.js → generate-KUQNDMMR.js} +13 -8
  30. package/dist/generate-KUQNDMMR.js.map +1 -0
  31. package/dist/init-GL6US653.js +9 -0
  32. package/dist/plan-XNXGWNGY.js +15 -0
  33. package/dist/pr-XILDNJLM.js +15 -0
  34. package/dist/pr-review-4WG2RPRP.js +15 -0
  35. package/dist/prd-BLJ2XBPL.js +15 -0
  36. package/dist/review-MRA7ZQQG.js +13 -0
  37. package/dist/{run-EFXDXSB4.js → run-X62QZCXN.js} +20 -16
  38. package/dist/run-X62QZCXN.js.map +1 -0
  39. package/package.json +2 -2
  40. package/web/CLAUDE.md +35 -0
  41. package/web/public/app.css +5 -0
  42. package/web/public/app.js +80 -7
  43. package/dist/analyze-XMEULTT2.js.map +0 -1
  44. package/dist/chunk-6UBA7CSJ.js.map +0 -1
  45. package/dist/chunk-CYU6RSDJ.js.map +0 -1
  46. package/dist/chunk-GOZT2YP5.js.map +0 -1
  47. package/dist/chunk-JNEHVQH5.js.map +0 -1
  48. package/dist/chunk-MWUF4LMF.js.map +0 -1
  49. package/dist/chunk-RGCU2KE6.js.map +0 -1
  50. package/dist/chunk-SLHZPB3C.js +0 -50
  51. package/dist/chunk-SLHZPB3C.js.map +0 -1
  52. package/dist/chunk-WQ4SUZ4P.js.map +0 -1
  53. package/dist/chunk-WUITOU5B.js.map +0 -1
  54. package/dist/execute-BOQPTRH4.js +0 -13
  55. package/dist/generate-YEQRH36H.js.map +0 -1
  56. package/dist/init-5KXCG36P.js +0 -9
  57. package/dist/plan-4I47CQM5.js +0 -15
  58. package/dist/pr-F672VPMM.js +0 -15
  59. package/dist/pr-review-GKP4TJRJ.js +0 -15
  60. package/dist/prd-3QME4DYX.js +0 -15
  61. package/dist/review-RG2UIPOB.js +0 -13
  62. package/dist/run-EFXDXSB4.js.map +0 -1
  63. /package/dist/{chunk-EHVKXFFN.js.map → chunk-4M4EWFZZ.js.map} +0 -0
  64. /package/dist/{chunk-DKMBLNGO.js.map → chunk-7YS3J4A2.js.map} +0 -0
  65. /package/dist/{chunk-G3TJHMAS.js.map → chunk-OCXHO7YP.js.map} +0 -0
  66. /package/dist/{chunk-YLXPLHEL.js.map → chunk-RKASM5FF.js.map} +0 -0
  67. /package/dist/{chunk-PRVWJZV4.js.map → chunk-YPIIIDTT.js.map} +0 -0
  68. /package/dist/{config-2NDV7Y72.js.map → config-7N7UJP32.js.map} +0 -0
  69. /package/dist/{execute-BOQPTRH4.js.map → execute-EZKQKKLO.js.map} +0 -0
  70. /package/dist/{init-5KXCG36P.js.map → init-GL6US653.js.map} +0 -0
  71. /package/dist/{plan-4I47CQM5.js.map → plan-XNXGWNGY.js.map} +0 -0
  72. /package/dist/{pr-F672VPMM.js.map → pr-XILDNJLM.js.map} +0 -0
  73. /package/dist/{pr-review-GKP4TJRJ.js.map → pr-review-4WG2RPRP.js.map} +0 -0
  74. /package/dist/{prd-3QME4DYX.js.map → prd-BLJ2XBPL.js.map} +0 -0
  75. /package/dist/{review-RG2UIPOB.js.map → review-MRA7ZQQG.js.map} +0 -0
@@ -2,17 +2,18 @@
2
2
  import {
3
3
  issuePlaceholders,
4
4
  resolveCommandIssue
5
- } from "./chunk-DKMBLNGO.js";
5
+ } from "./chunk-7YS3J4A2.js";
6
6
  import {
7
7
  runHeadless
8
- } from "./chunk-GOZT2YP5.js";
8
+ } from "./chunk-IWBHBDRU.js";
9
9
  import {
10
10
  applyPlaceholders,
11
- loadPrompt
12
- } from "./chunk-SLHZPB3C.js";
11
+ loadPrompt,
12
+ publishPhaseMetrics
13
+ } from "./chunk-7HX2RPFY.js";
13
14
  import {
14
15
  resolveIssuePaths
15
- } from "./chunk-G3TJHMAS.js";
16
+ } from "./chunk-OCXHO7YP.js";
16
17
  import {
17
18
  getGlobalTimeout,
18
19
  loadTaskPlan,
@@ -20,7 +21,7 @@ import {
20
21
  printInfo,
21
22
  printSuccess,
22
23
  saveTaskPlan
23
- } from "./chunk-JNEHVQH5.js";
24
+ } from "./chunk-6HGZYTAT.js";
24
25
 
25
26
  // src/commands/analyze.ts
26
27
  import { mkdir, readFile, writeFile } from "fs/promises";
@@ -41,15 +42,19 @@ async function runAnalyze(issue, resolvedIssue) {
41
42
  // body that happens to contain a placeholder is left untouched.
42
43
  ...issuePlaceholders(resolution.resolved)
43
44
  });
45
+ const startedAtMs = Date.now();
44
46
  const result = await runHeadless({
45
47
  prompt,
46
48
  maxTurns: 30,
47
49
  timeout: getGlobalTimeout() ?? 3e5,
48
- outputFormat: "text",
50
+ // json (not text) so the CLI reports usage: the envelope's `result` field
51
+ // carries the same assistant text this phase already consumed.
52
+ outputFormat: "json",
49
53
  allowedTools: ["Bash", "Read", "Glob", "Grep", "Write"],
50
54
  addDirs: [paths.issueDir],
51
55
  statusMessage: `Analyzing issue #${issueNumber}...`
52
56
  });
57
+ publishPhaseMetrics("analyze", result.cost, startedAtMs);
53
58
  if (!result.success) {
54
59
  printError(`Analysis failed: ${result.error}`);
55
60
  return 1;
@@ -77,4 +82,4 @@ async function runAnalyze(issue, resolvedIssue) {
77
82
  export {
78
83
  runAnalyze
79
84
  };
80
- //# sourceMappingURL=analyze-XMEULTT2.js.map
85
+ //# sourceMappingURL=analyze-PJBY346H.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/analyze.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { ResolvedIssue } from '../issues/types.js';\nimport { resolveIssuePaths } from '../storage/resolve.js';\nimport { printError, printInfo, printSuccess } from '../ui/logger.js';\n\nexport async function runAnalyze(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const paths = await resolveIssuePaths(issueNumber);\n const analysisPath = paths.analysisFile;\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n await mkdir(paths.issueDir, { recursive: true });\n\n const template = await loadPrompt('analyze');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __ANALYSIS_PATH__: analysisPath,\n // Last: the Issue content is substituted in but never scanned again, so a\n // body that happens to contain a placeholder is left untouched.\n ...issuePlaceholders(resolution.resolved),\n });\n\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 30,\n timeout: getGlobalTimeout() ?? 300_000,\n // json (not text) so the CLI reports usage: the envelope's `result` field\n // carries the same assistant text this phase already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n addDirs: [paths.issueDir],\n statusMessage: `Analyzing issue #${issueNumber}...`,\n });\n // Before the success check: the tokens were spent either way.\n publishPhaseMetrics('analyze', result.cost, startedAtMs);\n\n if (!result.success) {\n printError(`Analysis failed: ${result.error}`);\n return 1;\n }\n\n // Verify the file was created\n try {\n const content = await readFile(analysisPath, 'utf-8');\n if (content.length < 10) {\n printError('Analysis file was created but appears empty');\n return 1;\n }\n } catch {\n // File wasn't created by headless — save the result as analysis\n printInfo('Headless did not create analysis file; saving output directly');\n await writeFile(analysisPath, result.result, 'utf-8');\n }\n\n // Update pipeline state\n const tasksPath = paths.tasksFile;\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.analyzeCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist yet — that's OK for standalone analyze\n }\n\n printSuccess(`Analysis saved to ${analysisPath}`);\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,OAAO,UAAU,iBAAiB;AAW3C,eAAsB,WAAW,OAAe,eAAgD;AAC9F,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,QAAQ,MAAM,kBAAkB,WAAW;AACjD,QAAM,eAAe,MAAM;AAE3B,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAEA,QAAM,MAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAE/C,QAAM,WAAW,MAAM,WAAW,SAAS;AAC3C,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,mBAAmB;AAAA;AAAA;AAAA,IAGnB,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,cAAc,KAAK,IAAI;AAC7B,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,SAAS,iBAAiB,KAAK;AAAA;AAAA;AAAA,IAG/B,cAAc;AAAA,IACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,IACtD,SAAS,CAAC,MAAM,QAAQ;AAAA,IACxB,eAAe,oBAAoB,WAAW;AAAA,EAChD,CAAC;AAED,sBAAoB,WAAW,OAAO,MAAM,WAAW;AAEvD,MAAI,CAAC,OAAO,SAAS;AACnB,eAAW,oBAAoB,OAAO,KAAK,EAAE;AAC7C,WAAO;AAAA,EACT;AAGA,MAAI;AACF,UAAM,UAAU,MAAM,SAAS,cAAc,OAAO;AACpD,QAAI,QAAQ,SAAS,IAAI;AACvB,iBAAW,6CAA6C;AACxD,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAEN,cAAU,+DAA+D;AACzE,UAAM,UAAU,cAAc,OAAO,QAAQ,OAAO;AAAA,EACtD;AAGA,QAAM,YAAY,MAAM;AACxB,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,mBAAmB;AACjC,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,eAAa,qBAAqB,YAAY,EAAE;AAChD,SAAO;AACT;","names":[]}
@@ -2,24 +2,25 @@
2
2
  import {
3
3
  issuePlaceholders,
4
4
  resolveCommandIssue
5
- } from "./chunk-DKMBLNGO.js";
5
+ } from "./chunk-7YS3J4A2.js";
6
6
  import {
7
7
  runHeadless
8
- } from "./chunk-GOZT2YP5.js";
8
+ } from "./chunk-IWBHBDRU.js";
9
9
  import {
10
10
  applyPlaceholders,
11
- loadPrompt
12
- } from "./chunk-SLHZPB3C.js";
11
+ loadPrompt,
12
+ publishPhaseMetrics
13
+ } from "./chunk-7HX2RPFY.js";
13
14
  import {
14
15
  resolveIssuePaths
15
- } from "./chunk-G3TJHMAS.js";
16
+ } from "./chunk-OCXHO7YP.js";
16
17
  import {
17
18
  getGlobalTimeout,
18
19
  loadTaskPlan,
19
20
  printError,
20
21
  printSuccess,
21
22
  saveTaskPlan
22
- } from "./chunk-JNEHVQH5.js";
23
+ } from "./chunk-6HGZYTAT.js";
23
24
 
24
25
  // src/commands/review.ts
25
26
  function parseReviewResult(output) {
@@ -58,15 +59,19 @@ async function runReview(issue, resolvedIssue) {
58
59
  __TASKS_PATH__: tasksPath,
59
60
  ...issuePlaceholders(resolution.resolved)
60
61
  });
62
+ const startedAtMs = Date.now();
61
63
  const result = await runHeadless({
62
64
  prompt,
63
65
  maxTurns: 25,
64
66
  timeout: getGlobalTimeout() ?? 3e5,
65
- outputFormat: "text",
67
+ // json (not text) so the CLI reports usage: the envelope's `result` field
68
+ // carries the same assistant text parseReviewResult() already consumed.
69
+ outputFormat: "json",
66
70
  allowedTools: ["Bash", "Read", "Glob", "Grep"],
67
71
  addDirs: [paths.issueDir],
68
72
  statusMessage: `Reviewing issue #${issueNumber} resolution...`
69
73
  });
74
+ publishPhaseMetrics("review", result.cost, startedAtMs);
70
75
  if (!result.success) {
71
76
  printError(`Review failed: ${result.error}`);
72
77
  return 1;
@@ -100,4 +105,4 @@ async function runReview(issue, resolvedIssue) {
100
105
  export {
101
106
  runReview
102
107
  };
103
- //# sourceMappingURL=chunk-MWUF4LMF.js.map
108
+ //# sourceMappingURL=chunk-3ISZ7T5M.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/review.ts"],"sourcesContent":["import { runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { ResolvedIssue } from '../issues/types.js';\nimport { resolveIssuePaths } from '../storage/resolve.js';\nimport { printError, printSuccess } from '../ui/logger.js';\n\nexport interface ReviewResult {\n status: 'PASS' | 'FAIL';\n findings?: string;\n}\n\n/**\n * Parse the <review-result> block from headless output.\n */\nfunction parseReviewResult(output: string): ReviewResult {\n const match = output.match(/<review-result>([\\s\\S]*?)<\\/review-result>/);\n if (!match) {\n // Try to detect PASS/FAIL from the raw output\n if (/STATUS:\\s*PASS/i.test(output)) {\n return { status: 'PASS' };\n }\n if (/STATUS:\\s*FAIL/i.test(output)) {\n const findingsMatch = output.match(/FINDINGS:([\\s\\S]*?)(?:$|<\\/)/);\n return { status: 'FAIL', findings: findingsMatch?.[1]?.trim() ?? 'Unknown findings' };\n }\n // Default to PASS if no explicit status found and no errors\n return { status: 'PASS' };\n }\n\n const block = match[1];\n if (/STATUS:\\s*PASS/i.test(block)) {\n return { status: 'PASS' };\n }\n\n const findingsMatch = block.match(/FINDINGS:([\\s\\S]*)/);\n return {\n status: 'FAIL',\n findings: findingsMatch?.[1]?.trim() ?? 'Unknown findings',\n };\n}\n\nexport async function runReview(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const paths = await resolveIssuePaths(issueNumber);\n const tasksPath = paths.tasksFile;\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n const template = await loadPrompt('review');\n const prompt = applyPlaceholders(template, {\n __ISSUE_NUMBER__: issueNumber,\n __TASKS_PATH__: tasksPath,\n ...issuePlaceholders(resolution.resolved),\n });\n\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 25,\n timeout: getGlobalTimeout() ?? 300_000,\n // json (not text) so the CLI reports usage: the envelope's `result` field\n // carries the same assistant text parseReviewResult() already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n addDirs: [paths.issueDir],\n statusMessage: `Reviewing issue #${issueNumber} resolution...`,\n });\n // Before the success check: the tokens were spent either way. A correction\n // cycle calls runReview() again and the reducer sums both invocations.\n publishPhaseMetrics('review', result.cost, startedAtMs);\n\n if (!result.success) {\n printError(`Review failed: ${result.error}`);\n return 1;\n }\n\n const review = parseReviewResult(result.result);\n\n if (review.status === 'PASS') {\n // Update pipeline state\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.reviewCompleted = true;\n plan.lastReviewFindings = null;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist\n }\n\n printSuccess('Review PASSED — implementation meets acceptance criteria');\n return 0;\n }\n\n printError('Review FAILED — issues found:');\n if (review.findings) {\n console.log(review.findings);\n }\n\n // Persist the findings so a correction cycle can act on them: without this,\n // a re-run of execute sees every userStories[].passes already true and\n // exits immediately without attempting any fix (see engine.ts's\n // \"already complete\" guards).\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.reviewCompleted = false;\n plan.lastReviewFindings = review.findings ?? 'Unknown findings';\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist\n }\n\n return 1;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAkBA,SAAS,kBAAkB,QAA8B;AACvD,QAAM,QAAQ,OAAO,MAAM,4CAA4C;AACvE,MAAI,CAAC,OAAO;AAEV,QAAI,kBAAkB,KAAK,MAAM,GAAG;AAClC,aAAO,EAAE,QAAQ,OAAO;AAAA,IAC1B;AACA,QAAI,kBAAkB,KAAK,MAAM,GAAG;AAClC,YAAMA,iBAAgB,OAAO,MAAM,8BAA8B;AACjE,aAAO,EAAE,QAAQ,QAAQ,UAAUA,iBAAgB,CAAC,GAAG,KAAK,KAAK,mBAAmB;AAAA,IACtF;AAEA,WAAO,EAAE,QAAQ,OAAO;AAAA,EAC1B;AAEA,QAAM,QAAQ,MAAM,CAAC;AACrB,MAAI,kBAAkB,KAAK,KAAK,GAAG;AACjC,WAAO,EAAE,QAAQ,OAAO;AAAA,EAC1B;AAEA,QAAM,gBAAgB,MAAM,MAAM,oBAAoB;AACtD,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,UAAU,gBAAgB,CAAC,GAAG,KAAK,KAAK;AAAA,EAC1C;AACF;AAEA,eAAsB,UAAU,OAAe,eAAgD;AAC7F,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,QAAQ,MAAM,kBAAkB,WAAW;AACjD,QAAM,YAAY,MAAM;AAExB,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAEA,QAAM,WAAW,MAAM,WAAW,QAAQ;AAC1C,QAAM,SAAS,kBAAkB,UAAU;AAAA,IACzC,kBAAkB;AAAA,IAClB,gBAAgB;AAAA,IAChB,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,cAAc,KAAK,IAAI;AAC7B,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,SAAS,iBAAiB,KAAK;AAAA;AAAA;AAAA,IAG/B,cAAc;AAAA,IACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,MAAM;AAAA,IAC7C,SAAS,CAAC,MAAM,QAAQ;AAAA,IACxB,eAAe,oBAAoB,WAAW;AAAA,EAChD,CAAC;AAGD,sBAAoB,UAAU,OAAO,MAAM,WAAW;AAEtD,MAAI,CAAC,OAAO,SAAS;AACnB,eAAW,kBAAkB,OAAO,KAAK,EAAE;AAC3C,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,kBAAkB,OAAO,MAAM;AAE9C,MAAI,OAAO,WAAW,QAAQ;AAE5B,QAAI;AACF,YAAM,OAAO,MAAM,aAAa,SAAS;AACzC,WAAK,SAAS,kBAAkB;AAChC,WAAK,qBAAqB;AAC1B,YAAM,aAAa,WAAW,IAAI;AAAA,IACpC,QAAQ;AAAA,IAER;AAEA,iBAAa,+DAA0D;AACvE,WAAO;AAAA,EACT;AAEA,aAAW,oCAA+B;AAC1C,MAAI,OAAO,UAAU;AACnB,YAAQ,IAAI,OAAO,QAAQ;AAAA,EAC7B;AAMA,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,kBAAkB;AAChC,SAAK,qBAAqB,OAAO,YAAY;AAC7C,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;","names":["findingsMatch"]}
@@ -7,7 +7,7 @@ import {
7
7
  getSessionPublisher,
8
8
  isoNow,
9
9
  printRetry
10
- } from "./chunk-JNEHVQH5.js";
10
+ } from "./chunk-6HGZYTAT.js";
11
11
 
12
12
  // src/core/phase-runner.ts
13
13
  import { readFile } from "fs/promises";
@@ -55,4 +55,4 @@ export {
55
55
  runPhaseWithRetry,
56
56
  readFileWithGrace
57
57
  };
58
- //# sourceMappingURL=chunk-EHVKXFFN.js.map
58
+ //# sourceMappingURL=chunk-4M4EWFZZ.js.map
@@ -2,6 +2,13 @@
2
2
 
3
3
  // src/schemas.ts
4
4
  import { z } from "zod";
5
+ var claudeUsageSchema = z.object({
6
+ inputTokens: z.number().optional(),
7
+ outputTokens: z.number().optional(),
8
+ cacheReadTokens: z.number().optional(),
9
+ cacheCreationTokens: z.number().optional(),
10
+ costUsd: z.number().optional()
11
+ });
5
12
  var userStorySchema = z.object({
6
13
  id: z.string(),
7
14
  title: z.string(),
@@ -9,7 +16,9 @@ var userStorySchema = z.object({
9
16
  acceptanceCriteria: z.array(z.string()),
10
17
  priority: z.number().int().positive(),
11
18
  passes: z.boolean(),
12
- notes: z.string()
19
+ notes: z.string(),
20
+ ...claudeUsageSchema.shape,
21
+ durationSeconds: z.number().optional()
13
22
  });
14
23
  var pipelineStateSchema = z.object({
15
24
  analyzeCompleted: z.boolean().optional(),
@@ -89,10 +98,7 @@ var taskPlanSchema = z.object({
89
98
  var headlessResultSchema = z.object({
90
99
  success: z.boolean(),
91
100
  result: z.string(),
92
- cost: z.object({
93
- inputTokens: z.number().optional(),
94
- outputTokens: z.number().optional()
95
- }).nullable(),
101
+ cost: claudeUsageSchema.nullable(),
96
102
  error: z.string().nullable()
97
103
  });
98
104
  var sessionLogEntrySchema = z.object({
@@ -100,20 +106,31 @@ var sessionLogEntrySchema = z.object({
100
106
  level: z.enum(["info", "warn", "error"]),
101
107
  message: z.string()
102
108
  });
109
+ var sessionUsageShape = {
110
+ inputTokens: z.number().nullable().default(null),
111
+ outputTokens: z.number().nullable().default(null),
112
+ cacheReadTokens: z.number().nullable().default(null),
113
+ cacheCreationTokens: z.number().nullable().default(null),
114
+ costUsd: z.number().nullable().default(null)
115
+ };
103
116
  var sessionPhaseSchema = z.object({
104
117
  name: z.string(),
105
118
  status: z.enum(["pending", "running", "completed", "failed"]),
106
119
  startedAt: z.string().nullable(),
107
120
  endedAt: z.string().nullable(),
108
121
  durationSeconds: z.number().nullable(),
109
- error: z.string().nullable()
122
+ error: z.string().nullable(),
123
+ ...sessionUsageShape
110
124
  });
111
125
  var sessionStorySchema = z.object({
112
126
  id: z.string(),
113
127
  title: z.string(),
114
128
  priority: z.number(),
115
129
  passes: z.boolean(),
116
- completedAt: z.string().nullable()
130
+ completedAt: z.string().nullable(),
131
+ // Also introduced with the metrics, hence the same tolerant default.
132
+ durationSeconds: z.number().nullable().default(null),
133
+ ...sessionUsageShape
117
134
  });
118
135
  var sessionSnapshotSchema = z.object({
119
136
  schemaVersion: z.literal(1),
@@ -143,6 +160,21 @@ var sessionSnapshotSchema = z.object({
143
160
  }).nullable(),
144
161
  phases: z.array(sessionPhaseSchema),
145
162
  stories: z.array(sessionStorySchema),
163
+ // The whole aggregate is additive: a snapshot from before it existed parses
164
+ // into the same "nothing reported" object the reducer starts from.
165
+ metrics: z.object({
166
+ totalInputTokens: z.number().nullable().default(null),
167
+ totalOutputTokens: z.number().nullable().default(null),
168
+ totalCacheReadTokens: z.number().nullable().default(null),
169
+ totalCacheCreationTokens: z.number().nullable().default(null),
170
+ totalCostUsd: z.number().nullable().default(null)
171
+ }).default({
172
+ totalInputTokens: null,
173
+ totalOutputTokens: null,
174
+ totalCacheReadTokens: null,
175
+ totalCacheCreationTokens: null,
176
+ totalCostUsd: null
177
+ }),
146
178
  execution: z.object({
147
179
  iteration: z.number(),
148
180
  retries: z.number(),
@@ -186,6 +218,24 @@ import { dirname } from "path";
186
218
  import { stripVTControlCharacters } from "util";
187
219
  var DEFAULT_LOG_LIMIT = 200;
188
220
  var DEFAULT_THROTTLE_MS = 1e3;
221
+ function emptyUsage() {
222
+ return {
223
+ inputTokens: null,
224
+ outputTokens: null,
225
+ cacheReadTokens: null,
226
+ cacheCreationTokens: null,
227
+ costUsd: null
228
+ };
229
+ }
230
+ function emptyMetrics() {
231
+ return {
232
+ totalInputTokens: null,
233
+ totalOutputTokens: null,
234
+ totalCacheReadTokens: null,
235
+ totalCacheCreationTokens: null,
236
+ totalCostUsd: null
237
+ };
238
+ }
189
239
  function createInitialSnapshot() {
190
240
  return {
191
241
  schemaVersion: 1,
@@ -210,6 +260,7 @@ function createInitialSnapshot() {
210
260
  currentActivity: null,
211
261
  phases: [],
212
262
  stories: [],
263
+ metrics: emptyMetrics(),
213
264
  execution: { iteration: 0, retries: 0, correctionCycle: 0, maxCorrectionCycles: null },
214
265
  git: { branch: null, baseBranch: null, commits: [] },
215
266
  pullRequests: [],
@@ -221,6 +272,19 @@ function createInitialSnapshot() {
221
272
  environment: null
222
273
  };
223
274
  }
275
+ function accumulate(current, delta) {
276
+ return delta === void 0 ? current : (current ?? 0) + delta;
277
+ }
278
+ function accumulateUsage(target, event) {
279
+ return {
280
+ ...target,
281
+ inputTokens: accumulate(target.inputTokens, event.inputTokens),
282
+ outputTokens: accumulate(target.outputTokens, event.outputTokens),
283
+ cacheReadTokens: accumulate(target.cacheReadTokens, event.cacheReadTokens),
284
+ cacheCreationTokens: accumulate(target.cacheCreationTokens, event.cacheCreationTokens),
285
+ costUsd: accumulate(target.costUsd, event.costUsd)
286
+ };
287
+ }
224
288
  function secondsBetween(from, to) {
225
289
  if (from === null) return null;
226
290
  const fromMs = Date.parse(from);
@@ -284,7 +348,8 @@ function applyEvent(snapshot, event, options) {
284
348
  startedAt: null,
285
349
  endedAt: null,
286
350
  durationSeconds: null,
287
- error: null
351
+ error: null,
352
+ ...emptyUsage()
288
353
  })),
289
354
  git: { branch: event.branch ?? null, baseBranch: event.baseBranch ?? null, commits: [] },
290
355
  environment: event.environment ?? null
@@ -308,7 +373,8 @@ function applyEvent(snapshot, event, options) {
308
373
  startedAt: event.at,
309
374
  endedAt: null,
310
375
  durationSeconds: null,
311
- error: null
376
+ error: null,
377
+ ...emptyUsage()
312
378
  }
313
379
  ];
314
380
  return {
@@ -363,7 +429,13 @@ function applyEvent(snapshot, event, options) {
363
429
  title: story.title,
364
430
  priority: story.priority,
365
431
  passes: story.passes,
366
- completedAt
432
+ completedAt,
433
+ durationSeconds: before?.durationSeconds ?? null,
434
+ inputTokens: before?.inputTokens ?? null,
435
+ outputTokens: before?.outputTokens ?? null,
436
+ cacheReadTokens: before?.cacheReadTokens ?? null,
437
+ cacheCreationTokens: before?.cacheCreationTokens ?? null,
438
+ costUsd: before?.costUsd ?? null
367
439
  };
368
440
  });
369
441
  return {
@@ -413,6 +485,44 @@ function applyEvent(snapshot, event, options) {
413
485
  maxCorrectionCycles: event.maxCycles
414
486
  }
415
487
  };
488
+ case "metrics:update": {
489
+ if (event.scope === "story") {
490
+ if (event.storyId === void 0) return snapshot;
491
+ if (!snapshot.stories.some((s) => s.id === event.storyId)) return snapshot;
492
+ return {
493
+ ...snapshot,
494
+ stories: snapshot.stories.map(
495
+ (story) => story.id === event.storyId ? {
496
+ ...accumulateUsage(story, event),
497
+ durationSeconds: accumulate(story.durationSeconds, event.durationSeconds)
498
+ } : story
499
+ )
500
+ };
501
+ }
502
+ if (event.phase === void 0) return snapshot;
503
+ if (!snapshot.phases.some((p) => p.name === event.phase)) return snapshot;
504
+ return {
505
+ ...snapshot,
506
+ // durationSeconds is deliberately untouched here: phase:start and
507
+ // phase:end remain the single source of a phase's wall-clock time.
508
+ phases: snapshot.phases.map(
509
+ (phase) => phase.name === event.phase ? accumulateUsage(phase, event) : phase
510
+ ),
511
+ metrics: {
512
+ totalInputTokens: accumulate(snapshot.metrics.totalInputTokens, event.inputTokens),
513
+ totalOutputTokens: accumulate(snapshot.metrics.totalOutputTokens, event.outputTokens),
514
+ totalCacheReadTokens: accumulate(
515
+ snapshot.metrics.totalCacheReadTokens,
516
+ event.cacheReadTokens
517
+ ),
518
+ totalCacheCreationTokens: accumulate(
519
+ snapshot.metrics.totalCacheCreationTokens,
520
+ event.cacheCreationTokens
521
+ ),
522
+ totalCostUsd: accumulate(snapshot.metrics.totalCostUsd, event.costUsd)
523
+ }
524
+ };
525
+ }
416
526
  case "session:end":
417
527
  return {
418
528
  ...snapshot,
@@ -599,6 +709,86 @@ async function writeFileAtomic(path, content) {
599
709
  }
600
710
  }
601
711
 
712
+ // src/core/metrics.ts
713
+ var USAGE_FIELDS = [
714
+ "inputTokens",
715
+ "outputTokens",
716
+ "cacheReadTokens",
717
+ "cacheCreationTokens",
718
+ "costUsd"
719
+ ];
720
+ function num(value) {
721
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
722
+ }
723
+ function isRecord(value) {
724
+ return typeof value === "object" && value !== null;
725
+ }
726
+ function parseUsage(payload) {
727
+ if (!isRecord(payload)) return null;
728
+ const usageRaw = isRecord(payload.usage) ? payload.usage : void 0;
729
+ const usage = {};
730
+ const inputTokens = usageRaw ? num(usageRaw.input_tokens) : void 0;
731
+ const outputTokens = usageRaw ? num(usageRaw.output_tokens) : void 0;
732
+ const cacheReadTokens = usageRaw ? num(usageRaw.cache_read_input_tokens) : void 0;
733
+ const cacheCreationTokens = usageRaw ? num(usageRaw.cache_creation_input_tokens) : void 0;
734
+ const costUsd = num(payload.total_cost_usd);
735
+ const legacyInput = num(payload.num_input_tokens);
736
+ const legacyOutput = num(payload.num_output_tokens);
737
+ const legacyCost = num(payload.cost_usd);
738
+ const resolvedInput = inputTokens ?? legacyInput;
739
+ const resolvedOutput = outputTokens ?? legacyOutput;
740
+ const resolvedCost = costUsd ?? legacyCost;
741
+ if (resolvedInput !== void 0) usage.inputTokens = resolvedInput;
742
+ if (resolvedOutput !== void 0) usage.outputTokens = resolvedOutput;
743
+ if (cacheReadTokens !== void 0) usage.cacheReadTokens = cacheReadTokens;
744
+ if (cacheCreationTokens !== void 0) usage.cacheCreationTokens = cacheCreationTokens;
745
+ if (resolvedCost !== void 0) usage.costUsd = resolvedCost;
746
+ return Object.keys(usage).length > 0 ? usage : null;
747
+ }
748
+ function sumUsage(a, b) {
749
+ const result = {};
750
+ for (const field of USAGE_FIELDS) {
751
+ const left = a?.[field];
752
+ const right = b?.[field];
753
+ if (left === void 0 && right === void 0) continue;
754
+ result[field] = (left ?? 0) + (right ?? 0);
755
+ }
756
+ return result;
757
+ }
758
+ function divideUsage(usage, parts) {
759
+ const result = {};
760
+ if (!usage) return result;
761
+ const divisor = Number.isFinite(parts) && parts > 1 ? parts : 1;
762
+ for (const field of USAGE_FIELDS) {
763
+ const value = usage[field];
764
+ if (value === void 0) continue;
765
+ result[field] = field === "costUsd" ? value / divisor : Math.round(value / divisor);
766
+ }
767
+ return result;
768
+ }
769
+ function compactTokens(value) {
770
+ const abs = Math.abs(value);
771
+ if (abs >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
772
+ if (abs >= 1e3) return `${(value / 1e3).toFixed(1)}k`;
773
+ return String(Math.round(value));
774
+ }
775
+ function formatTokens(usage) {
776
+ if (!usage) return "";
777
+ const segments = [];
778
+ const io = [];
779
+ if (usage.inputTokens !== void 0) io.push(`${compactTokens(usage.inputTokens)} in`);
780
+ if (usage.outputTokens !== void 0) io.push(`${compactTokens(usage.outputTokens)} out`);
781
+ if (io.length > 0) segments.push(io.join(" / "));
782
+ const cache = usage.cacheReadTokens !== void 0 || usage.cacheCreationTokens !== void 0 ? (usage.cacheReadTokens ?? 0) + (usage.cacheCreationTokens ?? 0) : void 0;
783
+ if (cache !== void 0) segments.push(`${compactTokens(cache)} cache`);
784
+ if (usage.costUsd !== void 0) segments.push(`~$${usage.costUsd.toFixed(4)}`);
785
+ return segments.join(" \xB7 ");
786
+ }
787
+ function hasUsageData(usage) {
788
+ if (!usage) return false;
789
+ return USAGE_FIELDS.some((field) => usage[field] !== void 0);
790
+ }
791
+
602
792
  // src/core/state-manager.ts
603
793
  function isoNow() {
604
794
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -650,6 +840,21 @@ function allStoriesPass(plan) {
650
840
  }
651
841
  return plan.userStories.every((story) => story.passes === true);
652
842
  }
843
+ function applyStoryMetrics(plan, storyIds, share, durationSeconds) {
844
+ if (storyIds.length === 0) return plan;
845
+ const targets = new Set(storyIds);
846
+ return {
847
+ ...plan,
848
+ userStories: plan.userStories.map((story) => {
849
+ if (!targets.has(story.id)) return story;
850
+ const updated = { ...story, ...sumUsage(story, share) };
851
+ if (story.durationSeconds !== void 0 || durationSeconds !== void 0) {
852
+ updated.durationSeconds = (story.durationSeconds ?? 0) + (durationSeconds ?? 0);
853
+ }
854
+ return updated;
855
+ })
856
+ };
857
+ }
653
858
  function markIssueInProgress(plan, timestamp) {
654
859
  const ts = timestamp ?? isoNow();
655
860
  return {
@@ -848,6 +1053,7 @@ var ElapsedTimer = class {
848
1053
  this.onTick = onTick;
849
1054
  this.startTime = Date.now();
850
1055
  }
1056
+ onTick;
851
1057
  startTime;
852
1058
  intervalId = null;
853
1059
  cleanup = () => this.stop();
@@ -887,12 +1093,18 @@ export {
887
1093
  setSessionPublisher,
888
1094
  getSessionPublisher,
889
1095
  writeFileAtomic,
1096
+ parseUsage,
1097
+ sumUsage,
1098
+ divideUsage,
1099
+ formatTokens,
1100
+ hasUsageData,
890
1101
  isoNow,
891
1102
  loadTaskPlan,
892
1103
  saveTaskPlan,
893
1104
  initializeState,
894
1105
  hasPendingCorrection,
895
1106
  allStoriesPass,
1107
+ applyStoryMetrics,
896
1108
  markIssueInProgress,
897
1109
  markIssueCompleted,
898
1110
  setLastError,
@@ -919,4 +1131,4 @@ export {
919
1131
  formatDuration,
920
1132
  ElapsedTimer
921
1133
  };
922
- //# sourceMappingURL=chunk-JNEHVQH5.js.map
1134
+ //# sourceMappingURL=chunk-6HGZYTAT.js.map