@smartmemory/compose 0.1.1-beta → 0.1.2-beta

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.
Files changed (124) hide show
  1. package/.claude/skills/bug-fix/SKILL.md +143 -0
  2. package/.claude/skills/compose/SKILL.md +604 -0
  3. package/.compose-deps.json +89 -0
  4. package/README.md +14 -3
  5. package/bin/compose.js +473 -0
  6. package/contracts/comp-obs-contract.schema.json +362 -0
  7. package/contracts/cross-model-review-result.json +78 -0
  8. package/contracts/review-result.json +126 -0
  9. package/dist/assets/{_baseUniq-CQwX6VLz.js → _baseUniq-D-avYfn5.js} +1 -1
  10. package/dist/assets/{arc-SxJ2J1sh.js → arc-BC4dfQ-X.js} +1 -1
  11. package/dist/assets/{architectureDiagram-Q4EWVU46-BykunY1F.js → architectureDiagram-Q4EWVU46-BZmFXnGI.js} +1 -1
  12. package/dist/assets/{blockDiagram-DXYQGD6D-ohAKBOUw.js → blockDiagram-DXYQGD6D-DlfWSuux.js} +1 -1
  13. package/dist/assets/{c4Diagram-AHTNJAMY-DBDC3ENB.js → c4Diagram-AHTNJAMY-Y__uJrRx.js} +1 -1
  14. package/dist/assets/channel-LRG9kHqJ.js +1 -0
  15. package/dist/assets/{chunk-4BX2VUAB-Cv93Z7uM.js → chunk-4BX2VUAB-BfMePfTp.js} +1 -1
  16. package/dist/assets/{chunk-4TB4RGXK-DE0WBDkj.js → chunk-4TB4RGXK-BdlMSdEA.js} +1 -1
  17. package/dist/assets/{chunk-55IACEB6-CE1EXenG.js → chunk-55IACEB6-vrQHZTdv.js} +1 -1
  18. package/dist/assets/{chunk-EDXVE4YY-DA7Ana6H.js → chunk-EDXVE4YY-B8wioVlW.js} +1 -1
  19. package/dist/assets/{chunk-FMBD7UC4-CTDIPA3p.js → chunk-FMBD7UC4-Cd6Hrux2.js} +1 -1
  20. package/dist/assets/{chunk-OYMX7WX6-uGBaPaTX.js → chunk-OYMX7WX6-CfrhdQXY.js} +1 -1
  21. package/dist/assets/{chunk-QZHKN3VN-CYlnXuUO.js → chunk-QZHKN3VN-B9JQerOU.js} +1 -1
  22. package/dist/assets/{chunk-YZCP3GAM-ojGkzcZK.js → chunk-YZCP3GAM-DFN9X99H.js} +1 -1
  23. package/dist/assets/classDiagram-6PBFFD2Q-BC9a6pDE.js +1 -0
  24. package/dist/assets/classDiagram-v2-HSJHXN6E-BC9a6pDE.js +1 -0
  25. package/dist/assets/clone-dRxgFrBv.js +1 -0
  26. package/dist/assets/{cose-bilkent-S5V4N54A-Bktn9hL-.js → cose-bilkent-S5V4N54A-BAn0ap_E.js} +1 -1
  27. package/dist/assets/{dagre-KV5264BT-DFaSzuRF.js → dagre-KV5264BT-DyxnVq1g.js} +1 -1
  28. package/dist/assets/{diagram-5BDNPKRD-DnfmDzEm.js → diagram-5BDNPKRD-XCrzqski.js} +1 -1
  29. package/dist/assets/{diagram-G4DWMVQ6-Bm8W9YnG.js → diagram-G4DWMVQ6-MBCAXft_.js} +1 -1
  30. package/dist/assets/{diagram-MMDJMWI5-B5-TSKvp.js → diagram-MMDJMWI5-DbtB2yS6.js} +1 -1
  31. package/dist/assets/{diagram-TYMM5635-ls4rqlky.js → diagram-TYMM5635-Bb5NzX61.js} +1 -1
  32. package/dist/assets/{erDiagram-SMLLAGMA-giG6WO-r.js → erDiagram-SMLLAGMA-CpIeCOh2.js} +1 -1
  33. package/dist/assets/{flowDiagram-DWJPFMVM-XvlUuz-7.js → flowDiagram-DWJPFMVM-CHyoKnhW.js} +1 -1
  34. package/dist/assets/{ganttDiagram-T4ZO3ILL-hLBV57oV.js → ganttDiagram-T4ZO3ILL-DErKteO_.js} +1 -1
  35. package/dist/assets/{gitGraphDiagram-UUTBAWPF-BHu3s_Gn.js → gitGraphDiagram-UUTBAWPF-KFVAtj2F.js} +1 -1
  36. package/dist/assets/{graph-D0Cfv00Y.js → graph-CRnO_ifT.js} +1 -1
  37. package/dist/assets/index-DKBsEUJ-.css +1 -0
  38. package/dist/assets/index-DkRKLuNr.js +1144 -0
  39. package/dist/assets/{infoDiagram-42DDH7IO-DbqRsOo3.js → infoDiagram-42DDH7IO-BZFnuSp5.js} +1 -1
  40. package/dist/assets/{ishikawaDiagram-UXIWVN3A-DnCdx7zb.js → ishikawaDiagram-UXIWVN3A-4Xe2Szde.js} +1 -1
  41. package/dist/assets/{journeyDiagram-VCZTEJTY-CfD7eNcP.js → journeyDiagram-VCZTEJTY-CZRByfS-.js} +1 -1
  42. package/dist/assets/{kanban-definition-6JOO6SKY-BYaO9-mK.js → kanban-definition-6JOO6SKY-B95sk6Fk.js} +1 -1
  43. package/dist/assets/{layout-Bj72wOEB.js → layout-BqNQzxWT.js} +1 -1
  44. package/dist/assets/{linear-BRFo114D.js → linear-CUh7qb64.js} +1 -1
  45. package/dist/assets/{min-GCHnKlJS.js → min-wXgOS3ig.js} +1 -1
  46. package/dist/assets/{mindmap-definition-QFDTVHPH-n0PMebY4.js → mindmap-definition-QFDTVHPH-DB6iaAbO.js} +1 -1
  47. package/dist/assets/{pieDiagram-DEJITSTG-pN4CljHF.js → pieDiagram-DEJITSTG-CHkZHrTW.js} +1 -1
  48. package/dist/assets/{quadrantDiagram-34T5L4WZ-DNoAy8-D.js → quadrantDiagram-34T5L4WZ-DoTEO8e3.js} +1 -1
  49. package/dist/assets/{requirementDiagram-MS252O5E-BhtY05PT.js → requirementDiagram-MS252O5E-Dn8peXYp.js} +1 -1
  50. package/dist/assets/{sankeyDiagram-XADWPNL6-B6AD-16A.js → sankeyDiagram-XADWPNL6-DRXs6Ipb.js} +1 -1
  51. package/dist/assets/{sequenceDiagram-FGHM5R23-DShHM-uk.js → sequenceDiagram-FGHM5R23-wBBYZ0aq.js} +1 -1
  52. package/dist/assets/{stateDiagram-FHFEXIEX-DMxn7HTo.js → stateDiagram-FHFEXIEX-DPlBNGmf.js} +1 -1
  53. package/dist/assets/stateDiagram-v2-QKLJ7IA2-BW0ezXb4.js +1 -0
  54. package/dist/assets/{timeline-definition-GMOUNBTQ-Cdu6uq52.js → timeline-definition-GMOUNBTQ-CbbyTlHk.js} +1 -1
  55. package/dist/assets/{vennDiagram-DHZGUBPP-CpK29iRe.js → vennDiagram-DHZGUBPP-Bj4GaFfj.js} +1 -1
  56. package/dist/assets/{wardley-RL74JXVD-BQgSkdcO.js → wardley-RL74JXVD-RtNzq8KU.js} +55 -55
  57. package/dist/assets/{wardleyDiagram-NUSXRM2D-DJHYev6O.js → wardleyDiagram-NUSXRM2D-CDfE3zSj.js} +1 -1
  58. package/dist/assets/{xychartDiagram-5P7HB3ND-1d75pbaO.js → xychartDiagram-5P7HB3ND-CZXHHYD5.js} +1 -1
  59. package/dist/index.html +2 -2
  60. package/lib/budget-ledger.js +45 -0
  61. package/lib/bug-bisect.js +292 -0
  62. package/lib/bug-checkpoint.js +191 -0
  63. package/lib/bug-escalation.js +306 -0
  64. package/lib/bug-index-gen.js +136 -0
  65. package/lib/bug-ledger.js +126 -0
  66. package/lib/build-stream-schema.js +176 -0
  67. package/lib/build-stream-writer.js +3 -1
  68. package/lib/build.js +854 -284
  69. package/lib/connector-factory-shim.js +167 -0
  70. package/lib/constants.js +18 -0
  71. package/lib/debug-discipline.js +176 -27
  72. package/lib/deps.js +205 -0
  73. package/lib/health-score.js +4 -4
  74. package/lib/import.js +26 -13
  75. package/lib/inject-schema.js +21 -0
  76. package/lib/new.js +27 -53
  77. package/lib/result-normalizer.js +160 -144
  78. package/lib/review-lenses.js +5 -5
  79. package/lib/review-normalize.js +413 -0
  80. package/lib/review-prompt.js +163 -0
  81. package/lib/sections.js +325 -0
  82. package/lib/step-prompt.js +21 -1
  83. package/lib/step-validator.js +5 -3
  84. package/lib/stratum-mcp-client.js +172 -7
  85. package/package.json +14 -3
  86. package/pipelines/bug-fix.stratum.yaml +39 -1
  87. package/pipelines/build.stratum.yaml +28 -45
  88. package/pipelines/review-fix.stratum.yaml +1 -1
  89. package/presets/team-review.stratum.yaml +21 -14
  90. package/server/build-stream-bridge.js +28 -0
  91. package/server/cc-session-feature-resolver.js +111 -0
  92. package/server/cc-session-reader.js +327 -0
  93. package/server/cc-session-watcher.js +318 -0
  94. package/server/compose-mcp-tools.js +0 -125
  95. package/server/compose-mcp.js +2 -4
  96. package/server/contract-diff.js +192 -0
  97. package/server/decision-event-emit.js +175 -0
  98. package/server/decision-event-id.js +64 -0
  99. package/server/decision-events-snapshot.js +166 -0
  100. package/server/design-routes.js +92 -49
  101. package/server/drift-axes.js +365 -0
  102. package/server/drift-emit.js +121 -0
  103. package/server/gate-log-store.js +102 -0
  104. package/server/lifecycle-phase-history.js +44 -0
  105. package/server/open-loops-store.js +102 -0
  106. package/server/schema-validator.js +49 -0
  107. package/server/status-emit.js +27 -0
  108. package/server/status-snapshot.js +218 -0
  109. package/server/vision-routes.js +332 -4
  110. package/server/vision-server.js +104 -12
  111. package/server/vision-store.js +21 -0
  112. package/dist/assets/channel-DGElom1e.js +0 -1
  113. package/dist/assets/classDiagram-6PBFFD2Q-KqWP9wWZ.js +0 -1
  114. package/dist/assets/classDiagram-v2-HSJHXN6E-KqWP9wWZ.js +0 -1
  115. package/dist/assets/clone-DUJKJXd7.js +0 -1
  116. package/dist/assets/index-CUd6pFGF.css +0 -1
  117. package/dist/assets/index-DReRlzZI.js +0 -1144
  118. package/dist/assets/stateDiagram-v2-QKLJ7IA2-o6PnCs4e.js +0 -1
  119. package/server/connectors/agent-connector.js +0 -78
  120. package/server/connectors/claude-sdk-connector.js +0 -198
  121. package/server/connectors/codex-connector.js +0 -240
  122. package/server/connectors/connector-discovery.js +0 -18
  123. package/server/connectors/connector-runtime.js +0 -13
  124. package/server/connectors/opencode-connector.js +0 -200
