@nusoft/nuos-build-catalogue 0.33.0 → 0.33.1

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.
@@ -299,22 +299,43 @@ async function checkWorkUnitsIndex(buildRoot) {
299
299
  const content = await fileContent(indexPath);
300
300
  if (!content)
301
301
  return true; // If no index, no rows to check.
302
+ // Design A (WU 112 fix-pass): operate on table rows only; check status cell only.
303
+ // Row shape after split on '|': ['', id, title, status, dependsOn, ...]
304
+ // (leading empty string from the leading pipe character)
302
305
  const lines = content.split('\n');
303
306
  for (const line of lines) {
304
- // Look for rows with that link to a file.
305
- if (!line.includes('✅'))
307
+ // Only consider actual table rows (lines starting with '|' after optional whitespace).
308
+ if (!/^\s*\|/.test(line))
306
309
  continue;
307
- // Extract the link target from markdown link syntax [text](path).
308
- const linkMatch = line.match(/\[.*?\]\((.*?)\)/);
309
- if (!linkMatch)
310
+ const cells = line.split('|');
311
+ // Need at least 5 cells: [empty, id, title, status, dependsOn, ...] (leading/trailing empty from outer pipes)
312
+ if (cells.length < 5)
310
313
  continue;
314
+ // Status is the 3rd content cell (index 3 in the split array, after the leading empty).
315
+ const statusCell = cells[3];
316
+ if (!statusCell)
317
+ continue;
318
+ // A row is completed only if its STATUS cell contains ✅.
319
+ // This avoids false-positives from Depends-on column mentions, legend lines, and phase headers.
320
+ if (!statusCell.includes('✅'))
321
+ continue;
322
+ // Completed row: extract the first markdown link from the TITLE cell (index 2).
323
+ const titleCell = cells[2];
324
+ if (!titleCell)
325
+ continue;
326
+ const linkMatch = titleCell.match(/\[.*?\]\((.*?)\)/);
327
+ if (!linkMatch) {
328
+ // No link in the title cell — legacy/sibling WU (lives in a sibling repo, never had a done/ file here).
329
+ // Skip: not verifiable by this gate (presence-only, D130).
330
+ continue;
331
+ }
311
332
  const linkTarget = linkMatch[1];
312
- // WUs should link to done/ subdirectory.
333
+ // A completed row linking to a top-level NNN-...md (not done/) is drift: the WU was never moved.
313
334
  if (!linkTarget.includes('done/')) {
314
335
  return false;
315
336
  }
316
- // Check the linked file exists.
317
- const filePath = path.join(buildRoot, 'work-units', linkTarget.startsWith('done/') ? linkTarget : `done/${path.basename(linkTarget)}`);
337
+ // A completed row whose done/ file is missing is also drift.
338
+ const filePath = path.join(buildRoot, 'work-units', linkTarget);
318
339
  const mtime = await fileMtime(filePath);
319
340
  if (!mtime) {
320
341
  return false;
@@ -330,21 +351,26 @@ async function checkStateMd(buildRoot, sessionStartMs, sessionDate) {
330
351
  let stateMdLastUpdated = '';
331
352
  let stateMdLastSessionResolves = false;
332
353
  if (content) {
333
- // Parse "Last updated:" line.
334
- const updatedMatch = content.match(/\*\*Last updated:\*\*\s*(\d{4}-\d{2}-\d{2})/i) ||
335
- content.match(/Last updated:\s*(\d{4}-\d{2}-\d{2})/i);
354
+ // Fix 1 (WU 112 fix-pass): accept all three "Last updated" shapes:
355
+ // table-row: | Last updated | 2026-05-31 (**Session 115 — ...**) ... |
356
+ // bold-colon: **Last updated:** 2026-05-31
357
+ // plain-colon: Last updated: 2026-05-31
358
+ // Anchor on the label text (colon optional), grab the FIRST YYYY-MM-DD on the same logical line.
359
+ // The [^\n]*? keeps the match within the label's own row.
360
+ const updatedMatch = content.match(/Last updated[^\n]*?(\d{4}-\d{2}-\d{2})/i);
336
361
  if (updatedMatch) {
337
362
  stateMdLastUpdated = updatedMatch[1];
338
363
  }
339
- // Parse "Last session:" link and check the target file exists.
340
- const sessionLinkMatch = content.match(/\*\*Last session:\*\*.*?\[.*?\]\((.*?)\)/i) ||
341
- content.match(/Last session:.*?\[.*?\]\((.*?)\)/i);
342
- if (sessionLinkMatch) {
343
- const linkTarget = sessionLinkMatch[1];
344
- // Link targets are relative to docs/build/ (the buildRoot).
345
- const targetPath = path.join(buildRoot, linkTarget);
346
- const targetMtime = await fileMtime(targetPath);
347
- stateMdLastSessionResolves = targetMtime !== null;
364
+ // Fix 2 (WU 112 fix-pass): the real "Last session" row is narrative prose with NO markdown link.
365
+ // Real format: | Last session | Session 112 — ...prose... |
366
+ // Assert only that a non-empty "Last session" row/line is present (D130: do not overclaim).
367
+ // Link-resolution is dropped because the real format carries no link to resolve.
368
+ // Session-log existence on disk is independently verified by Step 7 (checkSessionLog).
369
+ const sessionLineMatch = content.match(/Last session[^\n]*/i);
370
+ if (sessionLineMatch) {
371
+ // The row is non-empty if it contains more than just the label itself.
372
+ const rowText = sessionLineMatch[0].replace(/Last session/i, '').replace(/[|:\s]/g, '');
373
+ stateMdLastSessionResolves = rowText.length > 0;
348
374
  }
349
375
  }
350
376
  return { stateMdTouched, stateMdLastUpdated, stateMdLastSessionResolves };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nusoft/nuos-build-catalogue",
3
- "version": "0.33.0",
3
+ "version": "0.33.1",
4
4
  "description": "NuOS build-catalogue tooling: semantic search (WU 110) + migration runner that lifts markdown artefacts into JSON-backed workflow records (WU 111, Phase G).",
5
5
  "type": "module",
6
6
  "bin": {