dotmd-cli 0.13.0 → 0.13.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.
package/bin/dotmd.mjs CHANGED
@@ -4,29 +4,6 @@ import { readFileSync } from 'node:fs';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import path from 'node:path';
6
6
  import { resolveConfig } from '../src/config.mjs';
7
- import { buildIndex } from '../src/index.mjs';
8
- import { renderCompactList, renderVerboseList, renderContext, renderBriefing, renderCheck, renderCoverage, buildCoverage } from '../src/render.mjs';
9
- import { renderIndexFile, writeIndex } from '../src/index-file.mjs';
10
- import { runFocus, runQuery } from '../src/query.mjs';
11
- import { runStatus, runArchive, runTouch, runBulkArchive, runPickup, runFinish } from '../src/lifecycle.mjs';
12
- import { runInit } from '../src/init.mjs';
13
- import { runNew } from '../src/new.mjs';
14
- import { runCompletions } from '../src/completions.mjs';
15
- import { runWatch } from '../src/watch.mjs';
16
- import { runDiff } from '../src/diff.mjs';
17
- import { runLint } from '../src/lint.mjs';
18
- import { runRename } from '../src/rename.mjs';
19
- import { runMigrate } from '../src/migrate.mjs';
20
- import { runFixRefs, fixBrokenRefs } from '../src/fix-refs.mjs';
21
- import { buildGraph, renderGraphText, renderGraphDot, renderGraphJson } from '../src/graph.mjs';
22
- import { runDoctor } from '../src/doctor.mjs';
23
- import { buildStats, renderStats, renderStatsJson } from '../src/stats.mjs';
24
- import { runSummary } from '../src/summary.mjs';
25
- import { runDeps, runUnblocks } from '../src/deps.mjs';
26
- import { runHealth } from '../src/health.mjs';
27
- import { runGlossary } from '../src/glossary.mjs';
28
- import { runExport } from '../src/export.mjs';
29
- import { runNotion } from '../src/notion.mjs';
30
7
  import { die, warn, levenshtein } from '../src/util.mjs';
31
8
 
32
9
  const __filename = fileURLToPath(import.meta.url);
@@ -427,6 +404,7 @@ async function main() {
427
404
  }
428
405
 
429
406
  if (command === 'completions') {
407
+ const { runCompletions } = await import('../src/completions.mjs');
430
408
  runCompletions(args.slice(1));
431
409
  return;
432
410
  }
