@smartmemory/compose 0.1.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/LICENSE +21 -0
- package/README.md +1014 -0
- package/bin/compose.js +1515 -0
- package/dist/assets/_baseUniq-CQwX6VLz.js +1 -0
- package/dist/assets/arc-SxJ2J1sh.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-BykunY1F.js +36 -0
- package/dist/assets/blockDiagram-DXYQGD6D-ohAKBOUw.js +132 -0
- package/dist/assets/c4Diagram-AHTNJAMY-DBDC3ENB.js +10 -0
- package/dist/assets/channel-DGElom1e.js +1 -0
- package/dist/assets/chunk-4BX2VUAB-Cv93Z7uM.js +1 -0
- package/dist/assets/chunk-4TB4RGXK-DE0WBDkj.js +206 -0
- package/dist/assets/chunk-55IACEB6-CE1EXenG.js +1 -0
- package/dist/assets/chunk-EDXVE4YY-DA7Ana6H.js +1 -0
- package/dist/assets/chunk-FMBD7UC4-CTDIPA3p.js +15 -0
- package/dist/assets/chunk-OYMX7WX6-uGBaPaTX.js +231 -0
- package/dist/assets/chunk-QZHKN3VN-CYlnXuUO.js +1 -0
- package/dist/assets/chunk-YZCP3GAM-ojGkzcZK.js +1 -0
- package/dist/assets/classDiagram-6PBFFD2Q-KqWP9wWZ.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-KqWP9wWZ.js +1 -0
- package/dist/assets/clone-DUJKJXd7.js +1 -0
- package/dist/assets/cose-bilkent-S5V4N54A-Bktn9hL-.js +1 -0
- package/dist/assets/dagre-KV5264BT-DFaSzuRF.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-5BDNPKRD-DnfmDzEm.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-Bm8W9YnG.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-B5-TSKvp.js +43 -0
- package/dist/assets/diagram-TYMM5635-ls4rqlky.js +24 -0
- package/dist/assets/erDiagram-SMLLAGMA-giG6WO-r.js +85 -0
- package/dist/assets/flowDiagram-DWJPFMVM-XvlUuz-7.js +162 -0
- package/dist/assets/ganttDiagram-T4ZO3ILL-hLBV57oV.js +292 -0
- package/dist/assets/gitGraphDiagram-UUTBAWPF-BHu3s_Gn.js +106 -0
- package/dist/assets/graph-D0Cfv00Y.js +1 -0
- package/dist/assets/index-CUd6pFGF.css +1 -0
- package/dist/assets/index-DReRlzZI.js +1144 -0
- package/dist/assets/infoDiagram-42DDH7IO-DbqRsOo3.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-UXIWVN3A-DnCdx7zb.js +70 -0
- package/dist/assets/journeyDiagram-VCZTEJTY-CfD7eNcP.js +139 -0
- package/dist/assets/kanban-definition-6JOO6SKY-BYaO9-mK.js +89 -0
- package/dist/assets/katex-DkKDou_j.js +257 -0
- package/dist/assets/layout-Bj72wOEB.js +1 -0
- package/dist/assets/linear-BRFo114D.js +1 -0
- package/dist/assets/min-GCHnKlJS.js +1 -0
- package/dist/assets/mindmap-definition-QFDTVHPH-n0PMebY4.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pieDiagram-DEJITSTG-pN4CljHF.js +30 -0
- package/dist/assets/quadrantDiagram-34T5L4WZ-DNoAy8-D.js +7 -0
- package/dist/assets/requirementDiagram-MS252O5E-BhtY05PT.js +84 -0
- package/dist/assets/sankeyDiagram-XADWPNL6-B6AD-16A.js +10 -0
- package/dist/assets/sequenceDiagram-FGHM5R23-DShHM-uk.js +157 -0
- package/dist/assets/stateDiagram-FHFEXIEX-DMxn7HTo.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-o6PnCs4e.js +1 -0
- package/dist/assets/timeline-definition-GMOUNBTQ-Cdu6uq52.js +120 -0
- package/dist/assets/vennDiagram-DHZGUBPP-CpK29iRe.js +34 -0
- package/dist/assets/wardley-RL74JXVD-BQgSkdcO.js +162 -0
- package/dist/assets/wardleyDiagram-NUSXRM2D-DJHYev6O.js +20 -0
- package/dist/assets/xychartDiagram-5P7HB3ND-1d75pbaO.js +7 -0
- package/dist/index.html +30 -0
- package/lib/agent-chains.js +65 -0
- package/lib/agent-string.js +86 -0
- package/lib/budget-ledger.js +86 -0
- package/lib/build-all.js +162 -0
- package/lib/build-dag.js +120 -0
- package/lib/build-stream-writer.js +190 -0
- package/lib/build.js +2997 -0
- package/lib/capability-checker.js +53 -0
- package/lib/cert-inject.js +38 -0
- package/lib/cli-progress.js +483 -0
- package/lib/constants.js +69 -0
- package/lib/cross-layer-audit.js +84 -0
- package/lib/debug-discipline.js +173 -0
- package/lib/feature-json.js +106 -0
- package/lib/gate-prompt.js +291 -0
- package/lib/gate-tiers.js +194 -0
- package/lib/health-history.js +119 -0
- package/lib/health-score.js +227 -0
- package/lib/ideabox.js +570 -0
- package/lib/import.js +244 -0
- package/lib/migrate-roadmap.js +94 -0
- package/lib/model-pricing.js +67 -0
- package/lib/new.js +413 -0
- package/lib/pipeline-cli.js +489 -0
- package/lib/plan-parser.js +103 -0
- package/lib/qa-scoping.js +474 -0
- package/lib/questionnaire.js +200 -0
- package/lib/resolve-port.js +7 -0
- package/lib/result-normalizer.js +349 -0
- package/lib/review-lenses.js +166 -0
- package/lib/roadmap-gen.js +210 -0
- package/lib/roadmap-parser.js +176 -0
- package/lib/server-probe.js +23 -0
- package/lib/staleness.js +87 -0
- package/lib/step-prompt.js +260 -0
- package/lib/step-validator.js +49 -0
- package/lib/stratum-mcp-client.js +365 -0
- package/lib/team-flag.js +46 -0
- package/lib/test-bootstrap.js +401 -0
- package/lib/triage.js +274 -0
- package/lib/vision-writer.js +391 -0
- package/package.json +111 -0
- package/pipelines/bug-fix.stratum.yaml +230 -0
- package/pipelines/build.stratum.yaml +498 -0
- package/pipelines/content.stratum.yaml +112 -0
- package/pipelines/coverage-sweep.stratum.yaml +52 -0
- package/pipelines/refactor.stratum.yaml +169 -0
- package/pipelines/research.stratum.yaml +88 -0
- package/pipelines/review-fix.stratum.yaml +109 -0
- package/presets/team-feature.stratum.yaml +105 -0
- package/presets/team-research.stratum.yaml +108 -0
- package/presets/team-review.stratum.yaml +106 -0
- package/scripts/agent-activity-hook.sh +31 -0
- package/scripts/agent-error-hook.sh +28 -0
- package/scripts/analyze-orphans.mjs +50 -0
- package/scripts/find-orphans.mjs +26 -0
- package/scripts/fix-phases.mjs +49 -0
- package/scripts/generate-stratum-spec.mjs +137 -0
- package/scripts/import-roadmap.mjs +116 -0
- package/scripts/phase-audit.mjs +33 -0
- package/scripts/run-pipeline.mjs +314 -0
- package/scripts/session-end-hook.sh +18 -0
- package/scripts/session-start-hook.sh +38 -0
- package/scripts/vision-hook.sh +104 -0
- package/scripts/vision-track.mjs +554 -0
- package/scripts/wire-all-orphans.mjs +108 -0
- package/scripts/wire-orphans.mjs +164 -0
- package/server/activity-routes.js +123 -0
- package/server/agent-health.js +197 -0
- package/server/agent-hooks.js +102 -0
- package/server/agent-mcp.js +10 -0
- package/server/agent-registry.js +95 -0
- package/server/agent-server.js +290 -0
- package/server/agent-spawn.js +251 -0
- package/server/agent-templates.js +77 -0
- package/server/artifact-manager.js +247 -0
- package/server/artifact-templates/architecture.md +28 -0
- package/server/artifact-templates/blueprint.md +21 -0
- package/server/artifact-templates/design.md +36 -0
- package/server/artifact-templates/plan.md +25 -0
- package/server/artifact-templates/prd.md +43 -0
- package/server/artifact-templates/report.md +40 -0
- package/server/block-tracker.js +90 -0
- package/server/build-stream-bridge.js +502 -0
- package/server/coalescing-buffer.js +46 -0
- package/server/compose-mcp-tools.js +479 -0
- package/server/compose-mcp.js +324 -0
- package/server/connectors/agent-connector.js +78 -0
- package/server/connectors/claude-sdk-connector.js +198 -0
- package/server/connectors/codex-connector.js +240 -0
- package/server/connectors/connector-discovery.js +18 -0
- package/server/connectors/connector-runtime.js +13 -0
- package/server/connectors/opencode-connector.js +200 -0
- package/server/design-routes.js +540 -0
- package/server/design-session.js +161 -0
- package/server/feature-scan.js +593 -0
- package/server/file-watcher.js +284 -0
- package/server/find-root.js +29 -0
- package/server/graph-export.js +343 -0
- package/server/ideabox-cache.js +77 -0
- package/server/ideabox-routes.js +294 -0
- package/server/index.js +156 -0
- package/server/model-tiers.js +49 -0
- package/server/pipeline-routes.js +288 -0
- package/server/policy-evaluator.js +36 -0
- package/server/project-root.js +122 -0
- package/server/security.js +23 -0
- package/server/session-manager.js +403 -0
- package/server/session-routes.js +190 -0
- package/server/session-store.js +107 -0
- package/server/settings-routes.js +35 -0
- package/server/settings-store.js +234 -0
- package/server/stratum-api.js +102 -0
- package/server/stratum-client.js +192 -0
- package/server/stratum-sync.js +193 -0
- package/server/summarizer.js +139 -0
- package/server/supervisor.js +196 -0
- package/server/vision-routes.js +668 -0
- package/server/vision-server.js +393 -0
- package/server/vision-store.js +360 -0
- package/server/vision-utils.js +179 -0
- package/server/worktree-gc.js +137 -0
- package/templates/ROADMAP.md +46 -0
package/lib/build-dag.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* build-dag.js — Build a dependency DAG from roadmap feature entries.
|
|
3
|
+
*
|
|
4
|
+
* Dependencies are positional:
|
|
5
|
+
* - Items within the same phase depend on the previous item (sequential chain)
|
|
6
|
+
* - The first item of phase N depends on the last item of phase N-1 (cross-phase edge)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {{ code: string, deps: string[] }} DagNode
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build a DAG from roadmap entries.
|
|
15
|
+
*
|
|
16
|
+
* @param {import('./roadmap-parser.js').FeatureEntry[]} entries - Full ordered list from parseRoadmap()
|
|
17
|
+
* @returns {DagNode[]}
|
|
18
|
+
*/
|
|
19
|
+
export function buildDag(entries) {
|
|
20
|
+
if (entries.length === 0) return [];
|
|
21
|
+
|
|
22
|
+
// Deduplicate by code — keep first occurrence only
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
const unique = [];
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
if (!seen.has(entry.code)) {
|
|
27
|
+
seen.add(entry.code);
|
|
28
|
+
unique.push(entry);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Group by phaseId preserving encounter order
|
|
33
|
+
const phases = new Map();
|
|
34
|
+
for (const entry of unique) {
|
|
35
|
+
if (!phases.has(entry.phaseId)) {
|
|
36
|
+
phases.set(entry.phaseId, []);
|
|
37
|
+
}
|
|
38
|
+
phases.get(entry.phaseId).push(entry);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Build adjacency: code → Set<depCode>
|
|
42
|
+
const deps = new Map();
|
|
43
|
+
for (const entry of unique) {
|
|
44
|
+
deps.set(entry.code, new Set());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let lastPhaseLastCode = null;
|
|
48
|
+
|
|
49
|
+
for (const [, phaseEntries] of phases) {
|
|
50
|
+
let prevCode = null;
|
|
51
|
+
|
|
52
|
+
for (const entry of phaseEntries) {
|
|
53
|
+
// Cross-phase edge: first item depends on last item of previous phase
|
|
54
|
+
if (prevCode === null && lastPhaseLastCode !== null) {
|
|
55
|
+
deps.get(entry.code).add(lastPhaseLastCode);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Within-phase sequential chain
|
|
59
|
+
if (prevCode !== null) {
|
|
60
|
+
deps.get(entry.code).add(prevCode);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
prevCode = entry.code;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
lastPhaseLastCode = prevCode;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return unique.map(e => ({
|
|
70
|
+
code: e.code,
|
|
71
|
+
deps: [...deps.get(e.code)],
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Topological sort via Kahn's algorithm.
|
|
77
|
+
* Returns feature codes in build order (dependencies first).
|
|
78
|
+
*
|
|
79
|
+
* @param {DagNode[]} nodes
|
|
80
|
+
* @returns {string[]}
|
|
81
|
+
* @throws {Error} if a cycle is detected
|
|
82
|
+
*/
|
|
83
|
+
export function topoSort(nodes) {
|
|
84
|
+
const inDegree = new Map();
|
|
85
|
+
const successors = new Map();
|
|
86
|
+
|
|
87
|
+
for (const node of nodes) {
|
|
88
|
+
if (!inDegree.has(node.code)) inDegree.set(node.code, 0);
|
|
89
|
+
if (!successors.has(node.code)) successors.set(node.code, []);
|
|
90
|
+
|
|
91
|
+
for (const dep of node.deps) {
|
|
92
|
+
if (!inDegree.has(dep)) inDegree.set(dep, 0);
|
|
93
|
+
if (!successors.has(dep)) successors.set(dep, []);
|
|
94
|
+
successors.get(dep).push(node.code);
|
|
95
|
+
inDegree.set(node.code, inDegree.get(node.code) + 1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const queue = [];
|
|
100
|
+
for (const [code, degree] of inDegree) {
|
|
101
|
+
if (degree === 0) queue.push(code);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const order = [];
|
|
105
|
+
while (queue.length > 0) {
|
|
106
|
+
const code = queue.shift();
|
|
107
|
+
order.push(code);
|
|
108
|
+
for (const succ of successors.get(code) ?? []) {
|
|
109
|
+
const newDegree = inDegree.get(succ) - 1;
|
|
110
|
+
inDegree.set(succ, newDegree);
|
|
111
|
+
if (newDegree === 0) queue.push(succ);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (order.length !== inDegree.size) {
|
|
116
|
+
throw new Error('Cycle detected in feature dependency graph');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return order;
|
|
120
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BuildStreamWriter — appends JSONL events to .compose/build-stream.jsonl
|
|
3
|
+
*
|
|
4
|
+
* Used by build.js to emit build lifecycle events that the agent-server's
|
|
5
|
+
* BuildStreamBridge tails and rebroadcasts via SSE.
|
|
6
|
+
*
|
|
7
|
+
* Sync I/O is intentional — JSONL lines are small and the CLI is already
|
|
8
|
+
* I/O-bound on agent calls between writes.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { mkdirSync, appendFileSync, unlinkSync, existsSync, readFileSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
|
|
14
|
+
export class BuildStreamWriter {
|
|
15
|
+
#path;
|
|
16
|
+
#seq = 0;
|
|
17
|
+
#featureCode;
|
|
18
|
+
#closed = false;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} composeDir Path to .compose directory
|
|
22
|
+
* @param {string} featureCode Feature code (e.g. 'STRAT-COMP-7')
|
|
23
|
+
* @param {object} [opts]
|
|
24
|
+
* @param {boolean} [opts.truncate=false] Truncate existing stream (fresh builds only)
|
|
25
|
+
*/
|
|
26
|
+
constructor(composeDir, featureCode, { truncate = false } = {}) {
|
|
27
|
+
mkdirSync(composeDir, { recursive: true });
|
|
28
|
+
this.#path = join(composeDir, 'build-stream.jsonl');
|
|
29
|
+
this.#featureCode = featureCode;
|
|
30
|
+
|
|
31
|
+
// Only truncate on fresh builds — resumed builds append to existing stream
|
|
32
|
+
if (truncate && existsSync(this.#path)) {
|
|
33
|
+
unlinkSync(this.#path);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// On resume: read the last _seq from the existing JSONL so new events
|
|
37
|
+
// have monotonically increasing _seq values. The SSE bridge deduplicates
|
|
38
|
+
// on _seq, so restarting from 0 would cause all resumed events to be dropped.
|
|
39
|
+
if (!truncate && existsSync(this.#path)) {
|
|
40
|
+
try {
|
|
41
|
+
const content = readFileSync(this.#path, 'utf-8').trimEnd();
|
|
42
|
+
if (content) {
|
|
43
|
+
const lastLine = content.slice(content.lastIndexOf('\n') + 1);
|
|
44
|
+
const lastEvent = JSON.parse(lastLine);
|
|
45
|
+
if (typeof lastEvent._seq === 'number') {
|
|
46
|
+
this.#seq = lastEvent._seq + 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
// Corrupt file — start from 0, bridge will handle duplicates
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Append a JSONL event with auto-incremented _seq and _ts fields.
|
|
57
|
+
* @param {object} event Event payload (must include `type`)
|
|
58
|
+
*/
|
|
59
|
+
write(event) {
|
|
60
|
+
const line = JSON.stringify({
|
|
61
|
+
...event,
|
|
62
|
+
_seq: this.#seq++,
|
|
63
|
+
_ts: Date.now(),
|
|
64
|
+
});
|
|
65
|
+
appendFileSync(this.#path, line + '\n');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Emit a capability_profile event noting the active template for a step.
|
|
70
|
+
* Informational only — never blocks or fails the build.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} stepId Step ID
|
|
73
|
+
* @param {string} agent Full agent string, e.g. "claude:read-only-reviewer"
|
|
74
|
+
* @param {string|null} templateName Template name, or null if no template
|
|
75
|
+
* @param {string[]|null} allowedTools Allowed tools list from template
|
|
76
|
+
* @param {string[]|null} disallowedTools Disallowed tools list from template
|
|
77
|
+
*/
|
|
78
|
+
writeCapabilityProfile(stepId, agent, templateName, allowedTools, disallowedTools) {
|
|
79
|
+
this.write({
|
|
80
|
+
type: 'capability_profile',
|
|
81
|
+
stepId,
|
|
82
|
+
agent,
|
|
83
|
+
template: templateName,
|
|
84
|
+
allowedTools,
|
|
85
|
+
disallowedTools,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Emit a capability_violation event for informational audit.
|
|
91
|
+
* Violations are never blocking in v1.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} stepId Step ID where the violation was detected
|
|
94
|
+
* @param {string} agent Full agent string
|
|
95
|
+
* @param {string} templateName Active template name
|
|
96
|
+
* @param {string} detail Description of the violation
|
|
97
|
+
*/
|
|
98
|
+
writeViolation(stepId, agent, templateName, detail) {
|
|
99
|
+
this.write({
|
|
100
|
+
type: 'capability_violation',
|
|
101
|
+
stepId,
|
|
102
|
+
agent,
|
|
103
|
+
template: templateName,
|
|
104
|
+
detail,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Emit a per-step usage event with token and cost data (COMP-OBS-COST).
|
|
110
|
+
*
|
|
111
|
+
* @param {string} stepId Step ID this usage belongs to
|
|
112
|
+
* @param {object} usage Usage object from result-normalizer
|
|
113
|
+
* @param {number} usage.input_tokens
|
|
114
|
+
* @param {number} usage.output_tokens
|
|
115
|
+
* @param {number} [usage.cache_creation_input_tokens]
|
|
116
|
+
* @param {number} [usage.cache_read_input_tokens]
|
|
117
|
+
* @param {number} usage.cost_usd
|
|
118
|
+
* @param {string|null} [usage.model]
|
|
119
|
+
*/
|
|
120
|
+
writeUsage(stepId, usage) {
|
|
121
|
+
this.write({
|
|
122
|
+
type: 'step_usage',
|
|
123
|
+
stepId,
|
|
124
|
+
input_tokens: usage.input_tokens ?? 0,
|
|
125
|
+
output_tokens: usage.output_tokens ?? 0,
|
|
126
|
+
cache_creation_input_tokens: usage.cache_creation_input_tokens ?? 0,
|
|
127
|
+
cache_read_input_tokens: usage.cache_read_input_tokens ?? 0,
|
|
128
|
+
cost_usd: usage.cost_usd ?? 0,
|
|
129
|
+
model: usage.model ?? null,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Emit a gate_tier_result event for a step's tier classification result (COMP-OBS-GATES).
|
|
135
|
+
*
|
|
136
|
+
* @param {string} stepId Pipeline step ID (e.g. 'review', 'coverage_check')
|
|
137
|
+
* @param {string} tierId Tier ID (e.g. 'T3')
|
|
138
|
+
* @param {boolean} passed Whether this tier passed
|
|
139
|
+
* @param {string|null} details Optional summary / detail string
|
|
140
|
+
*/
|
|
141
|
+
writeGateTier(stepId, tierId, passed, details = null) {
|
|
142
|
+
this.write({
|
|
143
|
+
type: 'gate_tier_result',
|
|
144
|
+
stepId,
|
|
145
|
+
tierId,
|
|
146
|
+
passed,
|
|
147
|
+
details,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Emit a health_score event with the composite score and dimension breakdown.
|
|
153
|
+
* COMP-HEALTH item 118: emitted after build_end so consumers can display it.
|
|
154
|
+
*
|
|
155
|
+
* @param {number} score Composite score 0-100
|
|
156
|
+
* @param {object} breakdown Per-dimension scores { dim: number }
|
|
157
|
+
* @param {string[]} [missing] Dimensions not scored (no signal available)
|
|
158
|
+
*/
|
|
159
|
+
writeHealthScore(score, breakdown, missing = []) {
|
|
160
|
+
this.write({
|
|
161
|
+
type: 'health_score',
|
|
162
|
+
score,
|
|
163
|
+
breakdown,
|
|
164
|
+
missing,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Write a build_end sentinel and mark the writer as closed.
|
|
170
|
+
* Idempotent — calling multiple times writes exactly one build_end.
|
|
171
|
+
* @param {string} [status='complete'] Build exit status
|
|
172
|
+
* @param {object} [costTotals] Optional cumulative cost/token totals
|
|
173
|
+
*/
|
|
174
|
+
close(status = 'complete', costTotals = null) {
|
|
175
|
+
if (this.#closed) return;
|
|
176
|
+
this.#closed = true;
|
|
177
|
+
const payload = { type: 'build_end', status, featureCode: this.#featureCode };
|
|
178
|
+
if (costTotals) {
|
|
179
|
+
payload.total_input_tokens = costTotals.input_tokens ?? 0;
|
|
180
|
+
payload.total_output_tokens = costTotals.output_tokens ?? 0;
|
|
181
|
+
payload.total_cost_usd = costTotals.cost_usd ?? 0;
|
|
182
|
+
}
|
|
183
|
+
this.write(payload);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** @returns {string} Absolute path to the JSONL file */
|
|
187
|
+
get filePath() {
|
|
188
|
+
return this.#path;
|
|
189
|
+
}
|
|
190
|
+
}
|