dotmd-cli 0.31.2 → 0.31.3
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 +3 -2
- package/package.json +1 -1
- package/src/doctor.mjs +31 -19
- package/src/init.mjs +13 -3
- package/src/pickup-card.mjs +19 -6
- package/src/render.mjs +7 -1
package/bin/dotmd.mjs
CHANGED
|
@@ -690,10 +690,11 @@ async function main() {
|
|
|
690
690
|
|
|
691
691
|
const config = await resolveConfig(process.cwd(), explicitConfig);
|
|
692
692
|
|
|
693
|
-
// Init —
|
|
693
|
+
// Init — runInit re-resolves the config from disk internally (after any
|
|
694
|
+
// starter-config write), so we don't need to pre-pass it.
|
|
694
695
|
if (command === 'init') {
|
|
695
696
|
const { runInit } = await import('../src/init.mjs');
|
|
696
|
-
runInit(process.cwd(), config
|
|
697
|
+
await runInit(process.cwd(), config, { dryRun });
|
|
697
698
|
return;
|
|
698
699
|
}
|
|
699
700
|
|
package/package.json
CHANGED
package/src/doctor.mjs
CHANGED
|
@@ -67,27 +67,39 @@ export function runDoctor(argv, config, opts = {}) {
|
|
|
67
67
|
process.stdout.write('\n' + bold('3. Syncing dates from git...') + '\n');
|
|
68
68
|
runTouch(['--git'], config, { dryRun });
|
|
69
69
|
|
|
70
|
-
// Step 4: Regenerate index
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
// Step 4: Regenerate index. Heading always prints so the numbering stays
|
|
71
|
+
// `1,2,3,4,5,6` even when `index.path` isn't configured — pre-fix this was
|
|
72
|
+
// gated on `config.indexPath`, producing `1,2,3,5,6` on repos with no index.
|
|
73
|
+
process.stdout.write('\n' + bold('4. Regenerating index...') + '\n');
|
|
74
|
+
if (!config.indexPath) {
|
|
75
|
+
process.stdout.write('No index path configured (skip).\n');
|
|
76
|
+
} else if (dryRun) {
|
|
77
|
+
process.stdout.write('[dry-run] Would regenerate index.\n');
|
|
78
|
+
} else {
|
|
79
|
+
const index = buildIndex(config);
|
|
80
|
+
writeIndex(renderIndexFile(index, config), config);
|
|
81
|
+
process.stdout.write('Index updated.\n');
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
// Step 5: Refresh Claude Code commands
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
// Step 5: Refresh Claude Code commands. Always print the heading so the
|
|
85
|
+
// numbering stays `1,2,3,4,5,6` — pre-fix it was conditional, so a doctor
|
|
86
|
+
// run where everything was already current printed `1,2,3,4,6` with `5.`
|
|
87
|
+
// silently missing.
|
|
88
|
+
process.stdout.write('\n' + bold('5. Claude Code commands:') + '\n');
|
|
89
|
+
if (dryRun) {
|
|
90
|
+
process.stdout.write('[dry-run] Would refresh .claude/commands/ if outdated.\n');
|
|
91
|
+
} else {
|
|
92
|
+
const claudeResults = scaffoldClaudeCommands(config.repoRoot, config);
|
|
93
|
+
const changes = claudeResults.filter(r => r.action === 'updated' || r.action === 'created');
|
|
94
|
+
if (changes.length === 0) {
|
|
95
|
+
process.stdout.write('Nothing to refresh.\n');
|
|
96
|
+
} else {
|
|
97
|
+
for (const r of changes) {
|
|
98
|
+
if (r.action === 'updated') {
|
|
99
|
+
process.stdout.write(`${green('Updated')} .claude/commands/${r.name} (v${r.from} → v${r.to})\n`);
|
|
100
|
+
} else if (r.action === 'created') {
|
|
101
|
+
process.stdout.write(`${green('Created')} .claude/commands/${r.name}\n`);
|
|
102
|
+
}
|
|
91
103
|
}
|
|
92
104
|
}
|
|
93
105
|
}
|
package/src/init.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { extractFrontmatter, parseSimpleFrontmatter } from './frontmatter.mjs';
|
|
|
4
4
|
import { green, dim, yellow } from './color.mjs';
|
|
5
5
|
import { warn } from './util.mjs';
|
|
6
6
|
import { scaffoldClaudeCommands } from './claude-commands.mjs';
|
|
7
|
+
import { resolveConfig } from './config.mjs';
|
|
7
8
|
|
|
8
9
|
// Subdirectories scaffolded under docsRoot and tracked separately during scans.
|
|
9
10
|
// Each maps to a builtin type (plan, prompt). New types added here should also
|
|
@@ -151,7 +152,7 @@ function generateDetectedConfig(scan, rootPath) {
|
|
|
151
152
|
return lines.join('\n');
|
|
152
153
|
}
|
|
153
154
|
|
|
154
|
-
export function runInit(cwd, config, opts = {}) {
|
|
155
|
+
export async function runInit(cwd, config, opts = {}) {
|
|
155
156
|
const { dryRun = false } = opts;
|
|
156
157
|
const configPath = path.join(cwd, 'dotmd.config.mjs');
|
|
157
158
|
const docsDir = path.join(cwd, 'docs');
|
|
@@ -260,11 +261,20 @@ export function runInit(cwd, config, opts = {}) {
|
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
// Claude Code integration — auto-detect .claude/ directory.
|
|
264
|
+
// Re-resolve config so the scaffold sees whatever we (may have) just written.
|
|
265
|
+
// Pre-fix: the dispatcher passed `null` to runInit on a fresh repo because
|
|
266
|
+
// resolveConfig was called before init wrote the starter, so the `if (config)`
|
|
267
|
+
// gate below silently skipped slash-command scaffolding entirely on first init.
|
|
268
|
+
// Re-resolving picks up STARTER_CONFIG (or any pre-existing config) in the
|
|
269
|
+
// real-run path; in dry-run with no on-disk config, it returns the merged
|
|
270
|
+
// DEFAULTS, which is enough for the preview line (`would create…`).
|
|
271
|
+
//
|
|
263
272
|
// Reports all four scaffold outcomes so the user can't be surprised by
|
|
264
273
|
// either a silent regenerate (pre-fix: `updated` was unreported) or by
|
|
265
274
|
// dotmd skipping a user-managed file (pre-fix: `skipped` was unreported).
|
|
266
|
-
|
|
267
|
-
|
|
275
|
+
const scaffoldConfig = await resolveConfig(cwd);
|
|
276
|
+
if (scaffoldConfig) {
|
|
277
|
+
const results = scaffoldClaudeCommands(cwd, scaffoldConfig, { dryRun });
|
|
268
278
|
for (const r of results) {
|
|
269
279
|
const filename = `.claude/commands/${r.name}`;
|
|
270
280
|
if (r.action === 'created') {
|
package/src/pickup-card.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { extractFrontmatter, parseSimpleFrontmatter } from './frontmatter.mjs';
|
|
4
|
-
import { asString, toRepoPath, resolveDocPath } from './util.mjs';
|
|
4
|
+
import { asString, toRepoPath, resolveDocPath, resolveRefPath } from './util.mjs';
|
|
5
5
|
import { walkSections, findSection, findActivePhase, summarizePhases, isPhaseHeading, detectMarker } from './section.mjs';
|
|
6
6
|
import { dim, green } from './color.mjs';
|
|
7
7
|
|
|
@@ -29,7 +29,14 @@ function statusSummary(counts) {
|
|
|
29
29
|
return order.filter(k => counts[k]).map(k => `${counts[k]}${icons[k]}`).join(' ');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
// `docDir` is the directory of the doc whose frontmatter we're reading.
|
|
33
|
+
// Pre-fix: this used `resolveDocPath` which only tries repo-root and docsRoots-
|
|
34
|
+
// relative, NOT doc-relative — so a bare basename like `sibling-plan.md` written
|
|
35
|
+
// in `docs/plans/foo.md`'s `related_plans:` always showed `(missing)`, even
|
|
36
|
+
// though graph/validate resolve the same ref fine via `resolveRefPath`. Now
|
|
37
|
+
// matches graph's resolver semantics: doc-relative first, then repo-relative;
|
|
38
|
+
// docsRoots-relative is kept as a final fallback for legacy refs.
|
|
39
|
+
function readRelatedSummary(rawList, config, docDir) {
|
|
33
40
|
const list = Array.isArray(rawList) ? rawList : (typeof rawList === 'string' && rawList.trim() ? [rawList] : []);
|
|
34
41
|
const out = [];
|
|
35
42
|
for (const ref of list) {
|
|
@@ -37,7 +44,10 @@ function readRelatedSummary(rawList, config) {
|
|
|
37
44
|
const refStr = String(ref).trim();
|
|
38
45
|
if (!refStr) continue;
|
|
39
46
|
let abs = null;
|
|
40
|
-
try {
|
|
47
|
+
try {
|
|
48
|
+
abs = resolveRefPath(refStr, docDir, config.repoRoot)
|
|
49
|
+
?? resolveDocPath(refStr, config);
|
|
50
|
+
} catch { abs = null; }
|
|
41
51
|
if (!abs || !existsSync(abs)) {
|
|
42
52
|
out.push({ ref: refStr, status: null, missing: true });
|
|
43
53
|
continue;
|
|
@@ -96,10 +106,13 @@ export function buildCard(filePath, raw, config) {
|
|
|
96
106
|
const currentState = truncate(cleanInline(fm.current_state), CAPS.currentState);
|
|
97
107
|
const nextStep = truncate(cleanInline(fm.next_step), CAPS.nextStep);
|
|
98
108
|
|
|
99
|
-
// Related plans (compressed: slug + status only — show all, don't cap count)
|
|
109
|
+
// Related plans (compressed: slug + status only — show all, don't cap count).
|
|
110
|
+
// docDir lets the resolver try same-dir basenames first — graph/validate do this
|
|
111
|
+
// already; pickup-card now matches.
|
|
112
|
+
const docDir = path.dirname(filePath);
|
|
100
113
|
const related = [
|
|
101
|
-
...readRelatedSummary(fm.parent_plan, config).map(r => ({ ...r, kind: 'parent' })),
|
|
102
|
-
...readRelatedSummary(fm.related_plans, config).map(r => ({ ...r, kind: 'related' })),
|
|
114
|
+
...readRelatedSummary(fm.parent_plan, config, docDir).map(r => ({ ...r, kind: 'parent' })),
|
|
115
|
+
...readRelatedSummary(fm.related_plans, config, docDir).map(r => ({ ...r, kind: 'related' })),
|
|
103
116
|
];
|
|
104
117
|
|
|
105
118
|
// Phases summary + active phase (pointer only, no body)
|
package/src/render.mjs
CHANGED
|
@@ -297,7 +297,13 @@ export function renderBriefing(index, config) {
|
|
|
297
297
|
if (parts.length) lines.push(parts.join(' | '));
|
|
298
298
|
|
|
299
299
|
const stale = index.docs.filter(d => d.isStale && !config.lifecycle.skipStaleFor.has(d.status)).length;
|
|
300
|
-
|
|
300
|
+
// Append a hint when errors are present — otherwise the user sees `Errors: 1`
|
|
301
|
+
// with no clue what or where. `dotmd check` is the canonical detail view.
|
|
302
|
+
const errorCount = index.errors.length;
|
|
303
|
+
const errorPart = errorCount > 0
|
|
304
|
+
? `Errors: ${errorCount} ${dim('(run `dotmd check` to see)')}`
|
|
305
|
+
: `Errors: ${errorCount}`;
|
|
306
|
+
lines.push(`Stale: ${stale} | ${errorPart} | Warnings: ${index.warnings.length}`);
|
|
301
307
|
|
|
302
308
|
try {
|
|
303
309
|
const staleLeases = findStaleLeases(config);
|