dotmd-cli 0.43.0 → 0.44.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/bin/dotmd.mjs CHANGED
@@ -14,6 +14,26 @@ const pkg = JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json'),
14
14
  const HELP = {
15
15
  _main: `dotmd v${pkg.version} — frontmatter markdown document manager
16
16
 
17
+ Common commands:
18
+ plans Live plans (excludes archived)
19
+ briefing Full briefing with plan counts + next steps
20
+ set <status> [file] Transition status (start work, finish, archive — all via target status)
21
+ new <type> <name> Create plan/doc/prompt (pipe stdin or @path for body)
22
+ archive <file> Close out a plan (status → archived, move, update refs)
23
+ prompts [next|use|new] Manage saved prompts
24
+
25
+ More help:
26
+ dotmd help all Full command list
27
+ dotmd help statuses Status vocabulary + transitions
28
+ dotmd <cmd> --help Per-command details
29
+
30
+ Global flags: --config <path> --root <name> --type <t,…> --dry-run/-n --verbose --version`,
31
+
32
+ // Full command list — opt-in via \`dotmd help all\`. Kept exhaustive so the
33
+ // top-level \`--help\` can stay terse without losing discoverability. When you
34
+ // add a new command, add it here too.
35
+ 'help:all': `dotmd v${pkg.version} — full command list
36
+
17
37
  View & Query:
18
38
  hud [--json] Two-line actionable triage (held / prompts / stuck) — silent when clean
19
39
  list [--verbose] [--json] List docs grouped by status (default command)
@@ -42,17 +62,14 @@ Analyze:
42
62
  glossary <term> [--list] [--json] Look up domain terms + related docs
43
63
 
44
64
  Validate & Fix:
45
- check [--fix] [--errors-only] [--json] Validate frontmatter and references
46
65
  doctor [--apply] Auto-fix everything: refs, lint, dates, index (preview by default)
47
66
  lint [--fix] Check and auto-fix frontmatter issues
48
67
  fix-refs [--dry-run] Auto-fix broken reference paths + body links
49
68
 
50
69
  Lifecycle:
51
- pickup <file> [--takeover] Pick up a plan (set in-session + print body)
52
- release [<file>] [--to <s>] Release in-session lease (alias: unpickup)
70
+ set <status> [<file>] Unified transition: archive/release/start/transition in one verb
53
71
  runlist <hub> [next] Show or walk an ordered group of plans (see \`dotmd help runlist\`)
54
72
  status <file> <status> Transition document status (deprecated; prefer \`set\`)
55
- set <status> [<file>] Unified transition: archive/release/transition in one verb
56
73
  archive <file> Archive (status + move + update refs)
57
74
  bulk archive <f1> <f2> ... Archive multiple files at once
58
75
  ship [patch|minor|major] Regen + commit + bump in one step (default: patch)
@@ -1042,7 +1059,7 @@ async function main() {
1042
1059
  const key = `help:${topic}`;
1043
1060
  if (HELP[key]) { process.stdout.write(`${HELP[key]}\n`); return; }
1044
1061
  if (HELP[topic]) { process.stdout.write(`${HELP[topic]}\n`); return; }
1045
- process.stderr.write(`Unknown help topic: ${topic}\n\nAvailable topics: statuses\nPer-command help: dotmd <cmd> --help\n`);
1062
+ process.stderr.write(`Unknown help topic: ${topic}\n\nAvailable topics: all, statuses\nPer-command help: dotmd <cmd> --help\n`);
1046
1063
  process.exit(1);
1047
1064
  }
1048
1065
  process.stdout.write(`${HELP._main}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotmd-cli",
3
- "version": "0.43.0",
3
+ "version": "0.44.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",
@@ -63,19 +63,18 @@ function generatePlansCommand(config, version) {
63
63
  lines.push('');
64
64
  lines.push('Plan-specific commands:');
65
65
  lines.push('- `dotmd context` — briefing with active/paused/ready plans, age tags, next steps');
66
- lines.push('- `dotmd pickup <file>`pick up a plan (set in-session + print body)');
67
- lines.push('- `dotmd release`release current session\'s leases (alias: unpickup)');
68
- lines.push('- `dotmd health` — plan velocity, aging, checklist progress, pipeline view');
69
- lines.push('- `dotmd unblocks <file>` — what depends on / is blocked by a plan');
70
- lines.push('- `dotmd actionable`ready plans with next steps (what to promote)');
66
+ lines.push('- `dotmd set <status> [<file>]`single status verb. Use this to start, transition, or close any plan:');
67
+ lines.push(' - `dotmd set in-session <file>` start work on a plan (acquires the lease + prints body)');
68
+ lines.push(' - `dotmd set <status> [<file>]` — transition to any other status; if you held the lease it releases automatically');
69
+ lines.push(' - `dotmd set archived <file>` — close out (same as `dotmd archive`)');
70
+ lines.push('- `dotmd archive <file>` explicit archive with ref-fixing (equivalent to `set archived`)');
71
+ lines.push('- `dotmd bulk archive <files>` — archive multiple at once');
71
72
  lines.push('- `dotmd new plan <name>` — scaffold with full phase structure');
72
- lines.push('- `dotmd prompts new <name> "<body>"` — save a resume-prompt to docs/prompts/');
73
+ lines.push('- `dotmd prompts new <name>` — save a resume-prompt to docs/prompts/ (pipe stdin or @path for body)');
73
74
  lines.push('- `dotmd prompts next` — consume oldest pending prompt (prints body, auto-archives)');
74
75
  lines.push('- `dotmd prompts use <file>` — consume a specific prompt (prints body, auto-archives)');
75
- lines.push('- `dotmd archive <file>` — archive with auto ref-fixing (both directions)');
76
- lines.push('- `dotmd bulk archive <files>` archive multiple at once');
77
- lines.push('- `dotmd set <status> [<file>]` — unified transition (archive / release / status bump in one verb; infers path from held lease)');
78
- lines.push('- `dotmd status <file> <status>` — transition status (legacy; `set` is preferred)');
76
+ lines.push('- `dotmd unblocks <file>` — what depends on / is blocked by a plan');
77
+ lines.push('- `dotmd actionable` ready plans with next steps (what to promote)');
79
78
  lines.push('- `dotmd query --keyword <term>` — find plans by keyword');
80
79
 
81
80
  if (config.raw?.glossary) {
@@ -85,8 +84,8 @@ function generatePlansCommand(config, version) {
85
84
  lines.push('');
86
85
  lines.push('If the user asks about a specific plan, read its file directly (path is in the briefing or findable via `dotmd query --keyword <term>`).');
87
86
  lines.push('');
88
- lines.push('If the user asks to change a plan\'s status, use `dotmd status <file> <status>`.');
89
- lines.push('If the user asks to archive a plan, use `dotmd archive <file>`.');
87
+ lines.push('If the user asks to change a plan\'s status, use `dotmd set <status> <file>`.');
88
+ lines.push('If the user asks to archive a plan, use `dotmd set archived <file>` (or `dotmd archive <file>`).');
90
89
  lines.push('');
91
90
  lines.push('**Saved prompts (`docs/prompts/*.md`):** if the user references a file under `docs/prompts/` — e.g. "resume via docs/prompts/foo.md", "use this prompt", "load that one" — consume it with `dotmd prompts use <file>` (atomically prints the body and archives the prompt so it cannot be double-consumed). Do NOT `cat` it, read it with the file-reading tool, or copy its body into chat. To pick the oldest pending prompt without naming a file, use `dotmd prompts next`.');
92
91
  lines.push('');
@@ -96,15 +95,15 @@ function generatePlansCommand(config, version) {
96
95
 
97
96
  function generateBatonCommand(config, version) {
98
97
  const lines = [...frontmatterFor('baton', config), markerFor(version), ''];
99
- lines.push('Wrap this session. Minimum required (two commands):');
100
- lines.push('');
101
- lines.push('1. **Save the resume prompt.** `dotmd new prompt resume-<plan-slug>` with a 10-20 line body via heredoc: the next concrete decision plus any gotchas. NOT a recap of the plan body. The saved prompt IS the handoff — never print it into chat for copy-paste.');
98
+ lines.push('Wrap this session. Two commands:');
102
99
  lines.push('');
103
- lines.push('2. **Release the lease.** `dotmd release` — or `dotmd archive <file>` if the work is fully shipped (archive auto-releases).');
100
+ lines.push('1. **Save the resume prompt.** `dotmd new prompt resume-<plan-slug>` pipe stdin or pass `@path`. 10-20 line body: the next concrete decision plus any gotchas. NOT a recap of the plan body. The saved prompt IS the handoff — never print it into chat for copy-paste.');
104
101
  lines.push('');
105
- lines.push('Optional, only when genuinely needed:');
106
- lines.push('- Status really changed (paused / awaiting / partial / blocked): `dotmd status <file> <status>` BEFORE `dotmd release`.');
107
- lines.push('- Plan dashboard text is misleading the user: edit `next_step:` in the plan frontmatter. Cosmetic the next session reads the resume prompt, not the plan frontmatter.');
102
+ lines.push('2. **Close out via `dotmd set <status>`.** Pick the status that matches reality:');
103
+ lines.push(' - `dotmd set active <file>` work continues, release the lease back to the queue');
104
+ lines.push(' - `dotmd set archived <file>`fully shipped (also: `dotmd archive <file>`)');
105
+ lines.push(' - `dotmd set paused <file>` / `awaiting <file>` / `partial <file>` / `blocked <file>` — when the status really changed');
106
+ lines.push(' `set` releases the held lease automatically when transitioning out of `in-session`.');
108
107
  lines.push('');
109
108
  lines.push('If you don\'t already know which plan you hold: `dotmd hud --json` and read `.owned`. Do NOT use `dotmd plans --status in-session` — that lists every session\'s holdings, not just yours.');
110
109
  lines.push('');
@@ -131,7 +130,6 @@ function generateDocsCommand(config, version) {
131
130
 
132
131
  lines.push('Commands for working with docs:');
133
132
  lines.push('- `dotmd context` — LLM-oriented briefing across all types');
134
- lines.push('- `dotmd check` — validate frontmatter, refs, body links (target: 0 errors)');
135
133
  lines.push('- `dotmd doctor` — auto-fix everything in one pass (refs, lint, dates, index)');
136
134
  lines.push('- `dotmd query [filters]` — search by status, keyword, module, surface, type, staleness');
137
135
  lines.push('- `dotmd health` — plan pipeline, velocity, aging');
package/src/hud.mjs CHANGED
@@ -1,10 +1,10 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
- import { readLeases, findStaleLeases, currentSessionId, STALE_LEASE_AGE_HOURS } from './lease.mjs';
3
+ import { readLeases, findStaleLeases, currentSessionId } from './lease.mjs';
4
4
  import { scrubStaleSilently } from './lease-scrub.mjs';
5
5
  import { extractFrontmatter, parseSimpleFrontmatter } from './frontmatter.mjs';
6
6
  import { asString, toRepoPath } from './util.mjs';
7
- import { green, yellow, red, dim } from './color.mjs';
7
+ import { dim } from './color.mjs';
8
8
  import { buildIndex } from './index.mjs';
9
9
  import { refreshStaleSlashCommands } from './claude-commands.mjs';
10
10
 
@@ -115,18 +115,15 @@ export function runHud(argv, config) {
115
115
  }
116
116
 
117
117
  const lines = [];
118
- if (hud.owned.length > 0) {
119
- lines.push(green(`▶ You hold ${hud.owned.length} plan${hud.owned.length === 1 ? '' : 's'}: ${previewList(hud.owned)}`));
120
- }
121
- if (hud.prompts.length > 0) {
122
- lines.push(green(`▶ ${hud.prompts.length} pending prompt${hud.prompts.length === 1 ? '' : 's'}: ${previewList(hud.prompts)} ${dim('(consume: `dotmd prompts use <file>` — do not cat/read)')}`));
123
- }
124
- if (hud.stale.length > 0) {
125
- lines.push(yellow(`⚠ ${hud.stale.length} stuck lease${hud.stale.length === 1 ? '' : 's'} >${STALE_LEASE_AGE_HOURS}h ${dim('(run: dotmd release --stale)')}`));
126
- }
127
- if (hud.errors > 0) {
128
- lines.push(red(`✗ ${hud.errors} validation error${hud.errors === 1 ? '' : 's'} ${dim('(run: dotmd check)')}`));
129
- }
118
+
119
+ // Always-on command primer. Replaces the prior plan-state / prompts /
120
+ // stuck-leases / validation-errors lines — those signals belong inside
121
+ // their own commands (`plans`, `prompts`), not in the SessionStart hook.
122
+ // hud's job is purely to remind the agent which verbs exist, since that's
123
+ // the one thing the agent reaches for `--help` to recover. Keep it tight:
124
+ // one line, the minimum verb set.
125
+ lines.push(dim('dotmd: plans|briefing set <status> [<file>] new <type> <slug> prompts next|use|new archive <file>'));
126
+
130
127
  if (refreshed.length > 0) {
131
128
  const from = refreshed[0].from;
132
129
  const to = refreshed[0].to;
@@ -134,21 +131,5 @@ export function runHud(argv, config) {
134
131
  lines.push(dim(`↻ slash commands refreshed (v${from} → v${to}): ${names}`));
135
132
  }
136
133
 
137
- // Teach-once primer for fresh installs. The first session that runs hud in
138
- // a dotmd-managed repo gets one line explaining what dotmd is and how to
139
- // hand off — without this, a clean repo emits nothing and Claude has no
140
- // signal that dotmd is the prompt/handoff machinery here. Gated on a
141
- // marker under .dotmd/ (already the per-repo state dir, already gitignored
142
- // by `dotmd init`) so subsequent sessions stay silent.
143
- const primerMarker = path.join(config.repoRoot, '.dotmd', 'primer-shown');
144
- if (!existsSync(primerMarker)) {
145
- lines.push(dim('dotmd: managing this repo\'s docs. Save in one shot: `cat draft.md | dotmd new <type> <slug>` (or `dotmd new <type> <slug> @path`). Types: plan, doc, prompt. `dotmd plans` shows the queue.'));
146
- try {
147
- mkdirSync(path.dirname(primerMarker), { recursive: true });
148
- writeFileSync(primerMarker, '');
149
- } catch { /* best-effort — failed marker write just means the primer reprints next session */ }
150
- }
151
-
152
- if (lines.length === 0) return; // silent when clean
153
134
  process.stdout.write(lines.join('\n') + '\n');
154
135
  }
package/src/lifecycle.mjs CHANGED
@@ -303,11 +303,11 @@ export async function runPickup(argv, config, opts = {}) {
303
303
  const pickupable = new Set(['active', 'planned', 'in-session']);
304
304
  if (oldStatus && !pickupable.has(oldStatus)) {
305
305
  die(
306
- `Cannot pick up a plan with status '${oldStatus}'. Must be active or planned.\n` +
306
+ `Cannot start work on a plan with status '${oldStatus}'. Must be active or planned.\n` +
307
307
  ` ${repoPath}\n` +
308
308
  `\n` +
309
309
  `Recover with:\n` +
310
- ` dotmd status ${repoPath} active && dotmd pickup ${repoPath}`,
310
+ ` dotmd set active ${repoPath} && dotmd set in-session ${repoPath}`,
311
311
  );
312
312
  }
313
313
 
@@ -370,7 +370,7 @@ export async function runPickup(argv, config, opts = {}) {
370
370
  process.stderr.write(`${green('▶ Picked up')}: ${repoPath} (${oldStatus ?? 'unset'} → in-session)\n\n`);
371
371
  }
372
372
  if (fullBody) {
373
- const header = `[dotmd] holding ${repoPath} — release with: dotmd release ${repoPath}\n---\n`;
373
+ const header = `[dotmd] holding ${repoPath} — close with: dotmd set <status> ${repoPath}\n---\n`;
374
374
  process.stdout.write(header);
375
375
  const content = (body ?? '').trim();
376
376
  if (content) process.stdout.write(content + '\n');
@@ -665,8 +665,14 @@ export async function runSet(argv, config, opts = {}) {
665
665
 
666
666
  if (!newStatus) die('Usage: dotmd set <status> [<path>]');
667
667
 
668
+ // `set in-session` acquires a lease + prints the plan body (same as the
669
+ // legacy `pickup` verb). Delegated so `set` is the single status verb.
668
670
  if (newStatus === 'in-session') {
669
- die('To acquire an in-session lease, use `dotmd pickup <file>`. `dotmd set` is for releasing/transitioning a held lease.');
671
+ const pickArgs = input ? [input] : [];
672
+ if (argv.includes('--takeover')) pickArgs.push('--takeover');
673
+ if (noIndex) pickArgs.push('--no-index');
674
+ if (showFiles) pickArgs.push('--show-files');
675
+ return runPickup(pickArgs, config, { dryRun });
670
676
  }
671
677
 
672
678
  if (!input) {
@@ -194,7 +194,7 @@ export function buildCard(filePath, raw, config) {
194
194
  // Render the card to human-format string.
195
195
  export function renderCard(card) {
196
196
  const lines = [];
197
- lines.push(`[dotmd] holding ${card.path} — release with: dotmd release ${card.path}`);
197
+ lines.push(`[dotmd] holding ${card.path} — close with: dotmd set <status> ${card.path}`);
198
198
  lines.push('---');
199
199
  lines.push(`# ${card.title}`);
200
200
  const meta = [card.status, card.updated && `updated ${card.updated}`].filter(Boolean).join(' · ');
@@ -252,7 +252,7 @@ export function renderCard(card) {
252
252
  }
253
253
 
254
254
  lines.push('');
255
- lines.push(dim(`Body: ${formatBytes(card.bodyBytes)}. \`dotmd pickup ${card.path} --full\` for everything, or Read the file with offset/limit to target a section by line number.`));
255
+ lines.push(dim(`Body: ${formatBytes(card.bodyBytes)}. \`dotmd set in-session ${card.path} --full\` for everything, or Read the file with offset/limit to target a section by line number.`));
256
256
  return lines.join('\n') + '\n';
257
257
  }
258
258
 
package/src/validate.mjs CHANGED
@@ -186,14 +186,14 @@ export function validateDoc(doc, frontmatter, headingTitle, config) {
186
186
  doc.warnings.push({
187
187
  path: doc.path,
188
188
  level: 'warning',
189
- message: `\`status: in-session\` but no active lease found (last session may have crashed without releasing). Run \`dotmd release ${doc.path}\` to clear, or \`dotmd status ${doc.path} active\` to re-queue.`,
189
+ message: `\`status: in-session\` but no active lease found (last session may have crashed without releasing). Run \`dotmd set active ${doc.path}\` to clear and re-queue.`,
190
190
  });
191
191
  } else if (isLeaseStale(lease)) {
192
192
  const ageHours = Math.floor((Date.now() - new Date(lease.pickedUpAt).getTime()) / (1000 * 60 * 60));
193
193
  doc.warnings.push({
194
194
  path: doc.path,
195
195
  level: 'warning',
196
- message: `\`status: in-session\` but lease is stale (last touched ${ageHours}h ago, >${STALE_LEASE_AGE_HOURS}h threshold). Run \`dotmd release ${doc.path}\` to clear, or \`dotmd status ${doc.path} active\` to re-queue.`,
196
+ message: `\`status: in-session\` but lease is stale (last touched ${ageHours}h ago, >${STALE_LEASE_AGE_HOURS}h threshold). Run \`dotmd set active ${doc.path}\` to clear and re-queue.`,
197
197
  });
198
198
  }
199
199
  }