dotmd-cli 0.14.12 → 0.15.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
@@ -88,7 +88,7 @@ Every document can have a `type` field in its frontmatter. Types determine which
88
88
 
89
89
  | Type | Purpose | Valid Statuses |
90
90
  |------|---------|----------------|
91
- | `plan` | Execution plans | `in-session`, `active`, `planned`, `blocked`, `done`, `archived` |
91
+ | `plan` | Execution plans | `in-session`, `active`, `planned`, `blocked`, `partial`, `paused`, `awaiting`, `queued-after`, `archived` |
92
92
  | `doc` | Design docs, specs, ADRs, RFCs | `draft`, `active`, `review`, `reference`, `deprecated`, `archived` |
93
93
  | `research` | Investigations, audits, analysis | `active`, `reference`, `archived` |
94
94
 
@@ -106,6 +106,26 @@ dotmd export --type research # export research only
106
106
 
107
107
  Customize types and their statuses in config with the `types` key. See [`dotmd.config.example.mjs`](dotmd.config.example.mjs).
108
108
 
109
+ ### What each plan status means
110
+
111
+ The default plan vocabulary is shaped around the **unstuck-action test**: every stop-status should map to a distinct next move. If two statuses have the same unstuck-action, one is dead weight; if a single status covers several different actions, it's overloaded.
112
+
113
+ | Status | Unstuck-action | When to use |
114
+ |--------|----------------|-------------|
115
+ | `in-session` | — | A Claude session is working on it right now. Don't pick up. |
116
+ | `active` | Pick up | Ready to be worked on. |
117
+ | `planned` | Wait for trigger | Queued; not yet ready to execute. |
118
+ | `blocked` | **Monitor** | External arrival on its own schedule (hardware, vendor, third-party rollout). You can't speed it up. |
119
+ | `partial` | **Spawn successors** | Shipped most of the plan; tail deferred. Body should reference successor plans tracking the tail. Visible but quiet (no nagging). |
120
+ | `paused` | **Re-evaluate** | Intentionally set aside, no external dependency. Resume by deciding the work is still worth doing. Quiet. |
121
+ | `awaiting` | **Ask** | Needs a human decision or input. NOT quiet — pings get forgotten, so this status generates stale pressure to chase the answer. |
122
+ | `queued-after` | **Check predecessor** | Sequenced behind another plan; can start once that one ships. Quiet. |
123
+ | `archived` | — | No longer relevant; auto-moved to the archive directory on transition. |
124
+
125
+ Each *quiet* status (`partial`, `paused`, `queued-after`, `archived`) is exempt from stale-warning pressure but still appears in active scope and metrics — quietness is a presentation flag, not a closure flag. `awaiting` deliberately stays loud so unanswered questions don't decay into invisible backlog.
126
+
127
+ > **Heads-up:** versions before 0.15 included a `done` plan status in the defaults. It saw effectively zero real-world use (plans went `in-session`/`active` → `archived` directly), so it was dropped from the built-in vocabulary. To finish a plan, run `dotmd archive <plan-file>` — or, if you preferred the previous behavior, add `done` back via the `types.plan.statuses` key in your config.
128
+
109
129
  ## Commands
110
130
 
111
131
  ```
@@ -390,8 +410,8 @@ dotmd rename old-name.md new-name # renames + updates refs
390
410
  ### Migrate
391
411
 
392
412
  ```bash
393
- dotmd migrate status research exploration # rename a status
394
- dotmd migrate module auth identity # rename a module
413
+ dotmd migrate status research scoping # rename a status (e.g. for the 0.15 default-vocab change)
414
+ dotmd migrate module auth identity # rename a module
395
415
  ```
396
416
 
397
417
  ### Preset Aliases
package/bin/dotmd.mjs CHANGED
@@ -140,6 +140,17 @@ Moves the document to the new status. If transitioning to an archive
140
140
  status, automatically moves the file to the archive directory and
141
141
  regenerates the index (if configured).
142
142
 
143
+ Default plan statuses (each maps to a distinct unstuck-action):
144
+ in-session A Claude session is working on it now
145
+ active Ready to be picked up
146
+ planned Queued for future work
147
+ blocked External arrival wait — monitor (hardware, vendor, rollout)
148
+ partial Shipped + deferred tail — spawn successor plans
149
+ paused Intentionally set aside — re-evaluate to resume
150
+ awaiting Needs human input/decision — chase the answer
151
+ queued-after Sequenced behind another plan — check predecessor
152
+ archived No longer relevant; auto-moved to archive directory
153
+
143
154
  Use --dry-run (-n) to preview changes without writing anything.`,
