dotmd-cli 0.8.3 → 0.8.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 CHANGED
@@ -35,7 +35,7 @@ const HELP = {
35
35
  _main: `dotmd v${pkg.version} — frontmatter markdown document manager
36
36
 
37
37
  Commands:
38
- list [--verbose] List docs grouped by status (default)
38
+ list [--verbose|--json] List docs grouped by status (default)
39
39
  json Full index as JSON
40
40
  check [flags] Validate frontmatter and references
41
41
  coverage [--json] Metadata coverage report
@@ -70,6 +70,22 @@ Options:
70
70
  --help, -h Show help
71
71
  --version, -v Show version`,
72
72
 
73
+ list: `dotmd list — list docs grouped by status
74
+
75
+ Options:
76
+ --verbose Show full details per doc
77
+ --json Output full index as JSON (same as dotmd json)`,
78
+
79
+ json: `dotmd json — full index as JSON
80
+
81
+ Outputs the complete document index as JSON to stdout.`,
82
+
83
+ completions: `dotmd completions <bash|zsh> — output shell completion script
84
+
85
+ Add to your shell config:
86
+ bash: eval "$(dotmd completions bash)"
87
+ zsh: eval "$(dotmd completions zsh)"`,
88
+
73
89
  query: `dotmd query — filtered document search
74
90
 
75
91
  Filters:
@@ -105,7 +121,9 @@ Use --dry-run (-n) to preview changes without writing anything.`,
105
121
 
106
122
  Options:
107
123
  --errors-only Show only errors, suppress warnings
108
- --fix Auto-fix broken references and regenerate index`,
124
+ --fix Auto-fix broken refs, lint issues, and regenerate index
125
+ --json Output errors and warnings as JSON
126
+ --dry-run, -n Preview fixes without writing (with --fix)`,
109
127
 
110
128
  archive: `dotmd archive <file> — archive a document
111
129
 
@@ -123,13 +141,17 @@ Options:
123
141
 
124
142
  focus: `dotmd focus [status] — detailed view for one status group
125
143
 
126
- Shows detailed info for all docs matching the given status (default: active).`,
144
+ Shows detailed info for all docs matching the given status (default: active).
145
+
146
+ Options:
147
+ --json Output as JSON`,
127
148
 
128
149
  context: `dotmd context — compact briefing (LLM-oriented)
129
150
 
130
151
  Generates a compact status briefing designed for AI/LLM consumption.
131
152
 
132
153
  Options:
154
+ --json Output as JSON
133
155
  --summarize Add AI summaries for expanded docs
134
156
  --model <name> MLX model for AI summaries`,
135
157
 
@@ -242,7 +264,8 @@ Options:
242
264
  --output <path> Write to file/directory (default: stdout for md/json)
243
265
  --status <s1,s2> Filter by status
244
266
  --module <name> Filter by module
245
- --root <name> Filter by root`,
267
+ --root <name> Filter by root
268
+ --dry-run, -n Preview without writing`,
246
269
 
247
270
  summary: `dotmd summary <file> — AI summary of a document
248
271
 
@@ -266,8 +289,14 @@ Options:
266
289
 
267
290
  lint: `dotmd lint [--fix] — check and auto-fix frontmatter issues
268
291
 
269
- Scans all docs for fixable problems: missing updated dates, status casing,
270
- camelCase key names, trailing whitespace in values, missing EOF newline.
292
+ Scans all docs for fixable problems:
293
+ - Missing status (inferred via local AI model when available)
294
+ - Missing updated date (set to today)
295
+ - Status casing (e.g. Active → active)
296
+ - camelCase key names (e.g. nextStep → next_step)
297
+ - Comma-separated surface values (converted to surfaces: array)
298
+ - Trailing whitespace in frontmatter values
299
+ - Missing newline at end of file
271
300
 
272
301
  Without --fix, reports all issues. With --fix, applies fixes in place.
273
302
  Use --dry-run (-n) with --fix to preview without writing anything.`,
@@ -433,19 +462,32 @@ async function main() {
433
462
 
434
463
  if (fix) {
435
464
  // Auto-fix: broken refs, then lint, then rebuild index
436
- const refResult = fixBrokenRefs(config, { dryRun, quiet: false });
437
- if (!dryRun) {
438
- runLint(['--fix'], config, { dryRun });
439
- }
440
- if (!dryRun && config.indexPath) {
441
- const { renderIndexFile: rif, writeIndex: wi } = await import('../src/index-file.mjs');
442
- const freshIndex = buildIndex(config);
443
- wi(rif(freshIndex, config), config);
444
- process.stdout.write('Index regenerated.\n');
465
+ fixBrokenRefs(config, { dryRun, quiet: false });
466
+ runLint(['--fix'], config, { dryRun });
467
+ if (config.indexPath) {
468
+ if (!dryRun) {
469
+ const { renderIndexFile: rif, writeIndex: wi } = await import('../src/index-file.mjs');
470
+ const freshIndex = buildIndex(config);
471
+ wi(rif(freshIndex, config), config);
472
+ process.stdout.write('Index regenerated.\n');
473
+ } else {
474
+ process.stdout.write('[dry-run] Would regenerate index.\n');
475
+ }
445
476
  }
446
477
  // Show remaining issues
447
478
  const freshIndex = buildIndex(config);
448
- process.stdout.write('\n' + renderCheck(freshIndex, config, { errorsOnly }));
479
+ if (args.includes('--json')) {
480
+ process.stdout.write(JSON.stringify({
481
+ docsScanned: freshIndex.docs.length,
482
+ errors: freshIndex.errors,
483
+ warnings: errorsOnly ? [] : freshIndex.warnings,
484
+ errorCount: freshIndex.errors.length,
485
+ warningCount: freshIndex.warnings.length,
486
+ passed: freshIndex.errors.length === 0,
487
+ }, null, 2) + '\n');
488
+ } else {
489
+ process.stdout.write('\n' + renderCheck(freshIndex, config, { errorsOnly }));
490
+ }
449
491
  if (freshIndex.errors.length > 0) process.exitCode = 1;
450
492
  return;
451
493
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotmd-cli",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
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/lint.mjs CHANGED
@@ -78,9 +78,13 @@ export function runLint(argv, config, opts = {}) {
78
78
  }
79
79
  }
80
80
 
81
- // Also get non-fixable issues from index
81
+ // Also get non-fixable issues from index, excluding issues we can already fix
82
82
  const index = buildIndex(config);
83
- const nonFixable = [...index.errors, ...index.warnings];
83
+ const fixablePaths = new Set(fixable.map(f => f.repoPath));
84
+ const nonFixable = [...index.errors, ...index.warnings].filter(issue => {
85
+ if (issue.message.includes('Missing frontmatter `status`') && fixablePaths.has(issue.path)) return false;
86
+ return true;
87
+ });
84
88
 
85
89
  if (!fix) {
86
90
  // Report mode
package/src/render.mjs CHANGED
@@ -41,6 +41,32 @@ function _renderCompactList(index, config) {
41
41
  lines.push('');
42
42
  }
43
43
 
44
+ // Render docs with statuses not in statusOrder
45
+ const knownStatuses = new Set(config.statusOrder);
46
+ const otherStatuses = [...new Set(index.docs.filter(d => d.status && !knownStatuses.has(d.status)).map(d => d.status))].sort();
47
+ for (const status of otherStatuses) {
48
+ const docs = index.docs.filter(d => d.status === status);
49
+ if (!docs.length) continue;
50
+
51
+ lines.push(bold(`${capitalize(status)} (${docs.length})`));
52
+ const maxTitle = Math.min(config.display.truncateTitle || 30, Math.max(...docs.map(d => d.title.length)));
53
+
54
+ for (const doc of docs) {
55
+ const title = doc.title.length > maxTitle
56
+ ? doc.title.slice(0, maxTitle - 3) + '...'
57
+ : doc.title.padEnd(maxTitle);
58
+ const days = doc.daysSinceUpdate != null ? `${doc.daysSinceUpdate}d` : '';
59
+ const progress = renderProgressBar(doc.checklist);
60
+ const next = doc.nextStep ? `next: ${doc.nextStep}` : '';
61
+ const parts = [` ${title} ${days.padStart(4)}`];
62
+ if (progress) parts.push(progress);
63
+ if (next) parts.push(next);
64
+ const line = parts.join(' ');
65
+ lines.push(line.length > maxWidth ? line.slice(0, maxWidth - 3) + '...' : line);
66
+ }
67
+ lines.push('');
68
+ }
69
+
44
70
  return `${lines.join('\n').trimEnd()}\n`;
45
71
  }
46
72
 
@@ -62,6 +88,24 @@ export function renderVerboseList(index, config) {
62
88
  lines.push('');
63
89
  }
64
90
 
91
+ // Render docs with statuses not in statusOrder
92
+ const knownStatuses = new Set(config.statusOrder);
93
+ const otherStatuses = [...new Set(index.docs.filter(d => d.status && !knownStatuses.has(d.status)).map(d => d.status))].sort();
94
+ for (const status of otherStatuses) {
95
+ const docs = index.docs.filter(doc => doc.status === status);
96
+ if (docs.length === 0) continue;
97
+
98
+ lines.push(`${capitalize(status)} (${docs.length})`);
99
+ for (const doc of docs) {
100
+ const parts = [`- ${doc.title}`, `${capitalize(status)}: ${doc.currentState}`, `(${doc.path})`];
101
+ if (doc.nextStep) {
102
+ parts.push(`next: ${doc.nextStep}`);
103
+ }
104
+ lines.push(parts.join(' — '));
105
+ }
106
+ lines.push('');
107
+ }
108
+
65
109
  return `${lines.join('\n').trimEnd()}\n`;
66
110
  }
67
111