dotmd-cli 0.69.0 → 0.70.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.
Files changed (53) hide show
  1. package/README.md +144 -964
  2. package/bin/dotmd.mjs +238 -195
  3. package/dotmd.config.example.mjs +5 -8
  4. package/package.json +6 -10
  5. package/src/agent-context.mjs +132 -0
  6. package/src/atomic-mutation.mjs +1505 -0
  7. package/src/baton.mjs +109 -114
  8. package/src/bulk-tag.mjs +7 -7
  9. package/src/commands.mjs +326 -12
  10. package/src/completions.mjs +38 -98
  11. package/src/config.mjs +18 -3
  12. package/src/diff.mjs +7 -3
  13. package/src/doctor.mjs +12 -5
  14. package/src/export.mjs +154 -25
  15. package/src/fix-refs.mjs +2 -0
  16. package/src/frontmatter-fix.mjs +2 -0
  17. package/src/frontmatter.mjs +3 -2
  18. package/src/git.mjs +531 -14
  19. package/src/graph.mjs +53 -25
  20. package/src/guard.mjs +163 -60
  21. package/src/hud.mjs +65 -76
  22. package/src/index-file.mjs +28 -16
  23. package/src/index.mjs +17 -12
  24. package/src/init.mjs +1 -1
  25. package/src/journal.mjs +145 -12
  26. package/src/lifecycle.mjs +554 -282
  27. package/src/lint.mjs +3 -3
  28. package/src/managed-path.mjs +192 -0
  29. package/src/migrate-prompts.mjs +2 -0
  30. package/src/migrate-template.mjs +2 -0
  31. package/src/migrate.mjs +7 -1
  32. package/src/new.mjs +135 -54
  33. package/src/output-identity.mjs +106 -0
  34. package/src/pickup-card.mjs +24 -10
  35. package/src/pickup.mjs +457 -0
  36. package/src/prompts.mjs +134 -75
  37. package/src/query.mjs +22 -10
  38. package/src/reference-planner.mjs +292 -0
  39. package/src/rename.mjs +65 -73
  40. package/src/render.mjs +17 -8
  41. package/src/runlist.mjs +109 -71
  42. package/src/section.mjs +2 -1
  43. package/src/ship.mjs +39 -20
  44. package/src/stats.mjs +1 -1
  45. package/src/status-metadata.mjs +87 -0
  46. package/src/statuses.mjs +11 -26
  47. package/src/summary.mjs +14 -3
  48. package/src/update.mjs +38 -10
  49. package/src/use.mjs +4 -1
  50. package/src/util.mjs +1 -0
  51. package/src/validate.mjs +14 -6
  52. package/src/watch.mjs +6 -1
  53. package/src/notion.mjs +0 -528
package/README.md CHANGED
@@ -1,1070 +1,250 @@
1
1
  # dotmd
2
2
 
3
- CLI for managing markdown documents with YAML frontmatter.
3
+ CLI for managing Markdown documents with YAML frontmatter.
4
4
 
5
- Index, query, validate, and lifecycle-manage any collection of `.md` files — plans, ADRs, RFCs, design docs, meeting notes. Built for AI-assisted development workflows where structured docs need to stay current.
5
+ dotmd indexes, queries, validates, graphs, exports, and lifecycle-manages plans,
6
+ ADRs, RFCs, design docs, and other structured Markdown. It is built for
7
+ AI-assisted development workflows where documents need to remain current and
8
+ safe to mutate.
9
+
10
+ - Zero runtime dependencies
11
+ - Node.js 20 or newer
12
+ - Runtime support for Linux, macOS, and Windows
13
+ - Type-aware lifecycle rules for plans, docs, and saved prompts
6
14
 
7
15
  ## Install
8
16
 
9
17
  ```bash
10
- npm install -g dotmd-cli # global — use `dotmd` anywhere
11
- npm install -D dotmd-cli # project devDep — use via npm scripts
12
- npx dotmd-cli init # try it without installing — scaffold a repo first
13
- # requires Node.js >= 20
18
+ npm install -g dotmd-cli # global CLI and Claude Code plugin hooks
19
+ npm install -D dotmd-cli # project scripts via node_modules/.bin
20
+ npx dotmd-cli init # try it without installing
14
21
  ```
15
22
 
16
- ### Claude Code plugin (recommended)
23
+ Maintainer release automation is POSIX-only because it uses Bash and POSIX
24
+ command-line tools. The published Node.js CLI remains cross-platform.
17
25
 
18
- If you drive dotmd from Claude Code, install the **dotmd plugin**. It teaches every session and subagent the dotmd workflow and guards the wrong-moves agents keep making (committing session-local prompts, `cat`-ing prompts instead of consuming them, hand-editing `status:`):
26
+ ### Claude Code Plugin
19
27
 
20
- ```
28
+ The recommended Claude Code setup is the dotmd plugin:
29
+
30
+ ```text
21
31
  /plugin marketplace add reowens/dotmd
22
32
  /plugin install dotmd@dotmd
23
33
  ```
24
34
 
25
- The plugin bundles the hooks (`SessionStart`/`SubagentStart` priming, a `PreToolUse` guard) and a canonical workflow skill, so guidance travels to **every** repo automatically — no per-repo setup. (Source: `plugins/dotmd/` in this repo.)
35
+ The plugin provides SessionStart and SubagentStart orientation, a PreToolUse
36
+ guard, the canonical workflow skill, and `/plans`, `/docs`, `/prompts`, and
37
+ `/baton` commands.
26
38
 
27
- > **The plugin requires a _global_ CLI install.** Its hooks resolve `dotmd` from your `PATH`, so run `npm install -g dotmd-cli`. A project devDependency (`npm install -D dotmd-cli`) lives at `./node_modules/.bin/dotmd` — off `PATH` — so the hooks silently no-op (no errors, just no priming/guarding). Use the devDep for `npm run` scripts; use the global install for the plugin.
39
+ The plugin requires a global CLI install because its hooks resolve `dotmd` from
40
+ `PATH`. A project devDependency is useful for npm scripts but does not put the
41
+ CLI on the hook's `PATH`.
28
42
 
29
- > **Upgrading to 0.57.0+:** per-repo `.claude/commands/{plans,docs,baton}.md` scaffolding is retired — that guidance now ships via the plugin's workflow skill and `/plans`, `/docs`, `/prompts`, `/baton` commands. On the next `dotmd hud` (SessionStart), dotmd removes those generated files (only banner-stamped `<!-- dotmd-generated -->` ones — your hand-authored command files are never touched). If you'd committed them, you'll see deletions to commit — that's expected. Run `claude plugin update dotmd@dotmd` to pick up `/baton`.
30
-
31
- ### Updating
32
-
33
- The CLI and the Claude Code plugin are versioned in lockstep but ship as separate artifacts, so upgrading one can leave the other behind. `dotmd update` keeps them aligned:
43
+ Keep the CLI and plugin aligned with:
34
44
 
35
45
  ```bash
36
- dotmd update # update both: npm CLI + the plugin
37
- dotmd update --check # report CLI vs plugin versions, change nothing (no network)
38
- dotmd update --cli-only # just the npm CLI
39
- dotmd update --plugin-only # just the plugin (what to run after a plain npm upgrade)
46
+ dotmd update
47
+ dotmd update --check
48
+ dotmd update --cli-only
49
+ dotmd update --plugin-only
40
50
  ```
41
51
 
42
- `--plugin-only` is the usual fixup: after `npm i -g dotmd-cli@latest` the CLI is fresh but the plugin is stale, so run `dotmd update --plugin-only`, then restart the session (or `/reload-plugins`).
52
+ Restart Claude Code, or run `/reload-plugins`, after a plugin update.
43
53
 
44
54
  ## Quick Start
45
55
 
46
56
  ```bash
