dotmd-cli 0.39.3 → 0.39.4
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 +102 -3
- package/package.json +1 -1
- package/src/lifecycle.mjs +19 -1
- package/src/prompts.mjs +76 -2
package/bin/dotmd.mjs
CHANGED
|
@@ -68,6 +68,7 @@ Create & Export:
|
|
|
68
68
|
Setup:
|
|
69
69
|
init Create starter config + docs directory
|
|
70
70
|
statuses [list|add|set|remove|migrate] Manage per-project status taxonomy
|
|
71
|
+
help statuses Full status vocabulary + unstuck-actions + transitions
|
|
71
72
|
watch [command] Re-run a command on file changes
|
|
72
73
|
completions <shell> Shell completion script (bash, zsh)
|
|
73
74
|
journal [--tail N|--errors|--by-command|--session id|--since iso|--json]
|
|
@@ -92,6 +93,90 @@ Options:
|
|
|
92
93
|
|
|
93
94
|
Outputs the complete document index as JSON to stdout.`,
|
|
94
95
|
|
|
96
|
+
// Help topic accessed via \`dotmd help statuses\` (not a command — see dispatch
|
|
97
|
+
// below). Single-source-of-truth for the built-in status vocabulary across all
|
|
98
|
+
// three doc types. User-defined types/statuses live in config; introspect them
|
|
99
|
+
// with \`dotmd statuses list\`.
|
|
100
|
+
'help:statuses': `dotmd help statuses — status vocabulary, unstuck-actions, and transitions
|
|
101
|
+
|
|
102
|
+
Every document has a \`type:\` field; each type has its own valid statuses.
|
|
103
|
+
Status validation is type-aware (type > root > global). To inspect or edit
|
|
104
|
+
the status taxonomy in a specific project, use \`dotmd statuses list\`.
|
|
105
|
+
|
|
106
|
+
────────────────────────────────────────────────────────────────────
|
|
107
|
+
plan statuses (each maps to a distinct unstuck-action)
|
|
108
|
+
|
|
109
|
+
in-session A Claude session is working on it now.
|
|
110
|
+
Don't pick up unless you own it (auto-reattaches) or pass
|
|
111
|
+
--takeover. Stale lease cleanup: \`dotmd release --stale\`.
|
|
112
|
+
|
|
113
|
+
active Ready to be picked up.
|
|
114
|
+
\`dotmd pickup <file>\` → in-session.
|
|
115
|
+
|
|
116
|
+
planned Queued for future work, not yet ready to execute.
|
|
117
|
+
Transition to active when ready to start.
|
|
118
|
+
|
|
119
|
+
blocked External arrival wait — monitor.
|
|
120
|
+
Hardware, vendor delivery, third-party rollout. Quiet
|
|
121
|
+
(skipStale) — you can't speed it up by nagging.
|
|
122
|
+
|
|
123
|
+
partial Shipped + deferred tail — spawn successor plans.
|
|
124
|
+
Plan body should reference the successor plan(s). Quiet.
|
|
125
|
+
|
|
126
|
+
paused Started but stopped mid-work — re-evaluate to resume.
|
|
127
|
+
Short stale window (3 days) so resume-decisions don't decay.
|
|
128
|
+
|
|
129
|
+
awaiting Needs human input/decision — chase the answer.
|
|
130
|
+
NOT quiet — generates stale pressure so pings aren't forgotten.
|
|
131
|
+
|
|
132
|
+
queued-after Sequenced behind another plan — check predecessor.
|
|
133
|
+
Quiet. Can start once the predecessor ships.
|
|
134
|
+
|
|
135
|
+
archived No longer relevant; auto-moved to archive directory.
|
|
136
|
+
|
|
137
|
+
Canonical transitions:
|
|
138
|
+
active → in-session \`dotmd pickup <file>\`
|
|
139
|
+
in-session → active \`dotmd release <file>\`
|
|
140
|
+
in-session → partial \`dotmd status <file> partial\` (+ release)
|
|
141
|
+
in-session → awaiting \`dotmd status <file> awaiting\` (+ release)
|
|
142
|
+
any → archived \`dotmd archive <file>\`
|
|
143
|
+
|
|
144
|
+
────────────────────────────────────────────────────────────────────
|
|
145
|
+
doc statuses
|
|
146
|
+
|
|
147
|
+
draft Work-in-progress reference doc.
|
|
148
|
+
active Living document, kept up-to-date.
|
|
149
|
+
review Awaiting peer review.
|
|
150
|
+
reference Stable canonical reference (excluded from stale checks).
|
|
151
|
+
deprecated Superseded but kept for history.
|
|
152
|
+
archived No longer relevant; moved to archive directory.
|
|
153
|
+
|
|
154
|
+
────────────────────────────────────────────────────────────────────
|
|
155
|
+
prompt statuses
|
|
156
|
+
|
|
157
|
+
pending Ready for the next session to consume.
|
|
158
|
+
\`dotmd prompts use <file>\` prints body + archives atomically.
|
|
159
|
+
\`dotmd prompts next\` does the same for the oldest pending.
|
|
160
|
+
|
|
161
|
+
shelved Saved but hidden from \`hud\` / \`briefing\` / \`prompts next\`.
|
|
162
|
+
Still listed by \`dotmd prompts list\`.
|
|
163
|
+
\`dotmd prompts unshelve <file>\` → pending.
|
|
164
|
+
|
|
165
|
+
claimed Legacy intermediate state (atomic use → archived now).
|
|
166
|
+
|
|
167
|
+
archived Consumed prompt; body preserved in archive directory.
|
|
168
|
+
|
|
169
|
+
────────────────────────────────────────────────────────────────────
|
|
170
|
+
Related commands:
|
|
171
|
+
dotmd statuses Inspect/manage per-project status taxonomy
|
|
172
|
+
dotmd status <f> <new> Transition a document's status
|
|
173
|
+
dotmd briefing See plans grouped by status
|
|
174
|
+
dotmd plans --status <s> Filter live plans by status
|
|
175
|
+
dotmd hud Two-line actionable triage (held / prompts / stuck)
|
|
176
|
+
|
|
177
|
+
Run \`dotmd statuses list --type plan\` to see the full set (including any
|
|
178
|
+
project-specific custom statuses) with their flags.`,
|
|
179
|
+
|
|
95
180
|
completions: `dotmd completions <bash|zsh> — output shell completion script
|
|
96
181
|
|
|
97
182
|
Add to your shell config:
|
|
@@ -241,6 +326,9 @@ Default plan statuses (each maps to a distinct unstuck-action):
|
|
|
241
326
|
queued-after Sequenced behind another plan — check predecessor
|
|
242
327
|
archived No longer relevant; auto-moved to archive directory
|
|
243
328
|
|
|
329
|
+
Run \`dotmd help statuses\` for the full vocabulary across all doc types
|
|
330
|
+
(plan, doc, prompt) plus canonical transitions and related commands.
|
|
331
|
+
|
|
244
332
|
Use --dry-run (-n) to preview changes without writing anything.`,
|
|
245
333
|
|
|
246
334
|
check: `dotmd check — validate frontmatter and references
|
|
@@ -630,8 +718,8 @@ sorted by status. Supports all query flags (--status, --module, --json,
|
|
|
630
718
|
--sort, --group, etc.).
|
|
631
719
|
|
|
632
720
|
Default plan statuses: in-session, active, planned, blocked, partial,
|
|
633
|
-
paused, awaiting, queued-after, archived. Run \`dotmd
|
|
634
|
-
the unstuck-action behind each one.
|
|
721
|
+
paused, awaiting, queued-after, archived. Run \`dotmd help statuses\` for
|
|
722
|
+
the unstuck-action behind each one and canonical transitions.
|
|
635
723
|
|
|
636
724
|
Examples:
|
|
637
725
|
dotmd plans # live plans (default)
|
|
@@ -671,6 +759,9 @@ Default prompt statuses: pending, shelved, claimed, archived.
|
|
|
671
759
|
|
|
672
760
|
Examples:
|
|
673
761
|
dotmd prompts # pending prompts (default)
|
|
762
|
+
dotmd prompts list --verbose # one row per prompt + target plan ref
|
|
763
|
+
# (from related_plans, parent_plan,
|
|
764
|
+
# or the first body .md link)
|
|
674
765
|
dotmd prompts list --include-archived # all prompts including archived
|
|
675
766
|
dotmd prompts list --status claimed # already-consumed prompts
|
|
676
767
|
dotmd prompts --json # JSON output
|
|
@@ -825,6 +916,14 @@ async function main() {
|
|
|
825
916
|
}
|
|
826
917
|
|
|
827
918
|
if (command === 'help' || command === '--help' || command === '-h') {
|
|
919
|
+
const topic = args[1];
|
|
920
|
+
if (topic) {
|
|
921
|
+
const key = `help:${topic}`;
|
|
922
|
+
if (HELP[key]) { process.stdout.write(`${HELP[key]}\n`); return; }
|
|
923
|
+
if (HELP[topic]) { process.stdout.write(`${HELP[topic]}\n`); return; }
|
|
924
|
+
process.stderr.write(`Unknown help topic: ${topic}\n\nAvailable topics: statuses\nPer-command help: dotmd <cmd> --help\n`);
|
|
925
|
+
process.exit(1);
|
|
926
|
+
}
|
|
828
927
|
process.stdout.write(`${HELP._main}\n`);
|
|
829
928
|
return;
|
|
830
929
|
}
|
|
@@ -927,7 +1026,7 @@ async function main() {
|
|
|
927
1026
|
}
|
|
928
1027
|
if (command === 'prompts') {
|
|
929
1028
|
const { runPrompts } = await import('../src/prompts.mjs');
|
|
930
|
-
await runPrompts(restArgs, config, { dryRun });
|
|
1029
|
+
await runPrompts(restArgs, config, { dryRun, verbose });
|
|
931
1030
|
return;
|
|
932
1031
|
}
|
|
933
1032
|
|
package/package.json
CHANGED
package/src/lifecycle.mjs
CHANGED
|
@@ -246,7 +246,15 @@ export async function runPickup(argv, config, opts = {}) {
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
const pickupable = new Set(['active', 'planned', 'in-session']);
|
|
249
|
-
if (oldStatus && !pickupable.has(oldStatus))
|
|
249
|
+
if (oldStatus && !pickupable.has(oldStatus)) {
|
|
250
|
+
die(
|
|
251
|
+
`Cannot pick up a plan with status '${oldStatus}'. Must be active or planned.\n` +
|
|
252
|
+
` ${repoPath}\n` +
|
|
253
|
+
`\n` +
|
|
254
|
+
`Recover with:\n` +
|
|
255
|
+
` dotmd status ${repoPath} active && dotmd pickup ${repoPath}`,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
250
258
|
|
|
251
259
|
const today = nowIso();
|
|
252
260
|
const leaseOldStatus = oldStatus === 'in-session' ? 'active' : (oldStatus ?? 'active');
|
|
@@ -572,6 +580,16 @@ export function runArchive(argv, config, opts = {}) {
|
|
|
572
580
|
if (refCount > 0) {
|
|
573
581
|
out.write(`${prefix} Would update references in ${refCount} file(s)\n`);
|
|
574
582
|
}
|
|
583
|
+
|
|
584
|
+
// Preview lease release (only if a lease exists for this plan)
|
|
585
|
+
if (readLeases(config)[oldRepoPath]) {
|
|
586
|
+
out.write(`${prefix} Would release in-session lease: ${oldRepoPath}\n`);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Preview onArchive hook fire
|
|
590
|
+
if (config.hooks?.onArchive) {
|
|
591
|
+
out.write(`${prefix} Would fire hook: onArchive\n`);
|
|
592
|
+
}
|
|
575
593
|
return;
|
|
576
594
|
}
|
|
577
595
|
|
package/src/prompts.mjs
CHANGED
|
@@ -29,12 +29,17 @@ export async function runPrompts(argv, config, opts = {}) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
function runPromptsList(argv, config) {
|
|
32
|
+
function runPromptsList(argv, config, opts = {}) {
|
|
33
33
|
const index = buildIndex(config);
|
|
34
34
|
const hasStatusFlag = argv.includes('--status');
|
|
35
35
|
const includeArchived = argv.includes('--include-archived');
|
|
36
36
|
const sub = argv[0];
|
|
37
37
|
|
|
38
|
+
if (opts.verbose && !argv.includes('--json')) {
|
|
39
|
+
renderPromptsVerbose(index, config, { hasStatusFlag, includeArchived });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
38
43
|
let defaults;
|
|
39
44
|
let extras = argv;
|
|
40
45
|
if (sub === 'status') {
|
|
@@ -48,6 +53,67 @@ function runPromptsList(argv, config) {
|
|
|
48
53
|
runQuery(index, [...defaults, ...extras], config, { preset: 'prompts' });
|
|
49
54
|
}
|
|
50
55
|
|
|
56
|
+
// Resolve a prompt's "target plan" for `prompts list --verbose`. Order:
|
|
57
|
+
// 1. frontmatter `related_plans:` (first entry — assumed plan slug)
|
|
58
|
+
// 2. frontmatter `parent_plan:`
|
|
59
|
+
// 3. first body markdown link to a .md file
|
|
60
|
+
// Returns a repo-relative display path or null.
|
|
61
|
+
function findPromptTarget(promptDoc, config) {
|
|
62
|
+
const refs = promptDoc.refFields ?? {};
|
|
63
|
+
const fmTargets = [...(refs.related_plans ?? []), ...(refs.parent_plan ?? [])];
|
|
64
|
+
for (const t of fmTargets) {
|
|
65
|
+
if (typeof t === 'string' && t.trim()) return slugToPlanPath(t.trim(), config);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const links = promptDoc.bodyLinks ?? [];
|
|
69
|
+
const mdLink = links.find(l => /\.md(?:#|$)/.test(l.href ?? ''));
|
|
70
|
+
if (mdLink) return resolveBodyLink(mdLink.href, promptDoc.path);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Plan slugs in frontmatter (e.g. `related_plans: [foo-bar]`) resolve to
|
|
75
|
+
// <docs-root>/plans/<slug>.md.
|
|
76
|
+
function slugToPlanPath(s, config) {
|
|
77
|
+
const cleaned = s.replace(/#.*$/, '').replace(/^\.\//, '');
|
|
78
|
+
if (cleaned.includes('/') || cleaned.endsWith('.md')) return cleaned;
|
|
79
|
+
return `${config.docsRootPrefix || 'docs/'}plans/${cleaned}.md`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Resolve a markdown body link relative to the prompt's location so e.g.
|
|
83
|
+
// `../plans/foo.md` from docs/prompts/x.md → docs/plans/foo.md.
|
|
84
|
+
function resolveBodyLink(link, promptRepoPath) {
|
|
85
|
+
const cleaned = link.replace(/#.*$/, '');
|
|
86
|
+
if (cleaned.startsWith('/')) return cleaned.replace(/^\/+/, '');
|
|
87
|
+
const promptDir = path.dirname(promptRepoPath);
|
|
88
|
+
return path.normalize(path.join(promptDir, cleaned));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function renderPromptsVerbose(index, config, { hasStatusFlag, includeArchived }) {
|
|
92
|
+
let prompts = index.docs.filter(d => d.type === 'prompt');
|
|
93
|
+
if (!hasStatusFlag && !includeArchived) {
|
|
94
|
+
prompts = prompts.filter(d => d.status !== 'archived');
|
|
95
|
+
}
|
|
96
|
+
if (prompts.length === 0) {
|
|
97
|
+
process.stdout.write('No prompts.\n');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
prompts.sort((a, b) => (b.updated ?? '').localeCompare(a.updated ?? ''));
|
|
102
|
+
|
|
103
|
+
const counts = {};
|
|
104
|
+
for (const p of prompts) counts[p.status ?? 'unknown'] = (counts[p.status ?? 'unknown'] ?? 0) + 1;
|
|
105
|
+
const summary = Object.entries(counts).map(([s, n]) => `${n} ${s}`).join(' · ');
|
|
106
|
+
process.stdout.write(`${prompts.length} prompt${prompts.length === 1 ? '' : 's'} · ${summary}\n\n`);
|
|
107
|
+
|
|
108
|
+
for (const p of prompts) {
|
|
109
|
+
const slug = path.basename(p.path, '.md');
|
|
110
|
+
const target = findPromptTarget(p, config);
|
|
111
|
+
const status = (p.status ?? 'unknown').toUpperCase();
|
|
112
|
+
const arrow = target ? ` ${dim('→')} ${target}` : ` ${dim('→ (no target plan)')}`;
|
|
113
|
+
process.stdout.write(` ${green(slug)} [${status}]\n${arrow}\n`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
51
117
|
function pendingPromptsOldestFirst(config) {
|
|
52
118
|
const index = buildIndex(config);
|
|
53
119
|
const prompts = index.docs.filter(d => d.type === 'prompt' && d.status === 'pending');
|
|
@@ -139,7 +205,15 @@ function consumePrompt(filePath, config, opts) {
|
|
|
139
205
|
}
|
|
140
206
|
|
|
141
207
|
if (dryRun) {
|
|
142
|
-
|
|
208
|
+
const prefix = dim('[dry-run]');
|
|
209
|
+
process.stderr.write(`${prefix} Would emit body and archive: ${repoPath} (${status ?? 'unknown'} → archived)\n`);
|
|
210
|
+
const bytes = Buffer.byteLength(body, 'utf8');
|
|
211
|
+
const lines = body.split('\n').length;
|
|
212
|
+
process.stderr.write(`${prefix} body preview (${bytes}B, ${lines} lines):\n`);
|
|
213
|
+
process.stderr.write(`${dim('---8<---')}\n`);
|
|
214
|
+
process.stderr.write(body);
|
|
215
|
+
if (!body.endsWith('\n')) process.stderr.write('\n');
|
|
216
|
+
process.stderr.write(`${dim('--->8---')}\n`);
|
|
143
217
|
runArchive([filePath], config, { dryRun: true, noIndex, out: process.stderr });
|
|
144
218
|
return;
|
|
145
219
|
}
|