dotmd-cli 0.15.0 → 0.16.0

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
@@ -244,8 +244,30 @@ Shows: status counts, staleness, errors/warnings, freshness (today/week/month),
244
244
  ```bash
245
245
  dotmd doctor # fix refs → lint → sync git dates → regen index
246
246
  dotmd doctor --dry-run # preview all changes
247
+ dotmd doctor --statuses # detect overloaded status buckets (read-only)
248
+ dotmd doctor --statuses --json # machine-readable suggestions
247
249
  ```
248
250
 
251
+ `--statuses` is a read-only diagnostic. It scans each status with at least
252
+ 10 plans and groups their `current_state` / `next_step` text against cue
253
+ keywords for `partial`, `paused`, `awaiting`, `queued-after`, and `blocked`.
254
+ When a single bucket lands plans in two or more cue groups (each above 15%
255
+ of the bucket), it prints a split suggestion:
256
+
257
+ ```
258
+ 47 plan/backlog plans cluster across 4 patterns — consider splitting:
259
+ ~22 → partial (cues: "shipped", "landed", "tail", "deferred")
260
+ ~15 → paused (cues: "paused", "on hold", "set aside")
261
+ ~ 6 → queued-after (cues: "after", "once", "depends on", "waiting on <plan>")
262
+ ~ 4 → (kept in backlog — no clear pattern match)
263
+
264
+ Heuristic — verify before migrating.
265
+ ```
266
+
267
+ The heuristic is intentionally conservative: small buckets are skipped, plans
268
+ that match no cues stay in the original bucket, and the output is always a
269
+ suggestion — never a verdict.
270
+
249
271
  ### Graph
250
272
 
251
273
  ```bash
@@ -401,6 +423,34 @@ dotmd lint # report issues
401
423
  dotmd lint --fix # fix all issues
402
424
  ```
403
425
 
426
+ ### Manage Statuses
427
+
428
+ ```bash
429
+ dotmd statuses # table view, all types
430
+ dotmd statuses --type plan # one type
431
+ dotmd statuses --json # machine-readable
432
+
433
+ dotmd statuses add paused --type plan --like blocked --quiet # clone blocked, then quiet
434
+ dotmd statuses set archived --type plan --no-quiet # tweak a flag
435
+ dotmd statuses remove obsolete --type plan # refuses if any docs use it
436
+
437
+ dotmd statuses migrate plan # array-form → rich-form
438
+ ```
439
+
440
+ `--like <existing>` is the affordance for "kinda like X but…" — clones every
441
+ flag from another status, then user flags override. Write commands print a flag
442
+ diff and prompt for confirmation; pass `--yes` to skip the prompt or
443
+ `--dry-run` to preview without writing. Edits are atomic: the rewrite lands in
444
+ a sibling temp file, is validated by re-importing it and running
445
+ `resolveConfig`, then renamed into place — a syntax error or new warning
446
+ leaves the original untouched.
447
+
448
+ **Lifecycle-override gotcha.** If your config has both rich-form `types` and an
449
+ explicit `export const lifecycle = {...}`, the explicit lifecycle silently
450
+ overrides per-status flags at runtime. `dotmd statuses` write commands refuse
451
+ to write into that state and recommend deleting the explicit `lifecycle` block;
452
+ pass `--ignore-lifecycle-override` to write anyway.
453
+
404
454
  ### Rename
405
455
 
406
456
  ```bash
@@ -412,8 +462,20 @@ dotmd rename old-name.md new-name # renames + updates refs
412
462
  ```bash
413
463
  dotmd migrate status research scoping # rename a status (e.g. for the 0.15 default-vocab change)
414
464
  dotmd migrate module auth identity # rename a module
