dotmd-cli 0.10.2 → 0.10.3

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/README.md CHANGED
@@ -57,6 +57,7 @@ Any `.md` file with YAML frontmatter:
57
57
 
58
58
  ```markdown
59
59
  ---
60
+ type: doc
60
61
  status: active
61
62
  updated: 2026-03-14
62
63
  module: auth
@@ -76,7 +77,31 @@ Design doc content here...
76
77
  - [ ] Add tests
77
78
  ```
78
79
 
79
- The only required field is `status`. Everything else is optional but unlocks more features.
80
+ The only required field is `status`. Everything else is optional but unlocks more features. The `type` field (`plan`, `doc`, or `research`) enables type-specific statuses and smarter context briefings.
81
+
82
+ ## Document Types
83
+
84
+ Every document can have a `type` field in its frontmatter. Types determine which statuses are valid and how the document appears in context briefings.
85
+
86
+ | Type | Purpose | Valid Statuses |
87
+ |------|---------|----------------|
88
+ | `plan` | Execution plans | `in-session`, `active`, `planned`, `blocked`, `done`, `archived` |
89
+ | `doc` | Design docs, specs, ADRs, RFCs | `draft`, `active`, `review`, `reference`, `deprecated`, `archived` |
90
+ | `research` | Investigations, audits, analysis | `active`, `reference`, `archived` |
91
+
92
+ Documents without a `type` field use the global `statuses.order` from config.
93
+
94
+ Templates auto-set the type: `--template plan` sets `type: plan`, `--template adr` sets `type: doc`, `--template audit` sets `type: research`.
95
+
96
+ Filter by type with `--type`:
97
+
98
+ ```bash
99
+ dotmd query --type plan --status active # active plans
100
+ dotmd list --type doc # all docs
101
+ dotmd export --type research # export research only
102
+ ```
103
+
104
+ Customize types and their statuses in config with the `types` key. See [`dotmd.config.example.mjs`](dotmd.config.example.mjs).
80
105
 
81
106
  ## Commands
82
107
 
@@ -117,6 +142,7 @@ dotmd completions <shell> Output shell completion script (bash, zsh)
117
142
  --config <path> Explicit config file path
118
143
  --dry-run, -n Preview changes without writing anything
119
144
  --root <name> Filter to a specific docs root
145
+ --type <t1,t2> Filter by document type (plan, doc, research)
120
146
  --verbose Show resolved config details
121
147
  --help, -h Show help (per-command with: dotmd <cmd> --help)
122
148
  --version, -v Show version
@@ -133,7 +159,7 @@ dotmd query --status active --summarize # AI summaries
133
159
  dotmd query --status active --summarize --summarize-limit 3
134
160
  ```
135
161
 
136
- Flags: `--status`, `--keyword`, `--module`, `--surface`, `--domain`, `--owner`, `--updated-since`, `--stale`, `--has-next-step`, `--has-blockers`, `--checklist-open`, `--sort`, `--limit`, `--all`, `--git`, `--json`, `--summarize`, `--summarize-limit`, `--model`.
162
+ Flags: `--type`, `--status`, `--keyword`, `--module`, `--surface`, `--domain`, `--owner`, `--updated-since`, `--stale`, `--has-next-step`, `--has-blockers`, `--checklist-open`, `--sort`, `--limit`, `--all`, `--git`, `--json`, `--summarize`, `--summarize-limit`, `--model`.
137
163
 
138
164
  ### Scaffold with Templates
139
165
 
@@ -225,6 +251,7 @@ dotmd export --format html --output site # static HTML site
225
251
  dotmd export --format json > bundle.json # JSON bundle with bodies
226
252
  dotmd export docs/plan-a.md # single doc + dependencies
227
253
  dotmd export --status active # filtered export
254
+ dotmd export --type plan # export only plans
228
255
  ```
229
256
 
230
257
  ### Notion Integration
@@ -359,6 +386,7 @@ export const lifecycle = {
359
386
  archiveStatuses: ['archived'], // auto-move to archiveDir
360
387
  skipStaleFor: ['archived'],
361
388
  skipWarningsFor: ['archived'],
389
+ terminalStatuses: ['archived', 'deprecated', 'reference', 'done'],
362
390
  };
363
391
 
