dotmd-cli 0.32.1 → 0.33.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotmd-cli",
3
- "version": "0.32.1",
3
+ "version": "0.33.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",
@@ -46,6 +46,22 @@ function generatePlansCommand(config) {
46
46
  return lines.join('\n');
47
47
  }
48
48
 
49
+ function generateBatonCommand() {
50
+ const lines = [VERSION_MARKER, ''];
51
+ lines.push('You are wrapping this session. Hand the baton cleanly to the next one.');
52
+ lines.push('');
53
+ lines.push('1. **Update the in-flight plan.** Find it via `dotmd plans --status in-session`. Edit its `current_state:` / `next_step:` frontmatter so they reflect where things actually stand. If status should change (shipped → archive, stuck on a human decision → awaiting, etc.), transition with `dotmd status <file> <status>` — or `dotmd archive <file>` if work is done.');
54
+ lines.push('');
55
+ lines.push('2. **Save ONE lean handoff prompt.** Run `dotmd new prompt resume-<plan-slug>` with a body of ~10-20 lines: point at the plan file, name the next concrete decision, flag any gotchas. Do NOT recap the plan body (the plan is for that). Do NOT print the handoff into chat for the user to copy-paste — the saved prompt is the handoff.');
56
+ lines.push('');
57
+ lines.push('3. **Release the lease.** `dotmd release` (skip if `dotmd archive` already closed out — archive auto-releases).');
58
+ lines.push('');
59
+ lines.push('The next session\'s `dotmd hud` (SessionStart hook) surfaces the pending prompt automatically.');
60
+ lines.push('');
61
+
62
+ return lines.join('\n');
63
+ }
64
+
49
65
  function generateDocsCommand(config) {
50
66
  const roots = Array.isArray(config.raw?.root) ? config.raw.root : [config.raw?.root ?? 'docs'];
51
67
  const rootCount = roots.length;
@@ -118,6 +134,7 @@ export function scaffoldClaudeCommands(cwd, config, opts = {}) {
118
134
  const files = [
119
135
  { name: 'plans.md', generate: () => generatePlansCommand(config) },
120
136
  { name: 'docs.md', generate: () => generateDocsCommand(config) },
137
+ { name: 'baton.md', generate: () => generateBatonCommand() },
121
138
  ];
122
139
 
123
140
  for (const { name, generate } of files) {
@@ -149,12 +166,24 @@ export function scaffoldClaudeCommands(cwd, config, opts = {}) {
149
166
  return results;
150
167
  }
151
168
 
169
+ // Self-heal: regen any slash-command file whose banner is older than pkg.version.
170
+ // Designed for runHud to call at SessionStart — closes the gap between "user
171
+ // upgraded dotmd" and "slash-command body reflects the new version" without
172
+ // requiring a manual `dotmd doctor`. Returns only the entries that actually
173
+ // changed so the caller can surface a one-line note; an empty array means the
174
+ // hud silent-clean contract is preserved. `skipped` (user-managed, no banner)
175
+ // and `current` entries are filtered out — callers don't care about them.
176
+ export function refreshStaleSlashCommands(config) {
177
+ const results = scaffoldClaudeCommands(config.repoRoot, config);
178
+ return results.filter(r => r.action === 'updated');
179
+ }
180
+
152
181
  export function checkClaudeCommands(cwd) {
153
182
  const commandsDir = path.join(cwd, '.claude', 'commands');
154
183
  if (!existsSync(commandsDir)) return [];
155
184
 
156
185
  const warnings = [];
157
- for (const name of ['plans.md', 'docs.md']) {
186
+ for (const name of ['plans.md', 'docs.md', 'baton.md']) {
158
187
  const filePath = path.join(commandsDir, name);
159
188
  const installedVersion = getInstalledVersion(filePath);
160
189
  if (installedVersion && installedVersion !== pkg.version) {
package/src/hud.mjs CHANGED
@@ -5,6 +5,7 @@ import { extractFrontmatter, parseSimpleFrontmatter } from './frontmatter.mjs';
5
5
  import { asString, toRepoPath } from './util.mjs';
6
6
  import { green, yellow, red, dim } from './color.mjs';
7
7
  import { buildIndex } from './index.mjs';
8
+ import { refreshStaleSlashCommands } from './claude-commands.mjs';
8
9
 
9
10
  const MAX_PREVIEW = 5;
10
11
 
@@ -89,6 +90,15 @@ export function runHud(argv, config) {
89
90
  const json = argv.includes('--json');
90
91
  const hud = buildHud(config);
91
92
 
93
+ // Self-heal stale slash-command files. Wrapped: a broken scaffolder must
94
+ // never kill the SessionStart hook (would block every session). Skipped in
95
+ // --json mode to keep the structured shape stable for programmatic callers.
96
+ let refreshed = [];
97
+ if (!json) {
98
+ try { refreshed = refreshStaleSlashCommands(config); }
99
+ catch { /* swallow — see comment above */ }
100
+ }
101
+
92
102
  if (json) {
93
103
  process.stdout.write(JSON.stringify(hud, null, 2) + '\n');
94
104
  return;
@@ -107,6 +117,12 @@ export function runHud(argv, config) {
107
117
  if (hud.errors > 0) {
108
118
  lines.push(red(`✗ ${hud.errors} validation error${hud.errors === 1 ? '' : 's'} ${dim('(run: dotmd check)')}`));
109
119
  }
120
+ if (refreshed.length > 0) {
121
+ const from = refreshed[0].from;
122
+ const to = refreshed[0].to;
123
+ const names = refreshed.map(r => r.name).join(', ');
124
+ lines.push(dim(`↻ slash commands refreshed (v${from} → v${to}): ${names}`));
125
+ }
110
126
 
111
127
  if (lines.length === 0) return; // silent when clean
112
128
  process.stdout.write(lines.join('\n') + '\n');