465
+
466
+ # Per-file form: split one overloaded status into several distinct ones.
467
+ # Only the listed files are rewritten; every other doc with the old value is left alone.
468
+ dotmd migrate status backlog paused docs/plans/foo.md docs/plans/bar.md
469
+ dotmd migrate status backlog partial docs/plans/payments-future.md # one at a time also works
415
470
  ```
416
471
 
472
+ With no file args, `migrate` rewrites every doc whose field matches
473
+ `<old-value>` (whole-bucket rename). Pass file args to scope the
474
+ rewrite — useful when one status has been doing several jobs and you
475
+ want to split it across the new vocabulary. File args match the same
476
+ way as `bulk archive`: exact path first, then substring fallback
477
+ against full path or basename.
478
+
417
479
  ### Preset Aliases
418
480
 
419
481
  Built-in presets: `plans`, `stale`, `actionable`. Add your own in config:
package/bin/dotmd.mjs CHANGED
@@ -49,7 +49,7 @@ Lifecycle:
49
49
  touch <file> Bump updated date
50
50
  touch --git Bulk-sync dates from git history
51
51
  rename <old> <new> Rename doc and update all references
52
- migrate <field> <old> <new> Batch update a frontmatter field value
52
+ migrate <field> <old> <new> [f...]Batch update a frontmatter field value (optional file filter)
53
53
 
54
54
  Create & Export:
55
55
  new <name> [--template <t>] Create doc from template (plan, adr, rfc, audit, design)
@@ -59,6 +59,7 @@ Create & Export:
59
59
 
60
60
  Setup:
61
61
  init Create starter config + docs directory
62
+ statuses [list|add|set|remove|migrate] Manage per-project status taxonomy
62
63
  watch [command] Re-run a command on file changes
63
64
  completions <shell> Shell completion script (bash, zsh)
64
65
 
@@ -236,6 +237,16 @@ Options:
236
237
  Runs in sequence: fix broken references, lint --fix, sync dates from
237
238
  git, regenerate index, then show remaining issues.
238
239
 
240
+ Modes:
241
+ (default) Auto-fix pass (writes by default; honors --dry-run)
242
+ --statuses Read-only diagnostic: detect overloaded status
243
+ buckets where one status holds plans pursuing
244
+ multiple distinct unstuck-actions. Suggests how
245
+ a bucket might split (e.g. backlog → partial /
246
+ paused / queued-after). Heuristic only — verify
247
+ before migrating.
248
+ --statuses --json Machine-readable suggestion shape for tooling.
249
+
239
250
  Use --dry-run (-n) to preview all changes without writing anything.`,
240
251
 
241
252
  'fix-refs': `dotmd fix-refs — auto-fix broken reference paths
@@ -359,14 +370,22 @@ in other docs that point to the old filename.
359
370
  Body markdown links are warned about but not auto-fixed.
360
371
  Use --dry-run (-n) to preview changes without writing anything.`,
361
372
 
362
- migrate: `dotmd migrate <field> <old-value> <new-value> — batch update a frontmatter field
373
+ migrate: `dotmd migrate <field> <old-value> <new-value> [files...] — batch update a frontmatter field
363
374
 
364
375
  Finds all docs where the given field equals old-value and updates it