@@ -0,0 +1,126 @@
1
+ /**
2
+ * bug-ledger.js — COMP-FIX-HARD hypothesis ledger persistence.
3
+ *
4
+ * Persists hypothesis entries to docs/bugs/<bug-code>/hypotheses.jsonl
5
+ * (append-only JSONL). One JSON object per line.
6
+ *
7
+ * Idempotent on (attempt, ts): repeated writes with the same key are skipped
8
+ * (the first writer wins). Tolerates malformed lines on read (skip + warn).
9
+ *
10
+ * Pattern reference: server/gate-log-store.js (gate log JSONL helpers).
11
+ *
12
+ * Entry shape:
13
+ * Required: attempt (number), ts (ISO string), hypothesis (string),
14
+ * verdict ('confirmed' | 'rejected' | 'inconclusive')
15
+ * Optional: evidence_for[], evidence_against[], next_to_try, agent,
16
+ * tokens_used, findings[]
17
+ */
18
+
19
+ import { appendFileSync, readFileSync, existsSync, mkdirSync } from 'node:fs';
20
+ import { join, dirname } from 'node:path';
21
+
22
+ /**
23
+ * Resolve the on-disk path for a bug's hypothesis ledger.
24
+ * @param {string} cwd — repository root
25
+ * @param {string} bugCode — bug identifier (e.g. "BUG-123")
26
+ * @returns {string} absolute path to hypotheses.jsonl
27
+ */
28
+ export function getHypothesesPath(cwd, bugCode) {
29
+ return join(cwd, 'docs', 'bugs', bugCode, 'hypotheses.jsonl');
30
+ }
31
+
32
+ /**
33
+ * Append one hypothesis entry to the bug's ledger.
34
+ * Idempotent: if an entry with the same (attempt, ts) already exists, the
35
+ * write is skipped. Creates the parent directory if missing.
36
+ *
37
+ * @param {string} cwd
38
+ * @param {string} bugCode
39
+ * @param {object} entry — must have attempt, ts, hypothesis, verdict
40
+ */
41
+ export function appendHypothesisEntry(cwd, bugCode, entry) {
42
+ const filePath = getHypothesesPath(cwd, bugCode);
43
+ mkdirSync(dirname(filePath), { recursive: true });
44
+
45
+ // Idempotency check: scan existing entries for matching (attempt, ts).
46
+ // Volume per bug is small so a linear scan is fine.
47
+ if (existsSync(filePath)) {
48
+ const raw = readFileSync(filePath, 'utf8');
49
+ for (const line of raw.split('\n')) {
50
+ const trimmed = line.trim();
51
+ if (!trimmed) continue;
52
+ try {
53
+ const obj = JSON.parse(trimmed);
54
+ if (obj.attempt === entry.attempt && obj.ts === entry.ts) return; // already written
55
+ } catch {
56
+ // malformed line — skip
57
+ }
58
+ }
59
+ }
60
+
61
+ appendFileSync(filePath, JSON.stringify(entry) + '\n', 'utf8');
62
+ }
63
+
64
+ /**
65
+ * Read all hypothesis entries for a bug.
66
+ * Returns [] if the file does not exist. Tolerates malformed lines: each
67
+ * unparseable line is skipped with a stderr warning, valid lines returned.
68
+ *
69
+ * @param {string} cwd
70
+ * @param {string} bugCode
71
+ * @returns {object[]}
72
+ */
73
+ export function readHypotheses(cwd, bugCode) {
74
+ const filePath = getHypothesesPath(cwd, bugCode);
75
+ if (!existsSync(filePath)) return [];
76
+
77
+ const raw = readFileSync(filePath, 'utf8');
78
+ const entries = [];
79
+
80
+ for (const line of raw.split('\n')) {
81
+ const trimmed = line.trim();
82
+ if (!trimmed) continue;
83
+ try {
84
+ entries.push(JSON.parse(trimmed));
85
+ } catch {
86
+ console.warn('[bug-ledger] malformed line skipped:', trimmed.slice(0, 80));
87
+ }
88
+ }
89
+
90
+ return entries;
91
+ }
92
+
93
+ /**
94
+ * Render rejected hypotheses as a markdown block, suitable for splicing into
95
+ * a bug-fix prompt so the next attempt avoids re-trying dead ends.
96
+ *
97
+ * Returns "" if the input has no entries with verdict === 'rejected'.
98
+ *
99
+ * @param {object[]} entries
100
+ * @returns {string}
101
+ */
102
+ export function formatRejectedHypotheses(entries) {
103
+ if (!Array.isArray(entries) || entries.length === 0) return '';
104
+ const rejected = entries.filter((e) => e && e.verdict === 'rejected');
105
+ if (rejected.length === 0) return '';
106
+
107
+ const blocks = rejected.map((e) => {
108
+ const lines = [];
109
+ lines.push(`### Attempt ${e.attempt} (${e.ts})`);
110
+ lines.push(`**Hypothesis:** ${e.hypothesis}`);
111
+ if (Array.isArray(e.evidence_against) && e.evidence_against.length > 0) {
112
+ lines.push('**Evidence against:**');
113
+ for (const ev of e.evidence_against) lines.push(`- ${ev}`);
114
+ }
115
+ if (Array.isArray(e.evidence_for) && e.evidence_for.length > 0) {
116
+ lines.push('**Evidence for:**');
117
+ for (const ev of e.evidence_for) lines.push(`- ${ev}`);
118
+ }
119
+ if (e.next_to_try) {
120
+ lines.push(`**Next to try:** ${e.next_to_try}`);
121
+ }
122
+ return lines.join('\n');
123
+ });
124
+
125
+ return ['## Previously Rejected Hypotheses', '', ...blocks].join('\n\n') + '\n';
126
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * build-stream-schema.js — JSON Schema validator for BuildStreamEvent envelopes.
3
+ *
4
+ * STRAT-PAR-STREAM-CONSUMER-VALIDATE: validates incoming progress notifications
5
+ * in stratum-mcp-client.js#dispatchEvent against the v0.2.6 envelope schema.
6
+ *
7
+ * Design decisions:
8
+ * - Uses AJV (already in compose deps) compiled once at module load.
9
+ * - On validation failure the caller should warn+drop — never throw.
10
+ * - KNOWN_VERSIONS: set of accepted schema_version strings. v0.2.5 accepted for
11
+ * one-cycle backward compatibility; v0.2.6 is current.
12
+ * - reply_required (Option A, STRAT-PAR-STREAM-CONSUMER-VALIDATE design):
13
+ * optional boolean reserved for future gate/permission/question kinds.
14
+ */
15
+
16
+ import Ajv2020 from 'ajv/dist/2020.js';
17
+
18
+ export const KNOWN_VERSIONS = new Set(['0.2.5', '0.2.6']);
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Envelope schema (top-level fields only; metadata shape is kind-specific)
22
+ // ---------------------------------------------------------------------------
23
+
24
+ const ENVELOPE_SCHEMA = {
25
+ type: 'object',
26
+ required: ['schema_version', 'flow_id', 'step_id', 'seq', 'ts', 'kind', 'metadata'],
27
+ // Closed envelope — matches the producer contract exactly (events.py + build-stream-event.v0.2.6.schema.json).
28
+ // Unknown top-level fields are rejected so envelope-level producer drift is caught.
29
+ additionalProperties: false,
30
+ properties: {
31
+ schema_version: { type: 'string' },
32
+ flow_id: { type: 'string' },
33
+ step_id: { type: 'string' },
34
+ seq: { type: 'integer', minimum: 0 },
35
+ ts: { type: 'string' },
36
+ kind: { type: 'string' },
37
+ // task_id is optional (omitted when null by producer — events.py#to_json drops None).
38
+ // Type is string only; null task_id should be omitted, not sent.
39
+ task_id: { type: 'string' },
40
+ reply_required: { type: 'boolean' },
41
+ metadata: { type: 'object' },
42
+ },
43
+ };
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Per-kind closed metadata schemas (v0.2.6 CONTRACT)
47
+ // Source of truth: stratum-mcp/contracts/build-stream-event.v0.2.6.schema.json
48
+ // ---------------------------------------------------------------------------
49
+
50
+ const KIND_METADATA_SCHEMAS = {
51
+ capability_profile: {
52
+ type: 'object',
53
+ required: ['agent', 'template'],
54
+ additionalProperties: false,
55
+ properties: {
56
+ agent: { type: 'string' },
57
+ template: { type: ['string', 'null'] },
58
+ allowedTools: { type: ['array', 'null'], items: { type: 'string' } },
59
+ disallowedTools: { type: ['array', 'null'], items: { type: 'string' } },
60
+ },
61
+ },
62
+ capability_violation: {
63
+ type: 'object',
64
+ required: ['agent', 'template', 'detail', 'severity'],
65
+ additionalProperties: false,
66
+ properties: {
67
+ agent: { type: 'string' },
68
+ template: { type: ['string', 'null'] },
69
+ detail: { type: 'string' },
70
+ severity: { type: 'string', enum: ['violation', 'warning'] },
71
+ },
72
+ },
73
+ step_usage: {
74
+ type: 'object',
75
+ required: ['stepId', 'input_tokens', 'output_tokens', 'cost_usd'],
76
+ additionalProperties: false,
77
+ properties: {
78
+ stepId: { type: 'string' },
79
+ input_tokens: { type: 'number', minimum: 0 },
80
+ output_tokens: { type: 'number', minimum: 0 },
81
+ cache_creation_input_tokens: { type: 'number', minimum: 0 },
82
+ cache_read_input_tokens: { type: 'number', minimum: 0 },
83
+ cost_usd: { type: 'number', minimum: 0 },
84
+ model: { type: ['string', 'null'] },
85
+ },
86
+ },
87
+ gate_tier_result: {
88
+ type: 'object',
89
+ required: ['stepId', 'tierId', 'passed'],
90
+ additionalProperties: false,
91
+ properties: {
92
+ stepId: { type: 'string' },
93
+ tierId: { type: 'string' },
94
+ passed: { type: 'boolean' },
95
+ details: { type: ['string', 'null'] },
96
+ },
97
+ },
98
+ health_score: {
99
+ type: 'object',
100
+ required: ['score', 'breakdown'],
101
+ additionalProperties: false,
102
+ properties: {
103
+ score: { type: 'number', minimum: 0, maximum: 100 },
104
+ breakdown: { type: 'object', additionalProperties: { type: 'number' } },
105
+ missing: { type: 'array', items: { type: 'string' } },
106
+ },
107
+ },
108
+ build_end: {
109
+ type: 'object',
110
+ required: ['status', 'featureCode'],
111
+ additionalProperties: false,
112
+ properties: {
113
+ status: { type: 'string', enum: ['complete', 'killed', 'crashed', 'failed'] },
114
+ featureCode: { type: 'string' },
115
+ total_input_tokens: { type: 'number', minimum: 0 },
116
+ total_output_tokens: { type: 'number', minimum: 0 },
117
+ total_cost_usd: { type: 'number', minimum: 0 },
118
+ },
119
+ },
120
+ };
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // AJV setup — compile once (Ajv2020 for draft-2020-12 feature support)
124
+ // ---------------------------------------------------------------------------
125
+
126
+ const ajv = new Ajv2020({ strict: false });
127
+ const validateEnvelope = ajv.compile(ENVELOPE_SCHEMA);
128
+
129
+ const compiledKindValidators = new Map(
130
+ Object.entries(KIND_METADATA_SCHEMAS).map(([kind, schema]) => [kind, ajv.compile(schema)])
131
+ );
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // Public API
135
+ // ---------------------------------------------------------------------------
136
+
137
+ /**
138
+ * Validate a parsed BuildStreamEvent envelope.
139
+ *
140
+ * Returns { valid: true } on success.
141
+ * Returns { valid: false, error: string } on failure.
142
+ *
143
+ * Validation rules:
144
+ * 1. Envelope must pass top-level schema (required fields, correct types).
145
+ * 2. schema_version must be a known accepted version.
146
+ * 3. If the kind has a closed metadata schema, metadata must pass it.
147
+ *
148
+ * @param {object} envelope Parsed event object
149
+ * @returns {{ valid: boolean, error?: string }}
150
+ */
151
+ export function validateBuildStreamEvent(envelope) {
152
+ // 1. Top-level envelope shape
153
+ if (!validateEnvelope(envelope)) {
154
+ const errMsg = ajv.errorsText(validateEnvelope.errors);
155
+ return { valid: false, error: `envelope schema: ${errMsg}` };
156
+ }
157
+
158
+ // 2. schema_version check (accepts both 0.2.5 for backward compat and 0.2.6)
159
+ if (!KNOWN_VERSIONS.has(envelope.schema_version)) {
160
+ return {
161
+ valid: false,
162
+ error: `unknown schema_version "${envelope.schema_version}" (accepted: ${[...KNOWN_VERSIONS].join(', ')})`,
163
+ };
164
+ }
165
+
166
+ // 3. Kind-specific metadata validation (only for the 6 closed kinds)
167
+ const kindValidator = compiledKindValidators.get(envelope.kind);
168
+ if (kindValidator) {
169
+ if (!kindValidator(envelope.metadata)) {
170
+ const errMsg = ajv.errorsText(kindValidator.errors);
171
+ return { valid: false, error: `metadata for kind "${envelope.kind}": ${errMsg}` };
172
+ }
173
+ }
174
+
175
+ return { valid: true };
176
+ }
@@ -94,14 +94,16 @@ export class BuildStreamWriter {
94
94
  * @param {string} agent Full agent string
95
95
  * @param {string} templateName Active template name
96
96
  * @param {string} detail Description of the violation
97
+ * @param {'violation'|'warning'} [severity='violation'] Severity from checkCapabilityViolation
97
98
  */
98
- writeViolation(stepId, agent, templateName, detail) {
99
+ writeViolation(stepId, agent, templateName, detail, severity = 'violation') {
99
100
  this.write({
100
101
  type: 'capability_violation',
101
102
  stepId,
102
103
  agent,
103
104
  template: templateName,
104
105
  detail,
106
+ severity,
105
107
  });
106
108
  }
107
109