dotmd-cli 0.40.0 → 0.40.2

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 CHANGED
@@ -335,15 +335,19 @@ Use --dry-run (-n) to preview changes without writing anything.`,
335
335
 
336
336
  check: `dotmd check — validate frontmatter and references
337
337
 
338
+ By default the warning list is suppressed: you see counts plus a one-line
339
+ pointer to \`dotmd doctor\` (auto-fix) or \`dotmd check --verbose\`
340
+ (per-doc detail). Errors are always shown in full.
341
+
338
342
  Options:
339
- --errors-only Show only errors, suppress warnings
343
+ --verbose Show every warning per-doc (with category collapse
344
+ applied — high-frequency auto-fixable categories
345
+ summarize to a one-line bulk-fix hint).
346
+ --no-collapse Like --verbose but disables category collapse too —
347
+ every warning prints raw.
348
+ --errors-only Show only errors, suppress warnings entirely
340
349
  --fix Auto-fix broken refs, lint issues, and regenerate index
341
- --json Output errors and warnings as JSON
342
- --no-collapse Show every warning per-doc (since 0.37.0, high-frequency
343
- auto-fixable warning categories — singular module/surface
344
- deprecations, updated-behind-git — are collapsed into a
345
- one-line summary with the bulk-fix command). --json output
346
- is unchanged regardless.
350
+ --json Output errors and warnings as JSON (always full detail)
347
351
  --dry-run, -n Preview fixes without writing (with --fix)`,
348
352
 
