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/runlist.mjs
CHANGED
|
@@ -426,7 +426,7 @@ function renderRunlist(hubRepoPath, children, opts = {}) {
|
|
|
426
426
|
const parked = children.filter(c => !c.missing && !archiveStatuses.has(c.status));
|
|
427
427
|
lines.push(dim(parked.length === 0
|
|
428
428
|
? ' All children archived. Hub is ready for archive.'
|
|
429
|
-
: ` No pickup-able child — ${parked.length} parked. Unstick one (e.g. \`
|
|
429
|
+
: ` No pickup-able child — ${parked.length} parked. Unstick one (e.g. \`runlist set active <child>\`) to continue.`));
|
|
430
430
|
}
|
|
431
431
|
return lines.join('\n') + '\n';
|
|
432
432
|
}
|
|
@@ -497,7 +497,7 @@ async function runRunlistAdd(positional, config, { dryRun, json, testHooks }) {
|
|
|
497
497
|
const hubInput = positional[0];
|
|
498
498
|
const childTokens = positional.slice(1);
|
|
499
499
|
if (!hubInput || childTokens.length === 0) {
|
|
500
|
-
die('Usage:
|
|
500
|
+
die('Usage: runlist runlist add <hub-plan> <child...> (one or more child slugs or plan paths)');
|
|
501
501
|
}
|
|
502
502
|
|
|
503
503
|
let hubAbs = resolveHubInput(hubInput, config);
|
|
@@ -596,7 +596,7 @@ async function runRunlistAdd(positional, config, { dryRun, json, testHooks }) {
|
|
|
596
596
|
if (!json) {
|
|
597
597
|
process.stdout.write(dim(` runlist now has ${newRefs.length} ${newRefs.length === 1 ? 'child' : 'children'}.`) + '\n');
|
|
598
598
|
if (!dryRun) {
|
|
599
|
-
process.stdout.write(dim(` Show:
|
|
599
|
+
process.stdout.write(dim(` Show: runlist runlist ${hubRepoPath} · pick up next: runlist runlist next ${hubRepoPath}`) + '\n');
|
|
600
600
|
}
|
|
601
601
|
}
|
|
602
602
|
}
|
|
@@ -729,7 +729,7 @@ function planClearChildParent(childAbs, hubAbs, config) {
|
|
|
729
729
|
// Shared front half of remove/reorder: resolve the hub, read its `runlist:`,
|
|
730
730
|
// die if it isn't a sprint hub with an array to mutate.
|
|
731
731
|
function loadSprintHub(hubInput, verb, config) {
|
|
732
|
-
if (!hubInput) die(`Usage:
|
|
732
|
+
if (!hubInput) die(`Usage: runlist runlist ${verb} <hub-plan> <child...>`);
|
|
733
733
|
let hubAbs = resolveHubInput(hubInput, config);
|
|
734
734
|
if (!hubAbs) die(`Hub plan not found: ${hubInput}`);
|
|
735
735
|
hubAbs = authorizeManagedSource(hubAbs, config, { kind: `Runlist ${verb} hub source` }).path;
|
|
@@ -749,7 +749,7 @@ function loadSprintHub(hubInput, verb, config) {
|
|
|
749
749
|
async function runRunlistRemove(positional, config, { dryRun, json, clearParent, testHooks }) {
|
|
750
750
|
const { hubAbs, hubRepoPath, hubDir, existingRefs, raw: hubRaw } = loadSprintHub(positional[0], 'remove', config);
|
|
751
751
|
const childTokens = positional.slice(1);
|
|
752
|
-
if (childTokens.length === 0) die('Usage:
|
|
752
|
+
if (childTokens.length === 0) die('Usage: runlist runlist remove <hub-plan> <child...>');
|
|
753
753
|
|
|
754
754
|
const removeRefs = [];
|
|
755
755
|
for (const token of childTokens) {
|
|
@@ -814,7 +814,7 @@ function parseReorderArgs(argv) {
|
|
|
814
814
|
async function runRunlistReorder(argv, config, { dryRun, json, testHooks }) {
|
|
815
815
|
const { hubInput, children, before, after } = parseReorderArgs(argv);
|
|
816
816
|
if (!hubInput || children.length === 0) {
|
|
817
|
-
die('Usage:
|
|
817
|
+
die('Usage: runlist runlist reorder <hub> <child> --before|--after <other>\n or: runlist runlist reorder <hub> <child1> <child2> ... (full new order)');
|
|
818
818
|
}
|
|
819
819
|
const { hubAbs, hubRepoPath, hubDir, existingRefs, raw: hubRaw } = loadSprintHub(hubInput, 'reorder', config);
|
|
820
820
|
|
|
@@ -876,8 +876,8 @@ export async function runRunlist(argv, config, opts = {}) {
|
|
|
876
876
|
|
|
877
877
|
if (!hubInput) {
|
|
878
878
|
die(sub === 'next'
|
|
879
|
-
? 'Usage:
|
|
880
|
-
: 'Usage:
|
|
879
|
+
? 'Usage: runlist runlist next <hub-plan>'
|
|
880
|
+
: 'Usage: runlist runlist <hub-plan>');
|
|
881
881
|
}
|
|
882
882
|
|
|
883
883
|
const hubAbs = resolveHubInput(hubInput, config);
|
|
@@ -919,13 +919,13 @@ export async function runRunlist(argv, config, opts = {}) {
|
|
|
919
919
|
`No pickup-able child in runlist ${hubRepoPath} — every remaining child is parked:\n` +
|
|
920
920
|
`${listed}\n` +
|
|
921
921
|
`Unstick one before continuing:\n` +
|
|
922
|
-
`
|
|
923
|
-
`
|
|
922
|
+
` runlist set active <child> # if ready to resume\n` +
|
|
923
|
+
` runlist use <child> # to inspect`,
|
|
924
924
|
);
|
|
925
925
|
}
|
|
926
926
|
const allArchived = children.some(c => !c.missing) && children.every(c => c.missing || archiveStatuses.has(c.status));
|
|
927
927
|
if (allArchived) {
|
|
928
|
-
die(`All children in runlist ${hubRepoPath} are archived. Hub is ready for \`
|
|
928
|
+
die(`All children in runlist ${hubRepoPath} are archived. Hub is ready for \`runlist archive ${hubRepoPath}\`.`);
|
|
929
929
|
}
|
|
930
930
|
const missing = children.filter(c => c.missing).map(c => c.ref);
|
|
931
931
|
die(`No pickup-able child in runlist ${hubRepoPath}. Unresolved refs: ${missing.join(', ')}`);
|
package/src/ship.mjs
CHANGED
|
@@ -76,7 +76,7 @@ export async function runShip(argv, config, opts = {}) {
|
|
|
76
76
|
const positional = argv.filter(a => !a.startsWith('-'));
|
|
77
77
|
const bump = positional[0] ?? 'patch';
|
|
78
78
|
if (!['patch', 'minor', 'major'].includes(bump)) {
|
|
79
|
-
die(`Invalid bump: ${bump}. Usage:
|
|
79
|
+
die(`Invalid bump: ${bump}. Usage: runlist ship [patch|minor|major]`);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
const pkgPath = path.join(config.repoRoot, 'package.json');
|
|
@@ -140,7 +140,7 @@ export async function runShip(argv, config, opts = {}) {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
const subject = `chore: release ${target}`;
|
|
143
|
-
const body = `Auto-staged by \`
|
|
143
|
+
const body = `Auto-staged by \`runlist ship\`:\n${allToStage.map(p => `- ${p}`).join('\n')}`;
|
|
144
144
|
const commitMsg = `${subject}\n\n${body}`;
|
|
145
145
|
const commit = spawnSync('git', ['commit', '-m', commitMsg], { cwd: config.repoRoot, encoding: 'utf8' });
|
|
146
146
|
if (commit.status !== 0) die(`git commit failed: ${commit.stderr || commit.stdout}`);
|
package/src/skill-drift.mjs
CHANGED
|
@@ -15,9 +15,19 @@ import path from 'node:path';
|
|
|
15
15
|
// surface and forgetting the other is exactly what this catches.
|
|
16
16
|
|
|
17
17
|
export const CANONICAL_MARKERS = {
|
|
18
|
+
start: '<!-- runlist:canonical-workflow:start -->',
|
|
19
|
+
end: '<!-- runlist:canonical-workflow:end -->',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// The spelling used through dotmd-cli 0.78.0. Still read, so a copy of either
|
|
23
|
+
// surface that predates the rename keeps participating in the lockstep instead
|
|
24
|
+
// of silently dropping out of it. A block must open and close with the same
|
|
25
|
+
// spelling; the current spelling is tried first.
|
|
26
|
+
export const LEGACY_CANONICAL_MARKERS = {
|
|
18
27
|
start: '<!-- dotmd:canonical-workflow:start -->',
|
|
19
28
|
end: '<!-- dotmd:canonical-workflow:end -->',
|
|
20
29
|
};
|
|
30
|
+
const MARKER_SETS = [CANONICAL_MARKERS, LEGACY_CANONICAL_MARKERS];
|
|
21
31
|
|
|
22
32
|
const CLAUDE_MD = 'CLAUDE.md';
|
|
23
33
|
const SKILL_MD = path.join('plugins', 'dotmd', 'skills', 'dotmd', 'SKILL.md');
|
|
@@ -28,12 +38,15 @@ const SKILL_MD = path.join('plugins', 'dotmd', 'skills', 'dotmd', 'SKILL.md');
|
|
|
28
38
|
// its own CLAUDE.md (but never adopted the block) from ever tripping the guard.
|
|
29
39
|
export function extractCanonicalBlock(text) {
|
|
30
40
|
if (typeof text !== 'string') return null;
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
for (const markers of MARKER_SETS) {
|
|
42
|
+
const startIdx = text.indexOf(markers.start);
|
|
43
|
+
if (startIdx === -1) continue;
|
|
44
|
+
const afterStart = startIdx + markers.start.length;
|
|
45
|
+
const endIdx = text.indexOf(markers.end, afterStart);
|
|
46
|
+
if (endIdx === -1 || endIdx < afterStart) continue;
|
|
47
|
+
return text.slice(afterStart, endIdx);
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
37
50
|
}
|
|
38
51
|
|
|
39
52
|
// Whitespace-tolerant so a CRLF vs LF or a stray trailing space between the two
|
package/src/statuses.mjs
CHANGED
|
@@ -125,8 +125,8 @@ function formatPropDisplay(v) {
|
|
|
125
125
|
async function runAddStatus(args, config, opts) {
|
|
126
126
|
const flags = parseFlags(args, { allowProps: true });
|
|
127
127
|
const name = flags.positional[0];
|
|
128
|
-
if (!name) die('Usage:
|
|
129
|
-
if (!flags.type) die('--type is required for `
|
|
128
|
+
if (!name) die('Usage: runlist statuses add <name> --type <type> [--like <existing>] [flags]');
|
|
129
|
+
if (!flags.type) die('--type is required for `runlist statuses add`.');
|
|
130
130
|
|
|
131
131
|
const validationErr = validateStatusName(name);
|
|
132
132
|
if (validationErr) die(validationErr);
|
|
@@ -135,11 +135,11 @@ async function runAddStatus(args, config, opts) {
|
|
|
135
135
|
const content = readFileSync(config.configPath, 'utf8');
|
|
136
136
|
const parsed = parseStatusesBlock(content, flags.type);
|
|
137
137
|
if (parsed.form === 'array') {
|
|
138
|
-
die(`Type '${flags.type}' uses array-form statuses. Run \`
|
|
138
|
+
die(`Type '${flags.type}' uses array-form statuses. Run \`runlist statuses migrate ${flags.type}\` first to convert to rich form.`);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
if (parsed.entries.some(e => e.name === name)) {
|
|
142
|
-
die(`Status '${name}' already exists in type '${flags.type}'. Use \`
|
|
142
|
+
die(`Status '${name}' already exists in type '${flags.type}'. Use \`runlist statuses set\` to edit it.`);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
// Resolve --like base flags
|
|
@@ -152,7 +152,7 @@ async function runAddStatus(args, config, opts) {
|
|
|
152
152
|
die(`--like target '${likeName}' is not defined in type '${flags.type}'.`);
|
|
153
153
|
}
|
|
154
154
|
if (likeEntry.multiLine) {
|
|
155
|
-
die(`--like target '${likeName}' spans multiple lines in
|
|
155
|
+
die(`--like target '${likeName}' spans multiple lines in runlist.config.mjs; this CLI only edits single-line entries.`);
|
|
156
156
|
}
|
|
157
157
|
baseProps = parseEntryProps(likeEntry.raw);
|
|
158
158
|
}
|
|
@@ -235,8 +235,8 @@ function labelOrigin(key, userProps) {
|
|
|
235
235
|
async function runSetStatus(args, config, opts) {
|
|
236
236
|
const flags = parseFlags(args, { allowProps: true });
|
|
237
237
|
const name = flags.positional[0];
|
|
238
|
-
if (!name) die('Usage:
|
|
239
|
-
if (!flags.type) die('--type is required for `
|
|
238
|
+
if (!name) die('Usage: runlist statuses set <name> --type <type> [flags]');
|
|
239
|
+
if (!flags.type) die('--type is required for `runlist statuses set`.');
|
|
240
240
|
if (Object.keys(flags.props).length === 0) {
|
|
241
241
|
die('At least one flag is required (e.g. --quiet, --staleDays 30).');
|
|
242
242
|
}
|
|
@@ -245,15 +245,15 @@ async function runSetStatus(args, config, opts) {
|
|
|
245
245
|
const content = readFileSync(config.configPath, 'utf8');
|
|
246
246
|
const parsed = parseStatusesBlock(content, flags.type);
|
|
247
247
|
if (parsed.form === 'array') {
|
|
248
|
-
die(`Type '${flags.type}' uses array-form statuses. Run \`
|
|
248
|
+
die(`Type '${flags.type}' uses array-form statuses. Run \`runlist statuses migrate ${flags.type}\` first.`);
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
const existing = parsed.entries.find(e => e.name === name);
|
|
252
252
|
if (!existing) {
|
|
253
|
-
die(`Status '${name}' is not defined in type '${flags.type}'. Use \`
|
|
253
|
+
die(`Status '${name}' is not defined in type '${flags.type}'. Use \`runlist statuses add\` to create it.`);
|
|
254
254
|
}
|
|
255
255
|
if (existing.multiLine) {
|
|
256
|
-
die(`Status '${name}' spans multiple lines in
|
|
256
|
+
die(`Status '${name}' spans multiple lines in runlist.config.mjs; edit it by hand.`);
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
const oldProps = parseEntryProps(existing.raw);
|
|
@@ -306,14 +306,14 @@ function printSetDiff(name, typeName, oldProps, newProps, userProps) {
|
|
|
306
306
|
async function runRemoveStatus(args, config, opts) {
|
|
307
307
|
const flags = parseFlags(args, { allowProps: false });
|
|
308
308
|
const name = flags.positional[0];
|
|
309
|
-
if (!name) die('Usage:
|
|
310
|
-
if (!flags.type) die('--type is required for `
|
|
309
|
+
if (!name) die('Usage: runlist statuses remove <name> --type <type>');
|
|
310
|
+
if (!flags.type) die('--type is required for `runlist statuses remove`.');
|
|
311
311
|
|
|
312
312
|
requireConfigPath(config);
|
|
313
313
|
const content = readFileSync(config.configPath, 'utf8');
|
|
314
314
|
const parsed = parseStatusesBlock(content, flags.type);
|
|
315
315
|
if (parsed.form === 'array') {
|
|
316
|
-
die(`Type '${flags.type}' uses array-form statuses. Run \`
|
|
316
|
+
die(`Type '${flags.type}' uses array-form statuses. Run \`runlist statuses migrate ${flags.type}\` first.`);
|
|
317
317
|
}
|
|
318
318
|
if (!parsed.entries.find(e => e.name === name)) {
|
|
319
319
|
die(`Status '${name}' is not defined in type '${flags.type}'.`);
|
|
@@ -324,7 +324,7 @@ async function runRemoveStatus(args, config, opts) {
|
|
|
324
324
|
if (offenders.length > 0) {
|
|
325
325
|
const list = offenders.slice(0, 10).map(p => ` - ${p}`).join('\n');
|
|
326
326
|
const more = offenders.length > 10 ? `\n ... and ${offenders.length - 10} more` : '';
|
|
327
|
-
die(`${offenders.length} doc(s) currently use status '${name}':\n${list}${more}\n\nMigrate them first: \`
|
|
327
|
+
die(`${offenders.length} doc(s) currently use status '${name}':\n${list}${more}\n\nMigrate them first: \`runlist migrate status ${name} <other> [files...]\``);
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
// Warn (don't refuse) if explicit lifecycle references the name.
|
|
@@ -412,7 +412,7 @@ function findMatchingBrace(content, openPos) {
|
|
|
412
412
|
async function runMigrateType(args, config, opts) {
|
|
413
413
|
const flags = parseFlags(args, { allowProps: false });
|
|
414
414
|
const typeName = flags.positional[0];
|
|
415
|
-
if (!typeName) die('Usage:
|
|
415
|
+
if (!typeName) die('Usage: runlist statuses migrate <type>');
|
|
416
416
|
|
|
417
417
|
requireConfigPath(config);
|
|
418
418
|
const content = readFileSync(config.configPath, 'utf8');
|
|
@@ -581,7 +581,7 @@ function lastNewlineIndexBefore(content, pos) {
|
|
|
581
581
|
|
|
582
582
|
function requireConfigPath(config) {
|
|
583
583
|
if (!config.configPath || !existsSync(config.configPath)) {
|
|
584
|
-
die(`No
|
|
584
|
+
die(`No runlist.config.mjs found in ${process.cwd()} — run \`runlist init\` first.`);
|
|
585
585
|
}
|
|
586
586
|
}
|
|
587
587
|
|
package/src/summary.mjs
CHANGED
|
@@ -22,7 +22,7 @@ export function runSummary(argv, config) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const input = positional[0];
|
|
25
|
-
if (!input) { die('Usage:
|
|
25
|
+
if (!input) { die('Usage: runlist summary <file> [--model <name>] [--json]'); }
|
|
26
26
|
|
|
27
27
|
const filePath = resolveDocArg(input, config);
|
|
28
28
|
|
package/src/surfaces.mjs
CHANGED
|
@@ -20,7 +20,7 @@ export function runSurfaces(argv, config) {
|
|
|
20
20
|
|
|
21
21
|
if (!surfaces || surfaces.length === 0) {
|
|
22
22
|
process.stdout.write(dim('No surface taxonomy configured. Any surface value is accepted.\n'));
|
|
23
|
-
process.stdout.write(dim('To restrict, set `taxonomy.surfaces` in
|
|
23
|
+
process.stdout.write(dim('To restrict, set `taxonomy.surfaces` in runlist.config.mjs.\n'));
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
|
package/src/sync-status.mjs
CHANGED
|
@@ -163,8 +163,8 @@ export function checkHubStatusDrift(docs, config) {
|
|
|
163
163
|
path: hub.path,
|
|
164
164
|
level: row.marked ? 'error' : 'warning',
|
|
165
165
|
message: row.marked
|
|
166
|
-
? `line ${row.line} rows \`${row.target}\` as \`${row.printed}\` inside a \`${MARKER_OPEN}…${MARKER_CLOSE}\` marker, but that doc's status is \`${row.actual}\`. The marker means
|
|
167
|
-
: `line ${row.line} rows \`${row.target}\` as \`${row.printed}\`, but that doc's status is \`${row.actual}\`. Run \`
|
|
166
|
+
? `line ${row.line} rows \`${row.target}\` as \`${row.printed}\` inside a \`${MARKER_OPEN}…${MARKER_CLOSE}\` marker, but that doc's status is \`${row.actual}\`. The marker means runlist owns that word — run \`runlist sync-status\` to rewrite it, or change the doc's status.`
|
|
167
|
+
: `line ${row.line} rows \`${row.target}\` as \`${row.printed}\`, but that doc's status is \`${row.actual}\`. Run \`runlist sync-status\` to rewrite the row, or change the doc's status.`,
|
|
168
168
|
meta: { kind: DRIFT_KIND, target: row.target, printed: row.printed, actual: row.actual, line: row.line, marked: row.marked },
|
|
169
169
|
};
|
|
170
170
|
(row.marked ? errors : warnings).push(entry);
|
|
@@ -257,7 +257,7 @@ export function syncHubStatuses(config, { docs, dryRun = false, adopt = false, h
|
|
|
257
257
|
if (result.unreadable > 0) {
|
|
258
258
|
process.stdout.write(yellow(`${result.unreadable} row(s) sit under a status column with no readable status word`)
|
|
259
259
|
+ ' — lead the cell with the status, or wrap it in '
|
|
260
|
-
+ `${MARKER_OPEN}…${MARKER_CLOSE}. Run \`
|
|
260
|
+
+ `${MARKER_OPEN}…${MARKER_CLOSE}. Run \`runlist check\` to list them.\n`);
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
return result;
|
|
@@ -281,7 +281,7 @@ function resolveHubArgs(args, docs) {
|
|
|
281
281
|
const [match] = matches;
|
|
282
282
|
if (!isHubDoc(match)) {
|
|
283
283
|
die(`${match.path} is not a hub — it has no \`runlist:\` and no \`execution_mode: coordination|roadmap\`. `
|
|
284
|
-
+ 'Run `
|
|
284
|
+
+ 'Run `runlist sync-status` with no argument to sweep every hub.');
|
|
285
285
|
}
|
|
286
286
|
paths.add(match.path);
|
|
287
287
|
}
|
package/src/update.mjs
CHANGED
|
@@ -89,7 +89,7 @@ export function readInstalledPlugin(opts = {}) {
|
|
|
89
89
|
export function planMarketplaceRepair(plugin, { hasClaude, verb }) {
|
|
90
90
|
const reason = `marketplace "${plugin.marketplace}" is not registered, so the installed plugin cannot load`;
|
|
91
91
|
if (plugin.marketplace !== 'dotmd') {
|
|
92
|
-
return [{ kind: 'skip', reason: `${reason} — re-add that marketplace (
|
|
92
|
+
return [{ kind: 'skip', reason: `${reason} — re-add that marketplace (runlist does not know its source), then rerun` }];
|
|
93
93
|
}
|
|
94
94
|
if (!hasClaude) {
|
|
95
95
|
return [{ kind: 'manual', reason, lines: [`/plugin marketplace add ${CLAUDE_MARKETPLACE}`, `/plugin ${verb} ${plugin.id}`] }];
|
|
@@ -112,7 +112,7 @@ export function planUpdate(opts, ctx) {
|
|
|
112
112
|
}
|
|
113
113
|
if (!opts.cliOnly) {
|
|
114
114
|
if (!ctx.plugin) {
|
|
115
|
-
steps.push({ kind: 'skip', reason: '
|
|
115
|
+
steps.push({ kind: 'skip', reason: 'runlist plugin not installed — skipping plugin update' });
|
|
116
116
|
} else if (ctx.plugin.marketplaceRegistered === false) {
|
|
117
117
|
steps.push(...planMarketplaceRepair(ctx.plugin, { hasClaude: ctx.hasClaude, verb: 'update' }));
|
|
118
118
|
} else if (!ctx.hasClaude) {
|
|
@@ -127,7 +127,7 @@ export function planUpdate(opts, ctx) {
|
|
|
127
127
|
if (ctx.opencode?.exists && ctx.opencode.stale) {
|
|
128
128
|
steps.push({ kind: 'opencode', path: ctx.opencode.path });
|
|
129
129
|
} else if (ctx.opencode?.foreign) {
|
|
130
|
-
steps.push({ kind: 'skip', reason: `${ctx.opencode.path} was not written by
|
|
130
|
+
steps.push({ kind: 'skip', reason: `${ctx.opencode.path} was not written by runlist — leaving it alone` });
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
return steps;
|
|
@@ -140,24 +140,24 @@ export function runUpdate(argv, _config, opts = {}) {
|
|
|
140
140
|
const plugin = readInstalledPlugin();
|
|
141
141
|
|
|
142
142
|
if (check) {
|
|
143
|
-
process.stdout.write(`
|
|
143
|
+
process.stdout.write(`runlist CLI: ${pkg.version}\n`);
|
|
144
144
|
if (plugin) {
|
|
145
145
|
const cmp = compareVersions(plugin.version, pkg.version);
|
|
146
146
|
const tag = cmp === 0 ? green('in sync')
|
|
147
147
|
: cmp === null ? dim('(unknown)')
|
|
148
|
-
: cmp < 0 ? yellow('behind — run `
|
|
148
|
+
: cmp < 0 ? yellow('behind — run `runlist update`')
|
|
149
149
|
: yellow('ahead — CLI is behind');
|
|
150
|
-
process.stdout.write(`
|
|
150
|
+
process.stdout.write(`runlist plugin: ${plugin.version ?? '?'} (${plugin.id}) ${tag}\n`);
|
|
151
151
|
if (plugin.marketplaceRegistered === false) {
|
|
152
|
-
process.stdout.write(yellow(` marketplace "${plugin.marketplace}" is not registered — the plugin fails to load; run \`
|
|
152
|
+
process.stdout.write(yellow(` marketplace "${plugin.marketplace}" is not registered — the plugin fails to load; run \`runlist install claude\`\n`));
|
|
153
153
|
}
|
|
154
154
|
} else {
|
|
155
|
-
process.stdout.write(dim('
|
|
155
|
+
process.stdout.write(dim('runlist plugin: not installed — `runlist install claude`\n'));
|
|
156
156
|
}
|
|
157
157
|
const oc = opencodeStatus({ version: pkg.version });
|
|
158
|
-
if (!oc.exists) process.stdout.write(dim('
|
|
159
|
-
else if (oc.foreign) process.stdout.write(`
|
|
160
|
-
else process.stdout.write(`
|
|
158
|
+
if (!oc.exists) process.stdout.write(dim('runlist opencode: not installed\n'));
|
|
159
|
+
else if (oc.foreign) process.stdout.write(`runlist opencode: ${yellow('unmanaged file — not written by runlist')}\n`);
|
|
160
|
+
else process.stdout.write(`runlist opencode: ${oc.version} ${oc.stale ? yellow('behind — run `runlist update`') : green('in sync')}\n`);
|
|
161
161
|
return;
|
|
162
162
|
}
|
|
163
163
|
|
package/src/validate.mjs
CHANGED
|
@@ -143,7 +143,7 @@ export function validateDoc(doc, frontmatter, headingTitle, config) {
|
|
|
143
143
|
doc.warnings.push({
|
|
144
144
|
path: doc.path,
|
|
145
145
|
level: 'warning',
|
|
146
|
-
message: `Unknown surface \`${surface}\`; expected a known surface taxonomy value.${hint} Run \`
|
|
146
|
+
message: `Unknown surface \`${surface}\`; expected a known surface taxonomy value.${hint} Run \`runlist surfaces\` to list all valid values.`,
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
}
|
|
@@ -173,7 +173,7 @@ export function validateDoc(doc, frontmatter, headingTitle, config) {
|
|
|
173
173
|
const autoFixable = singularValues.every(autoFixableString)
|
|
174
174
|
&& pluralValue.every(autoFixableString);
|
|
175
175
|
const guidance = autoFixable
|
|
176
|
-
? `use \`${target}\`. Run \`
|
|
176
|
+
? `use \`${target}\`. Run \`runlist lint --fix\` to migrate.`
|
|
177
177
|
: `use a \`${plural}:\` YAML list. Remove the deprecated \`${singular}:\` block manually and preserve all of its values.`;
|
|
178
178
|
doc.warnings.push({
|
|
179
179
|
path: doc.path,
|
|
@@ -228,7 +228,7 @@ export function validateDoc(doc, frontmatter, headingTitle, config) {
|
|
|
228
228
|
doc.errors.push({
|
|
229
229
|
path: doc.path,
|
|
230
230
|
level: 'error',
|
|
231
|
-
message: `\`status: ${doc.status}\` but file is a direct child of \`${parentDir}/\`, not \`${parentDir}/${config.archiveDir}/\`. Run \`
|
|
231
|
+
message: `\`status: ${doc.status}\` but file is a direct child of \`${parentDir}/\`, not \`${parentDir}/${config.archiveDir}/\`. Run \`runlist archive ${doc.path}\` to relocate to \`${expected}\`, or change the status.`,
|
|
232
232
|
});
|
|
233
233
|
}
|
|
234
234
|
}
|
|
@@ -244,8 +244,8 @@ export function validateDoc(doc, frontmatter, headingTitle, config) {
|
|
|
244
244
|
const parentSegments = path.dirname(doc.path).split('/');
|
|
245
245
|
if (parentSegments.includes(config.archiveDir)) {
|
|
246
246
|
const heal = doc.type === 'prompt'
|
|
247
|
-
? `
|
|
248
|
-
: `
|
|
247
|
+
? `runlist prompts archive ${doc.path}`
|
|
248
|
+
: `runlist set archived ${doc.path}`;
|
|
249
249
|
doc.errors.push({
|
|
250
250
|
path: doc.path,
|
|
251
251
|
level: 'error',
|
|
@@ -492,7 +492,7 @@ export function checkCoordinationHubExecutionMode(docs, config) {
|
|
|
492
492
|
warnings.push({
|
|
493
493
|
path: doc.path,
|
|
494
494
|
level: 'warning',
|
|
495
|
-
message: `reads as a coordination runlist (slug \`${base}\`) but is missing \`execution_mode: coordination\`. Add it so \`
|
|
495
|
+
message: `reads as a coordination runlist (slug \`${base}\`) but is missing \`execution_mode: coordination\`. Add it so \`runlist plans\` / \`runlist runlists\` reliably treat it as a runlist, not an active plan.`,
|
|
496
496
|
});
|
|
497
497
|
}
|
|
498
498
|
return warnings;
|
|
@@ -547,7 +547,7 @@ export function checkRoadmapHubExecutionMode(docs, config) {
|
|
|
547
547
|
warnings.push({
|
|
548
548
|
path: doc.path,
|
|
549
549
|
level: 'warning',
|
|
550
|
-
message: `is a coordination hub whose ${hubChildren} of ${resolved} \`related_plans:\` children are themselves runlists — that's structurally a tier-3 roadmap. Set \`execution_mode: roadmap\` so \`
|
|
550
|
+
message: `is a coordination hub whose ${hubChildren} of ${resolved} \`related_plans:\` children are themselves runlists — that's structurally a tier-3 roadmap. Set \`execution_mode: roadmap\` so \`runlist roadmap\` rolls their progress up (recursive done/total) instead of rendering it flat among them.`,
|
|
551
551
|
});
|
|
552
552
|
}
|
|
553
553
|
}
|
|
@@ -622,7 +622,7 @@ export function validatePlanShape(doc, body, frontmatter, config) {
|
|
|
622
622
|
doc.warnings.push({
|
|
623
623
|
path: doc.path,
|
|
624
624
|
level: 'warning',
|
|
625
|
-
message: `\`next_step\` is ${nextStep.length} chars (cap: 800). One mechanical fix: \`
|
|
625
|
+
message: `\`next_step\` is ${nextStep.length} chars (cap: 800). One mechanical fix: \`runlist doctor --frontmatter-fix\` (moves the overflow into the body) — do NOT hand-trim or re-run check in a loop. Going forward, write next_step as a 1-2 sentence pointer; detail goes in the body.`,
|
|
626
626
|
});
|
|
627
627
|
}
|
|
628
628
|
|
|
@@ -635,7 +635,7 @@ export function validatePlanShape(doc, body, frontmatter, config) {
|
|
|
635
635
|
doc.warnings.push({
|
|
636
636
|
path: doc.path,
|
|
637
637
|
level: 'warning',
|
|
638
|
-
message: `\`current_state\` is ${currentState.length} chars (cap: 1500). One mechanical fix: \`
|
|
638
|
+
message: `\`current_state\` is ${currentState.length} chars (cap: 1500). One mechanical fix: \`runlist doctor --frontmatter-fix\` (moves the overflow into the body) — do NOT hand-trim or re-run check in a loop. Going forward, write current_state as a 2-4 sentence summary; detail goes in the body.`,
|
|
639
639
|
});
|
|
640
640
|
}
|
|
641
641
|
|
package/src/watch.mjs
CHANGED
|
@@ -22,7 +22,7 @@ export function runWatch(argv, config) {
|
|
|
22
22
|
|
|
23
23
|
// Clear terminal
|
|
24
24
|
process.stdout.write('\x1b[2J\x1b[H');
|
|
25
|
-
process.stderr.write(dim(`[${new Date().toLocaleTimeString()}]
|
|
25
|
+
process.stderr.write(dim(`[${new Date().toLocaleTimeString()}] runlist ${subCommand.join(' ')}`) + '\n\n');
|
|
26
26
|
|
|
27
27
|
spawnSync(process.execPath, [cliPath, ...subCommand], {
|
|
28
28
|
stdio: 'inherit',
|