47
- dotmd init # creates dotmd.config.mjs, docs/, docs/docs.md
48
- dotmd new my-feature # scaffold a new doc with frontmatter
49
- dotmd list # index all docs grouped by status
50
- dotmd check # validate frontmatter and references
51
- dotmd context # compact briefing (great for LLM context)
52
- dotmd doctor # preview fixes for everything (--apply to write)
57
+ dotmd init # create config, docs/, and the generated index
58
+ dotmd new plan auth-refresh # scaffold a typed document
59
+ dotmd briefing # compact active-work orientation
60
+ dotmd plans # live plan dashboard
61
+ dotmd check # validate schema, references, and lifecycle shape
62
+ dotmd doctor # preview repairs; add --apply to write
53
63
  ```
54
64
 
55
- ### Shell Completion
65
+ `dotmd briefing` is the compact orientation view. `dotmd context` is the fuller
66
+ human/LLM briefing, while `dotmd agent-context` emits bounded structured JSON for
67
+ agent integrations.
56
68
 
57
- ```bash
58
- # bash
59
- eval "$(dotmd completions bash)" # add to ~/.bashrc
69
+ ## Core Workflow
60
70
 
61
- # zsh
62
- eval "$(dotmd completions zsh)" # add to ~/.zshrc
71
+ ```bash
72
+ dotmd briefing
73
+ dotmd use docs/plans/auth-refresh.md
74
+ dotmd set awaiting docs/plans/auth-refresh.md --note "Need API owner decision"
75
+ dotmd set active docs/plans/auth-refresh.md --note "Decision received"
76
+ dotmd archive docs/plans/auth-refresh.md --note "Shipped and verified"
63
77
  ```
64
78
 
65
- ## Auto-Detected From Your Markdown
66
-
67
- dotmd reads what's already in your `.md` files — you don't have to migrate everything into frontmatter to get useful output.
79
+ Use `dotmd set <status> [<file>]` for lifecycle changes rather than editing a
80
+ `status:` line. It validates the status for the document type, updates history,
81
+ runs lifecycle hooks, repairs references after moves, and synchronizes the
82
+ index.
68
83
 
69
- Add `- [ ]` checkboxes anywhere in the body:
84
+ For unfinished session work, save the handoff and release the owned plan in one
85
+ operation:
70
86
 
71
- ```markdown
72
- ## Polish
73
-
74
- - [x] Index regen on every mutation
75
- - [x] Auto-checklist progress bars
76
- - [x] Untagged docs surfaced in `list`
77
- - [ ] SessionStart hook auto-wired by init
78
- - [ ] Bulk-tag prompt for brownfield repos
79
- ```
80
-
81
- `dotmd list` picks them up — zero config, no extra field:
82
-
83
- ```
84
- Polish-Pass 2d ██████░░░░ 3/5
87
+ ```bash
88
+ dotmd baton @/tmp/resume.md
85
89
  ```
86
90
 
87
- Same story for these signals, each picked up from body text when the matching frontmatter field is missing:
88
-
89
- | Field | Falls back to | Example body |
90
- |-------|---------------|--------------|
91
- | `title` | first `# H1` heading | `# Auth Token Refresh` |
92
- | `summary` | first `> blockquote` line (skipping `Status note` lines) | `> One-line summary of what this doc covers.` |
93
- | `current_state` | `**Status:** ...`, `- Status: ...`, or `> Status note (...): ...` lines (skipped on terminal docs to avoid stale claims) | `**Status:** Phase 2 underway` |
94
- | `next_step` | first bullet under a `## Next Step` (or `## Suggested Next Step`) H2 section | `## Next Step`<br>`- wire token refresh into middleware` |
95
- | Body links | inline `[text](path.md)` references | validated as ref edges by `check` |
96
-
97
- Explicit frontmatter always wins. Body extraction is a cushion for partially-tagged docs, not a replacement for it.
98
-
99
- ## What It Does
100
-
101
- - **Index** — group docs by status, with auto-detected progress bars (from `- [ ]` checklists) and next steps
102
- - **Query** — filter by status, keyword, module, surface, owner, staleness; `dotmd grep` searches document bodies too
103
- - **Resume handoff** — `dotmd baton` saves a resume prompt for the next session and releases the in-session plan in one verb
104
- - **Runlists** — group plans into an ordered sequence on a hub plan; `dotmd runlist next` picks up the next one
105
- - **Validate** — check for missing fields, broken references, broken body links, stale dates
106
- - **Stats** — health dashboard with staleness, completeness, audit coverage
107
- - **Graph** — visualize document relationships as text, Graphviz DOT, or JSON
108
- - **Deps** — dependency tree or overview of what blocks what
109
- - **Unblocks** — impact analysis: what depends on a doc
110
- - **Health** — plan velocity, aging, pipeline status
111
- - **Glossary** — domain term lookup with related docs
112
- - **Lifecycle** — transition statuses, auto-archive with `git mv` and reference updates
113
- - **Doctor** — auto-fix broken refs, lint issues, date drift, and stale indexes in one pass
114
- - **Scaffold** — create new docs from templates (plan, ADR, RFC, audit, design)
115
- - **AI summaries** — summarize docs via local MLX model or custom hook
116
- - **Export** — generate concatenated markdown, static HTML site, or JSON bundle
117
- - **Notion** — import from, export to, and bidirectionally sync with Notion databases
118
- - **Multi-root** — manage docs across multiple directories with a single config
119
- - **Context briefing** — compact summary designed for AI/LLM consumption
120
- - **Dry-run** — preview any mutation with `--dry-run` before committing
91
+ Saved prompts are local session state. Consume them with `dotmd use`; inspect
92
+ without consuming via `dotmd prompts show`.
121
93
 
122
94
  ## Document Format
123
95
 
