dotmd-cli 0.78.0 → 0.79.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +66 -63
  2. package/assets/opencode/plugin.js +11 -6
  3. package/bin/dotmd.mjs +272 -285
  4. package/package.json +2 -2
  5. package/scripts/postinstall.mjs +4 -4
  6. package/src/atomic-mutation.mjs +1 -1
  7. package/src/baton.mjs +7 -7
  8. package/src/check-collapse.mjs +5 -5
  9. package/src/claude-commands.mjs +6 -2
  10. package/src/commands.mjs +4 -4
  11. package/src/config.mjs +2 -2
  12. package/src/deps.mjs +1 -1
  13. package/src/doctor.mjs +17 -17
  14. package/src/fix-membership.mjs +4 -2
  15. package/src/frontmatter-fix.mjs +1 -1
  16. package/src/git.mjs +1 -1
  17. package/src/glossary.mjs +3 -3
  18. package/src/graph.mjs +1 -1
  19. package/src/guard.mjs +8 -8
  20. package/src/health.mjs +2 -2
  21. package/src/hints.mjs +7 -7
  22. package/src/host-integration.mjs +41 -22
  23. package/src/hub-membership.mjs +1 -1
  24. package/src/hud.mjs +14 -14
  25. package/src/index-file.mjs +2 -2
  26. package/src/init.mjs +24 -24
  27. package/src/install.mjs +4 -4
  28. package/src/journal.mjs +40 -7
  29. package/src/lifecycle.mjs +17 -17
  30. package/src/lint.mjs +1 -1
  31. package/src/migrate-prompts.mjs +1 -1
  32. package/src/migrate-template.mjs +2 -2
  33. package/src/migrate.mjs +1 -1
  34. package/src/misuse-read.mjs +4 -5
  35. package/src/modules.mjs +3 -3
  36. package/src/new.mjs +11 -11
  37. package/src/output-identity.mjs +7 -2
  38. package/src/pickup-card.mjs +2 -2
  39. package/src/pickup.mjs +2 -2
  40. package/src/prompts.mjs +12 -12
  41. package/src/query.mjs +9 -9
  42. package/src/rename.mjs +2 -2
  43. package/src/render.mjs +20 -20
  44. package/src/roadmap.mjs +5 -5
  45. package/src/runlist.mjs +11 -11
  46. package/src/ship.mjs +2 -2
  47. package/src/skill-drift.mjs +19 -6
  48. package/src/statuses.mjs +16 -16
  49. package/src/summary.mjs +1 -1
  50. package/src/surfaces.mjs +1 -1
  51. package/src/sync-status.mjs +4 -4
  52. package/src/update.mjs +11 -11
  53. package/src/validate.mjs +9 -9
  54. package/src/watch.mjs +1 -1
@@ -163,8 +163,8 @@ export function runMigrateTemplate(argv, config, opts = {}) {
163
163
  }
164
164
 
