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 +6 -6
- package/package.json +1 -1
- package/src/claude-commands.mjs +151 -0
- package/src/doctor.mjs +17 -3
- package/src/index.mjs +4 -0
- package/src/init.mjs +14 -1
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
|
@@ -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:
|
|
38
|
-
|
|
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`);
|