@rqml/cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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';
@@ -3372,6 +3373,86 @@ ${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
+ }
3375
3456
  function endpointKey2(locator) {
3376
3457
  return locator.kind === "local" ? locator.id : locator.uri;
3377
3458
  }
@@ -4053,15 +4134,21 @@ async function runInit(rest) {
4053
4134
  if (!wrote) process.stdout.write("nothing to do; project already initialized\n");
4054
4135
  return EXIT.OK;
4055
4136
  }
4056
- var USAGE = "usage: rqml link <artifact-id> <uri> [--type implements|verifiedBy] [--id <edge-id>] [--kind <k>] [--title <t>] [--spec <path>]";
4137
+ 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
4138
  async function runLink(rest) {
4058
4139
  const args = parseArgs(rest);
4140
+ if (args.flags.has("refresh")) {
4141
+ const edgeId2 = flagString(args, "refresh");
4142
+ if (edgeId2 === void 0) throw new UsageError(USAGE);
4143
+ return runRefresh(args, edgeId2);
4144
+ }
4059
4145
  const [artifactId, uri] = args.positionals;
4060
4146
  if (artifactId === void 0 || uri === void 0) throw new UsageError(USAGE);
4061
4147
  const type = flagString(args, "type") ?? "implements";
4062
4148
  if (type !== "implements" && type !== "verifiedBy") {
4063
4149
  throw new UsageError(`unknown link type "${type}" (implements|verifiedBy)`);
4064
4150
  }
4151
+ const update = args.flags.get("update") === true || args.flags.get("update") === "true";
4065
4152
  const { path, xml } = readSpec(specArgs(args));
4066
4153
  const request = { artifactId, uri, type };
4067
4154
  const edgeId = flagString(args, "id");
@@ -4070,7 +4157,7 @@ async function runLink(rest) {
4070
4157
  if (kind !== void 0) request.kind = kind;
4071
4158
  const title = flagString(args, "title");
4072
4159
  if (title !== void 0) request.title = title;
4073
- const result = appendTraceEdge(xml, request);
4160
+ const result = update ? updateTraceEdge(xml, request) : appendTraceEdge(xml, request);
4074
4161
  if (!result.ok) {
4075
4162
  process.stderr.write(`\u2717 link failed: ${result.error}
4076
4163
  `);
@@ -4099,6 +4186,7 @@ async function runLink(rest) {
4099
4186
  if (args.json) {
4100
4187
  const report = {
4101
4188
  spec: path,
4189
+ mode: update ? "update" : "append",
4102
4190
  edgeId: result.edgeId,
4103
4191
  type,
4104
4192
  artifactId,
@@ -4109,14 +4197,52 @@ async function runLink(rest) {
4109
4197
  `);
4110
4198
  } else {
4111
4199
  const arrow = type === "implements" ? "\u2190" : "\u2192";
4200
+ const mode = update ? ", updated" : "";
4112
4201
  const baseline = baselineRecorded ? ", baseline recorded" : "";
4113
4202
  process.stdout.write(
4114
- `\u2713 ${artifactId} ${arrow} ${uri} (${result.edgeId}, ${type}${baseline})
4203
+ `\u2713 ${artifactId} ${arrow} ${uri} (${result.edgeId}, ${type}${mode}${baseline})
4115
4204
  `
4116
4205
  );
4117
4206
  }
4118
4207
  return EXIT.OK;
4119
4208
  }
4209
+ function runRefresh(args, edgeId) {
4210
+ const { path, xml } = readSpec(specArgs(args));
4211
+ const parsed = parse(xml);
4212
+ if (!parsed.ok) {
4213
+ process.stderr.write(`\u2717 refresh failed: ${parsed.error.message}
4214
+ `);
4215
+ return EXIT.VALIDATION;
4216
+ }
4217
+ const link = implementsLinks(parsed.document).find((l) => l.edgeId === edgeId);
4218
+ if (link === void 0) {
4219
+ process.stderr.write(
4220
+ `\u2717 refresh failed: no implements edge "${edgeId}" with an external locator exists (only implements edges carry baselines)
4221
+ `
4222
+ );
4223
+ return EXIT.VALIDATION;
4224
+ }
4225
+ const hash = computeBaseline(parsed.document, { baseDir: args.baseDir })[edgeId];
4226
+ if (hash === void 0) {
4227
+ process.stderr.write(
4228
+ `\u2717 refresh failed: "${link.uri}" cannot be hashed (missing file or non-filesystem URI)
4229
+ `
4230
+ );
4231
+ return EXIT.VALIDATION;
4232
+ }
4233
+ const baseline = loadBaseline(args.baseDir) ?? {};
4234
+ baseline[edgeId] = hash;
4235
+ saveBaseline(args.baseDir, baseline);
4236
+ if (args.json) {
4237
+ const report = { spec: path, mode: "refresh", edgeId, uri: link.uri, hash };
4238
+ process.stdout.write(`${JSON.stringify(report, null, 2)}
4239
+ `);
4240
+ } else {
4241
+ process.stdout.write(`\u2713 baseline refreshed for ${edgeId} (${link.uri})
4242
+ `);
4243
+ }
4244
+ return EXIT.OK;
4245
+ }
4120
4246
 
4121
4247
  // src/commands/show.ts
4122
4248
  async function runShow(rest) {
@@ -4234,7 +4360,7 @@ async function runValidate(rest) {
4234
4360
  }
4235
4361
 
4236
4362
  // src/index.ts
4237
- var VERSION = "0.1.0";
4363
+ var VERSION = createRequire(import.meta.url)("../package.json").version;
4238
4364
  var HELP = `rqml \u2014 RQML reference CLI (v${VERSION})
4239
4365
 
4240
4366
  Usage:
@@ -4246,6 +4372,8 @@ Commands:
4246
4372
  status [path] Show spec, coverage, and lint summary
4247
4373
  check [path] Deterministic enforcement gate (validation + coverage + drift)
4248
4374
  link <id> <uri> Record an implements/verifiedBy edge and its drift baseline
4375
+ (--update repoints an existing edge; --refresh <edge-id>
4376
+ re-records only the baseline for an intentional change)
4249
4377
  show <id> Extract one artifact with its trace neighborhood
4250
4378
  impact <id> What is affected, transitively, if this artifact changes
4251
4379
  skeleton <kind> Print a schema-valid snippet (req|edge|testCase|stateMachine)
@@ -4259,6 +4387,8 @@ Options:
4259
4387
  --id <id> Explicit edge id (link) or skeleton root id
4260
4388
  --kind <kind> Locator kind hint for link (default: code/test by type)
4261
4389
  --title <title> Locator title hint for link
4390
+ --update Replace the external locator of an existing edge (link)
4391
+ --refresh <edge-id> Re-record the drift baseline for one edge (link)
4262
4392
  -h, --help Show this help
4263
4393
  -v, --version Show version
4264
4394