dotmd-cli 0.13.0 → 0.13.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 +44 -53
- package/package.json +1 -1
- package/src/git.mjs +18 -0
- package/src/lifecycle.mjs +3 -2
- package/src/query.mjs +3 -2
- package/src/validate.mjs +3 -2
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);
|
|
@@ -38,23 +15,24 @@ const HELP = {
|
|
|
38
15
|
|
|
39
16
|
View & Query:
|
|
40
17
|
list [--verbose] [--json] List docs grouped by status (default command)
|
|
41
|
-
json
|
|
42
|
-
briefing [--json] Compact summary for session start (5-10 lines)
|
|
18
|
+
briefing [--json] Compact summary for session start
|
|
43
19
|
context [--summarize] [--json] Full briefing (LLM-oriented)
|
|
44
20
|
focus [status] [--json] Detailed view for one status group
|
|
45
21
|
query [filters] [--json] Filtered search (--status, --keyword, --stale, etc.)
|
|
46
|
-
|
|
22
|
+
plans All plans (preset)
|
|
23
|
+
stale Stale docs (preset)
|
|
24
|
+
actionable Docs with next steps (preset)
|
|
25
|
+
|
|
26
|
+
Analyze:
|
|
47
27
|
stats [--json] Doc health dashboard
|
|
28
|
+
health [--json] Plan velocity, aging, and pipeline health
|
|
29
|
+
coverage [--json] Metadata coverage report
|
|
48
30
|
graph [--dot] [--json] Visualize document relationships
|
|
49
31
|
deps [file] [--json] Dependency tree or overview
|
|
50
32
|
unblocks <file> [--json] Show what completes when this doc ships
|
|
51
|
-
health [--json] Plan velocity, aging, and pipeline health
|
|
52
|
-
glossary <term> [--list] [--json] Look up domain terms + related docs
|
|
53
33
|
diff [file] [--summarize] Show changes since last updated date
|
|
54
|
-
plans List all plans (shortcut for query --type plan)
|
|
55
|
-
stale List stale docs across all statuses
|
|
56
|
-
actionable List docs with a next step ready to act on
|
|
57
34
|
summary <file> [--json] AI summary of a document
|
|
35
|
+
glossary <term> [--list] [--json] Look up domain terms + related docs
|
|
58
36
|
|
|
59
37
|
Validate & Fix:
|
|
60
38
|
check [--fix] [--errors-only] [--json] Validate frontmatter and references
|
|
@@ -427,6 +405,7 @@ async function main() {
|
|
|
427
405
|
}
|
|
428
406
|
|
|
429
407
|
if (command === 'completions') {
|
|
408
|
+
const { runCompletions } = await import('../src/completions.mjs');
|
|
430
409
|
runCompletions(args.slice(1));
|
|
431
410
|
return;
|
|
432
411
|
}
|
|
@@ -447,12 +426,13 @@ async function main() {
|
|
|
447
426
|
|
|
448
427
|
// Init — now has access to config for Claude command generation
|
|
449
428
|
if (command === 'init') {
|
|
429
|
+
const { runInit } = await import('../src/init.mjs');
|
|
450
430
|
runInit(process.cwd(), config.configFound ? config : null);
|
|
451
431
|
return;
|
|
452
432
|
}
|
|
453
433
|
|
|
454
434
|
// 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; }
|
|
435
|
+
if (command === 'watch') { const { runWatch } = await import('../src/watch.mjs'); runWatch(args.slice(1), config); return; }
|
|
456
436
|
|
|
457
437
|
// Strip global flags from restArgs so commands don't have to filter them
|
|
458
438
|
const restArgs = [];
|
|
@@ -485,35 +465,41 @@ async function main() {
|
|
|
485
465
|
|
|
486
466
|
// Preset aliases
|
|
487
467
|
if (config.presets[command]) {
|
|
468
|
+
const { buildIndex } = await import('../src/index.mjs');
|
|
469
|
+
const { runQuery } = await import('../src/query.mjs');
|
|
488
470
|
const index = buildIndex(config);
|
|
489
471
|
runQuery(index, [...config.presets[command], ...restArgs], config);
|
|
490
472
|
return;
|
|
491
473
|
}
|
|
492
474
|
|
|
493
475
|
// 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; }
|
|
476
|
+
if (command === 'diff') { const { runDiff } = await import('../src/diff.mjs'); runDiff(restArgs, config); return; }
|
|
477
|
+
if (command === 'summary') { const { runSummary } = await import('../src/summary.mjs'); runSummary(restArgs, config); return; }
|
|
478
|
+
if (command === 'deps') { const { runDeps } = await import('../src/deps.mjs'); runDeps(restArgs, config); return; }
|
|
479
|
+
if (command === 'unblocks') { const { runUnblocks } = await import('../src/deps.mjs'); runUnblocks(restArgs, config); return; }
|
|
480
|
+
if (command === 'health') { const { runHealth } = await import('../src/health.mjs'); runHealth(restArgs, config); return; }
|
|
481
|
+
if (command === 'glossary') { const { runGlossary } = await import('../src/glossary.mjs'); runGlossary(restArgs, config); return; }
|
|
482
|
+
if (command === 'export') { const { runExport } = await import('../src/export.mjs'); runExport(restArgs, config, { dryRun, root: rootArg, type: typeArg }); return; }
|
|
483
|
+
if (command === 'notion') { const { runNotion } = await import('../src/notion.mjs'); await runNotion(restArgs, config, { dryRun }); return; }
|
|
502
484
|
|
|
503
485
|
// 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
|
-
|
|
486
|
+
if (command === 'pickup') { const { runPickup } = await import('../src/lifecycle.mjs'); await runPickup(restArgs, config, { dryRun }); return; }
|
|
487
|
+
if (command === 'finish') { const { runFinish } = await import('../src/lifecycle.mjs'); await runFinish(restArgs, config, { dryRun }); return; }
|
|
488
|
+
if (command === 'status') { const { runStatus } = await import('../src/lifecycle.mjs'); await runStatus(restArgs, config, { dryRun }); return; }
|
|
489
|
+
if (command === 'archive') { const { runArchive } = await import('../src/lifecycle.mjs'); runArchive(restArgs, config, { dryRun }); return; }
|
|
490
|
+
if (command === 'bulk' && restArgs[0] === 'archive') { const { runBulkArchive } = await import('../src/lifecycle.mjs'); runBulkArchive(restArgs.slice(1), config, { dryRun }); return; }
|
|
491
|
+
if (command === 'touch') { const { runTouch } = await import('../src/lifecycle.mjs'); runTouch(restArgs, config, { dryRun }); return; }
|
|
492
|
+
if (command === 'new') { const { runNew } = await import('../src/new.mjs'); await runNew(restArgs, config, { dryRun, root: rootArg }); return; }
|
|
493
|
+
if (command === 'lint') { const { runLint } = await import('../src/lint.mjs'); runLint(restArgs, config, { dryRun }); return; }
|
|
494
|
+
if (command === 'rename') { const { runRename } = await import('../src/rename.mjs'); await runRename(restArgs, config, { dryRun }); return; }
|
|
495
|
+
if (command === 'migrate') { const { runMigrate } = await import('../src/migrate.mjs'); runMigrate(restArgs, config, { dryRun }); return; }
|
|
496
|
+
if (command === 'fix-refs') { const { runFixRefs } = await import('../src/fix-refs.mjs'); runFixRefs(restArgs, config, { dryRun }); return; }
|
|
497
|
+
if (command === 'doctor') { const { runDoctor } = await import('../src/doctor.mjs'); runDoctor(restArgs, config, { dryRun }); return; }
|
|
498
|
+
|
|
499
|
+
// All remaining commands need the index + render modules
|
|
500
|
+
const { buildIndex } = await import('../src/index.mjs');
|
|
501
|
+
const { renderCompactList, renderVerboseList, renderContext, renderBriefing, renderCheck, renderCoverage, buildCoverage } = await import('../src/render.mjs');
|
|
502
|
+
const { runFocus, runQuery } = await import('../src/query.mjs');
|
|
517
503
|
const index = buildIndex(config);
|
|
518
504
|
|
|
519
505
|
// Apply --root and --type filters
|
|
@@ -570,6 +556,8 @@ async function main() {
|
|
|
570
556
|
|
|
571
557
|
if (fix) {
|
|
572
558
|
// Auto-fix: broken refs, then lint, then rebuild index
|
|
559
|
+
const { fixBrokenRefs } = await import('../src/fix-refs.mjs');
|
|
560
|
+
const { runLint } = await import('../src/lint.mjs');
|
|
573
561
|
fixBrokenRefs(config, { dryRun, quiet: false });
|
|
574
562
|
runLint(['--fix'], config, { dryRun });
|
|
575
563
|
if (config.indexPath) {
|
|
@@ -629,6 +617,7 @@ async function main() {
|
|
|
629
617
|
}
|
|
630
618
|
|
|
631
619
|
if (command === 'stats') {
|
|
620
|
+
const { buildStats, renderStats, renderStatsJson } = await import('../src/stats.mjs');
|
|
632
621
|
const stats = buildStats(index, config);
|
|
633
622
|
if (args.includes('--json')) {
|
|
634
623
|
process.stdout.write(renderStatsJson(stats));
|
|
@@ -643,6 +632,7 @@ async function main() {
|
|
|
643
632
|
die('Index generation is not configured. Add an `index` section to your dotmd.config.mjs.');
|
|
644
633
|
}
|
|
645
634
|
const write = args.includes('--write');
|
|
635
|
+
const { renderIndexFile, writeIndex } = await import('../src/index-file.mjs');
|
|
646
636
|
const rendered = renderIndexFile(index, config);
|
|
647
637
|
if (write && !dryRun) {
|
|
648
638
|
writeIndex(rendered, config);
|
|
@@ -730,6 +720,7 @@ async function main() {
|
|
|
730
720
|
}
|
|
731
721
|
|
|
732
722
|
if (command === 'graph') {
|
|
723
|
+
const { buildGraph, renderGraphText, renderGraphDot, renderGraphJson } = await import('../src/graph.mjs');
|
|
733
724
|
const statusFilter = (() => { const i = args.indexOf('--status'); return i !== -1 && args[i + 1] ? args[i + 1] : null; })();
|
|
734
725
|
const moduleFilter = (() => { const i = args.indexOf('--module'); return i !== -1 && args[i + 1] ? args[i + 1] : null; })();
|
|
735
726
|
const surfaceFilter = (() => { const i = args.indexOf('--surface'); return i !== -1 && args[i + 1] ? args[i + 1] : null; })();
|
package/package.json
CHANGED
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 =
|
|
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 {
|
|
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 =
|
|
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 =
|
|
158
|
+
const gitDate = gitDates.get(doc.path) ?? null;
|
|
158
159
|
if (!gitDate) continue;
|
|
159
160
|
|
|
160
161
|
const gitDay = gitDate.slice(0, 10);
|