364
392
  export const taxonomy = {
@@ -378,7 +406,7 @@ export const index = {
378
406
  };
379
407
  ```
380
408
 
381
- All exports are optional. See [`dotmd.config.example.mjs`](dotmd.config.example.mjs) for the full reference.
409
+ All exports are optional. Additional options: `types`, `context`, `display`, `presets`, `templates`, `excludeDirs`, `notion`. See [`dotmd.config.example.mjs`](dotmd.config.example.mjs) for the full reference.
382
410
 
383
411
  Config discovery walks up from cwd looking for `dotmd.config.mjs` or `.dotmd.config.mjs`.
384
412
 
@@ -424,6 +452,16 @@ export function onArchive(doc, { oldPath, newPath }) {
424
452
 
425
453
  Available: `onArchive`, `onStatusChange`, `onTouch`, `onNew`, `onRename`, `onLint`.
426
454
 
455
+ ### Transform Hooks
456
+
457
+ ```js
458
+ // Add computed fields to every doc after parsing
459
+ export function transformDoc(doc) {
460
+ doc.priority = doc.blockers?.length ? 'high' : 'normal';
461
+ return doc;
462
+ }
463
+ ```
464
+
427
465
  ### AI Hooks
428
466
 
429
467
  ```js
package/bin/dotmd.mjs CHANGED
@@ -238,6 +238,7 @@ Options:
238
238
  --template <name> Use a template (default, plan, adr, rfc, audit, design)
239
239
  --status <s> Set initial status (default: active)
240
240
  --title <t> Override the document title
241
+ --root <name> Create in a specific docs root
241
242
  --list-templates Show available templates
242
243
 
243
244
  The filename is derived from <name> by slugifying it.
@@ -275,6 +276,7 @@ Options:
275
276
  --format <md|html|json> Output format (default: md)
276
277
  --output <path> Write to file/directory (default: stdout for md/json)
277
278
  --status <s1,s2> Filter by status
279
+ --type <t1,t2> Filter by type (plan, doc, research)
278
280
  --module <name> Filter by module
279
281
  --root <name> Filter by root
280
282
  --dry-run, -n Preview without writing`,
@@ -335,7 +337,10 @@ Use --dry-run (-n) to preview changes without writing anything.`,
335
337
  init: `dotmd init — create starter config and docs directory
336
338
 
337
339
  Creates dotmd.config.mjs, docs/, and docs/docs.md in the current
338
- directory. Skips any files that already exist.`,
340
+ directory. Skips any files that already exist.
341
+
342
+ If docs/ already contains .md files, auto-detects statuses, surfaces,
343
+ modules, and reference fields to pre-populate the config.`,
339
344
  };
340
345
 
341
346
  async function main() {
@@ -384,10 +389,17 @@ async function main() {
384
389
 
385
390
  const config = await resolveConfig(process.cwd(), explicitConfig);
386
391
 
392
+ // Watch is a pure proxy — pass raw args so the child process gets all flags
393
+ if (command === 'watch') { runWatch(args.slice(1), config); return; }
394
+
387
395
  // Strip global flags from restArgs so commands don't have to filter them
388
396
  const restArgs = [];
397
+ let rootArg = null;
398
+ let typeArg = null;
389
399
  for (let i = 1; i < args.length; i++) {
390
400
  if (args[i] === '--config') { i++; continue; }
401
+ if (args[i] === '--type' && args[i + 1]) { typeArg = args[++i]; continue; }
402
+ if (args[i] === '--root' && args[i + 1]) { rootArg = args[++i]; continue; }
391
403
  if (args[i] === '--dry-run' || args[i] === '-n' || args[i] === '--verbose') continue;
392
404
  restArgs.push(args[i]);
393
405
  }
@@ -416,19 +428,18 @@ async function main() {
416
428
  return;
417
429
  }
418
430
 
419
- // Watch and diff (handle their own index building)
420
- if (command === 'watch') { runWatch(restArgs, config); return; }
431
+ // Commands that handle their own index building
421
432
  if (command === 'diff') { runDiff(restArgs, config); return; }
422
433
  if (command === 'summary') { runSummary(restArgs, config); return; }
423
434
  if (command === 'deps') { runDeps(restArgs, config); return; }
424
- if (command === 'export') { runExport(restArgs, config, { dryRun }); return; }
435
+ if (command === 'export') { runExport(restArgs, config, { dryRun, root: rootArg, type: typeArg }); return; }
425
436
  if (command === 'notion') { await runNotion(restArgs, config, { dryRun }); return; }
426
437
 
427
438
  // Lifecycle commands
428
439
  if (command === 'status') { await runStatus(restArgs, config, { dryRun }); return; }
429
440
  if (command === 'archive') { runArchive(restArgs, config, { dryRun }); return; }
430
441
  if (command === 'touch') { runTouch(restArgs, config, { dryRun }); return; }
431
- if (command === 'new') { await runNew(restArgs, config, { dryRun }); return; }
442
+ if (command === 'new') { await runNew(restArgs, config, { dryRun, root: rootArg }); return; }
432
443
  if (command === 'lint') { runLint(restArgs, config, { dryRun }); return; }
433
444
  if (command === 'rename') { await runRename(restArgs, config, { dryRun }); return; }
434
445
  if (command === 'migrate') { runMigrate(restArgs, config, { dryRun }); return; }
@@ -437,27 +448,32 @@ async function main() {
437
448
 
438
449
  const index = buildIndex(config);
439
450
 
440
- // Apply --root filter
441
- const rootFilter = (() => { const i = args.indexOf('--root'); return i !== -1 && args[i + 1] ? args[i + 1] : null; })();
442
- if (rootFilter) {
443
- index.docs = index.docs.filter(d => d.root === rootFilter || d.root.endsWith('/' + rootFilter) || d.root.split('/').pop() === rootFilter);
444
- }
451
+ // Apply --root and --type filters
452
+ const rootFilter = rootArg;
453
+ const typeFilter = typeArg;
445
454
 
446
- // Apply --type filter
447
- const typeFilter = (() => { const i = args.indexOf('--type'); return i !== -1 && args[i + 1] ? args[i + 1] : null; })();
448
- if (typeFilter) {
449
- const types = typeFilter.split(',').map(t => t.trim()).filter(Boolean);
450
- index.docs = index.docs.filter(d => types.includes(d.type));
455
+ function applyIndexFilters(idx) {
456
+ if (rootFilter) {
457
+ idx.docs = idx.docs.filter(d => d.root === rootFilter || d.root.endsWith('/' + rootFilter) || d.root.split('/').pop() === rootFilter);
458
+ }
459
+ if (typeFilter) {
460
+ const types = typeFilter.split(',').map(t => t.trim()).filter(Boolean);
461
+ idx.docs = idx.docs.filter(d => types.includes(d.type));
462
+ }
463
+ if (rootFilter || typeFilter) {
464
+ idx.errors = idx.errors.filter(e => idx.docs.some(d => d.path === e.path));
465
+ idx.warnings = idx.warnings.filter(w => idx.docs.some(d => d.path === w.path));
466
+ idx.countsByStatus = {};
467
+ for (const doc of idx.docs) {
468
+ const s = doc.status ?? 'unknown';
469
+ idx.countsByStatus[s] = (idx.countsByStatus[s] ?? 0) + 1;
470
+ }
471
+ }
451
472
  }
452
473
 
474
+ applyIndexFilters(index);
475
+
453
476
  if (rootFilter || typeFilter) {
454
- index.errors = index.errors.filter(e => index.docs.some(d => d.path === e.path));
455
- index.warnings = index.warnings.filter(w => index.docs.some(d => d.path === w.path));
456
- index.countsByStatus = {};
457
- for (const doc of index.docs) {
458
- const s = doc.status ?? 'unknown';
459
- index.countsByStatus[s] = (index.countsByStatus[s] ?? 0) + 1;
460
- }
461
477
  }
462
478
 
463
479
  if (verbose) {
@@ -500,6 +516,7 @@ async function main() {
500
516
  }
501
517
  // Show remaining issues
502
518
  const freshIndex = buildIndex(config);
519
+ applyIndexFilters(freshIndex);
503
520
  if (args.includes('--json')) {
504
521
  process.stdout.write(JSON.stringify({
505
522
  docsScanned: freshIndex.docs.length,
@@ -573,6 +590,10 @@ async function main() {
573
590
  if (command === 'focus') { runFocus(index, restArgs, config); return; }
574
591
  if (command === 'query') { runQuery(index, restArgs, config); return; }
575
592
  if (command === 'context') {
593
+ const summarize = args.includes('--summarize');
594
+ const modelIdx = args.indexOf('--model');
595
+ const model = modelIdx !== -1 && args[modelIdx + 1] ? args[modelIdx + 1] : undefined;
596
+
576
597
  if (args.includes('--json')) {
577
598
  const byStatus = {};
578
599
  for (const doc of index.docs) {
@@ -580,9 +601,36 @@ async function main() {
580
601
  if (!byStatus[s]) byStatus[s] = [];
581
602
  byStatus[s].push(doc);
582
603
  }
604
+ const byType = {};
605
+ for (const doc of index.docs) {
606
+ if (doc.type) {
607
+ if (!byType[doc.type]) byType[doc.type] = [];
608
+ byType[doc.type].push(doc);
609
+ }
610
+ }
611
+ if (summarize) {
612
+ const { summarizeDocBody } = await import('../src/ai.mjs');
613
+ const { extractFrontmatter } = await import('../src/frontmatter.mjs');
614
+ const { readFileSync } = await import('node:fs');
615
+ const limit = 5;
616
+ for (let i = 0; i < index.docs.length && i < limit; i++) {
617
+ try {
618
+ const absPath = path.resolve(config.repoRoot, index.docs[i].path);
619
+ const raw = readFileSync(absPath, 'utf8');
620
+ const { body } = extractFrontmatter(raw);
621
+ if (body?.trim()) {
622
+ const meta = { title: index.docs[i].title, status: index.docs[i].status, path: index.docs[i].path };
623
+ index.docs[i].aiSummary = config.hooks.summarizeDoc
624
+ ? config.hooks.summarizeDoc(body, meta)
625
+ : summarizeDocBody(body, meta, { model });
626
+ }
627
+ } catch { /* skip */ }
628
+ }
629
+ }
583
630
  const stale = index.docs.filter(d => d.isStale && !config.lifecycle.skipStaleFor.has(d.status));
584
631
  process.stdout.write(JSON.stringify({
585
632
  generatedAt: new Date().toISOString(),
633
+ docsByType: Object.keys(byType).length > 0 ? byType : undefined,
586
634
  docsByStatus: byStatus,
587
635
  countsByStatus: index.countsByStatus,
588
636
  stale: stale.map(d => ({ path: d.path, title: d.title, daysSinceUpdate: d.daysSinceUpdate })),
@@ -591,9 +639,6 @@ async function main() {
591
639
  }, null, 2) + '\n');
592
640
  return;
593
641
  }
594
- const summarize = args.includes('--summarize');
595
- const modelIdx = args.indexOf('--model');
596
- const model = modelIdx !== -1 && args[modelIdx + 1] ? args[modelIdx + 1] : undefined;
597
642
  process.stdout.write(renderContext(index, config, { summarize, model }));
598
643
  return;
599
644
  }
@@ -57,6 +57,7 @@ export const lifecycle = {
57
57
  archiveStatuses: ['archived'], // auto-move to archiveDir on transition
58
58
  skipStaleFor: ['archived'], // skip staleness checks
59
59
  skipWarningsFor: ['archived'], // skip validation warnings (summary, etc.)
60
+ terminalStatuses: ['archived', 'deprecated', 'reference', 'done'], // skip current_state/next_step warnings, exclude from stats scope
60
61
  };
61
62
 
62
63
  // Taxonomy validation — set fields to null to skip validation
@@ -107,7 +108,7 @@ export const presets = {
107
108
  // IMPORTANT: Use environment variables for tokens — never hardcode secrets in config files.
108
109
  // export const notion = {
109
110
  // token: process.env.NOTION_TOKEN,
110
- // databaseId: process.env.NOTION_DATABASE_ID,
111
+ // database: process.env.NOTION_DATABASE_ID,
111
112
  // };
112
113
 
113
114
  // ─── Function Hooks ──────────────────────────────────────────────────────────
@@ -123,20 +124,13 @@ export const presets = {
123
124
  // return { errors: [], warnings };
124
125
  // }
125
126
 
126
- // Override the context briefing output.
127
- // export function renderContext(index, defaultRenderer) {
128
- // return defaultRenderer(index);
129
- // }
130
-
131
- // Override the index file rendering.
132
- // export function renderIndex(index, defaultRenderer) {
133
- // return defaultRenderer(index);
134
- // }
135
-
136
- // Override the status snapshot display format.
137
- // export function formatSnapshot(doc, defaultFormatter) {
138
- // return defaultFormatter(doc);
139
- // }
127
+ // Render hooks override any renderer by wrapping the default.
128
+ // export function renderContext(index, defaultRenderer) { return defaultRenderer(index); }
129
+ // export function renderCompactList(index, defaultRenderer) { return defaultRenderer(index); }
130
+ // export function renderCheck(index, defaultRenderer) { return defaultRenderer(index); }
131
+ // export function renderStats(stats, defaultRenderer) { return defaultRenderer(stats); }
132
+ // export function renderGraph(graph, defaultRenderer) { return defaultRenderer(graph); }
133
+ // export function formatSnapshot(doc, defaultFormatter) { return defaultFormatter(doc); }
140
134
 
141
135
  // Post-parse doc transformation — add computed fields.
142
136
  // export function transformDoc(doc) {
@@ -147,3 +141,10 @@ export const presets = {
147
141
  // export function onArchive(doc, { oldPath, newPath }) {}
148
142
  // export function onStatusChange(doc, { oldStatus, newStatus, path }) {}
149
143
  // export function onTouch(doc, { path, date }) {}
144
+ // export function onNew({ path, status, title, template }) {}
145
+ // export function onRename({ oldPath, newPath, referencesUpdated }) {}
146
+ // export function onLint({ path, fixes }) {}
147
+
148
+ // AI hooks — override summarization (replaces local MLX model).
149
+ // export function summarizeDoc(body, meta) { return 'Custom summary'; }
150
+ // export function summarizeDiff(diffOutput, filePath) { return 'Custom diff summary'; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotmd-cli",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
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",
@@ -6,10 +6,10 @@ const COMMANDS = [
6
6
  'fix-refs', 'notion', 'export', 'summary', 'watch', 'diff', 'init', 'new', 'completions',
7
7
  ];
8
8
 
9
- const GLOBAL_FLAGS = ['--config', '--dry-run', '--verbose', '--root', '--help', '--version'];
9
+ const GLOBAL_FLAGS = ['--config', '--dry-run', '--verbose', '--root', '--type', '--help', '--version'];
10
10
 
11
11
  const COMMAND_FLAGS = {
12
- query: ['--status', '--keyword', '--module', '--surface', '--domain', '--owner',
12
+ query: ['--type', '--status', '--keyword', '--module', '--surface', '--domain', '--owner',
13
13
  '--updated-since', '--stale', '--has-next-step', '--has-blockers',
14
14
  '--checklist-open', '--sort', '--limit', '--all', '--git', '--json',
15
15
  '--summarize', '--summarize-limit', '--model'],
@@ -17,13 +17,13 @@ const COMMAND_FLAGS = {
17
17
  list: ['--verbose', '--json'],
18
18
  coverage: ['--json'],
19
19
  new: ['--status', '--title', '--template', '--list-templates', '--root'],
20
- diff: ['--stat', '--since', '--summarize', '--model', '--max-tokens'],
20
+ diff: ['--stat', '--since', '--summarize', '--model'],
21
21
  check: ['--errors-only', '--fix', '--json'],
22
22
  stats: ['--json'],
23
23
  graph: ['--dot', '--json', '--status', '--module', '--surface'],
24
24
  deps: ['--json', '--depth'],
25
25
  notion: ['import', 'export', 'sync', '--force', '--dry-run'],
26
- export: ['--format', '--output', '--status', '--module', '--root'],
26
+ export: ['--format', '--output', '--status', '--module', '--root', '--type'],
27
27
  focus: ['--json'],
28
28
  status: [],
29
29
  archive: [],
package/src/config.mjs CHANGED
@@ -43,6 +43,7 @@ const DEFAULTS = {
43
43
  archiveStatuses: ['archived'],
44
44
  skipStaleFor: ['archived', 'reference'],
45
45
  skipWarningsFor: ['archived'],
46
+ terminalStatuses: ['archived', 'deprecated', 'reference', 'done'],
46
47
  },
47
48
 
48
49
  taxonomy: {
@@ -284,6 +285,7 @@ export async function resolveConfig(cwd, explicitConfigPath) {
284
285
  const archiveStatuses = new Set(lifecycle.archiveStatuses);
285
286
  const skipStaleFor = new Set(lifecycle.skipStaleFor);
286
287
  const skipWarningsFor = new Set(lifecycle.skipWarningsFor);
288
+ const terminalStatuses = new Set(lifecycle.terminalStatuses);
287
289
 
288
290
  // Warn if rootStatuses keys don't match any configured root
289
291
  for (const rootKey of Object.keys(rootStatusesRaw)) {
@@ -315,7 +317,7 @@ export async function resolveConfig(cwd, explicitConfigPath) {
315
317
  rootValidStatuses,
316
318
  staleDaysByStatus,
317
319
 
318
- lifecycle: { archiveStatuses, skipStaleFor, skipWarningsFor },
320
+ lifecycle: { archiveStatuses, skipStaleFor, skipWarningsFor, terminalStatuses },
319
321
 
320
322
  validSurfaces,
321
323
  moduleRequiredStatuses,
package/src/export.mjs CHANGED
@@ -11,7 +11,8 @@ export function runExport(argv, config, opts = {}) {
11
11
  let output = null;
12
12
  let statusFilter = null;
13
13
  let moduleFilter = null;
14
- let rootFilter = null;
14
+ let rootFilter = opts.root ?? null;
15
+ let typeFilter = opts.type ?? null;
15
16
  const dryRun = opts.dryRun;
16
17
 
17
18
  for (let i = 0; i < argv.length; i++) {
@@ -20,6 +21,7 @@ export function runExport(argv, config, opts = {}) {
20
21
  if (argv[i] === '--status' && argv[i + 1]) { statusFilter = argv[++i]; continue; }
21
22
  if (argv[i] === '--module' && argv[i + 1]) { moduleFilter = argv[++i]; continue; }
22
23
  if (argv[i] === '--root' && argv[i + 1]) { rootFilter = argv[++i]; continue; }
24
+ if (argv[i] === '--type' && argv[i + 1]) { typeFilter = argv[++i]; continue; }
23
25
  if (argv[i] === '--config') { i++; continue; }
24
26
  if (argv[i].startsWith('-')) continue;
25
27
  positional.push(argv[i]);
@@ -54,6 +56,10 @@ export function runExport(argv, config, opts = {}) {
54
56
  if (rootFilter) {
55
57
  docs = docs.filter(d => d.root === rootFilter || d.root?.endsWith('/' + rootFilter) || d.root?.split('/').pop() === rootFilter);
56
58
  }
59
+ if (typeFilter) {
60
+ const types = typeFilter.split(',').map(t => t.trim()).filter(Boolean);
61
+ docs = docs.filter(d => types.includes(d.type));
62
+ }
57
63
  }
58
64
 
59
65
  if (docs.length === 0) {
package/src/init.mjs CHANGED
@@ -137,8 +137,7 @@ export function runInit(cwd) {
137
137
  process.stdout.write(` ${green('create')} docs/docs.md\n`);
138
138
  }
139
139
 
140
- const today = new Date().toISOString().slice(0, 10);
141
140
  process.stdout.write(`\nReady. Create your first doc:\n`);
142
- process.stdout.write(` printf '---\\nstatus: active\\nupdated: ${today}\\n---\\n\\n# My Doc\\n' > docs/my-doc.md\n`);
141
+ process.stdout.write(` dotmd new my-doc\n`);
143
142
  process.stdout.write(` dotmd list\n\n`);
144
143
  }
package/src/new.mjs CHANGED
@@ -27,7 +27,7 @@ const BUILTIN_TEMPLATES = {
27
27
  },
28
28
  audit: {
29
29
  description: 'Codebase audit or research investigation',
30
- frontmatter: (s, d) => `type: research\nstatus: active\nupdated: ${d}\naudited: ${d}\naudit_level: pass1\nmodule:\nsource_of_truth: code\nsupports_plans:`,
30
+ frontmatter: (s, d) => `type: research\nstatus: ${s}\nupdated: ${d}\naudited: ${d}\naudit_level: pass1\nmodule:\nsource_of_truth: code\nsupports_plans:`,
31
31
  body: (t) => `\n# ${t}\n\n## Scope\n\n\n\n## Findings\n\n\n\n## Recommendations\n\n\n`,
32
32
  },
