dotmd-cli 0.76.3 → 0.76.5
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/bin/dotmd.mjs +2 -1
- package/package.json +1 -1
- package/src/config.mjs +18 -2
- package/src/index.mjs +2 -1
- package/src/render.mjs +16 -2
- package/src/status-metadata.mjs +1 -1
- package/src/validate.mjs +2 -2
package/bin/dotmd.mjs
CHANGED
|
@@ -1831,7 +1831,7 @@ async function main() {
|
|
|
1831
1831
|
|
|
1832
1832
|
// All remaining commands need the index + render modules
|
|
1833
1833
|
const { buildIndex } = await import('../src/index.mjs');
|
|
1834
|
-
const { renderCompactList, renderVerboseList, renderContext, renderBriefing, renderCheck, renderCoverage, buildCoverage } = await import('../src/render.mjs');
|
|
1834
|
+
const { renderCompactList, renderVerboseList, renderContext, renderBriefing, renderCheck, renderCoverage, buildCoverage, buildReferenceValidationCoverage } = await import('../src/render.mjs');
|
|
1835
1835
|
const { runFocus, runQuery } = await import('../src/query.mjs');
|
|
1836
1836
|
// `dotmd check` is the one shared-buildIndex command that should auto-heal a
|
|
1837
1837
|
// drifted index block (frontmatter edits by direct Edit/Write, `lint --fix`,
|
|
@@ -1898,6 +1898,7 @@ async function main() {
|
|
|
1898
1898
|
warnings: errorsOnly ? [] : checkIndex.warnings,
|
|
1899
1899
|
errorCount: checkIndex.errors.length,
|
|
1900
1900
|
warningCount: checkIndex.warnings.length,
|
|
1901
|
+
referenceValidation: buildReferenceValidationCoverage(checkIndex, config),
|
|
1901
1902
|
passed: complete ? builtInPassed : null,
|
|
1902
1903
|
...(complete ? {} : {
|
|
1903
1904
|
builtInPassed,
|
package/package.json
CHANGED
package/src/config.mjs
CHANGED
|
@@ -154,6 +154,7 @@ function normalizeRichStatuses(config, userConfig) {
|
|
|
154
154
|
// reads them has to be too.
|
|
155
155
|
skipWarningsForByType: {},
|
|
156
156
|
terminalStatuses: [],
|
|
157
|
+
terminalStatusesByType: {},
|
|
157
158
|
moduleRequiredFor: [],
|
|
158
159
|
// F15: status-name → directory-name (defaults to the status name verbatim).
|
|
159
160
|
// Filed statuses move docs into <root>/<dirName>/ on transition INTO the
|
|
@@ -177,6 +178,7 @@ function normalizeRichStatuses(config, userConfig) {
|
|
|
177
178
|
// declared its statuses and suppresses nothing", which must not fall back to
|
|
178
179
|
// the global union. Absent entirely means "type never declared rich statuses".
|
|
179
180
|
derived.skipWarningsForByType[typeName] ??= [];
|
|
181
|
+
derived.terminalStatusesByType[typeName] ??= [];
|
|
180
182
|
const statusNames = [];
|
|
181
183
|
const typeContext = { expanded: [], listed: [], counted: [] };
|
|
182
184
|
const typeStaleDays = {};
|
|
@@ -218,7 +220,10 @@ function normalizeRichStatuses(config, userConfig) {
|
|
|
218
220
|
if (!derived.skipWarningsFor.includes(name)) derived.skipWarningsFor.push(name);
|
|
219
221
|
(derived.skipWarningsForByType[typeName] ??= []).push(name);
|
|
220
222
|
}
|
|
221
|
-
if (p.terminal
|
|
223
|
+
if (p.terminal) {
|
|
224
|
+
if (!derived.terminalStatuses.includes(name)) derived.terminalStatuses.push(name);
|
|
225
|
+
derived.terminalStatusesByType[typeName].push(name);
|
|
226
|
+
}
|
|
222
227
|
if (p.requiresModule && !derived.moduleRequiredFor.includes(name)) derived.moduleRequiredFor.push(name);
|
|
223
228
|
if (p.filed && !derived.filedStatuses[name]) {
|
|
224
229
|
// dirName defaults to the status name; users can override with
|
|
@@ -281,6 +286,10 @@ function applyDerivedConfig(config, userConfig, derived) {
|
|
|
281
286
|
}
|
|
282
287
|
if (!userConfig.lifecycle?.terminalStatuses && derived.terminalStatuses.length) {
|
|
283
288
|
config.lifecycle.terminalStatuses = derived.terminalStatuses;
|
|
289
|
+
config.lifecycle.terminalStatusesByType = derived.terminalStatusesByType;
|
|
290
|
+
} else {
|
|
291
|
+
// An explicit lifecycle list is intentionally repo-wide.
|
|
292
|
+
config.lifecycle.terminalStatusesByType = {};
|
|
284
293
|
}
|
|
285
294
|
if (!userConfig.lifecycle?.filedStatuses && Object.keys(derived.filedStatuses).length) {
|
|
286
295
|
config.lifecycle.filedStatuses = derived.filedStatuses;
|
|
@@ -539,6 +548,13 @@ export async function resolveConfig(cwd, explicitConfigPath) {
|
|
|
539
548
|
return own ? own.has(status) : skipWarningsFor.has(status);
|
|
540
549
|
};
|
|
541
550
|
const terminalStatuses = new Set(lifecycle.terminalStatuses);
|
|
551
|
+
const terminalStatusesByType = new Map(
|
|
552
|
+
Object.entries(lifecycle.terminalStatusesByType ?? {}).map(([type, names]) => [type, new Set(names)]),
|
|
553
|
+
);
|
|
554
|
+
const isTerminal = (status, type) => {
|
|
555
|
+
const own = type != null ? terminalStatusesByType.get(type) : undefined;
|
|
556
|
+
return own ? own.has(status) : terminalStatuses.has(status);
|
|
557
|
+
};
|
|
542
558
|
// F15: filedStatuses keyed by status name, value = directory name. Empty
|
|
543
559
|
// object when no status opts in via `filed: true` (or `filed: '<dirname>'`).
|
|
544
560
|
const filedStatuses = new Map(Object.entries(lifecycle.filedStatuses ?? {}));
|
|
@@ -589,7 +605,7 @@ export async function resolveConfig(cwd, explicitConfigPath) {
|
|
|
589
605
|
rootValidStatuses,
|
|
590
606
|
staleDaysByStatus,
|
|
591
607
|
|
|
592
|
-
lifecycle: { archiveStatuses, startableStatuses, skipStaleFor, skipWarningsFor, skipWarningsForByType, skipsWarnings, terminalStatuses, filedStatuses, archiveNestedTypes },
|
|
608
|
+
lifecycle: { archiveStatuses, startableStatuses, skipStaleFor, skipWarningsFor, skipWarningsForByType, skipsWarnings, terminalStatuses, terminalStatusesByType, isTerminal, filedStatuses, archiveNestedTypes },
|
|
593
609
|
|
|
594
610
|
validSurfaces,
|
|
595
611
|
validModules,
|
package/src/index.mjs
CHANGED
|
@@ -290,7 +290,8 @@ export function parseDocFile(filePath, config, opts = {}) {
|
|
|
290
290
|
// criterion: "should defer to frontmatter when status is terminal."
|
|
291
291
|
const fmCurrentState = asString(parsedFrontmatter.current_state);
|
|
292
292
|
const docStatus = asString(parsedFrontmatter.status);
|
|
293
|
-
const isTerminalDoc = docStatus && config.lifecycle?.
|
|
293
|
+
const isTerminalDoc = docStatus && (config.lifecycle?.isTerminal?.(docStatus, asString(parsedFrontmatter.type) ?? null)
|
|
294
|
+
?? config.lifecycle?.terminalStatuses?.has?.(docStatus));
|
|
294
295
|
// Track where currentState came from so renderers can prefix `(auto)` on
|
|
295
296
|
// body-scraped values. Frontmatter wins silently; body-scraped values flag
|
|
296
297
|
// their origin so the user knows the string was inferred (and that adding
|
package/src/render.mjs
CHANGED
|
@@ -404,7 +404,7 @@ export function renderBriefing(index, config) {
|
|
|
404
404
|
}
|
|
405
405
|
|
|
406
406
|
export function renderCheck(index, config, opts = {}) {
|
|
407
|
-
const defaultRenderer = (idx) => _renderCheck(idx, opts);
|
|
407
|
+
const defaultRenderer = (idx) => _renderCheck(idx, config, opts);
|
|
408
408
|
if (!config._execution?.suppressSideEffects && config.hooks.renderCheck) {
|
|
409
409
|
try { return config.hooks.renderCheck(index, defaultRenderer); }
|
|
410
410
|
catch (err) { warn(`Hook 'renderCheck' threw: ${err.message}`); }
|
|
@@ -500,12 +500,26 @@ export function renderManualFixes(index) {
|
|
|
500
500
|
return lines.join('\n');
|
|
501
501
|
}
|
|
502
502
|
|
|
503
|
-
function
|
|
503
|
+
export function buildReferenceValidationCoverage(index, config) {
|
|
504
|
+
let checkedDocs = 0;
|
|
505
|
+
let terminalDocsSkipped = 0;
|
|
506
|
+
for (const doc of index.docs) {
|
|
507
|
+
const terminal = config.lifecycle.isTerminal?.(doc.status, doc.type)
|
|
508
|
+
?? config.lifecycle.terminalStatuses.has(doc.status);
|
|
509
|
+
if (terminal) terminalDocsSkipped++;
|
|
510
|
+
else checkedDocs++;
|
|
511
|
+
}
|
|
512
|
+
return { checkedDocs, terminalDocsSkipped };
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function _renderCheck(index, config, opts = {}) {
|
|
504
516
|
const { errorsOnly, noCollapse, verbose } = opts;
|
|
517
|
+
const referenceValidation = buildReferenceValidationCoverage(index, config);
|
|
505
518
|
const lines = ['Check', ''];
|
|
506
519
|
lines.push(`- docs scanned: ${index.docs.length}`);
|
|
507
520
|
lines.push(`- errors: ${index.errors.length}`);
|
|
508
521
|
lines.push(`- warnings: ${index.warnings.length}`);
|
|
522
|
+
lines.push(`- reference validation: ${referenceValidation.checkedDocs} docs checked; ${referenceValidation.terminalDocsSkipped} terminal docs skipped`);
|
|
509
523
|
lines.push('');
|
|
510
524
|
|
|
511
525
|
if (index.errors.length > 0) {
|
package/src/status-metadata.mjs
CHANGED
|
@@ -30,7 +30,7 @@ export function resolveStatusMetadata(config) {
|
|
|
30
30
|
context: contextByStatus.get(name) ?? 'counted',
|
|
31
31
|
staleDays: hasTypeStaleDays ? typeDef.staleDays[name] : (config.staleDaysByStatus?.[name] ?? null),
|
|
32
32
|
startable: config.lifecycle.startableStatuses.has(name),
|
|
33
|
-
terminal: config.lifecycle.terminalStatuses.has(name),
|
|
33
|
+
terminal: config.lifecycle.isTerminal?.(name, type) ?? config.lifecycle.terminalStatuses.has(name),
|
|
34
34
|
archive: config.lifecycle.archiveStatuses.has(name),
|
|
35
35
|
filed: config.lifecycle.filedStatuses.get(name) ?? null,
|
|
36
36
|
skipStale,
|
package/src/validate.mjs
CHANGED
|
@@ -259,8 +259,8 @@ export function validateDoc(doc, frontmatter, headingTitle, config) {
|
|
|
259
259
|
// exit code with a hard error.
|
|
260
260
|
const docDir = path.dirname(path.join(config.repoRoot, doc.path));
|
|
261
261
|
const allRefFields = [...(config.referenceFields.bidirectional || []), ...(config.referenceFields.unidirectional || [])];
|
|
262
|
-
const skipRefValidation = config.lifecycle.
|
|
263
|
-
|
|
262
|
+
const skipRefValidation = config.lifecycle.isTerminal?.(doc.status, doc.type)
|
|
263
|
+
?? config.lifecycle.terminalStatuses.has(doc.status);
|
|
264
264
|
if (!skipRefValidation) {
|
|
265
265
|
for (const field of allRefFields) {
|
|
266
266
|
for (const relPath of (doc.refFields[field] || [])) {
|