@smartmemory/compose 0.3.6-beta → 0.3.7
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/.claude/skills/compose/SKILL.md +42 -88
- package/bin/compose.js +288 -0
- package/bin/git-hooks/pre-push.template +29 -0
- package/bin/judgment-import.js +7 -0
- package/contracts/feature-json.schema.json +5 -0
- package/contracts/judgment-record.schema.json +425 -4
- package/dist/assets/App-PkZzHeMj.js +894 -0
- package/dist/assets/_baseUniq-Bo837sRJ.js +1 -0
- package/dist/assets/arc-BafGpyqE.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-BOBfUsqL.js +36 -0
- package/dist/assets/blockDiagram-DXYQGD6D-Dwodev1a.js +132 -0
- package/dist/assets/{browser-BSM23If2.js → browser-1ntj1-x_.js} +6 -6
- package/dist/assets/{c4Diagram-LMCZKHZV-DZf45Fbz.js → c4Diagram-AHTNJAMY-CU_bhYag.js} +1 -1
- package/dist/assets/channel-qVK_qn4E.js +1 -0
- package/dist/assets/{chunk-JWPE2WC7-_7ujgd_Q.js → chunk-4BX2VUAB-p8WsDwnO.js} +1 -1
- package/dist/assets/chunk-4TB4RGXK-B8h7-eR0.js +206 -0
- package/dist/assets/{chunk-XXDRQBXY-DfdVhbmA.js → chunk-55IACEB6-DxeEr98s.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-Dt9NZ67m.js → chunk-EDXVE4YY-BYt8F151.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-BY4_PV5H.js → chunk-FMBD7UC4-DGSOVeie.js} +1 -1
- package/dist/assets/chunk-OYMX7WX6-B-QdgYR2.js +231 -0
- package/dist/assets/{chunk-2Q5K7J3B-Dn1spZYu.js → chunk-QZHKN3VN-Du5UAZLs.js} +1 -1
- package/dist/assets/{chunk-32BRIVSS-pURGrJDk.js → chunk-YZCP3GAM-C8JbNBSk.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-B8UcfC1q.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-B8UcfC1q.js +1 -0
- package/dist/assets/clone-Pu3RyLUh.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-BieYif4o.js → cose-bilkent-S5V4N54A-O1ESaqge.js} +1 -1
- package/dist/assets/dagre-KV5264BT-CPTmFPHw.js +4 -0
- package/dist/assets/diagram-5BDNPKRD-B3PNrWs5.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-Cscfr6vc.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-CSfqZ-TM.js +43 -0
- package/dist/assets/diagram-TYMM5635-Cg4aYS7W.js +24 -0
- package/dist/assets/erDiagram-SMLLAGMA-_ZqwG5pl.js +85 -0
- package/dist/assets/flowDiagram-DWJPFMVM-C83boxFT.js +162 -0
- package/dist/assets/ganttDiagram-T4ZO3ILL-CWnIjuEi.js +292 -0
- package/dist/assets/gitGraphDiagram-UUTBAWPF-DrMdxZfH.js +106 -0
- package/dist/assets/graph-Bi99_6Yf.js +331 -0
- package/dist/assets/graph-RE4I7Ty7.js +1 -0
- package/dist/assets/index-Rm2RE-c0.js +123 -0
- package/dist/assets/infoDiagram-42DDH7IO-BLmP4Epr.js +2 -0
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-CzEB9fQS.js → ishikawaDiagram-UXIWVN3A-yuWWshKN.js} +5 -5
- package/dist/assets/{journeyDiagram-5HDEW3XC-Bz8TCdz2.js → journeyDiagram-VCZTEJTY-BOfhaJov.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-tozrMoV_.js → kanban-definition-6JOO6SKY-Bbolde15.js} +7 -7
- package/dist/assets/katex-DkKDou_j.js +257 -0
- package/dist/assets/layout-BSf33zm8.js +1 -0
- package/dist/assets/{linear-Ck7gpa5N.js → linear-AvSTWMqx.js} +1 -1
- package/dist/assets/min-QBM8H4xN.js +1 -0
- package/dist/assets/{mindmap-definition-LN4V7U3C-DTcHO0DJ.js → mindmap-definition-QFDTVHPH-BuvgtqIc.js} +7 -7
- package/dist/assets/{mobile-CaoXUwAr.js → mobile-BnXEOE3U.js} +2 -2
- package/dist/assets/pieDiagram-DEJITSTG-DIzF16vh.js +30 -0
- package/dist/assets/quadrantDiagram-34T5L4WZ-D-mbUIjS.js +7 -0
- package/dist/assets/{requirementDiagram-TGXJPOKE-bnI2zJeT.js → requirementDiagram-MS252O5E-CEs4kCLd.js} +3 -3
- package/dist/assets/sankeyDiagram-XADWPNL6-DFsnCr9n.js +10 -0
- package/dist/assets/sequenceDiagram-FGHM5R23-BEJYdTjQ.js +157 -0
- package/dist/assets/stateDiagram-FHFEXIEX-BBXs57uY.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-BqKuX4rj.js +1 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-D267GQFF.js → timeline-definition-GMOUNBTQ-BGvLoVAY.js} +3 -3
- package/dist/assets/vennDiagram-DHZGUBPP-9LaBTMe0.js +34 -0
- package/dist/assets/wardley-RL74JXVD-P4MEqMTP.js +162 -0
- package/dist/assets/wardleyDiagram-NUSXRM2D-o-tmxnlC.js +20 -0
- package/dist/assets/xychartDiagram-5P7HB3ND-Dpn7V6qk.js +7 -0
- package/dist/index.html +2 -2
- package/lib/bug-escalation.js +30 -4
- package/lib/build.js +777 -52
- package/lib/canon-guard.js +223 -0
- package/lib/canon-registry.js +187 -0
- package/lib/codex-preflight.js +26 -4
- package/lib/dispatch-ledger.js +301 -0
- package/lib/dispatch-metrics.js +236 -0
- package/lib/experiment-judge.js +6 -1
- package/lib/feature-writer.js +9 -0
- package/lib/gsd.js +11 -2
- package/lib/hooks-status.js +32 -3
- package/lib/judgment/store/index.js +158 -0
- package/lib/judgment/store/records.js +183 -24
- package/lib/judgment-attest.js +259 -0
- package/lib/judgment-gen.js +370 -21
- package/lib/judgment-verify.js +153 -0
- package/lib/judgment-writer.js +2803 -277
- package/lib/lane-gate.js +2 -0
- package/lib/local-claude-connector.js +199 -54
- package/lib/mcp-enforcement.js +21 -35
- package/lib/result-normalizer.js +93 -15
- package/lib/review-normalize.js +4 -0
- package/lib/stratum-mcp-client.js +131 -6
- package/package.json +2 -2
- package/server/compose-mcp-tools.js +15 -1
- package/server/compose-mcp.js +96 -1
- package/server/mcp-tool-policy.js +1 -1
- package/dist/assets/App-BG3ngu8H.js +0 -896
- package/dist/assets/abnfDiagram-VRR7QNED-CjB_sD3D.js +0 -1
- package/dist/assets/arc-_v4hR_uD.js +0 -1
- package/dist/assets/architectureDiagram-ZJ3FMSHR-DreJmzXQ.js +0 -36
- package/dist/assets/blockDiagram-677ZJIJ3-BG9-c0O1.js +0 -132
- package/dist/assets/channel-B3U5wFAT.js +0 -1
- package/dist/assets/chunk-EX3LRPZG-DdELs1qP.js +0 -231
- package/dist/assets/chunk-MOJQB5TN-D-ky35G-.js +0 -88
- package/dist/assets/chunk-RYQCIY6F-Dag_kVlO.js +0 -1
- package/dist/assets/chunk-V7JOEXUC-BtewURat.js +0 -206
- package/dist/assets/classDiagram-OUVF2IWQ-B6fCN-ht.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-B6fCN-ht.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-CT2BA6KE.js +0 -178
- package/dist/assets/cynefinDiagram-TSTJHNR4-Bh6exbyg.js +0 -62
- package/dist/assets/dagre-VKFMJZFB-aXMLSmQL.js +0 -4
- package/dist/assets/diagram-FQU43EPY-Dr7JAOuQ.js +0 -3
- package/dist/assets/diagram-G47NLZAW-DUvA3FQK.js +0 -24
- package/dist/assets/diagram-NH7WQ7WH-BQUARqcu.js +0 -24
- package/dist/assets/diagram-OA4YK3LP-dDUc1zHi.js +0 -30
- package/dist/assets/diagram-WEI45ONY-B2h5Qlb1.js +0 -41
- package/dist/assets/ebnfDiagram-CCIWWBDH-DThRGupB.js +0 -1
- package/dist/assets/erDiagram-Q63AITRT-BUCsprO2.js +0 -85
- package/dist/assets/flowDiagram-23GEKE2U-DXtNNi6r.js +0 -156
- package/dist/assets/ganttDiagram-NO4QXBWP-D4zbBHh_.js +0 -292
- package/dist/assets/gitGraphDiagram-IHSO6WYX-DpoQws0W.js +0 -106
- package/dist/assets/graph-BXPQrYYB.js +0 -331
- package/dist/assets/graph-C9eacEi8.js +0 -1
- package/dist/assets/index-3ZH5eMcZ.js +0 -119
- package/dist/assets/infoDiagram-FWYZ7A6U-Bbas2GAo.js +0 -2
- package/dist/assets/katex-C5jXJg4s.js +0 -257
- package/dist/assets/layout-DEXfKzaS.js +0 -1
- package/dist/assets/map-Czzmt4hB.js +0 -1
- package/dist/assets/pegDiagram-2B236MQR-CHiINrNy.js +0 -1
- package/dist/assets/pieDiagram-ENE6RG2P-CfS4YFlR.js +0 -39
- package/dist/assets/quadrantDiagram-ABIIQ3AL-CadesS9w.js +0 -7
- package/dist/assets/railroadDiagram-RFXS5EU6-CgWEspBN.js +0 -1
- package/dist/assets/sankeyDiagram-HTMAVEWB-YWKFgOGw.js +0 -40
- package/dist/assets/sequenceDiagram-DBY2YBRQ-BvkNOyF9.js +0 -162
- package/dist/assets/sizeCapture-X5ZJPWSS-DlFPA2yO.js +0 -1
- package/dist/assets/stateDiagram-2N3HPSRC-h8NIx0kQ.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-DjPgZtJ9.js +0 -1
- package/dist/assets/swimlanes-5IMT3BWC-CT5n22kG.js +0 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-Dn318Bhq.js +0 -8
- package/dist/assets/vennDiagram-L72KCM5P-Dj-wWLYG.js +0 -34
- package/dist/assets/wardleyDiagram-EHGQE667-BxCeYxkG.js +0 -78
- package/dist/assets/xychartDiagram-FW5EYKEG-DMFqWn7z.js +0 -7
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dispatch-ledger.js — closed, append-only telemetry events for agent dispatch.
|
|
3
|
+
*
|
|
4
|
+
* Ledger rows are addressed by an explicit project root so callers running in a
|
|
5
|
+
* worktree cannot accidentally split telemetry away from the build project.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { dirname, join, resolve } from 'node:path';
|
|
11
|
+
|
|
12
|
+
import { normalizeSince } from './feature-events.js';
|
|
13
|
+
|
|
14
|
+
export const DISPATCH_LEDGER_RELATIVE_PATH = '.compose/data/dispatch-ledger.jsonl';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the project root a dispatch connector should address its ledger by.
|
|
18
|
+
*
|
|
19
|
+
* Two paths write telemetry into the live compose checkout during a test run:
|
|
20
|
+
* an unattributed dispatch with no project_cwd falls back to process.cwd(), and
|
|
21
|
+
* a test that explicitly roots a build at process.cwd() threads that same live
|
|
22
|
+
* path through. Under NODE_TEST_CONTEXT, any ledger write addressed at the
|
|
23
|
+
* process cwd (the live checkout) is redirected to a hermetic tmpdir so
|
|
24
|
+
* full-suite runs never accrete fixture rows in the real .compose/data ledger.
|
|
25
|
+
* A test that threads its own tmpdir project root is addressed there, unchanged.
|
|
26
|
+
* Production paths never set NODE_TEST_CONTEXT, so they are unaffected.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} [explicitCwd] the caller's project_cwd, when known
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
export function resolveDispatchLedgerCwd(explicitCwd) {
|
|
32
|
+
const cwd = isString(explicitCwd) ? explicitCwd : process.cwd();
|
|
33
|
+
if (process.env.NODE_TEST_CONTEXT && resolve(cwd) === resolve(process.cwd())) {
|
|
34
|
+
return join(tmpdir(), 'compose-test-worktrees', 'dispatch-ledger');
|
|
35
|
+
}
|
|
36
|
+
return cwd;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const DISPATCH_SITES = Object.freeze([
|
|
40
|
+
'build-step', 'consumer', 'review', 'review-repair', 'gsd', 'gate-qa',
|
|
41
|
+
'escalation', 'preflight', 'judge', 'design-chat', 'import', 'validation',
|
|
42
|
+
'new-project', 'unattributed',
|
|
43
|
+
]);
|
|
44
|
+
export const DISPATCH_OUTCOMES = Object.freeze(['ok', 'error', 'blocked']);
|
|
45
|
+
export const SETTLEMENT_FAILURE_CLASSES = Object.freeze([
|
|
46
|
+
'ownership', 'vocabulary', 'normalization', 'agent', 'ensure-retry',
|
|
47
|
+
]);
|
|
48
|
+
export const ESTIMATE_LANES = Object.freeze(['trivial', 'standard', 'complex']);
|
|
49
|
+
export const ESTIMATE_SOURCES = Object.freeze(['fresh', 'cached', 'escalated']);
|
|
50
|
+
export const TRIAGE_CONFIDENCES = Object.freeze(['high', 'medium', 'low']);
|
|
51
|
+
export const BUILD_TERMINAL_STATUSES = Object.freeze(['complete', 'failed', 'aborted']);
|
|
52
|
+
export const BUILD_FILES_SOURCES = Object.freeze(['ship', 'accumulated']);
|
|
53
|
+
|
|
54
|
+
const EVENT_FIELDS = {
|
|
55
|
+
dispatch: {
|
|
56
|
+
required: ['dispatch_id', 'site', 'agent', 'outcome'],
|
|
57
|
+
optional: [
|
|
58
|
+
'build_id', 'feature_code', 'step_id', 'attempt', 'model',
|
|
59
|
+
'effort_intended', 'effort_executed', 'tokens_in', 'tokens_out',
|
|
60
|
+
'tokens_total', 'usd', 'duration_ms', 'note',
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
settlement: {
|
|
64
|
+
required: ['dispatch_id', 'accepted'],
|
|
65
|
+
optional: ['build_id', 'step_id', 'failure_class'],
|
|
66
|
+
},
|
|
67
|
+
'triage-estimate': {
|
|
68
|
+
required: ['build_id', 'feature_code', 'tier', 'lane', 'profile', 'estimate_source'],
|
|
69
|
+
optional: ['confidence'],
|
|
70
|
+
},
|
|
71
|
+
'build-actuals': {
|
|
72
|
+
required: [
|
|
73
|
+
'build_id', 'feature_code', 'terminal_status', 'files_changed_count',
|
|
74
|
+
'files_source', 'review_iterations', 'escalations', 'tokens_total', 'usd',
|
|
75
|
+
],
|
|
76
|
+
optional: ['test_count', 'pass_rate'],
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const countersByPath = new Map();
|
|
81
|
+
|
|
82
|
+
function ledgerPath(projectCwd) {
|
|
83
|
+
return resolve(projectCwd, DISPATCH_LEDGER_RELATIVE_PATH);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isPlainObject(value) {
|
|
87
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isString(value) {
|
|
91
|
+
return typeof value === 'string' && value.length > 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isFiniteNumber(value) {
|
|
95
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isNullableFiniteNumber(value) {
|
|
99
|
+
return value === null || isFiniteNumber(value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isNullableString(value) {
|
|
103
|
+
return value === null || isString(value);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isEnum(value, values) {
|
|
107
|
+
return values.includes(value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function invalid(message) {
|
|
111
|
+
throw new Error(`dispatch-ledger.appendEvent: ${message}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function validateEvent(event, { allowExtra = false } = {}) {
|
|
115
|
+
if (!isPlainObject(event)) invalid('event must be an object');
|
|
116
|
+
if (!isString(event.kind)) invalid('kind is required');
|
|
117
|
+
const shape = EVENT_FIELDS[event.kind];
|
|
118
|
+
if (!shape) invalid(`unknown kind "${event.kind}"`);
|
|
119
|
+
|
|
120
|
+
const allowed = new Set(['kind', ...shape.required, ...shape.optional]);
|
|
121
|
+
if (!allowExtra) {
|
|
122
|
+
for (const field of Object.keys(event)) {
|
|
123
|
+
if (!allowed.has(field)) invalid(`unknown field "${field}" for ${event.kind}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const field of shape.required) {
|
|
127
|
+
if (!Object.hasOwn(event, field) || event[field] === undefined) {
|
|
128
|
+
invalid(`required field "${field}" is missing for ${event.kind}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
switch (event.kind) {
|
|
133
|
+
case 'dispatch':
|
|
134
|
+
requireString(event, 'dispatch_id');
|
|
135
|
+
requireEnum(event, 'site', DISPATCH_SITES);
|
|
136
|
+
requireString(event, 'agent');
|
|
137
|
+
requireEnum(event, 'outcome', DISPATCH_OUTCOMES);
|
|
138
|
+
optionalString(event, 'build_id');
|
|
139
|
+
optionalString(event, 'feature_code');
|
|
140
|
+
optionalString(event, 'step_id');
|
|
141
|
+
optionalNumber(event, 'attempt');
|
|
142
|
+
optionalNullableString(event, 'model');
|
|
143
|
+
optionalNullableString(event, 'effort_intended');
|
|
144
|
+
optionalNullableString(event, 'effort_executed');
|
|
145
|
+
for (const field of ['tokens_in', 'tokens_out', 'tokens_total', 'usd', 'duration_ms']) {
|
|
146
|
+
optionalNullableNumber(event, field);
|
|
147
|
+
}
|
|
148
|
+
optionalString(event, 'note');
|
|
149
|
+
break;
|
|
150
|
+
case 'settlement':
|
|
151
|
+
requireString(event, 'dispatch_id');
|
|
152
|
+
if (typeof event.accepted !== 'boolean') invalid('accepted must be a boolean');
|
|
153
|
+
optionalString(event, 'build_id');
|
|
154
|
+
optionalString(event, 'step_id');
|
|
155
|
+
optionalEnum(event, 'failure_class', SETTLEMENT_FAILURE_CLASSES);
|
|
156
|
+
break;
|
|
157
|
+
case 'triage-estimate':
|
|
158
|
+
requireString(event, 'build_id');
|
|
159
|
+
requireString(event, 'feature_code');
|
|
160
|
+
if (!Number.isInteger(event.tier) || event.tier < 0 || event.tier > 4) {
|
|
161
|
+
invalid('tier must be an integer 0-4');
|
|
162
|
+
}
|
|
163
|
+
requireEnum(event, 'lane', ESTIMATE_LANES);
|
|
164
|
+
if (!isPlainObject(event.profile)) invalid('profile must be a non-array object');
|
|
165
|
+
requireEnum(event, 'estimate_source', ESTIMATE_SOURCES);
|
|
166
|
+
if (Object.hasOwn(event, 'confidence') && event.confidence !== null && !isEnum(event.confidence, TRIAGE_CONFIDENCES)) {
|
|
167
|
+
invalid(`confidence must be one of ${TRIAGE_CONFIDENCES.join(', ')} or null`);
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
case 'build-actuals':
|
|
171
|
+
requireString(event, 'build_id');
|
|
172
|
+
requireString(event, 'feature_code');
|
|
173
|
+
requireEnum(event, 'terminal_status', BUILD_TERMINAL_STATUSES);
|
|
174
|
+
requireNumber(event, 'files_changed_count');
|
|
175
|
+
requireEnum(event, 'files_source', BUILD_FILES_SOURCES);
|
|
176
|
+
requireNumber(event, 'review_iterations');
|
|
177
|
+
requireNumber(event, 'escalations');
|
|
178
|
+
requireNumber(event, 'tokens_total');
|
|
179
|
+
requireNumber(event, 'usd');
|
|
180
|
+
optionalNullableNumber(event, 'test_count');
|
|
181
|
+
optionalNullableNumber(event, 'pass_rate');
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function requireString(event, field) {
|
|
187
|
+
if (!isString(event[field])) invalid(`${field} must be a non-empty string`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function requireNumber(event, field) {
|
|
191
|
+
if (!isFiniteNumber(event[field])) invalid(`${field} must be a number`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function requireEnum(event, field, values) {
|
|
195
|
+
if (!isEnum(event[field], values)) invalid(`${field} must be one of ${values.join(', ')}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function optionalString(event, field) {
|
|
199
|
+
if (Object.hasOwn(event, field) && !isString(event[field])) invalid(`${field} must be a non-empty string`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function optionalNullableString(event, field) {
|
|
203
|
+
if (Object.hasOwn(event, field) && !isNullableString(event[field])) invalid(`${field} must be a string or null`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function optionalNumber(event, field) {
|
|
207
|
+
if (Object.hasOwn(event, field) && !isFiniteNumber(event[field])) invalid(`${field} must be a number`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function optionalNullableNumber(event, field) {
|
|
211
|
+
if (Object.hasOwn(event, field) && !isNullableFiniteNumber(event[field])) invalid(`${field} must be a number or null`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function optionalEnum(event, field, values) {
|
|
215
|
+
if (Object.hasOwn(event, field) && !isEnum(event[field], values)) {
|
|
216
|
+
invalid(`${field} must be one of ${values.join(', ')}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function isValidStoredRow(row) {
|
|
221
|
+
if (!isPlainObject(row) || row.v !== 1 || !isIsoTimestamp(row.ts) || !Number.isInteger(row._seq) || row._seq < 0) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
validateEvent(row, { allowExtra: true });
|
|
226
|
+
return true;
|
|
227
|
+
} catch {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function isIsoTimestamp(value) {
|
|
233
|
+
return typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value) && !Number.isNaN(Date.parse(value));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function seedCounter(path) {
|
|
237
|
+
if (countersByPath.has(path)) return countersByPath.get(path);
|
|
238
|
+
let highest = 0;
|
|
239
|
+
if (existsSync(path)) {
|
|
240
|
+
for (const line of readFileSync(path, 'utf8').split('\n')) {
|
|
241
|
+
if (!line.trim()) continue;
|
|
242
|
+
try {
|
|
243
|
+
const row = JSON.parse(line);
|
|
244
|
+
if (isValidStoredRow(row)) highest = Math.max(highest, row._seq);
|
|
245
|
+
} catch {
|
|
246
|
+
// A torn JSONL row must not prevent later rows from being read.
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
countersByPath.set(path, highest);
|
|
251
|
+
return highest;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Append one closed dispatch-ledger event, stamping its immutable envelope.
|
|
256
|
+
*
|
|
257
|
+
* @param {string} projectCwd
|
|
258
|
+
* @param {object} event caller-supplied kind and closed payload only
|
|
259
|
+
* @returns {object} the row written to disk
|
|
260
|
+
*/
|
|
261
|
+
export function appendEvent(projectCwd, event) {
|
|
262
|
+
validateEvent(event);
|
|
263
|
+
const path = ledgerPath(projectCwd);
|
|
264
|
+
const nextSeq = seedCounter(path) + 1;
|
|
265
|
+
countersByPath.set(path, nextSeq);
|
|
266
|
+
const row = { v: 1, ts: new Date().toISOString(), _seq: nextSeq, ...event };
|
|
267
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
268
|
+
appendFileSync(path, JSON.stringify(row) + '\n');
|
|
269
|
+
return row;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Tolerantly read valid, known ledger rows. Future fields are preserved.
|
|
274
|
+
*
|
|
275
|
+
* @param {string} projectCwd
|
|
276
|
+
* @param {object} [opts]
|
|
277
|
+
* @param {string} [opts.kind]
|
|
278
|
+
* @param {string|number|Date} [opts.since]
|
|
279
|
+
* @param {string} [opts.feature]
|
|
280
|
+
* @returns {Array<object>}
|
|
281
|
+
*/
|
|
282
|
+
export function readEvents(projectCwd, { kind, since, feature } = {}) {
|
|
283
|
+
const path = ledgerPath(projectCwd);
|
|
284
|
+
if (!existsSync(path)) return [];
|
|
285
|
+
const sinceMs = normalizeSince(since);
|
|
286
|
+
const rows = [];
|
|
287
|
+
for (const line of readFileSync(path, 'utf8').split('\n')) {
|
|
288
|
+
if (!line.trim()) continue;
|
|
289
|
+
try {
|
|
290
|
+
const row = JSON.parse(line);
|
|
291
|
+
if (!isValidStoredRow(row)) continue;
|
|
292
|
+
if (kind && row.kind !== kind) continue;
|
|
293
|
+
if (feature && row.feature_code !== feature) continue;
|
|
294
|
+
if (sinceMs !== null && Date.parse(row.ts) < sinceMs) continue;
|
|
295
|
+
rows.push(row);
|
|
296
|
+
} catch {
|
|
297
|
+
// Malformed rows are expected in an append-only JSONL file after a tear.
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return rows;
|
|
301
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dispatch-metrics.js — read-only aggregation over the dispatch ledger.
|
|
3
|
+
*
|
|
4
|
+
* This module deliberately keeps the ledger's append-only observations intact:
|
|
5
|
+
* settlements and terminal actuals are reduced at read time by their `_seq`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readEvents } from './dispatch-ledger.js';
|
|
9
|
+
|
|
10
|
+
const RETRY_NOTE = 'known undercount: child flows';
|
|
11
|
+
const GSD_NOTE = 'not instrumented';
|
|
12
|
+
|
|
13
|
+
function isNumber(value) {
|
|
14
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function upperMiddle(values) {
|
|
18
|
+
const sorted = values.filter(isNumber).sort((a, b) => a - b);
|
|
19
|
+
return sorted.length ? sorted[Math.floor(sorted.length / 2)] : null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function rate(numerator, denominator) {
|
|
23
|
+
return denominator > 0 ? numerator / denominator : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function highestBy(rows, key) {
|
|
27
|
+
const result = new Map();
|
|
28
|
+
for (const row of rows) {
|
|
29
|
+
const current = result.get(row[key]);
|
|
30
|
+
if (!current || row._seq > current._seq) result.set(row[key], row);
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function compareNullable(a, b) {
|
|
36
|
+
if (a === b) return 0;
|
|
37
|
+
if (a === null || a === undefined) return -1;
|
|
38
|
+
if (b === null || b === undefined) return 1;
|
|
39
|
+
return String(a).localeCompare(String(b));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function collectRowStats(dispatches, settlements, { gsd = false } = {}) {
|
|
43
|
+
const tokenValues = dispatches.map((row) => row.tokens_total).filter(isNumber);
|
|
44
|
+
const durationValues = dispatches.map((row) => row.duration_ms).filter(isNumber);
|
|
45
|
+
const settled = dispatches
|
|
46
|
+
.map((row) => settlements.get(row.dispatch_id))
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
const accepted = settled.filter((row) => row.accepted).length;
|
|
49
|
+
const retryEligible = dispatches.filter((row) => isNumber(row.attempt));
|
|
50
|
+
const retries = retryEligible.filter((row) => row.attempt > 1).length;
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
token_sample_count: tokenValues.length,
|
|
54
|
+
null_usage_count: dispatches.filter((row) => !isNumber(row.tokens_total)).length,
|
|
55
|
+
median_tokens: upperMiddle(tokenValues),
|
|
56
|
+
duration_sample_count: durationValues.length,
|
|
57
|
+
median_duration_ms: upperMiddle(durationValues),
|
|
58
|
+
completion: {
|
|
59
|
+
ok: dispatches.filter((row) => row.outcome === 'ok').length,
|
|
60
|
+
total: dispatches.length,
|
|
61
|
+
rate: rate(dispatches.filter((row) => row.outcome === 'ok').length, dispatches.length),
|
|
62
|
+
},
|
|
63
|
+
acceptance: {
|
|
64
|
+
accepted,
|
|
65
|
+
settled: settled.length,
|
|
66
|
+
rate: rate(accepted, settled.length),
|
|
67
|
+
note: gsd ? GSD_NOTE : null,
|
|
68
|
+
},
|
|
69
|
+
retry: {
|
|
70
|
+
retries,
|
|
71
|
+
eligible: retryEligible.length,
|
|
72
|
+
rate: rate(retries, retryEligible.length),
|
|
73
|
+
note: RETRY_NOTE,
|
|
74
|
+
},
|
|
75
|
+
usd_total: dispatches.reduce((total, row) => total + (isNumber(row.usd) ? row.usd : 0), 0),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function realizedLane(actual) {
|
|
80
|
+
if (actual.escalations >= 1 || actual.review_iterations >= 3 || actual.files_changed_count >= 6) return 'complex';
|
|
81
|
+
if (actual.files_changed_count <= 2 && actual.review_iterations <= 1 && actual.escalations === 0) return 'trivial';
|
|
82
|
+
return 'standard';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function acrrSummary(rows) {
|
|
86
|
+
const total = rows.length;
|
|
87
|
+
const matched = rows.filter((row) => row.matched).length;
|
|
88
|
+
return { matched, total, rate: rate(matched, total) };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Aggregate dispatch, settlement, estimate, and terminal actual rows without
|
|
93
|
+
* mutating the ledger.
|
|
94
|
+
*
|
|
95
|
+
* @param {string} projectCwd
|
|
96
|
+
* @param {{ since?: string|number|Date, feature?: string }} [opts]
|
|
97
|
+
* @returns {object}
|
|
98
|
+
*/
|
|
99
|
+
export function collectDispatchMetrics(projectCwd, { since, feature } = {}) {
|
|
100
|
+
// Settlements have no feature code. Read one since-filtered population first,
|
|
101
|
+
// then join them to the feature-filtered dispatches below.
|
|
102
|
+
const allRows = readEvents(projectCwd, { since });
|
|
103
|
+
const inFeature = (row) => !feature || row.feature_code === feature;
|
|
104
|
+
const dispatches = allRows.filter((row) => row.kind === 'dispatch' && inFeature(row));
|
|
105
|
+
const settlements = highestBy(allRows.filter((row) => row.kind === 'settlement'), 'dispatch_id');
|
|
106
|
+
|
|
107
|
+
const modelBuckets = new Map();
|
|
108
|
+
for (const row of dispatches) {
|
|
109
|
+
const executed = row.effort_executed ?? null;
|
|
110
|
+
const intendedWhenUnknown = executed === null ? (row.effort_intended ?? null) : null;
|
|
111
|
+
const values = [row.model ?? null, executed, intendedWhenUnknown];
|
|
112
|
+
const key = JSON.stringify(values);
|
|
113
|
+
if (!modelBuckets.has(key)) modelBuckets.set(key, { values, rows: [] });
|
|
114
|
+
modelBuckets.get(key).rows.push(row);
|
|
115
|
+
}
|
|
116
|
+
const model_effort = [...modelBuckets.values()]
|
|
117
|
+
.sort((a, b) => compareNullable(a.values[0], b.values[0]) || compareNullable(a.values[1], b.values[1]) || compareNullable(a.values[2], b.values[2]))
|
|
118
|
+
.map(({ values: [model, effort_executed, effort_intended_when_unknown], rows }) => {
|
|
119
|
+
const stats = collectRowStats(rows, settlements, { gsd: rows.every((row) => row.site === 'gsd') });
|
|
120
|
+
return {
|
|
121
|
+
model, effort_executed, effort_intended_when_unknown, dispatch_count: rows.length,
|
|
122
|
+
...stats,
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const siteBuckets = new Map();
|
|
127
|
+
for (const row of dispatches) {
|
|
128
|
+
if (!siteBuckets.has(row.site)) siteBuckets.set(row.site, []);
|
|
129
|
+
siteBuckets.get(row.site).push(row);
|
|
130
|
+
}
|
|
131
|
+
const sites = [...siteBuckets.entries()]
|
|
132
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
133
|
+
.map(([site, rows]) => ({ site, dispatch_count: rows.length, ...collectRowStats(rows, settlements, { gsd: site === 'gsd' }) }));
|
|
134
|
+
|
|
135
|
+
const estimates = allRows.filter((row) => row.kind === 'triage-estimate' && inFeature(row));
|
|
136
|
+
const actualRows = allRows.filter((row) => row.kind === 'build-actuals' && inFeature(row));
|
|
137
|
+
const actuals = highestBy(actualRows, 'build_id');
|
|
138
|
+
const estimateBuildIds = new Set(estimates.map((row) => row.build_id));
|
|
139
|
+
const acrrRows = estimates
|
|
140
|
+
.map((estimate) => {
|
|
141
|
+
const actual = actuals.get(estimate.build_id) ?? null;
|
|
142
|
+
const eligible = actual?.terminal_status === 'complete';
|
|
143
|
+
const lane = eligible ? realizedLane(actual) : null;
|
|
144
|
+
return {
|
|
145
|
+
build_id: estimate.build_id,
|
|
146
|
+
feature_code: estimate.feature_code,
|
|
147
|
+
estimate_source: estimate.estimate_source,
|
|
148
|
+
confidence: estimate.confidence ?? null,
|
|
149
|
+
estimated_lane: estimate.lane,
|
|
150
|
+
terminal_status: actual?.terminal_status ?? null,
|
|
151
|
+
realized_lane: lane,
|
|
152
|
+
matched: eligible ? estimate.lane === lane : false,
|
|
153
|
+
files_changed_count: actual?.files_changed_count ?? null,
|
|
154
|
+
files_source: actual?.files_source ?? null,
|
|
155
|
+
review_iterations: actual?.review_iterations ?? null,
|
|
156
|
+
escalations: actual?.escalations ?? null,
|
|
157
|
+
};
|
|
158
|
+
})
|
|
159
|
+
.sort((a, b) => a.build_id.localeCompare(b.build_id));
|
|
160
|
+
const eligibleRows = acrrRows.filter((row) => row.terminal_status === 'complete' && row.estimate_source !== 'escalated');
|
|
161
|
+
const escalatedRows = acrrRows.filter((row) => row.terminal_status === 'complete' && row.estimate_source === 'escalated');
|
|
162
|
+
const failed_attempts = actualRows.filter((row) => row.terminal_status === 'failed').length;
|
|
163
|
+
const aborted_attempts = actualRows.filter((row) => row.terminal_status === 'aborted').length;
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
v: 1,
|
|
167
|
+
filters: { since: since ?? null, feature: feature ?? null },
|
|
168
|
+
coverage: {
|
|
169
|
+
dispatch_count: dispatches.length,
|
|
170
|
+
unattributed_count: dispatches.filter((row) => row.site === 'unattributed').length,
|
|
171
|
+
null_usage_count: dispatches.filter((row) => !isNumber(row.tokens_total)).length,
|
|
172
|
+
},
|
|
173
|
+
model_effort,
|
|
174
|
+
sites,
|
|
175
|
+
acrr: {
|
|
176
|
+
eligible: acrrSummary(eligibleRows),
|
|
177
|
+
escalated: acrrSummary(escalatedRows),
|
|
178
|
+
attrition_count: failed_attempts + aborted_attempts,
|
|
179
|
+
failed_attempts,
|
|
180
|
+
aborted_attempts,
|
|
181
|
+
pending_count: acrrRows.filter((row) => row.terminal_status === null).length,
|
|
182
|
+
unpaired_count: [...actuals.keys()].filter((buildId) => !estimateBuildIds.has(buildId)).length,
|
|
183
|
+
rows: acrrRows,
|
|
184
|
+
},
|
|
185
|
+
known_limitations: { retry_rate: RETRY_NOTE, gsd_acceptance: GSD_NOTE },
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function formatRate(value) {
|
|
190
|
+
return value === null ? 'n/a' : `${(value * 100).toFixed(1)}%`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function formatMedian(value) {
|
|
194
|
+
return value === null ? 'n/a' : String(value);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function formatAcceptance(acceptance) {
|
|
198
|
+
return acceptance.note === GSD_NOTE ? 'n/a (not instrumented)' : formatRate(acceptance.rate);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Render a compact, stable human report for `compose metrics`. */
|
|
202
|
+
export function renderDispatchMetrics(report) {
|
|
203
|
+
const lines = [
|
|
204
|
+
'Model × executed effort',
|
|
205
|
+
'model | executed | intended when unknown | dispatches | median tokens | median ms | completion | acceptance | retry | usd',
|
|
206
|
+
];
|
|
207
|
+
for (const row of report.model_effort) {
|
|
208
|
+
lines.push([
|
|
209
|
+
row.model === null ? '(model: unknown)' : row.model,
|
|
210
|
+
row.effort_executed ?? 'unknown',
|
|
211
|
+
row.effort_intended_when_unknown ?? '—',
|
|
212
|
+
row.dispatch_count,
|
|
213
|
+
formatMedian(row.median_tokens),
|
|
214
|
+
formatMedian(row.median_duration_ms),
|
|
215
|
+
formatRate(row.completion.rate),
|
|
216
|
+
formatAcceptance(row.acceptance),
|
|
217
|
+
formatRate(row.retry.rate),
|
|
218
|
+
row.usd_total,
|
|
219
|
+
].join(' | '));
|
|
220
|
+
}
|
|
221
|
+
lines.push('', 'Sites', 'site | dispatches | median tokens | median ms | completion | acceptance | retry | usd');
|
|
222
|
+
for (const row of report.sites) {
|
|
223
|
+
lines.push([
|
|
224
|
+
row.site, row.dispatch_count, formatMedian(row.median_tokens), formatMedian(row.median_duration_ms),
|
|
225
|
+
formatRate(row.completion.rate), formatAcceptance(row.acceptance), formatRate(row.retry.rate), row.usd_total,
|
|
226
|
+
].join(' | '));
|
|
227
|
+
}
|
|
228
|
+
lines.push('', 'ACRR');
|
|
229
|
+
lines.push(`eligible: ${report.acrr.eligible.matched}/${report.acrr.eligible.total} (${formatRate(report.acrr.eligible.rate)})`);
|
|
230
|
+
lines.push(`escalated: ${report.acrr.escalated.matched}/${report.acrr.escalated.total} (${formatRate(report.acrr.escalated.rate)})`);
|
|
231
|
+
lines.push(`attrition: ${report.acrr.attrition_count} (failed attempts: ${report.acrr.failed_attempts}, aborted attempts: ${report.acrr.aborted_attempts}, pending: ${report.acrr.pending_count}, unpaired: ${report.acrr.unpaired_count})`);
|
|
232
|
+
lines.push('', 'Known limitations');
|
|
233
|
+
lines.push(`retry rate: ${report.known_limitations.retry_rate}`);
|
|
234
|
+
lines.push(`GSD acceptance: ${report.known_limitations.gsd_acceptance}`);
|
|
235
|
+
return `${lines.join('\n')}\n`;
|
|
236
|
+
}
|
package/lib/experiment-judge.js
CHANGED
|
@@ -121,6 +121,7 @@ export async function judge({ diff, goal, judgeModel, stratum, cwd }) {
|
|
|
121
121
|
try {
|
|
122
122
|
const prompt = buildJudgePrompt(diff, goal);
|
|
123
123
|
const { provider, modelID, thinking, effort } = resolveAgentConfig(judgeModel);
|
|
124
|
+
const telemetry = { site: 'judge', project_cwd: cwd };
|
|
124
125
|
|
|
125
126
|
let text;
|
|
126
127
|
if (typeof stratum.agentRun === 'function') {
|
|
@@ -130,11 +131,15 @@ export async function judge({ diff, goal, judgeModel, stratum, cwd }) {
|
|
|
130
131
|
thinking: thinking ?? undefined,
|
|
131
132
|
effort: effort ?? undefined,
|
|
132
133
|
cwd: cwd ?? undefined,
|
|
134
|
+
telemetry,
|
|
133
135
|
});
|
|
134
136
|
text = result?.text ?? '';
|
|
135
137
|
} else {
|
|
136
138
|
// Fallback: runAgentText (no model pinning — test harnesses may use this).
|
|
137
|
-
text = await stratum.runAgentText(provider, prompt, {
|
|
139
|
+
text = await stratum.runAgentText(provider, prompt, {
|
|
140
|
+
cwd: cwd ?? undefined,
|
|
141
|
+
telemetry,
|
|
142
|
+
});
|
|
138
143
|
}
|
|
139
144
|
|
|
140
145
|
return extractJudgeResult(text);
|
package/lib/feature-writer.js
CHANGED
|
@@ -68,6 +68,7 @@ const IMPACTS = new Set(['low', 'medium', 'high']);
|
|
|
68
68
|
// front estimator, the doc-reading refinement, or an escalation bump.
|
|
69
69
|
const LANES = new Set(['trivial', 'standard', 'complex']);
|
|
70
70
|
const ESTIMATE_SOURCES = new Set(['front', 'refined', 'escalated']);
|
|
71
|
+
const TRIAGE_CONFIDENCES = new Set(['high', 'medium', 'low']);
|
|
71
72
|
|
|
72
73
|
// ---------------------------------------------------------------------------
|
|
73
74
|
// validateFeatureFields — shared field-shape guard
|
|
@@ -90,6 +91,7 @@ const ESTIMATE_SOURCES = new Set(['front', 'refined', 'escalated']);
|
|
|
90
91
|
* @param {number} [fields.triageTier] integer 0-4
|
|
91
92
|
* @param {string} [fields.lane] one of trivial|standard|complex
|
|
92
93
|
* @param {string} [fields.estimateSource] one of front|refined|escalated
|
|
94
|
+
* @param {string} [fields.triageConfidence] one of high|medium|low
|
|
93
95
|
*/
|
|
94
96
|
export function validateFeatureFields(fields) {
|
|
95
97
|
if (fields.complexity !== undefined && !COMPLEXITIES.has(fields.complexity)) {
|
|
@@ -107,6 +109,11 @@ export function validateFeatureFields(fields) {
|
|
|
107
109
|
`feature-writer: invalid estimateSource "${fields.estimateSource}" — must be one of ${[...ESTIMATE_SOURCES].join(', ')}`
|
|
108
110
|
);
|
|
109
111
|
}
|
|
112
|
+
if (fields.triageConfidence !== undefined && !TRIAGE_CONFIDENCES.has(fields.triageConfidence)) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`feature-writer: invalid triageConfidence "${fields.triageConfidence}" — must be one of ${[...TRIAGE_CONFIDENCES].join(', ')}`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
// ---------------------------------------------------------------------------
|
|
@@ -164,6 +171,7 @@ function maybeIdempotent(args, fn) {
|
|
|
164
171
|
* @param {string} [args.lane] COMP-TRIAGE-5: trivial | standard | complex
|
|
165
172
|
* @param {number} [args.triageTier] COMP-TRIAGE-5: integer 0-4
|
|
166
173
|
* @param {string} [args.estimateSource] COMP-TRIAGE-5: front | refined | escalated
|
|
174
|
+
* @param {string} [args.triageConfidence] COMP-TRIAGE-6: high | medium | low
|
|
167
175
|
* @param {boolean} [args.force]
|
|
168
176
|
* @param {string} [args.idempotency_key]
|
|
169
177
|
*/
|
|
@@ -235,6 +243,7 @@ export async function addRoadmapEntry(cwd, args) {
|
|
|
235
243
|
if (args.lane !== undefined) feature.lane = args.lane;
|
|
236
244
|
if (args.triageTier !== undefined) feature.triageTier = args.triageTier;
|
|
237
245
|
if (args.estimateSource !== undefined) feature.estimateSource = args.estimateSource;
|
|
246
|
+
if (args.triageConfidence !== undefined) feature.triageConfidence = args.triageConfidence;
|
|
238
247
|
|
|
239
248
|
let roundtrip = null;
|
|
240
249
|
if (isLocalProvider(provider)) {
|
package/lib/gsd.js
CHANGED
|
@@ -518,7 +518,7 @@ async function runOneStep(response, ctx) {
|
|
|
518
518
|
// by definition a GSD task, so timing.json + diffs/<id>.diff are written
|
|
519
519
|
// for the milestone report. The build consumer path omits it (no marker),
|
|
520
520
|
// so build-mode fanout stays byte-identical.
|
|
521
|
-
context: { cwd, featureCode, gsd: true, gsdTaskId: consumerItem?.id, filesChanged: ctx.filesChanged, onUsage: (usage) => recordTsAgentUsage(ctx, usage), taskResultPath },
|
|
521
|
+
context: { cwd, projectCwd: cwd, featureCode, gsd: true, gsdTaskId: consumerItem?.id, filesChanged: ctx.filesChanged, onUsage: (usage) => recordTsAgentUsage(ctx, usage), taskResultPath },
|
|
522
522
|
// runAndNormalize narrates via progress.debug/warn/info/toolUse — gsd
|
|
523
523
|
// has no cockpit, so a COMPLETE no-op (not just stepStart/stepDone) is
|
|
524
524
|
// required or the item throws "progress.debug is not a function".
|
|
@@ -565,7 +565,16 @@ async function runOneStep(response, ctx) {
|
|
|
565
565
|
|
|
566
566
|
// D2(b): dispatch via agentRun (not runAgentText) so the TS complete
|
|
567
567
|
// envelope's usage is captured and debited into the cumulative ledger.
|
|
568
|
-
const runOut = await stratum.agentRun(step.agent ?? 'claude', step.do ?? '', {
|
|
568
|
+
const runOut = await stratum.agentRun(step.agent ?? 'claude', step.do ?? '', {
|
|
569
|
+
cwd,
|
|
570
|
+
telemetry: {
|
|
571
|
+
site: 'gsd',
|
|
572
|
+
project_cwd: cwd,
|
|
573
|
+
feature_code: featureCode,
|
|
574
|
+
step_id: step.id,
|
|
575
|
+
...(typeof step.attempt === 'number' ? { attempt: step.attempt } : {}),
|
|
576
|
+
},
|
|
577
|
+
});
|
|
569
578
|
const text = runOut?.text ?? '';
|
|
570
579
|
recordTsAgentUsage(ctx, runOut?.usage);
|
|
571
580
|
// V1: also report usage in the step_done envelope so the ENGINE debits its
|