issue-flow 0.7.1 → 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.
- package/README.md +11 -7
- package/dist/{analyze-R25HDGBJ.js → analyze-PJBY346H.js} +19 -14
- package/dist/analyze-PJBY346H.js.map +1 -0
- package/dist/{chunk-EM43T462.js → chunk-3ISZ7T5M.js} +17 -12
- package/dist/chunk-3ISZ7T5M.js.map +1 -0
- package/dist/{chunk-EHVKXFFN.js → chunk-4M4EWFZZ.js} +2 -2
- package/dist/{chunk-JNEHVQH5.js → chunk-6HGZYTAT.js} +223 -11
- package/dist/chunk-6HGZYTAT.js.map +1 -0
- package/dist/chunk-7HX2RPFY.js +123 -0
- package/dist/chunk-7HX2RPFY.js.map +1 -0
- package/dist/{chunk-RN2FAMUU.js → chunk-7YS3J4A2.js} +71 -22
- package/dist/chunk-7YS3J4A2.js.map +1 -0
- package/dist/{chunk-YGEWIEAI.js → chunk-EBJM6G2D.js} +90 -23
- package/dist/chunk-EBJM6G2D.js.map +1 -0
- package/dist/{chunk-TCOS2KFB.js → chunk-IWBHBDRU.js} +22 -23
- package/dist/chunk-IWBHBDRU.js.map +1 -0
- package/dist/{chunk-23WOEBGN.js → chunk-NVSJYJ72.js} +31 -22
- package/dist/chunk-NVSJYJ72.js.map +1 -0
- package/dist/{chunk-2AVZZAC6.js → chunk-OCXHO7YP.js} +332 -27
- package/dist/chunk-OCXHO7YP.js.map +1 -0
- package/dist/{chunk-HJ7JT777.js → chunk-R7X26MWK.js} +18 -13
- package/dist/chunk-R7X26MWK.js.map +1 -0
- package/dist/{chunk-YLXPLHEL.js → chunk-RKASM5FF.js} +2 -2
- package/dist/{chunk-ASMCLXQ2.js → chunk-SHG2PPN7.js} +20 -15
- package/dist/chunk-SHG2PPN7.js.map +1 -0
- package/dist/{chunk-ZAEBDO3Q.js → chunk-WEXU4R3H.js} +23 -15
- package/dist/chunk-WEXU4R3H.js.map +1 -0
- package/dist/{chunk-WHIBTNYI.js → chunk-YPIIIDTT.js} +3 -3
- package/dist/cli.js +13 -13
- package/dist/{config-SJAQMFWB.js → config-7N7UJP32.js} +3 -3
- package/dist/execute-EZKQKKLO.js +13 -0
- package/dist/{generate-LUK3XTIP.js → generate-KUQNDMMR.js} +19 -10
- package/dist/generate-KUQNDMMR.js.map +1 -0
- package/dist/init-GL6US653.js +9 -0
- package/dist/plan-XNXGWNGY.js +15 -0
- package/dist/pr-XILDNJLM.js +15 -0
- package/dist/pr-review-4WG2RPRP.js +15 -0
- package/dist/prd-BLJ2XBPL.js +15 -0
- package/dist/review-MRA7ZQQG.js +13 -0
- package/dist/{run-SDRQDMSZ.js → run-X62QZCXN.js} +35 -26
- package/dist/run-X62QZCXN.js.map +1 -0
- package/package.json +2 -2
- package/prompts/generate.md +1 -1
- package/prompts/prd.md +1 -1
- package/web/CLAUDE.md +35 -0
- package/web/public/app.css +5 -0
- package/web/public/app.js +80 -7
- package/dist/analyze-R25HDGBJ.js.map +0 -1
- package/dist/chunk-23WOEBGN.js.map +0 -1
- package/dist/chunk-2AVZZAC6.js.map +0 -1
- package/dist/chunk-ASMCLXQ2.js.map +0 -1
- package/dist/chunk-EM43T462.js.map +0 -1
- package/dist/chunk-HJ7JT777.js.map +0 -1
- package/dist/chunk-JNEHVQH5.js.map +0 -1
- package/dist/chunk-RN2FAMUU.js.map +0 -1
- package/dist/chunk-SLHZPB3C.js +0 -50
- package/dist/chunk-SLHZPB3C.js.map +0 -1
- package/dist/chunk-TCOS2KFB.js.map +0 -1
- package/dist/chunk-YGEWIEAI.js.map +0 -1
- package/dist/chunk-ZAEBDO3Q.js.map +0 -1
- package/dist/execute-VZ7SHLP4.js +0 -13
- package/dist/generate-LUK3XTIP.js.map +0 -1
- package/dist/init-5KXCG36P.js +0 -9
- package/dist/plan-R34SAWRK.js +0 -15
- package/dist/pr-P4BDQXUQ.js +0 -15
- package/dist/pr-review-TOSBE6ZS.js +0 -15
- package/dist/prd-RYLULM75.js +0 -15
- package/dist/review-ZSC5ELQA.js +0 -13
- package/dist/run-SDRQDMSZ.js.map +0 -1
- /package/dist/{chunk-EHVKXFFN.js.map → chunk-4M4EWFZZ.js.map} +0 -0
- /package/dist/{chunk-YLXPLHEL.js.map → chunk-RKASM5FF.js.map} +0 -0
- /package/dist/{chunk-WHIBTNYI.js.map → chunk-YPIIIDTT.js.map} +0 -0
- /package/dist/{config-SJAQMFWB.js.map → config-7N7UJP32.js.map} +0 -0
- /package/dist/{execute-VZ7SHLP4.js.map → execute-EZKQKKLO.js.map} +0 -0
- /package/dist/{init-5KXCG36P.js.map → init-GL6US653.js.map} +0 -0
- /package/dist/{plan-R34SAWRK.js.map → plan-XNXGWNGY.js.map} +0 -0
- /package/dist/{pr-P4BDQXUQ.js.map → pr-XILDNJLM.js.map} +0 -0
- /package/dist/{pr-review-TOSBE6ZS.js.map → pr-review-4WG2RPRP.js.map} +0 -0
- /package/dist/{prd-RYLULM75.js.map → prd-BLJ2XBPL.js.map} +0 -0
- /package/dist/{review-ZSC5ELQA.js.map → review-MRA7ZQQG.js.map} +0 -0
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ Verifies that `claude`, `gh` (authenticated), and `git` (inside a repo) are avai
|
|
|
103
103
|
npx issue-flow analyze 42
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
Invokes Claude headlessly to fetch issue data, analyze the codebase, and produce a structured analysis saved to
|
|
106
|
+
Invokes Claude headlessly to fetch issue data, analyze the codebase, and produce a structured analysis saved to `~/.issue-flow/…/issues/42/analysis.md` (see [Pipeline State](#pipeline-state)).
|
|
107
107
|
|
|
108
108
|
### `prd` -- Generate a PRD
|
|
109
109
|
|
|
@@ -111,7 +111,7 @@ Invokes Claude headlessly to fetch issue data, analyze the codebase, and produce
|
|
|
111
111
|
npx issue-flow prd 42
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
Generates a Product Requirements Document from the issue analysis. Reads `
|
|
114
|
+
Generates a Product Requirements Document from the issue analysis. Reads `analysis.md` from the same directory as context if available. Saves to `~/.issue-flow/…/issues/42/prd.md`.
|
|
115
115
|
|
|
116
116
|
### `plan` -- Convert PRD to task plan
|
|
117
117
|
|
|
@@ -119,7 +119,7 @@ Generates a Product Requirements Document from the issue analysis. Reads `issues
|
|
|
119
119
|
npx issue-flow plan 42
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
Converts the PRD into a structured
|
|
122
|
+
Converts the PRD into a structured `~/.issue-flow/…/issues/42/tasks.json` with ordered user stories, acceptance criteria, and pipeline state. Validates the output with zod schemas.
|
|
123
123
|
|
|
124
124
|
### `execute` -- Run the story execution loop
|
|
125
125
|
|
|
@@ -133,7 +133,7 @@ Runs the iterative agent loop. Each iteration is a fresh Claude instance that pi
|
|
|
133
133
|
|
|
134
134
|
| Flag | Description |
|
|
135
135
|
|------|-------------|
|
|
136
|
-
| `--issue N` | Issue number -- reads artifacts from
|
|
136
|
+
| `--issue N` | Issue number -- reads artifacts from `~/.issue-flow/…/issues/N/` |
|
|
137
137
|
| `--max-iterations N` | Stop after N iterations (default: unlimited) |
|
|
138
138
|
| `--retry-limit N` | Retry transient Claude failures up to N consecutive times (default: 10) |
|
|
139
139
|
| `--retry-forever` | Retry transient Claude failures indefinitely |
|
|
@@ -185,22 +185,26 @@ Each setting resolves with the precedence **CLI flag > environment variable > `.
|
|
|
185
185
|
| `--web-log-limit <n>` | `ISSUE_FLOW_WEB_LOG_LIMIT` | `web.logLimit` | `200` |
|
|
186
186
|
| `--web-no-logs` | -- | `web.includeLogs` | logs included |
|
|
187
187
|
|
|
188
|
-
Monitoring never affects the pipeline: with `--web` off the behavior is byte-for-byte identical, a busy port just skips the server with a warning, and killing the server mid-run has no effect on the execution. While enabled, the snapshot served at `/api/status` is also persisted to
|
|
188
|
+
Monitoring never affects the pipeline: with `--web` off the behavior is byte-for-byte identical, a busy port just skips the server with a warning, and killing the server mid-run has no effect on the execution. While enabled, the snapshot served at `/api/status` is also persisted to `~/.issue-flow/…/issues/N/session.json` -- outside your working tree, so there is nothing to add to `.gitignore`.
|
|
189
189
|
|
|
190
190
|
For the full documentation (endpoints, `session.json` format, Tailscale setup), see the [root README](../../README.md#web-monitoring).
|
|
191
191
|
|
|
192
192
|
## Pipeline State
|
|
193
193
|
|
|
194
|
-
Each issue's state is tracked in
|
|
194
|
+
Each issue's state is tracked in a directory of its own under `~/.issue-flow`, keyed by a deterministic project id and the issue identifier:
|
|
195
195
|
|
|
196
196
|
```
|
|
197
|
-
issues/42/
|
|
197
|
+
~/.issue-flow/projects/<project-id>/issues/42/
|
|
198
198
|
analysis.md # Issue analysis
|
|
199
199
|
prd.md # Product requirements
|
|
200
200
|
tasks.json # Task plan with pipeline state and user stories
|
|
201
201
|
progress.txt # Execution log
|
|
202
|
+
session.json # Live session snapshot (web monitoring only)
|
|
203
|
+
pr-review/ # PR review reports and index
|
|
202
204
|
```
|
|
203
205
|
|
|
206
|
+
Nothing is written to your repository. A `<projectRoot>/issues/` tree left by an earlier release is copied here automatically on first use and then treated as read-only -- see the [root README](../../README.md#global-storage) for the full layout, the project id derivation and the migration.
|
|
207
|
+
|
|
204
208
|
The `pipeline` field tracks which phases have completed, enabling resume from any point:
|
|
205
209
|
|
|
206
210
|
```json
|
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
import {
|
|
3
3
|
issuePlaceholders,
|
|
4
4
|
resolveCommandIssue
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-7YS3J4A2.js";
|
|
6
6
|
import {
|
|
7
7
|
runHeadless
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-IWBHBDRU.js";
|
|
9
9
|
import {
|
|
10
10
|
applyPlaceholders,
|
|
11
|
-
loadPrompt
|
|
12
|
-
|
|
11
|
+
loadPrompt,
|
|
12
|
+
publishPhaseMetrics
|
|
13
|
+
} from "./chunk-7HX2RPFY.js";
|
|
13
14
|
import {
|
|
14
|
-
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
resolveIssuePaths
|
|
16
|
+
} from "./chunk-OCXHO7YP.js";
|
|
16
17
|
import {
|
|
17
18
|
getGlobalTimeout,
|
|
18
19
|
loadTaskPlan,
|
|
@@ -20,20 +21,19 @@ import {
|
|
|
20
21
|
printInfo,
|
|
21
22
|
printSuccess,
|
|
22
23
|
saveTaskPlan
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-6HGZYTAT.js";
|
|
24
25
|
|
|
25
26
|
// src/commands/analyze.ts
|
|
26
27
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
27
|
-
import { join } from "path";
|
|
28
28
|
async function runAnalyze(issue, resolvedIssue) {
|
|
29
29
|
const issueNumber = issue.replace(/^#/, "");
|
|
30
|
-
const
|
|
31
|
-
const analysisPath =
|
|
30
|
+
const paths = await resolveIssuePaths(issueNumber);
|
|
31
|
+
const analysisPath = paths.analysisFile;
|
|
32
32
|
const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);
|
|
33
33
|
if (!resolution.ok) {
|
|
34
34
|
return resolution.code;
|
|
35
35
|
}
|
|
36
|
-
await mkdir(issueDir, { recursive: true });
|
|
36
|
+
await mkdir(paths.issueDir, { recursive: true });
|
|
37
37
|
const template = await loadPrompt("analyze");
|
|
38
38
|
const prompt = applyPlaceholders(template, {
|
|
39
39
|
__ISSUE_NUMBER__: issueNumber,
|
|
@@ -42,14 +42,19 @@ async function runAnalyze(issue, resolvedIssue) {
|
|
|
42
42
|
// body that happens to contain a placeholder is left untouched.
|
|
43
43
|
...issuePlaceholders(resolution.resolved)
|
|
44
44
|
});
|
|
45
|
+
const startedAtMs = Date.now();
|
|
45
46
|
const result = await runHeadless({
|
|
46
47
|
prompt,
|
|
47
48
|
maxTurns: 30,
|
|
48
49
|
timeout: getGlobalTimeout() ?? 3e5,
|
|
49
|
-
|
|
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",
|
|
50
53
|
allowedTools: ["Bash", "Read", "Glob", "Grep", "Write"],
|
|
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;
|
|
@@ -64,7 +69,7 @@ async function runAnalyze(issue, resolvedIssue) {
|
|
|
64
69
|
printInfo("Headless did not create analysis file; saving output directly");
|
|
65
70
|
await writeFile(analysisPath, result.result, "utf-8");
|
|
66
71
|
}
|
|
67
|
-
const tasksPath =
|
|
72
|
+
const tasksPath = paths.tasksFile;
|
|
68
73
|
try {
|
|
69
74
|
const plan = await loadTaskPlan(tasksPath);
|
|
70
75
|
plan.pipeline.analyzeCompleted = true;
|
|
@@ -77,4 +82,4 @@ async function runAnalyze(issue, resolvedIssue) {
|
|
|
77
82
|
export {
|
|
78
83
|
runAnalyze
|
|
79
84
|
};
|
|
80
|
-
//# sourceMappingURL=analyze-
|
|
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,27 +2,27 @@
|
|
|
2
2
|
import {
|
|
3
3
|
issuePlaceholders,
|
|
4
4
|
resolveCommandIssue
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-7YS3J4A2.js";
|
|
6
6
|
import {
|
|
7
7
|
runHeadless
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-IWBHBDRU.js";
|
|
9
9
|
import {
|
|
10
10
|
applyPlaceholders,
|
|
11
|
-
loadPrompt
|
|
12
|
-
|
|
11
|
+
loadPrompt,
|
|
12
|
+
publishPhaseMetrics
|
|
13
|
+
} from "./chunk-7HX2RPFY.js";
|
|
13
14
|
import {
|
|
14
|
-
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
resolveIssuePaths
|
|
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-
|
|
23
|
+
} from "./chunk-6HGZYTAT.js";
|
|
23
24
|
|
|
24
25
|
// src/commands/review.ts
|
|
25
|
-
import { join } from "path";
|
|
26
26
|
function parseReviewResult(output) {
|
|
27
27
|
const match = output.match(/<review-result>([\s\S]*?)<\/review-result>/);
|
|
28
28
|
if (!match) {
|
|
@@ -47,8 +47,8 @@ function parseReviewResult(output) {
|
|
|
47
47
|
}
|
|
48
48
|
async function runReview(issue, resolvedIssue) {
|
|
49
49
|
const issueNumber = issue.replace(/^#/, "");
|
|
50
|
-
const
|
|
51
|
-
const tasksPath =
|
|
50
|
+
const paths = await resolveIssuePaths(issueNumber);
|
|
51
|
+
const tasksPath = paths.tasksFile;
|
|
52
52
|
const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);
|
|
53
53
|
if (!resolution.ok) {
|
|
54
54
|
return resolution.code;
|
|
@@ -59,14 +59,19 @@ async function runReview(issue, resolvedIssue) {
|
|
|
59
59
|
__TASKS_PATH__: tasksPath,
|
|
60
60
|
...issuePlaceholders(resolution.resolved)
|
|
61
61
|
});
|
|
62
|
+
const startedAtMs = Date.now();
|
|
62
63
|
const result = await runHeadless({
|
|
63
64
|
prompt,
|
|
64
65
|
maxTurns: 25,
|
|
65
66
|
timeout: getGlobalTimeout() ?? 3e5,
|
|
66
|
-
|
|
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",
|
|
67
70
|
allowedTools: ["Bash", "Read", "Glob", "Grep"],
|
|
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-
|
|
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-
|
|
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-
|
|
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:
|
|
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-
|
|
1134
|
+
//# sourceMappingURL=chunk-6HGZYTAT.js.map
|