124
- Any `.md` file with YAML frontmatter:
125
-
126
96
  ```markdown
127
97
  ---
128
- type: doc
98
+ type: plan
129
99
  status: active
130
- updated: 2026-03-14
100
+ updated: 2026-07-13
131
101
  modules:
132
102
  - auth
133
103
  surfaces:
134
104
  - backend
135
- next_step: implement token refresh
136
- current_state: initial scaffolding complete
137
- related_plans:
138
- - ./design-doc.md
139
- ---
140
-
141
- # Auth Token Refresh
142
-
143
- Design doc content here...
144
-
145
- - [x] Research existing patterns
146
- - [ ] Implement refresh logic
147
- - [ ] Add tests
148
- ```
149
-
150
- The only required field is `status`. Everything else is optional but unlocks more features. The `type` field (`plan`, `doc`, or `prompt`) enables type-specific statuses and smarter context briefings.
151
-
152
- > **Note:** `module:` and `surface:` (singular) are deprecated as of 0.36.3 — use the plural array forms (`modules:`, `surfaces:`). Run `dotmd lint --fix` to migrate existing docs.
153
-
154
- ## Document Types
155
-
156
- Every document can have a `type` field in its frontmatter. Types determine which statuses are valid and how the document appears in context briefings.
157
-
158
- | Type | Purpose | Valid Statuses |
159
- |------|---------|----------------|
160
- | `plan` | Execution plans | `in-session`, `active`, `planned`, `blocked`, `partial`, `paused`, `awaiting`, `queued-after`, `archived` |
161
- | `doc` | Design docs, specs, ADRs, RFCs, reference material | `draft`, `active`, `review`, `reference`, `deprecated`, `archived` |
162
- | `prompt` | Saved prompts that seed future Claude sessions | `pending`, `held`, `shelved`, `claimed`, `archived` |
163
-
164
- Documents without a `type` field use the global `statuses.order` from config.
165
-
166
- `dotmd new <type> <name>` sets the `type:` field automatically (`plan`, `doc`, or `prompt`).
167
-
168
- Filter by type with `--type`:
169
-
170
- ```bash
171
- dotmd query --type plan --status active # active plans
172
- dotmd list --type doc # all docs
173
- dotmd export --type prompt # export only saved prompts
174
- ```
175
-
176
- Customize types and their statuses in config with the `types` key. See [`dotmd.config.example.mjs`](dotmd.config.example.mjs).
177
-
178
- ### What each plan status means
179
-
180
- 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.
181
-
182
- | Status | Unstuck-action | When to use |
183
- |--------|----------------|-------------|
184
- | `in-session` | — | A Claude session is working on it right now. Don't pick up. |
185
- | `active` | Pick up | Ready to be worked on. |
186
- | `planned` | Wait for trigger | Queued; not yet ready to execute. |
187
- | `blocked` | **Monitor** | External arrival on its own schedule (hardware, vendor, third-party rollout). You can't speed it up. |
188
- | `partial` | **Spawn successors** | Shipped most of the plan; tail deferred. Body should reference successor plans tracking the tail. Visible but quiet (no nagging). |
189
- | `paused` | **Re-evaluate** | Started but stopped mid-work; needs near-term review. NOT quiet — short (3-day) stale threshold so resume-decisions don't decay. |
190
- | `awaiting` | **Ask** | Needs a human decision or input. NOT quiet — pings get forgotten, so this status generates stale pressure to chase the answer. |
191
- | `queued-after` | **Check predecessor** | Sequenced behind another plan; can start once that one ships. Quiet. |
192
- | `archived` | — | No longer relevant; auto-moved to the archive directory on transition. |
193
-
194
- Each *quiet* status (`partial`, `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` and `paused` deliberately stay loud so unanswered questions and stalled mid-flight work don't decay into invisible backlog.
195
-
196
- > **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.
197
-
198
- ### Runlists: ordered groups of plans
199
-
200
- When several plans must ship in a known order (an "auth revamp" sprint with extract → rewrite → cleanup phases), declare a `runlist:` array on a hub plan instead of chaining `queued-after` per pair or keeping the order in prose:
201
-
202
- ```yaml
203
- ---
204
- type: plan
205
- status: active
206
- title: Auth Revamp
207
- runlist:
208
- - auth-revamp-01-extract.md
209
- - auth-revamp-02-rewrite.md
210
- - auth-revamp-03-cleanup.md
211
- ---
212
- ```
213
-
214
- Scaffold the whole sprint in one command instead of hand-writing the hub + children:
215
-
216
- ```bash
217
- $ dotmd new plan auth-revamp --runlist extract,rewrite,cleanup
218
- Created: plans/auth-revamp.md (plan (runlist hub))
219
- Created: plans/auth-revamp-01-extract.md (plan · runlist child, planned)
220
- Created: plans/auth-revamp-02-rewrite.md (plan · runlist child, planned)
221
- Created: plans/auth-revamp-03-cleanup.md (plan · runlist child, planned)
222
- ```
223
-
224
- That writes the hub above (the `runlist:` array + an `## Order of operations` link list) plus one `planned` child stub per slug, each carrying a `parent_plan: auth-revamp.md` back-ref. Then walk the sequence:
225
-
226
- ```bash
227
- $ dotmd runlist auth-revamp # the sequence + statuses; → marks the next pickup
228
- runlist: plans/auth-revamp.md
229
- → 1. [planned] plans/auth-revamp-01-extract.md
230
- 2. [planned] plans/auth-revamp-02-rewrite.md
231
- 3. [planned] plans/auth-revamp-03-cleanup.md
232
-
233
- $ dotmd runlist next auth-revamp # pick up the → child (planned → in-session) + print its card
234
- ▶ Started: plans/auth-revamp-01-extract.md (planned → in-session)
235
-
236
- $ dotmd runlists # dashboard of coordination-hub runlists (--json, --limit N)
237
- ```
238
-
239
- `runlist next` stops with a runlist-aware error if the next child isn't in a workable status (`active` / `planned` / `in-session`), so you resolve the blocker before continuing. Each child should set `parent_plan:` pointing back at the hub — `dotmd check` warns when it doesn't. There's no separate doc type: a runlist hub is just a plan with the array.
240
-
241
- In `dotmd plans`, the hub folds its children under one tagged row — `auth-revamp runlist · 0/3 · next → 01-extract [RUNLIST]` — so a multi-plan sprint reads as one runlist instead of cluttering the triage list. A child whose hub is filtered out of the view (e.g. `--status active` when the hub is `planned`) still renders on its own.
242
-
243
- **Coordination runlists.** A `runlist:` array fits a small ordered *sprint*. For a large, prose-first *coordination map* (a domain hub pointing at many plans, with gating/sequence rationale, sometimes unordered), set `execution_mode: coordination` instead — or just name it `*-runlist`. These aren't folded: `dotmd plans` lifts them into a pinned `Runlists` section and out of the active count, and `dotmd runlists` shows that dashboard standalone. `dotmd briefing` and `dotmd health` do the same — coordination hubs are pulled out of the live/active counts into a `runlists` bucket (briefing) and a held-out `Runlists:` tally (health), so they don't inflate the actionable-plan or aging numbers. The per-hub "N related" count comes from `related_plans:`. `dotmd check` nudges a `*-runlist` hub missing `execution_mode: coordination`.
244
-
245
- ## Commands
246
-
247
- ```
248
- dotmd list [--verbose] List docs grouped by status (default)
249
- dotmd json Full index as JSON
250
- dotmd check [flags] Validate frontmatter and references
251
- dotmd coverage [--json] Metadata coverage report
252
- dotmd stats [--json] Doc health dashboard
253
- dotmd graph [--dot|--json] Visualize document relationships
254
- dotmd deps [file] Dependency tree or overview
255
- dotmd unblocks <file> Show what depends on this doc
256
- dotmd health [--json] Plan velocity, aging, and pipeline
257
- dotmd briefing Compact summary for session start
258
- dotmd context [--summarize] Full briefing (LLM-oriented)
259
- dotmd agent-context Compact bounded JSON context for agents
260
- dotmd focus [status] Detailed view for one status group
261
- dotmd query [filters] Filtered search (--body scans document bodies)
262
- dotmd grep <term> Keyword search incl. bodies — "which doc discussed X?"
263
- dotmd plans List live plans (excludes archived)
264
- dotmd modules Module dashboard (plans grouped by module)
265
- dotmd module <name> Plans for one module, grouped by status
266
- dotmd surfaces List configured surface taxonomy
267
- dotmd stale List stale docs
268
- dotmd actionable List docs with next steps
269
- dotmd index [--print] Generate/update docs.md index block
270
- dotmd hud Actionable triage (silent when clean — ideal SessionStart hook)
271
- dotmd use [<file-or-slug>] Open by type: prompt → consume, plan → start, doc → read
272
- (no arg: consume the oldest pending prompt)
273
- dotmd set <status> <file> Change a document's status (--note appends why to Version History)
274
- dotmd baton [<plan>|<slug>] <@draft|-> Save a resume prompt; releases the in-session plan
275
- dotmd runlist <hub> [next] Show or walk an ordered group of plans
276
- dotmd status <file> <status> Transition document status (deprecated; prefer set)
277
- dotmd archive <file> Archive (status + move + update refs)
278
- dotmd bulk archive <files> Archive multiple files at once
279
- dotmd bulk-tag [files] Tag pre-existing untagged .md files
280
- dotmd touch <file> Bump updated date
281
- dotmd touch --git Bulk-sync dates from git history
282
- dotmd doctor [--apply] Fix refs, lint, dates, index (previews by default)
283
- dotmd self-check Project/version skew diagnostic
284
- dotmd fix-refs Auto-fix broken reference paths
285
- dotmd lint [--fix] Check and auto-fix frontmatter issues
286
- dotmd rename <old> <new> Rename doc and update references
287
- dotmd migrate <f> <old> <new> Batch update a frontmatter field
288
- dotmd ship [patch|minor|major] Regen + commit + bump in one step
289
- dotmd notion <sub> [db-id] Notion import/export/sync
290
- dotmd export [file] Export docs as md, html, or json
291
- dotmd summary <file> AI summary of a document
292
- dotmd glossary <term> Look up domain terms + related docs
293
- dotmd watch [command] Re-run a command on file changes
294
- dotmd diff [file] Show changes since last updated date
295
- dotmd new <type> <name> Create a new doc (type: doc, plan, or prompt)
296
- dotmd prompts [sub] Manage saved prompts (list, show, hold, archive, new)
297
- dotmd statuses [sub] Manage per-project status taxonomy
298
- dotmd journal [flags] View opt-in command-usage journal (DOTMD_JOURNAL=1)
299
- dotmd init Create starter config + docs directory
300
- dotmd completions <shell> Output shell completion script (bash, zsh)
301
- ```
302
-
303
- Run `dotmd help all` for the always-current version of this list, `dotmd help statuses` for the status vocabulary, and `dotmd <cmd> --help` for per-command details.
304
-
305
- ### Global Flags
306
-
307
- ```
308
- --config <path> Explicit config file path
309
- --dry-run, -n Preview changes without writing anything
310
- --root <name> Filter to a specific docs root
311
- --type <t1,t2> Filter by document type (plan, doc, prompt, or custom)
312
- --verbose Show resolved config details
313
- --help, -h Show help (per-command with: dotmd <cmd> --help)
314
- --version, -v Show version
315
- ```
316
-
317
- ### Query Filters
318
-
319
- ```bash
320
- dotmd query --status active,ready --module auth
321
- dotmd query --keyword "token" --has-next-step
322
- dotmd query --stale --sort updated --all
323
- dotmd query --surface backend --checklist-open
324
- dotmd query --status active --summarize # AI summaries
325
- dotmd query --status active --summarize --summarize-limit 3
326
- dotmd query --keyword "retries" --body # scan document bodies too
327
- ```
328
-
329
- Flags: `--type`, `--status`, `--keyword`, `--body`, `--module`, `--surface`, `--domain`, `--owner`, `--updated-since`, `--stale`, `--has-next-step`, `--has-blockers`, `--checklist-open`, `--sort`, `--limit`, `--all`, `--git`, `--json`, `--summarize`, `--summarize-limit`, `--model`.
330
-
331
- `dotmd grep <term>` is the "which doc discussed X?" shorthand — an alias for `dotmd query --keyword <term> --body --all` that prints doc cards plus line-numbered excerpts per body hit. Bodies are read lazily (frontmatter filters run first), and it composes with the usual query flags.
332
-
333
- ### Create Documents
334
-
335
- The signature is `dotmd new <type> <name> [body]`. `<type>` is one of the built-in types (`doc`, `plan`, `prompt`) or a custom type from your config. If you omit `<type>`, it defaults to `doc`.
336
-
337
- ```bash
338
- dotmd new plan auth-revamp # type: plan → docs/plans/auth-revamp.md
339
- dotmd new doc token-refresh-design # type: doc → docs/token-refresh-design.md
340
- dotmd new my-feature # implicit type: doc
341
- dotmd new plan auth --status planned # initial status override
342
- dotmd new doc my-doc --title "Custom Title" # title override
343
- dotmd new doc my-doc --root modules # create in a specific root
344
- dotmd new --list-types # show registered types
345
- ```
346
-
347
- Each built-in type has a template baked in:
348
-
349
- | Type | Default destination | Shape |
350
- |------|---------------------|-------|
351
- | `plan` | `docs/plans/<slug>.md` | Problem → Phases → Closeout, with phase status markers and Version History |
352
- | `doc` | `docs/<slug>.md` | Overview → Version History → Related (build-up shape lite) |
353
- | `prompt` | `docs/prompts/<slug>.md` | Body is required (see [Saved Prompts](#saved-prompts)) |
354
-
355
- **Plan body variants (plans only).** The default `plan` template is the full build-up shape. For a smaller plan, or the recurring audit shape, pass one body-variant flag:
356
-
357
- - `--lite` / `--minimal` — Problem → Phases → Version History (drops Goals / Non-Goals / What Exists Today / Constraints / Decisions / Deferred / Closeout).
358
- - `--audit` / `--findings` — Problem → Findings (ranked) → Suggested order → Open Questions, for "I investigated X, here's what I found" plans.
359
-
360
- ```bash
361
- dotmd new plan quick-fix --lite
362
- dotmd new plan perf-audit --audit
363
- ```
364
-
365
- To scaffold an ordered sprint or a coordination map instead, see [Runlists](#runlists-ordered-groups-of-plans) (`--runlist a,b,c` / `--coordination`). The body variants and the hub flags are all mutually exclusive — a plan has exactly one body shape.
366
-
367
- Add custom types via `templates` in your config:
368
-
369
- ```js
370
- export const templates = {
371
- spike: {
372
- description: 'Timeboxed investigation',
373
- defaultStatus: 'active',
374
- targetRoot: 'spikes', // in flat-array root configs, lands in the matching root
375
- dir: 'spikes', // in single-root configs, creates docs/spikes/<slug>.md
376
- frontmatter: (status, today) => `type: spike\nstatus: ${status}\nupdated: ${today}\ntimebox: 2d`,
377
- body: (title) => `\n# ${title}\n\n## Hypothesis\n\n\n\n## Findings\n\n\n`,
378
- },
379
- };
380
- ```
381
-
382
- Then `dotmd new spike my-spike` creates a doc from your template.
383
-
384
- **Routing your custom type to a directory.** Two knobs:
385
-
386
- - `targetRoot: '<name>'` — name (basename or suffix) of a root entry. In configs with `root: ['docs/plans', 'docs/spikes', ...]` (flat-array layout), the new doc lands in the matching root.
387
- - `dir: '<subdir>'` — subdirectory under `config.docsRoot`. Used as the fallback when `targetRoot` doesn't match anything (typical single-root layout).
388
-
389
- Set both for portability. The `--root` CLI flag overrides both. **Overrides do not inherit builtin properties** — if you override `templates.prompt`, re-declare `targetRoot`, `dir`, `defaultStatus`, `requiresBody`, etc. that you want preserved.
390
-
391
- ### Saved Prompts
392
-
393
- Saved prompts are `.md` files with `type: prompt` that capture a request meant to seed a future Claude session — "look at the remaining lint warnings tomorrow," "resume the payments refactor," "draft the on-call runbook." The body is the prompt; the frontmatter tracks status.
394
-
395
- ```bash
396
- dotmd new prompt cleanup-tomorrow "look at remaining lint warnings"
397
- dotmd new prompt resume-foo - <<'EOF'
398
- multi-line
399
- prompt body
400
- EOF
401
- dotmd new prompt from-file @/tmp/draft.md
402
- ```
403
-
404
- Manage them with the `prompts` command family:
405
-
406
- Consume one with `dotmd use` — it atomically prints the body and archives the prompt so it can't be double-consumed:
407
-
408
- ```bash
409
- dotmd use # consume the oldest pending prompt
410
- dotmd use <file-or-slug> # consume a specific prompt
411
- ```
412
-
413
- Admin verbs live under the `prompts` namespace:
414
-
415
- ```bash
416
- dotmd prompts # list pending prompts (default)
417
- dotmd prompts list --all # all statuses
418
- dotmd prompts show <file> # read-only peek: print the body WITHOUT consuming
419
- dotmd prompts next # print body of oldest pending + auto-archive (one-shot)
420
- dotmd prompts use <file> # print body of a specific prompt + auto-archive
421
- dotmd prompts hold <file> # park a prompt (status → held) under prompts/held/:
422
- # kept in list, hidden from hud/briefing, skipped by `next`
423
- dotmd prompts unhold <file> # move a held prompt back to pending
424
- dotmd prompts shelve <file> # legacy alias for `hold`
425
- dotmd prompts archive <file> # archive without printing the body
426
- dotmd prompts new <name> [body] # alias for `dotmd new prompt`
427
- ```
428
-
429
- `dotmd hud` surfaces pending prompts on session start, so a saved prompt acts as a self-addressed reminder: write it now, the next session sees it. Held prompts are kept out of the SessionStart surface — use them for "saved but not next."
430
-
431
- Statuses: `pending` (drafted, awaiting a session), `held` (saved but parked under `prompts/held/` — visible in `prompts list`, hidden from `hud`/`briefing`, skipped by `prompts next`), `archived` (consumed or filed away). `shelved` is a legacy spelling accepted for older files; `claimed` is reserved for a future "in-flight" state but is currently a synonym for archived in practice.
432
-
433
- ### Baton: resume prompts & session handoff
434
-
435
- `dotmd baton` is the "save a resume prompt" verb — the way one session hands work to the next without pasting resume text into chat. Write a short draft (the next concrete decision plus any gotchas, not a recap), then:
436
-
437
- ```bash
438
- dotmd baton @/tmp/draft.md # plan mode: a plan is in-session
439
- ```
440
-
441
- In plan mode, baton does the whole closeout in one call:
442
-
443
- 1. Saves a resume prompt named `resume-<plan-slug>` (collision-safe: `-2`, `-3`, …) under `docs/prompts/` with `status: pending`.
444
- 2. Releases the plan: one status flip, `in-session` → `active` by default (`--status paused|awaiting|partial|blocked` to override, `--note "why"` to record the reason in `## Version History`).
445
- 3. Prints the exact `git commit` command for the plan's frontmatter change — the prompt stays out of the pathspec, because saved prompts are session-local.
446
-
447
- Baton resolves *your* plan via the command journal (or takes it explicitly: `dotmd baton <plan-file> @draft`), falling back to the only in-session plan.
448
-
449
- No plan involved? Slug mode saves the prompt and touches nothing else:
450
-
451
- ```bash
452
- dotmd baton checkout-fixes @/tmp/draft.md # saves resume-checkout-fixes; no status changes
453
- cat /tmp/draft.md | dotmd baton # body from stdin
454
- ```
455
-
456
- Either way, the next session's `dotmd hud` surfaces the pending prompt, and `dotmd use` consumes it.
457
-
458
- ### Command Journal (opt-in)
459
-
460
- dotmd's primary user is an agent. Every CLI invocation can be journaled
461
- to `.dotmd/journal.jsonl` so agents (and humans) can see what got run,
462
- what failed, and how long things took — observability that turns every
463
- session into data the next design call can use.
464
-
465
- Default off. Enable with either:
466
-
467
- ```bash
468
- export DOTMD_JOURNAL=1 # env var
469
- # or, in dotmd.config.mjs:
470
- export const journal = true; # config flag
471
- ```
472
-
473
- (`DOTMD_JOURNAL=0` forces off even when the config opts in.)
474
-
475
- Each invocation appends one JSON line:
476
- `{ts, sid, pid, argv, exit, ms, v, err?}`. Writes are atomic via
477
- `O_APPEND` (entries are well under `PIPE_BUF`), so concurrent sessions
478
- interleave cleanly without locking. Lazy rotation to
479
- `.dotmd/journal.jsonl.1` on version change, at >5MB, or when the oldest
480
- entry is >30 days; one backup retained and pruned after 30 days.
481
- Version-change rotation keeps agent-facing journal summaries focused on the
482
- currently installed dotmd.
483
-
484
- Read it back with `dotmd journal`:
485
-
486
- ```bash
487
- dotmd journal --tail 20 # last N entries (default)
488
- dotmd journal --errors # only non-zero exits
489
- dotmd journal --session <id> # filter by session id
490
- dotmd journal --since 2026-05-01 # filter by ts
491
- dotmd journal --by-command # group by argv[0]: count, median ms, errors
492
- dotmd journal --json # raw entries as a JSON array
493
- ```
494
-
495
- The journal is local-only and gitignored (or should be — `.dotmd/` is
496
- typically already ignored). Default-off keeps the surface clean for
497
- users who don't want the storage / PII tradeoff.
498
-
499
- ### Check & Fix
500
-
501
- ```bash
502
- dotmd check # validate everything
503
- dotmd check --errors-only # suppress warnings, show only errors
504
- dotmd check --fix # auto-fix broken refs + lint + regen index
505
- ```
506
-
507
- Validates: required fields, status values, broken reference paths, broken body links (`[text](path.md)`), bidirectional reference symmetry, git date drift, taxonomy mismatches.
508
-
509
- #### Per-ref one-way opt-out (`>` prefix)
510
-
511
- `referenceFields.bidirectional` is per-field — once a field is bidirectional, every ref in it expects a back-ref. For leaf-to-upstream cases (a plan referencing the audit doc that spawned it; many docs pointing at a single hub) that's noise: the parent shouldn't list every child. Prefix the value with `>` to mark a single ref one-way without changing the field:
512
-
513
- ```yaml
105
+ current_state: Token validation is complete.
106
+ next_step: Wire refresh rotation into middleware.
514
107
  related_docs:
515
- - docs/sibling-design.md # bidirectional (default for the field)
516
- - "> docs/audit-beyond-platform.md" # one-way upstream — no back-ref expected
517
- ```
518
-
519
- The prefix is stripped before path resolution — refs still resolve normally. Works on any ref field. Quote the value (it starts with `>`, which is YAML's block-scalar indicator). Shipped 0.35.0 — closed 7 false-positive warnings in this repo's own corpus.
520
-
521
- ### Stats
522
-
523
- ```bash
524
- dotmd stats # health dashboard
525
- dotmd stats --json # machine-readable
526
- ```
527
-
528
- Shows: status counts, staleness, errors/warnings, freshness (today/week/month), completeness (owner/surface/module/next_step), checklist progress, audit coverage.
529
-
530
- ### Doctor
531
-
532
- ```bash
533
- dotmd doctor # preview: fix refs → lint → sync git dates → regen index
534
- dotmd doctor --apply # actually write the fixes (previews by default since 0.37.0)
535
- dotmd doctor --statuses # detect overloaded status buckets (read-only)
536
- dotmd doctor --statuses --json # machine-readable suggestions
537
- ```
538
-
539
- `--statuses` is a read-only diagnostic. It scans each status with at least
540
- 10 plans and groups their `current_state` / `next_step` text against cue
541
- keywords for `partial`, `paused`, `awaiting`, `queued-after`, and `blocked`.
542
- When a single bucket lands plans in two or more cue groups (each above 15%
543
- of the bucket), it prints a split suggestion:
544
-
545
- ```
546
- 47 plan/backlog plans cluster across 4 patterns — consider splitting:
547
- ~22 → partial (cues: "shipped", "landed", "tail", "deferred")
548
- ~15 → paused (cues: "paused", "on hold", "set aside")
549
- ~ 6 → queued-after (cues: "after", "once", "depends on", "waiting on <plan>")
550
- ~ 4 → (kept in backlog — no clear pattern match)
551
-
552
- Heuristic — verify before migrating.
553
- ```
554
-
555
- The heuristic is intentionally conservative: small buckets are skipped, plans
556
- that match no cues stay in the original bucket, and the output is always a
557
- suggestion — never a verdict.
558
-
559
- ### Graph
560
-
561
- ```bash
562
- dotmd graph # text adjacency list
563
- dotmd graph --dot | dot -Tpng -o g.png # Graphviz PNG
564
- dotmd graph --json # machine-readable
565
- dotmd graph --status active,ready # filter by status
566
- dotmd graph --module auth # filter by module
567
- ```
568
-
569
- ### Deps
570
-
571
- ```bash
572
- dotmd deps # overview: most blocking, most blocked
573
- dotmd deps docs/plan-a.md # tree: depends-on + depended-on-by
574
- dotmd deps docs/plan-a.md --depth 2 # limit tree depth
575
- dotmd deps --json # machine-readable
576
- ```
577
-
578
- ### Unblocks
579
-
580
- ```bash
581
- dotmd unblocks docs/plan-a.md # what depends on this plan
582
- dotmd unblocks docs/plan-a.md --json # machine-readable
583
- ```
584
-
585
- ### Health
586
-
587
- ```bash
588
- dotmd health # plan pipeline and aging
589
- dotmd health --json # machine-readable
590
- ```
591
-
592
- ### Briefing
593
-
594
- ```bash
595
- dotmd briefing # compact 5-10 line summary
596
- dotmd briefing --json # machine-readable
597
- ```
598
-
599
- ### Modules Dashboard
600
-
601
- A triage view for codebases with enough plans that a flat list stops being useful (rule-of-thumb: ~50+ plans across many modules). Composes existing primitives (`modules: []`, `isStale`, `daysSinceUpdate`, `hasNextStep`, `statusOrder`) — no new config.
602
-
603
- ```bash
604
- dotmd modules # one row per module, dynamic status columns
605
- dotmd modules --sort cleanup # rank by (stale × avgAge) / total — "rotting hardest"
606
- dotmd modules --sort stale|age|nextstep|total
607
- dotmd modules --type doc # docs instead of plans
608
- dotmd modules --limit 20 # default 20; --all to disable
609
- dotmd modules --json # includes _totalUnique for double-count detection
610
-
611
- dotmd module <name> # deep view of one module, plans grouped by status
612
- dotmd module <name> --sort updated|age # default sort is status
613
- dotmd module <name> --json
614
- ```
615
-
616
- Workflow for systematic cleanup:
617
-
618
- ```
619
- dotmd modules --sort cleanup → walk the top row → dotmd module <name> → triage/archive → next
620
- ```
621
-
622
- Notes:
623
-
624
- - Status columns are dynamic — only statuses with ≥1 plan render, so default and custom vocabularies both look right.
625
- - A plan with `modules: [a, b]` counts in both rows. This is intentional. `--json` exposes `_totalUnique` so tooling can detect this if needed.
626
- - `(none)` is a literal row for unmoduled plans — surfaces unowned work that would otherwise hide in a flat list.
627
- - Unknown module names exit with `Module 'foo' not found. Did you mean: …?` (substring-first, Levenshtein ≤3 fallback).
628
- - The dashboard auto-falls-back to a stacked render when the table doesn't fit your terminal width.
629
-
630
- `dotmd stale --group module` is the canonical "what's rotting per module" companion view (uses the existing `query --group` mechanism, called out here so it's findable).
631
-
632
- ### AI Summaries
633
-
634
- ```bash
635
- dotmd summary docs/plan-a.md # AI summary of a single doc
636
- dotmd summary docs/plan-a.md --json # JSON output
637
- dotmd query --status active --summarize # AI summaries in query results
638
- dotmd context --summarize # AI-enhanced briefing
639
- ```
640
-
641
- Uses a local model by default. Override with `--model <name>` or the `summarizeDoc` hook.
642
-
643
- ### Glossary
644
-
645
- ```bash
646
- dotmd glossary "auth token" # look up a term
647
- dotmd glossary --list # list all terms
648
- dotmd glossary --json # machine-readable
649
- ```
650
-
651
- ### Export
652
-
653
- ```bash
654
- dotmd export # all docs as concatenated markdown
655
- dotmd export --format html --output site # static HTML site
656
- dotmd export --format json > bundle.json # JSON bundle with bodies
657
- dotmd export docs/plan-a.md # single doc + dependencies
658
- dotmd export --status active # filtered export
659
- dotmd export --type plan # export only plans
660
- ```
661
-
662
- ### Notion Integration
663
-
664
- ```bash
665
- dotmd notion import <database-id> # pull Notion database → local .md files
666
- dotmd notion export <database-id> # push local docs → Notion database
667
- dotmd notion sync <database-id> # bidirectional sync (newer wins)
668
- dotmd notion import <db-id> --force # overwrite existing files
669
- dotmd notion sync <db-id> --dry-run # preview sync actions
670
- ```
671
-
672
- Requires `NOTION_TOKEN` env var or `notion.token` in config. Maps Notion properties (select, multi_select, date, status, people, etc.) to YAML frontmatter fields. Configure property mapping in config:
673
-
674
- ```js
675
- export const notion = {
676
- token: process.env.NOTION_TOKEN,
677
- database: 'your-database-id',
678
- propertyMap: {
679
- 'Status': 'status',
680
- 'Last Updated': 'updated',
681
- 'Tags': 'surfaces',
682
- },
683
- };
684
- ```
685
-
686
- ### Multi-Root
687
-
688
- Manage docs across multiple directories:
689
-
690
- ```js
691
- export const root = ['docs/plans', 'docs/modules', 'docs/app'];
692
- ```
693
-
694
- All commands work across all roots. Filter with `--root`:
695
-
696
- ```bash
697
- dotmd list --root plans # only docs from docs/plans
698
- dotmd stats --root modules # stats for modules only
699
- dotmd new my-doc --root modules # create in docs/modules
700
- ```
701
-
702
- Archive stays within the source file's root. Cross-root references validate correctly.
703
-
704
- ### Archive
705
-
706
- ```bash
707
- dotmd archive docs/old-plan.md # move + update refs + regen index
708
- dotmd archive docs/old-plan.md -n # preview
709
- ```
710
-
711
- ### Bulk Archive
712
-
713
- ```bash
714
- dotmd bulk archive docs/old-a.md docs/old-b.md # archive multiple
715
- dotmd bulk archive docs/old-*.md -n # preview
716
- ```
717
-
718
- ### Open & Closeout
719
-
720
- Status is just frontmatter. There's no checkout, lock, or lease — opening a
721
- plan, transitioning it, and closing it are all plain status writes (archive
722
- also moves the file).
723
-
724
- ```bash
725
- dotmd use docs/plans/my-plan.md # mark in-session + print the plan card
726
- dotmd set in-session docs/plans/my-plan.md # set the status without printing
727
- dotmd set active docs/plans/my-plan.md # need more work: flip back to active
728
- dotmd set partial docs/plans/my-plan.md # shipped + tail deferred (reference successors in body)
729
- dotmd set awaiting docs/plans/my-plan.md # stuck on a human decision
730
- dotmd archive docs/plans/my-plan.md # fully shipped: archive + move + update refs
731
- dotmd archive docs/plans/my-plan.md --closeout-template # also inject ## Closeout skeleton
732
- ```
733
-
734
- `in-session` is a status like any other — `dotmd set <status> <file>` writes it
735
- to the file's frontmatter and does nothing else.
736
-
737
- Add `--note "why"` to any `set` or `archive` to append the reason to the doc's
738
- `## Version History` section in the same call (creates the section if missing) —
739
- it saves the status-change + worklog-edit round-trip. `set partial` without a
740
- note or successor link prints a reminder.
741
-
742
- To stop mid-work and hand off to a future session, use `dotmd baton` (see
743
- [Baton](#baton-resume-prompts--session-handoff)) — it saves the resume prompt
744
- and releases the plan in one verb.
108
+ - ./auth-design.md
109
+ ---
745
110
 
746
- **Recommended Claude Code hook** — add to `~/.claude/settings.json`
747
- (or your project's `.claude/settings.json`):
111
+ # Auth Refresh
748
112
 
749
- ```json
750
- {
751
- "hooks": {
752
- "SessionStart": [
753
- {
754
- "hooks": [
755
- { "type": "command", "command": "dotmd hud", "timeout": 5 }
756
- ]
757
- }
758
- ]
759
- }
760
- }
113
+ - [x] Validate tokens
114
+ - [ ] Rotate refresh tokens
761
115
  ```
762
116
 
763
- - **SessionStart** runs `dotmd hud`, which prints the command primer and stays
764
- silent when nothing is queued. Use this instead of `dotmd briefing` for the
765
- hook role — `briefing` dumps per-plan next_step prose that can run to many
766
- kilobytes on large repos. `hud` is the zero-pollution surface.
117
+ `status` is the only universally required field. A `type` enables type-specific
118
+ statuses, validation, templates, and briefing behavior. Explicit frontmatter
119
+ wins, but dotmd can also derive titles, summaries, state, next steps, checklist
120
+ progress, and Markdown links from the body.
767
121
 
768
- > The double-`hooks` nesting is correct: `hooks.<Event>[*].hooks[*]` is the
769
- > schema Claude Code requires. `Bash(dotmd:*)` should be in your
770
- > `permissions.allow` list as well, otherwise the hooks will be blocked.
122
+ Use plural `modules:` and `surfaces:` arrays. The old singular keys remain
123
+ readable for compatibility and can be migrated with `dotmd lint --fix`.
771
124
 
772
- ### Touch
125
+ ### Built-In Types
773
126
 
774
- ```bash
775
- dotmd touch docs/my-doc.md # set updated to today
776
- dotmd touch --git # bulk-sync all docs from git history
777
- ```
127
+ | Type | Purpose | Default statuses |
128
+ |---|---|---|
129
+ | `plan` | Executable work | `in-session`, `active`, `planned`, `blocked`, `partial`, `paused`, `awaiting`, `queued-after`, `archived` |
130
+ | `doc` | Specs, ADRs, audits, and reference material | `draft`, `active`, `review`, `reference`, `deprecated`, `archived` |
131
+ | `prompt` | Saved future-session instructions | `pending`, `held`, `shelved`, `claimed`, `archived` |
778
132
 
779
- ### Fix References
133
+ Status definitions can be customized per type. Rich status objects co-locate
134
+ display, staleness, validation, terminal, and archive behavior in one place.
780
135
 
781
- ```bash
782
- dotmd fix-refs # fix broken frontmatter refs + body links
783
- dotmd fix-refs --dry-run # preview fixes
784
- ```
136
+ ## Runlists And Roadmaps
785
137
 
786
- ### Lint
138
+ A sprint runlist is an ordered `runlist:` array on a hub plan. Scaffold a hub
139
+ and children together:
787
140
 
788
141
  ```bash
789
- dotmd lint # report issues
790
- dotmd lint --fix # fix all issues
142
+ dotmd new plan auth-revamp --runlist extract,rewrite,cleanup
143
+ dotmd runlist auth-revamp
144
+ dotmd runlist next auth-revamp
791
145
  ```
792
146
 
793
- ### Manage Statuses
147
+ Mutate the structure through the CLI so the array, child `parent_plan` refs, and
148
+ body order list remain synchronized:
794
149
 
795
150
  ```bash
796
- dotmd statuses # table view, all types
797
- dotmd statuses --type plan # one type
798
- dotmd statuses --json # machine-readable
799
-
800
- dotmd statuses add paused --type plan --like blocked --quiet # clone blocked, then quiet
801
- dotmd statuses set archived --type plan --no-quiet # tweak a flag
802
- dotmd statuses remove obsolete --type plan # refuses if any docs use it
803
-
804
- dotmd statuses migrate plan # array-form → rich-form
151
+ dotmd runlist add auth-revamp docs/plans/existing-plan.md
152
+ dotmd runlist add auth-revamp follow-up
153
+ dotmd runlist reorder auth-revamp follow-up --before cleanup
154
+ dotmd runlist remove auth-revamp extract --clear-parent
805
155
  ```
806
156
 
807
- `--like <existing>` is the affordance for "kinda like X but…" — clones every
808
- flag from another status, then user flags override. Write commands print a flag
809
- diff and prompt for confirmation; pass `--yes` to skip the prompt or
810
- `--dry-run` to preview without writing. Edits are atomic: the rewrite lands in
811
- a sibling temp file, is validated by re-importing it and running
812
- `resolveConfig`, then renamed into place — a syntax error or new warning
813
- leaves the original untouched.
157
+ Archived children count as complete. Parked children (`blocked`, `partial`,
158
+ `paused`, `awaiting`, and `queued-after`) are skipped when choosing the next
159
+ pickup but do not count as done.
814
160
 
815
- **Lifecycle-override gotcha.** If your config has both rich-form `types` and an
816
- explicit `export const lifecycle = {...}`, the explicit lifecycle silently
817
- overrides per-status flags at runtime. `dotmd statuses` write commands refuse
818
- to write into that state and recommend deleting the explicit `lifecycle` block;
819
- pass `--ignore-lifecycle-override` to write anyway.
820
-
821
- ### Rename
161
+ For a larger prose-first domain map, create a coordination runlist:
822
162
 
823
163
  ```bash
824
- dotmd rename old-name.md new-name # renames + updates refs
164
+ dotmd new plan platform-work --coordination
165
+ dotmd runlists
825
166
  ```
826
167
 
827
- ### Migrate
168
+ For progress across several runlists, create a roadmap:
828
169
 
829
170
  ```bash
830
- dotmd migrate status research scoping # rename a status (e.g. for the 0.15 default-vocab change)
831
- dotmd migrate module auth identity # rename a module
832
-
833
- # Per-file form: split one overloaded status into several distinct ones.
834
- # Only the listed files are rewritten; every other doc with the old value is left alone.
835
- dotmd migrate status backlog paused docs/plans/foo.md docs/plans/bar.md
836
- dotmd migrate status backlog partial docs/plans/payments-future.md # one at a time also works
171
+ dotmd new plan platform-roadmap --roadmap
172
+ dotmd roadmap platform-roadmap
173
+ dotmd roadmap platform-roadmap next
837
174
  ```
838
175
 
839
- With no file args, `migrate` rewrites every doc whose field matches
840
- `<old-value>` (whole-bucket rename). Pass file args to scope the
841
- rewrite — useful when one status has been doing several jobs and you
842
- want to split it across the new vocabulary. File args match the same
843
- way as `bulk archive`: exact path first, then substring fallback
844
- against full path or basename.
845
-
846
- ### Preset Aliases
176
+ Roadmaps roll up progress recursively and choose the first startable plan across
177
+ their child runlists. Runlists and roadmaps are held out of actionable plan
178
+ counts so dashboards do not double-count their children.
847
179
 
848
- Built-in presets: `plans`, `stale`, `actionable`. Add your own in config:
180
+ ## Safety Model
849
181
 
850
- ```js
851
- export const presets = {
852
- mine: ['--owner', 'robert', '--status', 'active', '--all'],
853
- blocked: ['--status', 'blocked', '--all'],
854
- };
855
- ```
182
+ - Mutation commands support `--dry-run` / `-n`.
183
+ - Managed writes are confined to configured document roots.
184
+ - Lifecycle and multi-file moves use atomic, conflict-aware mutation paths.
185
+ - Session ownership is durable local state, not inferred from telemetry.
186
+ - Passive orientation commands do not mutate repository state.
187
+ - Repository paths in machine and human output use stable slash-normalized
188
+ identities across supported operating systems.
856
189
 
857
- Then run `dotmd mine` or `dotmd blocked` as shorthand. All presets support query flags (`--json`, `--sort`, etc.).
190
+ ## Command Reference
858
191
 
859
- ### Watch Mode
192
+ The CLI is the source of truth for command syntax and options:
860
193
 
861
194
  ```bash
862
- dotmd watch # re-run list on every .md change
863
- dotmd watch check # live validation
864
- dotmd watch context # live briefing
195
+ dotmd --help
196
+ dotmd help all
197
+ dotmd help statuses
198
+ dotmd <command> --help
865
199
  ```
866
200
 
867
- ### Diff & Summarize
201
+ Shell completion is generated from the same command registry:
868
202
 
869
203
  ```bash
870
- dotmd diff # all drifted docs
871
- dotmd diff docs/plans/auth.md # single file
872
- dotmd diff --stat # summary stats only
873
- dotmd diff --summarize # AI summary via local MLX model
204
+ eval "$(dotmd completions bash)"
205
+ eval "$(dotmd completions zsh)"
874
206
  ```
875
207
 
876
- ### Init Auto-Detect
877
-
878
- When `dotmd init` runs in a directory with existing `.md` files, it scans them and pre-populates the config with discovered statuses, surfaces, modules, and reference fields.
208
+ This README intentionally documents onboarding and concepts instead of
209
+ duplicating the complete command catalog.
879
210
 
880
211
  ## Configuration
881
212
 
882
- Create `dotmd.config.mjs` at your project root (or run `dotmd init`).
883
-
884
- ### Rich status definitions (recommended)
885
-
886
- Define each status as an object that co-locates all behavioral properties. Adding a new status is one line in one place — no need to update separate `lifecycle`, `staleDays`, `context`, or `taxonomy` sections.
213
+ Run `dotmd init` to create `dotmd.config.mjs`. A minimal typed configuration:
887
214
 
888
215
  ```js
889
- export const root = 'docs/plans';
216
+ export const root = 'docs';
890
217
  export const archiveDir = 'archived';
891
218
 
892
219
  export const types = {
893
220
  plan: {
894
221
  statuses: {
895
- 'active': { context: 'expanded', staleDays: 14, requiresModule: true },
896
- 'planned': { context: 'listed', staleDays: 30, requiresModule: true },
897
- 'blocked': { context: 'listed', skipStale: true },
898
- 'archived': { context: 'counted', archive: true, terminal: true, skipStale: true, skipWarnings: true },
222
+ 'in-session': { context: 'expanded', staleDays: 1 },
223
+ active: { context: 'expanded', staleDays: 14 },
224
+ planned: { context: 'listed', staleDays: 30 },
225
+ archived: {
226
+ context: 'counted',
227
+ archive: true,
228
+ terminal: true,
229
+ skipStale: true,
230
+ skipWarnings: true,
231
+ },
899
232
  },
900
233
  },
901
234
  };
902
235
  ```
903
236
 
904
- **Status properties:**
905
-
906
- | Property | Type | Default | Effect |
907
- |---|---|---|---|
908
- | `context` | `'expanded'` \| `'listed'` \| `'counted'` | `'counted'` | Display mode in `dotmd context` |
909
- | `staleDays` | `number` \| `null` | `null` | Days before doc is stale (`null` = never) |
910
- | `requiresModule` | `boolean` | `false` | Require `module` in frontmatter |
911
- | `terminal` | `boolean` | `false` | Skip `current_state`/`next_step` warnings |
912
- | `archive` | `boolean` | `false` | Auto-move to `archiveDir` on transition |
913
- | `skipStale` | `boolean` | `false` | Exempt from stale checks |
914
- | `skipWarnings` | `boolean` | `false` | Exempt from validation warnings |
915
-
916
- Object key order determines display order. The config resolver derives `statuses.order`, `lifecycle.*`, `taxonomy.moduleRequiredFor`, and `context.*` from these definitions. Explicit global sections still win when provided.
917
-
918
- **Contradiction check.** Combining `skipStale: true` with a `staleDays` value, or `skipWarnings: true` with `requiresModule: true`, makes one of the fields dead config — the boolean wins silently. From 0.36.2, dotmd `warn()`s at config load when it spots either pair, naming the type, status, and conflicting fields. Drop one to silence the warning. The same check runs against the `quiet: true` sugar (which implies both `skipStale` and `skipWarnings` unless explicitly overridden).
919
-
920
- ### Array form (also supported)
921
-
922
- The traditional array form remains fully backwards compatible:
923
-
924
- ```js
925
- export const types = {
926
- plan: {
927
- statuses: ['active', 'planned', 'blocked', 'archived'],
928
- context: { expanded: ['active'], listed: ['planned', 'blocked'], counted: ['archived'] },
929
- staleDays: { active: 14, planned: 30, blocked: 30 },
930
- },
931
- };
932
-
933
- // When using array form, define behavior in separate sections:
934
- export const statuses = {
935
- order: ['active', 'planned', 'blocked', 'archived'],
936
- staleDays: { active: 14, planned: 30, blocked: 30 },
937
- };
938
-
939
- export const lifecycle = {
940
- archiveStatuses: ['archived'],
941
- skipStaleFor: ['archived'],
942
- skipWarningsFor: ['archived'],
943
- terminalStatuses: ['archived'],
944
- // Types that archive into their own <typeDir>/<archiveDir> (e.g.
945
- // docs/prompts/archived/) instead of the shared <root>/<archiveDir>.
946
- // Defaults to ['prompt'] so session-local prompt churn doesn't bury
947
- // plans and docs in the shared archive. Set to [] to disable.
948
- archiveNestedTypes: ['prompt'],
949
- };
950
-
951
- export const taxonomy = {
952
- moduleRequiredFor: ['active', 'planned', 'blocked'],
953
- };
954
- ```
955
-
956
- ### Other config
957
-
958
- ```js
959
- export const taxonomy = {
960
- surfaces: ['web', 'ios', 'backend', 'api', 'platform'],
961
- };
962
-
963
- export const referenceFields = {
964
- bidirectional: ['related_plans'], // warn if A→B but B↛A
965
- unidirectional: ['supports_plans'], // one-way, no symmetry check
966
- };
967
- // Per-ref opt-out: prefix any value with `>` to mark that specific ref one-way
968
- // without changing the field's default. Useful for leaf→upstream-parent refs
969
- // (audits, hub docs) where a back-ref would force editing a stable parent.
970
- // related_docs:
971
- // - docs/sibling-design.md # bidirectional (default for the field)
972
- // - "> docs/audit-beyond-platform.md" # one-way upstream — no back-ref expected
973
-
974
- export const index = {
975
- path: 'docs/docs.md',
976
- startMarker: '<!-- GENERATED:dotmd:start -->',
977
- endMarker: '<!-- GENERATED:dotmd:end -->',
978
- snapshot: 'status', // default; use 'state' to include live current_state text
979
- };
980
- ```
981
-
982
- Generated indexes default to status-only rows for live sections so README files
983
- do not become stale mirrors of volatile `current_state` text. Set
984
- `snapshot: 'state'` if you want the older `Status Snapshot` table for live
985
- sections too. Archived highlights still include their historical snapshots.
986
-
987
- All exports are optional. Additional options: `context`, `display`, `presets`, `templates`, `excludeDirs`, `notion`. See [`dotmd.config.example.mjs`](dotmd.config.example.mjs) for the full reference.
988
-
989
- Config discovery walks up from cwd looking for `dotmd.config.mjs` or `.dotmd.config.mjs`.
237
+ Configuration supports multiple roots, custom types and templates, taxonomy,
238
+ reference fields, presets, rendering, lifecycle hooks, validation hooks, and
239
+ AI summarization hooks. See [`dotmd.config.example.mjs`](dotmd.config.example.mjs)
240
+ for the complete annotated reference.
990
241
 
991
242
  ## Hooks
992
243
 
993
- Hooks are function exports in your config file. They let you extend validation, customize rendering, and react to lifecycle events.
994
-
995
- ### Custom Validation
996
-
997
- ```js
998
- export function validate(doc, ctx) {
999
- const warnings = [];
1000
- if (doc.status === 'active' && !doc.owner) {
1001
- warnings.push({
1002
- path: doc.path, level: 'warning',
1003
- message: 'Active docs should have an owner.',
1004
- });
1005
- }
1006
- return { errors: [], warnings };
1007
- }
1008
- ```
1009
-
1010
- ### Render Hooks
1011
-
1012
- Override any renderer by exporting a function that receives the default:
1013
-
1014
- ```js
1015
- export function renderContext(index, defaultRenderer) {
1016
- let output = defaultRenderer(index);
1017
- return `# My Project\n\n${output}`;
1018
- }
1019
- ```
1020
-
1021
- Available: `renderContext`, `renderCompactList`, `renderCheck`, `renderGraph`, `renderStats`, `formatSnapshot`.
1022
-
1023
- ### Lifecycle Hooks
1024
-
1025
- ```js
1026
- export function onArchive(doc, { oldPath, newPath }) {
1027
- console.log(`Archived: ${oldPath} → ${newPath}`);
1028
- }
1029
- ```
1030
-
1031
- Available: `onArchive`, `onStatusChange`, `onTouch`, `onNew`, `onRename`, `onLint`.
1032
-
1033
- ### Transform Hooks
1034
-
1035
- ```js
1036
- // Add computed fields to every doc after parsing
1037
- export function transformDoc(doc) {
1038
- doc.priority = doc.blockers?.length ? 'high' : 'normal';
1039
- return doc;
1040
- }
1041
- ```
1042
-
1043
- ### AI Hooks
1044
-
1045
- ```js
1046
- // Override doc summarization (replaces local MLX model)
1047
- export function summarizeDoc(body, meta) {
1048
- return 'Custom summary for ' + meta.title;
1049
- }
1050
-
1051
- // Override diff summarization
1052
- export function summarizeDiff(diffOutput, filePath) {
1053
- return `Changes in ${filePath}: ...`;
1054
- }
1055
- ```
1056
-
1057
- ## Features
1058
-
1059
- - **Git-aware** — detects frontmatter date drift vs git history, uses `git mv` for archives
1060
- - **Dry-run everything** — preview any mutation with `--dry-run` / `-n`
1061
- - **Multi-root** — manage docs across multiple directories with `--root` filtering
1062
- - **Configurable** — statuses, taxonomy, lifecycle, validation rules, display, templates
1063
- - **Hook system** — extend with JS functions, no plugin framework to learn
1064
- - **AI-powered** — local MLX summaries for docs, queries, diffs, and context briefings
1065
- - **Notion sync** — import, export, and bidirectional sync with Notion databases
1066
- - **LLM-friendly** — `dotmd context` generates compact briefings for AI assistants
1067
- - **Shell completion** — bash and zsh via `dotmd completions`
244
+ Functions exported from `dotmd.config.mjs` are detected as hooks. They can add
245
+ validation, customize rendering and summaries, or react to lifecycle events.
246
+ Hooks receive the resolved config and command context; mutation hooks participate
247
+ in the command's dry-run and failure contracts.
1068
248
 
1069
249
  ## License
1070
250