@smartmemory/compose 0.2.50-beta → 0.2.51
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 +24 -1
- package/.claude/skills/compose/references/hermes-tools.md +80 -0
- package/README.md +1 -1
- package/bin/compose.js +310 -52
- package/dist/assets/App-CT0vXPgd.js +724 -0
- package/dist/assets/_baseUniq-CsOHc_iS.js +1 -0
- package/dist/assets/{arc-DeHak63Z.js → arc-BXht3LyE.js} +1 -1
- package/dist/assets/architectureDiagram-Q4EWVU46-BV1r86-w.js +36 -0
- package/dist/assets/blockDiagram-DXYQGD6D-BgcMJjAR.js +132 -0
- package/dist/assets/{browser-DW2JlJCC.js → browser-CnKiSnlr.js} +6 -6
- package/dist/assets/{c4Diagram-AAUBKEIU-BsFJmPuS.js → c4Diagram-AHTNJAMY-nBIRLJTv.js} +1 -1
- package/dist/assets/channel-Dd4XaiYv.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-XO4S2_89.js → chunk-4BX2VUAB-CNmGyhrp.js} +1 -1
- package/dist/assets/chunk-4TB4RGXK-MLT7I7w4.js +206 -0
- package/dist/assets/{chunk-55IACEB6-CtFSLInj.js → chunk-55IACEB6-D8KuKVLa.js} +1 -1
- package/dist/assets/{chunk-2J33WTMH-AEu6HIoY.js → chunk-EDXVE4YY-TPlt1bS2.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-BMGd_Y9a.js → chunk-FMBD7UC4-tYY8xcjr.js} +1 -1
- package/dist/assets/chunk-OYMX7WX6-yBPwT1AT.js +231 -0
- package/dist/assets/{chunk-QZHKN3VN-DHhUJ8RK.js → chunk-QZHKN3VN-B4Wfybww.js} +1 -1
- package/dist/assets/{chunk-ND2GUHAM-BlrOECEr.js → chunk-YZCP3GAM-DE6_5aqh.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-DshSQDF9.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-DshSQDF9.js +1 -0
- package/dist/assets/clone-CBsbNGAa.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-C3M0mZqk.js → cose-bilkent-S5V4N54A-DzzRyJtB.js} +1 -1
- package/dist/assets/dagre-KV5264BT-CTcTVGdU.js +4 -0
- package/dist/assets/diagram-5BDNPKRD-qR6mc8kJ.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-CUcGXyNb.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-B68cPonH.js +43 -0
- package/dist/assets/diagram-TYMM5635-q8Id8eFG.js +24 -0
- package/dist/assets/{erDiagram-TEJ5UH35-AMDitV0b.js → erDiagram-SMLLAGMA-CtxJi57K.js} +1 -1
- package/dist/assets/{flowDiagram-I6XJVG4X-C0t2ZGQv.js → flowDiagram-DWJPFMVM-BzzO4JYU.js} +1 -1
- package/dist/assets/ganttDiagram-T4ZO3ILL-Dn0wIyhR.js +292 -0
- package/dist/assets/gitGraphDiagram-UUTBAWPF-tnNFXfMM.js +106 -0
- package/dist/assets/graph-DZe55uk8.js +331 -0
- package/dist/assets/graph-Tq_bs_r0.js +1 -0
- package/dist/assets/index-8UhRLbGq.js +123 -0
- package/dist/assets/index-CRqB9els.css +1 -0
- package/dist/assets/infoDiagram-42DDH7IO-BXLvLeS0.js +2 -0
- package/dist/assets/{ishikawaDiagram-YF4QCWOH-CCBwgjFs.js → ishikawaDiagram-UXIWVN3A-DWx-7BBy.js} +5 -5
- package/dist/assets/{journeyDiagram-JHISSGLW-zUJN38EU.js → journeyDiagram-VCZTEJTY-C42hXNha.js} +1 -1
- package/dist/assets/{kanban-definition-UN3LZRKU-BNxWi-8J.js → kanban-definition-6JOO6SKY-CEq930ew.js} +1 -1
- package/dist/assets/katex-DkKDou_j.js +257 -0
- package/dist/assets/layout-qtUgN9BC.js +1 -0
- package/dist/assets/{linear-CRH9b4g7.js → linear-D158F7OT.js} +1 -1
- package/dist/assets/min-hESxN-c0.js +1 -0
- package/dist/assets/{mindmap-definition-RKZ34NQL-DolYwq7Y.js → mindmap-definition-QFDTVHPH-d9SSh2nG.js} +7 -7
- package/dist/assets/mobile-1gVCT0OK.css +1 -0
- package/dist/assets/mobile-Chw8RWyH.js +17 -0
- package/dist/assets/pieDiagram-DEJITSTG-gglmeMav.js +30 -0
- package/dist/assets/quadrantDiagram-34T5L4WZ-CqFC_htU.js +7 -0
- package/dist/assets/{requirementDiagram-4Y6WPE33-DsXZ05jo.js → requirementDiagram-MS252O5E-B1ZFIotK.js} +1 -1
- package/dist/assets/sankeyDiagram-XADWPNL6-BvItvZt5.js +10 -0
- package/dist/assets/sequenceDiagram-FGHM5R23-CAIn5Z3U.js +157 -0
- package/dist/assets/stateDiagram-FHFEXIEX-CyJrGzib.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-DprhF_Ho.js +1 -0
- package/dist/assets/{timeline-definition-PNZ67QCA-Bvnhw58e.js → timeline-definition-GMOUNBTQ-k_0vJQAK.js} +1 -1
- package/dist/assets/{vennDiagram-CIIHVFJN-5WnyjZqf.js → vennDiagram-DHZGUBPP-TZAW60b0.js} +4 -4
- package/dist/assets/wardley-RL74JXVD-DzQEoG6X.js +162 -0
- package/dist/assets/wardleyDiagram-NUSXRM2D-BAoVVIjZ.js +20 -0
- package/dist/assets/{xychartDiagram-2RQKCTM6-BCShes6B.js → xychartDiagram-5P7HB3ND-CynkZcUb.js} +1 -1
- package/dist/index.html +4 -4
- package/lib/build.js +272 -33
- package/lib/codex-preflight.js +151 -0
- package/lib/completion-writer.js +21 -1
- package/lib/feature-validator.js +5 -1
- package/lib/hooks-status.js +135 -0
- package/lib/install-agent-defs.js +22 -0
- package/lib/migrate-anon.js +277 -0
- package/lib/roadmap-parser.js +1 -1
- package/lib/test-bootstrap.js +165 -2
- package/lib/vocabulary-inject.js +103 -0
- package/package.json +2 -1
- package/pipelines/build-quick.stratum.yaml +433 -0
- package/pipelines/build.stratum.yaml +60 -3
- package/scripts/watch-server.sh +32 -0
- package/server/build-routes.js +89 -9
- package/server/build-stream-bridge.js +12 -0
- package/server/compose-mcp-tools.js +73 -1
- package/server/compose-mcp.js +27 -0
- package/server/feature-scaffold-routes.js +82 -0
- package/server/health-routes.js +261 -0
- package/server/qa-scope-routes.js +96 -0
- package/server/validate-routes.js +70 -0
- package/server/vision-routes.js +29 -0
- package/server/vision-server.js +21 -0
- package/dist/assets/App-Bxyif1yv.js +0 -706
- package/dist/assets/architectureDiagram-3BPJPVTR-C5Nkyl9y.js +0 -36
- package/dist/assets/blockDiagram-GPEHLZMM-BilnUBG2.js +0 -132
- package/dist/assets/channel-ZgbQ1k0u.js +0 -1
- package/dist/assets/chunk-727SXJPM-CZeBRpM9.js +0 -206
- package/dist/assets/chunk-AQP2D5EJ-_vmxcBc4.js +0 -231
- package/dist/assets/classDiagram-4FO5ZUOK-KXxOorUx.js +0 -1
- package/dist/assets/classDiagram-v2-Q7XG4LA2-KXxOorUx.js +0 -1
- package/dist/assets/dagre-BM42HDAG-DJlm4fEk.js +0 -4
- package/dist/assets/diagram-2AECGRRQ-BmcONQG5.js +0 -43
- package/dist/assets/diagram-5GNKFQAL-5JbWGIwd.js +0 -10
- package/dist/assets/diagram-KO2AKTUF-DAnIvyI0.js +0 -3
- package/dist/assets/diagram-LMA3HP47-Bmh7tvmI.js +0 -24
- package/dist/assets/diagram-OG6HWLK6-hnHVMNnk.js +0 -24
- package/dist/assets/ganttDiagram-6RSMTGT7-BPlM-BEN.js +0 -292
- package/dist/assets/gitGraphDiagram-PVQCEYII-Lv_YTxnb.js +0 -106
- package/dist/assets/graph-BBXaecIU.js +0 -331
- package/dist/assets/graph-CAnANduQ.js +0 -1
- package/dist/assets/index-BwLfbbOu.css +0 -1
- package/dist/assets/index-COq21Zym.js +0 -119
- package/dist/assets/infoDiagram-5YYISTIA-Dul1vdUm.js +0 -2
- package/dist/assets/katex-C5jXJg4s.js +0 -257
- package/dist/assets/layout-DGIYPm2g.js +0 -1
- package/dist/assets/mobile-Cag5dHlF.css +0 -1
- package/dist/assets/mobile-DwmxS_O4.js +0 -17
- package/dist/assets/pieDiagram-4H26LBE5-oxNfr2TX.js +0 -30
- package/dist/assets/quadrantDiagram-W4KKPZXB-BmZCvD-z.js +0 -7
- package/dist/assets/sankeyDiagram-5OEKKPKP-DsnYaawP.js +0 -40
- package/dist/assets/sequenceDiagram-3UESZ5HK-Dt5mu3g8.js +0 -162
- package/dist/assets/stateDiagram-AJRCARHV-BFR5ZINQ.js +0 -1
- package/dist/assets/stateDiagram-v2-BHNVJYJU-BDrQD8fR.js +0 -1
- package/dist/assets/wardley-L42UT6IY-CTxW4xow.js +0 -173
- package/dist/assets/wardleyDiagram-YWT4CUSO-K8Y1EOvb.js +0 -78
package/lib/test-bootstrap.js
CHANGED
|
@@ -119,7 +119,10 @@ export function detectTestFramework(cwd) {
|
|
|
119
119
|
framework: 'go-test',
|
|
120
120
|
runner: 'go',
|
|
121
121
|
configFile: 'go.mod',
|
|
122
|
-
|
|
122
|
+
// -v emits per-test "--- PASS:/FAIL:" verdicts so parseTestSummary can
|
|
123
|
+
// count tests (COMP-TEST-BOOTSTRAP-4); plain `go test` prints only a
|
|
124
|
+
// package-level ok/FAIL with no counts.
|
|
125
|
+
command: 'go test -v ./...',
|
|
123
126
|
};
|
|
124
127
|
}
|
|
125
128
|
}
|
|
@@ -298,7 +301,8 @@ export function scaffoldTestFramework(cwd, language) {
|
|
|
298
301
|
framework: 'go-test',
|
|
299
302
|
configFile: 'golden_test.go',
|
|
300
303
|
testDir: '.',
|
|
301
|
-
|
|
304
|
+
// -v so parseTestSummary can count per-test verdicts (COMP-TEST-BOOTSTRAP-4).
|
|
305
|
+
command: 'go test -v ./...',
|
|
302
306
|
};
|
|
303
307
|
}
|
|
304
308
|
|
|
@@ -399,3 +403,162 @@ export function scaffoldTestFramework(cwd, language) {
|
|
|
399
403
|
}
|
|
400
404
|
}
|
|
401
405
|
}
|
|
406
|
+
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
// COMP-TEST-BOOTSTRAP-4-1: isTestFile
|
|
409
|
+
// ---------------------------------------------------------------------------
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Whether a repo-relative path looks like a test file. Used to scope the
|
|
413
|
+
* post-coverage test-review pass to the tests the coverage step produced.
|
|
414
|
+
* Conservative: matches conventional test locations/suffixes across the
|
|
415
|
+
* frameworks detectTestFramework knows, and avoids substring false positives
|
|
416
|
+
* (e.g. "contest", "greatest").
|
|
417
|
+
*
|
|
418
|
+
* @param {string} p - repo-relative file path
|
|
419
|
+
* @returns {boolean}
|
|
420
|
+
*/
|
|
421
|
+
export function isTestFile(p) {
|
|
422
|
+
if (typeof p !== 'string' || p.length === 0) return false;
|
|
423
|
+
return (
|
|
424
|
+
/(^|\/)(tests?|__tests__|spec)\//.test(p) || // test/ tests/ __tests__/ spec/ dirs
|
|
425
|
+
/\.(test|spec)\.[mc]?[jt]sx?$/.test(p) || // *.test.* / *.spec.* (js/ts/jsx/tsx/mjs/cjs)
|
|
426
|
+
/_test\.go$/.test(p) || // go
|
|
427
|
+
/(^|\/)test_[^/]*\.py$/.test(p) || // pytest test_*.py
|
|
428
|
+
/_test\.py$/.test(p) // pytest *_test.py
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ---------------------------------------------------------------------------
|
|
433
|
+
// COMP-TEST-BOOTSTRAP-4: parseTestSummary
|
|
434
|
+
// ---------------------------------------------------------------------------
|
|
435
|
+
|
|
436
|
+
const _UNPARSED = Object.freeze({ test_count: 0, pass_rate: 0, parsed: false });
|
|
437
|
+
|
|
438
|
+
/** Strip ANSI color/escape codes so regexes match raw text. */
|
|
439
|
+
function _stripAnsi(s) {
|
|
440
|
+
// eslint-disable-next-line no-control-regex
|
|
441
|
+
return s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** First capture group as an int, or null if no match. */
|
|
445
|
+
function _int(m) {
|
|
446
|
+
return m ? parseInt(m[1], 10) : null;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Build a result from pass/fail counts. The denominator is passed+failed only —
|
|
451
|
+
* skipped/pending/ignored tests are excluded from the rate (a fully-skipped run
|
|
452
|
+
* is not "100% passing"; a run with zero verdicts degrades to parsed:false).
|
|
453
|
+
*/
|
|
454
|
+
function _fromCounts(passed, failed) {
|
|
455
|
+
const total = passed + failed;
|
|
456
|
+
if (total <= 0) return _UNPARSED;
|
|
457
|
+
// Round to 2 decimals; an all-pass run yields exactly 100.
|
|
458
|
+
const pass_rate = Math.round((passed / total) * 10000) / 100;
|
|
459
|
+
return { test_count: total, pass_rate, parsed: true };
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function _parseVitest(out) {
|
|
463
|
+
// " Tests 2 failed | 10 passed (12)" / " Tests 12 passed (12)"
|
|
464
|
+
// Match the "Tests" line specifically, not the "Test Files" line.
|
|
465
|
+
const line = out.match(/^[^\S\n]*Tests[^\S\n]+(.+)$/m);
|
|
466
|
+
if (!line) return _UNPARSED;
|
|
467
|
+
const passed = _int(line[1].match(/(\d+)\s+passed/));
|
|
468
|
+
const failed = _int(line[1].match(/(\d+)\s+failed/));
|
|
469
|
+
if (passed === null && failed === null) return _UNPARSED;
|
|
470
|
+
return _fromCounts(passed ?? 0, failed ?? 0);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function _parseJest(out) {
|
|
474
|
+
// "Tests: 2 failed, 10 passed, 12 total"
|
|
475
|
+
const line = out.match(/^[^\S\n]*Tests:[^\S\n]+(.+)$/m);
|
|
476
|
+
if (!line) return _UNPARSED;
|
|
477
|
+
const passed = _int(line[1].match(/(\d+)\s+passed/));
|
|
478
|
+
const failed = _int(line[1].match(/(\d+)\s+failed/));
|
|
479
|
+
if (passed === null && failed === null) return _UNPARSED;
|
|
480
|
+
return _fromCounts(passed ?? 0, failed ?? 0);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function _parseMocha(out) {
|
|
484
|
+
// " 10 passing (24ms)" + optional " 2 failing"; pending excluded.
|
|
485
|
+
const passed = _int(out.match(/(\d+)\s+passing/));
|
|
486
|
+
const failed = _int(out.match(/(\d+)\s+failing/));
|
|
487
|
+
if (passed === null && failed === null) return _UNPARSED;
|
|
488
|
+
return _fromCounts(passed ?? 0, failed ?? 0);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function _parsePytest(out) {
|
|
492
|
+
// "===== 2 failed, 9 passed, 1 error, 1 skipped in 0.50s =====" / "12 passed in 0.34s"
|
|
493
|
+
const passed = _int(out.match(/(\d+)\s+passed/));
|
|
494
|
+
const failed = _int(out.match(/(\d+)\s+failed/));
|
|
495
|
+
const errors = _int(out.match(/(\d+)\s+error/)); // "error"/"errors"
|
|
496
|
+
if (passed === null && failed === null && errors === null) return _UNPARSED;
|
|
497
|
+
return _fromCounts(passed ?? 0, (failed ?? 0) + (errors ?? 0));
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function _parseGoTest(out) {
|
|
501
|
+
// Requires verbose output (`go test -v`): per-test "--- PASS:" / "--- FAIL:" lines.
|
|
502
|
+
// Without -v there are no per-test verdicts to count → degrade.
|
|
503
|
+
const passed = (out.match(/^[^\S\n]*--- PASS:/gm) || []).length;
|
|
504
|
+
const failed = (out.match(/^[^\S\n]*--- FAIL:/gm) || []).length;
|
|
505
|
+
if (passed + failed === 0) return _UNPARSED;
|
|
506
|
+
return _fromCounts(passed, failed);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function _parseCargo(out) {
|
|
510
|
+
// Sum every "test result: <ok|FAILED>. N passed; M failed; ..." line
|
|
511
|
+
// (a crate emits one per unit/integration/doc target).
|
|
512
|
+
const re = /test result:\s+\w+\.\s+(\d+)\s+passed;\s+(\d+)\s+failed/g;
|
|
513
|
+
let passed = 0, failed = 0, found = false, m;
|
|
514
|
+
while ((m = re.exec(out)) !== null) {
|
|
515
|
+
passed += parseInt(m[1], 10);
|
|
516
|
+
failed += parseInt(m[2], 10);
|
|
517
|
+
found = true;
|
|
518
|
+
}
|
|
519
|
+
if (!found) return _UNPARSED;
|
|
520
|
+
return _fromCounts(passed, failed);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Extract a structured test-result summary from raw test-runner stdout.
|
|
525
|
+
*
|
|
526
|
+
* Pure and total: any framework it can't read — or output it can't recognize —
|
|
527
|
+
* degrades to `{ test_count: 0, pass_rate: 0, parsed: false }` rather than
|
|
528
|
+
* guessing. Callers MUST treat `parsed:false` as "no signal" and never as a
|
|
529
|
+
* failing gate (see lib/build.js ship path).
|
|
530
|
+
*
|
|
531
|
+
* @param {string|null} framework - value from detectTestFramework().framework
|
|
532
|
+
* (vitest | jest | mocha | pytest | go-test | cargo-test; others degrade)
|
|
533
|
+
* @param {string|null} stdout - combined stdout+stderr of the test run
|
|
534
|
+
* @returns {{ test_count: number, pass_rate: number, parsed: boolean }}
|
|
535
|
+
*/
|
|
536
|
+
export function parseTestSummary(framework, stdout) {
|
|
537
|
+
if (!framework || typeof stdout !== 'string' || stdout.length === 0) return _UNPARSED;
|
|
538
|
+
const out = _stripAnsi(stdout);
|
|
539
|
+
switch (framework) {
|
|
540
|
+
case 'vitest': return _parseVitest(out);
|
|
541
|
+
case 'jest': return _parseJest(out);
|
|
542
|
+
case 'mocha': return _parseMocha(out);
|
|
543
|
+
case 'pytest': return _parsePytest(out);
|
|
544
|
+
case 'go-test': return _parseGoTest(out);
|
|
545
|
+
case 'cargo-test': return _parseCargo(out);
|
|
546
|
+
default: return _UNPARSED; // ava, tap, unknown
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* The ship gate: derive a `tests_pass` boolean from a parsed summary.
|
|
552
|
+
*
|
|
553
|
+
* - parsed: pass only when at least one test ran AND every test passed.
|
|
554
|
+
* - unparsed (or absent): degrade to `true` — an unreadable summary is "no
|
|
555
|
+
* signal", never a failing gate. This is the safety valve that keeps the
|
|
556
|
+
* gate from misfiring on frameworks/output the parser can't read.
|
|
557
|
+
*
|
|
558
|
+
* @param {{ test_count: number, pass_rate: number, parsed: boolean }|null} summary
|
|
559
|
+
* @returns {boolean}
|
|
560
|
+
*/
|
|
561
|
+
export function deriveTestsPass(summary) {
|
|
562
|
+
if (!summary || !summary.parsed) return true;
|
|
563
|
+
return summary.test_count >= 1 && summary.pass_rate === 100;
|
|
564
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STRAT-VOCAB-3 — Compose integration for vocabulary enforcement.
|
|
3
|
+
*
|
|
4
|
+
* Wires the already-shipped Stratum `vocabulary_compliance` ensure builtin
|
|
5
|
+
* (stratum-mcp/src/stratum_mcp/spec.py) into the compose lifecycle:
|
|
6
|
+
* - VOCABULARY_TEMPLATE : starter contracts/vocabulary.yaml (compose init)
|
|
7
|
+
* - vocabularyEnabled() : gate — capability not disabled AND a vocab file exists
|
|
8
|
+
* - injectVocabularyEnsure() : append the vocab ensure to the build flow's `review` step
|
|
9
|
+
* - tagVocabularyViolations() : mark vocab violation strings as must-fix for display
|
|
10
|
+
*
|
|
11
|
+
* Design: docs/features/STRAT-VOCAB-3/design.md
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
|
|
16
|
+
/** Project-relative location of the vocabulary file (also the path baked into the ensure). */
|
|
17
|
+
export const VOCABULARY_FILE = 'contracts/vocabulary.yaml';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The ensure expression injected into the build flow's `review` step.
|
|
21
|
+
* Empty-list literal + git_fallback=True: never references a possibly-missing
|
|
22
|
+
* `result.*` attribute; scans the uncommitted working-tree diff vs HEAD (the
|
|
23
|
+
* implementation changes are merged but not yet committed at review time).
|
|
24
|
+
* Python-evaluated by the Stratum executor — hence `[]` and `True`.
|
|
25
|
+
*/
|
|
26
|
+
export const VOCABULARY_ENSURE = `vocabulary_compliance('${VOCABULARY_FILE}', [], True)`;
|
|
27
|
+
|
|
28
|
+
/** Starter vocabulary file: all comments, so `_load_vocabulary` returns {} (inert) until edited. */
|
|
29
|
+
export const VOCABULARY_TEMPLATE = `# contracts/vocabulary.yaml — project naming vocabulary (STRAT-VOCAB)
|
|
30
|
+
#
|
|
31
|
+
# Declare canonical names and the aliases that must NOT appear in code. During
|
|
32
|
+
# \`compose build\`, the review step scans the files changed by the build for any
|
|
33
|
+
# rejected alias (whole-word, case-sensitive) and fails until each is replaced
|
|
34
|
+
# with its canonical name. An empty / comments-only file is a no-op.
|
|
35
|
+
#
|
|
36
|
+
# Format — a flat map of canonical_name -> { reject: [...], reason: "..." }:
|
|
37
|
+
#
|
|
38
|
+
# auth_token:
|
|
39
|
+
# reject: [jwt, accessToken, JwtToken, authToken]
|
|
40
|
+
# reason: "use auth_token everywhere for the session credential"
|
|
41
|
+
#
|
|
42
|
+
# user_id:
|
|
43
|
+
# reject: [uid, userId, UserId]
|
|
44
|
+
#
|
|
45
|
+
# Rules: canonical names and aliases must be identifiers; an alias may not also
|
|
46
|
+
# be a canonical name or be repeated across entries; \`reason\` is optional.
|
|
47
|
+
# Uncomment and edit the examples above (or add your own) to enable enforcement.
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Is vocabulary enforcement active for this project?
|
|
52
|
+
* Default-ON (honoring the roadmap's "by default") but gated on the file existing,
|
|
53
|
+
* so the generated spec is byte-identical for any project without a vocab file.
|
|
54
|
+
* Opt out with capabilities.vocabularyCompliance === false.
|
|
55
|
+
*/
|
|
56
|
+
export function vocabularyEnabled(cwd, composeConfig) {
|
|
57
|
+
if (composeConfig?.capabilities?.vocabularyCompliance === false) return false;
|
|
58
|
+
return existsSync(join(cwd, VOCABULARY_FILE));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Append the vocabulary ensure to the EXECUTED flow's `review` step (idempotent).
|
|
63
|
+
* Mutates and returns specObj. No-op when that flow has no `review` step.
|
|
64
|
+
*
|
|
65
|
+
* `flowName` must be the flow Stratum will actually run (build.js resolves it via
|
|
66
|
+
* extractFlowName). Targeting it precisely matters because a sub-flow
|
|
67
|
+
* (`review_check`) ALSO has a step id'd `review`; injecting there would be wrong.
|
|
68
|
+
* When `flowName` is omitted/unknown, fall back to the `build` flow (or the first),
|
|
69
|
+
* matching the shipped templates.
|
|
70
|
+
*/
|
|
71
|
+
export function injectVocabularyEnsure(specObj, flowName) {
|
|
72
|
+
const flows = specObj?.flows ?? {};
|
|
73
|
+
const keys = Object.keys(flows);
|
|
74
|
+
const flowKey = flowName && keys.includes(flowName)
|
|
75
|
+
? flowName
|
|
76
|
+
: (keys.includes('build') ? 'build' : keys[0]);
|
|
77
|
+
const steps = flows[flowKey]?.steps ?? [];
|
|
78
|
+
const review = steps.find((s) => s?.id === 'review');
|
|
79
|
+
if (!review) return specObj;
|
|
80
|
+
if (!Array.isArray(review.ensure)) review.ensure = [];
|
|
81
|
+
if (!review.ensure.includes(VOCABULARY_ENSURE)) review.ensure.push(VOCABULARY_ENSURE);
|
|
82
|
+
return specObj;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Tag vocabulary failure strings as must-fix for the findings display.
|
|
87
|
+
* The cli-progress parser classifies a string by keyword (defaults to `nit`);
|
|
88
|
+
* vocab failures carry no marker, so prefix them with `must-fix:` — the parser
|
|
89
|
+
* then classifies must-fix AND still extracts the file:line, and strips the
|
|
90
|
+
* prefix for a clean description. Non-string / non-vocab items pass through
|
|
91
|
+
* unchanged. Returns a new array (does not mutate input or stored violations).
|
|
92
|
+
*
|
|
93
|
+
* Matches every string the builtin emits — both alias hits
|
|
94
|
+
* ("vocabulary violation: …") and a broken vocab file
|
|
95
|
+
* ("vocabulary.yaml malformed: …" / "vocabulary.yaml schema error: …") — all of
|
|
96
|
+
* which block the step and so deserve must-fix, not nit.
|
|
97
|
+
*/
|
|
98
|
+
export function tagVocabularyViolations(violations) {
|
|
99
|
+
if (!Array.isArray(violations)) return violations;
|
|
100
|
+
return violations.map((v) =>
|
|
101
|
+
typeof v === 'string' && /^vocabulary[ .]/i.test(v) ? `must-fix: ${v}` : v
|
|
102
|
+
);
|
|
103
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartmemory/compose",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.51",
|
|
4
4
|
"description": "Structured AI dev pipeline — goal-to-product orchestration with gates, iteration loops, and feature lifecycle management.",
|
|
5
5
|
"author": "SmartMemory",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"dev": "node server/supervisor.js 2>&1 | tee /tmp/compose-server.log",
|
|
17
17
|
"dev:server": "node server/supervisor.js",
|
|
18
18
|
"dev:server:bare": "node server/index.js",
|
|
19
|
+
"dev:watch": "bash scripts/watch-server.sh",
|
|
19
20
|
"dev:client": "vite",
|
|
20
21
|
"build": "vite build",
|
|
21
22
|
"preview": "vite preview",
|