33
33
  design: {
@@ -45,7 +45,7 @@ export async function runNew(argv, config, opts = {}) {
45
45
  let status = 'active';
46
46
  let title = null;
47
47
  let templateName = null;
48
- let rootName = null;
48
+ let rootName = opts.root ?? null;
49
49
  for (let i = 0; i < argv.length; i++) {
50
50
  if (argv[i] === '--status' && argv[i + 1]) { status = argv[++i]; continue; }
51
51
  if (argv[i] === '--title' && argv[i + 1]) { title = argv[++i]; continue; }
package/src/query.mjs CHANGED
@@ -9,7 +9,16 @@ import { summarizeDocBody } from './ai.mjs';
9
9
  import { dim } from './color.mjs';
10
10
 
11
11
  export function runFocus(index, argv, config) {
12
- const statusFilter = argv.find(a => !a.startsWith('-')) ?? 'active';
12
+ // Find first positional arg, skipping flag-value pairs like --root <name>
13
+ const FLAGS_WITH_VALUES = new Set(['--root']);
14
+ let statusFilter = 'active';
15
+ for (let i = 0; i < argv.length; i++) {
16
+ if (FLAGS_WITH_VALUES.has(argv[i])) { i++; continue; }
17
+ if (argv[i].startsWith('-')) continue;
18
+ statusFilter = argv[i];
19
+ break;
20
+ }
21
+
13
22
  const docs = index.docs.filter(doc => doc.status === statusFilter);
14
23
 
15
24
  if (argv.includes('--json')) {
package/src/render.mjs CHANGED
@@ -324,9 +324,8 @@ export function renderCoverage(index, config) {
324
324
  }
325
325
 
326
326
  export function buildCoverage(index, config) {
327
- const terminalStatuses = new Set(['archived', 'deprecated', 'reference', 'done']);
328
- const scope = [...new Set(index.docs.map(d => d.status).filter(s => s && !terminalStatuses.has(s) && !config.lifecycle.skipWarningsFor.has(s)))];
329
- const scoped = index.docs.filter(doc => doc.status && !terminalStatuses.has(doc.status) && !config.lifecycle.skipWarningsFor.has(doc.status));
327
+ const scope = [...new Set(index.docs.map(d => d.status).filter(s => s && !config.lifecycle.terminalStatuses.has(s) && !config.lifecycle.skipWarningsFor.has(s)))];
328
+ const scoped = index.docs.filter(doc => doc.status && !config.lifecycle.terminalStatuses.has(doc.status) && !config.lifecycle.skipWarningsFor.has(doc.status));
330
329
  const missingSurface = scoped.filter(doc => !doc.surface);
331
330
  const missingModule = scoped.filter(doc => !doc.module);
332
331
  const modulePlatform = scoped.filter(doc => doc.module === 'platform');
package/src/stats.mjs CHANGED
@@ -8,7 +8,12 @@ function pct(n, total) {
8
8
 
9
9
  export function buildStats(index, config) {
10
10
  const docs = index.docs;
11
- const scope = ['active', 'ready', 'planned', 'blocked'];
11
+ const scope = config.statusOrder.filter(s => !config.lifecycle.terminalStatuses.has(s) && !config.lifecycle.skipWarningsFor.has(s));
12
+ for (const typeSet of (config.typeStatuses?.values() ?? [])) {
13
+ for (const s of typeSet) {
14
+ if (!config.lifecycle.terminalStatuses.has(s) && !config.lifecycle.skipWarningsFor.has(s) && !scope.includes(s)) scope.push(s);
15
+ }
16
+ }
12
17
  const scoped = docs.filter(d => scope.includes(d.status));
13
18
  const nonArchived = docs.filter(d => !config.lifecycle.skipWarningsFor.has(d.status));
14
19
 
package/src/validate.mjs CHANGED
@@ -80,8 +80,7 @@ export function validateDoc(doc, frontmatter, headingTitle, config) {
80
80
  }
81
81
 
82
82
  // Determine which statuses should have current_state and next_step
83
- const terminalStatuses = new Set(['archived', 'deprecated', 'reference', 'done']);
84
- const isWorkStatus = knownStatus && doc.status && !terminalStatuses.has(doc.status) && !config.lifecycle.skipWarningsFor.has(doc.status);
83
+ const isWorkStatus = knownStatus && doc.status && !config.lifecycle.terminalStatuses.has(doc.status) && !config.lifecycle.skipWarningsFor.has(doc.status);
85
84
 
86
85
  if (isWorkStatus && !asString(frontmatter.current_state)) {
87
86
  doc.warnings.push({ path: doc.path, level: 'warning', message: 'Missing `current_state`; index output is using a fallback or placeholder.' });