cabloy 5.1.198 → 5.1.200

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/.cabloy-version CHANGED
@@ -1 +1 @@
1
- 5.1.198
1
+ 5.1.200
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.1.200
4
+
5
+ ### Features
6
+
7
+ - Apply feature updates.
8
+
9
+ ## 5.1.199
10
+
11
+ ### Improvements
12
+
13
+ - Update implementation chart tests.
14
+
3
15
  ## 5.1.198
4
16
 
5
17
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cabloy",
3
- "version": "5.1.198",
3
+ "version": "5.1.200",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "A Node.js fullstack framework",
6
6
  "keywords": [
@@ -208,6 +208,50 @@ test('local Markdown destinations, fragments and reference links are checked', a
208
208
  );
209
209
  });
210
210
 
211
+ test('link destinations are not authority references but labels and prose remain audited', async t => {
212
+ const records = {
213
+ ...BASELINE,
214
+ 'rollout.md':
215
+ '# Rollout\n\n[WBS-MISSING-01](./evidence/WBS-DEMO-10-01-current-source.md) and `ATP-MISSING-01`.\n\n![SRS-MISSING-01](<./evidence/WBS-DEMO-10-01-image.md>)\n\n[WBS-DEMO-10-01][proof]\n\n[proof]: ./evidence/WBS-DEMO-10-01-referenced.md\n',
216
+ 'evidence/WBS-DEMO-10-01-current-source.md': '# Current source\n',
217
+ 'evidence/WBS-DEMO-10-01-image.md': '# Image\n',
218
+ 'evidence/WBS-DEMO-10-01-referenced.md': '# Referenced\n',
219
+ };
220
+ const result = await auditPlanning(await fixture(t, records));
221
+ assert.deepEqual(
222
+ result.diagnostics.map(diagnostic => [
223
+ diagnostic.code,
224
+ diagnostic.file,
225
+ diagnostic.line,
226
+ diagnostic.id,
227
+ ]),
228
+ [
229
+ ['undefined-reference', 'rollout.md', 3, 'ATP-MISSING-01'],
230
+ ['undefined-reference', 'rollout.md', 3, 'WBS-MISSING-01'],
231
+ ['undefined-reference', 'rollout.md', 5, 'SRS-MISSING-01'],
232
+ ],
233
+ );
234
+ });
235
+
236
+ test('destination masking preserves local link and reference diagnostics', async t => {
237
+ const records = {
238
+ ...BASELINE,
239
+ 'rollout.md':
240
+ '# Rollout\n\n[Missing](./evidence/WBS-DEMO-10-01-missing.md)\n[Bad fragment](./evidence/WBS-DEMO-10-01-existing.md#absent)\n[Good fragment](./evidence/WBS-DEMO-10-01-existing.md#present)\n[Bad encoding](./evidence/WBS-DEMO-10-01-%ZZ.md)\n[Undefined][missing]\n\n[unused]: <./evidence/WBS-DEMO-10-01-unused.md>\n',
241
+ 'evidence/WBS-DEMO-10-01-existing.md': '# Present\n',
242
+ };
243
+ const result = await auditPlanning(await fixture(t, records));
244
+ assert.deepEqual(
245
+ result.diagnostics.map(diagnostic => [diagnostic.code, diagnostic.line]),
246
+ [
247
+ ['link-target', 3],
248
+ ['link-fragment', 4],
249
+ ['link-format', 6],
250
+ ['link-reference', 7],
251
+ ],
252
+ );
253
+ });
254
+
211
255
  test('exact range endpoints do not masquerade as exact traceability', async t => {
212
256
  const records = {
213
257
  ...BASELINE,
@@ -142,17 +142,31 @@ function headingIds(markdown) {
142
142
  return slugs;
143
143
  }
144
144
 
145
+ const LINK_DEFINITION = /^(\s*\[([^\]]+)\]:\s*)(<[^>]+>|\S+)/;
146
+ const INLINE_LINK = /(!?\[[^\]]*\]\(\s*)(<[^>]+>|[^\s)]+)((?:\s+["'][^\n]*["'])?\s*\))/g;
147
+
148
+ function referenceText(text) {
149
+ return text
150
+ .replace(
151
+ LINK_DEFINITION,
152
+ (_, start, _label, destination) => `${start}${' '.repeat(destination.length)}`,
153
+ )
154
+ .replace(
155
+ INLINE_LINK,
156
+ (_, start, destination, end) => `${start}${' '.repeat(destination.length)}${end}`,
157
+ );
158
+ }
159
+
145
160
  function markdownLinks(markdown) {
146
161
  const lines = markdownLines(markdown);
147
162
  const references = new Map();
148
163
  const links = [];
149
164
  for (const { text, line } of lines) {
150
- const declaration = text.match(/^\s*\[([^\]]+)\]:\s*(<[^>]+>|\S+)/);
165
+ const declaration = text.match(LINK_DEFINITION);
151
166
  if (declaration)
152
- references.set(declaration[1].trim().toLowerCase(), declaration[2].replace(/^<|>$/g, ''));
153
- const inline = /!?\[[^\]]*\]\(\s*(<[^>]+>|[^\s)]+)(?:\s+["'][^\n]*["'])?\s*\)/g;
154
- for (const match of text.matchAll(inline))
155
- links.push({ destination: match[1].replace(/^<|>$/g, ''), line });
167
+ references.set(declaration[2].trim().toLowerCase(), declaration[3].replace(/^<|>$/g, ''));
168
+ for (const match of text.matchAll(INLINE_LINK))
169
+ links.push({ destination: match[2].replace(/^<|>$/g, ''), line });
156
170
  const referenced = /!?\[([^\]]+)\]\[([^\]]*)\]/g;
157
171
  for (const match of text.matchAll(referenced))
158
172
  links.push({ reference: (match[2] || match[1]).trim().toLowerCase(), line });
@@ -260,7 +274,7 @@ export async function auditPlanning(
260
274
  for (const [file, markdown] of records) {
261
275
  for (const { text, line } of markdownLines(markdown)) {
262
276
  for (const prefix of OWNERS.keys()) {
263
- for (const id of findIdentifiers(text, prefix)) {
277
+ for (const id of findIdentifiers(referenceText(text), prefix)) {
264
278
  if (!definitions.has(id)) {
265
279
  report(
266
280
  'undefined-reference',
@@ -1,5 +1,5 @@
1
1
  import assert from 'node:assert/strict';
2
- import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { resolve } from 'node:path';
5
5
  import { test } from 'node:test';
@@ -440,20 +440,19 @@ test('ATP catalogue role headers define scenarios but coverage and evidence tabl
440
440
  );
441
441
  });
442
442
 
443
- test('existing business ATP catalogues remain compatible with strict definitions', async () => {
443
+ test('repository ATP catalogues remain compatible with strict definitions', async () => {
444
444
  const root = resolve(import.meta.dirname, '../../..');
445
- const commerce = parseAtpIds(
446
- await readFile(resolve(root, 'repo-specs/a-commerce/test-plan.md'), 'utf8'),
447
- );
448
- const account = parseAtpIds(
449
- await readFile(resolve(root, 'repo-specs/home-user/test-plan.md'), 'utf8'),
450
- );
451
- assert.ok(commerce.has('ATP-SPC-04'));
452
- assert.ok(commerce.has('ATP-SPC-05'));
453
- assert.ok(account.has('ATP-HUA-ACC-01'));
454
- assert.ok(account.has('ATP-HUA-RST-03'));
455
- assert.ok(commerce.size > 10);
456
- assert.ok(account.size > 10);
445
+ const suiteEntries = await readdir(resolve(root, 'repo-specs'), { withFileTypes: true });
446
+ const suiteNames = suiteEntries
447
+ .filter(entry => entry.isDirectory() && !entry.name.startsWith('.'))
448
+ .map(entry => entry.name)
449
+ .sort();
450
+ assert.ok(suiteNames.length > 0, 'repo-specs must contain at least one suite directory');
451
+ for (const suiteName of suiteNames) {
452
+ const testPlan = await readFile(resolve(root, 'repo-specs', suiteName, 'test-plan.md'), 'utf8');
453
+ const atpIds = parseAtpIds(testPlan);
454
+ assert.ok(atpIds.size > 0, `${suiteName}/test-plan.md must define formal ATP scenarios`);
455
+ }
457
456
  });
458
457
 
459
458
  test('progress rejects duplicate rows and multiple IDs in one row with source locations', () => {