dotmd-cli 0.65.0 → 0.65.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.
- package/bin/dotmd.mjs +13 -6
- package/package.json +1 -1
- package/src/query.mjs +5 -1
- package/src/runlist.mjs +56 -18
package/bin/dotmd.mjs
CHANGED
|
@@ -1219,12 +1219,19 @@ frontmatter — there is no separate doc type. The hub plan can have any status;
|
|
|
1219
1219
|
the order of the children comes from the array.
|
|
1220
1220
|
|
|
1221
1221
|
Usage:
|
|
1222
|
-
dotmd runlist <hub> Show children + their statuses, in order.
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1222
|
+
dotmd runlist <hub> Show children + their statuses, in order. The
|
|
1223
|
+
first pickup-able child (active / planned /
|
|
1224
|
+
in-session) is marked \`→\`. Archived (done) and
|
|
1225
|
+
parked children (blocked / partial / paused /
|
|
1226
|
+
awaiting / queued-after) are skipped — \`→\`
|
|
1227
|
+
advances to the first child you can actually
|
|
1228
|
+
start. Parked ≠ done: they don't count toward
|
|
1229
|
+
done/total.
|
|
1230
|
+
dotmd runlist next <hub> Open the first pickup-able child (marks it
|
|
1231
|
+
in-session + prints it), advancing past archived
|
|
1232
|
+
and parked children. If every remaining child is
|
|
1233
|
+
parked, stops and lists them + the unstick verbs
|
|
1234
|
+
so you resolve a blocker first.
|
|
1228
1235
|
dotmd runlist add <hub> <child...>
|
|
1229
1236
|
Append children to the hub's \`runlist:\` array
|
|
1230
1237
|
(no more hand-editing the YAML). Each child can be:
|
package/package.json
CHANGED
package/src/query.mjs
CHANGED
|
@@ -768,9 +768,13 @@ function renderHubBlock(hub, info, children, maxWidth, topMaxSlug) {
|
|
|
768
768
|
const hubSlug = toSlug(hub);
|
|
769
769
|
const nextDoc = info.nextChildPath ? info.children.find(c => c.path === info.nextChildPath)?.doc : null;
|
|
770
770
|
const nextLabel = nextDoc ? stripHubPrefix(toSlug(nextDoc), hubSlug) : null;
|
|
771
|
+
// No pickup-able child: "N parked" when a live-but-unstartable child remains
|
|
772
|
+
// (the runlist is stuck, not finished), else "all archived" (truly done).
|
|
771
773
|
const descr = nextLabel
|
|
772
774
|
? `runlist · ${info.doneCount}/${info.total} · next → ${nextLabel}`
|
|
773
|
-
:
|
|
775
|
+
: info.parkedCount > 0
|
|
776
|
+
? `runlist · ${info.doneCount}/${info.total} · ${info.parkedCount} parked`
|
|
777
|
+
: `runlist · ${info.doneCount}/${info.total} · all archived`;
|
|
774
778
|
|
|
775
779
|
// Header row: hub slug + descriptor, with `[RUNLIST]` right-aligned like a tag.
|
|
776
780
|
const slugCell = hubSlug.padEnd(topMaxSlug);
|
package/src/runlist.mjs
CHANGED
|
@@ -20,6 +20,20 @@ import { bold, cyan, dim, green, red, yellow } from './color.mjs';
|
|
|
20
20
|
|
|
21
21
|
const PICKUPABLE_STATUSES = new Set(['active', 'planned', 'in-session']);
|
|
22
22
|
|
|
23
|
+
// A child is the runlist's NEXT PICKUP only when a session could start it right
|
|
24
|
+
// now — i.e. its status is one `dotmd use` accepts. The "parked" statuses
|
|
25
|
+
// (blocked/partial/paused/awaiting/queued-after) are deliberately NOT
|
|
26
|
+
// pickup-able: each needs its own unstuck action (monitor / spawn successor /
|
|
27
|
+
// re-evaluate / ask / check predecessor) before work resumes. So next-pickup
|
|
28
|
+
// resolution skips them and advances to the first child that's actually
|
|
29
|
+
// startable. Skipping ≠ done, though: a parked child is not archived, so it
|
|
30
|
+
// never counts toward `done/total` — that tally tracks closed (archived) only.
|
|
31
|
+
// This keeps the `→` marker in agreement with `runlist next`, which already
|
|
32
|
+
// gates on PICKUPABLE_STATUSES.
|
|
33
|
+
function isPickupable(status) {
|
|
34
|
+
return PICKUPABLE_STATUSES.has(status);
|
|
35
|
+
}
|
|
36
|
+
|
|
23
37
|
// Build a hub/child map straight from the in-memory index — no disk IO. A doc
|
|
24
38
|
// is a runlist *hub* when its `runlist:` frontmatter (`refFields.runlist`) is
|
|
25
39
|
// non-empty. Each ref resolves to a doc in the index by path, falling back to
|
|
@@ -61,11 +75,17 @@ export function buildRunlistIndex(index, config) {
|
|
|
61
75
|
}
|
|
62
76
|
}
|
|
63
77
|
|
|
64
|
-
|
|
78
|
+
// Next pickup = first child a session can actually start. Skip archived
|
|
79
|
+
// (done) AND parked children alike; advance to the first pickup-able one.
|
|
80
|
+
const next = children.find(c => !c.missing && !c.archived && isPickupable(c.status)) ?? null;
|
|
65
81
|
hubs.set(hub.path, {
|
|
66
82
|
hub,
|
|
67
83
|
total: children.length,
|
|
68
84
|
doneCount: children.filter(c => c.archived).length,
|
|
85
|
+
// Live-but-not-startable children (parked: blocked/partial/paused/…). Lets
|
|
86
|
+
// the `dotmd plans` fold say "N parked" instead of mislabelling a hub with
|
|
87
|
+
// a parked-but-unfinished child as "all archived".
|
|
88
|
+
parkedCount: children.filter(c => !c.missing && !c.archived && !isPickupable(c.status)).length,
|
|
69
89
|
children,
|
|
70
90
|
nextChildPath: next?.path ?? null,
|
|
71
91
|
});
|
|
@@ -271,6 +291,11 @@ function resolveHubNextPickup(hubDoc, hubDir, resolveRef, archiveStatuses, confi
|
|
|
271
291
|
if (child.type && child.type !== 'plan') continue;
|
|
272
292
|
const archived = archiveStatuses.has(child.status) || isArchivedPath(child.path, config);
|
|
273
293
|
if (archived) continue;
|
|
294
|
+
// Skip parked ranked children too (blocked/partial/paused/awaiting/
|
|
295
|
+
// queued-after) — the hub's next-pickup is the first startable plan, the
|
|
296
|
+
// same gate sprint runlists use, so a hub never points `→` at a child a
|
|
297
|
+
// session can't actually pick up.
|
|
298
|
+
if (!isPickupable(child.status)) continue;
|
|
274
299
|
return { path: child.path, status: child.status ?? null, label: coordinationChildLabel(child, hubDoc) };
|
|
275
300
|
}
|
|
276
301
|
return null;
|
|
@@ -330,7 +355,9 @@ function renderRunlist(hubRepoPath, children, opts = {}) {
|
|
|
330
355
|
lines.push(` ${idx}. ${red('missing')} ${c.ref}`);
|
|
331
356
|
continue;
|
|
332
357
|
}
|
|
333
|
-
|
|
358
|
+
// → marks the first child a session can actually start: skip archived and
|
|
359
|
+
// parked (blocked/partial/paused/awaiting/queued-after) alike.
|
|
360
|
+
const isNext = !nextPicked && isPickupable(c.status);
|
|
334
361
|
if (isNext) nextPicked = true;
|
|
335
362
|
const marker = isNext ? green('→') : ' ';
|
|
336
363
|
const statusTag = `[${colorStatus(c.status)}]`;
|
|
@@ -338,7 +365,12 @@ function renderRunlist(hubRepoPath, children, opts = {}) {
|
|
|
338
365
|
}
|
|
339
366
|
if (!nextPicked) {
|
|
340
367
|
lines.push('');
|
|
341
|
-
|
|
368
|
+
// No pickup-able child. Distinguish "done — ready to archive" from "stuck —
|
|
369
|
+
// a parked child needs unsticking" so the agent knows which it is.
|
|
370
|
+
const parked = children.filter(c => !c.missing && !archiveStatuses.has(c.status));
|
|
371
|
+
lines.push(dim(parked.length === 0
|
|
372
|
+
? ' All children archived. Hub is ready for archive.'
|
|
373
|
+
: ` No pickup-able child — ${parked.length} parked. Unstick one (e.g. \`dotmd set active <child>\`) to continue.`));
|
|
342
374
|
}
|
|
343
375
|
return lines.join('\n') + '\n';
|
|
344
376
|
}
|
|
@@ -817,11 +849,29 @@ export async function runRunlist(argv, config, opts = {}) {
|
|
|
817
849
|
return;
|
|
818
850
|
}
|
|
819
851
|
|
|
820
|
-
// sub === 'next' —
|
|
821
|
-
|
|
852
|
+
// sub === 'next' — pick up the first child a session can actually start.
|
|
853
|
+
// Skip both archived (done) and parked (blocked/partial/paused/awaiting/
|
|
854
|
+
// queued-after) children: the runlist advances to the first pickup-able one,
|
|
855
|
+
// so the picked target is guaranteed in a `dotmd use`-able status.
|
|
856
|
+
const target = children.find(c => !c.missing && !archiveStatuses.has(c.status) && isPickupable(c.status));
|
|
822
857
|
if (!target) {
|
|
823
858
|
if (children.length === 0) die(`Hub ${hubRepoPath} has empty \`runlist:\` — nothing to pick up.`);
|
|
824
|
-
|
|
859
|
+
// Live (non-archived, non-missing) children that exist but aren't startable.
|
|
860
|
+
const parked = children.filter(c => !c.missing && !archiveStatuses.has(c.status));
|
|
861
|
+
if (parked.length > 0) {
|
|
862
|
+
// Every remaining child is parked — surface them with statuses + the
|
|
863
|
+
// unstick verbs so the agent can resume one instead of being told a
|
|
864
|
+
// generic "no pickup" with no path forward.
|
|
865
|
+
const listed = parked.map(c => ` ${c.path} (status: ${c.status})`).join('\n');
|
|
866
|
+
die(
|
|
867
|
+
`No pickup-able child in runlist ${hubRepoPath} — every remaining child is parked:\n` +
|
|
868
|
+
`${listed}\n` +
|
|
869
|
+
`Unstick one before continuing:\n` +
|
|
870
|
+
` dotmd set active <child> # if ready to resume\n` +
|
|
871
|
+
` dotmd use <child> # to inspect`,
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
const allArchived = children.some(c => !c.missing) && children.every(c => c.missing || archiveStatuses.has(c.status));
|
|
825
875
|
if (allArchived) {
|
|
826
876
|
die(`All children in runlist ${hubRepoPath} are archived. Hub is ready for \`dotmd archive ${hubRepoPath}\`.`);
|
|
827
877
|
}
|
|
@@ -829,18 +879,6 @@ export async function runRunlist(argv, config, opts = {}) {
|
|
|
829
879
|
die(`No pickup-able child in runlist ${hubRepoPath}. Unresolved refs: ${missing.join(', ')}`);
|
|
830
880
|
}
|
|
831
881
|
|
|
832
|
-
// Pre-check status: pickup will die on non-pickup-able statuses, but with
|
|
833
|
-
// a generic message. Surface the runlist context first so the agent knows
|
|
834
|
-
// which list is blocked and on which item.
|
|
835
|
-
if (!PICKUPABLE_STATUSES.has(target.status)) {
|
|
836
|
-
die(
|
|
837
|
-
`Next child in runlist ${hubRepoPath} is ${target.path} (status: ${target.status}).\n` +
|
|
838
|
-
`Resolve the blocker before continuing the runlist.\n` +
|
|
839
|
-
` dotmd set active ${target.path} # if ready to resume\n` +
|
|
840
|
-
` dotmd use ${target.path} # to inspect`,
|
|
841
|
-
);
|
|
842
|
-
}
|
|
843
|
-
|
|
844
882
|
// Open the next child: set it in-session (frontmatter) and render its card.
|
|
845
883
|
// Dynamic import to avoid circular module-load cost when the runlist command
|
|
846
884
|
// isn't used.
|