dotmd-cli 0.31.1 → 0.31.2
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 -9
- package/package.json +1 -1
- package/src/claude-commands.mjs +11 -6
- package/src/commands.mjs +11 -0
- package/src/config.mjs +6 -2
- package/src/init.mjs +47 -25
- package/src/lifecycle.mjs +23 -8
- package/src/new.mjs +4 -0
- package/src/rename.mjs +3 -0
- package/src/validate.mjs +7 -2
package/bin/dotmd.mjs
CHANGED
|
@@ -693,7 +693,7 @@ async function main() {
|
|
|
693
693
|
// Init — now has access to config for Claude command generation
|
|
694
694
|
if (command === 'init') {
|
|
695
695
|
const { runInit } = await import('../src/init.mjs');
|
|
696
|
-
runInit(process.cwd(), config.configFound ? config : null);
|
|
696
|
+
runInit(process.cwd(), config.configFound ? config : null, { dryRun });
|
|
697
697
|
return;
|
|
698
698
|
}
|
|
699
699
|
|
|
@@ -1036,14 +1036,8 @@ async function main() {
|
|
|
1036
1036
|
}
|
|
1037
1037
|
|
|
1038
1038
|
// Unknown command — suggest closest match
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1041
|
-
'focus', 'query', 'plans', 'prompts', 'stale', 'actionable', 'index', 'pickup', 'release', 'finish', 'status', 'archive', 'bulk', 'touch', 'doctor',
|
|
1042
|
-
'unblocks', 'health', 'glossary',
|
|
1043
|
-
'fix-refs', 'lint', 'rename', 'migrate', 'notion', 'export', 'summary',
|
|
1044
|
-
'watch', 'diff', 'new', 'init', 'completions', 'statuses',
|
|
1045
|
-
];
|
|
1046
|
-
const matches = allCommands
|
|
1039
|
+
const { KNOWN_COMMANDS } = await import('../src/commands.mjs');
|
|
1040
|
+
const matches = KNOWN_COMMANDS
|
|
1047
1041
|
.map(c => ({ cmd: c, dist: levenshtein(command, c) }))
|
|
1048
1042
|
.sort((a, b) => a.dist - b.dist);
|
|
1049
1043
|
if (matches[0] && matches[0].dist <= 3) {
|
package/package.json
CHANGED
package/src/claude-commands.mjs
CHANGED
|
@@ -20,7 +20,7 @@ function generatePlansCommand(config) {
|
|
|
20
20
|
lines.push('- `dotmd release` — release current session\'s leases (alias: unpickup)');
|
|
21
21
|
lines.push('- `dotmd health` — plan velocity, aging, checklist progress, pipeline view');
|
|
22
22
|
lines.push('- `dotmd unblocks <file>` — what depends on / is blocked by a plan');
|
|
23
|
-
lines.push('- `dotmd
|
|
23
|
+
lines.push('- `dotmd actionable` — ready plans with next steps (what to promote)');
|
|
24
24
|
lines.push('- `dotmd new plan <name>` — scaffold with full phase structure');
|
|
25
25
|
lines.push('- `dotmd prompts new <name> "<body>"` — save a resume-prompt to docs/prompts/');
|
|
26
26
|
lines.push('- `dotmd prompts next` — consume oldest pending prompt (prints body, auto-archives)');
|
|
@@ -107,7 +107,8 @@ function getInstalledVersion(filePath) {
|
|
|
107
107
|
return match ? match[1] : null;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
export function scaffoldClaudeCommands(cwd, config) {
|
|
110
|
+
export function scaffoldClaudeCommands(cwd, config, opts = {}) {
|
|
111
|
+
const { dryRun = false } = opts;
|
|
111
112
|
const claudeDir = path.join(cwd, '.claude');
|
|
112
113
|
if (!existsSync(claudeDir)) return [];
|
|
113
114
|
|
|
@@ -127,13 +128,17 @@ export function scaffoldClaudeCommands(cwd, config) {
|
|
|
127
128
|
results.push({ name, action: 'current' });
|
|
128
129
|
} else if (installedVersion) {
|
|
129
130
|
// Outdated — regenerate
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
if (!dryRun) {
|
|
132
|
+
mkdirSync(commandsDir, { recursive: true });
|
|
133
|
+
writeFileSync(filePath, generate(), 'utf8');
|
|
134
|
+
}
|
|
132
135
|
results.push({ name, action: 'updated', from: installedVersion, to: pkg.version });
|
|
133
136
|
} else if (!existsSync(filePath)) {
|
|
134
137
|
// New — create
|
|
135
|
-
|
|
136
|
-
|
|
138
|
+
if (!dryRun) {
|
|
139
|
+
mkdirSync(commandsDir, { recursive: true });
|
|
140
|
+
writeFileSync(filePath, generate(), 'utf8');
|
|
141
|
+
}
|
|
137
142
|
results.push({ name, action: 'created' });
|
|
138
143
|
} else {
|
|
139
144
|
// File exists but no version marker — user-managed, don't touch
|
package/src/commands.mjs
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Canonical list of CLI verbs the dispatcher in bin/dotmd.mjs handles.
|
|
2
|
+
// Source of truth for both the unknown-command suggester and the self-check
|
|
3
|
+
// that asserts every `dotmd <verb>` reference in generated slash-command
|
|
4
|
+
// templates points at a real command.
|
|
5
|
+
export const KNOWN_COMMANDS = [
|
|
6
|
+
'list', 'json', 'check', 'coverage', 'stats', 'graph', 'deps', 'briefing', 'context', 'hud',
|
|
7
|
+
'focus', 'query', 'plans', 'prompts', 'stale', 'actionable', 'index', 'pickup', 'release', 'finish', 'status', 'archive', 'bulk', 'touch', 'doctor',
|
|
8
|
+
'unblocks', 'health', 'glossary',
|
|
9
|
+
'fix-refs', 'lint', 'rename', 'migrate', 'notion', 'export', 'summary',
|
|
10
|
+
'watch', 'diff', 'new', 'init', 'completions', 'statuses',
|
|
11
|
+
];
|
package/src/config.mjs
CHANGED
|
@@ -84,8 +84,12 @@ const DEFAULTS = {
|
|
|
84
84
|
},
|
|
85
85
|
|
|
86
86
|
referenceFields: {
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
// Defaults match the fields the built-in `plan`, `doc`, and `prompt`
|
|
88
|
+
// templates scaffold, so graph / deps / unblocks / pickup's Related:
|
|
89
|
+
// resolver work without config. Override by setting `referenceFields`
|
|
90
|
+
// in your config — your value fully replaces (no per-field merge).
|
|
91
|
+
bidirectional: ['related_plans', 'related_docs'],
|
|
92
|
+
unidirectional: ['parent_plan'],
|
|
89
93
|
},
|
|
90
94
|
|
|
91
95
|
templates: {},
|
package/src/init.mjs
CHANGED
|
@@ -21,6 +21,14 @@ export const index = {
|
|
|
21
21
|
endMarker: '<!-- GENERATED:dotmd:end -->',
|
|
22
22
|
archivedLimit: 8,
|
|
23
23
|
};
|
|
24
|
+
|
|
25
|
+
// Frontmatter fields graph / deps / unblocks / pickup's Related: resolver
|
|
26
|
+
// traverse. Defaults match what the built-in plan/doc/prompt templates scaffold.
|
|
27
|
+
// Add field names here (and to your templates) to track more relationships.
|
|
28
|
+
export const referenceFields = {
|
|
29
|
+
bidirectional: ['related_plans', 'related_docs'],
|
|
30
|
+
unidirectional: ['parent_plan'],
|
|
31
|
+
};
|
|
24
32
|
`;
|
|
25
33
|
|
|
26
34
|
const STARTER_INDEX = `# Docs
|
|
@@ -143,32 +151,38 @@ function generateDetectedConfig(scan, rootPath) {
|
|
|
143
151
|
return lines.join('\n');
|
|
144
152
|
}
|
|
145
153
|
|
|
146
|
-
export function runInit(cwd, config) {
|
|
154
|
+
export function runInit(cwd, config, opts = {}) {
|
|
155
|
+
const { dryRun = false } = opts;
|
|
147
156
|
const configPath = path.join(cwd, 'dotmd.config.mjs');
|
|
148
157
|
const docsDir = path.join(cwd, 'docs');
|
|
149
158
|
const indexPath = path.join(docsDir, 'docs.md');
|
|
150
159
|
|
|
160
|
+
// Prefix every reported line during dry-run so the user can't mistake the
|
|
161
|
+
// preview for a real run. Without this, every write below would silently
|
|
162
|
+
// execute — runInit previously ignored the `--dry-run` flag entirely.
|
|
163
|
+
const dryTag = dryRun ? `${dim('[dry-run]')} ` : '';
|
|
164
|
+
|
|
151
165
|
process.stdout.write('\n');
|
|
152
166
|
|
|
153
167
|
const scan = existsSync(docsDir) ? scanExistingDocs(docsDir) : null;
|
|
154
168
|
|
|
155
169
|
if (existsSync(configPath)) {
|
|
156
|
-
process.stdout.write(` ${dim('exists')} dotmd.config.mjs\n`);
|
|
170
|
+
process.stdout.write(` ${dryTag}${dim('exists')} dotmd.config.mjs\n`);
|
|
157
171
|
} else {
|
|
158
172
|
if (scan && scan.docCount > 0) {
|
|
159
|
-
writeFileSync(configPath, generateDetectedConfig(scan, 'docs'), 'utf8');
|
|
160
|
-
process.stdout.write(` ${green('create')} dotmd.config.mjs (detected ${scan.docCount} docs)\n`);
|
|
173
|
+
if (!dryRun) writeFileSync(configPath, generateDetectedConfig(scan, 'docs'), 'utf8');
|
|
174
|
+
process.stdout.write(` ${dryTag}${green('create')} dotmd.config.mjs (detected ${scan.docCount} docs)\n`);
|
|
161
175
|
} else {
|
|
162
|
-
writeFileSync(configPath, STARTER_CONFIG, 'utf8');
|
|
163
|
-
process.stdout.write(` ${green('create')} dotmd.config.mjs\n`);
|
|
176
|
+
if (!dryRun) writeFileSync(configPath, STARTER_CONFIG, 'utf8');
|
|
177
|
+
process.stdout.write(` ${dryTag}${green('create')} dotmd.config.mjs\n`);
|
|
164
178
|
}
|
|
165
179
|
}
|
|
166
180
|
|
|
167
181
|
if (existsSync(docsDir)) {
|
|
168
|
-
process.stdout.write(` ${dim('exists')} docs/\n`);
|
|
182
|
+
process.stdout.write(` ${dryTag}${dim('exists')} docs/\n`);
|
|
169
183
|
} else {
|
|
170
|
-
mkdirSync(docsDir, { recursive: true });
|
|
171
|
-
process.stdout.write(` ${green('create')} docs/\n`);
|
|
184
|
+
if (!dryRun) mkdirSync(docsDir, { recursive: true });
|
|
185
|
+
process.stdout.write(` ${dryTag}${green('create')} docs/\n`);
|
|
172
186
|
}
|
|
173
187
|
|
|
174
188
|
// Inspect root-level siblings (e.g. ./plans/, ./prompts/) before scaffolding.
|
|
@@ -192,25 +206,25 @@ export function runInit(cwd, config) {
|
|
|
192
206
|
const counts = scan?.subdirCounts?.[sub];
|
|
193
207
|
const total = counts ? counts.withFrontmatter + counts.withoutFrontmatter : 0;
|
|
194
208
|
if (siblingSet.has(sub) && !existsSync(subPath)) {
|
|
195
|
-
process.stdout.write(` ${yellow('skip')} docs/${sub}/ (root-level ./${sub}/ already holds content)\n`);
|
|
209
|
+
process.stdout.write(` ${dryTag}${yellow('skip')} docs/${sub}/ (root-level ./${sub}/ already holds content)\n`);
|
|
196
210
|
continue;
|
|
197
211
|
}
|
|
198
212
|
if (existsSync(subPath)) {
|
|
199
213
|
const detail = total > 0
|
|
200
214
|
? ` (${counts.withFrontmatter} dotmd-tracked, ${counts.withoutFrontmatter} plain .md)`
|
|
201
215
|
: '';
|
|
202
|
-
process.stdout.write(` ${dim('exists')} docs/${sub}/${detail}\n`);
|
|
216
|
+
process.stdout.write(` ${dryTag}${dim('exists')} docs/${sub}/${detail}\n`);
|
|
203
217
|
} else {
|
|
204
|
-
mkdirSync(subPath, { recursive: true });
|
|
205
|
-
process.stdout.write(` ${green('create')} docs/${sub}/\n`);
|
|
218
|
+
if (!dryRun) mkdirSync(subPath, { recursive: true });
|
|
219
|
+
process.stdout.write(` ${dryTag}${green('create')} docs/${sub}/\n`);
|
|
206
220
|
}
|
|
207
221
|
}
|
|
208
222
|
|
|
209
223
|
if (existsSync(indexPath)) {
|
|
210
|
-
process.stdout.write(` ${dim('exists')} docs/docs.md\n`);
|
|
224
|
+
process.stdout.write(` ${dryTag}${dim('exists')} docs/docs.md\n`);
|
|
211
225
|
} else {
|
|
212
|
-
writeFileSync(indexPath, STARTER_INDEX, 'utf8');
|
|
213
|
-
process.stdout.write(` ${green('create')} docs/docs.md\n`);
|
|
226
|
+
if (!dryRun) writeFileSync(indexPath, STARTER_INDEX, 'utf8');
|
|
227
|
+
process.stdout.write(` ${dryTag}${green('create')} docs/docs.md\n`);
|
|
214
228
|
}
|
|
215
229
|
|
|
216
230
|
if (siblingsWithContent.length > 0) {
|
|
@@ -235,24 +249,32 @@ export function runInit(cwd, config) {
|
|
|
235
249
|
const has = current.split('\n').some(l => l.trim() === ignoreLine || l.trim() === '.dotmd');
|
|
236
250
|
if (!has) {
|
|
237
251
|
const sep = current.endsWith('\n') ? '' : '\n';
|
|
238
|
-
writeFileSync(gitignorePath, `${current}${sep}${ignoreLine}\n`, 'utf8');
|
|
239
|
-
process.stdout.write(` ${green('update')} .gitignore (+${ignoreLine})\n`);
|
|
252
|
+
if (!dryRun) writeFileSync(gitignorePath, `${current}${sep}${ignoreLine}\n`, 'utf8');
|
|
253
|
+
process.stdout.write(` ${dryTag}${green('update')} .gitignore (+${ignoreLine})\n`);
|
|
240
254
|
} else {
|
|
241
|
-
process.stdout.write(` ${dim('exists')} .gitignore\n`);
|
|
255
|
+
process.stdout.write(` ${dryTag}${dim('exists')} .gitignore\n`);
|
|
242
256
|
}
|
|
243
257
|
} else {
|
|
244
|
-
writeFileSync(gitignorePath, `${ignoreLine}\n`, 'utf8');
|
|
245
|
-
process.stdout.write(` ${green('create')} .gitignore\n`);
|
|
258
|
+
if (!dryRun) writeFileSync(gitignorePath, `${ignoreLine}\n`, 'utf8');
|
|
259
|
+
process.stdout.write(` ${dryTag}${green('create')} .gitignore\n`);
|
|
246
260
|
}
|
|
247
261
|
|
|
248
|
-
// Claude Code integration — auto-detect .claude/ directory
|
|
262
|
+
// Claude Code integration — auto-detect .claude/ directory.
|
|
263
|
+
// Reports all four scaffold outcomes so the user can't be surprised by
|
|
264
|
+
// either a silent regenerate (pre-fix: `updated` was unreported) or by
|
|
265
|
+
// dotmd skipping a user-managed file (pre-fix: `skipped` was unreported).
|
|
249
266
|
if (config) {
|
|
250
|
-
const results = scaffoldClaudeCommands(cwd, config);
|
|
267
|
+
const results = scaffoldClaudeCommands(cwd, config, { dryRun });
|
|
251
268
|
for (const r of results) {
|
|
269
|
+
const filename = `.claude/commands/${r.name}`;
|
|
252
270
|
if (r.action === 'created') {
|
|
253
|
-
process.stdout.write(` ${green('create')}
|
|
271
|
+
process.stdout.write(` ${dryTag}${green('create')} ${filename}\n`);
|
|
272
|
+
} else if (r.action === 'updated') {
|
|
273
|
+
process.stdout.write(` ${dryTag}${green('update')} ${filename} (v${r.from} → v${r.to})\n`);
|
|
254
274
|
} else if (r.action === 'current') {
|
|
255
|
-
process.stdout.write(` ${dim('
|
|
275
|
+
process.stdout.write(` ${dryTag}${dim('exists')} ${filename}\n`);
|
|
276
|
+
} else if (r.action === 'skipped') {
|
|
277
|
+
process.stdout.write(` ${dryTag}${yellow('skip')} ${filename} (no version marker — user-managed)\n`);
|
|
256
278
|
}
|
|
257
279
|
}
|
|
258
280
|
}
|
package/src/lifecycle.mjs
CHANGED
|
@@ -24,6 +24,21 @@ function findFileRoot(filePath, config) {
|
|
|
24
24
|
return roots.find(r => filePath.startsWith(r + '/')) ?? config.docsRoot;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// Best-effort index regen for any doc-set or doc-status mutation. The
|
|
28
|
+
// generated block groups by status and embeds per-doc snapshots, so any
|
|
29
|
+
// change that affects what would render leaves the index stale. Wrapped
|
|
30
|
+
// in try/catch — a regen failure shouldn't undo the successful mutation,
|
|
31
|
+
// only warn with the recovery command.
|
|
32
|
+
export function regenIndex(config) {
|
|
33
|
+
if (!config.indexPath) return;
|
|
34
|
+
try {
|
|
35
|
+
const index = buildIndex(config);
|
|
36
|
+
writeIndex(renderIndexFile(index, config), config);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
warn(`Could not regenerate index (run \`dotmd index --write\`): ${err.message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
// Pick an archive destination that won't clobber an existing record. If
|
|
28
43
|
// `<dir>/<basename>` is free, returns it unchanged; otherwise appends a UTC
|
|
29
44
|
// timestamp (and a counter on the vanishingly rare same-second collision) so
|
|
@@ -142,10 +157,10 @@ export async function runStatus(argv, config, opts = {}) {
|
|
|
142
157
|
finalPath = targetPath;
|
|
143
158
|
}
|
|
144
159
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
160
|
+
// Regen the index on every status change — `active → planned` etc. drift
|
|
161
|
+
// the per-status sections just as much as archive crossings. Archive paths
|
|
162
|
+
// also benefit (replaces the previously-gated regen).
|
|
163
|
+
regenIndex(config);
|
|
149
164
|
|
|
150
165
|
process.stdout.write(`${green(toRepoPath(finalPath, config.repoRoot))}: ${oldStatus ?? 'unknown'} → ${newStatus}\n`);
|
|
151
166
|
|
|
@@ -230,6 +245,7 @@ export async function runPickup(argv, config, opts = {}) {
|
|
|
230
245
|
}
|
|
231
246
|
if (oldStatus !== 'in-session') {
|
|
232
247
|
updateFrontmatter(filePath, { status: 'in-session', updated: today });
|
|
248
|
+
regenIndex(config);
|
|
233
249
|
}
|
|
234
250
|
// VH append per lease outcome:
|
|
235
251
|
// acquired → `Picked up (<old> → in-session).`
|
|
@@ -336,6 +352,7 @@ export async function runUnpickup(argv, config, opts = {}) {
|
|
|
336
352
|
const today = nowIso();
|
|
337
353
|
updateFrontmatter(filePath, { status: newStatus, updated: today });
|
|
338
354
|
appendVersionHistory(filePath, `Released (in-session → ${newStatus}).`);
|
|
355
|
+
regenIndex(config);
|
|
339
356
|
}
|
|
340
357
|
// If frontmatter is no longer in-session (manual flip), leave it alone.
|
|
341
358
|
} catch (err) {
|
|
@@ -450,6 +467,7 @@ export async function runFinish(argv, config, opts = {}) {
|
|
|
450
467
|
process.stderr.write(`${dim('[dry-run]')} Would update: status: in-session → ${targetStatus}, updated: ${today}\n`);
|
|
451
468
|
} else {
|
|
452
469
|
updateFrontmatter(filePath, { status: targetStatus, updated: today });
|
|
470
|
+
regenIndex(config);
|
|
453
471
|
}
|
|
454
472
|
|
|
455
473
|
if (json) {
|
|
@@ -517,10 +535,7 @@ export function runArchive(argv, config, opts = {}) {
|
|
|
517
535
|
// Auto-update references in other docs
|
|
518
536
|
const updatedRefCount = updateRefsAfterMove(filePath, targetPath, config);
|
|
519
537
|
|
|
520
|
-
|
|
521
|
-
const index = buildIndex(config);
|
|
522
|
-
writeIndex(renderIndexFile(index, config), config);
|
|
523
|
-
}
|
|
538
|
+
regenIndex(config);
|
|
524
539
|
|
|
525
540
|
out.write(`${green('Archived')}: ${oldRepoPath} → ${newRepoPath}\n`);
|
|
526
541
|
if (selfRefsFixed) out.write('Updated references in archived file.\n');
|
package/src/new.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { toRepoPath, die, warn, nowIso } from './util.mjs';
|
|
5
5
|
import { green, dim, bold } from './color.mjs';
|
|
6
6
|
import { isInteractive, promptText } from './prompt.mjs';
|
|
7
|
+
import { regenIndex } from './lifecycle.mjs';
|
|
7
8
|
|
|
8
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const pkg = JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
@@ -133,6 +134,7 @@ Status markers (put in heading text):
|
|
|
133
134
|
'type: prompt',
|
|
134
135
|
`status: ${s}`,
|
|
135
136
|
`created: ${d}`,
|
|
137
|
+
`updated: ${d}`,
|
|
136
138
|
`dotmd_version: ${pkg.version}`,
|
|
137
139
|
`context: ${ctx?.title ? `"${ctx.title.replace(/"/g, '\\"')}"` : ''}`,
|
|
138
140
|
'related_plans:',
|
|
@@ -328,6 +330,8 @@ export async function runNew(argv, config, opts = {}) {
|
|
|
328
330
|
writeFileSync(filePath, content, 'utf8');
|
|
329
331
|
process.stdout.write(`${green('Created')}: ${repoPath} ${dim(`(${typeName})`)}\n`);
|
|
330
332
|
|
|
333
|
+
regenIndex(config);
|
|
334
|
+
|
|
331
335
|
try { config.hooks.onNew?.({ path: repoPath, status, title: docTitle, type: typeName }); } catch (err) { warn(`Hook 'onNew' threw: ${err.message}`); }
|
|
332
336
|
}
|
|
333
337
|
|
package/src/rename.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { toRepoPath, resolveDocPath, die, warn } from './util.mjs';
|
|
4
4
|
import { migrateLease } from './lease.mjs';
|
|
5
5
|
import { collectDocFiles } from './index.mjs';
|
|
6
|
+
import { regenIndex } from './lifecycle.mjs';
|
|
6
7
|
import { gitMv } from './git.mjs';
|
|
7
8
|
import { green, dim } from './color.mjs';
|
|
8
9
|
import { isInteractive, promptText } from './prompt.mjs';
|
|
@@ -101,6 +102,8 @@ export async function runRename(argv, config, opts = {}) {
|
|
|
101
102
|
|
|
102
103
|
try { migrateLease(config, oldRepoPath, newRepoPath); } catch (err) { warn(`Could not migrate lease ${oldRepoPath} → ${newRepoPath}: ${err.message}`); }
|
|
103
104
|
|
|
105
|
+
regenIndex(config);
|
|
106
|
+
|
|
104
107
|
process.stdout.write(`${green('Renamed')}: ${oldRepoPath} → ${newRepoPath}\n`);
|
|
105
108
|
if (updatedCount > 0) {
|
|
106
109
|
process.stdout.write(`Updated references in ${updatedCount} file(s).\n`);
|
package/src/validate.mjs
CHANGED
|
@@ -114,11 +114,16 @@ export function validateDoc(doc, frontmatter, headingTitle, config) {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
// Prompts are intentionally body-only one-shot artifacts: the slug names the
|
|
118
|
+
// prompt, the body IS the payload. Forcing a `title` or blockquote `summary`
|
|
119
|
+
// is friction the prompt format was designed around.
|
|
120
|
+
const skipTitleSummary = doc.type === 'prompt';
|
|
121
|
+
|
|
122
|
+
if (!skipTitleSummary && !headingTitle && !asString(frontmatter.title)) {
|
|
118
123
|
doc.warnings.push({ path: doc.path, level: 'warning', message: 'Missing `title` and no H1 found for fallback.' });
|
|
119
124
|
}
|
|
120
125
|
|
|
121
|
-
if (!config.lifecycle.skipWarningsFor.has(doc.status) && !asString(frontmatter.summary) && !doc.summary) {
|
|
126
|
+
if (!skipTitleSummary && !config.lifecycle.skipWarningsFor.has(doc.status) && !asString(frontmatter.summary) && !doc.summary) {
|
|
122
127
|
doc.warnings.push({ path: doc.path, level: 'warning', message: 'Missing `summary` and no blockquote fallback found.' });
|
|
123
128
|
}
|
|
124
129
|
|