dotmd-cli 0.11.0 → 0.11.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/bin/dotmd.mjs CHANGED
@@ -393,12 +393,6 @@ async function main() {
393
393
  return;
394
394
  }
395
395
 
396
- // Init and completions don't need config
397
- if (command === 'init') {
398
- runInit(process.cwd());
399
- return;
400
- }
401
-
402
396
  if (command === 'completions') {
403
397
  runCompletions(args.slice(1));
404
398
  return;
@@ -418,6 +412,12 @@ async function main() {
418
412
 
419
413
  const config = await resolveConfig(process.cwd(), explicitConfig);
420
414
 
415
+ // Init — now has access to config for Claude command generation
416
+ if (command === 'init') {
417
+ runInit(process.cwd(), config.configFound ? config : null);
418
+ return;
419
+ }
420
+
421
421
  // Watch is a pure proxy — pass raw args so the child process gets all flags
422
422
  if (command === 'watch') { runWatch(args.slice(1), config); return; }
423
423
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotmd-cli",
3
- "version": "0.11.0",
3
+ "version": "0.11.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",
@@ -0,0 +1,151 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { green, dim, yellow } from './color.mjs';
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const pkg = JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
8
+ const VERSION_MARKER = `<!-- dotmd-generated: ${pkg.version} -->`;
9
+ const VERSION_REGEX = /^<!-- dotmd-generated: ([\d.]+) -->/;
10
+
11
+ function generatePlansCommand(config) {
12
+ const lines = [VERSION_MARKER, ''];
13
+ lines.push('Run `dotmd context` to get the current plans briefing, then use it to orient yourself.');
14
+ lines.push('');
15
+ lines.push(`Plans are managed by **dotmd** (v${pkg.version}). Config at \`dotmd.config.mjs\`. Always use \`dotmd\` directly.`);
16
+ lines.push('');
17
+ lines.push('Plan-specific commands:');
18
+ lines.push('- `dotmd context` — briefing with active/paused/ready plans, age tags, next steps');
19
+ lines.push('- `dotmd health` — plan velocity, aging, checklist progress, pipeline view');
20
+ lines.push('- `dotmd unblocks <file>` — what depends on / is blocked by a plan');
21
+ lines.push('- `dotmd next` — ready plans with next steps (what to promote)');
22
+ lines.push('- `dotmd new <name> --template plan` — scaffold with full phase structure');
23
+ lines.push('- `dotmd archive <file>` — archive with auto ref-fixing (both directions)');
24
+ lines.push('- `dotmd bulk archive <files>` — archive multiple at once');
25
+ lines.push('- `dotmd status <file> <status>` — transition status');
26
+ lines.push('- `dotmd query --keyword <term>` — find plans by keyword');
27
+
28
+ if (config.raw?.glossary) {
29
+ lines.push('- `dotmd glossary <term>` — domain term lookup with related plans');
30
+ }
31
+
32
+ lines.push('');
33
+ 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>`).');
34
+ lines.push('');
35
+ lines.push('If the user asks to change a plan\'s status, use `dotmd status <file> <status>`.');
36
+ lines.push('If the user asks to archive a plan, use `dotmd archive <file>`.');
37
+ lines.push('');
38
+
39
+ return lines.join('\n');
40
+ }
41
+
42
+ function generateDocsCommand(config) {
43
+ const roots = Array.isArray(config.raw?.root) ? config.raw.root : [config.raw?.root ?? 'docs'];
44
+ const rootCount = roots.length;
45
+
46
+ const lines = [VERSION_MARKER, ''];
47
+ 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\`.`);
48
+ lines.push('');
49
+
50
+ // Document types from config
51
+ const types = config.raw?.types ? Object.keys(config.raw.types) : [];
52
+ if (types.length > 0) {
53
+ lines.push(`Document types: ${types.map(t => '`' + t + '`').join(', ')}.`);
54
+ lines.push('');
55
+ }
56
+
57
+ lines.push('Commands for working with docs:');
58
+ lines.push('- `dotmd context` — LLM-oriented briefing across all types');
59
+ lines.push('- `dotmd check` — validate frontmatter, refs, body links (target: 0 errors)');
60
+ lines.push('- `dotmd doctor` — auto-fix everything in one pass (refs, lint, dates, index)');
61
+ lines.push('- `dotmd query [filters]` — search by status, keyword, module, surface, type, staleness');
62
+ lines.push('- `dotmd health` — plan pipeline, velocity, aging');
63
+ lines.push('- `dotmd stats` — doc health dashboard (completeness, checklists, audit coverage)');
64
+ lines.push('- `dotmd graph [--dot]` — visualize document relationships');
65
+ lines.push('- `dotmd deps [file]` — dependency tree');
66
+ lines.push('- `dotmd unblocks <file>` — impact analysis for a doc');
67
+ lines.push('- `dotmd diff [file]` — git changes since last updated date');
68
+ lines.push('- `dotmd list` — all docs grouped by status');
69
+ lines.push('- `dotmd focus <status>` — detailed view for one status group');
70
+
71
+ if (config.raw?.glossary) {
72
+ lines.push('- `dotmd glossary <term>` — domain term lookup with related docs and plans');
73
+ }
74
+
75
+ lines.push('');
76
+ lines.push('Lifecycle:');
77
+ lines.push('- `dotmd new <name> --template plan` — scaffold new plan');
78
+ lines.push('- `dotmd status <file> <status>` — transition status');
79
+ lines.push('- `dotmd archive <file>` — archive with auto ref-fixing');
80
+ lines.push('- `dotmd bulk archive <files>` — archive multiple at once');
81
+ lines.push('- `dotmd touch --git` — bulk-sync updated dates from git history');
82
+ lines.push('- `dotmd lint --fix` — auto-fix frontmatter issues');
83
+ lines.push('- `dotmd fix-refs` — repair broken references and body links');
84
+ lines.push('- `dotmd rename <old> <new>` — rename doc + update all references');
85
+ lines.push('');
86
+
87
+ return lines.join('\n');
88
+ }
89
+
90
+ function getInstalledVersion(filePath) {
91
+ if (!existsSync(filePath)) return null;
92
+ const content = readFileSync(filePath, 'utf8');
93
+ const match = content.match(VERSION_REGEX);
94
+ return match ? match[1] : null;
95
+ }
96
+
97
+ export function scaffoldClaudeCommands(cwd, config) {
98
+ const claudeDir = path.join(cwd, '.claude');
99
+ if (!existsSync(claudeDir)) return [];
100
+
101
+ const commandsDir = path.join(claudeDir, 'commands');
102
+ const results = [];
103
+
104
+ const files = [
105
+ { name: 'plans.md', generate: () => generatePlansCommand(config) },
106
+ { name: 'docs.md', generate: () => generateDocsCommand(config) },
107
+ ];
108
+
109
+ for (const { name, generate } of files) {
110
+ const filePath = path.join(commandsDir, name);
111
+ const installedVersion = getInstalledVersion(filePath);
112
+
113
+ if (installedVersion === pkg.version) {
114
+ results.push({ name, action: 'current' });
115
+ } else if (installedVersion) {
116
+ // Outdated — regenerate
117
+ mkdirSync(commandsDir, { recursive: true });
118
+ writeFileSync(filePath, generate(), 'utf8');
119
+ results.push({ name, action: 'updated', from: installedVersion, to: pkg.version });
120
+ } else if (!existsSync(filePath)) {
121
+ // New — create
122
+ mkdirSync(commandsDir, { recursive: true });
123
+ writeFileSync(filePath, generate(), 'utf8');
124
+ results.push({ name, action: 'created' });
125
+ } else {
126
+ // File exists but no version marker — user-managed, don't touch
127
+ results.push({ name, action: 'skipped' });
128
+ }
129
+ }
130
+
131
+ return results;
132
+ }
133
+
134
+ export function checkClaudeCommands(cwd) {
135
+ const commandsDir = path.join(cwd, '.claude', 'commands');
136
+ if (!existsSync(commandsDir)) return [];
137
+
138
+ const warnings = [];
139
+ for (const name of ['plans.md', 'docs.md']) {
140
+ const filePath = path.join(commandsDir, name);
141
+ const installedVersion = getInstalledVersion(filePath);
142
+ if (installedVersion && installedVersion !== pkg.version) {
143
+ warnings.push({
144
+ path: `.claude/commands/${name}`,
145
+ level: 'warning',
146
+ message: `Claude command outdated (v${installedVersion} → v${pkg.version}). Run \`dotmd doctor\` to update.`,
147
+ });
148
+ }
149
+ }
150
+ return warnings;
151
+ }
package/src/doctor.mjs CHANGED
@@ -4,7 +4,8 @@ import { runTouch } from './lifecycle.mjs';
4
4
  import { buildIndex } from './index.mjs';
5
5
  import { renderIndexFile, writeIndex } from './index-file.mjs';
6
6
  import { renderCheck } from './render.mjs';
7
- import { bold } from './color.mjs';
7
+ import { bold, dim, green, yellow } from './color.mjs';
8
+ import { scaffoldClaudeCommands } from './claude-commands.mjs';
8
9
 
9
10
  export function runDoctor(argv, config, opts = {}) {
10
11
  const { dryRun } = opts;
@@ -34,8 +35,21 @@ export function runDoctor(argv, config, opts = {}) {
34
35
  }
35
36
  }
36
37
 
37
- // Step 5: Show remaining check
38
- process.stdout.write('\n' + bold('5. Remaining issues:') + '\n');
38
+ // Step 5: Refresh Claude Code commands
39
+ const claudeResults = dryRun ? [] : scaffoldClaudeCommands(config.repoRoot, config);
40
+ if (claudeResults.some(r => r.action !== 'current' && r.action !== 'skipped')) {
41
+ process.stdout.write('\n' + bold('5. Claude Code commands:') + '\n');
42
+ for (const r of claudeResults) {
43
+ if (r.action === 'updated') {
44
+ process.stdout.write(`${green('Updated')} .claude/commands/${r.name} (v${r.from} → v${r.to})\n`);
45
+ } else if (r.action === 'created') {
46
+ process.stdout.write(`${green('Created')} .claude/commands/${r.name}\n`);
47
+ }
48
+ }
49
+ }
50
+
51
+ // Step 6: Show remaining check
52
+ process.stdout.write('\n' + bold('6. Remaining issues:') + '\n');
39
53
  const freshIndex = buildIndex(config);
40
54
  process.stdout.write(renderCheck(freshIndex, config));
41
55
  }
