dotmd-cli 0.78.0 → 0.79.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/README.md +66 -63
- package/assets/opencode/plugin.js +7 -6
- package/bin/dotmd.mjs +272 -285
- package/package.json +2 -2
- package/scripts/postinstall.mjs +4 -4
- package/src/atomic-mutation.mjs +1 -1
- package/src/baton.mjs +7 -7
- package/src/check-collapse.mjs +5 -5
- package/src/claude-commands.mjs +6 -2
- package/src/commands.mjs +4 -4
- package/src/config.mjs +2 -2
- package/src/deps.mjs +1 -1
- package/src/doctor.mjs +17 -17
- package/src/fix-membership.mjs +1 -1
- package/src/frontmatter-fix.mjs +1 -1
- package/src/git.mjs +1 -1
- package/src/glossary.mjs +3 -3
- package/src/graph.mjs +1 -1
- package/src/guard.mjs +8 -8
- package/src/health.mjs +2 -2
- package/src/hints.mjs +7 -7
- package/src/host-integration.mjs +41 -22
- package/src/hub-membership.mjs +1 -1
- package/src/hud.mjs +14 -14
- package/src/index-file.mjs +2 -2
- package/src/init.mjs +24 -24
- package/src/install.mjs +4 -4
- package/src/journal.mjs +40 -7
- package/src/lifecycle.mjs +17 -17
- package/src/lint.mjs +1 -1
- package/src/migrate-prompts.mjs +1 -1
- package/src/migrate-template.mjs +2 -2
- package/src/migrate.mjs +1 -1
- package/src/misuse-read.mjs +4 -5
- package/src/modules.mjs +3 -3
- package/src/new.mjs +11 -11
- package/src/output-identity.mjs +7 -2
- package/src/pickup-card.mjs +2 -2
- package/src/pickup.mjs +2 -2
- package/src/prompts.mjs +12 -12
- package/src/query.mjs +9 -9
- package/src/rename.mjs +2 -2
- package/src/render.mjs +20 -20
- package/src/roadmap.mjs +5 -5
- package/src/runlist.mjs +11 -11
- package/src/ship.mjs +2 -2
- package/src/skill-drift.mjs +19 -6
- package/src/statuses.mjs +16 -16
- package/src/summary.mjs +1 -1
- package/src/surfaces.mjs +1 -1
- package/src/sync-status.mjs +4 -4
- package/src/update.mjs +11 -11
- package/src/validate.mjs +9 -9
- package/src/watch.mjs +1 -1
package/src/migrate-template.mjs
CHANGED
|
@@ -163,8 +163,8 @@ export function runMigrateTemplate(argv, config, opts = {}) {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
if (dryRun) {
|
|
166
|
-
process.stdout.write(`\nRun ${bold('
|
|
166
|
+
process.stdout.write(`\nRun ${bold('runlist doctor --migrate-template')} without --dry-run to apply.\n`);
|
|
167
167
|
} else {
|
|
168
|
-
process.stdout.write(`\n${green('Done.')} Re-run ${bold('
|
|
168
|
+
process.stdout.write(`\n${green('Done.')} Re-run ${bold('runlist check')} to see remaining issues.\n`);
|
|
169
169
|
}
|
|
170
170
|
}
|
package/src/migrate.mjs
CHANGED
|
@@ -23,7 +23,7 @@ export function runMigrate(argv, config, opts = {}) {
|
|
|
23
23
|
const fileArgs = positional.slice(3);
|
|
24
24
|
|
|
25
25
|
if (!field || !oldValue || !newValue) {
|
|
26
|
-
die('Usage:
|
|
26
|
+
die('Usage: runlist migrate <field> <old-value> <new-value> [files...]');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const allFiles = collectDocFiles(config);
|
package/src/misuse-read.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { readMisuseEntries,
|
|
2
|
+
import { readMisuseEntries, globalMisuseLogPaths } from './journal.mjs';
|
|
3
3
|
import { dim, red, yellow } from './color.mjs';
|
|
4
4
|
|
|
5
5
|
// `dotmd misuse` — read the cross-repo guard log. Every wrong-move the
|
|
@@ -19,11 +19,10 @@ function parseArgs(argv) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function runMisuse(argv, _config) {
|
|
22
|
-
|
|
23
|
-
if (!existsSync(file)) {
|
|
22
|
+
if (!globalMisuseLogPaths().some(file => existsSync(file))) {
|
|
24
23
|
process.stderr.write(
|
|
25
|
-
'No misuse log yet. The PreToolUse guard (`
|
|
26
|
-
'Wire it up: add a PreToolUse hook that runs `
|
|
24
|
+
'No misuse log yet. The PreToolUse guard (`runlist guard`) writes here when it intercepts a wrong move.\n' +
|
|
25
|
+
'Wire it up: add a PreToolUse hook that runs `runlist guard` (see `runlist help guard`).\n',
|
|
27
26
|
);
|
|
28
27
|
return;
|
|
29
28
|
}
|
package/src/modules.mjs
CHANGED
|
@@ -268,7 +268,7 @@ export function runModulesDashboard(index, argv, config) {
|
|
|
268
268
|
|
|
269
269
|
if (!flags.all && rows.length > flags.limit) {
|
|
270
270
|
const hidden = rows.length - flags.limit;
|
|
271
|
-
process.stdout.write('\n' + dim(` ${hidden} more module${hidden === 1 ? '' : 's'} ·
|
|
271
|
+
process.stdout.write('\n' + dim(` ${hidden} more module${hidden === 1 ? '' : 's'} · runlist modules --all\n`));
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
|
|
@@ -283,7 +283,7 @@ export function runModuleDetail(index, argv, config) {
|
|
|
283
283
|
positional.push(arg);
|
|
284
284
|
}
|
|
285
285
|
const name = positional[0];
|
|
286
|
-
if (!name) die('Usage:
|
|
286
|
+
if (!name) die('Usage: runlist module <name>');
|
|
287
287
|
|
|
288
288
|
const { rows } = aggregateModules(index.docs, config);
|
|
289
289
|
const row = rows.find(r => r.name === name);
|
|
@@ -292,7 +292,7 @@ export function runModuleDetail(index, argv, config) {
|
|
|
292
292
|
const suggestions = suggestCandidates(name, candidates);
|
|
293
293
|
const hint = suggestions.length
|
|
294
294
|
? `Did you mean: ${suggestions.join(', ')}?`
|
|
295
|
-
: `Available: ${candidates.slice(0, 5).join(', ')}${candidates.length > 5 ? ', …' : ''} (run \`
|
|
295
|
+
: `Available: ${candidates.slice(0, 5).join(', ')}${candidates.length > 5 ? ', …' : ''} (run \`runlist modules\` for the full list).`;
|
|
296
296
|
die(`Module '${name}' not found. ${hint}`);
|
|
297
297
|
}
|
|
298
298
|
|
package/src/new.mjs
CHANGED
|
@@ -338,7 +338,7 @@ export function readBodyInput(source) {
|
|
|
338
338
|
if (isBodyPlaceholder(file)) {
|
|
339
339
|
die(`\`@${file}\` is a placeholder, not a real path — write the body to a file first, then pass it:\n` +
|
|
340
340
|
` @/tmp/my-draft.md read the body from that file\n` +
|
|
341
|
-
` - read the body from stdin (\`cat notes.md |
|
|
341
|
+
` - read the body from stdin (\`cat notes.md | runlist …\`)\n` +
|
|
342
342
|
` --message "..." short one-liners only`);
|
|
343
343
|
}
|
|
344
344
|
die(`Body file not found: ${file}`);
|
|
@@ -447,8 +447,8 @@ ${bodyInput?.trim() ?? ''}
|
|
|
447
447
|
|
|
448
448
|
${steps}
|
|
449
449
|
|
|
450
|
-
Pick up the next child with \`
|
|
451
|
-
first non-archived child. \`
|
|
450
|
+
Pick up the next child with \`runlist runlist next ${hubSlug}\` — it targets the
|
|
451
|
+
first non-archived child. \`runlist runlist ${hubSlug}\` shows the sequence + status.
|
|
452
452
|
|
|
453
453
|
## Version History
|
|
454
454
|
|
|
@@ -493,7 +493,7 @@ function roadmapHubBody(title, hubSlug, bodyInput, today) {
|
|
|
493
493
|
# ${title}
|
|
494
494
|
|
|
495
495
|
> One-paragraph: the domain this roadmap composes. A roadmap points at *runlists*
|
|
496
|
-
> (not leaf plans) and rolls their done/total up — see \`
|
|
496
|
+
> (not leaf plans) and rolls their done/total up — see \`runlist roadmap ${hubSlug}\`.
|
|
497
497
|
|
|
498
498
|
## Scope
|
|
499
499
|
|
|
@@ -687,7 +687,7 @@ export async function runNew(argv, config, opts = {}) {
|
|
|
687
687
|
name = await promptText(`${typeName} name: `);
|
|
688
688
|
if (!name) die('No name provided.');
|
|
689
689
|
} else {
|
|
690
|
-
die(`Usage:
|
|
690
|
+
die(`Usage: runlist new <type> <name> [body]\n types: ${[...knownTypes].join(', ')}\n body: inline text | piped stdin (auto) | "@path" (file) | --body "..."`);
|
|
691
691
|
}
|
|
692
692
|
}
|
|
693
693
|
|
|
@@ -734,7 +734,7 @@ export async function runNew(argv, config, opts = {}) {
|
|
|
734
734
|
if (planShapes.length > 0 && typeName !== 'plan') {
|
|
735
735
|
const flag = planShapes[0][0];
|
|
736
736
|
const usage = flag === '--runlist' ? '--runlist a,b,c' : flag;
|
|
737
|
-
die(`${flag} only applies to plans. Use:
|
|
737
|
+
die(`${flag} only applies to plans. Use: runlist new plan <name> ${usage}`);
|
|
738
738
|
}
|
|
739
739
|
if (planShapes.length > 1) {
|
|
740
740
|
die(`${planShapes.map(([f]) => f).join(' and ')} are mutually exclusive — a plan has one body shape. Pick one.`);
|
|
@@ -787,7 +787,7 @@ export async function runNew(argv, config, opts = {}) {
|
|
|
787
787
|
}
|
|
788
788
|
|
|
789
789
|
if (template.requiresBody && (!bodyInput || !bodyInput.trim())) {
|
|
790
|
-
die(`\`${typeName}\` template requires a body. Pipe stdin (\`cat draft.md |
|
|
790
|
+
die(`\`${typeName}\` template requires a body. Pipe stdin (\`cat draft.md | runlist new ${typeName} <slug>\`), pass @path, --body "...", or inline text.`);
|
|
791
791
|
}
|
|
792
792
|
|
|
793
793
|
// Fail-fast when the user passes body input to a template that doesn't
|
|
@@ -817,10 +817,10 @@ export async function runNew(argv, config, opts = {}) {
|
|
|
817
817
|
const builtinAccepts = Boolean(builtin && (builtin.acceptsBody || builtin.requiresBody));
|
|
818
818
|
let cause;
|
|
819
819
|
if (isOverride && builtinAccepts) {
|
|
820
|
-
const where = config.configPath ? toRepoPath(config.configPath, config.repoRoot) : '
|
|
820
|
+
const where = config.configPath ? toRepoPath(config.configPath, config.repoRoot) : 'runlist.config.mjs';
|
|
821
821
|
cause = `Your config (${where}) overrides the built-in \`${typeName}\` template, and the override drops body acceptance.\nFix: in that override, add \`acceptsBody: true\` AND interpolate \`\${ctx?.bodyInput?.trim() ?? ''}\` into your \`body\` fn (e.g., inside \`## Problem\`). Or drop the override to use the built-in.`;
|
|
822
822
|
} else {
|
|
823
|
-
cause = `Either drop the body, switch to a template that accepts it, or set \`acceptsBody: true\` on your custom \`${typeName}\` template in
|
|
823
|
+
cause = `Either drop the body, switch to a template that accepts it, or set \`acceptsBody: true\` on your custom \`${typeName}\` template in runlist.config.mjs.`;
|
|
824
824
|
}
|
|
825
825
|
die(`\`${typeName}\` template does not accept body input, but body was passed via ${bodyInputSource}.${hint}\n${cause}`);
|
|
826
826
|
}
|
|
@@ -909,7 +909,7 @@ export async function runNew(argv, config, opts = {}) {
|
|
|
909
909
|
+ ` ${repoPath}\n\n`
|
|
910
910
|
+ `A name with a \`/\` is read relative to the repo.\n`
|
|
911
911
|
+ `To place it under a root instead:\n`
|
|
912
|
-
+ `
|
|
912
|
+
+ ` runlist new ${typeName} ${name} --root ${path.basename(targetRoot)}\n`
|
|
913
913
|
+ ` → ${toRepoPath(rooted, config.repoRoot)}\n\n`
|
|
914
914
|
+ `Roots: ${allRoots.map(root => path.basename(root)).join(', ')}`);
|
|
915
915
|
}
|
|
@@ -1039,7 +1039,7 @@ export async function runNew(argv, config, opts = {}) {
|
|
|
1039
1039
|
// confusingly). Tell the agent the next step explicitly, and flag a gitignored
|
|
1040
1040
|
// target for any type so "why won't this commit" never happens silently.
|
|
1041
1041
|
if (typeName === 'prompt') {
|
|
1042
|
-
process.stdout.write(dim('Session-local — no need to commit. The next session runs `
|
|
1042
|
+
process.stdout.write(dim('Session-local — no need to commit. The next session runs `runlist use` (or `runlist use ' + repoPath + '`) to consume it.\n'));
|
|
1043
1043
|
}
|
|
1044
1044
|
// Teach the field-length contract at the moment the fields get written —
|
|
1045
1045
|
// learning it from a cap warning later sends sessions into hand-trim /
|
package/src/output-identity.mjs
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
export const HTML_FALLBACK_DIR = '
|
|
4
|
+
export const HTML_FALLBACK_DIR = '__runlist';
|
|
5
|
+
// Exports written by dotmd-cli 0.78.0 and earlier put fallbacks under this
|
|
6
|
+
// directory, and an old export tree may still hold it. It stays reserved so a
|
|
7
|
+
// document's own path can never land inside a stale fallback directory.
|
|
8
|
+
export const LEGACY_HTML_FALLBACK_DIR = '__dotmd';
|
|
9
|
+
const RESERVED_FALLBACK_DIRS = [HTML_FALLBACK_DIR, LEGACY_HTML_FALLBACK_DIR];
|
|
5
10
|
|
|
6
11
|
function normalizeRelative(value, kind) {
|
|
7
12
|
if (typeof value !== 'string' || value.includes('\0')) {
|
|
@@ -59,7 +64,7 @@ export function allocateOutputIdentities(docs) {
|
|
|
59
64
|
preferred,
|
|
60
65
|
conflict: preferred.toLowerCase() === 'index.html'
|
|
61
66
|
|| preferred.toLowerCase().startsWith('index.html/')
|
|
62
|
-
|| preferred.toLowerCase().startsWith(`${
|
|
67
|
+
|| RESERVED_FALLBACK_DIRS.some(dir => preferred.toLowerCase().startsWith(`${dir.toLowerCase()}/`)),
|
|
63
68
|
});
|
|
64
69
|
}
|
|
65
70
|
|
package/src/pickup-card.mjs
CHANGED
|
@@ -208,7 +208,7 @@ export function buildCard(filePath, raw, config) {
|
|
|
208
208
|
// Render the card to human-format string.
|
|
209
209
|
export function renderCard(card) {
|
|
210
210
|
const lines = [];
|
|
211
|
-
lines.push(`[
|
|
211
|
+
lines.push(`[runlist] in-session: ${card.path} — close with: runlist set <status> ${card.path}`);
|
|
212
212
|
lines.push('---');
|
|
213
213
|
lines.push(`# ${card.title}`);
|
|
214
214
|
const meta = [card.status, card.updated && `updated ${card.updated}`].filter(Boolean).join(' · ');
|
|
@@ -266,7 +266,7 @@ export function renderCard(card) {
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
lines.push('');
|
|
269
|
-
lines.push(dim(`Body: ${formatBytes(card.bodyBytes)}. \`
|
|
269
|
+
lines.push(dim(`Body: ${formatBytes(card.bodyBytes)}. \`runlist set in-session ${card.path} --full\` for everything, or Read the file with offset/limit to target a section by line number.`));
|
|
270
270
|
return lines.join('\n') + '\n';
|
|
271
271
|
}
|
|
272
272
|
|
package/src/pickup.mjs
CHANGED
|
@@ -251,7 +251,7 @@ export function prepareOwnershipMigration(oldRepoPath, newPath, config, { sessio
|
|
|
251
251
|
// `rename` has no --force of its own: a rename carries the claim across to
|
|
252
252
|
// the new path rather than ending it, so the takeover has to happen first.
|
|
253
253
|
throw planBusyError(oldRepoPath, ownership,
|
|
254
|
-
`release it first with \`
|
|
254
|
+
`release it first with \`runlist set <status> ${oldRepoPath} --force\`, then rename.`);
|
|
255
255
|
}
|
|
256
256
|
const identity = plannedPlanIdentity(newPath, config);
|
|
257
257
|
const recordPath = recordPathForIdentity(identity, config);
|
|
@@ -459,7 +459,7 @@ export function releaseVanishedPlanClaim(claim, config, { now = new Date().toISO
|
|
|
459
459
|
throw new Error(`Claim changed under us for ${claim.plan}; re-run to see the current state.`);
|
|
460
460
|
}
|
|
461
461
|
if (existsSync(path.resolve(config.repoRoot, ownership.plan))) {
|
|
462
|
-
throw new Error(`${ownership.plan} exists after all — release it with \`
|
|
462
|
+
throw new Error(`${ownership.plan} exists after all — release it with \`runlist set active\` instead.`);
|
|
463
463
|
}
|
|
464
464
|
replaceSnapshot(snapshot, JSON.stringify({
|
|
465
465
|
schema: OWNERSHIP_SCHEMA,
|
package/src/prompts.mjs
CHANGED
|
@@ -33,9 +33,9 @@ export async function runPrompts(argv, config, opts = {}) {
|
|
|
33
33
|
const sub = argv[0];
|
|
34
34
|
|
|
35
35
|
if (sub && REMOVED_SUBCOMMANDS.has(sub)) {
|
|
36
|
-
die(`\`
|
|
36
|
+
die(`\`runlist prompts ${sub}\` was removed — a prompt is pending or archived, and nothing else.\n`
|
|
37
37
|
+ 'A prompt directs work, so parking one instead of archiving it leaves work state outside the\n'
|
|
38
|
-
+ 'plan that owns it. Lift its content into that plan, then `
|
|
38
|
+
+ 'plan that owns it. Lift its content into that plan, then `runlist prompts archive <file>`.');
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
if (!sub || !SUBCOMMANDS.has(sub)) {
|
|
@@ -242,7 +242,7 @@ export function resolvePromptInput(input, config, options = {}) {
|
|
|
242
242
|
|
|
243
243
|
async function runPromptsUse(argv, config, opts = {}) {
|
|
244
244
|
const input = argv.find(a => !a.startsWith('-'));
|
|
245
|
-
if (!input) die('Usage:
|
|
245
|
+
if (!input) die('Usage: runlist prompts use <file-or-slug>');
|
|
246
246
|
const noIndex = argv.includes('--no-index') || opts.noIndex;
|
|
247
247
|
const showFiles = argv.includes('--show-files') || opts.showFiles;
|
|
248
248
|
const noClaim = argv.includes('--no-claim');
|
|
@@ -374,8 +374,8 @@ export async function writeConsumedBody(body, archivedPath, write = null, linked
|
|
|
374
374
|
}
|
|
375
375
|
}
|
|
376
376
|
catch (err) {
|
|
377
|
-
const completion = linkedPlan ? ` Then complete the linked claim with \`
|
|
378
|
-
throw new Error(`Prompt was consumed but body output failed (${err.code ?? err.message}). It will not be emitted again; recover it with \`
|
|
377
|
+
const completion = linkedPlan ? ` Then complete the linked claim with \`runlist use ${linkedPlan}\`.` : '';
|
|
378
|
+
throw new Error(`Prompt was consumed but body output failed (${err.code ?? err.message}). It will not be emitted again; recover it with \`runlist prompts show ${archivedPath}\`.${completion}`);
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
381
|
|
|
@@ -395,7 +395,7 @@ export async function writeConsumedBody(body, archivedPath, write = null, linked
|
|
|
395
395
|
function explainUnclaimablePlan(disposition, repoPath, status, startable) {
|
|
396
396
|
switch (disposition.kind) {
|
|
397
397
|
case 'parked':
|
|
398
|
-
return `${repoPath} is ${status} — \`
|
|
398
|
+
return `${repoPath} is ${status} — \`runlist set ${startable} ${repoPath}\` to unpark it, then \`runlist use ${repoPath}\``;
|
|
399
399
|
case 'busy':
|
|
400
400
|
return `${repoPath} is claimed by another session (${disposition.owner})`;
|
|
401
401
|
case 'terminal':
|
|
@@ -406,7 +406,7 @@ function explainUnclaimablePlan(disposition, repoPath, status, startable) {
|
|
|
406
406
|
case 'unconfigured-status':
|
|
407
407
|
return `${repoPath} has a status this repo does not configure (${status ?? 'none'})`;
|
|
408
408
|
case 'ownership-corrupt':
|
|
409
|
-
return `${repoPath} has an unreadable ownership record — \`
|
|
409
|
+
return `${repoPath} has an unreadable ownership record — \`runlist doctor --claims\``;
|
|
410
410
|
default:
|
|
411
411
|
return `${repoPath} cannot be claimed (${disposition.kind})`;
|
|
412
412
|
}
|
|
@@ -504,7 +504,7 @@ function runPromptsShow(argv, config) {
|
|
|
504
504
|
const inputs = argv.filter((a, i) => !a.startsWith('-') && i !== limitValueIdx);
|
|
505
505
|
|
|
506
506
|
if (!inputs.length && !all) {
|
|
507
|
-
die('Usage:
|
|
507
|
+
die('Usage: runlist prompts show <file-or-slug>...\n runlist prompts show --all [--limit N] # peek the whole pending queue');
|
|
508
508
|
}
|
|
509
509
|
if (inputs.length && all) die('Pass either prompt names or --all, not both.');
|
|
510
510
|
|
|
@@ -532,7 +532,7 @@ function runPromptsShow(argv, config) {
|
|
|
532
532
|
if (shown > 0) process.stdout.write('\n');
|
|
533
533
|
process.stdout.write(dim(`──── ${repoPath} [${status}] ────\n`));
|
|
534
534
|
} else {
|
|
535
|
-
process.stderr.write(dim(`${repoPath} [${status}] — read-only peek; \`
|
|
535
|
+
process.stderr.write(dim(`${repoPath} [${status}] — read-only peek; \`runlist use ${repoPath}\` to consume\n`));
|
|
536
536
|
}
|
|
537
537
|
process.stdout.write(body);
|
|
538
538
|
if (!body.endsWith('\n')) process.stdout.write('\n');
|
|
@@ -545,13 +545,13 @@ function runPromptsShow(argv, config) {
|
|
|
545
545
|
const truncated = total > targets.length;
|
|
546
546
|
if (multi || truncated) {
|
|
547
547
|
const suffix = truncated ? ` of ${total} (use --limit to change)` : '';
|
|
548
|
-
process.stderr.write(dim(`\n${shown} prompt${shown === 1 ? '' : 's'}${suffix} — read-only peek; \`
|
|
548
|
+
process.stderr.write(dim(`\n${shown} prompt${shown === 1 ? '' : 's'}${suffix} — read-only peek; \`runlist use <file>\` to consume one\n`));
|
|
549
549
|
}
|
|
550
550
|
}
|
|
551
551
|
|
|
552
552
|
function runPromptsArchive(argv, config, opts = {}) {
|
|
553
553
|
const input = argv.find(a => !a.startsWith('-'));
|
|
554
|
-
if (!input) die('Usage:
|
|
554
|
+
if (!input) die('Usage: runlist prompts archive <file-or-slug>');
|
|
555
555
|
const noIndex = argv.includes('--no-index') || opts.noIndex;
|
|
556
556
|
const showFiles = argv.includes('--show-files') || opts.showFiles;
|
|
557
557
|
const filePath = resolvePromptInput(input, config);
|
|
@@ -568,7 +568,7 @@ function runPromptsArchive(argv, config, opts = {}) {
|
|
|
568
568
|
|
|
569
569
|
async function runPromptsNew(argv, config, opts = {}) {
|
|
570
570
|
if (!argv[0] || argv[0].startsWith('-')) {
|
|
571
|
-
die('Usage:
|
|
571
|
+
die('Usage: runlist prompts new <slug> [body]\n body: inline text | piped stdin (auto) | "@path" (file) | --body "..."');
|
|
572
572
|
}
|
|
573
573
|
return runNew(['prompt', ...argv], config, opts);
|
|
574
574
|
}
|
package/src/query.mjs
CHANGED
|
@@ -83,7 +83,7 @@ export function runQuery(index, argv, config, opts = {}) {
|
|
|
83
83
|
if (opts.type && !filters.types) filters.types = opts.type.split(',').map(v => v.trim()).filter(Boolean);
|
|
84
84
|
if (opts.root && !filters.root) filters.root = opts.root;
|
|
85
85
|
if (filters.body && !filters.keyword) {
|
|
86
|
-
die('`--body` extends a keyword search into document bodies — pass `--keyword <term>` (or use `
|
|
86
|
+
die('`--body` extends a keyword search into document bodies — pass `--keyword <term>` (or use `runlist grep <term>`).');
|
|
87
87
|
}
|
|
88
88
|
const docs = filterDocs(index.docs, filters, config, opts.gitMetadataOptions);
|
|
89
89
|
const summaryPreviewSkipped = filters.summarize && config._execution?.suppressSideEffects;
|
|
@@ -193,7 +193,7 @@ export function runRunlists(index, argv, config) {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
const roadmapPointer = roadmapCount > 0
|
|
196
|
-
? dim(` ${roadmapCount} roadmap${roadmapCount === 1 ? '' : 's'} ·
|
|
196
|
+
? dim(` ${roadmapCount} roadmap${roadmapCount === 1 ? '' : 's'} · runlist roadmaps\n`)
|
|
197
197
|
: '';
|
|
198
198
|
|
|
199
199
|
if (hubs.length === 0) {
|
|
@@ -206,7 +206,7 @@ export function runRunlists(index, argv, config) {
|
|
|
206
206
|
const shown = hubs.slice(0, limit);
|
|
207
207
|
renderCoordinationSection(shown, coordination, maxWidth, hubs.length);
|
|
208
208
|
const hidden = hubs.length - shown.length;
|
|
209
|
-
if (hidden > 0) process.stdout.write(dim(` ${hidden} more ·
|
|
209
|
+
if (hidden > 0) process.stdout.write(dim(` ${hidden} more · runlist runlists --limit ${hubs.length}\n`));
|
|
210
210
|
if (roadmapPointer) process.stdout.write(roadmapPointer);
|
|
211
211
|
process.stdout.write('\n');
|
|
212
212
|
}
|
|
@@ -651,7 +651,7 @@ function renderPlansOutput(docs, filters, config, opts = {}) {
|
|
|
651
651
|
const mainHidden = mainAll.length - mainShown.length;
|
|
652
652
|
if (mainHidden > 0) {
|
|
653
653
|
process.stdout.write('\n');
|
|
654
|
-
process.stdout.write(dim(` ${mainHidden} more ${noun} ·
|
|
654
|
+
process.stdout.write(dim(` ${mainHidden} more ${noun} · runlist ${noun} --all · runlist ${noun} status\n`));
|
|
655
655
|
}
|
|
656
656
|
|
|
657
657
|
// Roadmaps tier — pinned above Runlists, with the recursive grand total.
|
|
@@ -659,7 +659,7 @@ function renderPlansOutput(docs, filters, config, opts = {}) {
|
|
|
659
659
|
renderRoadmapsSection(roadmapShown, roadmap, maxWidth, roadmapAll.length);
|
|
660
660
|
const roadmapHidden = roadmapAll.length - roadmapShown.length;
|
|
661
661
|
if (roadmapHidden > 0) {
|
|
662
|
-
process.stdout.write(dim(` ${roadmapHidden} more roadmaps ·
|
|
662
|
+
process.stdout.write(dim(` ${roadmapHidden} more roadmaps · runlist roadmaps\n`));
|
|
663
663
|
}
|
|
664
664
|
}
|
|
665
665
|
|
|
@@ -667,7 +667,7 @@ function renderPlansOutput(docs, filters, config, opts = {}) {
|
|
|
667
667
|
renderCoordinationSection(coordShown, coordination, maxWidth, coordAll.length);
|
|
668
668
|
const coordHidden = coordAll.length - coordShown.length;
|
|
669
669
|
if (coordHidden > 0) {
|
|
670
|
-
process.stdout.write(dim(` ${coordHidden} more runlists ·
|
|
670
|
+
process.stdout.write(dim(` ${coordHidden} more runlists · runlist ${noun} --all\n`));
|
|
671
671
|
}
|
|
672
672
|
}
|
|
673
673
|
|
|
@@ -684,7 +684,7 @@ function renderPlansOutput(docs, filters, config, opts = {}) {
|
|
|
684
684
|
&& !archiveStatuses.has(v.doc.status)
|
|
685
685
|
&& !isArchivedPath(v.doc.path, config)).length;
|
|
686
686
|
if (filterHidden > 0) {
|
|
687
|
-
process.stdout.write(dim(` ${filterHidden} runlist${filterHidden === 1 ? '' : 's'} hidden by filter ·
|
|
687
|
+
process.stdout.write(dim(` ${filterHidden} runlist${filterHidden === 1 ? '' : 's'} hidden by filter · runlist runlists\n`));
|
|
688
688
|
}
|
|
689
689
|
}
|
|
690
690
|
|
|
@@ -696,7 +696,7 @@ function renderPlansOutput(docs, filters, config, opts = {}) {
|
|
|
696
696
|
const hidden = totalAll - totalShown;
|
|
697
697
|
if (hidden > 0) {
|
|
698
698
|
process.stdout.write('\n');
|
|
699
|
-
process.stdout.write(dim(` ${hidden} more ${noun} ·
|
|
699
|
+
process.stdout.write(dim(` ${hidden} more ${noun} · runlist ${noun} --all · runlist ${noun} status\n`));
|
|
700
700
|
}
|
|
701
701
|
|
|
702
702
|
process.stdout.write('\n');
|
|
@@ -876,7 +876,7 @@ function renderHubBlock(hub, info, children, maxWidth, topMaxSlug) {
|
|
|
876
876
|
// bird's-eye), distinct from the Runlists section's per-hub rollup. `dotmd
|
|
877
877
|
// roadmap <hub>` expands one into its child rows.
|
|
878
878
|
function renderRoadmapsSection(roadmapDocs, roadmap, maxWidth, total) {
|
|
879
|
-
process.stdout.write(`\n${bold(`Roadmaps (${total ?? roadmapDocs.length})`)} ${dim('·
|
|
879
|
+
process.stdout.write(`\n${bold(`Roadmaps (${total ?? roadmapDocs.length})`)} ${dim('· runlist roadmap')}\n`);
|
|
880
880
|
const maxSlug = Math.min(34, Math.max(...roadmapDocs.map(d => hubLabel(d).length)));
|
|
881
881
|
for (const doc of roadmapDocs) {
|
|
882
882
|
const info = roadmap?.get(doc.path);
|
package/src/rename.mjs
CHANGED
|
@@ -16,9 +16,9 @@ export async function runRename(argv, config, opts = {}) {
|
|
|
16
16
|
const positional = argv.filter(arg => !arg.startsWith('-'));
|
|
17
17
|
const oldInput = positional[0];
|
|
18
18
|
let newInput = positional[1];
|
|
19
|
-
if (!oldInput) die('Usage:
|
|
19
|
+
if (!oldInput) die('Usage: runlist rename <old> <new>');
|
|
20
20
|
if (!newInput) {
|
|
21
|
-
if (!isInteractive()) die('Usage:
|
|
21
|
+
if (!isInteractive()) die('Usage: runlist rename <old> <new>');
|
|
22
22
|
newInput = await promptText('New name: ');
|
|
23
23
|
if (!newInput) die('No name provided.');
|
|
24
24
|
}
|
package/src/render.mjs
CHANGED
|
@@ -284,7 +284,7 @@ function _renderContext(index, config, opts = {}) {
|
|
|
284
284
|
const shown = stale.slice(0, cap).map(d => `${toSlug(d)} (${d.daysSinceUpdate}d)`).join(', ');
|
|
285
285
|
const overflow = stale.length - cap;
|
|
286
286
|
const tail = overflow > 0
|
|
287
|
-
? `${shown}, …and ${overflow} more (run \`
|
|
287
|
+
? `${shown}, …and ${overflow} more (run \`runlist stale\` for the full list)`
|
|
288
288
|
: shown;
|
|
289
289
|
lines.push(`Stale: ${tail}`);
|
|
290
290
|
} else {
|
|
@@ -297,7 +297,7 @@ function _renderContext(index, config, opts = {}) {
|
|
|
297
297
|
const parts = [];
|
|
298
298
|
if (withErrors.length) parts.push(`${withErrors.length} with errors`);
|
|
299
299
|
if (withWarnings.length) parts.push(`${withWarnings.length} with warnings`);
|
|
300
|
-
lines.push(`Non-compliant: ${parts.join(', ')} (run \`
|
|
300
|
+
lines.push(`Non-compliant: ${parts.join(', ')} (run \`runlist check\` for details)`);
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
const ctx = config.context;
|
|
@@ -371,10 +371,10 @@ export function renderBriefing(index, config) {
|
|
|
371
371
|
lines.push(` > ${path.basename(p.path, '.md')} (${p.status}) ${next}`);
|
|
372
372
|
}
|
|
373
373
|
if (liveRoadmaps.length) {
|
|
374
|
-
lines.push(` ${liveRoadmaps.length} roadmap${liveRoadmaps.length === 1 ? '' : 's'} ${dim('·
|
|
374
|
+
lines.push(` ${liveRoadmaps.length} roadmap${liveRoadmaps.length === 1 ? '' : 's'} ${dim('· runlist roadmaps')}`);
|
|
375
375
|
}
|
|
376
376
|
if (liveRunlists) {
|
|
377
|
-
lines.push(` ${liveRunlists} runlist${liveRunlists === 1 ? '' : 's'} ${dim('·
|
|
377
|
+
lines.push(` ${liveRunlists} runlist${liveRunlists === 1 ? '' : 's'} ${dim('· runlist runlists')}`);
|
|
378
378
|
}
|
|
379
379
|
}
|
|
380
380
|
|
|
@@ -396,7 +396,7 @@ export function renderBriefing(index, config) {
|
|
|
396
396
|
// with no clue what or where. `dotmd check` is the canonical detail view.
|
|
397
397
|
const errorCount = index.errors.length;
|
|
398
398
|
const errorPart = errorCount > 0
|
|
399
|
-
? `Errors: ${errorCount} ${dim('(run `
|
|
399
|
+
? `Errors: ${errorCount} ${dim('(run `runlist check` to see)')}`
|
|
400
400
|
: `Errors: ${errorCount}`;
|
|
401
401
|
lines.push(`Stale: ${stale} | ${errorPart} | Warnings: ${index.warnings.length}`);
|
|
402
402
|
|
|
@@ -420,13 +420,13 @@ export function classifyIssueAction(issue) {
|
|
|
420
420
|
// re-matching their prose. Drift is mechanically fixable; an unreadable cell
|
|
421
421
|
// is not — nothing can guess which word in it was meant to be the status.
|
|
422
422
|
if (issue?.meta?.kind === 'hub-status-drift') {
|
|
423
|
-
return { action: '
|
|
423
|
+
return { action: 'runlist sync-status', fixable: true, label: 'hub status drift' };
|
|
424
424
|
}
|
|
425
425
|
if (issue?.meta?.kind === 'hub-status-unreadable') {
|
|
426
426
|
return { action: `edit ${file}: lead the status cell with the status word, or wrap it in <!--s-->…<!--/s-->`, fixable: false, label: 'hub status rows' };
|
|
427
427
|
}
|
|
428
428
|
if (issue?.meta?.kind === 'hub-membership-backref') {
|
|
429
|
-
return { action: '
|
|
429
|
+
return { action: 'runlist fix-membership', fixable: true, label: 'membership back-references' };
|
|
430
430
|
}
|
|
431
431
|
if (issue?.meta?.kind === 'hub-membership-orphan') {
|
|
432
432
|
return { action: `edit ${file}: add the child to the hub's meaningful list, or correct the child's parent_plan`, fixable: false, label: 'orphan membership claims' };
|
|
@@ -436,41 +436,41 @@ export function classifyIssueAction(issue) {
|
|
|
436
436
|
}
|
|
437
437
|
|
|
438
438
|
if (/Missing frontmatter `status`/.test(message)) {
|
|
439
|
-
return { action: `
|
|
439
|
+
return { action: `runlist bulk-tag ${file}`, fixable: false, label: 'missing status' };
|
|
440
440
|
}
|
|
441
441
|
if (/Missing frontmatter `updated`/.test(message) || /frontmatter `updated: .*` is behind git history/.test(message)) {
|
|
442
|
-
return { action: '
|
|
442
|
+
return { action: 'runlist touch --git', fixable: true, label: 'dates' };
|
|
443
443
|
}
|
|
444
|
-
if ((/`(?:module|surface):` \(singular\) is deprecated/.test(message) && /Run `
|
|
444
|
+
if ((/`(?:module|surface):` \(singular\) is deprecated/.test(message) && /Run `runlist lint --fix`/.test(message))
|
|
445
445
|
|| /camelCase|nextStep|currentState|auditLevel/.test(message)) {
|
|
446
|
-
return { action: '
|
|
446
|
+
return { action: 'runlist lint --fix', fixable: true, label: 'frontmatter migrations' };
|
|
447
447
|
}
|
|
448
448
|
if (/Unknown surface/.test(message)) {
|
|
449
|
-
return { action: '
|
|
449
|
+
return { action: 'runlist surfaces', fixable: false, label: 'taxonomy' };
|
|
450
450
|
}
|
|
451
451
|
if (/Glossary config points at section|Glossary file configured/.test(message)) {
|
|
452
|
-
return { action: 'edit
|
|
452
|
+
return { action: 'edit runlist.config.mjs glossary.path/glossary.section', fixable: false, label: 'glossary config' };
|
|
453
453
|
}
|
|
454
454
|
if (/under `.*\/` but `status: .*` is not an archive status/.test(message)) {
|
|
455
|
-
return { action: message.match(/Run `([^`]+)`/)?.[1] ?? `
|
|
455
|
+
return { action: message.match(/Run `([^`]+)`/)?.[1] ?? `runlist set archived ${file}`, fixable: true, label: 'archive drift' };
|
|
456
456
|
}
|
|
457
457
|
if (/`status: .*` but file is a direct child/.test(message)) {
|
|
458
|
-
return { action: `
|
|
458
|
+
return { action: `runlist archive ${file}`, fixable: true, label: 'archive drift' };
|
|
459
459
|
}
|
|
460
460
|
if (/`current_state` is \d+ chars|`next_step` is \d+ chars/.test(message)) {
|
|
461
|
-
return { action: '
|
|
461
|
+
return { action: 'runlist doctor --frontmatter-fix', fixable: true, label: 'long frontmatter' };
|
|
462
462
|
}
|
|
463
463
|
if (/`modules` is required/.test(message)) {
|
|
464
|
-
return { action: `edit ${file} modules: or run
|
|
464
|
+
return { action: `edit ${file} modules: or run runlist lint --fix if scaffolded empty`, fixable: false, label: 'module metadata' };
|
|
465
465
|
}
|
|
466
466
|
if (issue?.meta?.kind === 'body-link-resolution') {
|
|
467
467
|
if (issue.meta.targetKind === 'document' && issue.meta.reason === 'missing') {
|
|
468
|
-
return { action: '
|
|
468
|
+
return { action: 'runlist fix-refs --dry-run', fixable: true, label: 'document links' };
|
|
469
469
|
}
|
|
470
470
|
return { action: `edit ${file}: correct the linked file, asset, or directory`, fixable: false, label: 'file links' };
|
|
471
471
|
}
|
|
472
472
|
if (/entry `.*` does not resolve/.test(message)) {
|
|
473
|
-
return { action: '
|
|
473
|
+
return { action: 'runlist fix-refs --dry-run', fixable: true, label: 'references' };
|
|
474
474
|
}
|
|
475
475
|
|
|
476
476
|
return { action: `edit ${file}`, fixable: false, label: 'manual review' };
|
|
@@ -573,7 +573,7 @@ function _renderCheck(index, config, opts = {}) {
|
|
|
573
573
|
}
|
|
574
574
|
lines.push('');
|
|
575
575
|
} else {
|
|
576
|
-
lines.push(dim(`Run \`
|
|
576
|
+
lines.push(dim(`Run \`runlist check --verbose\` for per-doc detail. \`runlist doctor --apply\` auto-fixes supported issues (bare \`runlist doctor\` previews only); remaining issues need the suggested manual command.`));
|
|
577
577
|
lines.push('');
|
|
578
578
|
}
|
|
579
579
|
}
|
package/src/roadmap.mjs
CHANGED
|
@@ -61,7 +61,7 @@ function renderRoadmap(info, maxWidth) {
|
|
|
61
61
|
return lines.join('\n') + '\n';
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
const NO_ROADMAPS = 'No roadmaps found. A roadmap is a plan with `execution_mode: roadmap` that composes runlists.\nScaffold one:
|
|
64
|
+
const NO_ROADMAPS = 'No roadmaps found. A roadmap is a plan with `execution_mode: roadmap` that composes runlists.\nScaffold one: runlist new plan <name> --roadmap\n';
|
|
65
65
|
|
|
66
66
|
// `dotmd roadmaps` — the dashboard over every roadmap hub (mirrors `dotmd
|
|
67
67
|
// runlists`): one row per roadmap with its recursive grand total + child count.
|
|
@@ -106,7 +106,7 @@ export function runRoadmap(index, argv, config) {
|
|
|
106
106
|
const info = roadmaps.get(repoPath);
|
|
107
107
|
if (!info) {
|
|
108
108
|
die(`${repoPath} is not a roadmap hub (needs \`execution_mode: roadmap\`).\n` +
|
|
109
|
-
`See \`
|
|
109
|
+
`See \`runlist roadmaps\` for roadmaps, or \`runlist runlist ${hubArg}\` if it's a runlist.`);
|
|
110
110
|
}
|
|
111
111
|
if (json) { process.stdout.write(JSON.stringify(roadmapJson(info), null, 2) + '\n'); return; }
|
|
112
112
|
process.stdout.write('\n' + renderRoadmap(info, process.stdout.columns || 100) + '\n');
|
|
@@ -139,10 +139,10 @@ function resolveRoadmapTarget(roadmaps, hubArg, config) {
|
|
|
139
139
|
if (!info) die(`${hubArg} is not a roadmap hub (needs \`execution_mode: roadmap\`).`);
|
|
140
140
|
return info;
|
|
141
141
|
}
|
|
142
|
-
if (roadmaps.size === 0) die('No roadmaps found. Scaffold one:
|
|
142
|
+
if (roadmaps.size === 0) die('No roadmaps found. Scaffold one: runlist new plan <name> --roadmap');
|
|
143
143
|
if (roadmaps.size > 1) {
|
|
144
144
|
const listed = [...roadmaps.values()].map(r => ` ${hubLabel(r.doc)}`).join('\n');
|
|
145
|
-
die(`Multiple roadmaps — name one:
|
|
145
|
+
die(`Multiple roadmaps — name one: runlist roadmap next <hub>\n${listed}`);
|
|
146
146
|
}
|
|
147
147
|
return [...roadmaps.values()][0];
|
|
148
148
|
}
|
|
@@ -169,7 +169,7 @@ export async function runRoadmapNext(index, argv, config, opts = {}) {
|
|
|
169
169
|
return ` ${hubLabel(c.doc)} (${c.doneCount}/${c.total} · ${state})`;
|
|
170
170
|
}).join('\n');
|
|
171
171
|
die(`No pickup-able plan across roadmap ${hubLabel(info.doc)} — every child runlist is done or parked:\n${lines}\n` +
|
|
172
|
-
`Unstick one (e.g. \`
|
|
172
|
+
`Unstick one (e.g. \`runlist set active <child>\`), or inspect a runlist: \`runlist runlist <hub>\`.`);
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
if (!json) {
|