144
155
 
145
156
  check: `dotmd check — validate frontmatter and references
@@ -262,6 +273,10 @@ Options:
262
273
  --root <name> Create in a specific docs root
263
274
  --list-templates Show available templates
264
275
 
276
+ For plans, the default status vocabulary is: in-session, active, planned,
277
+ blocked, partial, paused, awaiting, queued-after, archived. Run
278
+ \`dotmd status --help\` for what each one means.
279
+
265
280
  The filename is derived from <name> by slugifying it.
266
281
  Use --dry-run (-n) to preview without creating the file.`,
267
282
 
@@ -350,7 +365,7 @@ Finds all docs where the given field equals old-value and updates it
350
365
  to new-value.
351
366
 
352
367
  Examples:
353
- dotmd migrate status research exploration
368
+ dotmd migrate status research scoping
354
369
  dotmd migrate module auth identity
355
370
 
356
371
  Use --dry-run (-n) to preview changes without writing anything.`,
@@ -368,12 +383,18 @@ modules, and reference fields to pre-populate the config.`,
368
383
  Shows all documents with type: plan, sorted by status.
369
384
  Supports all query flags (--status, --module, --json, --sort, --group, etc.)
370
385
 
386
+ Default plan statuses: in-session, active, planned, blocked, partial,
387
+ paused, awaiting, queued-after, archived. Run \`dotmd status --help\` for
388
+ the unstuck-action behind each one.
389
+
371
390
  Examples:
372
- dotmd plans # all plans
373
- dotmd plans --status active # active plans only
374
- dotmd plans --module auth # plans for the auth module
375
- dotmd plans --group module # all plans grouped by module
376
- dotmd plans --json # JSON output`,
391
+ dotmd plans # all plans
392
+ dotmd plans --status active # active plans only
393
+ dotmd plans --status awaiting # plans waiting on a human decision
394
+ dotmd plans --status partial,paused # shipped-tail and parked plans
395
+ dotmd plans --module auth # plans for the auth module
396
+ dotmd plans --group module # all plans grouped by module
397
+ dotmd plans --json # JSON output`,
377
398
 