349
353
  archive: `dotmd archive <file> — archive a document
@@ -1219,6 +1223,7 @@ async function main() {
1219
1223
  const fix = args.includes('--fix');
1220
1224
  const errorsOnly = args.includes('--errors-only');
1221
1225
  const noCollapse = args.includes('--no-collapse');
1226
+ const verbose = args.includes('--verbose');
1222
1227
 
1223
1228
  if (fix) {
1224
1229
  // Auto-fix: broken refs, then lint, then rebuild index
@@ -1249,7 +1254,7 @@ async function main() {
1249
1254
  passed: freshIndex.errors.length === 0,
1250
1255
  }, null, 2) + '\n');
1251
1256
  } else {
1252
- process.stdout.write('\n' + renderCheck(freshIndex, config, { errorsOnly, noCollapse }));
1257
+ process.stdout.write('\n' + renderCheck(freshIndex, config, { errorsOnly, noCollapse, verbose }));
1253
1258
  }
1254
1259
  if (freshIndex.errors.length > 0) process.exitCode = 1;
1255
1260
  return;
@@ -1268,7 +1273,7 @@ async function main() {
1268
1273
  return;
1269
1274
  }
1270
1275
 
1271
- process.stdout.write(renderCheck(index, config, { errorsOnly, noCollapse }));
1276
+ process.stdout.write(renderCheck(index, config, { errorsOnly, noCollapse, verbose }));
1272
1277
  if (index.errors.length > 0) process.exitCode = 1;
1273
1278
  return;
1274
1279
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotmd-cli",
3
- "version": "0.40.0",
3
+ "version": "0.40.2",
4
4
  "description": "CLI for managing markdown documents with YAML frontmatter — index, query, validate, graph, export, Notion sync, AI summaries.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/lifecycle.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
- import { extractFrontmatter, parseSimpleFrontmatter, replaceFrontmatter } from './frontmatter.mjs';
3
+ import { extractFrontmatter, parseSimpleFrontmatter } from './frontmatter.mjs';
4
4
  import { asString, toRepoPath, die, warn, resolveDocPath, resolveRefPath, escapeRegex, nowIso, suggestCandidates, emitFilesFooter } from './util.mjs';
5
5
  import { gitMv, getGitLastModified, getGitLastModifiedBatch } from './git.mjs';
6
6
  import { buildIndex, collectDocFiles } from './index.mjs';
@@ -829,30 +829,40 @@ function updateRefsAfterMove(oldPath, newPath, config) {
829
829
 
830
830
  for (const docFile of allFiles) {
831
831
  if (docFile === newPath) continue;
832
- let raw = readFileSync(docFile, 'utf8');
833
- const { frontmatter: fm } = extractFrontmatter(raw);
834
- if (!fm || !fm.includes(basename)) continue;
832
+ const raw = readFileSync(docFile, 'utf8');
833
+ if (!raw.includes(basename)) continue;
834
+ const { frontmatter: fm, body } = extractFrontmatter(raw);
835
+ if (!fm) continue;
835
836
 
836
837
  const docDir = path.dirname(docFile);
837
838
  const oldRelPath = path.relative(docDir, oldPath).split(path.sep).join('/');
838
839
  const newRelPath = path.relative(docDir, newPath).split(path.sep).join('/');
839
840
 
840
841
  let newFm = fm;
841
-
842
- // Replace exact relative path
843
842
  if (newFm.includes(oldRelPath)) {
844
843
  newFm = newFm.split(oldRelPath).join(newRelPath);
845
844
  }
846
-
847
- // Also handle ./ prefix variant
848
845
  const dotSlashOld = './' + oldRelPath;
849
846
  if (newFm.includes(dotSlashOld)) {
850
847
  newFm = newFm.split(dotSlashOld).join(newRelPath);
851
848
  }
852
849
 
853
- if (newFm !== fm) {
854
- raw = replaceFrontmatter(raw, newFm);
855
- writeFileSync(docFile, raw, 'utf8');
850
+ // Body markdown links [text](path.md) or [text](path.md#anchor) pointing
851
+ // at oldPath. resolveRefPath can't be used here: oldPath no longer exists
852
+ // on disk (git mv already ran), so its existsSync probe would fail. Match
853
+ // by resolving the href manually and comparing absolute paths instead.
854
+ const linkRegex = /(\[[^\]]*\]\()([^)#]+\.md)(#[^)]*)?(\))/g;
855
+ const newBody = body.replace(linkRegex, (match, pre, href, frag, post) => {
856
+ if (/^https?:/i.test(href)) return match;
857
+ const docRelAbs = path.resolve(docDir, href);
858
+ const repoRelAbs = path.resolve(config.repoRoot, href);
859
+ if (docRelAbs !== oldPath && repoRelAbs !== oldPath) return match;
860
+ const newHref = path.relative(docDir, newPath).split(path.sep).join('/');
861
+ return `${pre}${newHref}${frag ?? ''}${post}`;
862
+ });
863
+
864
+ if (newFm !== fm || newBody !== body) {
865
+ writeFileSync(docFile, `---\n${newFm}\n---\n${newBody}`, 'utf8');
856
866
  touched.push(docFile);
857
867
  }
858
868
  }
@@ -895,10 +905,7 @@ function updateRefsFromMovedFile(oldPath, newPath, config) {
895
905
  });
896
906
 
897
907
  if (newFm !== frontmatter || newBody !== body) {
898
- const rebuilt = replaceFrontmatter(raw, newFm);
899
- // Replace body: rebuilt has updated frontmatter but old body
900
- const { frontmatter: updatedFm } = extractFrontmatter(rebuilt);
901
- writeFileSync(newPath, `---\n${updatedFm}\n---${newBody}`, 'utf8');
908
+ writeFileSync(newPath, `---\n${newFm}\n---\n${newBody}`, 'utf8');
902
909
  return 1;
903
910
  }
904
911
 
@@ -913,14 +920,27 @@ function countRefsToUpdate(oldPath, newPath, config) {
913
920
  for (const docFile of allFiles) {
914
921
  if (docFile === newPath) continue;
915
922
  const raw = readFileSync(docFile, 'utf8');
916
- const { frontmatter: fm } = extractFrontmatter(raw);
917
- if (!fm || !fm.includes(basename)) continue;
923
+ if (!raw.includes(basename)) continue;
924
+ const { frontmatter: fm, body } = extractFrontmatter(raw);
925
+ if (!fm) continue;
918
926
 
919
927
  const docDir = path.dirname(docFile);
920
928
  const oldRelPath = path.relative(docDir, oldPath).split(path.sep).join('/');
921
- if (fm.includes(oldRelPath) || fm.includes('./' + oldRelPath)) {
922
- count++;
929
+ const fmHit = fm.includes(oldRelPath) || fm.includes('./' + oldRelPath);
930
+
931
+ let bodyHit = false;
932
+ if (!fmHit) {
933
+ const linkRegex = /\[[^\]]*\]\(([^)#]+\.md)(?:#[^)]*)?\)/g;
934
+ for (const match of body.matchAll(linkRegex)) {
935
+ const href = match[1];
936
+ if (/^https?:/i.test(href)) continue;
937
+ const docRelAbs = path.resolve(docDir, href);
938
+ const repoRelAbs = path.resolve(config.repoRoot, href);
939
+ if (docRelAbs === oldPath || repoRelAbs === oldPath) { bodyHit = true; break; }
940
+ }
923
941
  }
942
+
943
+ if (fmHit || bodyHit) count++;
924
944
  }
925
945
 
926
946
  return count;
package/src/render.mjs CHANGED
@@ -377,7 +377,7 @@ export function renderCheck(index, config, opts = {}) {
377
377
  }
378
378
 
379
379
  function _renderCheck(index, opts = {}) {
380
- const { errorsOnly, noCollapse } = opts;
380
+ const { errorsOnly, noCollapse, verbose } = opts;
381
381
  const lines = ['Check', ''];
382
382
  lines.push(`- docs scanned: ${index.docs.length}`);
383
383
  lines.push(`- errors: ${index.errors.length}`);
@@ -392,22 +392,32 @@ function _renderCheck(index, opts = {}) {
392
392
  lines.push('');
393
393
  }
394
394
 
395
+ // Warnings: terse by default — print count + pointer. The full per-doc list
396
+ // grew long enough on real projects that it drowned the summary; agents now
397
+ // see warnings as a single line and opt in to detail when they need it.
398
+ // --verbose / --no-collapse expand to the full list (with collapse-by-category
399
+ // still applied unless --no-collapse is set).
395
400
  if (!errorsOnly && index.warnings.length > 0) {
396
- lines.push(yellow('Warnings'));
397
- if (noCollapse) {
398
- for (const issue of index.warnings) {
399
- lines.push(`- ${issue.path}: ${issue.message}`);
401
+ if (verbose || noCollapse) {
402
+ lines.push(yellow('Warnings'));
403
+ if (noCollapse) {
404
+ for (const issue of index.warnings) {
405
+ lines.push(`- ${issue.path}: ${issue.message}`);
406
+ }
407
+ } else {
408
+ const { passthrough, collapsed } = categorizeWarnings(index.warnings);
409
+ for (const issue of passthrough) {
410
+ lines.push(`- ${issue.path}: ${issue.message}`);
411
+ }
412
+ for (const sum of collapsed) {
413
+ lines.push(`- ${sum.count} ${sum.label} — run \`${sum.fix}\` to bulk-fix`);
414
+ }
400
415
  }
416
+ lines.push('');
401
417
  } 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
- }
418
+ lines.push(dim(`Run \`dotmd check --verbose\` for per-doc detail, or \`dotmd doctor\` to auto-fix where possible.`));
419
+ lines.push('');
409
420
  }
410
- lines.push('');
411
421
  }
412
422
 
413
423
  if (index.errors.length === 0 && index.warnings.length === 0) {