365
- to new-value.
376
+ to new-value. With no file args, every matching doc in the project is
377
+ rewritten (whole-bucket rename).
378
+
379
+ Pass one or more file args to scope the rewrite — only those files
380
+ are considered. This is how you split one overloaded status into
381
+ several distinct ones (e.g. moving some \`backlog\` plans to
382
+ \`paused\` and others to \`partial\`). File args use the same matching
383
+ as \`bulk archive\`: exact path, then substring fallback.
366
384
 
367
385
  Examples:
368
386
  dotmd migrate status research scoping
369
387
  dotmd migrate module auth identity
388
+ dotmd migrate status backlog paused docs/plans/foo.md docs/plans/bar.md
370
389
 
371
390
  Use --dry-run (-n) to preview changes without writing anything.`,
372
391
 
@@ -431,6 +450,53 @@ Options:
431
450
  --list List all glossary terms
432
451
  --json Output as JSON`,
433
452
 
453
+ statuses: `dotmd statuses — manage per-project status taxonomy
454
+
455
+ Subcommands:
456
+ list [--type <t>] [--json] Default. Table view of every status × type with all flags.
457
+ --type accepts comma-separated types.
458
+ add <name> --type <t> [--like <e>] [flags...]
459
+ Add a new status. --like <existing> clones every flag from
460
+ another status; user flags override. Inserts before the
461
+ first terminal/archive status. Refuses if name already
462
+ exists or is invalid.
463
+ set <name> --type <t> <flags...> Edit flags on an existing status. Refuses if status doesn't
464
+ exist. Flags overwrite individually.
465
+ remove <name> --type <t> Delete a status entry. Refuses if any docs use the status
466
+ (lists offenders, suggests \`dotmd migrate\`). Warns if an
467
+ explicit lifecycle export references the name.
468
+ migrate <type> One-shot conversion of array-form types.<t>.statuses to
469
+ rich form, pulling in peer staleDays/context and per-status
470
+ requiresModule from taxonomy.moduleRequiredFor.
471
+
472
+ Flags accepted by add/set:
473
+ --context <expanded|listed|counted> Briefing layout bucket
474
+ --staleDays <n|null> Stale threshold; null = never stale
475
+ --requiresModule / --no-requiresModule
476
+ --terminal / --no-terminal Closure state — excluded from active-work scope
477
+ --archive / --no-archive Auto-move to archive dir on transition
478
+ --skipStale / --no-skipStale
479
+ --skipWarnings / --no-skipWarnings
480
+ --quiet / --no-quiet Sugar for skipStale + skipWarnings (explicit overrides win)
481
+
482
+ Workflow flags:
483
+ --yes Skip the confirmation prompt
484
+ --dry-run, -n Show the diff without writing
485
+ --ignore-lifecycle-override Write even when an explicit \`lifecycle\` export
486
+ would silently mask the per-status flags
487
+
488
+ Examples:
489
+ dotmd statuses # list everything
490
+ dotmd statuses add paused --type plan --like blocked --quiet
491
+ dotmd statuses set archived --type plan --no-quiet
492
+ dotmd statuses remove obsolete --type plan
493
+ dotmd statuses migrate plan # array → rich
494
+
495
+ Lifecycle-override gotcha: if your config has both rich-form types and an explicit
496
+ \`export const lifecycle\`, the runtime ignores per-status flags. The CLI refuses
497
+ to write in that case unless you pass --ignore-lifecycle-override; the recommended
498
+ fix is to delete the explicit \`lifecycle\` block so flags take effect.`,
499
+
434
500
  bulk: `dotmd bulk archive <f1> <f2> ... — archive multiple files at once
435
501
 
436
502
  Archives each file: sets status to archived, moves to archive
@@ -551,6 +617,7 @@ async function main() {
551
617
  if (command === 'migrate') { const { runMigrate } = await import('../src/migrate.mjs'); runMigrate(restArgs, config, { dryRun }); return; }
552
618
  if (command === 'fix-refs') { const { runFixRefs } = await import('../src/fix-refs.mjs'); runFixRefs(restArgs, config, { dryRun }); return; }
553
619
  if (command === 'doctor') { const { runDoctor } = await import('../src/doctor.mjs'); runDoctor(restArgs, config, { dryRun }); return; }
620
+ if (command === 'statuses') { const { runStatuses } = await import('../src/statuses.mjs'); await runStatuses(restArgs, config, { dryRun, type: typeArg }); return; }
554
621
 
555
622
  // All remaining commands need the index + render modules
556
623
  const { buildIndex } = await import('../src/index.mjs');
@@ -801,7 +868,7 @@ async function main() {
801
868
  'focus', 'query', 'plans', 'stale', 'actionable', 'index', 'pickup', 'finish', 'status', 'archive', 'bulk', 'touch', 'doctor',
802
869
  'unblocks', 'health', 'glossary',
803
870
  'fix-refs', 'lint', 'rename', 'migrate', 'notion', 'export', 'summary',
804
- 'watch', 'diff', 'new', 'init', 'completions',
871
+ 'watch', 'diff', 'new', 'init', 'completions', 'statuses',
805
872
  ];
806
873
  const matches = allCommands
807
874
  .map(c => ({ cmd: c, dist: levenshtein(command, c) }))
@@ -103,13 +103,20 @@ export const statuses = {
103
103
  };
104
104
 
105
105
  // Lifecycle behavior — which statuses trigger special handling.
106
- // When using rich status definitions, these are derived from per-status flags.
107
- export const lifecycle = {
108
- archiveStatuses: ['archived'], // auto-move to archiveDir on transition
109
- skipStaleFor: ['archived'], // skip staleness checks
110
- skipWarningsFor: ['archived'], // skip validation warnings (summary, etc.)
111
- terminalStatuses: ['archived', 'deprecated', 'reference', 'done'], // skip current_state/next_step warnings, exclude from stats scope
112
- };
106
+ //
107
+ // IMPORTANT: only enable this block if you're using ARRAY-form types (above) or
108
+ // no `types` definition. With rich-form types, the runtime derives these from
109
+ // per-status `terminal` / `archive` / `skipStale` / `skipWarnings` / `quiet`
110
+ // flags. An explicit `lifecycle` export sitting alongside rich-form types will
111
+ // SILENTLY OVERRIDE the per-status flags `dotmd statuses` will warn you
112
+ // before writing into a config in that state.
113
+ //
114
+ // export const lifecycle = {
115
+ // archiveStatuses: ['archived'], // auto-move to archiveDir on transition
116
+ // skipStaleFor: ['archived'], // skip staleness checks
117
+ // skipWarningsFor: ['archived'], // skip validation warnings (summary, etc.)
118
+ // terminalStatuses: ['archived', 'deprecated', 'reference'], // skip current_state/next_step warnings, exclude from stats scope
119
+ // };
113
120
 
114
121
  // Taxonomy validation — set fields to null to skip validation.
115
122
  // moduleRequiredFor is derived from requiresModule when using rich status definitions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotmd-cli",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
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",