@smartmemory/compose 0.2.57-beta → 0.3.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 +2 -2
- package/bin/compose.js +55 -54
- package/contracts/feature-json.schema.json +16 -0
- package/dist/assets/{App-BmhlHOXF.js → App-DJ5xk_Wx.js} +219 -219
- package/dist/assets/{abnfDiagram-VRR7QNED-CMOTYAnt.js → abnfDiagram-VRR7QNED-BPWGdCFx.js} +1 -1
- package/dist/assets/{arc-BJAv_6dL.js → arc-DX5jqmcO.js} +1 -1
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-xa4DLUcp.js → architectureDiagram-ZJ3FMSHR-B6jmNVw7.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-BdkDqeQF.js → blockDiagram-677ZJIJ3-DAx2i-sB.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-DiNrLIuL.js → c4Diagram-LMCZKHZV-zmsZCplj.js} +1 -1
- package/dist/assets/channel-vsDnTvkh.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BsjtgrSL.js → chunk-2Q5K7J3B-3QSh6sI7.js} +1 -1
- package/dist/assets/{chunk-32BRIVSS-DpImULWy.js → chunk-32BRIVSS-BiH5Di_y.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-DTdG5JnR.js → chunk-5VM5RSS4-C0EfjFLd.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-Cj1jNuKY.js → chunk-EX3LRPZG-BGyfmVm-.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DwQ0wwWn.js → chunk-JWPE2WC7-Ba0mrNlg.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-DZZa7unc.js → chunk-MOJQB5TN-D3QO9EzH.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-CgdjqYQj.js → chunk-RYQCIY6F-D1uvNA6d.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-DrPIj6XI.js → chunk-V7JOEXUC-fihlnodT.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-BBUOVC5W.js → chunk-VR4S4FIN-nmRUEo1H.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-BEIiZ6gS.js → chunk-XXDRQBXY-CM693yEg.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-DO-wdSFZ.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-DO-wdSFZ.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-ClFTQEyD.js → cose-bilkent-JH36ORCC-CkacPn8W.js} +1 -1
- package/dist/assets/{cynefin-VYW2F7L2-5Bp7xFO0.js → cynefin-VYW2F7L2-BiHaIptG.js} +1 -1
- package/dist/assets/{cynefinDiagram-TSTJHNR4-C-0QlSda.js → cynefinDiagram-TSTJHNR4-CdKxMmiy.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-DLkqsfBy.js → dagre-VKFMJZFB-BQLY5y_W.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-Cwh_tJ_r.js → diagram-FQU43EPY-D7uMBHvq.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-CvvAjulf.js → diagram-G47NLZAW-B3Z1cuH7.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-CCoyx8_e.js → diagram-NH7WQ7WH-BZyRD45e.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-DfjhKYCp.js → diagram-OA4YK3LP-DcThOTt7.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-Vjz0ygfB.js → diagram-WEI45ONY-BcgRAkqY.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-GsA8c6g2.js → ebnfDiagram-CCIWWBDH-CGwfO_xH.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-Cc0lJ9sQ.js → erDiagram-Q63AITRT-pV58-Ncc.js} +1 -1
- package/dist/assets/{flowDiagram-23GEKE2U-C_sotK-k.js → flowDiagram-23GEKE2U-DJ_SqE8h.js} +1 -1
- package/dist/assets/{ganttDiagram-NO4QXBWP-BBtu_WUe.js → ganttDiagram-NO4QXBWP-Dgy0Iyss.js} +1 -1
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-BV_aSB8-.js → gitGraphDiagram-IHSO6WYX-CbiZw9fb.js} +1 -1
- package/dist/assets/{index-CKfOVv2N.js → index-DZTJEk-y.js} +2 -2
- package/dist/assets/{infoDiagram-FWYZ7A6U-DMAvNGtX.js → infoDiagram-FWYZ7A6U-CtsyyEc-.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-DfQB91MZ.js → ishikawaDiagram-FXEZZL3T-BJilNFkK.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-Bq4Llm9o.js → journeyDiagram-5HDEW3XC-C2UCMP4t.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-DTXHiDiE.js → kanban-definition-HUTT4EX6-DmLDJBRy.js} +1 -1
- package/dist/assets/{linear-BQSYZcVg.js → linear-C1paCqE7.js} +1 -1
- package/dist/assets/{mindmap-definition-LN4V7U3C-KuH2NIj0.js → mindmap-definition-LN4V7U3C-CX-RxKVn.js} +1 -1
- package/dist/assets/{pegDiagram-2B236MQR-BKoNGQFr.js → pegDiagram-2B236MQR-CTU32H2W.js} +1 -1
- package/dist/assets/{pieDiagram-ENE6RG2P-EpYv162Y.js → pieDiagram-ENE6RG2P-D8L1aYoo.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-C9deUjQ2.js → quadrantDiagram-ABIIQ3AL-hQ3bXosy.js} +1 -1
- package/dist/assets/{railroadDiagram-RFXS5EU6-Csqw2hK9.js → railroadDiagram-RFXS5EU6--_vuYcda.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE-DCuz3emr.js → requirementDiagram-TGXJPOKE-C6h_m3Az.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-BTeTL24f.js → sankeyDiagram-HTMAVEWB-Dpc7CfIQ.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-Cf1ZkTwO.js → sequenceDiagram-DBY2YBRQ-CK5ZzbvJ.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-D3Lbewzg.js → sizeCapture-X5ZJPWSS-BAxVvM9G.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-O8l37yJI.js → stateDiagram-2N3HPSRC-7j33_2PY.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-GviN0FpJ.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-BikmTh8b.js → swimlanes-5IMT3BWC-CBG2yOob.js} +2 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-D0wMoxl1.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-BOxSosG0.js → timeline-definition-FHXFAJF6-BOnZkQvc.js} +1 -1
- package/dist/assets/{vennDiagram-L72KCM5P-BSClOErZ.js → vennDiagram-L72KCM5P-D52NhIfj.js} +1 -1
- package/dist/assets/{wardleyDiagram-EHGQE667-DHFRjOK0.js → wardleyDiagram-EHGQE667-YoPY2ZU-.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-Dh1v4x-O.js → xychartDiagram-FW5EYKEG-BanNfgtO.js} +1 -1
- package/dist/index.html +1 -1
- package/lib/build-all.js +0 -5
- package/lib/build-stream-schema.js +1 -1
- package/lib/build.js +1809 -2646
- package/lib/consumer-fanout.js +1317 -0
- package/lib/escalation.js +69 -0
- package/lib/feature-validator.js +6 -4
- package/lib/feature-writer.js +58 -3
- package/lib/flow-state.js +15 -14
- package/lib/gsd-budget.js +48 -10
- package/lib/gsd-prompt.js +3 -4
- package/lib/gsd-stuck.js +1 -1
- package/lib/gsd.js +303 -149
- package/lib/lane-gate.js +200 -0
- package/lib/local-claude-connector.js +149 -0
- package/lib/new.js +162 -307
- package/lib/result-normalizer.js +233 -18
- package/lib/review-lenses.js +1 -1
- package/lib/step-prompt.js +41 -119
- package/lib/stratum-engine.js +297 -0
- package/lib/stratum-mcp-client.js +224 -213
- package/lib/triage.js +144 -0
- package/lib/vocabulary-compliance.js +268 -0
- package/lib/vocabulary-inject.js +1 -36
- package/package.json +2 -1
- package/pipelines/build-quick.stratum.yaml +5 -17
- package/pipelines/build.profiles.json +11 -0
- package/pipelines/build.stratum.yaml +288 -467
- package/pipelines/gsd.stratum.yaml +73 -125
- package/pipelines/new.stratum.yaml +68 -149
- package/server/build-routes.js +4 -1
- package/server/design-routes.js +37 -21
- package/server/index.js +13 -21
- package/server/lifecycle-guard.js +1 -1
- package/server/pipeline-routes.js +113 -31
- package/server/stratum-client.js +33 -47
- package/server/stratum-sync.js +3 -4
- package/server/vision-server.js +1 -1
- package/dist/assets/channel-CYErfopw.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-qCC_hnXu.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-qCC_hnXu.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-BWqk_9py.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-C60ICdks.js +0 -8
- package/lib/connector-factory-shim.js +0 -167
- package/server/agent-mcp.js +0 -10
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* escalation.js — Pure escalation decision for the triage lane ladder.
|
|
3
|
+
*
|
|
4
|
+
* COMP-TRIAGE-5 / S03 (see docs/features/COMP-TRIAGE-5/plan.md, blueprint.md C3).
|
|
5
|
+
*
|
|
6
|
+
* `escalate` is a PURE decision function: no file I/O, no imports of build.js
|
|
7
|
+
* or the vision store. The caller (lib/build.js's escalation observer) reads
|
|
8
|
+
* currentPhase from the vision store itself and performs all side effects
|
|
9
|
+
* (un-skipping phases, re-entering, writing checkpoints, human handoff).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Lane ladder, low → high. Exported so callers/tests (and S04) share one order. */
|
|
13
|
+
export const LANE_ORDER = ['trivial', 'standard', 'complex'];
|
|
14
|
+
|
|
15
|
+
/** Bound on escalation attempts before forcing a human handoff (STOP). */
|
|
16
|
+
const MAX_ESCALATIONS = 2;
|
|
17
|
+
|
|
18
|
+
/** Earliest phase to re-enter for each escalated-to lane. */
|
|
19
|
+
const RE_ENTRY_PHASE_BY_LANE = {
|
|
20
|
+
standard: 'blueprint',
|
|
21
|
+
complex: 'design',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Decide whether/how to escalate the current build lane in response to a
|
|
26
|
+
* normalized gate result.
|
|
27
|
+
*
|
|
28
|
+
* @param {{gate: 'review'|'test', passed: boolean}} normalizedGate - normalized gate signal.
|
|
29
|
+
* @param {'trivial'|'standard'|'complex'} currentLane - current lane on the ladder.
|
|
30
|
+
* @param {number} escalationCount - number of escalations already performed for this feature.
|
|
31
|
+
* @param {string} [currentPhase] - caller-supplied current phase (from the vision store).
|
|
32
|
+
* Not read or fetched here — the caller owns all I/O. Reserved for future use to avoid
|
|
33
|
+
* re-entering a phase later than necessary than the lane's default reEntryPhase; ignored
|
|
34
|
+
* in this implementation (see Deviation note below).
|
|
35
|
+
* @returns {null|'STOP'|{nextLane: string, reEntryPhase: string}}
|
|
36
|
+
* - `null` — gate passed, no escalation needed.
|
|
37
|
+
* - `'STOP'` — bounded out (escalationCount >= 2) or already at the top lane ('complex')
|
|
38
|
+
* and the gate failed; caller performs human handoff.
|
|
39
|
+
* - `{nextLane, reEntryPhase}` — escalate one rung up the ladder and re-enter at the
|
|
40
|
+
* earliest phase the new lane requires.
|
|
41
|
+
*/
|
|
42
|
+
export function escalate(normalizedGate, currentLane, escalationCount, currentPhase) {
|
|
43
|
+
// currentPhase is intentionally unused: the design instructs "if unsure, ignore it and
|
|
44
|
+
// document that" (docs/features/COMP-TRIAGE-5/plan.md S03). Accepted as a parameter so
|
|
45
|
+
// the caller's signature is stable for S04, but the reEntryPhase returned here is always
|
|
46
|
+
// the ladder default for the target lane, not narrowed by currentPhase.
|
|
47
|
+
void currentPhase;
|
|
48
|
+
|
|
49
|
+
if (normalizedGate.passed === true) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (escalationCount >= MAX_ESCALATIONS) {
|
|
54
|
+
return 'STOP';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const currentIndex = LANE_ORDER.indexOf(currentLane);
|
|
58
|
+
const nextIndex = currentIndex + 1;
|
|
59
|
+
|
|
60
|
+
if (currentIndex === -1 || nextIndex >= LANE_ORDER.length) {
|
|
61
|
+
// Unknown lane or already at the top rung ('complex') — cannot escalate further.
|
|
62
|
+
return 'STOP';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const nextLane = LANE_ORDER[nextIndex];
|
|
66
|
+
const reEntryPhase = RE_ENTRY_PHASE_BY_LANE[nextLane];
|
|
67
|
+
|
|
68
|
+
return { nextLane, reEntryPhase };
|
|
69
|
+
}
|
package/lib/feature-validator.js
CHANGED
|
@@ -572,14 +572,16 @@ function runArtifactLinkChecks(fctx, ctx, findings) {
|
|
|
572
572
|
}
|
|
573
573
|
// PLANNED / SUPERSEDED / KILLED / PARKED — no finding (expected baseline)
|
|
574
574
|
}
|
|
575
|
-
// MISSING_COMPLETION_REPORT (
|
|
575
|
+
// MISSING_COMPLETION_REPORT (info, COMPLETE)
|
|
576
|
+
// The report phase is OPTIONAL/skippable (small features legitimately never
|
|
577
|
+
// write one), so a missing report.md is informational, not warning-level debt.
|
|
576
578
|
// COMP-BUILD-QUICK-1: the quick lifecycle (compose build --quick) omits the
|
|
577
579
|
// report phase by design and stamps built_via:'build-quick' on feature.json at
|
|
578
|
-
// ship
|
|
580
|
+
// ship — those stay fully exempt (no finding at all).
|
|
579
581
|
if (status === 'COMPLETE' && !folder.files.has('report.md') &&
|
|
580
582
|
featureJson?.built_via !== 'build-quick') {
|
|
581
|
-
findings.push(finding('
|
|
582
|
-
`Feature is COMPLETE
|
|
583
|
+
findings.push(finding('info', 'MISSING_COMPLETION_REPORT', code,
|
|
584
|
+
`Feature is COMPLETE without report.md — the report phase is optional (skippable for small features); informational only`));
|
|
583
585
|
}
|
|
584
586
|
|
|
585
587
|
// DANGLING_ARTIFACT_LINK + ARTIFACT_OUTSIDE_FEATURE_FOLDER
|
package/lib/feature-writer.js
CHANGED
|
@@ -63,6 +63,52 @@ const COMPLEXITIES = new Set(['S', 'M', 'L', 'XL']);
|
|
|
63
63
|
// produced feature.json. Same vocab the ideabox uses (low|medium|high).
|
|
64
64
|
const IMPACTS = new Set(['low', 'medium', 'high']);
|
|
65
65
|
|
|
66
|
+
// COMP-TRIAGE-5: front-of-pipeline scope-estimation vocab. `lane` drives which
|
|
67
|
+
// phases get skipped; `estimateSource` records whether the lane came from the
|
|
68
|
+
// front estimator, the doc-reading refinement, or an escalation bump.
|
|
69
|
+
const LANES = new Set(['trivial', 'standard', 'complex']);
|
|
70
|
+
const ESTIMATE_SOURCES = new Set(['front', 'refined', 'escalated']);
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// validateFeatureFields — shared field-shape guard
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Validate the subset of feature.json fields that carry a fixed vocabulary or
|
|
78
|
+
* range. Called from both addRoadmapEntry (below) and the build write path
|
|
79
|
+
* (lib/build.js) so neither can bypass the guard the way the old inline
|
|
80
|
+
* `COMPLEXITIES.has` check — scoped only to addRoadmapEntry — allowed
|
|
81
|
+
* `complexity: String(triageResult.tier)` (e.g. "0".."4") to reach disk via a
|
|
82
|
+
* raw provider write.
|
|
83
|
+
*
|
|
84
|
+
* Every field is optional; only fields present on `fields` are checked.
|
|
85
|
+
* Throws a plain Error (matching addRoadmapEntry's existing signaling) on the
|
|
86
|
+
* first violation found.
|
|
87
|
+
*
|
|
88
|
+
* @param {object} fields
|
|
89
|
+
* @param {string} [fields.complexity] one of S|M|L|XL
|
|
90
|
+
* @param {number} [fields.triageTier] integer 0-4
|
|
91
|
+
* @param {string} [fields.lane] one of trivial|standard|complex
|
|
92
|
+
* @param {string} [fields.estimateSource] one of front|refined|escalated
|
|
93
|
+
*/
|
|
94
|
+
export function validateFeatureFields(fields) {
|
|
95
|
+
if (fields.complexity !== undefined && !COMPLEXITIES.has(fields.complexity)) {
|
|
96
|
+
throw new Error(`feature-writer: invalid complexity "${fields.complexity}"`);
|
|
97
|
+
}
|
|
98
|
+
if (fields.triageTier !== undefined &&
|
|
99
|
+
(!Number.isInteger(fields.triageTier) || fields.triageTier < 0 || fields.triageTier > 4)) {
|
|
100
|
+
throw new Error(`feature-writer: invalid triageTier "${fields.triageTier}" — must be an integer 0-4`);
|
|
101
|
+
}
|
|
102
|
+
if (fields.lane !== undefined && !LANES.has(fields.lane)) {
|
|
103
|
+
throw new Error(`feature-writer: invalid lane "${fields.lane}" — must be one of ${[...LANES].join(', ')}`);
|
|
104
|
+
}
|
|
105
|
+
if (fields.estimateSource !== undefined && !ESTIMATE_SOURCES.has(fields.estimateSource)) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`feature-writer: invalid estimateSource "${fields.estimateSource}" — must be one of ${[...ESTIMATE_SOURCES].join(', ')}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
66
112
|
// ---------------------------------------------------------------------------
|
|
67
113
|
// Helpers
|
|
68
114
|
// ---------------------------------------------------------------------------
|
|
@@ -115,6 +161,9 @@ function maybeIdempotent(args, fn) {
|
|
|
115
161
|
* @param {string} [args.triageTimestamp] COMP-ROADMAP-PLAN: triage cache stamp
|
|
116
162
|
* @param {string} [args.plannedBy] COMP-ROADMAP-PLAN: originating plan session
|
|
117
163
|
* @param {string} [args.impact] COMP-ROADMAP-PLAN: low | medium | high
|
|
164
|
+
* @param {string} [args.lane] COMP-TRIAGE-5: trivial | standard | complex
|
|
165
|
+
* @param {number} [args.triageTier] COMP-TRIAGE-5: integer 0-4
|
|
166
|
+
* @param {string} [args.estimateSource] COMP-TRIAGE-5: front | refined | escalated
|
|
118
167
|
* @param {boolean} [args.force]
|
|
119
168
|
* @param {string} [args.idempotency_key]
|
|
120
169
|
*/
|
|
@@ -127,9 +176,11 @@ export async function addRoadmapEntry(cwd, args) {
|
|
|
127
176
|
validateCode(args.code);
|
|
128
177
|
if (!args.description) throw new Error('feature-writer: description is required');
|
|
129
178
|
if (!args.phase) throw new Error('feature-writer: phase is required');
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
179
|
+
// COMP-TRIAGE-5: complexity/triageTier/lane/estimateSource shape validation
|
|
180
|
+
// lives in the shared validateFeatureFields guard so the build write path
|
|
181
|
+
// (lib/build.js) can call the same check and can't bypass it the way the
|
|
182
|
+
// old inline `COMPLEXITIES.has` (scoped only to this function) allowed.
|
|
183
|
+
validateFeatureFields(args);
|
|
133
184
|
const status = args.status ?? 'PLANNED';
|
|
134
185
|
if (!STATUSES.has(status)) {
|
|
135
186
|
throw new Error(`feature-writer: invalid status "${status}"`);
|
|
@@ -180,6 +231,10 @@ export async function addRoadmapEntry(cwd, args) {
|
|
|
180
231
|
if (args.triageTimestamp !== undefined) feature.triageTimestamp = args.triageTimestamp;
|
|
181
232
|
if (args.plannedBy !== undefined) feature.plannedBy = args.plannedBy;
|
|
182
233
|
if (args.impact !== undefined) feature.impact = args.impact;
|
|
234
|
+
// COMP-TRIAGE-5: front-of-pipeline scope-estimation fields.
|
|
235
|
+
if (args.lane !== undefined) feature.lane = args.lane;
|
|
236
|
+
if (args.triageTier !== undefined) feature.triageTier = args.triageTier;
|
|
237
|
+
if (args.estimateSource !== undefined) feature.estimateSource = args.estimateSource;
|
|
183
238
|
|
|
184
239
|
let roundtrip = null;
|
|
185
240
|
if (isLocalProvider(provider)) {
|
package/lib/flow-state.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* flow-state.js — small read-only helpers over
|
|
3
|
-
*
|
|
2
|
+
* flow-state.js — small read-only helpers over persisted Stratum flow state.
|
|
3
|
+
*
|
|
4
|
+
* Reads the TS store (`STRATUM_STATE_ROOT/<flowId>.json` when configured,
|
|
5
|
+
* otherwise `~/.stratum/ts/flows/<flowId>.json`).
|
|
4
6
|
*
|
|
5
7
|
* Shared by the gate handlers in build.js and new.js so the gate id can be made
|
|
6
8
|
* round-aware (COMP-PLAN-GATE-LOOP).
|
|
@@ -10,27 +12,26 @@ import { join } from 'node:path';
|
|
|
10
12
|
import { homedir } from 'node:os';
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
|
-
* Read Stratum's current round for a flow from its persisted state
|
|
15
|
+
* Read Stratum's current round for a flow from its persisted state files.
|
|
14
16
|
*
|
|
15
|
-
* The
|
|
16
|
-
*
|
|
17
|
+
* The TS running response does not carry the round, but the persisted flow
|
|
18
|
+
* file does. Threading the round into the gate id
|
|
17
19
|
* (`<flowId>:<stepId>:<round>`) makes each gate re-entry after a `revise` a
|
|
18
20
|
* fresh, pending gate rather than colliding with the prior resolved gate and
|
|
19
21
|
* replaying its stale outcome.
|
|
20
22
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
+
* Fresh TS runs have no `rounds` field and use round 0. A read failure must
|
|
24
|
+
* never block a gate.
|
|
23
25
|
*
|
|
24
26
|
* @param {string} flowId
|
|
25
27
|
* @returns {number}
|
|
26
28
|
*/
|
|
27
29
|
export function readFlowRound(flowId) {
|
|
30
|
+
// TS store — STRATUM_STATE_ROOT-aware; flows are stored flat at the root.
|
|
28
31
|
try {
|
|
29
|
-
const
|
|
30
|
-
const state = JSON.parse(readFileSync(
|
|
31
|
-
const r = state?.
|
|
32
|
-
return Number.isInteger(r) && r >= 0 ? r :
|
|
33
|
-
} catch {
|
|
34
|
-
return 1;
|
|
35
|
-
}
|
|
32
|
+
const tsRoot = process.env.STRATUM_STATE_ROOT || join(homedir(), '.stratum', 'ts', 'flows');
|
|
33
|
+
const state = JSON.parse(readFileSync(join(tsRoot, `${flowId}.json`), 'utf-8'));
|
|
34
|
+
const r = state?.rounds;
|
|
35
|
+
return Number.isInteger(r) && r >= 0 ? r : 0;
|
|
36
|
+
} catch { return 0; }
|
|
36
37
|
}
|
package/lib/gsd-budget.js
CHANGED
|
@@ -62,8 +62,8 @@ export function buildBudgetBlock(cfg = {}) {
|
|
|
62
62
|
if (Object.keys(budget).length > 0) out.budget = budget;
|
|
63
63
|
|
|
64
64
|
if (cfg.per_task_ms != null) {
|
|
65
|
-
//
|
|
66
|
-
//
|
|
65
|
+
// Compose's per-consumer timeout is carried in seconds. Convert from ms and
|
|
66
|
+
// floor at 1s.
|
|
67
67
|
out.taskTimeoutSec = Math.max(1, Math.ceil(cfg.per_task_ms / 1000));
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -91,8 +91,13 @@ export function buildBudgetBlock(cfg = {}) {
|
|
|
91
91
|
*/
|
|
92
92
|
export function injectBudget(specYaml, cfg = {}) {
|
|
93
93
|
const built = buildBudgetBlock(cfg);
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
// D2(a): per_task_ms is a PER-ITEM wall-clock ceiling enforced compose-side
|
|
95
|
+
// (runConsumerIssuance runs each agent), NOT an engine step budget — a step
|
|
96
|
+
// budget on the fanout cannot be per-item (the engine can't know N). So only
|
|
97
|
+
// the honest flow-wide budget is injected here; per_task_ms is threaded to the
|
|
98
|
+
// consumer loop via gsd.budget.per_task_ms.
|
|
99
|
+
if (!built.budget) {
|
|
100
|
+
return specYaml; // identity — nothing to inject into the spec
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
const parsed = YAML.parse(specYaml);
|
|
@@ -102,16 +107,49 @@ export function injectBudget(specYaml, cfg = {}) {
|
|
|
102
107
|
throw new Error('injectBudget: spec has no flows.gsd to attach a budget to');
|
|
103
108
|
}
|
|
104
109
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
flow.budget = parsed.version === 1
|
|
111
|
+
? {
|
|
112
|
+
...(built.budget.max_tokens != null ? { tokens: built.budget.max_tokens } : {}),
|
|
113
|
+
...(built.budget.max_agent_dispatches != null ? { dispatches: built.budget.max_agent_dispatches } : {}),
|
|
114
|
+
...(built.budget.usd != null ? { usd: built.budget.usd } : {}),
|
|
115
|
+
...(built.budget.ms != null ? { ms: built.budget.ms } : {}),
|
|
116
|
+
}
|
|
117
|
+
: built.budget;
|
|
111
118
|
|
|
112
119
|
return YAML.stringify(parsed);
|
|
113
120
|
}
|
|
114
121
|
|
|
122
|
+
/**
|
|
123
|
+
* D2(c): adapt the TS engine's terminal `ledger` ({spent, budget}) into the
|
|
124
|
+
* legacy `budget_state` ({caps, consumed}) shape the GSD diagnostics/recording
|
|
125
|
+
* helpers consume. The python terminal envelope carried `budget_state`
|
|
126
|
+
* directly; the TS engine returns a `ledger` with the Budget key names
|
|
127
|
+
* (usd/tokens/dispatches/ms). Returns null for a missing/empty ledger so
|
|
128
|
+
* callers can `response.budget_state ?? budgetStateFromLedger(response.ledger)`.
|
|
129
|
+
*
|
|
130
|
+
* @param {{ spent?: object, budget?: object, limits?: object }} ledger
|
|
131
|
+
* @returns {{ caps: object, consumed: object } | null}
|
|
132
|
+
*/
|
|
133
|
+
export function budgetStateFromLedger(ledger) {
|
|
134
|
+
if (!ledger || typeof ledger !== 'object') return null;
|
|
135
|
+
const spent = ledger.spent ?? {};
|
|
136
|
+
const caps = ledger.budget ?? ledger.limits ?? {};
|
|
137
|
+
return {
|
|
138
|
+
caps: {
|
|
139
|
+
...(caps.tokens != null ? { max_tokens: caps.tokens } : {}),
|
|
140
|
+
...(caps.dispatches != null ? { max_agent_dispatches: caps.dispatches } : {}),
|
|
141
|
+
...(caps.usd != null ? { usd: caps.usd } : {}),
|
|
142
|
+
...(caps.ms != null ? { ms: caps.ms } : {}),
|
|
143
|
+
},
|
|
144
|
+
consumed: {
|
|
145
|
+
tokens: spent.tokens ?? 0,
|
|
146
|
+
dispatches: spent.dispatches ?? 0,
|
|
147
|
+
dollars: spent.usd ?? 0,
|
|
148
|
+
wall_s: (spent.ms ?? 0) / 1000,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
115
153
|
// Maps a stratum budget axis → human label for diagnostics.
|
|
116
154
|
const AXIS_LABEL = {
|
|
117
155
|
max_tokens: 'tokens',
|
package/lib/gsd-prompt.js
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
//
|
|
3
3
|
// COMP-GSD-2 T4: buildTaskDescription — pure string assembly.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// tasks summary, the gate commands — must be packed inside `task.description`.
|
|
5
|
+
// TS consumer fanout renders each item, but richer per-slice context — the
|
|
6
|
+
// produces/consumes contract, upstream summary, and gate commands — is packed
|
|
7
|
+
// inside `task.description`.
|
|
9
8
|
//
|
|
10
9
|
// This module produces the rich `description` string. It is also used by
|
|
11
10
|
// runGsd as a deterministic fallback when the decompose_gsd agent failed to
|
package/lib/gsd-stuck.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* no-progress are the only new bookkeeping.
|
|
16
16
|
*
|
|
17
17
|
* Consumes BuildStreamEvents from stratum.onEvent inside
|
|
18
|
-
*
|
|
18
|
+
* the TS consumer runner, keyed by event.task_id. GSD runs the execute
|
|
19
19
|
* step max_concurrent:1, so per-task state is unambiguous. Telemetry contract
|
|
20
20
|
* (schema 0.2.7, STRAT-PAR-STREAM-TOOLDETAIL):
|
|
21
21
|
* tool_use_summary.metadata = { tool, summary, ok, duration_ms, input, tool_use_id }
|