dotmd-cli 0.38.1 → 0.39.1

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.38.1",
3
+ "version": "0.39.1",
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",
@@ -6,10 +6,29 @@ import { green, dim, yellow } from './color.mjs';
6
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
7
  const pkg = JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
8
8
  const VERSION_MARKER = `<!-- dotmd-generated: ${pkg.version} -->`;
9
- const VERSION_REGEX = /^<!-- dotmd-generated: ([\d.]+) -->/;
9
+ // Marker is no longer pinned to line 1 — it now lives below the YAML
10
+ // frontmatter that Claude Code surfaces as the slash command's description.
11
+ // The regex is intentionally non-anchored so getInstalledVersion finds it
12
+ // wherever it sits, and the marker string is specific enough that a false
13
+ // positive elsewhere in a user-edited file is not a realistic concern.
14
+ const VERSION_REGEX = /<!-- dotmd-generated: ([\d.]+) -->/;
15
+
16
+ // Trigger sentences surfaced by Claude Code's available-skills system reminder.
17
+ // Keep each ≤200 chars (well under the 1,536-char auto-load truncation limit)
18
+ // and front-load the "when to reach for it" cue so Claude can route to the
19
+ // right slash command without the user having to type the slash.
20
+ const SLASH_DESCRIPTIONS = {
21
+ plans: "dotmd-managed plan briefing for this repo. Use when the user asks what's on the plate, references a plan slug, queues work, or wants to pick up / release / archive a plan.",
22
+ docs: "dotmd-managed docs briefing for this repo. Use when the user asks to list, scaffold, query, validate, archive, or rename non-plan docs (reference docs, ADRs, RFCs, design notes), or asks how the dotmd doc lifecycle works here.",
23
+ baton: "Save a resume prompt for the held plan and release the lease — the minimum handoff. Use when the user says hand off / save a resume / wrap up, or when context is getting tight.",
24
+ };
25
+
26
+ function frontmatterFor(name) {
27
+ return ['---', `description: ${SLASH_DESCRIPTIONS[name]}`, '---'];
28
+ }
10
29
 
11
30
  function generatePlansCommand(config) {
12
- const lines = [VERSION_MARKER, ''];
31
+ const lines = [...frontmatterFor('plans'), VERSION_MARKER, ''];
13
32
  lines.push('Run `dotmd context` to get the current plans briefing, then use it to orient yourself.');
14
33
  lines.push('');
15
34
  lines.push(`Plans are managed by **dotmd** (v${pkg.version}). Config at \`dotmd.config.mjs\`. Always use \`dotmd\` directly.`);
@@ -47,14 +66,18 @@ function generatePlansCommand(config) {
47
66
  }
48
67
 
49
68
  function generateBatonCommand() {
50
- const lines = [VERSION_MARKER, ''];
51
- lines.push('You are wrapping this session. Hand the baton cleanly to the next one.');
69
+ const lines = [...frontmatterFor('baton'), VERSION_MARKER, ''];
70
+ lines.push('Wrap this session. Minimum required (two commands):');
71
+ lines.push('');
72
+ 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.');
52
73
  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.');
74
+ lines.push('2. **Release the lease.** `dotmd release` — or `dotmd archive <file>` if the work is fully shipped (archive auto-releases).');
54
75
  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.');
76
+ lines.push('Optional, only when genuinely needed:');
77
+ lines.push('- Status really changed (paused / awaiting / partial / blocked): `dotmd status <file> <status>` BEFORE `dotmd release`.');
78
+ 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.');
56
79
  lines.push('');
57
- lines.push('3. **Release the lease.** `dotmd release` (skip if `dotmd archive` already closed out archive auto-releases).');
80
+ 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.');
58
81
  lines.push('');
59
82
  lines.push('The next session\'s `dotmd hud` (SessionStart hook) surfaces the pending prompt automatically.');
60
83
  lines.push('');
@@ -66,7 +89,7 @@ function generateDocsCommand(config) {
66
89
  const roots = Array.isArray(config.raw?.root) ? config.raw.root : [config.raw?.root ?? 'docs'];
67
90
  const rootCount = roots.length;
68
91
 
69
- const lines = [VERSION_MARKER, ''];
92
+ const lines = [...frontmatterFor('docs'), VERSION_MARKER, ''];
70
93
  lines.push(`All documentation in this repo is managed by **dotmd** (v${pkg.version}). Docs across ${rootCount} root${rootCount > 1 ? 's' : ''}: ${roots.join(', ')}. Config at \`dotmd.config.mjs\`.`);
71
94
  lines.push('');
72
95
 
package/src/hud.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, readdirSync, readFileSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { readLeases, findStaleLeases, currentSessionId } from './lease.mjs';
4
4
  import { extractFrontmatter, parseSimpleFrontmatter } from './frontmatter.mjs';
@@ -124,6 +124,21 @@ export function runHud(argv, config) {
124
124
  lines.push(dim(`↻ slash commands refreshed (v${from} → v${to}): ${names}`));
125
125
  }
126
126
 
127
+ // Teach-once primer for fresh installs. The first session that runs hud in
128
+ // a dotmd-managed repo gets one line explaining what dotmd is and how to
129
+ // hand off — without this, a clean repo emits nothing and Claude has no
130
+ // signal that dotmd is the prompt/handoff machinery here. Gated on a
131
+ // marker under .dotmd/ (already the per-repo state dir, already gitignored
132
+ // by `dotmd init`) so subsequent sessions stay silent.
133
+ const primerMarker = path.join(config.repoRoot, '.dotmd', 'primer-shown');
134
+ if (!existsSync(primerMarker)) {
135
+ lines.push(dim('dotmd: managing this repo\'s docs. Use `dotmd new prompt` for handoffs (never hand-write docs/prompts/*.md). `dotmd plans` shows the queue. `dotmd --help` for more.'));
136
+ try {
137
+ mkdirSync(path.dirname(primerMarker), { recursive: true });
138
+ writeFileSync(primerMarker, '');
139
+ } catch { /* best-effort — failed marker write just means the primer reprints next session */ }
140
+ }
141
+
127
142
  if (lines.length === 0) return; // silent when clean
128
143
  process.stdout.write(lines.join('\n') + '\n');
129
144
  }