165
165
  if (dryRun) {
166
- process.stdout.write(`\nRun ${bold('dotmd doctor --migrate-template')} without --dry-run to apply.\n`);
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('dotmd check')} to see remaining issues.\n`);
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: dotmd migrate <field> <old-value> <new-value> [files...]');
26
+ die('Usage: runlist migrate <field> <old-value> <new-value> [files...]');
27
27
  }
28
28
 
29
29
  const allFiles = collectDocFiles(config);
@@ -1,5 +1,5 @@
1
1
  import { existsSync } from 'node:fs';
2
- import { readMisuseEntries, globalMisuseLogPath } from './journal.mjs';
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
- const file = globalMisuseLogPath();
23
- if (!existsSync(file)) {
22
+ if (!globalMisuseLogPaths().some(file => existsSync(file))) {
24
23
  process.stderr.write(
25
- 'No misuse log yet. The PreToolUse guard (`dotmd guard`) writes here when it intercepts a wrong move.\n' +
26
- 'Wire it up: add a PreToolUse hook that runs `dotmd guard` (see `dotmd help guard`).\n',
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'} · dotmd modules --all\n`));
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: dotmd module <name>');
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 \`dotmd modules\` for the full list).`;
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 | dotmd …\`)\n` +
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 \`dotmd runlist next ${hubSlug}\` — it targets the
451
- first non-archived child. \`dotmd runlist ${hubSlug}\` shows the sequence + status.
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 \`dotmd roadmap ${hubSlug}\`.
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: dotmd new <type> <name> [body]\n types: ${[...knownTypes].join(', ')}\n body: inline text | piped stdin (auto) | "@path" (file) | --body "..."`);
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: dotmd new plan <name> ${usage}`);
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 | dotmd new ${typeName} <slug>\`), pass @path, --body "...", or inline text.`);
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) : 'dotmd.config.mjs';
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 dotmd.config.mjs.`;
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
- + ` dotmd new ${typeName} ${name} --root ${path.basename(targetRoot)}\n`
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 `dotmd use` (or `dotmd use ' + repoPath + '`) to consume it.\n'));
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 /
@@ -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 = '__dotmd';
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(`${HTML_FALLBACK_DIR.toLowerCase()}/`),
67
+ || RESERVED_FALLBACK_DIRS.some(dir => preferred.toLowerCase().startsWith(`${dir.toLowerCase()}/`)),
63
68
  });
64
69
  }
65
70
 
@@ -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(`[dotmd] in-session: ${card.path} — close with: dotmd set <status> ${card.path}`);
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)}. \`dotmd set in-session ${card.path} --full\` for everything, or Read the file with offset/limit to target a section by line number.`));
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 \`dotmd set <status> ${oldRepoPath} --force\`, then rename.`);
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 \`dotmd set active\` instead.`);
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(`\`dotmd prompts ${sub}\` was removed — a prompt is pending or archived, and nothing else.\n`
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 `dotmd prompts archive <file>`.');
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: dotmd prompts use <file-or-slug>');
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 \`dotmd 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 \`dotmd prompts show ${archivedPath}\`.${completion}`);
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} — \`dotmd set ${startable} ${repoPath}\` to unpark it, then \`dotmd use ${repoPath}\``;
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 — \`dotmd doctor --claims\``;
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: dotmd prompts show <file-or-slug>...\n dotmd prompts show --all [--limit N] # peek the whole pending queue');
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; \`dotmd use ${repoPath}\` to consume\n`));
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; \`dotmd use <file>\` to consume one\n`));
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: dotmd prompts archive <file-or-slug>');
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: dotmd prompts new <slug> [body]\n body: inline text | piped stdin (auto) | "@path" (file) | --body "..."');
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 `dotmd grep <term>`).');
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'} · dotmd roadmaps\n`)
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 · dotmd runlists --limit ${hubs.length}\n`));
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} · dotmd ${noun} --all · dotmd ${noun} status\n`));
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 · dotmd roadmaps\n`));
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 · dotmd ${noun} --all\n`));
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 · dotmd runlists\n`));
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} · dotmd ${noun} --all · dotmd ${noun} status\n`));
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('· dotmd roadmap')}\n`);
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: dotmd rename <old> <new>');
19
+ if (!oldInput) die('Usage: runlist rename <old> <new>');
20
20
  if (!newInput) {
21
- if (!isInteractive()) die('Usage: dotmd rename <old> <new>');
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 \`dotmd stale\` for the full list)`
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 \`dotmd check\` for details)`);
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('· dotmd roadmaps')}`);
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('· dotmd runlists')}`);
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 `dotmd check` to see)')}`
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: 'dotmd sync-status', fixable: true, label: 'hub status drift' };
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: 'dotmd fix-membership', fixable: true, label: 'membership back-references' };
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: `dotmd bulk-tag ${file}`, fixable: false, label: 'missing status' };
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: 'dotmd touch --git', fixable: true, label: 'dates' };
442
+ return { action: 'runlist touch --git', fixable: true, label: 'dates' };
443
443
  }
444
- if ((/`(?:module|surface):` \(singular\) is deprecated/.test(message) && /Run `dotmd lint --fix`/.test(message))
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: 'dotmd lint --fix', fixable: true, label: 'frontmatter migrations' };
446
+ return { action: 'runlist lint --fix', fixable: true, label: 'frontmatter migrations' };
447
447
  }
448
448
  if (/Unknown surface/.test(message)) {
449
- return { action: 'dotmd surfaces', fixable: false, label: 'taxonomy' };
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 dotmd.config.mjs glossary.path/glossary.section', fixable: false, label: 'glossary config' };
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] ?? `dotmd set archived ${file}`, fixable: true, label: 'archive drift' };
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: `dotmd archive ${file}`, fixable: true, label: 'archive drift' };
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: 'dotmd doctor --frontmatter-fix', fixable: true, label: 'long frontmatter' };
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 dotmd lint --fix if scaffolded empty`, fixable: false, label: 'module metadata' };
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: 'dotmd fix-refs --dry-run', fixable: true, label: 'document links' };
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: 'dotmd fix-refs --dry-run', fixable: true, label: 'references' };
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 \`dotmd check --verbose\` for per-doc detail. \`dotmd doctor --apply\` auto-fixes supported issues (bare \`dotmd doctor\` previews only); remaining issues need the suggested manual command.`));
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: dotmd new plan <name> --roadmap\n';
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 \`dotmd roadmaps\` for roadmaps, or \`dotmd runlist ${hubArg}\` if it's a runlist.`);
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: dotmd new plan <name> --roadmap');
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: dotmd roadmap next <hub>\n${listed}`);
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. \`dotmd set active <child>\`), or inspect a runlist: \`dotmd runlist <hub>\`.`);
172
+ `Unstick one (e.g. \`runlist set active <child>\`), or inspect a runlist: \`runlist runlist <hub>\`.`);
173
173
  }
174
174
 
175
175
  if (!json) {