dotmd-cli 0.77.3 → 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.
Files changed (55) hide show
  1. package/README.md +70 -60
  2. package/assets/opencode/plugin.js +45 -8
  3. package/bin/dotmd.mjs +274 -283
  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 +86 -50
  8. package/src/check-collapse.mjs +5 -5
  9. package/src/claude-commands.mjs +6 -2
  10. package/src/commands.mjs +8 -8
  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 +1 -1
  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 +13 -10
  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 +15 -12
  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 +18 -15
  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/use.mjs +6 -2
  54. package/src/validate.mjs +9 -9
  55. package/src/watch.mjs +1 -1
package/src/lifecycle.mjs CHANGED
@@ -35,7 +35,7 @@ import {
35
35
 
36
36
  export function renderLifecycleMutation(raw, updates, historyEntry, { createSection = false, bodyTransform = null } = {}) {
37
37
  raw = normalizeEol(raw);
38
- if (!raw.startsWith('---\n')) throw new Error('Document has no frontmatter block. Retrofit it with `dotmd bulk-tag` first.');
38
+ if (!raw.startsWith('---\n')) throw new Error('Document has no frontmatter block. Retrofit it with `runlist bulk-tag` first.');
39
39
  const endMarker = raw.indexOf('\n---\n', 4);
40
40
  if (endMarker === -1) throw new Error('Document has an unclosed frontmatter block.');
41
41
  let frontmatter = raw.slice(4, endMarker);
@@ -192,7 +192,7 @@ export function regenIndex(config, options = {}) {
192
192
  return true;
193
193
  } catch (err) {
194
194
  if (options.throwOnError) throw err;
195
- warn(`Could not regenerate index (run \`dotmd index\`): ${err.message}`);
195
+ warn(`Could not regenerate index (run \`runlist index\`): ${err.message}`);
196
196
  return false;
197
197
  }
198
198
  }
@@ -360,10 +360,10 @@ export async function runStatus(argv, config, opts = {}) {
360
360
  let newStatus = argv[1];
361
361
 
362
362
  if (!opts.suppressDeprecation) {
363
- process.stderr.write(dim('`dotmd status <file> <status>` is deprecated; prefer `dotmd set <status> [<file>]` (note: <status> first; <file> optional when a plan is in-session). Removed in a future major.\n'));
363
+ process.stderr.write(dim('`runlist status <file> <status>` is deprecated; prefer `runlist set <status> [<file>]` (note: <status> first; <file> optional when a plan is in-session). Removed in a future major.\n'));
364
364
  }
365
365
 
366
- if (!input) { die('Usage: dotmd status <file> <new-status>'); }
366
+ if (!input) { die('Usage: runlist status <file> <new-status>'); }
367
367
 
368
368
  let filePath = resolveDocArg(input, config);
369
369
  const sourceAuthorization = authorizeManagedSource(filePath, config, { kind: 'Status source' });
@@ -394,7 +394,7 @@ export async function runStatus(argv, config, opts = {}) {
394
394
  newStatus = await promptChoice('Which status?', effectiveOrder);
395
395
  if (!newStatus) die('No status selected.');
396
396
  } else {
397
- die('Usage: dotmd status <file> <new-status>');
397
+ die('Usage: runlist status <file> <new-status>');
398
398
  }
399
399
  }
400
400
 
@@ -526,7 +526,7 @@ export async function runStatus(argv, config, opts = {}) {
526
526
  // this so concurrent agents can do path-limited commits without pulling
527
527
  // each other's uncommitted index changes into the staging area.
528
528
  if (noIndex) {
529
- process.stderr.write(dim('(index not regenerated — run `dotmd index` to refresh)\n'));
529
+ process.stderr.write(dim('(index not regenerated — run `runlist index` to refresh)\n'));
530
530
  } else if (!opts.deferIndex) {
531
531
  regenIndex(config);
532
532
  }
@@ -568,7 +568,7 @@ export async function startPlan(argv, config, opts = {}) {
568
568
 
569
569
  // Interactive: pick from active/planned plans
570
570
  if (!input) {
571
- if (!isInteractive()) die('Usage: dotmd use <plan>');
571
+ if (!isInteractive()) die('Usage: runlist use <plan>');
572
572
  const index = buildIndex(config);
573
573
  const sessionId = authoritativeSessionId();
574
574
  const candidates = pickupCandidates(index, config, sessionId);
@@ -672,7 +672,7 @@ export async function startPlan(argv, config, opts = {}) {
672
672
  } else {
673
673
  process.stderr.write(`${green('▶ Started')}: ${repoPath} (${oldStatus ?? 'unset'} → in-session)\n\n`);
674
674
  if (fullBody) {
675
- const header = `[dotmd] in-session: ${repoPath} — close with: dotmd set <status> ${repoPath}\n---\n`;
675
+ const header = `[runlist] in-session: ${repoPath} — close with: runlist set <status> ${repoPath}\n---\n`;
676
676
  process.stdout.write(header);
677
677
  const content = (body ?? '').trim();
678
678
  if (content) process.stdout.write(content + '\n');
@@ -707,7 +707,7 @@ export function runArchive(argv, config, opts = {}) {
707
707
  }
708
708
  const input = argv[0];
709
709
 
710
- if (!input) { die('Usage: dotmd archive <file>'); }
710
+ if (!input) { die('Usage: runlist archive <file>'); }
711
711
 
712
712
  let filePath = resolveDocArg(input, config);
713
713
  const sourceAuthorization = authorizeManagedSource(filePath, config, { kind: 'Archive source' });
@@ -861,7 +861,7 @@ export function runArchive(argv, config, opts = {}) {
861
861
  if (selfRefsFixed) out.write('Updated references in archived file.\n');
862
862
  if (updatedRefCount > 0) out.write(`Updated references in ${updatedRefCount} file(s).\n`);
863
863
  if (config.indexPath && indexRegenerated) out.write('Index regenerated.\n');
864
- if (config.indexPath && noIndex) out.write(dim('(index not regenerated — run `dotmd index` to refresh)\n'));
864
+ if (config.indexPath && noIndex) out.write(dim('(index not regenerated — run `runlist index` to refresh)\n'));
865
865
 
866
866
  const touched = [oldRepoPath, newRepoPath, ...refTouchedPaths];
867
867
  if (config.indexPath && indexRegenerated) touched.push(config.indexPath);
@@ -931,7 +931,7 @@ export async function runSet(argv, config, opts = {}) {
931
931
  const newStatus = argv[0];
932
932
  let input = argv[1];
933
933
 
934
- if (!newStatus) die('Usage: dotmd set <status> [<path>]');
934
+ if (!newStatus) die('Usage: runlist set <status> [<path>]');
935
935
  let sessionId = null;
936
936
  if (!input) {
937
937
  sessionId = authoritativeSessionId();
@@ -1051,7 +1051,7 @@ export async function runSet(argv, config, opts = {}) {
1051
1051
  // `@<draft-file>` stays an obvious metavariable: the old `@draft` read as
1052
1052
  // a runnable path and got copied verbatim into failing batons. If it is
1053
1053
  // copied anyway, readBodyInput recognizes the <…> form and says so.
1054
- warn(`wrapping up? leave a baton so the next session picks up cleanly — write your resume notes to a file, then \`dotmd baton ${path.basename(filePath, '.md')} @<draft-file>\` saves a resume prompt (no copy-paste into chat).`);
1054
+ warn(`wrapping up? leave a baton so the next session picks up cleanly — write your resume notes to a file, then \`runlist baton ${path.basename(filePath, '.md')} @<draft-file>\` saves a resume prompt (no copy-paste into chat).`);
1055
1055
  }
1056
1056
  }
1057
1057
  return result;
@@ -1063,7 +1063,7 @@ export function runBulkArchive(argv, config, opts = {}) {
1063
1063
  const noIndex = argv.includes('--no-index') || opts.noIndex;
1064
1064
  const showFiles = argv.includes('--show-files') || opts.showFiles;
1065
1065
  const inputs = argv.filter(a => !a.startsWith('-'));
1066
- if (inputs.length === 0) die('Usage: dotmd bulk archive <file1> <file2> ... or <glob>');
1066
+ if (inputs.length === 0) die('Usage: runlist bulk archive <file1> <file2> ... or <glob>');
1067
1067
 
1068
1068
  const allFiles = collectDocFiles(config);
1069
1069
  const matched = [];
@@ -1124,7 +1124,7 @@ export function runBulkArchive(argv, config, opts = {}) {
1124
1124
  catch (err) { indexError = err.message; }
1125
1125
  if (config.indexPath && indexRegenerated) process.stdout.write('Index regenerated.\n');
1126
1126
  } else if (config.indexPath) {
1127
- process.stdout.write(dim('(index not regenerated — run `dotmd index` to refresh)\n'));
1127
+ process.stdout.write(dim('(index not regenerated — run `runlist index` to refresh)\n'));
1128
1128
  }
1129
1129
  if (showFiles) {
1130
1130
  const all = [...bulkTouched];
@@ -1248,8 +1248,8 @@ export function runTouch(argv, config, opts = {}) {
1248
1248
  return;
1249
1249
  }
1250
1250
 
1251
- if (inputs.length > 1) die('Multiple files require `dotmd touch --git <file...>`.');
1252
- if (!input) { die('Usage: dotmd touch <file>\n dotmd touch --git Bulk-sync dates from git history'); }
1251
+ if (inputs.length > 1) die('Multiple files require `runlist touch --git <file...>`.');
1252
+ if (!input) { die('Usage: runlist touch <file>\n runlist touch --git Bulk-sync dates from git history'); }
1253
1253
 
1254
1254
  let filePath = resolveDocArg(input, config);
1255
1255
  filePath = authorizeManagedSource(filePath, config, { kind: 'Touch source' }).path;
@@ -1358,7 +1358,7 @@ export function updateFrontmatter(filePath, updates) {
1358
1358
  // Name the remedy in the error: this is where every status verb lands when a
1359
1359
  // doc was created outside dotmd, and "no frontmatter block" alone left
1360
1360
  // sessions retrying other verbs instead of fixing the doc.
1361
- if (!raw.startsWith('---\n')) throw new Error(`${filePath} has no frontmatter block. Retrofit it first: dotmd bulk-tag ${filePath} --type <type> --status <status>`);
1361
+ if (!raw.startsWith('---\n')) throw new Error(`${filePath} has no frontmatter block. Retrofit it first: runlist bulk-tag ${filePath} --type <type> --status <status>`);
1362
1362
 
1363
1363
  const endMarker = raw.indexOf('\n---\n', 4);
1364
1364
  if (endMarker === -1) throw new Error(`${filePath} has unclosed frontmatter block.`);
package/src/lint.mjs CHANGED
@@ -218,7 +218,7 @@ export function runLint(argv, config, opts = {}) {
218
218
  }
219
219
  }
220
220
  }
221
- process.stdout.write(`\nRun ${bold('dotmd lint --fix')} to auto-fix.\n`);
221
+ process.stdout.write(`\nRun ${bold('runlist lint --fix')} to auto-fix.\n`);
222
222
  }
223
223
 
224
224
  if (nonFixable.length > 0) {
@@ -167,5 +167,5 @@ export function runMigratePrompts(argv, config, opts = {}) {
167
167
  process.stdout.write(dim(` [${c.kind}] ${c.detail}\n`));
168
168
  }
169
169
  }
170
- if (dryRun) process.stdout.write(`\nRun ${bold('dotmd doctor --migrate-prompts')} without --dry-run to apply.\n`);
170
+ if (dryRun) process.stdout.write(`\nRun ${bold('runlist doctor --migrate-prompts')} without --dry-run to apply.\n`);
171
171
  }
@@ -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
@@ -132,7 +132,10 @@ ${ctx?.bodyInput?.trim() ?? ''}
132
132
  description: 'Execution plan — build-up shape (Problem → Phases → Closeout) with phase status markers and Version History',
133
133
  dir: 'plans',
134
134
  targetRoot: 'plans',
135
- defaultStatus: 'active',
135
+ // A scaffolded plan is scoped, not being worked; `dotmd use` starts it.
136
+ // Defaulting to `active` forced a second, guarded status write for every
137
+ // plan that was only being written down.
138
+ defaultStatus: 'planned',
136
139
  // Body input lands in the Problem section. Plans don't have an Overview;
137
140
  // Problem is the established opening section in the build-up shape.
138
141
  acceptsBody: true,
@@ -335,7 +338,7 @@ export function readBodyInput(source) {
335
338
  if (isBodyPlaceholder(file)) {
336
339
  die(`\`@${file}\` is a placeholder, not a real path — write the body to a file first, then pass it:\n` +
337
340
  ` @/tmp/my-draft.md read the body from that file\n` +
338
- ` - read the body from stdin (\`cat notes.md | dotmd …\`)\n` +
341
+ ` - read the body from stdin (\`cat notes.md | runlist …\`)\n` +
339
342
  ` --message "..." short one-liners only`);
340
343
  }
341
344
  die(`Body file not found: ${file}`);
@@ -444,8 +447,8 @@ ${bodyInput?.trim() ?? ''}
444
447
 
445
448
  ${steps}
446
449
 
447
- Pick up the next child with \`dotmd runlist next ${hubSlug}\` — it targets the
448
- 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.
449
452
 
450
453
  ## Version History
451
454
 
@@ -490,7 +493,7 @@ function roadmapHubBody(title, hubSlug, bodyInput, today) {
490
493
  # ${title}
491
494
 
492
495
  > One-paragraph: the domain this roadmap composes. A roadmap points at *runlists*
493
- > (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}\`.
494
497
 
495
498
  ## Scope
496
499
 
@@ -684,7 +687,7 @@ export async function runNew(argv, config, opts = {}) {
684
687
  name = await promptText(`${typeName} name: `);
685
688
  if (!name) die('No name provided.');
686
689
  } else {
687
- 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 "..."`);
688
691
  }
689
692
  }
690
693
 
@@ -731,7 +734,7 @@ export async function runNew(argv, config, opts = {}) {
731
734
  if (planShapes.length > 0 && typeName !== 'plan') {
732
735
  const flag = planShapes[0][0];
733
736
  const usage = flag === '--runlist' ? '--runlist a,b,c' : flag;
734
- 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}`);
735
738
  }
736
739
  if (planShapes.length > 1) {
737
740
  die(`${planShapes.map(([f]) => f).join(' and ')} are mutually exclusive — a plan has one body shape. Pick one.`);
@@ -784,7 +787,7 @@ export async function runNew(argv, config, opts = {}) {
784
787
  }
785
788
 
786
789
  if (template.requiresBody && (!bodyInput || !bodyInput.trim())) {
787
- 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.`);
788
791
  }
789
792
 
790
793
  // Fail-fast when the user passes body input to a template that doesn't
@@ -814,10 +817,10 @@ export async function runNew(argv, config, opts = {}) {
814
817
  const builtinAccepts = Boolean(builtin && (builtin.acceptsBody || builtin.requiresBody));
815
818
  let cause;
816
819
  if (isOverride && builtinAccepts) {
817
- 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';
818
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.`;
819
822
  } else {
820
- 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.`;
821
824
  }
822
825
  die(`\`${typeName}\` template does not accept body input, but body was passed via ${bodyInputSource}.${hint}\n${cause}`);
823
826
  }
@@ -906,7 +909,7 @@ export async function runNew(argv, config, opts = {}) {
906
909
  + ` ${repoPath}\n\n`
907
910
  + `A name with a \`/\` is read relative to the repo.\n`
908
911
  + `To place it under a root instead:\n`
909
- + ` dotmd new ${typeName} ${name} --root ${path.basename(targetRoot)}\n`
912
+ + ` runlist new ${typeName} ${name} --root ${path.basename(targetRoot)}\n`
910
913
  + ` → ${toRepoPath(rooted, config.repoRoot)}\n\n`
911
914
  + `Roots: ${allRoots.map(root => path.basename(root)).join(', ')}`);
912
915
  }
@@ -1036,7 +1039,7 @@ export async function runNew(argv, config, opts = {}) {
1036
1039
  // confusingly). Tell the agent the next step explicitly, and flag a gitignored
1037
1040
  // target for any type so "why won't this commit" never happens silently.
1038
1041
  if (typeName === 'prompt') {
1039
- 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'));
1040
1043
  }
1041
1044
  // Teach the field-length contract at the moment the fields get written —
1042
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)) {
@@ -196,7 +196,7 @@ async function runPromptsNext(argv, config, opts = {}) {
196
196
  }
197
197
  const head = queue[0];
198
198
  if (!head.abs) die(`Could not resolve path: ${head.doc.path}`);
199
- return consumePrompt(head.abs, config, opts);
199
+ return consumePrompt(head.abs, config, { ...opts, noClaim: argv.includes('--no-claim') });
200
200
  }
201
201
 
202
202
  // Resolve user input to a prompt path. Tries (in order): exact path,
@@ -242,11 +242,12 @@ 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
+ const noClaim = argv.includes('--no-claim');
248
249
  const filePath = resolvePromptInput(input, config);
249
- return consumePrompt(filePath, config, { ...opts, noIndex, showFiles });
250
+ return consumePrompt(filePath, config, { ...opts, noIndex, showFiles, noClaim });
250
251
  }
251
252
 
252
253
  export async function consumePrompt(filePath, config, opts) {
@@ -269,7 +270,9 @@ export async function consumePrompt(filePath, config, opts) {
269
270
  const planRef = asString(parsed.plan);
270
271
  let linkedClaim = null;
271
272
  let claimSkipReason = null;
272
- if (planRef) {
273
+ if (planRef && opts.noClaim) {
274
+ claimSkipReason = `--no-claim, ${planRef} left as it is`;
275
+ } else if (planRef) {
273
276
  const outcome = prepareLinkedPromptClaim(planRef, config, path.dirname(filePath));
274
277
  if (outcome?.skipped) claimSkipReason = outcome.reason;
275
278
  else linkedClaim = outcome;
@@ -371,8 +374,8 @@ export async function writeConsumedBody(body, archivedPath, write = null, linked
371
374
  }
372
375
  }
373
376
  catch (err) {
374
- const completion = linkedPlan ? ` Then complete the linked claim with \`dotmd use ${linkedPlan}\`.` : '';
375
- 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}`);
376
379
  }
377
380
  }
378
381
 
@@ -392,7 +395,7 @@ export async function writeConsumedBody(body, archivedPath, write = null, linked
392
395
  function explainUnclaimablePlan(disposition, repoPath, status, startable) {
393
396
  switch (disposition.kind) {
394
397
  case 'parked':
395
- 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}\``;
396
399
  case 'busy':
397
400
  return `${repoPath} is claimed by another session (${disposition.owner})`;
398
401
  case 'terminal':
@@ -403,7 +406,7 @@ function explainUnclaimablePlan(disposition, repoPath, status, startable) {
403
406
  case 'unconfigured-status':
404
407
  return `${repoPath} has a status this repo does not configure (${status ?? 'none'})`;
405
408
  case 'ownership-corrupt':
406
- return `${repoPath} has an unreadable ownership record — \`dotmd doctor --claims\``;
409
+ return `${repoPath} has an unreadable ownership record — \`runlist doctor --claims\``;
407
410
  default:
408
411
  return `${repoPath} cannot be claimed (${disposition.kind})`;
409
412
  }
@@ -501,7 +504,7 @@ function runPromptsShow(argv, config) {
501
504
  const inputs = argv.filter((a, i) => !a.startsWith('-') && i !== limitValueIdx);
502
505
 
503
506
  if (!inputs.length && !all) {
504
- 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');
505
508
  }
506
509
  if (inputs.length && all) die('Pass either prompt names or --all, not both.');
507
510
 
@@ -529,7 +532,7 @@ function runPromptsShow(argv, config) {
529
532
  if (shown > 0) process.stdout.write('\n');
530
533
  process.stdout.write(dim(`──── ${repoPath} [${status}] ────\n`));
531
534
  } else {
532
- 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`));
533
536
  }
534
537
  process.stdout.write(body);
535
538
  if (!body.endsWith('\n')) process.stdout.write('\n');
@@ -542,13 +545,13 @@ function runPromptsShow(argv, config) {
542
545
  const truncated = total > targets.length;
543
546
  if (multi || truncated) {
544
547
  const suffix = truncated ? ` of ${total} (use --limit to change)` : '';
545
- 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`));
546
549
  }
547
550
  }
548
551
 
549
552
  function runPromptsArchive(argv, config, opts = {}) {
550
553
  const input = argv.find(a => !a.startsWith('-'));
551
- if (!input) die('Usage: dotmd prompts archive <file-or-slug>');
554
+ if (!input) die('Usage: runlist prompts archive <file-or-slug>');
552
555
  const noIndex = argv.includes('--no-index') || opts.noIndex;
553
556
  const showFiles = argv.includes('--show-files') || opts.showFiles;
554
557
  const filePath = resolvePromptInput(input, config);
@@ -565,7 +568,7 @@ function runPromptsArchive(argv, config, opts = {}) {
565
568
 
566
569
  async function runPromptsNew(argv, config, opts = {}) {
567
570
  if (!argv[0] || argv[0].startsWith('-')) {
568
- 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 "..."');
569
572
  }
570
573
  return runNew(['prompt', ...argv], config, opts);
571
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
  }