@@ -447,12 +425,13 @@ async function main() {
447
425
 
448
426
  // Init — now has access to config for Claude command generation
449
427
  if (command === 'init') {
428
+ const { runInit } = await import('../src/init.mjs');
450
429
  runInit(process.cwd(), config.configFound ? config : null);
451
430
  return;
452
431
  }
453
432
 
454
433
  // Watch is a pure proxy — pass raw args so the child process gets all flags
455
- if (command === 'watch') { runWatch(args.slice(1), config); return; }
434
+ if (command === 'watch') { const { runWatch } = await import('../src/watch.mjs'); runWatch(args.slice(1), config); return; }
456
435
 
457
436
  // Strip global flags from restArgs so commands don't have to filter them
458
437
  const restArgs = [];
@@ -485,35 +464,41 @@ async function main() {
485
464
 
486
465
  // Preset aliases
487
466
  if (config.presets[command]) {
467
+ const { buildIndex } = await import('../src/index.mjs');
468
+ const { runQuery } = await import('../src/query.mjs');
488
469
  const index = buildIndex(config);
489
470
  runQuery(index, [...config.presets[command], ...restArgs], config);
490
471
  return;
491
472
  }
492
473
 
493
474
  // Commands that handle their own index building
494
- if (command === 'diff') { runDiff(restArgs, config); return; }
495
- if (command === 'summary') { runSummary(restArgs, config); return; }
496
- if (command === 'deps') { runDeps(restArgs, config); return; }
497
- if (command === 'unblocks') { runUnblocks(restArgs, config); return; }
498
- if (command === 'health') { runHealth(restArgs, config); return; }
499
- if (command === 'glossary') { runGlossary(restArgs, config); return; }
500
- if (command === 'export') { runExport(restArgs, config, { dryRun, root: rootArg, type: typeArg }); return; }
501
- if (command === 'notion') { await runNotion(restArgs, config, { dryRun }); return; }
475
+ if (command === 'diff') { const { runDiff } = await import('../src/diff.mjs'); runDiff(restArgs, config); return; }
476
+ if (command === 'summary') { const { runSummary } = await import('../src/summary.mjs'); runSummary(restArgs, config); return; }
477
+ if (command === 'deps') { const { runDeps } = await import('../src/deps.mjs'); runDeps(restArgs, config); return; }
478
+ if (command === 'unblocks') { const { runUnblocks } = await import('../src/deps.mjs'); runUnblocks(restArgs, config); return; }
479
+ if (command === 'health') { const { runHealth } = await import('../src/health.mjs'); runHealth(restArgs, config); return; }
480
+ if (command === 'glossary') { const { runGlossary } = await import('../src/glossary.mjs'); runGlossary(restArgs, config); return; }
481
+ if (command === 'export') { const { runExport } = await import('../src/export.mjs'); runExport(restArgs, config, { dryRun, root: rootArg, type: typeArg }); return; }
482
+ if (command === 'notion') { const { runNotion } = await import('../src/notion.mjs'); await runNotion(restArgs, config, { dryRun }); return; }
502
483
 
503
484
  // Lifecycle commands
504
- if (command === 'pickup') { await runPickup(restArgs, config, { dryRun }); return; }
505
- if (command === 'finish') { await runFinish(restArgs, config, { dryRun }); return; }
506
- if (command === 'status') { await runStatus(restArgs, config, { dryRun }); return; }
507
- if (command === 'archive') { runArchive(restArgs, config, { dryRun }); return; }
508
- if (command === 'bulk' && restArgs[0] === 'archive') { runBulkArchive(restArgs.slice(1), config, { dryRun }); return; }
509
- if (command === 'touch') { runTouch(restArgs, config, { dryRun }); return; }
510
- if (command === 'new') { await runNew(restArgs, config, { dryRun, root: rootArg }); return; }
511
- if (command === 'lint') { runLint(restArgs, config, { dryRun }); return; }
512
- if (command === 'rename') { await runRename(restArgs, config, { dryRun }); return; }
513
- if (command === 'migrate') { runMigrate(restArgs, config, { dryRun }); return; }
514
- if (command === 'fix-refs') { runFixRefs(restArgs, config, { dryRun }); return; }
515
- if (command === 'doctor') { runDoctor(restArgs, config, { dryRun }); return; }
516
-
485
+ if (command === 'pickup') { const { runPickup } = await import('../src/lifecycle.mjs'); await runPickup(restArgs, config, { dryRun }); return; }
486
+ if (command === 'finish') { const { runFinish } = await import('../src/lifecycle.mjs'); await runFinish(restArgs, config, { dryRun }); return; }
487
+ if (command === 'status') { const { runStatus } = await import('../src/lifecycle.mjs'); await runStatus(restArgs, config, { dryRun }); return; }
488
+ if (command === 'archive') { const { runArchive } = await import('../src/lifecycle.mjs'); runArchive(restArgs, config, { dryRun }); return; }
489
+ if (command === 'bulk' && restArgs[0] === 'archive') { const { runBulkArchive } = await import('../src/lifecycle.mjs'); runBulkArchive(restArgs.slice(1), config, { dryRun }); return; }
490
+ if (command === 'touch') { const { runTouch } = await import('../src/lifecycle.mjs'); runTouch(restArgs, config, { dryRun }); return; }
491
+ if (command === 'new') { const { runNew } = await import('../src/new.mjs'); await runNew(restArgs, config, { dryRun, root: rootArg }); return; }
492
+ if (command === 'lint') { const { runLint } = await import('../src/lint.mjs'); runLint(restArgs, config, { dryRun }); return; }
493
+ if (command === 'rename') { const { runRename } = await import('../src/rename.mjs'); await runRename(restArgs, config, { dryRun }); return; }
494
+ if (command === 'migrate') { const { runMigrate } = await import('../src/migrate.mjs'); runMigrate(restArgs, config, { dryRun }); return; }
495
+ if (command === 'fix-refs') { const { runFixRefs } = await import('../src/fix-refs.mjs'); runFixRefs(restArgs, config, { dryRun }); return; }
496
+ if (command === 'doctor') { const { runDoctor } = await import('../src/doctor.mjs'); runDoctor(restArgs, config, { dryRun }); return; }
497
+
498
+ // All remaining commands need the index + render modules
499
+ const { buildIndex } = await import('../src/index.mjs');
500
+ const { renderCompactList, renderVerboseList, renderContext, renderBriefing, renderCheck, renderCoverage, buildCoverage } = await import('../src/render.mjs');
501
+ const { runFocus, runQuery } = await import('../src/query.mjs');
517
502
  const index = buildIndex(config);
518
503
 
519
504
  // Apply --root and --type filters
@@ -570,6 +555,8 @@ async function main() {
570
555
 
571
556
  if (fix) {
572
557
  // Auto-fix: broken refs, then lint, then rebuild index
558
+ const { fixBrokenRefs } = await import('../src/fix-refs.mjs');
559
+ const { runLint } = await import('../src/lint.mjs');
573
560
  fixBrokenRefs(config, { dryRun, quiet: false });
574
561
  runLint(['--fix'], config, { dryRun });
575
562
  if (config.indexPath) {
@@ -629,6 +616,7 @@ async function main() {
629
616
  }
630
617
 
631
618
  if (command === 'stats') {
619
+ const { buildStats, renderStats, renderStatsJson } = await import('../src/stats.mjs');
632
620
  const stats = buildStats(index, config);
633
621
  if (args.includes('--json')) {
634
622
  process.stdout.write(renderStatsJson(stats));
@@ -643,6 +631,7 @@ async function main() {
643
631
  die('Index generation is not configured. Add an `index` section to your dotmd.config.mjs.');
644
632
  }
645
633
  const write = args.includes('--write');
634
+ const { renderIndexFile, writeIndex } = await import('../src/index-file.mjs');
646
635
  const rendered = renderIndexFile(index, config);
647
636
  if (write && !dryRun) {
648
637
  writeIndex(rendered, config);
@@ -730,6 +719,7 @@ async function main() {
730
719
  }
731
720
 
732
721
  if (command === 'graph') {
722
+ const { buildGraph, renderGraphText, renderGraphDot, renderGraphJson } = await import('../src/graph.mjs');
733
723
  const statusFilter = (() => { const i = args.indexOf('--status'); return i !== -1 && args[i + 1] ? args[i + 1] : null; })();
734
724
  const moduleFilter = (() => { const i = args.indexOf('--module'); return i !== -1 && args[i + 1] ? args[i + 1] : null; })();
735
725
  const surfaceFilter = (() => { const i = args.indexOf('--surface'); return i !== -1 && args[i + 1] ? args[i + 1] : null; })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotmd-cli",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
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/git.mjs CHANGED
@@ -19,6 +19,24 @@ export function getGitLastModified(relPath, repoRoot) {
19
19
  return result.stdout.trim();
20
20
  }
21
21
 
22
+ export function getGitLastModifiedBatch(repoRoot) {
23
+ const result = spawnSync('git', [
24
+ 'log', '--format=commit %aI', '--name-only', '--diff-filter=ACDMR', 'HEAD',
25
+ ], { cwd: repoRoot, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 });
26
+ if (result.error || result.status !== 0) return new Map();
27
+
28
+ const map = new Map();
29
+ let currentDate = null;
30
+ for (const line of result.stdout.split('\n')) {
31
+ if (line.startsWith('commit ')) {
32
+ currentDate = line.slice(7).trim();
33
+ } else if (line && currentDate && !map.has(line)) {
34
+ map.set(line, currentDate);
35
+ }
36
+ }
37
+ return map;
38
+ }
39
+
22
40
  export function gitMv(source, target, repoRoot) {
23
41
  ensureGit();
24
42
  const result = spawnSync('git', ['mv', source, target], {
package/src/lifecycle.mjs CHANGED
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { extractFrontmatter, parseSimpleFrontmatter, replaceFrontmatter } from './frontmatter.mjs';
4
4
  import { asString, toRepoPath, die, warn, resolveDocPath, escapeRegex } from './util.mjs';
5
- import { gitMv, getGitLastModified } from './git.mjs';
5
+ import { gitMv, getGitLastModified, getGitLastModifiedBatch } from './git.mjs';
6
6
  import { buildIndex, collectDocFiles } from './index.mjs';
7
7
  import { renderIndexFile, writeIndex } from './index-file.mjs';
8
8
  import { green, dim, yellow } from './color.mjs';
@@ -356,6 +356,7 @@ export function runTouch(argv, config, opts = {}) {
356
356
 
357
357
  const prefix = dryRun ? dim('[dry-run] ') : '';
358
358
  let synced = 0;
359
+ const gitDates = getGitLastModifiedBatch(config.repoRoot);
359
360
 
360
361
  for (const filePath of allFiles) {
361
362
  const repoPath = toRepoPath(filePath, config.repoRoot);
@@ -368,7 +369,7 @@ export function runTouch(argv, config, opts = {}) {
368
369
  if (config.lifecycle.skipStaleFor.has(status)) continue;
369
370
 
370
371
  const fmUpdated = asString(parsed.updated);
371
- const gitDate = getGitLastModified(repoPath, config.repoRoot);
372
+ const gitDate = gitDates.get(repoPath) ?? null;
372
373
  if (!gitDate) continue;
373
374
 
374
375
  const gitDay = gitDate.slice(0, 10);
package/src/query.mjs CHANGED
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import { capitalize, toSlug, warn } from './util.mjs';
4
4
  import { renderProgressBar } from './render.mjs';
5
5
  import { computeDaysSinceUpdate, computeIsStale } from './validate.mjs';
6
- import { getGitLastModified } from './git.mjs';
6
+ import { getGitLastModifiedBatch } from './git.mjs';
7
7
  import { extractFrontmatter } from './frontmatter.mjs';
8
8
  import { summarizeDocBody } from './ai.mjs';
9
9
  import { dim } from './color.mjs';
@@ -128,8 +128,9 @@ export function filterDocs(docs, filters, config) {
128
128
  if (filters.updatedSince) result = result.filter(d => d.updated && d.updated >= filters.updatedSince);
129
129
 
130
130
  if (filters.git) {
131
+ const gitDates = getGitLastModifiedBatch(config.repoRoot);
131
132
  for (const doc of result) {
132
- const gitDate = getGitLastModified(doc.path, config.repoRoot);
133
+ const gitDate = gitDates.get(doc.path) ?? null;
133
134
  if (gitDate) {
134
135
  doc.daysSinceUpdate = computeDaysSinceUpdate(gitDate);
135
136
  doc.isStale = computeIsStale(doc.status, gitDate, config);
package/src/validate.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { asString } from './util.mjs';
4
- import { getGitLastModified } from './git.mjs';
4
+ import { getGitLastModified, getGitLastModifiedBatch } from './git.mjs';
5
5
  import { toRepoPath } from './util.mjs';
6
6
 
7
7
  const NOW = new Date();
@@ -150,11 +150,12 @@ export function checkBidirectionalReferences(docs, config) {
150
150
 
151
151
  export function checkGitStaleness(docs, config) {
152
152
  const warnings = [];
153
+ const gitDates = getGitLastModifiedBatch(config.repoRoot);
153
154
  for (const doc of docs) {
154
155
  if (config.lifecycle.skipStaleFor.has(doc.status)) continue;
155
156
  if (!doc.updated) continue;
156
157
 
157
- const gitDate = getGitLastModified(doc.path, config.repoRoot);
158
+ const gitDate = gitDates.get(doc.path) ?? null;
158
159
  if (!gitDate) continue;
159
160
 
160
161
  const gitDay = gitDate.slice(0, 10);