378
399
  stale: `dotmd stale — list stale documents
379
400
 
@@ -25,20 +25,35 @@ export const excludeDirs = ['evidence'];
25
25
  // context: 'expanded' | 'listed' | 'counted' (default: 'counted')
26
26
  // staleDays: number | null — stale threshold (default: null = never stale)
27
27
  // requiresModule: boolean — require `module` frontmatter (default: false)
28
- // terminal: boolean — skip current_state/next_step warnings (default: false)
29
28
  // archive: boolean — auto-move to archiveDir on transition (default: false)
29
+ // terminal: boolean — closed state; excluded from active-work stats/coverage scope (default: false)
30
30
  // skipStale: boolean — exempt from stale checks (default: false)
31
31
  // skipWarnings: boolean — exempt from validation warnings (default: false)
32
+ // quiet: boolean — sugar for `skipStale: true, skipWarnings: true`. Use for visible-but-quiet
33
+ // statuses (e.g. `partial`) where you want no nagging but DO want them in scope.
34
+ // Setting `skipStale: false` or `skipWarnings: false` explicitly overrides the sugar.
35
+ //
36
+ // `terminal` and `quiet` are orthogonal. Mark a status `terminal` only when it represents closure
37
+ // (excluded from active-work scope). Use `quiet` for noise suppression without closure semantics.
38
+ //
39
+ // Each plan stop-status maps to a distinct unstuck-action — the test for whether
40
+ // the vocabulary earns its weight. blocked = monitor (external arrival on its own
41
+ // schedule), awaiting = ask (chase the human/decision), queued-after = check the
42
+ // predecessor, paused = re-evaluate, partial = spawn successor plans for the
43
+ // deferred tail.
32
44
  //
33
45
  // export const types = {
34
46
  // plan: {
35
47
  // statuses: {
36
- // 'in-session': { context: 'expanded', staleDays: 1, requiresModule: true },
37
- // 'active': { context: 'expanded', staleDays: 14, requiresModule: true },
38
- // 'planned': { context: 'listed', staleDays: 30, requiresModule: true },
39
- // 'blocked': { context: 'listed', staleDays: 30, requiresModule: true, skipStale: true },
40
- // 'done': { context: 'counted', terminal: true, skipStale: true, skipWarnings: true },
41
- // 'archived': { context: 'counted', archive: true, terminal: true, skipStale: true, skipWarnings: true },
48
+ // 'in-session': { context: 'expanded', staleDays: 1, requiresModule: true },
49
+ // 'active': { context: 'expanded', staleDays: 14, requiresModule: true },
50
+ // 'planned': { context: 'listed', staleDays: 30, requiresModule: true },
51
+ // 'blocked': { context: 'listed', staleDays: 30, requiresModule: true },
52
+ // 'partial': { context: 'expanded', requiresModule: true, quiet: true }, // shipped + deferred tail; visible, no nagging
53
+ // 'paused': { context: 'listed', requiresModule: true, quiet: true }, // intentionally set aside, no external dep
54
+ // 'awaiting': { context: 'listed', staleDays: 14, requiresModule: true }, // human input/decision wait — NOT quiet (pings get forgotten)
55
+ // 'queued-after': { context: 'counted', requiresModule: true, quiet: true }, // sequenced behind another plan
56
+ // 'archived': { context: 'counted', archive: true, terminal: true, quiet: true },
42
57
  // },
43
58
  // },
44
59
  // doc: {
@@ -48,7 +63,7 @@ export const excludeDirs = ['evidence'];
48
63
  // 'review': { context: 'listed', staleDays: 14 },
49
64
  // 'reference': { context: 'counted', skipStale: true },
50
65
  // 'deprecated': { context: 'counted', terminal: true, skipStale: true },
51
- // 'archived': { context: 'counted', archive: true, terminal: true, skipStale: true, skipWarnings: true },
66
+ // 'archived': { context: 'counted', archive: true, terminal: true, quiet: true },
52
67
  // },
53
68
  // },
54
69
  // };
@@ -57,16 +72,20 @@ export const excludeDirs = ['evidence'];
57
72
  // When using array form, define behavior in separate statuses/lifecycle/taxonomy sections.
58
73
  // export const types = {
59
74
  // plan: {
60
- // statuses: ['in-session', 'active', 'planned', 'blocked', 'done', 'archived'],
61
- // context: { expanded: ['in-session', 'active'], listed: ['planned', 'blocked'], counted: ['done', 'archived'] },
62
- // staleDays: { 'in-session': 1, active: 14, planned: 30, blocked: 30 },
75
+ // statuses: ['in-session', 'active', 'planned', 'blocked', 'partial', 'paused', 'awaiting', 'queued-after', 'archived'],
76
+ // context: {
77
+ // expanded: ['in-session', 'active', 'partial'],
78
+ // listed: ['planned', 'blocked', 'paused', 'awaiting'],
79
+ // counted: ['queued-after', 'archived'],
80
+ // },
81
+ // staleDays: { 'in-session': 1, active: 14, planned: 30, blocked: 30, awaiting: 14 },
63
82
  // },
64
83
  // };
65
84
 
66
85
  // Status workflow — fallback for docs without a type field. Order determines display grouping.
67
86
  // When using rich status definitions, statuses.order and staleDays are derived automatically.
68
87
  export const statuses = {
69
- order: ['active', 'ready', 'planned', 'research', 'blocked', 'reference', 'archived'],
88
+ order: ['active', 'ready', 'planned', 'scoping', 'blocked', 'reference', 'archived'],
70
89
  // Additional statuses valid only in specific roots (merged with order)
71
90
  // Useful when different doc areas track different things (e.g. plans vs module docs)
72
91
  // rootStatuses: {
@@ -79,7 +98,7 @@ export const statuses = {
79
98
  ready: 14,
80
99
  planned: 30,
81
100
  blocked: 30,
82
- research: 30,
101
+ scoping: 30,
83
102
  },
84
103
  };
85
104
 
@@ -111,7 +130,7 @@ export const index = {
111
130
  export const context = {
112
131
  expanded: ['active'],
113
132
  listed: ['ready', 'planned'],
114
- counted: ['blocked', 'research', 'reference', 'archived'],
133
+ counted: ['blocked', 'scoping', 'reference', 'archived'],
115
134
  recentDays: 3,
116
135
  recentStatuses: ['active', 'ready', 'planned'],
117
136
  recentLimit: 10,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotmd-cli",
3
- "version": "0.14.12",
3
+ "version": "0.15.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",
package/src/config.mjs CHANGED
@@ -22,9 +22,13 @@ const DEFAULTS = {
22
22
 
23
23
  types: {
24
24
  plan: {
25
- statuses: ['in-session', 'active', 'planned', 'blocked', 'done', 'archived'],
26
- context: { expanded: ['in-session', 'active'], listed: ['planned', 'blocked'], counted: ['done', 'archived'] },
27
- staleDays: { 'in-session': 1, active: 14, planned: 30, blocked: 30 },
25
+ statuses: ['in-session', 'active', 'planned', 'blocked', 'partial', 'paused', 'awaiting', 'queued-after', 'archived'],
26
+ context: {
27
+ expanded: ['in-session', 'active', 'partial'],
28
+ listed: ['planned', 'blocked', 'paused', 'awaiting'],
29
+ counted: ['queued-after', 'archived'],
30
+ },
31
+ staleDays: { 'in-session': 1, active: 14, planned: 30, blocked: 30, awaiting: 14 },
28
32
  },
29
33
  doc: {
30
34
  statuses: ['draft', 'active', 'review', 'reference', 'deprecated', 'archived'],
@@ -39,26 +43,26 @@ const DEFAULTS = {
39
43
  },
40
44
 
41
45
  statuses: {
42
- order: ['active', 'ready', 'planned', 'research', 'blocked', 'reference', 'archived'],
46
+ order: ['active', 'ready', 'planned', 'scoping', 'blocked', 'reference', 'archived'],
43
47
  staleDays: {
44
48
  active: 14,
45
49
  ready: 14,
46
50
  planned: 30,
47
51
  blocked: 30,
48
- research: 30,
52
+ scoping: 30,
49
53
  },
50
54
  },
51
55
 
52
56
  lifecycle: {
53
57
  archiveStatuses: ['archived'],
54
- skipStaleFor: ['archived', 'reference'],
55
- skipWarningsFor: ['archived'],
56
- terminalStatuses: ['archived', 'deprecated', 'reference', 'done'],
58
+ skipStaleFor: ['archived', 'reference', 'partial', 'paused', 'queued-after'],
59
+ skipWarningsFor: ['archived', 'partial', 'paused', 'queued-after'],
60
+ terminalStatuses: ['archived', 'deprecated', 'reference'],
57
61
  },
58
62
 
59
63
  taxonomy: {
60
64
  surfaces: null,
61
- moduleRequiredFor: [],
65
+ moduleRequiredFor: ['partial', 'paused', 'awaiting', 'queued-after'],
62
66
  },
63
67
 
64
68
  index: null,
@@ -66,7 +70,7 @@ const DEFAULTS = {
66
70
  context: {
67
71
  expanded: ['active'],
68
72
  listed: ['ready', 'planned'],
69
- counted: ['blocked', 'research', 'reference', 'archived'],
73
+ counted: ['blocked', 'scoping', 'reference', 'archived'],
70
74
  recentDays: 3,
71
75
  recentStatuses: ['active', 'ready', 'planned'],
72
76
  recentLimit: 10,
@@ -92,7 +96,7 @@ const DEFAULTS = {
92
96
 
93
97
  presets: {
94
98
  plans: ['--type', 'plan', '--sort', 'status', '--all'],
95
- stale: ['--status', 'active,ready,planned,blocked,research', '--stale', '--sort', 'updated', '--all'],
99
+ stale: ['--status', 'active,ready,planned,blocked,scoping', '--stale', '--sort', 'updated', '--all'],
96
100
  actionable: ['--status', 'active,ready', '--has-next-step', '--sort', 'updated', '--all'],
97
101
  },
98
102
  };
@@ -147,9 +151,13 @@ function normalizeRichStatuses(config, userConfig) {
147
151
  derived.staleDays[name] = p.staleDays;
148
152
  }
149
153
 
154
+ // `quiet: true` is sugar for skipStale + skipWarnings unless those are explicitly false.
155
+ const quietImpliesSkipStale = p.quiet && p.skipStale !== false;
156
+ const quietImpliesSkipWarnings = p.quiet && p.skipWarnings !== false;
157
+
150
158
  if (p.archive && !derived.archiveStatuses.includes(name)) derived.archiveStatuses.push(name);
151
- if (p.skipStale && !derived.skipStaleFor.includes(name)) derived.skipStaleFor.push(name);
152
- if (p.skipWarnings && !derived.skipWarningsFor.includes(name)) derived.skipWarningsFor.push(name);
159
+ if ((p.skipStale || quietImpliesSkipStale) && !derived.skipStaleFor.includes(name)) derived.skipStaleFor.push(name);
160
+ if ((p.skipWarnings || quietImpliesSkipWarnings) && !derived.skipWarningsFor.includes(name)) derived.skipWarningsFor.push(name);
153
161
  if (p.terminal && !derived.terminalStatuses.includes(name)) derived.terminalStatuses.push(name);
154
162
  if (p.requiresModule && !derived.moduleRequiredFor.includes(name)) derived.moduleRequiredFor.push(name);
155
163
 
package/src/health.mjs CHANGED
@@ -76,7 +76,7 @@ export function runHealth(argv, config) {
76
76
 
77
77
  // Pipeline
78
78
  process.stdout.write(bold('Pipeline:') + '\n');
79
- const pipeline = ['active', 'paused', 'ready', 'planned', 'blocked', 'research', 'archived'];
79
+ const pipeline = ['active', 'paused', 'ready', 'planned', 'blocked', 'scoping', 'archived'];
80
80
  for (const s of pipeline) {
81
81
  const count = byStatus[s] || 0;
82
82
  if (count > 0) {
package/src/init.mjs CHANGED
@@ -68,7 +68,7 @@ function generateDetectedConfig(scan, rootPath) {
68
68
  lines.push(`export const root = '${rootPath}';`);
69
69
  lines.push('');
70
70
 
71
- const defaultOrder = ['active', 'ready', 'planned', 'research', 'blocked', 'reference', 'archived'];
71
+ const defaultOrder = ['active', 'ready', 'planned', 'scoping', 'blocked', 'reference', 'archived'];
72
72
  const ordered = defaultOrder.filter(s => scan.statuses.has(s));
73
73
  const extra = [...scan.statuses].filter(s => !defaultOrder.includes(s)).sort();
74
74
  const allStatuses = [...ordered, ...extra];
package/src/render.mjs CHANGED
@@ -368,8 +368,8 @@ export function renderCoverage(index, config) {
368
368
  }
369
369
 
370
370
  export function buildCoverage(index, config) {
371
- const scope = [...new Set(index.docs.map(d => d.status).filter(s => s && !config.lifecycle.terminalStatuses.has(s) && !config.lifecycle.skipWarningsFor.has(s)))];
372
- const scoped = index.docs.filter(doc => doc.status && !config.lifecycle.terminalStatuses.has(doc.status) && !config.lifecycle.skipWarningsFor.has(doc.status));
371
+ const scope = [...new Set(index.docs.map(d => d.status).filter(s => s && !config.lifecycle.terminalStatuses.has(s)))];
372
+ const scoped = index.docs.filter(doc => doc.status && !config.lifecycle.terminalStatuses.has(doc.status));
373
373
  const missingSurface = scoped.filter(doc => !doc.surface);
374
374
  const missingModule = scoped.filter(doc => !doc.module);
375
375
  const modulePlatform = scoped.filter(doc => doc.module === 'platform');
@@ -403,7 +403,7 @@ export function formatSnapshot(doc, config) {
403
403
 
404
404
  function _formatSnapshot(doc) {
405
405
  const state = doc.currentState ?? 'No current_state set';
406
- if (/^active:|^ready:|^planned:|^research:|^blocked:|^archived:/i.test(state)) {
406
+ if (/^active:|^ready:|^planned:|^scoping:|^blocked:|^archived:/i.test(state)) {
407
407
  return state;
408
408
  }
409
409
  return `${capitalize(doc.status ?? 'unknown')}: ${state}`;
package/src/stats.mjs CHANGED
@@ -8,10 +8,10 @@ function pct(n, total) {
8
8
 
9
9
  export function buildStats(index, config) {
10
10
  const docs = index.docs;
11
- const scope = config.statusOrder.filter(s => !config.lifecycle.terminalStatuses.has(s) && !config.lifecycle.skipWarningsFor.has(s));
11
+ const scope = config.statusOrder.filter(s => !config.lifecycle.terminalStatuses.has(s));
12
12
  for (const typeSet of (config.typeStatuses?.values() ?? [])) {
13
13
  for (const s of typeSet) {
14
- if (!config.lifecycle.terminalStatuses.has(s) && !config.lifecycle.skipWarningsFor.has(s) && !scope.includes(s)) scope.push(s);
14
+ if (!config.lifecycle.terminalStatuses.has(s) && !scope.includes(s)) scope.push(s);
15
15
  }
16
16
  }
17
17
  const scoped = docs.filter(d => scope.includes(d.status));