dotmd-cli 0.36.3 → 0.37.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/bin/dotmd.mjs +32 -6
- package/package.json +1 -1
- package/src/check-collapse.mjs +75 -0
- package/src/doctor.mjs +6 -1
- package/src/render.mjs +14 -3
package/bin/dotmd.mjs
CHANGED
|
@@ -41,7 +41,7 @@ Analyze:
|
|
|
41
41
|
|
|
42
42
|
Validate & Fix:
|
|
43
43
|
check [--fix] [--errors-only] [--json] Validate frontmatter and references
|
|
44
|
-
doctor [--
|
|
44
|
+
doctor [--apply] Auto-fix everything: refs, lint, dates, index (preview by default)
|
|
45
45
|
lint [--fix] Check and auto-fix frontmatter issues
|
|
46
46
|
fix-refs [--dry-run] Auto-fix broken reference paths + body links
|
|
47
47
|
|
|
@@ -204,6 +204,11 @@ Options:
|
|
|
204
204
|
--errors-only Show only errors, suppress warnings
|
|
205
205
|
--fix Auto-fix broken refs, lint issues, and regenerate index
|
|
206
206
|
--json Output errors and warnings as JSON
|
|
207
|
+
--no-collapse Show every warning per-doc (since 0.37.0, high-frequency
|
|
208
|
+
auto-fixable warning categories — singular module/surface
|
|
209
|
+
deprecations, updated-behind-git — are collapsed into a
|
|
210
|
+
one-line summary with the bulk-fix command). --json output
|
|
211
|
+
is unchanged regardless.
|
|
207
212
|
--dry-run, -n Preview fixes without writing (with --fix)`,
|
|
208
213
|
|
|
209
214
|
archive: `dotmd archive <file> — archive a document
|
|
@@ -342,7 +347,10 @@ Runs in sequence: fix broken references, lint --fix, sync dates from
|
|
|
342
347
|
git, regenerate index, then show remaining issues.
|
|
343
348
|
|
|
344
349
|
Modes:
|
|
345
|
-
(default) Auto-fix pass
|
|
350
|
+
(default) Auto-fix pass — previews by default since 0.37.0
|
|
351
|
+
(F4). Use --apply (alias --yes) to actually write;
|
|
352
|
+
explicit --dry-run still wins over --apply if both
|
|
353
|
+
are passed (safety prevails).
|
|
346
354
|
--statuses Read-only diagnostic: detect overloaded status
|
|
347
355
|
buckets where one status holds plans pursuing
|
|
348
356
|
multiple distinct unstuck-actions. Suggests how
|
|
@@ -374,7 +382,9 @@ Modes:
|
|
|
374
382
|
in their Version History would be misleading).
|
|
375
383
|
--migrate-template --json Machine-readable result.
|
|
376
384
|
|
|
377
|
-
|
|
385
|
+
--apply (or --yes) opts into writes for the default auto-fix pass.
|
|
386
|
+
Sub-modes (--statuses, --migrate-*) keep their existing contracts:
|
|
387
|
+
they write by default and honor --dry-run.`,
|
|
378
388
|
|
|
379
389
|
'fix-refs': `dotmd fix-refs — auto-fix broken reference paths
|
|
380
390
|
|
|
@@ -871,7 +881,22 @@ async function main() {
|
|
|
871
881
|
if (command === 'rename') { const { runRename } = await import('../src/rename.mjs'); await runRename(restArgs, config, { dryRun }); return; }
|
|
872
882
|
if (command === 'migrate') { const { runMigrate } = await import('../src/migrate.mjs'); runMigrate(restArgs, config, { dryRun }); return; }
|
|
873
883
|
if (command === 'fix-refs') { const { runFixRefs } = await import('../src/fix-refs.mjs'); runFixRefs(restArgs, config, { dryRun }); return; }
|
|
874
|
-
if (command === 'doctor') {
|
|
884
|
+
if (command === 'doctor') {
|
|
885
|
+
// 0.37.0 (F4): the default auto-fix loop previews by default; --apply
|
|
886
|
+
// (alias --yes) writes. Explicit --dry-run still works and wins over
|
|
887
|
+
// --apply (safety prevails). The F4 flip applies ONLY to the default
|
|
888
|
+
// auto-fix path — sub-modes (--statuses, --migrate-template,
|
|
889
|
+
// --migrate-prompts) keep their existing "write unless --dry-run"
|
|
890
|
+
// contract because they're explicit one-shots the user opted into.
|
|
891
|
+
const subMode = args.includes('--statuses') || args.includes('--migrate-template') || args.includes('--migrate-prompts');
|
|
892
|
+
const explicitApply = args.includes('--apply') || args.includes('--yes');
|
|
893
|
+
const explicitDryRun = args.includes('--dry-run') || args.includes('-n');
|
|
894
|
+
const doctorDryRun = subMode ? dryRun : (explicitDryRun || !explicitApply);
|
|
895
|
+
const filtered = restArgs.filter(a => a !== '--apply' && a !== '--yes');
|
|
896
|
+
const { runDoctor } = await import('../src/doctor.mjs');
|
|
897
|
+
runDoctor(filtered, config, { dryRun: doctorDryRun });
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
875
900
|
if (command === 'statuses') { const { runStatuses } = await import('../src/statuses.mjs'); await runStatuses(restArgs, config, { dryRun, type: typeArg }); return; }
|
|
876
901
|
|
|
877
902
|
// All remaining commands need the index + render modules
|
|
@@ -931,6 +956,7 @@ async function main() {
|
|
|
931
956
|
if (command === 'check') {
|
|
932
957
|
const fix = args.includes('--fix');
|
|
933
958
|
const errorsOnly = args.includes('--errors-only');
|
|
959
|
+
const noCollapse = args.includes('--no-collapse');
|
|
934
960
|
|
|
935
961
|
if (fix) {
|
|
936
962
|
// Auto-fix: broken refs, then lint, then rebuild index
|
|
@@ -961,7 +987,7 @@ async function main() {
|
|
|
961
987
|
passed: freshIndex.errors.length === 0,
|
|
962
988
|
}, null, 2) + '\n');
|
|
963
989
|
} else {
|
|
964
|
-
process.stdout.write('\n' + renderCheck(freshIndex, config, { errorsOnly }));
|
|
990
|
+
process.stdout.write('\n' + renderCheck(freshIndex, config, { errorsOnly, noCollapse }));
|
|
965
991
|
}
|
|
966
992
|
if (freshIndex.errors.length > 0) process.exitCode = 1;
|
|
967
993
|
return;
|
|
@@ -980,7 +1006,7 @@ async function main() {
|
|
|
980
1006
|
return;
|
|
981
1007
|
}
|
|
982
1008
|
|
|
983
|
-
process.stdout.write(renderCheck(index, config, { errorsOnly }));
|
|
1009
|
+
process.stdout.write(renderCheck(index, config, { errorsOnly, noCollapse }));
|
|
984
1010
|
if (index.errors.length > 0) process.exitCode = 1;
|
|
985
1011
|
return;
|
|
986
1012
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// F13 (0.37.0): collapse high-frequency auto-fixable `dotmd check` warnings
|
|
2
|
+
// into one-line remediation hints. Without this, bulk-fixable noise (43
|
|
3
|
+
// `updated behind git history`, N singular-key deprecations) drowns out
|
|
4
|
+
// structural findings in the per-doc list — and forces the reader to
|
|
5
|
+
// reconstruct the fix command per category.
|
|
6
|
+
//
|
|
7
|
+
// Each category names the message regex to match, a short label for the
|
|
8
|
+
// summary line, and the exact `dotmd …` command that bulk-fixes it.
|
|
9
|
+
|
|
10
|
+
const COLLAPSE_THRESHOLD = 3;
|
|
11
|
+
|
|
12
|
+
const CATEGORIES = [
|
|
13
|
+
{
|
|
14
|
+
key: 'updated-behind-git',
|
|
15
|
+
match: /^frontmatter `updated:.*` is behind git history/,
|
|
16
|
+
label: 'docs have `updated` behind git history',
|
|
17
|
+
fix: 'dotmd touch --git',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
key: 'singular-module',
|
|
21
|
+
match: /^`module:` \(singular\) is deprecated/,
|
|
22
|
+
label: 'docs use deprecated singular `module:`',
|
|
23
|
+
fix: 'dotmd lint --fix',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
key: 'singular-surface',
|
|
27
|
+
match: /^`surface:` \(singular\) is deprecated/,
|
|
28
|
+
label: 'docs use deprecated singular `surface:`',
|
|
29
|
+
fix: 'dotmd lint --fix',
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function categoryFor(message) {
|
|
34
|
+
for (const cat of CATEGORIES) {
|
|
35
|
+
if (cat.match.test(message)) return cat;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Split warnings into per-doc passthrough lines and collapsed summary buckets.
|
|
41
|
+
// A category that hits ≥COLLAPSE_THRESHOLD warnings is summarized; below the
|
|
42
|
+
// threshold each warning falls through as a normal per-doc line (small counts
|
|
43
|
+
// don't gain from collapse and lose path information).
|
|
44
|
+
export function categorizeWarnings(warnings) {
|
|
45
|
+
const buckets = new Map();
|
|
46
|
+
const orphans = [];
|
|
47
|
+
|
|
48
|
+
for (const w of warnings) {
|
|
49
|
+
const cat = categoryFor(w.message);
|
|
50
|
+
if (!cat) {
|
|
51
|
+
orphans.push(w);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (!buckets.has(cat.key)) buckets.set(cat.key, { cat, items: [] });
|
|
55
|
+
buckets.get(cat.key).items.push(w);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const collapsed = [];
|
|
59
|
+
const passthrough = [...orphans];
|
|
60
|
+
|
|
61
|
+
for (const { cat, items } of buckets.values()) {
|
|
62
|
+
if (items.length >= COLLAPSE_THRESHOLD) {
|
|
63
|
+
collapsed.push({ key: cat.key, label: cat.label, fix: cat.fix, count: items.length });
|
|
64
|
+
} else {
|
|
65
|
+
passthrough.push(...items);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
passthrough.sort((a, b) => a.path.localeCompare(b.path));
|
|
70
|
+
collapsed.sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
|
|
71
|
+
|
|
72
|
+
return { passthrough, collapsed };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const _internalForTest = { COLLAPSE_THRESHOLD, CATEGORIES };
|
package/src/doctor.mjs
CHANGED
|
@@ -53,7 +53,12 @@ export function runDoctor(argv, config, opts = {}) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const { dryRun } = opts;
|
|
56
|
-
|
|
56
|
+
// 0.37.0 (F4): the mode banner makes it impossible to mistake a preview run
|
|
57
|
+
// for a real one — and tells the user the exact flag that flips it.
|
|
58
|
+
const modeNote = dryRun
|
|
59
|
+
? dim('[preview — run with --apply to write]')
|
|
60
|
+
: dim('[applying changes]');
|
|
61
|
+
process.stdout.write(bold('dotmd doctor') + ' ' + modeNote + '\n\n');
|
|
57
62
|
|
|
58
63
|
// Step 1: Fix broken references
|
|
59
64
|
process.stdout.write(bold('1. Fixing broken references...') + '\n');
|
package/src/render.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { extractFrontmatter } from './frontmatter.mjs';
|
|
|
5
5
|
import { summarizeDocBody } from './ai.mjs';
|
|
6
6
|
import { bold, red, yellow, green, dim } from './color.mjs';
|
|
7
7
|
import { findStaleLeases } from './lease.mjs';
|
|
8
|
+
import { categorizeWarnings } from './check-collapse.mjs';
|
|
8
9
|
|
|
9
10
|
// Render `currentState` with an `(auto)` prefix when the value was body-scraped
|
|
10
11
|
// rather than read from frontmatter. Lets a reader see at a glance which docs
|
|
@@ -376,7 +377,7 @@ export function renderCheck(index, config, opts = {}) {
|
|
|
376
377
|
}
|
|
377
378
|
|
|
378
379
|
function _renderCheck(index, opts = {}) {
|
|
379
|
-
const { errorsOnly } = opts;
|
|
380
|
+
const { errorsOnly, noCollapse } = opts;
|
|
380
381
|
const lines = ['Check', ''];
|
|
381
382
|
lines.push(`- docs scanned: ${index.docs.length}`);
|
|
382
383
|
lines.push(`- errors: ${index.errors.length}`);
|
|
@@ -393,8 +394,18 @@ function _renderCheck(index, opts = {}) {
|
|
|
393
394
|
|
|
394
395
|
if (!errorsOnly && index.warnings.length > 0) {
|
|
395
396
|
lines.push(yellow('Warnings'));
|
|
396
|
-
|
|
397
|
-
|
|
397
|
+
if (noCollapse) {
|
|
398
|
+
for (const issue of index.warnings) {
|
|
399
|
+
lines.push(`- ${issue.path}: ${issue.message}`);
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
const { passthrough, collapsed } = categorizeWarnings(index.warnings);
|
|
403
|
+
for (const issue of passthrough) {
|
|
404
|
+
lines.push(`- ${issue.path}: ${issue.message}`);
|
|
405
|
+
}
|
|
406
|
+
for (const sum of collapsed) {
|
|
407
|
+
lines.push(`- ${sum.count} ${sum.label} — run \`${sum.fix}\` to bulk-fix`);
|
|
408
|
+
}
|
|
398
409
|
}
|
|
399
410
|
lines.push('');
|
|
400
411
|
}
|