issue-flow 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -274
- package/dist/agent-BXRIEKVM.js +238 -0
- package/dist/agent-BXRIEKVM.js.map +1 -0
- package/dist/{analyze-OEET7KGE.js → analyze-WS47UYGN.js} +28 -16
- package/dist/analyze-WS47UYGN.js.map +1 -0
- package/dist/{apply-ZIA3C52S.js → apply-OS62UWRN.js} +15 -7
- package/dist/{apply-ZIA3C52S.js.map → apply-OS62UWRN.js.map} +1 -1
- package/dist/bench-SA3BOME5.js +906 -0
- package/dist/bench-SA3BOME5.js.map +1 -0
- package/dist/chunk-37N2RJW2.js +120 -0
- package/dist/chunk-37N2RJW2.js.map +1 -0
- package/dist/chunk-3XLCNVMD.js +180 -0
- package/dist/chunk-3XLCNVMD.js.map +1 -0
- package/dist/chunk-5TB6KLC2.js +72 -0
- package/dist/chunk-5TB6KLC2.js.map +1 -0
- package/dist/{chunk-MJULIKQU.js → chunk-664Y3NDX.js} +23 -47
- package/dist/chunk-664Y3NDX.js.map +1 -0
- package/dist/chunk-6FDFRNXZ.js +357 -0
- package/dist/chunk-6FDFRNXZ.js.map +1 -0
- package/dist/{chunk-56KS3TFQ.js → chunk-7KD66LIM.js} +115 -160
- package/dist/chunk-7KD66LIM.js.map +1 -0
- package/dist/chunk-7Z4Y3VKM.js +26 -0
- package/dist/chunk-7Z4Y3VKM.js.map +1 -0
- package/dist/chunk-ARFVHC2F.js +1111 -0
- package/dist/chunk-ARFVHC2F.js.map +1 -0
- package/dist/{chunk-NS6R37II.js → chunk-BHJCGWQX.js} +141 -12
- package/dist/chunk-BHJCGWQX.js.map +1 -0
- package/dist/chunk-BQDQTIM7.js +74 -0
- package/dist/chunk-BQDQTIM7.js.map +1 -0
- package/dist/chunk-CNBYCMSU.js +2709 -0
- package/dist/chunk-CNBYCMSU.js.map +1 -0
- package/dist/chunk-CRGIQFUQ.js +83 -0
- package/dist/chunk-CRGIQFUQ.js.map +1 -0
- package/dist/chunk-DIG5XKQQ.js +298 -0
- package/dist/chunk-DIG5XKQQ.js.map +1 -0
- package/dist/chunk-FZDR7CGH.js +71 -0
- package/dist/chunk-FZDR7CGH.js.map +1 -0
- package/dist/chunk-HEF2RMKN.js +413 -0
- package/dist/chunk-HEF2RMKN.js.map +1 -0
- package/dist/{chunk-M5UCXTIT.js → chunk-HJGGNLWI.js} +22 -18
- package/dist/chunk-HJGGNLWI.js.map +1 -0
- package/dist/chunk-HQAHNIAR.js +283 -0
- package/dist/chunk-HQAHNIAR.js.map +1 -0
- package/dist/{chunk-FJP3UQGD.js → chunk-IHIEXUTC.js} +39 -19
- package/dist/chunk-IHIEXUTC.js.map +1 -0
- package/dist/{chunk-Y5ZDJ4PJ.js → chunk-IV5SS4JN.js} +18 -10
- package/dist/chunk-IV5SS4JN.js.map +1 -0
- package/dist/{chunk-BYZDPR7N.js → chunk-IYV6KNVF.js} +146 -13
- package/dist/chunk-IYV6KNVF.js.map +1 -0
- package/dist/{chunk-TSOLWK7Y.js → chunk-J62QSVP6.js} +8 -8
- package/dist/chunk-KQHB7ZT3.js +559 -0
- package/dist/chunk-KQHB7ZT3.js.map +1 -0
- package/dist/chunk-L6HSKTQE.js +77 -0
- package/dist/chunk-L6HSKTQE.js.map +1 -0
- package/dist/{chunk-3QHK4YQA.js → chunk-LZ6GFQAG.js} +334 -117
- package/dist/chunk-LZ6GFQAG.js.map +1 -0
- package/dist/{chunk-LAFNLLQP.js → chunk-MIVHK5YI.js} +5 -5
- package/dist/chunk-NEZZ6KDR.js +1217 -0
- package/dist/chunk-NEZZ6KDR.js.map +1 -0
- package/dist/{chunk-NTMMF5UD.js → chunk-OPNGPUKJ.js} +436 -254
- package/dist/chunk-OPNGPUKJ.js.map +1 -0
- package/dist/chunk-P3LRUNC6.js +16 -0
- package/dist/chunk-P3LRUNC6.js.map +1 -0
- package/dist/{chunk-FADTCQGM.js → chunk-PIVGJM6K.js} +94 -32
- package/dist/chunk-PIVGJM6K.js.map +1 -0
- package/dist/{chunk-73ATPIYX.js → chunk-PV4OX5TC.js} +36 -71
- package/dist/chunk-PV4OX5TC.js.map +1 -0
- package/dist/chunk-QCXPNSEL.js +141 -0
- package/dist/chunk-QCXPNSEL.js.map +1 -0
- package/dist/{chunk-XQUSIEKA.js → chunk-QXDNBYQF.js} +52 -14
- package/dist/chunk-QXDNBYQF.js.map +1 -0
- package/dist/chunk-SNTTOQAD.js +30 -0
- package/dist/chunk-SNTTOQAD.js.map +1 -0
- package/dist/{chunk-QCPGHBSZ.js → chunk-TM67EHSV.js} +63 -21
- package/dist/chunk-TM67EHSV.js.map +1 -0
- package/dist/chunk-TVN2JVOF.js +77 -0
- package/dist/chunk-TVN2JVOF.js.map +1 -0
- package/dist/chunk-TX5WIVYP.js +129 -0
- package/dist/chunk-TX5WIVYP.js.map +1 -0
- package/dist/{chunk-A4SLHOW7.js → chunk-UJ7XWK7J.js} +24 -18
- package/dist/chunk-UJ7XWK7J.js.map +1 -0
- package/dist/{chunk-EZATO3DW.js → chunk-UOCPC2Y2.js} +594 -17
- package/dist/chunk-UOCPC2Y2.js.map +1 -0
- package/dist/chunk-XEYH6ZZI.js +289 -0
- package/dist/chunk-XEYH6ZZI.js.map +1 -0
- package/dist/cli.js +264 -44
- package/dist/cli.js.map +1 -1
- package/dist/{config-PRJGWW7J.js → config-MEM2IWWN.js} +28 -5
- package/dist/contract-JH2WBIO6.js +8 -0
- package/dist/conventions-WV4NDXGH.js +143 -0
- package/dist/conventions-WV4NDXGH.js.map +1 -0
- package/dist/execute-NITIHHCK.js +31 -0
- package/dist/{generate-EGB4KV2B.js → generate-MVQI7A45.js} +34 -20
- package/dist/{generate-EGB4KV2B.js.map → generate-MVQI7A45.js.map} +1 -1
- package/dist/git-YRVRUN3C.js +31 -0
- package/dist/init-OKWEC3EV.js +21 -0
- package/dist/{operations-BYT626KQ.js → operations-LU6JA3BX.js} +24 -15
- package/dist/{operations-BYT626KQ.js.map → operations-LU6JA3BX.js.map} +1 -1
- package/dist/permissions-7NMDFU27.js +18 -0
- package/dist/plan-LEJJF3DE.js +31 -0
- package/dist/{policy-6S4GFC76.js → policy-JSYEK5B2.js} +17 -6
- package/dist/policy-JSYEK5B2.js.map +1 -0
- package/dist/pr-HPVUZ7S3.js +36 -0
- package/dist/pr-review-R2QZBT3F.js +29 -0
- package/dist/prd-5O6GYCC6.js +30 -0
- package/dist/ps-TNE44R5V.js +90 -0
- package/dist/ps-TNE44R5V.js.map +1 -0
- package/dist/recorder-XGF55WXI.js +31 -0
- package/dist/recorder-XGF55WXI.js.map +1 -0
- package/dist/registry-LGPE53QD.js +15 -0
- package/dist/registry-LGPE53QD.js.map +1 -0
- package/dist/{resume-PPCNQVVV.js → resume-QMEPMSZW.js} +51 -34
- package/dist/{resume-PPCNQVVV.js.map → resume-QMEPMSZW.js.map} +1 -1
- package/dist/review-YKWD6SNN.js +32 -0
- package/dist/review-YKWD6SNN.js.map +1 -0
- package/dist/routing-2ZUMZPJB.js +91 -0
- package/dist/routing-2ZUMZPJB.js.map +1 -0
- package/dist/run-YU4TR62R.js +50 -0
- package/dist/run-YU4TR62R.js.map +1 -0
- package/dist/{state-manager-2EDVDBAR.js → state-manager-X3PT3FGT.js} +4 -2
- package/dist/state-manager-X3PT3FGT.js.map +1 -0
- package/dist/usage-VCFLDA5Y.js +174 -0
- package/dist/usage-VCFLDA5Y.js.map +1 -0
- package/dist/{web-EF5WIIGG.js → web-XVHLX62B.js} +41 -30
- package/dist/web-XVHLX62B.js.map +1 -0
- package/package.json +6 -2
- package/prompts/execute.md +7 -8
- package/prompts/plan.md +4 -5
- package/prompts/pr.md +2 -2
- package/web/AGENTS.md +3 -3
- package/web/public/app.css +38 -0
- package/web/public/app.js +121 -11
- package/web/public/index.html +24 -0
- package/dist/analyze-OEET7KGE.js.map +0 -1
- package/dist/chunk-3QHK4YQA.js.map +0 -1
- package/dist/chunk-42PJFMAR.js +0 -605
- package/dist/chunk-42PJFMAR.js.map +0 -1
- package/dist/chunk-4JSOYE4G.js +0 -368
- package/dist/chunk-4JSOYE4G.js.map +0 -1
- package/dist/chunk-56KS3TFQ.js.map +0 -1
- package/dist/chunk-73ATPIYX.js.map +0 -1
- package/dist/chunk-A4SLHOW7.js.map +0 -1
- package/dist/chunk-BAUPQXPE.js +0 -173
- package/dist/chunk-BAUPQXPE.js.map +0 -1
- package/dist/chunk-BYZDPR7N.js.map +0 -1
- package/dist/chunk-CD5ULRVC.js +0 -615
- package/dist/chunk-CD5ULRVC.js.map +0 -1
- package/dist/chunk-DIDUVL6O.js +0 -1365
- package/dist/chunk-DIDUVL6O.js.map +0 -1
- package/dist/chunk-EZATO3DW.js.map +0 -1
- package/dist/chunk-FADTCQGM.js.map +0 -1
- package/dist/chunk-FJP3UQGD.js.map +0 -1
- package/dist/chunk-LWRSMTTZ.js +0 -18
- package/dist/chunk-LWRSMTTZ.js.map +0 -1
- package/dist/chunk-M5UCXTIT.js.map +0 -1
- package/dist/chunk-M66SJHQQ.js +0 -206
- package/dist/chunk-M66SJHQQ.js.map +0 -1
- package/dist/chunk-MJULIKQU.js.map +0 -1
- package/dist/chunk-NS6R37II.js.map +0 -1
- package/dist/chunk-NTMMF5UD.js.map +0 -1
- package/dist/chunk-QCPGHBSZ.js.map +0 -1
- package/dist/chunk-XQUSIEKA.js.map +0 -1
- package/dist/chunk-Y5ZDJ4PJ.js.map +0 -1
- package/dist/execute-XBNVL753.js +0 -18
- package/dist/init-AJHCXJBP.js +0 -10
- package/dist/plan-QOZ3RIO6.js +0 -21
- package/dist/policy-6S4GFC76.js.map +0 -1
- package/dist/pr-EBBHD2ZX.js +0 -26
- package/dist/pr-review-VVMW5PO4.js +0 -20
- package/dist/prd-JQSZF7PR.js +0 -21
- package/dist/review-3F6MARWH.js +0 -19
- package/dist/run-RXE5GDQO.js +0 -36
- package/dist/web-EF5WIIGG.js.map +0 -1
- /package/dist/{chunk-TSOLWK7Y.js.map → chunk-J62QSVP6.js.map} +0 -0
- /package/dist/{chunk-LAFNLLQP.js.map → chunk-MIVHK5YI.js.map} +0 -0
- /package/dist/{config-PRJGWW7J.js.map → config-MEM2IWWN.js.map} +0 -0
- /package/dist/{execute-XBNVL753.js.map → contract-JH2WBIO6.js.map} +0 -0
- /package/dist/{init-AJHCXJBP.js.map → execute-NITIHHCK.js.map} +0 -0
- /package/dist/{plan-QOZ3RIO6.js.map → git-YRVRUN3C.js.map} +0 -0
- /package/dist/{pr-EBBHD2ZX.js.map → init-OKWEC3EV.js.map} +0 -0
- /package/dist/{pr-review-VVMW5PO4.js.map → permissions-7NMDFU27.js.map} +0 -0
- /package/dist/{prd-JQSZF7PR.js.map → plan-LEJJF3DE.js.map} +0 -0
- /package/dist/{review-3F6MARWH.js.map → pr-HPVUZ7S3.js.map} +0 -0
- /package/dist/{run-RXE5GDQO.js.map → pr-review-R2QZBT3F.js.map} +0 -0
- /package/dist/{state-manager-2EDVDBAR.js.map → prd-5O6GYCC6.js.map} +0 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
isProcessAlive,
|
|
4
|
+
taskPlanSchema
|
|
5
|
+
} from "./chunk-NEZZ6KDR.js";
|
|
6
|
+
import {
|
|
7
|
+
writeFileAtomic
|
|
8
|
+
} from "./chunk-7Z4Y3VKM.js";
|
|
9
|
+
|
|
10
|
+
// src/core/state-manager.ts
|
|
11
|
+
import { readFile } from "fs/promises";
|
|
12
|
+
import { ZodError } from "zod";
|
|
13
|
+
|
|
14
|
+
// src/telemetry/reconcile.ts
|
|
15
|
+
import { hostname } from "os";
|
|
16
|
+
function nowIso() {
|
|
17
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
18
|
+
}
|
|
19
|
+
function reconcileInterruptedExecutions(plan) {
|
|
20
|
+
if (plan.executions === void 0 || plan.executions.length === 0) return plan;
|
|
21
|
+
let changed = false;
|
|
22
|
+
const executions = plan.executions.map((record) => {
|
|
23
|
+
if (record.status !== "running" || record.finishedAt !== null) return record;
|
|
24
|
+
const owner = record.owner;
|
|
25
|
+
if (owner === void 0 || owner === null) {
|
|
26
|
+
changed = true;
|
|
27
|
+
return { ...record, status: "interrupted", finishedAt: nowIso(), owner: null };
|
|
28
|
+
}
|
|
29
|
+
if (owner.host !== hostname()) return record;
|
|
30
|
+
if (isProcessAlive(owner.pid)) return record;
|
|
31
|
+
changed = true;
|
|
32
|
+
return { ...record, status: "interrupted", finishedAt: nowIso(), owner: null };
|
|
33
|
+
});
|
|
34
|
+
return changed ? { ...plan, executions } : plan;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/core/metrics.ts
|
|
38
|
+
var USAGE_FIELDS = [
|
|
39
|
+
"inputTokens",
|
|
40
|
+
"outputTokens",
|
|
41
|
+
"cacheReadTokens",
|
|
42
|
+
"cacheCreationTokens",
|
|
43
|
+
"costUsd"
|
|
44
|
+
];
|
|
45
|
+
function num(value) {
|
|
46
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
47
|
+
}
|
|
48
|
+
function isRecord(value) {
|
|
49
|
+
return typeof value === "object" && value !== null;
|
|
50
|
+
}
|
|
51
|
+
function parseCodexUsage(payload) {
|
|
52
|
+
if (!isRecord(payload)) return null;
|
|
53
|
+
const usage = {};
|
|
54
|
+
const inputTokens = num(payload.input_tokens);
|
|
55
|
+
const cachedInput = num(payload.cached_input_tokens);
|
|
56
|
+
const cacheWrite = num(payload.cache_write_input_tokens);
|
|
57
|
+
const outputTokens = num(payload.output_tokens);
|
|
58
|
+
if (inputTokens !== void 0) {
|
|
59
|
+
usage.inputTokens = cachedInput !== void 0 ? Math.max(0, inputTokens - cachedInput) : inputTokens;
|
|
60
|
+
}
|
|
61
|
+
if (outputTokens !== void 0) usage.outputTokens = outputTokens;
|
|
62
|
+
if (cachedInput !== void 0) usage.cacheReadTokens = cachedInput;
|
|
63
|
+
if (cacheWrite !== void 0) usage.cacheCreationTokens = cacheWrite;
|
|
64
|
+
return Object.keys(usage).length > 0 ? usage : null;
|
|
65
|
+
}
|
|
66
|
+
function parseUsage(payload) {
|
|
67
|
+
if (!isRecord(payload)) return null;
|
|
68
|
+
const usageRaw = isRecord(payload.usage) ? payload.usage : void 0;
|
|
69
|
+
const usage = {};
|
|
70
|
+
const inputTokens = usageRaw ? num(usageRaw.input_tokens) : void 0;
|
|
71
|
+
const outputTokens = usageRaw ? num(usageRaw.output_tokens) : void 0;
|
|
72
|
+
const cacheReadTokens = usageRaw ? num(usageRaw.cache_read_input_tokens) : void 0;
|
|
73
|
+
const cacheCreationTokens = usageRaw ? num(usageRaw.cache_creation_input_tokens) : void 0;
|
|
74
|
+
const costUsd = num(payload.total_cost_usd);
|
|
75
|
+
const legacyInput = num(payload.num_input_tokens);
|
|
76
|
+
const legacyOutput = num(payload.num_output_tokens);
|
|
77
|
+
const legacyCost = num(payload.cost_usd);
|
|
78
|
+
const resolvedInput = inputTokens ?? legacyInput;
|
|
79
|
+
const resolvedOutput = outputTokens ?? legacyOutput;
|
|
80
|
+
const resolvedCost = costUsd ?? legacyCost;
|
|
81
|
+
if (resolvedInput !== void 0) usage.inputTokens = resolvedInput;
|
|
82
|
+
if (resolvedOutput !== void 0) usage.outputTokens = resolvedOutput;
|
|
83
|
+
if (cacheReadTokens !== void 0) usage.cacheReadTokens = cacheReadTokens;
|
|
84
|
+
if (cacheCreationTokens !== void 0) usage.cacheCreationTokens = cacheCreationTokens;
|
|
85
|
+
if (resolvedCost !== void 0) usage.costUsd = resolvedCost;
|
|
86
|
+
const cliDurationMs = num(payload.duration_ms);
|
|
87
|
+
const apiDurationMs = num(payload.duration_api_ms);
|
|
88
|
+
const ttftMs = num(payload.ttft_ms) ?? num(payload.time_to_first_token_ms);
|
|
89
|
+
const numTurns = num(payload.num_turns);
|
|
90
|
+
if (cliDurationMs !== void 0) usage.cliDurationMs = cliDurationMs;
|
|
91
|
+
if (apiDurationMs !== void 0) usage.apiDurationMs = apiDurationMs;
|
|
92
|
+
if (ttftMs !== void 0) usage.ttftMs = ttftMs;
|
|
93
|
+
if (numTurns !== void 0) usage.numTurns = numTurns;
|
|
94
|
+
return Object.keys(usage).length > 0 ? usage : null;
|
|
95
|
+
}
|
|
96
|
+
function sumUsage(a, b) {
|
|
97
|
+
const result = {};
|
|
98
|
+
for (const field of USAGE_FIELDS) {
|
|
99
|
+
const left = a?.[field];
|
|
100
|
+
const right = b?.[field];
|
|
101
|
+
if (left === void 0 && right === void 0) continue;
|
|
102
|
+
result[field] = (left ?? 0) + (right ?? 0);
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
function divideUsage(usage, parts) {
|
|
107
|
+
const result = {};
|
|
108
|
+
if (!usage) return result;
|
|
109
|
+
const divisor = Number.isFinite(parts) && parts > 1 ? parts : 1;
|
|
110
|
+
for (const field of USAGE_FIELDS) {
|
|
111
|
+
const value = usage[field];
|
|
112
|
+
if (value === void 0) continue;
|
|
113
|
+
result[field] = field === "costUsd" ? value / divisor : Math.round(value / divisor);
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
function compactTokens(value) {
|
|
118
|
+
const abs = Math.abs(value);
|
|
119
|
+
if (abs >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
120
|
+
if (abs >= 1e3) return `${(value / 1e3).toFixed(1)}k`;
|
|
121
|
+
return String(Math.round(value));
|
|
122
|
+
}
|
|
123
|
+
function formatTokens(usage) {
|
|
124
|
+
if (!usage) return "";
|
|
125
|
+
const segments = [];
|
|
126
|
+
const io = [];
|
|
127
|
+
if (usage.inputTokens !== void 0) io.push(`${compactTokens(usage.inputTokens)} in`);
|
|
128
|
+
if (usage.outputTokens !== void 0) io.push(`${compactTokens(usage.outputTokens)} out`);
|
|
129
|
+
if (io.length > 0) segments.push(io.join(" / "));
|
|
130
|
+
const cache = usage.cacheReadTokens !== void 0 || usage.cacheCreationTokens !== void 0 ? (usage.cacheReadTokens ?? 0) + (usage.cacheCreationTokens ?? 0) : void 0;
|
|
131
|
+
if (cache !== void 0) segments.push(`${compactTokens(cache)} cache`);
|
|
132
|
+
if (usage.costUsd !== void 0) segments.push(`~$${usage.costUsd.toFixed(4)}`);
|
|
133
|
+
return segments.join(" \xB7 ");
|
|
134
|
+
}
|
|
135
|
+
function hasUsageData(usage) {
|
|
136
|
+
if (!usage) return false;
|
|
137
|
+
return USAGE_FIELDS.some((field) => usage[field] !== void 0);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/core/state-manager.ts
|
|
141
|
+
function isoNow() {
|
|
142
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
143
|
+
}
|
|
144
|
+
async function loadTaskPlan(path) {
|
|
145
|
+
const content = await readFile(path, "utf-8");
|
|
146
|
+
const raw = JSON.parse(content);
|
|
147
|
+
try {
|
|
148
|
+
const parsed = taskPlanSchema.parse(raw);
|
|
149
|
+
const reconciled = reconcileInterruptedExecutions(parsed);
|
|
150
|
+
if (reconciled !== parsed) {
|
|
151
|
+
try {
|
|
152
|
+
await saveTaskPlan(path, reconciled);
|
|
153
|
+
} catch {
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return reconciled;
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if (err instanceof ZodError) {
|
|
159
|
+
const issues = err.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
160
|
+
throw new Error(`Invalid tasks.json at ${path}:
|
|
161
|
+
${issues}`);
|
|
162
|
+
}
|
|
163
|
+
throw err;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function saveTaskPlan(path, plan) {
|
|
167
|
+
await writeFileAtomic(path, `${JSON.stringify(plan, null, 2)}
|
|
168
|
+
`);
|
|
169
|
+
}
|
|
170
|
+
function initializeState(plan) {
|
|
171
|
+
const defaultPipeline = {
|
|
172
|
+
prdCompleted: false,
|
|
173
|
+
jsonCompleted: false,
|
|
174
|
+
executionCompleted: false,
|
|
175
|
+
reviewCompleted: false,
|
|
176
|
+
prCreated: false
|
|
177
|
+
};
|
|
178
|
+
return {
|
|
179
|
+
...plan,
|
|
180
|
+
issueStatus: plan.issueStatus ?? "pending",
|
|
181
|
+
completedAt: plan.completedAt ?? null,
|
|
182
|
+
lastAttemptAt: plan.lastAttemptAt ?? null,
|
|
183
|
+
lastError: plan.lastError ?? null,
|
|
184
|
+
correctionCycle: plan.correctionCycle ?? 0,
|
|
185
|
+
maxCorrectionCycles: plan.maxCorrectionCycles ?? 3,
|
|
186
|
+
lastReviewFindings: plan.lastReviewFindings ?? null,
|
|
187
|
+
pipeline: plan.pipeline ? { ...defaultPipeline, ...plan.pipeline } : defaultPipeline
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function hasPendingCorrection(plan) {
|
|
191
|
+
return Boolean(plan.lastReviewFindings);
|
|
192
|
+
}
|
|
193
|
+
function allStoriesPass(plan) {
|
|
194
|
+
if (plan.userStories.length === 0) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
return plan.userStories.every((story) => story.passes === true);
|
|
198
|
+
}
|
|
199
|
+
function selectActiveStory(stories) {
|
|
200
|
+
return [...stories].filter((story) => !story.passes).sort((a, b) => a.priority - b.priority)[0];
|
|
201
|
+
}
|
|
202
|
+
function markStoryPassing(plan, storyId) {
|
|
203
|
+
return {
|
|
204
|
+
...plan,
|
|
205
|
+
userStories: plan.userStories.map(
|
|
206
|
+
(story) => story.id === storyId ? { ...story, passes: true } : story
|
|
207
|
+
)
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function applyStoryMetrics(plan, storyIds, share, durationSeconds) {
|
|
211
|
+
if (storyIds.length === 0) return plan;
|
|
212
|
+
const targets = new Set(storyIds);
|
|
213
|
+
return {
|
|
214
|
+
...plan,
|
|
215
|
+
userStories: plan.userStories.map((story) => {
|
|
216
|
+
if (!targets.has(story.id)) return story;
|
|
217
|
+
const updated = { ...story, ...sumUsage(story, share) };
|
|
218
|
+
if (story.durationSeconds !== void 0 || durationSeconds !== void 0) {
|
|
219
|
+
updated.durationSeconds = (story.durationSeconds ?? 0) + (durationSeconds ?? 0);
|
|
220
|
+
}
|
|
221
|
+
return updated;
|
|
222
|
+
})
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function markIssueInProgress(plan, timestamp) {
|
|
226
|
+
const ts = timestamp ?? isoNow();
|
|
227
|
+
return {
|
|
228
|
+
...plan,
|
|
229
|
+
issueStatus: "in_progress",
|
|
230
|
+
completedAt: null,
|
|
231
|
+
lastAttemptAt: ts
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function markIssueCompleted(plan) {
|
|
235
|
+
const ts = isoNow();
|
|
236
|
+
return {
|
|
237
|
+
...plan,
|
|
238
|
+
issueStatus: "completed",
|
|
239
|
+
completedAt: ts,
|
|
240
|
+
lastAttemptAt: ts,
|
|
241
|
+
lastError: null,
|
|
242
|
+
pipeline: plan.pipeline ? { ...plan.pipeline, executionCompleted: true } : plan.pipeline
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function setLastError(plan, category, message) {
|
|
246
|
+
const ts = isoNow();
|
|
247
|
+
const error = {
|
|
248
|
+
category,
|
|
249
|
+
message,
|
|
250
|
+
at: ts
|
|
251
|
+
};
|
|
252
|
+
return {
|
|
253
|
+
...plan,
|
|
254
|
+
lastAttemptAt: ts,
|
|
255
|
+
lastError: error
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function clearLastError(plan, attemptStartedAt) {
|
|
259
|
+
const ts = isoNow();
|
|
260
|
+
if (plan.lastError && plan.lastError.at > attemptStartedAt) {
|
|
261
|
+
return {
|
|
262
|
+
...plan,
|
|
263
|
+
lastAttemptAt: ts
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
...plan,
|
|
268
|
+
lastAttemptAt: ts,
|
|
269
|
+
lastError: null
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function trimErrorMessage(message) {
|
|
273
|
+
return message.split("\n").filter((line) => line.trim().length > 0).slice(0, 8).join("\n");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export {
|
|
277
|
+
parseCodexUsage,
|
|
278
|
+
parseUsage,
|
|
279
|
+
sumUsage,
|
|
280
|
+
divideUsage,
|
|
281
|
+
formatTokens,
|
|
282
|
+
hasUsageData,
|
|
283
|
+
isoNow,
|
|
284
|
+
loadTaskPlan,
|
|
285
|
+
saveTaskPlan,
|
|
286
|
+
initializeState,
|
|
287
|
+
hasPendingCorrection,
|
|
288
|
+
allStoriesPass,
|
|
289
|
+
selectActiveStory,
|
|
290
|
+
markStoryPassing,
|
|
291
|
+
applyStoryMetrics,
|
|
292
|
+
markIssueInProgress,
|
|
293
|
+
markIssueCompleted,
|
|
294
|
+
setLastError,
|
|
295
|
+
clearLastError,
|
|
296
|
+
trimErrorMessage
|
|
297
|
+
};
|
|
298
|
+
//# sourceMappingURL=chunk-DIG5XKQQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/state-manager.ts","../src/telemetry/reconcile.ts","../src/core/metrics.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { ZodError } from 'zod';\nimport { taskPlanSchema } from '../schemas.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';\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 content = await readFile(path, 'utf-8');\n const raw = JSON.parse(content);\n\n try {\n const parsed = taskPlanSchema.parse(raw) as TaskPlan;\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 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 = {\n prdCompleted: false,\n jsonCompleted: false,\n executionCompleted: false,\n reviewCompleted: false,\n prCreated: false,\n };\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 ?? 3,\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`. This is the exact same rule\n * `prompts/execute.md` gives the agent (\"pick the highest priority user\n * story where `passes: false`\") — 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 [...stories].filter((story) => !story.passes).sort((a, b) => a.priority - b.priority)[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"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
IssueResolutionError,
|
|
4
|
+
resolveIssue
|
|
5
|
+
} from "./chunk-PV4OX5TC.js";
|
|
6
|
+
import {
|
|
7
|
+
printError,
|
|
8
|
+
printWarning
|
|
9
|
+
} from "./chunk-IYV6KNVF.js";
|
|
10
|
+
import {
|
|
11
|
+
actionOf,
|
|
12
|
+
classify,
|
|
13
|
+
requiresHumanAction
|
|
14
|
+
} from "./chunk-XEYH6ZZI.js";
|
|
15
|
+
|
|
16
|
+
// src/issues/context.ts
|
|
17
|
+
function localIssueRef(id) {
|
|
18
|
+
return `issues/${id}/issue.md`;
|
|
19
|
+
}
|
|
20
|
+
function issueReference(issue) {
|
|
21
|
+
return issue.remoteRef ?? localIssueRef(issue.id);
|
|
22
|
+
}
|
|
23
|
+
var NO_LABELS = "(none)";
|
|
24
|
+
function issuePlaceholders(resolved) {
|
|
25
|
+
const { issue } = resolved;
|
|
26
|
+
return {
|
|
27
|
+
__ISSUE_TITLE__: issue.title,
|
|
28
|
+
__ISSUE_BODY__: issue.body,
|
|
29
|
+
__ISSUE_LABELS__: issue.labels.length > 0 ? issue.labels.join(", ") : NO_LABELS,
|
|
30
|
+
__ISSUE_SOURCE__: resolved.source,
|
|
31
|
+
__ISSUE_URL__: issueReference(issue)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async function resolveCommandIssue(id, preResolved, options) {
|
|
35
|
+
if (preResolved !== void 0) {
|
|
36
|
+
return { ok: true, resolved: preResolved };
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return { ok: true, resolved: await resolveIssue(id, options) };
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (err instanceof IssueResolutionError) {
|
|
42
|
+
printError(err.message);
|
|
43
|
+
printAction(err.action);
|
|
44
|
+
return { ok: false, code: err.exitCode };
|
|
45
|
+
}
|
|
46
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
47
|
+
printError(`Could not resolve issue '${id}': ${message}`);
|
|
48
|
+
printAction(actionOf(err) ?? requiredAction(message));
|
|
49
|
+
return { ok: false, code: 1 };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function requiredAction(message) {
|
|
53
|
+
const failure = classify({ source: "github", stderr: message });
|
|
54
|
+
if (!requiresHumanAction(failure.kind)) return null;
|
|
55
|
+
if (failure.kind === "authentication") {
|
|
56
|
+
return "Run `gh auth login` to authenticate the GitHub CLI";
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function printAction(action) {
|
|
61
|
+
if (action !== null && action !== void 0 && action !== "") {
|
|
62
|
+
printWarning(`Action required: ${action}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export {
|
|
67
|
+
localIssueRef,
|
|
68
|
+
issuePlaceholders,
|
|
69
|
+
resolveCommandIssue
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=chunk-FZDR7CGH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/issues/context.ts"],"sourcesContent":["import { actionOf, classify, requiresHumanAction } from '../resilience/errors.js';\nimport { printError, printWarning } from '../ui/logger.js';\nimport { IssueResolutionError, type ResolveIssueOptions, resolveIssue } from './resolver.js';\nimport type { Issue, ResolvedIssue } from './types.js';\n\n/**\n * Bridge between the resolved Issue and the phase prompts.\n *\n * Commands never fetch the Issue themselves: they resolve it once through\n * `resolveIssue` and hand the content to the template, so every phase sees the\n * exact same title/body regardless of where the demand came from.\n */\n\n/** Path of the markdown file a local Issue lives in. */\nexport function localIssueRef(id: string): string {\n return `issues/${id}/issue.md`;\n}\n\n/**\n * Reference a human (or an agent) can follow to read the Issue: the remote URL\n * when there is one, the local file otherwise.\n */\nexport function issueReference(issue: Issue): string {\n return issue.remoteRef ?? localIssueRef(issue.id);\n}\n\n/** Shown instead of an empty list so the prompt never renders a dangling label. */\nconst NO_LABELS = '(none)';\n\n/**\n * Placeholder values every phase template consumes, replacing the former\n * `gh issue view` instruction.\n */\nexport function issuePlaceholders(resolved: ResolvedIssue): Record<string, string> {\n const { issue } = resolved;\n return {\n __ISSUE_TITLE__: issue.title,\n __ISSUE_BODY__: issue.body,\n __ISSUE_LABELS__: issue.labels.length > 0 ? issue.labels.join(', ') : NO_LABELS,\n __ISSUE_SOURCE__: resolved.source,\n __ISSUE_URL__: issueReference(issue),\n };\n}\n\nexport type CommandIssue = { ok: true; resolved: ResolvedIssue } | { ok: false; code: number };\n\n/**\n * Resolve the Issue a command works on, turning a resolution failure into the\n * exit code the command returns.\n *\n * `preResolved` short-circuits the lookup: the pipeline decides the origin once\n * and passes the decision down, so a phase never re-queries the providers (and\n * never asks the user twice about the same divergence).\n *\n * `options` forwards resolver options — `run.ts` passes the configuration it\n * already loaded so `.issue-flow.json` is read (and warned about) only once.\n */\nexport async function resolveCommandIssue(\n id: string,\n preResolved?: ResolvedIssue,\n options?: ResolveIssueOptions,\n): Promise<CommandIssue> {\n if (preResolved !== undefined) {\n return { ok: true, resolved: preResolved };\n }\n\n try {\n return { ok: true, resolved: await resolveIssue(id, options) };\n } catch (err) {\n if (err instanceof IssueResolutionError) {\n printError(err.message);\n printAction(err.action);\n return { ok: false, code: err.exitCode };\n }\n const message = err instanceof Error ? err.message : String(err);\n printError(`Could not resolve issue '${id}': ${message}`);\n printAction(actionOf(err) ?? requiredAction(message));\n return { ok: false, code: 1 };\n }\n}\n\n/**\n * What a human has to do about a failure that no amount of waiting fixes.\n *\n * The retry budget was already spent inside the providers — every `gh` call\n * goes through `run()` with the `network`/`rate_limit` policy — so by the time\n * a failure reaches here it is final. What is left to decide is whether it is\n * final *because a person has to act*, and that is what turns an opaque exit\n * code into an instruction.\n */\nfunction requiredAction(message: string): string | null {\n const failure = classify({ source: 'github', stderr: message });\n if (!requiresHumanAction(failure.kind)) return null;\n if (failure.kind === 'authentication') {\n return 'Run `gh auth login` to authenticate the GitHub CLI';\n }\n return null;\n}\n\nfunction printAction(action: string | null | undefined): void {\n if (action !== null && action !== undefined && action !== '') {\n printWarning(`Action required: ${action}`);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAcO,SAAS,cAAc,IAAoB;AAChD,SAAO,UAAU,EAAE;AACrB;AAMO,SAAS,eAAe,OAAsB;AACnD,SAAO,MAAM,aAAa,cAAc,MAAM,EAAE;AAClD;AAGA,IAAM,YAAY;AAMX,SAAS,kBAAkB,UAAiD;AACjF,QAAM,EAAE,MAAM,IAAI;AAClB,SAAO;AAAA,IACL,iBAAiB,MAAM;AAAA,IACvB,gBAAgB,MAAM;AAAA,IACtB,kBAAkB,MAAM,OAAO,SAAS,IAAI,MAAM,OAAO,KAAK,IAAI,IAAI;AAAA,IACtE,kBAAkB,SAAS;AAAA,IAC3B,eAAe,eAAe,KAAK;AAAA,EACrC;AACF;AAeA,eAAsB,oBACpB,IACA,aACA,SACuB;AACvB,MAAI,gBAAgB,QAAW;AAC7B,WAAO,EAAE,IAAI,MAAM,UAAU,YAAY;AAAA,EAC3C;AAEA,MAAI;AACF,WAAO,EAAE,IAAI,MAAM,UAAU,MAAM,aAAa,IAAI,OAAO,EAAE;AAAA,EAC/D,SAAS,KAAK;AACZ,QAAI,eAAe,sBAAsB;AACvC,iBAAW,IAAI,OAAO;AACtB,kBAAY,IAAI,MAAM;AACtB,aAAO,EAAE,IAAI,OAAO,MAAM,IAAI,SAAS;AAAA,IACzC;AACA,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,eAAW,4BAA4B,EAAE,MAAM,OAAO,EAAE;AACxD,gBAAY,SAAS,GAAG,KAAK,eAAe,OAAO,CAAC;AACpD,WAAO,EAAE,IAAI,OAAO,MAAM,EAAE;AAAA,EAC9B;AACF;AAWA,SAAS,eAAe,SAAgC;AACtD,QAAM,UAAU,SAAS,EAAE,QAAQ,UAAU,QAAQ,QAAQ,CAAC;AAC9D,MAAI,CAAC,oBAAoB,QAAQ,IAAI,EAAG,QAAO;AAC/C,MAAI,QAAQ,SAAS,kBAAkB;AACrC,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,YAAY,QAAyC;AAC5D,MAAI,WAAW,QAAQ,WAAW,UAAa,WAAW,IAAI;AAC5D,iBAAa,oBAAoB,MAAM,EAAE;AAAA,EAC3C;AACF;","names":[]}
|