@rqml/cli 0.2.0 → 0.4.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 CHANGED
@@ -18,10 +18,16 @@ rqml <command> [spec.rqml] [options]
18
18
  validate [path] XML well-formedness, XSD, and referential integrity
19
19
  status [path] Spec, coverage, and lint summary
20
20
  check [path] Deterministic enforcement gate (validation + coverage + drift)
21
-
22
- --json Machine-readable output (REQ-CLI-JSON)
23
- --strictness <level> relaxed | standard | strict | certified
24
- --base-dir <dir> Resolve the spec and implements code links against <dir>
21
+ show <id> One artifact: statement, acceptance criteria, trace neighborhood
22
+ impact <id> What is affected, transitively, if this artifact changes
23
+ matrix [path] Traceability matrix: status, goals, code, tests, coverage gaps
24
+ link <id> <uri> Record an implements/verifiedBy edge and its drift baseline
25
+ skeleton <kind> Print a schema-valid snippet (req|edge|testCase|stateMachine)
26
+
27
+ --json Machine-readable output (REQ-CLI-JSON)
28
+ --strictness <level> relaxed | standard | strict | certified
29
+ --base-dir <dir> Resolve the spec and implements code links against <dir>
30
+ --status/--type/--warning Filter matrix rows (comma-separated, e.g. --warning unverified)
25
31
  ```
26
32
 
27
33
  When no spec path is given, the lone `*.rqml` in the working directory is used
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { __commonJS, __toESM } from './chunk-5WRI5ZAA.js';
3
+ import { createRequire } from 'module';
3
4
  import { createHash } from 'crypto';
4
5
  import { writeFileSync, existsSync, readFileSync, mkdirSync, statSync, readdirSync } from 'fs';
5
6
  import { resolve, join, dirname, isAbsolute } from 'path';
@@ -13,7 +14,7 @@ var require_util = __commonJS({
13
14
  var nameRegexp = "[" + nameStartChar + "][" + nameChar + "]*";
14
15
  var regexName = new RegExp("^" + nameRegexp + "$");
15
16
  var getAllMatches = function(string, regex) {
16
- const matches = [];
17
+ const matches2 = [];
17
18
  let match = regex.exec(string);
18
19
  while (match) {
19
20
  const allmatches = [];
@@ -22,10 +23,10 @@ var require_util = __commonJS({
22
23
  for (let index = 0; index < len; index++) {
23
24
  allmatches.push(match[index]);
24
25
  }
25
- matches.push(allmatches);
26
+ matches2.push(allmatches);
26
27
  match = regex.exec(string);
27
28
  }
28
- return matches;
29
+ return matches2;
29
30
  };
30
31
  var isName = function(string) {
31
32
  const match = regexName.exec(string);
@@ -303,24 +304,24 @@ var require_validator = __commonJS({
303
304
  }
304
305
  var validAttrStrRegxp = new RegExp(`(\\s*)([^\\s=]+)(\\s*=)?(\\s*(['"])(([\\s\\S])*?)\\5)?`, "g");
305
306
  function validateAttributeString(attrStr, options) {
306
- const matches = util.getAllMatches(attrStr, validAttrStrRegxp);
307
+ const matches2 = util.getAllMatches(attrStr, validAttrStrRegxp);
307
308
  const attrNames = {};
308
- for (let i = 0; i < matches.length; i++) {
309
- if (matches[i][1].length === 0) {
310
- return getErrorObject("InvalidAttr", "Attribute '" + matches[i][2] + "' has no space in starting.", getPositionFromMatch(matches[i]));
311
- } else if (matches[i][3] !== void 0 && matches[i][4] === void 0) {
312
- return getErrorObject("InvalidAttr", "Attribute '" + matches[i][2] + "' is without value.", getPositionFromMatch(matches[i]));
313
- } else if (matches[i][3] === void 0 && !options.allowBooleanAttributes) {
314
- return getErrorObject("InvalidAttr", "boolean attribute '" + matches[i][2] + "' is not allowed.", getPositionFromMatch(matches[i]));
309
+ for (let i = 0; i < matches2.length; i++) {
310
+ if (matches2[i][1].length === 0) {
311
+ return getErrorObject("InvalidAttr", "Attribute '" + matches2[i][2] + "' has no space in starting.", getPositionFromMatch(matches2[i]));
312
+ } else if (matches2[i][3] !== void 0 && matches2[i][4] === void 0) {
313
+ return getErrorObject("InvalidAttr", "Attribute '" + matches2[i][2] + "' is without value.", getPositionFromMatch(matches2[i]));
314
+ } else if (matches2[i][3] === void 0 && !options.allowBooleanAttributes) {
315
+ return getErrorObject("InvalidAttr", "boolean attribute '" + matches2[i][2] + "' is not allowed.", getPositionFromMatch(matches2[i]));
315
316
  }
316
- const attrName = matches[i][2];
317
+ const attrName = matches2[i][2];
317
318
  if (!validateAttrName(attrName)) {
318
- return getErrorObject("InvalidAttr", "Attribute '" + attrName + "' is an invalid name.", getPositionFromMatch(matches[i]));
319
+ return getErrorObject("InvalidAttr", "Attribute '" + attrName + "' is an invalid name.", getPositionFromMatch(matches2[i]));
319
320
  }
320
321
  if (!attrNames.hasOwnProperty(attrName)) {
321
322
  attrNames[attrName] = 1;
322
323
  } else {
323
- return getErrorObject("InvalidAttr", "Attribute '" + attrName + "' is repeated.", getPositionFromMatch(matches[i]));
324
+ return getErrorObject("InvalidAttr", "Attribute '" + attrName + "' is repeated.", getPositionFromMatch(matches2[i]));
324
325
  }
325
326
  }
326
327
  return true;
@@ -1057,15 +1058,15 @@ var require_OrderedObjParser = __commonJS({
1057
1058
  var attrsRegx = new RegExp(`([^\\s=]+)\\s*(=\\s*(['"])([\\s\\S]*?)\\3)?`, "gm");
1058
1059
  function buildAttributesMap(attrStr, jPath, tagName) {
1059
1060
  if (this.options.ignoreAttributes !== true && typeof attrStr === "string") {
1060
- const matches = util.getAllMatches(attrStr, attrsRegx);
1061
- const len = matches.length;
1061
+ const matches2 = util.getAllMatches(attrStr, attrsRegx);
1062
+ const len = matches2.length;
1062
1063
  const attrs = {};
1063
1064
  for (let i = 0; i < len; i++) {
1064
- const attrName = this.resolveNameSpace(matches[i][1]);
1065
+ const attrName = this.resolveNameSpace(matches2[i][1]);
1065
1066
  if (this.ignoreAttributesFn(attrName, jPath)) {
1066
1067
  continue;
1067
1068
  }
1068
- let oldVal = matches[i][4];
1069
+ let oldVal = matches2[i][4];
1069
1070
  let aName = this.options.attributeNamePrefix + attrName;
1070
1071
  if (attrName.length) {
1071
1072
  if (this.options.transformAttributeName) {
@@ -1327,9 +1328,9 @@ var require_OrderedObjParser = __commonJS({
1327
1328
  }
1328
1329
  for (let entityName in this.docTypeEntities) {
1329
1330
  const entity = this.docTypeEntities[entityName];
1330
- const matches = val.match(entity.regx);
1331
- if (matches) {
1332
- this.entityExpansionCount += matches.length;
1331
+ const matches2 = val.match(entity.regx);
1332
+ if (matches2) {
1333
+ this.entityExpansionCount += matches2.length;
1333
1334
  if (entityConfig.maxTotalExpansions && this.entityExpansionCount > entityConfig.maxTotalExpansions) {
1334
1335
  throw new Error(
1335
1336
  `Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
@@ -1350,9 +1351,9 @@ var require_OrderedObjParser = __commonJS({
1350
1351
  if (val.indexOf("&") === -1) return val;
1351
1352
  for (const entityName of Object.keys(this.lastEntities)) {
1352
1353
  const entity = this.lastEntities[entityName];
1353
- const matches = val.match(entity.regex);
1354
- if (matches) {
1355
- this.entityExpansionCount += matches.length;
1354
+ const matches2 = val.match(entity.regex);
1355
+ if (matches2) {
1356
+ this.entityExpansionCount += matches2.length;
1356
1357
  if (entityConfig.maxTotalExpansions && this.entityExpansionCount > entityConfig.maxTotalExpansions) {
1357
1358
  throw new Error(
1358
1359
  `Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
@@ -1365,9 +1366,9 @@ var require_OrderedObjParser = __commonJS({
1365
1366
  if (this.options.htmlEntities) {
1366
1367
  for (const entityName of Object.keys(this.htmlEntities)) {
1367
1368
  const entity = this.htmlEntities[entityName];
1368
- const matches = val.match(entity.regex);
1369
- if (matches) {
1370
- this.entityExpansionCount += matches.length;
1369
+ const matches2 = val.match(entity.regex);
1370
+ if (matches2) {
1371
+ this.entityExpansionCount += matches2.length;
1371
1372
  if (entityConfig.maxTotalExpansions && this.entityExpansionCount > entityConfig.maxTotalExpansions) {
1372
1373
  throw new Error(
1373
1374
  `Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
@@ -3372,6 +3373,157 @@ ${sectionIndent}</trace>
3372
3373
  }
3373
3374
  return { ok: true, xml: updated, edgeId, edgeXml: edgeXml.trim() };
3374
3375
  }
3376
+ function escapeRegExp2(value) {
3377
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3378
+ }
3379
+ var EXTERNAL_ELEMENT = /<external\b[^>]*(?:\/>|>[\s\S]*?<\/external>)/g;
3380
+ function updateTraceEdge(xml, request) {
3381
+ const parsed = parse(xml);
3382
+ if (!parsed.ok) {
3383
+ return { ok: false, error: `document does not parse: ${parsed.error.message}` };
3384
+ }
3385
+ if (request.uri.trim() === "") {
3386
+ return { ok: false, error: "locator uri must not be empty" };
3387
+ }
3388
+ const prefix = request.type === "implements" ? "E-IMPL-" : "E-VER-";
3389
+ const edgeId = request.edgeId ?? prefix + request.artifactId.replace(/^REQ-/, "");
3390
+ const edge = parsed.document.trace.find((e) => e.id === edgeId);
3391
+ if (edge === void 0) {
3392
+ return {
3393
+ ok: false,
3394
+ error: request.edgeId !== void 0 ? `no trace edge with id "${edgeId}" exists` : `no trace edge with the derived id "${edgeId}" exists; pass --id for an explicitly named edge`
3395
+ };
3396
+ }
3397
+ if (edge.type !== request.type) {
3398
+ return {
3399
+ ok: false,
3400
+ error: `edge "${edgeId}" has type "${edge.type}", not "${request.type}"`
3401
+ };
3402
+ }
3403
+ const local = edge.from.kind === "local" ? edge.from : edge.to.kind === "local" ? edge.to : void 0;
3404
+ if (local === void 0 || local.id !== request.artifactId) {
3405
+ return {
3406
+ ok: false,
3407
+ error: `edge "${edgeId}" does not link artifact "${request.artifactId}"`
3408
+ };
3409
+ }
3410
+ const external = edge.from.kind === "external" ? edge.from : edge.to.kind === "external" ? edge.to : void 0;
3411
+ if (external === void 0) {
3412
+ return { ok: false, error: `edge "${edgeId}" has no external locator to update` };
3413
+ }
3414
+ const openTag = new RegExp(`<edge\\b[^>]*\\bid="${escapeRegExp2(edgeId)}"[^>]*>`);
3415
+ const open = openTag.exec(xml);
3416
+ const closeIdx = open === null ? -1 : xml.indexOf("</edge>", open.index);
3417
+ if (open === null || closeIdx < 0) {
3418
+ return { ok: false, error: `could not locate edge "${edgeId}" in the document text` };
3419
+ }
3420
+ const span = xml.slice(open.index, closeIdx);
3421
+ const occurrences = span.match(EXTERNAL_ELEMENT) ?? [];
3422
+ if (occurrences.length !== 1) {
3423
+ return {
3424
+ ok: false,
3425
+ error: `edge "${edgeId}" does not contain exactly one external locator element`
3426
+ };
3427
+ }
3428
+ const kind = request.kind ?? external.hintKind ?? (request.type === "implements" ? "code" : "test");
3429
+ const title = request.title ?? external.title;
3430
+ const titleAttr = title !== void 0 ? ` title="${escapeAttr(title)}"` : "";
3431
+ const replacement = `<external uri="${escapeAttr(request.uri)}" kind="${escapeAttr(kind)}"${titleAttr}/>`;
3432
+ const editedSpan = span.replace(occurrences[0], () => replacement);
3433
+ const updated = xml.slice(0, open.index) + editedSpan + xml.slice(closeIdx);
3434
+ const before = checkIntegrity(xml).length;
3435
+ const reparsed = parse(updated);
3436
+ if (!reparsed.ok) {
3437
+ return {
3438
+ ok: false,
3439
+ error: `edit produced an unparseable document: ${reparsed.error.message}`
3440
+ };
3441
+ }
3442
+ if (checkIntegrity(updated).length > before) {
3443
+ return {
3444
+ ok: false,
3445
+ error: "edit introduced an integrity violation; document left unchanged"
3446
+ };
3447
+ }
3448
+ return {
3449
+ ok: true,
3450
+ xml: updated,
3451
+ edgeId,
3452
+ edgeXml: `${editedSpan}</edge>`.trim(),
3453
+ previousUri: external.uri
3454
+ };
3455
+ }
3456
+ function collectTitles(doc) {
3457
+ const m = /* @__PURE__ */ new Map();
3458
+ const set = (id, title) => {
3459
+ if (id !== void 0 && title !== void 0 && title !== "" && !m.has(id)) {
3460
+ m.set(id, title);
3461
+ }
3462
+ };
3463
+ for (const p of doc.meta.profiles ?? []) set(p.id, p.type);
3464
+ const c = doc.catalogs;
3465
+ if (c) {
3466
+ for (const t of c.glossary ?? []) set(t.id, t.name);
3467
+ for (const a of c.actors ?? []) set(a.id, a.name);
3468
+ for (const s of c.stakeholders ?? []) set(s.id, s.name);
3469
+ for (const x of c.constraints ?? []) set(x.id, x.statement);
3470
+ for (const x of c.policies ?? []) set(x.id, x.obligation);
3471
+ for (const x of c.decisions ?? []) set(x.id, x.decision);
3472
+ for (const x of c.risks ?? []) set(x.id, x.statement);
3473
+ }
3474
+ const d = doc.domain;
3475
+ if (d) {
3476
+ for (const e of d.entities ?? []) {
3477
+ set(e.id, e.name);
3478
+ for (const a of e.attrs ?? []) set(a.id, a.name);
3479
+ }
3480
+ for (const r of d.businessRules ?? []) set(r.id, r.statement);
3481
+ }
3482
+ const g = doc.goals;
3483
+ if (g) {
3484
+ for (const x of g.goals ?? []) set(x.id, x.title);
3485
+ for (const x of g.qualityGoals ?? []) set(x.id, x.title);
3486
+ for (const x of g.obstacles ?? []) set(x.id, x.title);
3487
+ for (const x of g.goalLinks ?? []) set(x.id, x.id);
3488
+ }
3489
+ const sc = doc.scenarios;
3490
+ if (sc) {
3491
+ for (const x of sc.scenarios ?? []) set(x.id, x.title);
3492
+ for (const x of sc.misuseCases ?? []) set(x.id, x.title);
3493
+ for (const x of sc.edgeCases ?? []) set(x.id, x.title);
3494
+ }
3495
+ for (const p of doc.packages) {
3496
+ set(p.id, p.title);
3497
+ for (const r of p.requirements) set(r.id, r.title);
3498
+ }
3499
+ for (const r of doc.looseRequirements) set(r.id, r.title);
3500
+ const b = doc.behavior;
3501
+ if (b) {
3502
+ for (const sm of b.stateMachines ?? []) {
3503
+ set(sm.id, sm.name);
3504
+ for (const st of sm.states) set(st.id, st.name);
3505
+ }
3506
+ }
3507
+ const it = doc.interfaces;
3508
+ if (it) {
3509
+ for (const api of it.apis ?? []) {
3510
+ set(api.id, api.name);
3511
+ for (const ep of api.endpoints ?? []) set(ep.id, `${ep.method} ${ep.path}`);
3512
+ }
3513
+ for (const ev of it.events ?? []) set(ev.id, ev.name);
3514
+ }
3515
+ const v = doc.verification;
3516
+ if (v) {
3517
+ for (const ts of v.testSuites ?? []) set(ts.id, ts.title);
3518
+ for (const tc of v.testCases ?? []) set(tc.id, tc.title);
3519
+ }
3520
+ const gv = doc.governance;
3521
+ if (gv) {
3522
+ for (const x of gv.issues ?? []) set(x.id, x.statement);
3523
+ for (const x of gv.approvals ?? []) set(x.id, x.role);
3524
+ }
3525
+ return m;
3526
+ }
3375
3527
  function endpointKey2(locator) {
3376
3528
  return locator.kind === "local" ? locator.id : locator.uri;
3377
3529
  }
@@ -3713,6 +3865,131 @@ function computeCoverage(doc) {
3713
3865
  function finding(source, rule, message) {
3714
3866
  return { source, severity: "warning", rule, message };
3715
3867
  }
3868
+ function refFromEndpoint(ep, titleById) {
3869
+ const loc = ep.locator;
3870
+ if (loc.kind === "local") {
3871
+ const ref2 = { id: loc.id };
3872
+ const title = ep.requirement?.title ?? loc.title ?? titleById.get(loc.id);
3873
+ if (title !== void 0) ref2.title = title;
3874
+ if (ep.target === void 0) ref2.broken = true;
3875
+ return ref2;
3876
+ }
3877
+ const ref = {
3878
+ id: loc.kind === "doc" ? `${loc.uri}#${loc.id}` : loc.uri,
3879
+ external: true
3880
+ };
3881
+ if (loc.title !== void 0) ref.title = loc.title;
3882
+ return ref;
3883
+ }
3884
+ function matches(row, filter) {
3885
+ if (filter === void 0) return true;
3886
+ if (filter.status && !filter.status.includes(row.status)) return false;
3887
+ if (filter.type && !filter.type.includes(row.type)) return false;
3888
+ if (filter.warning && !filter.warning.some((w) => row.warnings.includes(w)))
3889
+ return false;
3890
+ return true;
3891
+ }
3892
+ function buildMatrix(doc, filter) {
3893
+ const cov = computeCoverage(doc);
3894
+ const reqById = new Map(allRequirements(doc).map((r) => [r.id, r]));
3895
+ const titleById = collectTitles(doc);
3896
+ const edgeById = new Map(resolveTrace(doc).edges.map((re) => [re.edge.id, re]));
3897
+ const unverified = new Set(cov.unverifiedRequirements);
3898
+ const unimplemented = new Set(cov.unimplementedRequirements);
3899
+ const orphans = new Set(cov.orphanRequirements);
3900
+ const premature = new Set(cov.prematureImplementations.map((p) => p.requirementId));
3901
+ const rows = [];
3902
+ for (const ac of cov.requirements) {
3903
+ const req = reqById.get(ac.id);
3904
+ if (req === void 0) continue;
3905
+ const goals = (ac.outgoing.satisfies ?? []).flatMap((edgeId) => {
3906
+ const re = edgeById.get(edgeId);
3907
+ return re ? [refFromEndpoint(re.to, titleById)] : [];
3908
+ });
3909
+ const implementations = (ac.incoming.implements ?? []).flatMap((edgeId) => {
3910
+ const re = edgeById.get(edgeId);
3911
+ return re ? [refFromEndpoint(re.from, titleById)] : [];
3912
+ });
3913
+ const tests = (ac.outgoing.verifiedBy ?? []).flatMap((edgeId) => {
3914
+ const re = edgeById.get(edgeId);
3915
+ return re ? [refFromEndpoint(re.to, titleById)] : [];
3916
+ });
3917
+ const verification = unverified.has(ac.id) ? "unverified" : "verified";
3918
+ const implementation = premature.has(ac.id) ? "premature" : unimplemented.has(ac.id) ? "unimplemented" : "implemented";
3919
+ const warnings = [];
3920
+ if (unverified.has(ac.id)) warnings.push("unverified");
3921
+ if (unimplemented.has(ac.id)) warnings.push("unimplemented");
3922
+ if (orphans.has(ac.id)) warnings.push("orphan");
3923
+ if (premature.has(ac.id)) warnings.push("premature");
3924
+ if ([...goals, ...implementations, ...tests].some((r) => r.broken))
3925
+ warnings.push("broken-trace");
3926
+ const row = {
3927
+ id: ac.id,
3928
+ title: req.title,
3929
+ type: req.type,
3930
+ status: req.status ?? "draft",
3931
+ goals,
3932
+ implementations,
3933
+ tests,
3934
+ verification,
3935
+ implementation,
3936
+ warnings
3937
+ };
3938
+ if (req.priority !== void 0) row.priority = req.priority;
3939
+ rows.push(row);
3940
+ }
3941
+ const kept = rows.filter((r) => matches(r, filter));
3942
+ const summary = {
3943
+ total: kept.length,
3944
+ verified: kept.filter((r) => r.verification === "verified").length,
3945
+ unverified: kept.filter((r) => r.verification === "unverified").length,
3946
+ implemented: kept.filter((r) => r.implementation === "implemented").length,
3947
+ unimplemented: kept.filter((r) => r.implementation === "unimplemented").length,
3948
+ premature: kept.filter((r) => r.implementation === "premature").length,
3949
+ orphans: kept.filter((r) => r.warnings.includes("orphan")).length,
3950
+ brokenTraces: kept.filter((r) => r.warnings.includes("broken-trace")).length
3951
+ };
3952
+ return { rows: kept, summary };
3953
+ }
3954
+ function escapeCell2(value) {
3955
+ return value.replace(/\|/g, "\\|").replace(/\n/g, " ");
3956
+ }
3957
+ function refLabel(r) {
3958
+ const base = r.title !== void 0 ? `${r.id} (${r.title})` : r.id;
3959
+ return r.broken ? `${base} [broken]` : base;
3960
+ }
3961
+ function matrixToMarkdown(report) {
3962
+ const s = report.summary;
3963
+ const lines = [
3964
+ "# Traceability matrix",
3965
+ "",
3966
+ `- **Requirements:** ${s.total}`,
3967
+ `- **Verified:** ${s.verified} \xB7 **Unverified:** ${s.unverified}`,
3968
+ `- **Implemented:** ${s.implemented} \xB7 **Unimplemented:** ${s.unimplemented} \xB7 **Premature:** ${s.premature}`,
3969
+ `- **Orphans:** ${s.orphans} \xB7 **Broken traces:** ${s.brokenTraces}`,
3970
+ "",
3971
+ "| ID | Title | Type | Status | Verify | Impl | Goals | Implemented by | Verified by | Warnings |",
3972
+ "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |"
3973
+ ];
3974
+ for (const row of report.rows) {
3975
+ const cells = [
3976
+ row.id,
3977
+ row.title,
3978
+ row.type,
3979
+ row.status,
3980
+ row.verification,
3981
+ row.implementation,
3982
+ row.goals.map(refLabel).join(", "),
3983
+ row.implementations.map(refLabel).join(", "),
3984
+ row.tests.map(refLabel).join(", "),
3985
+ row.warnings.join(", ")
3986
+ ].map(escapeCell2);
3987
+ lines.push(`| ${cells.join(" | ")} |`);
3988
+ }
3989
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
3990
+ return `${lines.join("\n")}
3991
+ `;
3992
+ }
3716
3993
  var BASELINE_PATH = ".rqml/baseline.json";
3717
3994
  function hashFileAt(filePath) {
3718
3995
  try {
@@ -4010,7 +4287,7 @@ async function runImpact(rest) {
4010
4287
  }
4011
4288
 
4012
4289
  // ../schema/dist/index.js
4013
- var AGENTS_default = '# RQML Agent Guidelines\n\n## Strictness: `standard`\n\n| Level | Description |\n|-------|-------------|\n| `relaxed` | Prototyping. Spec is advisory. Quick iteration allowed. |\n| `standard` | Production default. Spec-first for features. Core traces. |\n| `strict` | Full traceability. All behavior specified. No ghost features. |\n| `certified` | Regulated/safety-critical. Audit-grade traces with metadata. |\n\n---\n\nThis project uses **RQML** as the single source of truth for system intent. Familiarize yourself with the documentation at https://rqml.org/docs/user-guide/\n\n**Specification file:** Specification lives in a single .rqml file in the root of the project - convention is `requirements.rqml`. Multiple .rqml files may be employed in multirepo projects, in such cases a .rqml spec applies to everything that is higher in the project tree, unless overridden by another .rqml file.\n\n**Schema file:**\nThe RQML XSD schema is at https://rqml.org/schema/rqml-2.1.0.xsd (insert correct version number). Make sure to adhere to the schema at all times and follow guidelines in schema comments. Use as much of the RQML tagset as is necessary to capture and describe high quality requirements.\n\n---\n\n## Toolchain\n\nThe spec-first loop is enforced by the `rqml` CLI (npm: `@rqml/cli`; the `@rqml/mcp` server exposes the same engine as agent tools):\n\n```bash\nrqml check # deterministic gate: validation + coverage + drift (exit 0 = pass)\nrqml status # re-anchor: spec, coverage, and drift state\nrqml show <REQ-ID> # one requirement: statement, acceptance criteria, trace neighborhood\nrqml impact <ID> # what is affected, transitively, if this artifact changes\nrqml link <REQ-ID> <path> # record an implements edge + drift baseline (--type verifiedBy for tests)\nrqml skeleton <kind> # schema-valid snippet: req | edge | testCase | stateMachine\n```\n\nRun `rqml status` when you start a session to re-anchor on the spec. Run `rqml check` before finishing any task \u2014 it must exit 0.\n\n---\n\n## Core Principle: Spec-First Development\n\n```\n[Elicit] \u2192 [Specify] \u2192 [Implement] \u2192 [Verify] \u2192 [Trace]\n \u2191____________________\u2190______________________|\n```\n\nCode follows specification, not the reverse. If code and spec diverge, the spec is authoritative\u2014update the code or negotiate a spec change with the developer.\n\n---\n\n## Workflow\n\n### 1. Elicit\nAsk clarifying questions until you understand the goal, scope, acceptance criteria, and constraints. Don\'t assume\u2014capture assumptions as `<notes>` or `<issue>` elements.\n\n### 2. Specify\n**Never implement unspecified behavior.** Update the `.rqml` file before coding:\n- Add a `<req>` with statement and acceptance criteria\n- Set appropriate `type`, `priority`, and `status="draft"`\n- Get developer confirmation before proceeding\n\n### 3. Implement\nRead the requirement first: `rqml show REQ-XXX`. Check blast radius before changing existing artifacts: `rqml impact REQ-XXX`. If you discover missing requirements, stop and add them to the spec first. After implementing, record the trace link:\n\n```bash\nrqml link REQ-XXX src/path/to/implementation.ts\n```\n\n### 4. Verify\nAdd tests that reference requirement IDs, then record verification:\n\n```bash\nrqml link REQ-XXX test/path/to/test.ts --type verifiedBy\nrqml check # must exit 0 before you are done\n```\n\n---\n\n## When Code and Spec Diverge\n\n1. **Spec gap** (code has behavior not in spec): Propose adding the requirement, mark as `status="review"`\n2. **Code bug** (code doesn\'t match spec): Fix the code\n3. **Spec bug** (spec is wrong): Propose correction, wait for developer confirmation\n\n**Never silently change the spec to match code.**\n\n---\n\n## Strictness Reference\n\n| Aspect | relaxed | standard | strict | certified |\n|--------|---------|----------|--------|-----------|\n| Elicitation | Major features | Testable reqs | Edge cases | Formal |\n| Spec-first | Recommended | Required | Required | Approved first |\n| Code traces | Optional | New features | All changes | With metadata |\n| Test traces | Optional | New reqs | All reqs | Full matrix |\n| Ghost features | Allowed | Blocked | Blocked | Blocked |\n\n---\n\n## Change Summary Template\n\nFor PRs and commits:\n\n```\n## RQML Trace Summary\n\n**Requirements:** REQ-xxx (added/modified/implemented)\n**Implementation:** `path/to/file` \u2014 what changed\n**Verification:** `path/to/test` \u2014 what it verifies\n**Open items:** gaps, assumptions, follow-ups\n```\n\n---\n\n## Schema Validation\n\nThe `.rqml` file must remain valid XML conforming to the version of RQML referenced in the version attribute in the spec document.\n\n**To validate:** Use the toolchain \u2014 it validates offline against the bundled schema and also checks referential integrity the XSD alone cannot enforce:\n```bash\nrqml validate\n```\n\nIf the `rqml` CLI is not installed, `npx @rqml/cli validate` works without installation. As a last resort, xmllint (pre-installed on macOS/Linux) checks XSD validity only:\n```bash\nxmllint --schema https://rqml.org/schema/rqml-2.1.0.xsd <rqml-file-name> --noout\n```\n\n**IDE validation:** If the `.rqml` file includes `xsi:schemaLocation`, XML-aware editors (VS Code with XML extension, IntelliJ) validate automatically.\n\nThe schema comments contain detailed guidance on document structure, ID conventions, and requirement quality criteria.\n\n**If unsure:** Ask the developer before making structural changes to the spec.\n';
4290
+ var AGENTS_default = '# RQML Agent Guidelines\n\n## Strictness: `standard`\n\n| Level | Description |\n|-------|-------------|\n| `relaxed` | Prototyping. Spec is advisory. Quick iteration allowed. |\n| `standard` | Production default. Spec-first for features. Core traces. |\n| `strict` | Full traceability. All behavior specified. No ghost features. |\n| `certified` | Regulated/safety-critical. Audit-grade traces with metadata. |\n\n---\n\nThis project uses **RQML** as the single source of truth for system intent. Familiarize yourself with the documentation at https://rqml.org/docs/user-guide/ and the development process at https://rqml.org/docs/development-process/\n\n**Specification file:** Specification lives in a single .rqml file in the root of the project - convention is `requirements.rqml`. Multiple .rqml files may be employed in multirepo projects, in such cases a .rqml spec applies to everything that is higher in the project tree, unless overridden by another .rqml file.\n\n**Schema file:**\nThe RQML XSD schema is at https://rqml.org/schema/rqml-2.1.0.xsd (insert correct version number). Make sure to adhere to the schema at all times and follow guidelines in schema comments. Use as much of the RQML tagset as is necessary to capture and describe high quality requirements.\n\n---\n\n## Toolchain\n\nThe spec-first loop is enforced by the `rqml` CLI (npm: `@rqml/cli`; the `@rqml/mcp` server exposes the same engine as agent tools):\n\n```bash\nrqml check # deterministic gate: validation + coverage + drift (exit 0 = pass)\nrqml status # re-anchor: spec, coverage, and drift state\nrqml show <REQ-ID> # one requirement: statement, acceptance criteria, trace neighborhood\nrqml impact <ID> # what is affected, transitively, if this artifact changes\nrqml link <REQ-ID> <path> # record an implements edge + drift baseline (--type verifiedBy for tests)\nrqml skeleton <kind> # schema-valid snippet: req | edge | testCase | stateMachine\n```\n\nRun `rqml status` when you start a session to re-anchor on the spec. Run `rqml check` before finishing any task \u2014 it must exit 0.\n\n---\n\n## Core Principle: Spec-First Development\n\nCode follows specification, not the reverse. If code and spec diverge, the spec is authoritative\u2014update the code or negotiate a spec change with the developer.\n\nRQML organizes work into a **five-stage process** (https://rqml.org/docs/development-process/). Each stage produces a durable artifact in version control; verification feeds back to the spec, so it is a loop:\n\n| Stage | Task | Output |\n|-------|------|--------|\n| **Spec** | Capture intent as requirements | `requirements.rqml` |\n| **Design** | Decide architecture, record decisions | ADRs in `.rqml/adr/` |\n| **Plan** | Break work into agent-sized stages | `.rqml/plan.md` |\n| **Code** | Implement specified behavior, keep traces current | code + tests |\n| **Verify** | Prove coverage and catch drift | trace graph + `rqml check` |\n\nNever skip ahead: do not implement behavior that is not specified, and do not make a significant architectural choice without recording it as an ADR.\n\n---\n\n## Workflow\n\n### 1. Spec\nAsk clarifying questions until you understand the goal, scope, acceptance criteria, and constraints. Don\'t assume\u2014capture assumptions as `<notes>` or `<issue>` elements. **Never implement unspecified behavior.** Update the `.rqml` file before coding:\n- Add a `<req>` with statement and acceptance criteria\n- Set appropriate `type`, `priority`, and `status="draft"`\n- Get developer confirmation; only `status="approved"` requirements drive implementation\n\n### 2. Design\nBefore building, decide *how*. Record each significant architectural decision as an **Architecture Decision Record (ADR)** in `.rqml/adr/`, following the canonical format (https://rqml.org/docs/development-process/design): `NNNN-kebab-case-slug.md`, with Status, Classification, Context, Options considered, Decision, and Consequences. A decision is ADR-worthy when there are real alternatives or the choice constrains future work; skip ADRs for low-level implementation details. ADRs are immutable once accepted\u2014supersede, don\'t edit.\n\n### 3. Plan\nBreak approved requirements into a staged implementation plan at `.rqml/plan.md`, framed for coding agents: each stage names its goal, the requirement IDs it addresses, the files it touches, and how to verify it.\n\n### 4. Code (Implement)\nRead the requirement first: `rqml show REQ-XXX`. Check blast radius before changing existing artifacts: `rqml impact REQ-XXX`. Honor the ADRs. If you discover missing requirements, stop and add them to the spec first. After implementing, record the trace link:\n\n```bash\nrqml link REQ-XXX src/path/to/implementation.ts\n```\n\n### 5. Verify\nAdd tests that reference requirement IDs, then record verification and run the gate:\n\n```bash\nrqml link REQ-XXX test/path/to/test.ts --type verifiedBy\nrqml check # must exit 0 before you are done\n```\n\n---\n\n## When Code and Spec Diverge\n\n1. **Spec gap** (code has behavior not in spec): Propose adding the requirement, mark as `status="review"`\n2. **Code bug** (code doesn\'t match spec): Fix the code\n3. **Spec bug** (spec is wrong): Propose correction, wait for developer confirmation\n\n**Never silently change the spec to match code.**\n\n---\n\n## Strictness Reference\n\n| Aspect | relaxed | standard | strict | certified |\n|--------|---------|----------|--------|-----------|\n| Spec (elicitation) | Major features | Testable reqs | Edge cases | Formal |\n| Spec-first | Recommended | Required | Required | Approved first |\n| Design (ADRs) | Optional | Significant choices | All architectural choices | With approval |\n| Plan | Optional | For multi-stage work | Required | Required |\n| Code traces | Optional | New features | All changes | With metadata |\n| Verify (test traces) | Optional | New reqs | All reqs | Full matrix |\n| Ghost features | Allowed | Blocked | Blocked | Blocked |\n\n---\n\n## Change Summary Template\n\nFor PRs and commits:\n\n```\n## RQML Trace Summary\n\n**Requirements:** REQ-xxx (added/modified/implemented)\n**Design:** ADR-xxxx \u2014 decision recorded (if any)\n**Implementation:** `path/to/file` \u2014 what changed\n**Verification:** `path/to/test` \u2014 what it verifies\n**Open items:** gaps, assumptions, follow-ups\n```\n\n---\n\n## Schema Validation\n\nThe `.rqml` file must remain valid XML conforming to the version of RQML referenced in the version attribute in the spec document.\n\n**To validate:** Use the toolchain \u2014 it validates offline against the bundled schema and also checks referential integrity the XSD alone cannot enforce:\n```bash\nrqml validate\n```\n\nIf the `rqml` CLI is not installed, `npx @rqml/cli validate` works without installation. As a last resort, xmllint (pre-installed on macOS/Linux) checks XSD validity only:\n```bash\nxmllint --schema https://rqml.org/schema/rqml-2.1.0.xsd <rqml-file-name> --noout\n```\n\n**IDE validation:** If the `.rqml` file includes `xsi:schemaLocation`, XML-aware editors (VS Code with XML extension, IntelliJ) validate automatically.\n\nThe schema comments contain detailed guidance on document structure, ID conventions, and requirement quality criteria.\n\n**If unsure:** Ask the developer before making structural changes to the spec.\n';
4014
4291
  var AGENTS_TEMPLATE = AGENTS_default;
4015
4292
 
4016
4293
  // src/commands/init.ts
@@ -4053,15 +4330,21 @@ async function runInit(rest) {
4053
4330
  if (!wrote) process.stdout.write("nothing to do; project already initialized\n");
4054
4331
  return EXIT.OK;
4055
4332
  }
4056
- var USAGE = "usage: rqml link <artifact-id> <uri> [--type implements|verifiedBy] [--id <edge-id>] [--kind <k>] [--title <t>] [--spec <path>]";
4333
+ var USAGE = "usage: rqml link <artifact-id> <uri> [--update] [--type implements|verifiedBy] [--id <edge-id>] [--kind <k>] [--title <t>] [--spec <path>]\n rqml link --refresh <edge-id> [--spec <path>]";
4057
4334
  async function runLink(rest) {
4058
4335
  const args = parseArgs(rest);
4336
+ if (args.flags.has("refresh")) {
4337
+ const edgeId2 = flagString(args, "refresh");
4338
+ if (edgeId2 === void 0) throw new UsageError(USAGE);
4339
+ return runRefresh(args, edgeId2);
4340
+ }
4059
4341
  const [artifactId, uri] = args.positionals;
4060
4342
  if (artifactId === void 0 || uri === void 0) throw new UsageError(USAGE);
4061
4343
  const type = flagString(args, "type") ?? "implements";
4062
4344
  if (type !== "implements" && type !== "verifiedBy") {
4063
4345
  throw new UsageError(`unknown link type "${type}" (implements|verifiedBy)`);
4064
4346
  }
4347
+ const update = args.flags.get("update") === true || args.flags.get("update") === "true";
4065
4348
  const { path, xml } = readSpec(specArgs(args));
4066
4349
  const request = { artifactId, uri, type };
4067
4350
  const edgeId = flagString(args, "id");
@@ -4070,7 +4353,7 @@ async function runLink(rest) {
4070
4353
  if (kind !== void 0) request.kind = kind;
4071
4354
  const title = flagString(args, "title");
4072
4355
  if (title !== void 0) request.title = title;
4073
- const result = appendTraceEdge(xml, request);
4356
+ const result = update ? updateTraceEdge(xml, request) : appendTraceEdge(xml, request);
4074
4357
  if (!result.ok) {
4075
4358
  process.stderr.write(`\u2717 link failed: ${result.error}
4076
4359
  `);
@@ -4099,6 +4382,7 @@ async function runLink(rest) {
4099
4382
  if (args.json) {
4100
4383
  const report = {
4101
4384
  spec: path,
4385
+ mode: update ? "update" : "append",
4102
4386
  edgeId: result.edgeId,
4103
4387
  type,
4104
4388
  artifactId,
@@ -4109,12 +4393,81 @@ async function runLink(rest) {
4109
4393
  `);
4110
4394
  } else {
4111
4395
  const arrow = type === "implements" ? "\u2190" : "\u2192";
4396
+ const mode = update ? ", updated" : "";
4112
4397
  const baseline = baselineRecorded ? ", baseline recorded" : "";
4113
4398
  process.stdout.write(
4114
- `\u2713 ${artifactId} ${arrow} ${uri} (${result.edgeId}, ${type}${baseline})
4399
+ `\u2713 ${artifactId} ${arrow} ${uri} (${result.edgeId}, ${type}${mode}${baseline})
4400
+ `
4401
+ );
4402
+ }
4403
+ return EXIT.OK;
4404
+ }
4405
+ function runRefresh(args, edgeId) {
4406
+ const { path, xml } = readSpec(specArgs(args));
4407
+ const parsed = parse(xml);
4408
+ if (!parsed.ok) {
4409
+ process.stderr.write(`\u2717 refresh failed: ${parsed.error.message}
4410
+ `);
4411
+ return EXIT.VALIDATION;
4412
+ }
4413
+ const link = implementsLinks(parsed.document).find((l) => l.edgeId === edgeId);
4414
+ if (link === void 0) {
4415
+ process.stderr.write(
4416
+ `\u2717 refresh failed: no implements edge "${edgeId}" with an external locator exists (only implements edges carry baselines)
4115
4417
  `
4116
4418
  );
4419
+ return EXIT.VALIDATION;
4117
4420
  }
4421
+ const hash = computeBaseline(parsed.document, { baseDir: args.baseDir })[edgeId];
4422
+ if (hash === void 0) {
4423
+ process.stderr.write(
4424
+ `\u2717 refresh failed: "${link.uri}" cannot be hashed (missing file or non-filesystem URI)
4425
+ `
4426
+ );
4427
+ return EXIT.VALIDATION;
4428
+ }
4429
+ const baseline = loadBaseline(args.baseDir) ?? {};
4430
+ baseline[edgeId] = hash;
4431
+ saveBaseline(args.baseDir, baseline);
4432
+ if (args.json) {
4433
+ const report = { spec: path, mode: "refresh", edgeId, uri: link.uri, hash };
4434
+ process.stdout.write(`${JSON.stringify(report, null, 2)}
4435
+ `);
4436
+ } else {
4437
+ process.stdout.write(`\u2713 baseline refreshed for ${edgeId} (${link.uri})
4438
+ `);
4439
+ }
4440
+ return EXIT.OK;
4441
+ }
4442
+
4443
+ // src/commands/matrix.ts
4444
+ function list(value) {
4445
+ if (value === void 0) return void 0;
4446
+ const items = value.split(",").map((s) => s.trim()).filter(Boolean);
4447
+ return items.length > 0 ? items : void 0;
4448
+ }
4449
+ async function runMatrix(rest) {
4450
+ const args = parseArgs(rest);
4451
+ const { path, xml } = readSpec(specArgs(args));
4452
+ const parsed = parse(xml);
4453
+ if (!parsed.ok) {
4454
+ process.stderr.write(`\u2717 ${path}: ${parsed.error.message}
4455
+ `);
4456
+ return EXIT.VALIDATION;
4457
+ }
4458
+ const status = list(flagString(args, "status"));
4459
+ const type = list(flagString(args, "type"));
4460
+ const warning = list(flagString(args, "warning"));
4461
+ const filter = {};
4462
+ if (status) filter.status = status;
4463
+ if (type) filter.type = type;
4464
+ if (warning) filter.warning = warning;
4465
+ const filtered = status !== void 0 || type !== void 0 || warning !== void 0;
4466
+ const matrix = buildMatrix(parsed.document, filtered ? filter : void 0);
4467
+ process.stdout.write(
4468
+ args.json ? `${JSON.stringify(matrix, null, 2)}
4469
+ ` : matrixToMarkdown(matrix)
4470
+ );
4118
4471
  return EXIT.OK;
4119
4472
  }
4120
4473
 
@@ -4234,7 +4587,7 @@ async function runValidate(rest) {
4234
4587
  }
4235
4588
 
4236
4589
  // src/index.ts
4237
- var VERSION = "0.1.0";
4590
+ var VERSION = createRequire(import.meta.url)("../package.json").version;
4238
4591
  var HELP = `rqml \u2014 RQML reference CLI (v${VERSION})
4239
4592
 
4240
4593
  Usage:
@@ -4246,12 +4599,16 @@ Commands:
4246
4599
  status [path] Show spec, coverage, and lint summary
4247
4600
  check [path] Deterministic enforcement gate (validation + coverage + drift)
4248
4601
  link <id> <uri> Record an implements/verifiedBy edge and its drift baseline
4602
+ (--update repoints an existing edge; --refresh <edge-id>
4603
+ re-records only the baseline for an intentional change)
4249
4604
  show <id> Extract one artifact with its trace neighborhood
4250
4605
  impact <id> What is affected, transitively, if this artifact changes
4606
+ matrix [path] Traceability matrix: status, goals, code, tests, warnings
4251
4607
  skeleton <kind> Print a schema-valid snippet (req|edge|testCase|stateMachine)
4252
4608
 
4253
4609
  Options:
4254
- --json Emit machine-readable JSON (status, check, validate, link, show, impact)
4610
+ --json Emit machine-readable JSON (status, check, validate, link, show, impact, matrix)
4611
+ --status/--type/--warning Filter matrix rows (comma-separated, e.g. --warning unverified)
4255
4612
  --strictness <level> relaxed | standard | strict | certified (default: standard)
4256
4613
  --base-dir <dir> Directory to resolve the spec and code links against
4257
4614
  --spec <path> Explicit spec file (link, show, impact)
@@ -4259,6 +4616,8 @@ Options:
4259
4616
  --id <id> Explicit edge id (link) or skeleton root id
4260
4617
  --kind <kind> Locator kind hint for link (default: code/test by type)
4261
4618
  --title <title> Locator title hint for link
4619
+ --update Replace the external locator of an existing edge (link)
4620
+ --refresh <edge-id> Re-record the drift baseline for one edge (link)
4262
4621
  -h, --help Show this help
4263
4622
  -v, --version Show version
4264
4623
 
@@ -4281,6 +4640,8 @@ async function main(argv) {
4281
4640
  return runShow(rest);
4282
4641
  case "impact":
4283
4642
  return runImpact(rest);
4643
+ case "matrix":
4644
+ return runMatrix(rest);
4284
4645
  case "skeleton":
4285
4646
  return runSkeleton(rest);
4286
4647
  case "-v":