package/src/index.mjs CHANGED
@@ -5,6 +5,7 @@ import { extractFirstHeading, extractSummary, extractStatusSnapshot, extractNext
5
5
  import { asString, normalizeStringList, normalizeBlockers, mergeUniqueStrings, toRepoPath, warn } from './util.mjs';
6
6
  import { validateDoc, checkBidirectionalReferences, checkGitStaleness, computeDaysSinceUpdate, computeIsStale, computeChecklistCompletionRate } from './validate.mjs';
7
7
  import { checkIndex } from './index-file.mjs';
8
+ import { checkClaudeCommands } from './claude-commands.mjs';
8
9
 
9
10
  export function buildIndex(config) {
10
11
  const docs = collectDocFiles(config).map(f => parseDocFile(f, config));
@@ -70,6 +71,9 @@ export function buildIndex(config) {
70
71
  const gitWarnings = checkGitStaleness(transformedDocs, config);
71
72
  warnings.push(...gitWarnings);
72
73
 
74
+ const claudeWarnings = checkClaudeCommands(config.repoRoot);
75
+ warnings.push(...claudeWarnings);
76
+
73
77
  return {
74
78
  generatedAt: new Date().toISOString(),
75
79
  docs: transformedDocs,
package/src/init.mjs CHANGED
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from
2
2
  import path from 'node:path';
3
3
  import { extractFrontmatter, parseSimpleFrontmatter } from './frontmatter.mjs';
4
4
  import { green, dim } from './color.mjs';
5
+ import { scaffoldClaudeCommands } from './claude-commands.mjs';
5
6
 
6
7
  const STARTER_CONFIG = `// dotmd.config.mjs — document management configuration
7
8
  // All exports are optional. See dotmd.config.example.mjs for full reference.
@@ -103,7 +104,7 @@ function generateDetectedConfig(scan, rootPath) {
103
104
  return lines.join('\n');
104
105
  }
105
106
 
106
- export function runInit(cwd) {
107
+ export function runInit(cwd, config) {
107
108
  const configPath = path.join(cwd, 'dotmd.config.mjs');
108
109
  const docsDir = path.join(cwd, 'docs');
109
110
  const indexPath = path.join(docsDir, 'docs.md');
@@ -137,6 +138,18 @@ export function runInit(cwd) {
137
138
  process.stdout.write(` ${green('create')} docs/docs.md\n`);
138
139
  }
139
140
 
141
+ // Claude Code integration — auto-detect .claude/ directory
142
+ if (config) {
143
+ const results = scaffoldClaudeCommands(cwd, config);
144
+ for (const r of results) {
145
+ if (r.action === 'created') {
146
+ process.stdout.write(` ${green('create')} .claude/commands/${r.name}\n`);
147
+ } else if (r.action === 'current') {
148
+ process.stdout.write(` ${dim('current')} .claude/commands/${r.name}\n`);
149
+ }
150
+ }
151
+ }
152
+
140
153
  process.stdout.write(`\nReady. Create your first doc:\n`);
141
154
  process.stdout.write(` dotmd new my-doc\n`);
142
155
  process.stdout.write(` dotmd list\n\n`);