dotmd-cli 0.56.0 → 0.57.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 +1 -1
- package/src/claude-commands.mjs +55 -204
- package/src/doctor.mjs +18 -16
- package/src/hud.mjs +6 -5
- package/src/init.mjs +21 -43
- package/src/ship.mjs +4 -10
package/package.json
CHANGED
package/src/claude-commands.mjs
CHANGED
|
@@ -1,220 +1,71 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, unlinkSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { green, dim, yellow } from './color.mjs';
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// truncated with an ellipsis so the description stays bounded.
|
|
34
|
-
function statusVocabClause(config) {
|
|
35
|
-
if (!config?.typeStatuses) return '';
|
|
36
|
-
const parts = [];
|
|
37
|
-
for (const [type, statusesSet] of config.typeStatuses.entries()) {
|
|
38
|
-
if (!statusesSet || statusesSet.size === 0) continue;
|
|
39
|
-
let statuses = [...statusesSet];
|
|
40
|
-
if (statuses.length > VOCAB_TRUNCATE_AT) {
|
|
41
|
-
statuses = [...statuses.slice(0, VOCAB_TRUNCATE_AT), '…'];
|
|
42
|
-
}
|
|
43
|
-
parts.push(`Valid ${type} statuses: ${statuses.join(', ')}.`);
|
|
44
|
-
}
|
|
45
|
-
return parts.join(' ');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function frontmatterFor(name, config) {
|
|
49
|
-
let description = SLASH_DESCRIPTIONS[name];
|
|
50
|
-
if (name === 'plans') {
|
|
51
|
-
const vocab = statusVocabClause(config);
|
|
52
|
-
if (vocab) description = `${description} ${vocab}`;
|
|
53
|
-
}
|
|
54
|
-
return ['---', `description: ${description}`, '---'];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function generatePlansCommand(config, version) {
|
|
58
|
-
const lines = [...frontmatterFor('plans', config), markerFor(version), ''];
|
|
59
|
-
lines.push('Run `dotmd context` to get the current plans briefing, then use it to orient yourself.');
|
|
60
|
-
lines.push('');
|
|
61
|
-
lines.push(`Plans are managed by **dotmd** (v${version}). Config at \`dotmd.config.mjs\`. Always use \`dotmd\` directly.`);
|
|
62
|
-
lines.push('');
|
|
63
|
-
lines.push('Plan-specific commands:');
|
|
64
|
-
lines.push('- `dotmd context` — briefing with active/paused/ready plans, age tags, next steps');
|
|
65
|
-
lines.push('- `dotmd set <status> <file>` — single status verb. Writes the new status to the plan\'s frontmatter. Use it to transition or close any plan:');
|
|
66
|
-
lines.push(' - `dotmd set in-session <file>` — mark a plan in-session (just a frontmatter status; use `dotmd use <file>` to also print the body)');
|
|
67
|
-
lines.push(' - `dotmd set archived <file>` — close out (same as `dotmd archive`)');
|
|
68
|
-
lines.push('- `dotmd archive <file>` — explicit archive with ref-fixing (equivalent to `set archived`)');
|
|
69
|
-
lines.push('- `dotmd bulk archive <files>` — archive multiple at once');
|
|
70
|
-
lines.push('- `dotmd new plan <name>` — scaffold with full phase structure');
|
|
71
|
-
lines.push('- `dotmd new prompt <name>` — save a resume-prompt to docs/prompts/ (pipe stdin or @path for body)');
|
|
72
|
-
lines.push('- `dotmd use` — consume oldest pending prompt (prints body, auto-archives)');
|
|
73
|
-
lines.push('- `dotmd use <file>` — open any doc by type: prompt → consume, plan → mark in-session + print card, doc → read');
|
|
74
|
-
lines.push('- `dotmd unblocks <file>` — what depends on / is blocked by a plan');
|
|
75
|
-
lines.push('- `dotmd actionable` — ready plans with next steps (what to promote)');
|
|
76
|
-
lines.push('- `dotmd query --keyword <term>` — find plans by keyword');
|
|
77
|
-
lines.push('- `dotmd runlist <hub>` — show ordered children of a runlist hub (→ marks next)');
|
|
78
|
-
lines.push('- `dotmd runlist next <hub>` — open the next non-archived child of a runlist hub');
|
|
79
|
-
|
|
80
|
-
if (config.raw?.glossary) {
|
|
81
|
-
lines.push('- `dotmd glossary <term>` — domain term lookup with related plans');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
lines.push('');
|
|
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>`).');
|
|
86
|
-
lines.push('');
|
|
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>`).');
|
|
89
|
-
lines.push('If the user references a runlist by name — e.g. "what\'s next on <X> runlist", "<X> runlist status", "pick up the next in <X>" — use `dotmd runlist next <X>` (or `dotmd runlist <X>` first to inspect the ordering). Do NOT fall back to `dotmd context` for runlist-scoped questions.');
|
|
90
|
-
lines.push('');
|
|
91
|
-
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 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, run `dotmd use` with no arg.');
|
|
92
|
-
lines.push('');
|
|
93
|
-
|
|
94
|
-
return lines.join('\n');
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function generateDocsCommand(config, version) {
|
|
98
|
-
const roots = Array.isArray(config.raw?.root) ? config.raw.root : [config.raw?.root ?? 'docs'];
|
|
99
|
-
const rootCount = roots.length;
|
|
100
|
-
|
|
101
|
-
const lines = [...frontmatterFor('docs', config), markerFor(version), ''];
|
|
102
|
-
lines.push(`All documentation in this repo is managed by **dotmd** (v${version}). Docs across ${rootCount} root${rootCount > 1 ? 's' : ''}: ${roots.join(', ')}. Config at \`dotmd.config.mjs\`.`);
|
|
103
|
-
lines.push('');
|
|
104
|
-
|
|
105
|
-
// Document types from config
|
|
106
|
-
const types = config.raw?.types ? Object.keys(config.raw.types) : [];
|
|
107
|
-
if (types.length > 0) {
|
|
108
|
-
lines.push(`Document types: ${types.map(t => '`' + t + '`').join(', ')}.`);
|
|
109
|
-
lines.push('');
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
lines.push('Commands for working with docs:');
|
|
113
|
-
lines.push('- `dotmd context` — LLM-oriented briefing across all types');
|
|
114
|
-
lines.push('- `dotmd doctor --apply` — auto-fix everything in one pass (refs, lint, dates, index; bare `dotmd doctor` previews only)');
|
|
115
|
-
lines.push('- `dotmd query [filters]` — search by status, keyword, module, surface, type, staleness');
|
|
116
|
-
lines.push('- `dotmd health` — plan pipeline, velocity, aging');
|
|
117
|
-
lines.push('- `dotmd stats` — doc health dashboard (completeness, checklists, audit coverage)');
|
|
118
|
-
lines.push('- `dotmd graph [--dot]` — visualize document relationships');
|
|
119
|
-
lines.push('- `dotmd deps [file]` — dependency tree');
|
|
120
|
-
lines.push('- `dotmd unblocks <file>` — impact analysis for a doc');
|
|
121
|
-
lines.push('- `dotmd diff [file]` — git changes since last updated date');
|
|
122
|
-
lines.push('- `dotmd list` — all docs grouped by status');
|
|
123
|
-
lines.push('- `dotmd focus <status>` — detailed view for one status group');
|
|
124
|
-
|
|
125
|
-
if (config.raw?.glossary) {
|
|
126
|
-
lines.push('- `dotmd glossary <term>` — domain term lookup with related docs and plans');
|
|
4
|
+
// dotmd used to scaffold per-repo `.claude/commands/{plans,docs}.md` slash
|
|
5
|
+
// commands — version-stamped, generated from each repo's status vocab, and
|
|
6
|
+
// self-healed by `dotmd hud`. That mechanism is RETIRED. The dotmd Claude Code
|
|
7
|
+
// plugin (plugins/dotmd/skills/dotmd/SKILL.md + bundled hooks) now carries the
|
|
8
|
+
// canonical agent-facing workflow into every repo and every subagent, and
|
|
9
|
+
// `dotmd hud` injects the dynamic per-project status vocab at runtime. A static
|
|
10
|
+
// skill + a runtime hook covers the full picture with no per-repo file to drift.
|
|
11
|
+
//
|
|
12
|
+
// The only job left in this module is teardown: delete the stale generated
|
|
13
|
+
// command files dotmd left behind so retired scaffolding stops shadowing the
|
|
14
|
+
// plugin skill. Removal is banner-gated — files WITHOUT the dotmd marker are
|
|
15
|
+
// hand-authored (e.g. a repo's own module-*.md / domain-*.md briefings) and are
|
|
16
|
+
// NEVER touched. Every dotmd-stamped file is fair game, including legacy ones
|
|
17
|
+
// dotmd no longer generates (e.g. the old baton.md).
|
|
18
|
+
|
|
19
|
+
const GENERATED_MARKER = '<!-- dotmd-generated:';
|
|
20
|
+
|
|
21
|
+
// The marker sits just below the YAML frontmatter Claude Code surfaces as the
|
|
22
|
+
// command description. That description can be long (the retired plans.md baked
|
|
23
|
+
// the full per-type status vocab into it), pushing the banner well past the
|
|
24
|
+
// first kilobyte — so classify against the whole file, not a head slice. These
|
|
25
|
+
// are tiny command files, so reading them in full is cheap.
|
|
26
|
+
function isGeneratedCommandFile(filePath) {
|
|
27
|
+
try {
|
|
28
|
+
return readFileSync(filePath, 'utf8').includes(GENERATED_MARKER);
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
127
31
|
}
|
|
128
|
-
|
|
129
|
-
lines.push('');
|
|
130
|
-
lines.push('Lifecycle:');
|
|
131
|
-
lines.push('- `dotmd new plan <name>` — scaffold new plan');
|
|
132
|
-
lines.push('- `dotmd new doc <name>` — scaffold reference doc');
|
|
133
|
-
lines.push('- `dotmd new prompt <name>` — save a resume-prompt (pipe stdin or @path for body)');
|
|
134
|
-
lines.push('- `dotmd use` — consume oldest pending prompt (prints body, auto-archives)');
|
|
135
|
-
lines.push('- `dotmd use <file>` — open any doc by type: prompt → consume, plan → start work, doc → read');
|
|
136
|
-
lines.push('- `dotmd set <status> [<file>]` — unified transition (archive / status bump; infers path from your active in-session plan)');
|
|
137
|
-
lines.push('- `dotmd status <file> <status>` — transition status (legacy; `set` is preferred)');
|
|
138
|
-
lines.push('- `dotmd archive <file>` — archive with auto ref-fixing');
|
|
139
|
-
lines.push('- `dotmd bulk archive <files>` — archive multiple at once');
|
|
140
|
-
lines.push('- `dotmd touch --git` — bulk-sync updated dates from git history');
|
|
141
|
-
lines.push('- `dotmd lint --fix` — auto-fix frontmatter issues');
|
|
142
|
-
lines.push('- `dotmd fix-refs` — repair broken references and body links');
|
|
143
|
-
lines.push('- `dotmd rename <old> <new>` — rename doc + update all references');
|
|
144
|
-
lines.push('');
|
|
145
|
-
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" — consume it with `dotmd use <file>` (prints the body and archives atomically). Do NOT `cat` it or read it with the file-reading tool. To pick the oldest pending prompt without naming a file, run `dotmd use` with no arg.');
|
|
146
|
-
lines.push('');
|
|
147
|
-
|
|
148
|
-
return lines.join('\n');
|
|
149
32
|
}
|
|
150
33
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const results = [];
|
|
165
|
-
|
|
166
|
-
const files = [
|
|
167
|
-
{ name: 'plans.md', generate: () => generatePlansCommand(config, version) },
|
|
168
|
-
{ name: 'docs.md', generate: () => generateDocsCommand(config, version) },
|
|
169
|
-
];
|
|
170
|
-
|
|
171
|
-
for (const { name, generate } of files) {
|
|
34
|
+
// Remove every dotmd-generated slash-command file under .claude/commands.
|
|
35
|
+
// Returns [{ name, action: 'removed' }] for each file cleaned (or that would be
|
|
36
|
+
// cleaned, in dry-run). Never throws — teardown must not break a hook or a
|
|
37
|
+
// command. User-authored command files (no dotmd banner) survive untouched.
|
|
38
|
+
export function removeGeneratedSlashCommands(cwd, opts = {}) {
|
|
39
|
+
const { dryRun = false } = opts;
|
|
40
|
+
const commandsDir = path.join(cwd, '.claude', 'commands');
|
|
41
|
+
if (!existsSync(commandsDir)) return [];
|
|
42
|
+
let entries;
|
|
43
|
+
try { entries = readdirSync(commandsDir); } catch { return []; }
|
|
44
|
+
const removed = [];
|
|
45
|
+
for (const name of entries) {
|
|
46
|
+
if (!name.endsWith('.md')) continue;
|
|
172
47
|
const filePath = path.join(commandsDir, name);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
results.push({ name, action: 'current' });
|
|
177
|
-
} else if (installedVersion) {
|
|
178
|
-
// Outdated — regenerate
|
|
179
|
-
if (!dryRun) {
|
|
180
|
-
mkdirSync(commandsDir, { recursive: true });
|
|
181
|
-
writeFileSync(filePath, generate(), 'utf8');
|
|
182
|
-
}
|
|
183
|
-
results.push({ name, action: 'updated', from: installedVersion, to: version });
|
|
184
|
-
} else if (!existsSync(filePath)) {
|
|
185
|
-
// New — create
|
|
186
|
-
if (!dryRun) {
|
|
187
|
-
mkdirSync(commandsDir, { recursive: true });
|
|
188
|
-
writeFileSync(filePath, generate(), 'utf8');
|
|
189
|
-
}
|
|
190
|
-
results.push({ name, action: 'created' });
|
|
191
|
-
} else {
|
|
192
|
-
// File exists but no version marker — user-managed, don't touch
|
|
193
|
-
results.push({ name, action: 'skipped' });
|
|
48
|
+
if (!isGeneratedCommandFile(filePath)) continue;
|
|
49
|
+
if (!dryRun) {
|
|
50
|
+
try { unlinkSync(filePath); } catch { continue; }
|
|
194
51
|
}
|
|
52
|
+
removed.push({ name, action: 'removed' });
|
|
195
53
|
}
|
|
196
|
-
|
|
197
|
-
return results;
|
|
54
|
+
return removed;
|
|
198
55
|
}
|
|
199
56
|
|
|
200
|
-
// Self-heal
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
// hud silent-clean contract is preserved. `skipped` (user-managed, no banner)
|
|
206
|
-
// and `current` entries are filtered out — callers don't care about them.
|
|
57
|
+
// Self-heal entrypoint for `dotmd hud` (SessionStart hook). Was: regenerate
|
|
58
|
+
// stale slash commands. Now: delete the retired generated files so the plugin
|
|
59
|
+
// skill is the single source of truth. Returns only the removed entries; an
|
|
60
|
+
// empty array preserves hud's silent-clean contract. Kept under the old name so
|
|
61
|
+
// hud's call site (and its swallow-all-errors wrapper) is unchanged.
|
|
207
62
|
export function refreshStaleSlashCommands(config) {
|
|
208
|
-
|
|
209
|
-
return results.filter(r => r.action === 'updated');
|
|
63
|
+
return removeGeneratedSlashCommands(config.repoRoot);
|
|
210
64
|
}
|
|
211
65
|
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
// fired on every release until the next session, despite the user having no
|
|
216
|
-
// action to take (the heal is automatic). Kept the function for API stability
|
|
217
|
-
// in case downstream callers import it.
|
|
66
|
+
// Retained as a no-op for API stability. `dotmd check` never warned on slash
|
|
67
|
+
// commands (the old auto-heal made it pure noise), and now there is nothing to
|
|
68
|
+
// generate at all. See git history for the retired scaffolder.
|
|
218
69
|
export function checkClaudeCommands(_cwd, _opts = {}) {
|
|
219
70
|
return [];
|
|
220
71
|
}
|
package/src/doctor.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { buildIndex, collectDocFiles } from './index.mjs';
|
|
|
7
7
|
import { renderIndexFile, writeIndex } from './index-file.mjs';
|
|
8
8
|
import { renderCheck, renderManualFixes } from './render.mjs';
|
|
9
9
|
import { bold, dim, green, yellow } from './color.mjs';
|
|
10
|
-
import { checkClaudeCommands,
|
|
10
|
+
import { checkClaudeCommands, removeGeneratedSlashCommands } from './claude-commands.mjs';
|
|
11
11
|
import { runMigrateTemplate } from './migrate-template.mjs';
|
|
12
12
|
import { runMigratePrompts } from './migrate-prompts.mjs';
|
|
13
13
|
import { runFrontmatterFix } from './frontmatter-fix.mjs';
|
|
@@ -98,25 +98,27 @@ export function runDoctor(argv, config, opts = {}) {
|
|
|
98
98
|
process.stdout.write('Index updated.\n');
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
// Step 5:
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
101
|
+
// Step 5: Clean up retired Claude Code command scaffolding. The per-repo
|
|
102
|
+
// `.claude/commands/{plans,docs}.md` files are superseded by the dotmd plugin
|
|
103
|
+
// skill; doctor sweeps any leftover banner-stamped (dotmd-generated) files.
|
|
104
|
+
// Always print the heading so the numbering stays `1,2,3,4,5,6`.
|
|
105
105
|
process.stdout.write('\n' + bold('5. Claude Code commands:') + '\n');
|
|
106
106
|
if (dryRun) {
|
|
107
|
-
|
|
107
|
+
const wouldRemove = removeGeneratedSlashCommands(config.repoRoot, { dryRun: true });
|
|
108
|
+
if (wouldRemove.length === 0) {
|
|
109
|
+
process.stdout.write('[dry-run] No retired slash-command files to remove.\n');
|
|
110
|
+
} else {
|
|
111
|
+
for (const r of wouldRemove) {
|
|
112
|
+
process.stdout.write(`[dry-run] Would remove retired .claude/commands/${r.name} (guidance now ships via the dotmd plugin).\n`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
108
115
|
} else {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
process.stdout.write('Nothing to refresh.\n');
|
|
116
|
+
const removed = removeGeneratedSlashCommands(config.repoRoot);
|
|
117
|
+
if (removed.length === 0) {
|
|
118
|
+
process.stdout.write('Nothing to clean up.\n');
|
|
113
119
|
} else {
|
|
114
|
-
for (const r of
|
|
115
|
-
|
|
116
|
-
process.stdout.write(`${green('Updated')} .claude/commands/${r.name} (v${r.from} → v${r.to})\n`);
|
|
117
|
-
} else if (r.action === 'created') {
|
|
118
|
-
process.stdout.write(`${green('Created')} .claude/commands/${r.name}\n`);
|
|
119
|
-
}
|
|
120
|
+
for (const r of removed) {
|
|
121
|
+
process.stdout.write(`${green('Removed')} retired .claude/commands/${r.name} (guidance now ships via the dotmd plugin)\n`);
|
|
120
122
|
}
|
|
121
123
|
}
|
|
122
124
|
}
|
package/src/hud.mjs
CHANGED
|
@@ -261,11 +261,12 @@ export function runHud(argv, config) {
|
|
|
261
261
|
|
|
262
262
|
const hud = buildHud(config);
|
|
263
263
|
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
264
|
+
// Clean up retired generated slash-command files (the plugin skill replaces
|
|
265
|
+
// them). Banner-gated, so hand-authored commands survive. Wrapped: teardown
|
|
266
|
+
// must never kill the SessionStart hook (would block every session). Runs for
|
|
267
|
+
// its side effect only — nothing is announced in stdout (see the primer-only
|
|
268
|
+
// contract below). Skipped in --json mode to keep the structured shape stable
|
|
269
|
+
// for programmatic callers.
|
|
269
270
|
if (!json) {
|
|
270
271
|
try { refreshStaleSlashCommands(config); }
|
|
271
272
|
catch { /* swallow — see comment above */ }
|
package/src/init.mjs
CHANGED
|
@@ -5,8 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import { extractFrontmatter, parseSimpleFrontmatter } from './frontmatter.mjs';
|
|
6
6
|
import { green, dim, yellow } from './color.mjs';
|
|
7
7
|
import { warn } from './util.mjs';
|
|
8
|
-
import {
|
|
9
|
-
import { resolveConfig } from './config.mjs';
|
|
8
|
+
import { removeGeneratedSlashCommands } from './claude-commands.mjs';
|
|
10
9
|
|
|
11
10
|
// Subdirectories scaffolded under docsRoot and tracked separately during scans.
|
|
12
11
|
// Each maps to a builtin type (plan, prompt). New types added here should also
|
|
@@ -331,53 +330,32 @@ export async function runInit(cwd, config, opts = {}) {
|
|
|
331
330
|
process.stdout.write(`\n ${yellow('hint')} ${n} untagged .md ${noun} found — run \`dotmd bulk-tag --dry-run\` to preview tagging.\n`);
|
|
332
331
|
}
|
|
333
332
|
|
|
334
|
-
// Claude Code integration
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
//
|
|
339
|
-
//
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
// dotmd skipping a user-managed file (pre-fix: `skipped` was unreported).
|
|
346
|
-
const scaffoldConfig = await resolveConfig(cwd);
|
|
347
|
-
if (scaffoldConfig) {
|
|
348
|
-
const results = scaffoldClaudeCommands(cwd, scaffoldConfig, { dryRun });
|
|
349
|
-
for (const r of results) {
|
|
350
|
-
const filename = `.claude/commands/${r.name}`;
|
|
351
|
-
if (r.action === 'created') {
|
|
352
|
-
process.stdout.write(` ${dryTag}${green('create')} ${filename}\n`);
|
|
353
|
-
} else if (r.action === 'updated') {
|
|
354
|
-
process.stdout.write(` ${dryTag}${green('update')} ${filename} (v${r.from} → v${r.to})\n`);
|
|
355
|
-
} else if (r.action === 'current') {
|
|
356
|
-
process.stdout.write(` ${dryTag}${dim('exists')} ${filename}\n`);
|
|
357
|
-
} else if (r.action === 'skipped') {
|
|
358
|
-
process.stdout.write(` ${dryTag}${yellow('skip')} ${filename} (no version marker — user-managed)\n`);
|
|
359
|
-
}
|
|
333
|
+
// Claude Code integration. dotmd no longer scaffolds per-repo
|
|
334
|
+
// `.claude/commands/*.md` slash commands — the dotmd plugin's SKILL.md is the
|
|
335
|
+
// canonical agent-facing workflow now, and `dotmd hud` injects this repo's
|
|
336
|
+
// status vocab at runtime. If a `.claude/` exists, sweep any retired
|
|
337
|
+
// generated command files (banner-gated, so hand-authored ones survive) and
|
|
338
|
+
// point the user at the plugin instead.
|
|
339
|
+
if (existsSync(path.join(cwd, '.claude'))) {
|
|
340
|
+
const removed = removeGeneratedSlashCommands(cwd, { dryRun });
|
|
341
|
+
for (const r of removed) {
|
|
342
|
+
const verb = dryRun ? 'would remove' : 'removed';
|
|
343
|
+
process.stdout.write(` ${dryTag}${yellow('clean')} .claude/commands/${r.name} (retired — ${verb}; guidance ships via the dotmd plugin)\n`);
|
|
360
344
|
}
|
|
361
|
-
}
|
|
362
345
|
|
|
363
|
-
// SessionStart hook hint — only when .claude/ exists. Print-only; users with
|
|
364
|
-
// existing settings.json need to merge by hand because auto-merging hook
|
|
365
|
-
// arrays would silently mutate user-managed files.
|
|
366
|
-
if (existsSync(path.join(cwd, '.claude'))) {
|
|
367
346
|
const sessionStart = detectSessionStartHook(cwd);
|
|
368
347
|
if (sessionStart.wired) {
|
|
369
348
|
process.stdout.write(` ${dim('exists')} ${sessionStart.file} (SessionStart hook for \`dotmd hud\` already wired)\n`);
|
|
370
349
|
} else {
|
|
371
|
-
process.stdout.write(`\n ${yellow('hint')}
|
|
372
|
-
process.stdout.write(`
|
|
373
|
-
process.stdout.write(`
|
|
374
|
-
process.stdout.write(`
|
|
375
|
-
process.stdout.write(`
|
|
376
|
-
process.stdout.write(`
|
|
377
|
-
process.stdout.write(`
|
|
378
|
-
process.stdout.write(`
|
|
379
|
-
process.stdout.write(`
|
|
380
|
-
process.stdout.write(` \`hooks.SessionStart\` array rather than replacing the file.\n`);
|
|
350
|
+
process.stdout.write(`\n ${yellow('hint')} install the dotmd Claude Code plugin so its hooks + workflow skill\n`);
|
|
351
|
+
process.stdout.write(` travel to every session and subagent automatically:\n\n`);
|
|
352
|
+
process.stdout.write(` /plugin marketplace add reowens/dotmd\n`);
|
|
353
|
+
process.stdout.write(` /plugin install dotmd@dotmd\n\n`);
|
|
354
|
+
process.stdout.write(` Or, without the plugin, wire \`dotmd hud\` at SessionStart by hand —\n`);
|
|
355
|
+
process.stdout.write(` add to .claude/settings.json (merge into any existing hooks):\n\n`);
|
|
356
|
+
process.stdout.write(` "hooks": { "SessionStart": [\n`);
|
|
357
|
+
process.stdout.write(` { "hooks": [{ "type": "command", "command": "dotmd hud" }] }\n`);
|
|
358
|
+
process.stdout.write(` ] }\n`);
|
|
381
359
|
}
|
|
382
360
|
}
|
|
383
361
|
|
package/src/ship.mjs
CHANGED
|
@@ -3,7 +3,6 @@ import { spawnSync } from 'node:child_process';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { die, warn, toRepoPath } from './util.mjs';
|
|
5
5
|
import { green, dim, yellow } from './color.mjs';
|
|
6
|
-
import { scaffoldClaudeCommands } from './claude-commands.mjs';
|
|
7
6
|
|
|
8
7
|
// Files dotmd ship will auto-stage when they're dirty. Anything outside this
|
|
9
8
|
// allowlist stays in the working tree — user has to `git add` it explicitly,
|
|
@@ -77,16 +76,11 @@ export async function runShip(argv, config, opts = {}) {
|
|
|
77
76
|
|
|
78
77
|
process.stdout.write(`${green('→')} Shipping ${current} → ${target} (${bump})\n`);
|
|
79
78
|
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
const refreshed = regenResults.filter(r => r.action === 'updated' || r.action === 'created');
|
|
84
|
-
if (refreshed.length > 0) {
|
|
85
|
-
const verb = dryRun ? 'Would regenerate' : 'Regenerated';
|
|
86
|
-
process.stdout.write(`${green('→')} ${verb} slash commands @ ${target}: ${refreshed.map(r => r.name).join(', ')}\n`);
|
|
87
|
-
}
|
|
79
|
+
// Per-repo slash-command scaffolding is retired (the dotmd plugin's SKILL.md
|
|
80
|
+
// is canonical now), so there is nothing to regenerate at ship time. Any
|
|
81
|
+
// stale generated files are swept by `dotmd hud` / `dotmd doctor`.
|
|
88
82
|
|
|
89
|
-
//
|
|
83
|
+
// Identify dirty tracked files. Anything matching the allowlist gets
|
|
90
84
|
// staged; everything else is left dirty so the user can handle it.
|
|
91
85
|
const dirty = listDirtyFiles(config.repoRoot);
|
|
92
86
|
const untracked = dirty.filter(d => d.status === '??');
|