dotmd-cli 0.50.2 → 0.52.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 +22 -80
- package/bin/dotmd.mjs +143 -126
- package/package.json +1 -1
- package/src/claude-commands.mjs +5 -28
- package/src/commands.mjs +2 -1
- package/src/completions.mjs +1 -5
- package/src/doctor.mjs +1 -1
- package/src/git.mjs +19 -0
- package/src/guard.mjs +202 -0
- package/src/hints.mjs +1 -6
- package/src/hud.mjs +24 -25
- package/src/journal.mjs +58 -1
- package/src/lifecycle.mjs +18 -275
- package/src/misuse-read.mjs +62 -0
- package/src/new.mjs +16 -0
- package/src/rename.mjs +0 -3
- package/src/render.mjs +0 -8
- package/src/runlist.mjs +12 -13
- package/src/ship.mjs +1 -13
- package/src/use.mjs +4 -4
- package/src/util.mjs +10 -0
- package/src/validate.mjs +0 -26
- package/src/lease-scrub.mjs +0 -49
- package/src/lease.mjs +0 -232
package/README.md
CHANGED
|
@@ -187,9 +187,9 @@ dotmd stale List stale docs
|
|
|
187
187
|
dotmd actionable List docs with next steps
|
|
188
188
|
dotmd index [--print] Generate/update docs.md index block
|
|
189
189
|
dotmd hud Actionable triage (silent when clean — ideal SessionStart hook)
|
|
190
|
-
dotmd
|
|
191
|
-
dotmd
|
|
192
|
-
dotmd status <file> <status> Transition document status
|
|
190
|
+
dotmd use <file> Open a plan (in-session + print it) or consume a prompt
|
|
191
|
+
dotmd set <status> <file> Change a document's status (frontmatter write)
|
|
192
|
+
dotmd status <file> <status> Transition document status (deprecated; prefer set)
|
|
193
193
|
dotmd archive <file> Archive (status + move + update refs)
|
|
194
194
|
dotmd bulk archive <files> Archive multiple files at once
|
|
195
195
|
dotmd touch <file> Bump updated date
|
|
@@ -311,7 +311,7 @@ dotmd prompts archive <file> # archive without printing the body
|
|
|
311
311
|
dotmd prompts new <name> [body] # alias for `dotmd new prompt`
|
|
312
312
|
```
|
|
313
313
|
|
|
314
|
-
`dotmd hud` surfaces pending prompts on session start
|
|
314
|
+
`dotmd hud` surfaces pending prompts on session start, so a saved prompt acts as a self-addressed reminder: write it now, the next session sees it. Held prompts are kept out of the SessionStart surface — use them for "saved but not next."
|
|
315
315
|
|
|
316
316
|
Statuses: `pending` (drafted, awaiting a session), `held` (saved but parked under `prompts/held/` — visible in `prompts list`, hidden from `hud`/`briefing`, skipped by `prompts next`), `archived` (consumed or filed away). `shelved` is a legacy spelling accepted for older files; `claimed` is reserved for a future "in-flight" state but is currently a synonym for archived in practice.
|
|
317
317
|
|
|
@@ -575,60 +575,26 @@ dotmd bulk archive docs/old-a.md docs/old-b.md # archive multiple
|
|
|
575
575
|
dotmd bulk archive docs/old-*.md -n # preview
|
|
576
576
|
```
|
|
577
577
|
|
|
578
|
-
###
|
|
578
|
+
### Open & Closeout
|
|
579
579
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
dotmd archive docs/plans/my-plan.md --closeout-template # also inject ## Closeout skeleton
|
|
584
|
-
dotmd release docs/plans/my-plan.md # need more work: release lease, flip to prior status
|
|
585
|
-
dotmd status docs/plans/my-plan.md partial # shipped + tail deferred (reference successors in body)
|
|
586
|
-
dotmd status docs/plans/my-plan.md awaiting # stuck on a human decision
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
`finish` is an alias for `release`, kept for older agent instructions that use
|
|
590
|
-
that verb for closeout. To fully close shipped work, archive it. To keep working
|
|
591
|
-
later, release it back to the prior status or use `dotmd set <status> <file>`.
|
|
592
|
-
|
|
593
|
-
### Session leases & release
|
|
594
|
-
|
|
595
|
-
`dotmd pickup` records a lease at `<repoRoot>/.dotmd/in-session.json` that
|
|
596
|
-
identifies which Claude session owns the plan. The lease enables three
|
|
597
|
-
distinct outcomes when a plan is already `in-session`:
|
|
598
|
-
|
|
599
|
-
- **Same session re-attach.** A fresh `dotmd pickup` of a plan you already
|
|
600
|
-
hold (e.g., after `/clear` or auto-compaction) silently re-attaches and
|
|
601
|
-
re-prints the body. No conflict.
|
|
602
|
-
- **Cross-session conflict.** If another live session holds the plan,
|
|
603
|
-
pickup refuses with `Held by <host>/<session> (pid <pid>) since <time>`.
|
|
604
|
-
- **Reclaimable lease.** If the holder's same-host pid is dead, or the lease is
|
|
605
|
-
older than 4 hours, pickup can reclaim it without `--takeover`.
|
|
606
|
-
|
|
607
|
-
Releasing leases (both names work; `release` is the recommended verb):
|
|
580
|
+
Status is just frontmatter. There's no checkout, lock, or lease — opening a
|
|
581
|
+
plan, transitioning it, and closing it are all plain status writes (archive
|
|
582
|
+
also moves the file).
|
|
608
583
|
|
|
609
584
|
```bash
|
|
610
|
-
dotmd
|
|
611
|
-
dotmd
|
|
612
|
-
dotmd
|
|
613
|
-
dotmd
|
|
614
|
-
dotmd
|
|
615
|
-
dotmd
|
|
585
|
+
dotmd use docs/plans/my-plan.md # mark in-session + print the plan card
|
|
586
|
+
dotmd set in-session docs/plans/my-plan.md # set the status without printing
|
|
587
|
+
dotmd set active docs/plans/my-plan.md # need more work: flip back to active
|
|
588
|
+
dotmd set partial docs/plans/my-plan.md # shipped + tail deferred (reference successors in body)
|
|
589
|
+
dotmd set awaiting docs/plans/my-plan.md # stuck on a human decision
|
|
590
|
+
dotmd archive docs/plans/my-plan.md # fully shipped: archive + move + update refs
|
|
591
|
+
dotmd archive docs/plans/my-plan.md --closeout-template # also inject ## Closeout skeleton
|
|
616
592
|
```
|
|
617
593
|
|
|
618
|
-
`
|
|
619
|
-
|
|
594
|
+
`in-session` is a status like any other — `dotmd set <status> <file>` writes it
|
|
595
|
+
to the file's frontmatter and does nothing else.
|
|
620
596
|
|
|
621
|
-
**
|
|
622
|
-
|
|
623
|
-
1. `$CLAUDE_CODE_SESSION_ID` (set by Claude Code in Bash subprocess env)
|
|
624
|
-
2. `$CLAUDE_SESSION_ID` (legacy alias)
|
|
625
|
-
3. `$TERM_SESSION_ID` (macOS Terminal/iTerm — stable per window)
|
|
626
|
-
4. `shell:<user>@<host>` (last-resort coarse fallback)
|
|
627
|
-
|
|
628
|
-
The session id survives `/clear` and auto-compaction, so a re-attach after
|
|
629
|
-
either is silent.
|
|
630
|
-
|
|
631
|
-
**Recommended Claude Code hooks** — add both to `~/.claude/settings.json`
|
|
597
|
+
**Recommended Claude Code hook** — add to `~/.claude/settings.json`
|
|
632
598
|
(or your project's `.claude/settings.json`):
|
|
633
599
|
|
|
634
600
|
```json
|
|
@@ -640,44 +606,20 @@ either is silent.
|
|
|
640
606
|
{ "type": "command", "command": "dotmd hud", "timeout": 5 }
|
|
641
607
|
]
|
|
642
608
|
}
|
|
643
|
-
],
|
|
644
|
-
"SessionEnd": [
|
|
645
|
-
{
|
|
646
|
-
"hooks": [
|
|
647
|
-
{ "type": "command", "command": "dotmd release", "timeout": 10 }
|
|
648
|
-
]
|
|
649
|
-
}
|
|
650
609
|
]
|
|
651
610
|
}
|
|
652
611
|
}
|
|
653
612
|
```
|
|
654
613
|
|
|
655
|
-
- **SessionStart** runs `dotmd hud`, which prints
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
on large repos. `hud` is the zero-pollution surface.
|
|
660
|
-
- **SessionEnd** runs `dotmd release` (the new name for `dotmd unpickup`;
|
|
661
|
-
both still work), which releases every lease owned by the ending session
|
|
662
|
-
and flips plans back to their prior status.
|
|
614
|
+
- **SessionStart** runs `dotmd hud`, which prints the command primer and stays
|
|
615
|
+
silent when nothing is queued. Use this instead of `dotmd briefing` for the
|
|
616
|
+
hook role — `briefing` dumps per-plan next_step prose that can run to many
|
|
617
|
+
kilobytes on large repos. `hud` is the zero-pollution surface.
|
|
663
618
|
|
|
664
619
|
> The double-`hooks` nesting is correct: `hooks.<Event>[*].hooks[*]` is the
|
|
665
620
|
> schema Claude Code requires. `Bash(dotmd:*)` should be in your
|
|
666
621
|
> `permissions.allow` list as well, otherwise the hooks will be blocked.
|
|
667
622
|
|
|
668
|
-
`dotmd hud` (and `dotmd briefing` for the verbose case) surface a
|
|
669
|
-
`⚠ N stuck leases` line when stale leases exist, with a
|
|
670
|
-
`dotmd release --stale` suggestion.
|
|
671
|
-
|
|
672
|
-
`dotmd check` also catches the symmetric failure mode: a plan whose
|
|
673
|
-
frontmatter claims `status: in-session` but whose lease either doesn't
|
|
674
|
-
exist (last session crashed before releasing) or is stale (>4h since
|
|
675
|
-
pickup). Each warning names the exact unstuck command
|
|
676
|
-
(`dotmd release <plan>` or `dotmd status <plan> active`), so plans
|
|
677
|
-
don't sit stuck in-session indefinitely. Always-on — legit concurrent
|
|
678
|
-
sessions hold real leases, so the warning only fires on actual
|
|
679
|
-
divergence.
|
|
680
|
-
|
|
681
623
|
### Touch
|
|
682
624
|
|
|
683
625
|
```bash
|
package/bin/dotmd.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { resolveConfig } from '../src/config.mjs';
|
|
7
|
-
import { die, warn, levenshtein, isArchivedPath } from '../src/util.mjs';
|
|
7
|
+
import { die, warn, levenshtein, isArchivedPath, toRepoPath } from '../src/util.mjs';
|
|
8
8
|
import { recordCliInvocation, recordGlobalError } from '../src/journal.mjs';
|
|
9
9
|
import { findRepeatFailureHint } from '../src/hints.mjs';
|
|
10
10
|
|
|
@@ -34,13 +34,12 @@ const FLAG_SPECS = {
|
|
|
34
34
|
briefing: { flags: new Set(['--json']), values: new Set() },
|
|
35
35
|
context: { flags: new Set(['--json', '--compact', '--summarize', '--model']), values: new Set(['--model']) },
|
|
36
36
|
'agent-context': { flags: new Set(['--json']), values: new Set() },
|
|
37
|
-
hud: { flags: new Set(['--json']), values: new Set() },
|
|
37
|
+
hud: { flags: new Set(['--json', '--subagent']), values: new Set() },
|
|
38
|
+
guard: { flags: new Set(), values: new Set() },
|
|
39
|
+
misuse: { flags: new Set(['--json', '--tail', '--by-rule', '--repo']), values: new Set(['--tail', '--repo']) },
|
|
38
40
|
check: { flags: new Set(['--fix', '--errors-only', '--no-collapse', '--json', '--verbose']), values: new Set() },
|
|
39
41
|
doctor: { flags: new Set(['--apply', '--yes', '--dry-run', '-n', '--statuses', '--migrate-template', '--migrate-prompts', '--frontmatter-fix', '--project', '--json', '--include-archived']), values: new Set() },
|
|
40
|
-
runlist: { flags: new Set(['--json', '--
|
|
41
|
-
release: { flags: new Set(['--json', '--all', '--stale', '--to', '--force', '--no-index', '--show-files']), values: new Set(['--to']) },
|
|
42
|
-
unpickup: { flags: new Set(['--json', '--all', '--stale', '--to', '--force', '--no-index', '--show-files']), values: new Set(['--to']) },
|
|
43
|
-
finish: { flags: new Set(['--json', '--all', '--stale', '--to', '--force', '--no-index', '--show-files']), values: new Set(['--to']) },
|
|
42
|
+
runlist: { flags: new Set(['--json', '--full', '--no-index', '--show-files']), values: new Set(), subcommands: new Set(['next']) },
|
|
44
43
|
prompts: {
|
|
45
44
|
flags: new Set(['--json', '--status', '--include-archived', '--sort', '--limit', '--all', '--no-index', '--show-files', '--body', '--message', '--title']),
|
|
46
45
|
values: new Set(['--status', '--sort', '--limit', '--body', '--message', '--title']),
|
|
@@ -60,6 +59,67 @@ function validateKnownFlags(command, argv, config) {
|
|
|
60
59
|
}
|
|
61
60
|
}
|
|
62
61
|
|
|
62
|
+
function resolveExistingPath(input, config) {
|
|
63
|
+
if (!input) return null;
|
|
64
|
+
const candidates = [];
|
|
65
|
+
if (path.isAbsolute(input)) {
|
|
66
|
+
candidates.push(input);
|
|
67
|
+
if (!input.endsWith('.md')) candidates.push(`${input}.md`);
|
|
68
|
+
} else {
|
|
69
|
+
candidates.push(path.resolve(config.repoRoot, input));
|
|
70
|
+
if (!input.endsWith('.md')) candidates.push(path.resolve(config.repoRoot, `${input}.md`));
|
|
71
|
+
for (const root of config.docsRoots || [config.docsRoot]) {
|
|
72
|
+
candidates.push(path.resolve(root, input));
|
|
73
|
+
if (!input.endsWith('.md')) candidates.push(path.resolve(root, `${input}.md`));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return candidates.find(candidate => existsSync(candidate)) ?? null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function applyPathScopeToIndex(index, config, inputs) {
|
|
80
|
+
if (!inputs.length) return;
|
|
81
|
+
|
|
82
|
+
const selected = new Set();
|
|
83
|
+
for (const input of inputs) {
|
|
84
|
+
const resolved = resolveExistingPath(input, config);
|
|
85
|
+
if (!resolved) die(`Could not resolve check path: ${input}`);
|
|
86
|
+
|
|
87
|
+
const stat = statSync(resolved);
|
|
88
|
+
if (stat.isDirectory()) {
|
|
89
|
+
const dir = path.resolve(resolved);
|
|
90
|
+
const before = selected.size;
|
|
91
|
+
for (const doc of index.docs) {
|
|
92
|
+
const abs = path.resolve(config.repoRoot, doc.path);
|
|
93
|
+
if (abs === dir || abs.startsWith(dir + path.sep)) selected.add(doc.path);
|
|
94
|
+
}
|
|
95
|
+
if (selected.size === before) {
|
|
96
|
+
die(`No dotmd documents found under check path: ${toRepoPath(dir, config.repoRoot)}`);
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!stat.isFile() || !resolved.endsWith('.md')) die(`Check path is not a markdown file or directory: ${input}`);
|
|
102
|
+
const repoPath = toRepoPath(resolved, config.repoRoot);
|
|
103
|
+
if (!index.docs.some(d => d.path === repoPath)) {
|
|
104
|
+
die(`Check path is outside configured docs roots: ${repoPath}`);
|
|
105
|
+
}
|
|
106
|
+
selected.add(repoPath);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
index.docs = index.docs.filter(d => selected.has(d.path));
|
|
110
|
+
index.errors = index.errors.filter(e => selected.has(e.path));
|
|
111
|
+
index.warnings = index.warnings.filter(w => selected.has(w.path));
|
|
112
|
+
index.countsByStatus = {};
|
|
113
|
+
index.countsByType = {};
|
|
114
|
+
for (const doc of index.docs) {
|
|
115
|
+
const status = doc.status ?? 'unknown';
|
|
116
|
+
index.countsByStatus[status] = (index.countsByStatus[status] ?? 0) + 1;
|
|
117
|
+
const type = doc.type || 'unknown';
|
|
118
|
+
if (!index.countsByType[type]) index.countsByType[type] = {};
|
|
119
|
+
index.countsByType[type][status] = (index.countsByType[type][status] ?? 0) + 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
63
123
|
const HELP = {
|
|
64
124
|
_main: `dotmd v${pkg.version} — frontmatter markdown document manager
|
|
65
125
|
|
|
@@ -81,13 +141,39 @@ More help:
|
|
|
81
141
|
|
|
82
142
|
Global flags: --config <path> --root <name> --type <t,…> --dry-run/-n --verbose --version`,
|
|
83
143
|
|
|
144
|
+
guard: `dotmd guard — PreToolUse hook handler (reads the tool-call JSON on stdin)
|
|
145
|
+
|
|
146
|
+
Wire it into Claude Code as a PreToolUse hook to intercept the wrong-moves
|
|
147
|
+
sessions keep making, and to log every one for audit:
|
|
148
|
+
|
|
149
|
+
{"matcher":"Bash|Read|Edit|Write","hooks":[{"type":"command","command":"dotmd guard"}]}
|
|
150
|
+
|
|
151
|
+
Rules:
|
|
152
|
+
commit-prompt deny git add/commit of a (often gitignored) saved prompt
|
|
153
|
+
cat-prompt warn cat/less/head of a docs/prompts/*.md (use \`dotmd use\`)
|
|
154
|
+
read-prompt warn Read tool on a saved prompt (use \`dotmd use\`)
|
|
155
|
+
edit-status warn hand-edit of a \`status:\` field (use \`dotmd set\`)
|
|
156
|
+
|
|
157
|
+
Every catch is appended to the cross-repo misuse log. Disable with DOTMD_GUARD=0.
|
|
158
|
+
Read the log with \`dotmd misuse\`.`,
|
|
159
|
+
|
|
160
|
+
misuse: `dotmd misuse — read the cross-repo guard log (~/.claude/logs/dotmd-misuse.log)
|
|
161
|
+
|
|
162
|
+
dotmd misuse last 20 intercepted wrong-moves
|
|
163
|
+
dotmd misuse --tail 50 last N
|
|
164
|
+
dotmd misuse --by-rule counts per rule (deny/warn split)
|
|
165
|
+
dotmd misuse --repo <name> filter by repo
|
|
166
|
+
dotmd misuse --json machine-readable
|
|
167
|
+
|
|
168
|
+
Populated by the \`dotmd guard\` PreToolUse hook — see \`dotmd help guard\`.`,
|
|
169
|
+
|
|
84
170
|
// Full command list — opt-in via \`dotmd help all\`. Kept exhaustive so the
|
|
85
171
|
// top-level \`--help\` can stay terse without losing discoverability. When you
|
|
86
172
|
// add a new command, add it here too.
|
|
87
173
|
'help:all': `dotmd v${pkg.version} — full command list
|
|
88
174
|
|
|
89
175
|
View & Query:
|
|
90
|
-
hud [--json]
|
|
176
|
+
hud [--json] Command primer + pending-prompt triage — silent when clean
|
|
91
177
|
list [--verbose] [--json] List docs grouped by status (default command)
|
|
92
178
|
briefing [--json] Full briefing with plan status counts + next steps
|
|
93
179
|
context [--summarize] [--json] Full briefing (LLM-oriented; use --json --compact for bounded JSON)
|
|
@@ -121,11 +207,9 @@ Validate & Fix:
|
|
|
121
207
|
fix-refs [--dry-run] Auto-fix broken reference paths + body links
|
|
122
208
|
|
|
123
209
|
Lifecycle:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
set <status> [<file>] Unified transition: start work, change status, close out, archive — all via target status
|
|
210
|
+
use <file> Open a plan (mark in-session + print it) or consume a prompt
|
|
211
|
+
set <status> <file> Change a document's status (frontmatter write; archive also moves the file)
|
|
127
212
|
runlist <hub> [next] Show or walk an ordered group of plans (see \`dotmd help runlist\`)
|
|
128
|
-
unpickup [<file>] Release held in-session work
|
|
129
213
|
status <file> <status> Transition document status (deprecated; prefer \`set\`)
|
|
130
214
|
archive <file> Archive (status + move + update refs)
|
|
131
215
|
bulk archive <f1> <f2> ... Archive multiple files at once
|
|
@@ -184,11 +268,10 @@ the status taxonomy in a specific project, use \`dotmd statuses list\`.
|
|
|
184
268
|
plan statuses (each maps to a distinct unstuck-action)
|
|
185
269
|
|
|
186
270
|
in-session A Claude session is working on it now.
|
|
187
|
-
|
|
188
|
-
--takeover. Stale lease cleanup: \`dotmd release --stale\`.
|
|
271
|
+
\`dotmd use <file>\` marks it in-session and prints the plan.
|
|
189
272
|
|
|
190
|
-
active Ready to be
|
|
191
|
-
\`dotmd
|
|
273
|
+
active Ready to be worked on.
|
|
274
|
+
\`dotmd use <file>\` → in-session.
|
|
192
275
|
|
|
193
276
|
planned Queued for future work, not yet ready to execute.
|
|
194
277
|
Transition to active when ready to start.
|
|
@@ -212,10 +295,10 @@ plan statuses (each maps to a distinct unstuck-action)
|
|
|
212
295
|
archived No longer relevant; auto-moved to archive directory.
|
|
213
296
|
|
|
214
297
|
Canonical transitions:
|
|
215
|
-
active → in-session \`dotmd
|
|
216
|
-
in-session → active \`dotmd set active
|
|
217
|
-
in-session → partial \`dotmd set partial
|
|
218
|
-
in-session → awaiting \`dotmd set awaiting
|
|
298
|
+
active → in-session \`dotmd use <file>\` (or \`dotmd set in-session <file>\`)
|
|
299
|
+
in-session → active \`dotmd set active <file>\`
|
|
300
|
+
in-session → partial \`dotmd set partial <file>\`
|
|
301
|
+
in-session → awaiting \`dotmd set awaiting <file>\`
|
|
219
302
|
any → archived \`dotmd set archived <file>\` (or \`dotmd archive\`)
|
|
220
303
|
|
|
221
304
|
────────────────────────────────────────────────────────────────────
|
|
@@ -253,7 +336,7 @@ Related commands:
|
|
|
253
336
|
dotmd status <f> <new> Transition a document's status
|
|
254
337
|
dotmd briefing See plans grouped by status
|
|
255
338
|
dotmd plans --status <s> Filter live plans by status
|
|
256
|
-
dotmd hud
|
|
339
|
+
dotmd hud Command primer + pending-prompt triage
|
|
257
340
|
|
|
258
341
|
Run \`dotmd statuses list --type plan\` to see the full set (including any
|
|
259
342
|
project-specific custom statuses) with their flags.`,
|
|
@@ -324,76 +407,18 @@ Filters:
|
|
|
324
407
|
--summarize-limit <n> Max docs to summarize (default: 5)
|
|
325
408
|
--model <name> Model for AI summaries`,
|
|
326
409
|
|
|
327
|
-
pickup: `dotmd pickup <file> — pick up a plan and start working
|
|
328
|
-
|
|
329
|
-
Sets the plan to in-session and prints its content (prefixed with a
|
|
330
|
-
"[dotmd] holding <path>" line so the fresh session knows what it holds).
|
|
331
|
-
Writes a session lease to <repoRoot>/.dotmd/in-session.json so the same
|
|
332
|
-
Claude session can re-attach silently after compaction or /clear.
|
|
333
|
-
|
|
334
|
-
If a plan is already in-session:
|
|
335
|
-
- Same session → silent re-attach (prints body, no error).
|
|
336
|
-
- Different session, live pid → refuses with "Held by …" message.
|
|
337
|
-
- Different session, dead pid or >24h old → suggests --takeover.
|
|
338
|
-
|
|
339
|
-
Options:
|
|
340
|
-
--takeover Force-claim a plan held by another session
|
|
341
|
-
--no-index Skip index regen (see \`dotmd archive --help\`)
|
|
342
|
-
--show-files Append \`files: …\` line to stderr (see \`dotmd archive --help\`)
|
|
343
|
-
--json Output as JSON
|
|
344
|
-
--dry-run, -n Preview without writing
|
|
345
|
-
|
|
346
|
-
If no file is given, prompts with a list of active/planned plans.`,
|
|
347
|
-
|
|
348
|
-
unpickup: `dotmd unpickup [<file>] — release a plan from in-session
|
|
349
|
-
|
|
350
|
-
With no file: releases every lease owned by the current session.
|
|
351
|
-
This is the form intended for a Claude Code SessionEnd hook.
|
|
352
|
-
|
|
353
|
-
With <file>: releases that one. Refuses if held by another session
|
|
354
|
-
(use --force to override).
|
|
355
|
-
|
|
356
|
-
Flips the plan's frontmatter status from in-session back to its
|
|
357
|
-
prior status (recorded by pickup), or whatever --to specifies.
|
|
358
|
-
|
|
359
|
-
Options:
|
|
360
|
-
--to <status> Override target status (default: lease.oldStatus → fallback active)
|
|
361
|
-
--all Release every lease in the file (administrative)
|
|
362
|
-
--stale Release leases whose pid is dead or age >24h
|
|
363
|
-
--force Override "not yours" refusal on a specific file
|
|
364
|
-
--no-index Skip index regen (see \`dotmd archive --help\`)
|
|
365
|
-
--show-files Append \`files: …\` line to stderr (see \`dotmd archive --help\`)
|
|
366
|
-
--json Output as JSON ({ released, skipped })
|
|
367
|
-
--dry-run, -n Preview without writing
|
|
368
|
-
|
|
369
|
-
Manual-edit fallback: if the plan's status is in-session but no lease
|
|
370
|
-
exists, --to <status> flips it anyway with a warning.`,
|
|
371
|
-
|
|
372
|
-
release: `dotmd release [<file>] [--to <s>] — alias of dotmd unpickup
|
|
373
|
-
|
|
374
|
-
Release the in-session lease(s) and flip frontmatter back to the prior
|
|
375
|
-
status. With no file, releases every lease owned by the current session.
|
|
376
|
-
Identical behavior to \`dotmd unpickup\`; both names route to the same
|
|
377
|
-
implementation. See \`dotmd unpickup --help\` for full option list.`,
|
|
378
|
-
|
|
379
|
-
finish: `dotmd finish [<file>] [--to <s>] — alias of dotmd release
|
|
380
|
-
|
|
381
|
-
Compatibility alias for docs and agent loops that use "finish" for releasing
|
|
382
|
-
in-session work. Same behavior as \`dotmd release\` / \`dotmd unpickup\`.`,
|
|
383
|
-
|
|
384
410
|
ship: `dotmd ship [patch|minor|major] — regen + commit + bump in one step
|
|
385
411
|
|
|
386
|
-
Bundles the
|
|
412
|
+
Bundles the release steps into a single command:
|
|
387
413
|
1. Regenerate \`.claude/commands/*.md\` with the TARGET version stamp
|
|
388
414
|
(the post-bump version, so the slash-command files match the new
|
|
389
415
|
release and no dirty tree lingers after).
|
|
390
416
|
2. Auto-stage every dirty file matching the release allowlist
|
|
391
417
|
(src/, test/, bin/, docs/, .claude/commands/, package*.json,
|
|
392
418
|
dotmd.config*.mjs, README.md, CLAUDE.md, .gitignore). Anything
|
|
393
|
-
outside the allowlist is left dirty — secrets,
|
|
394
|
-
|
|
395
|
-
3. Commit with an auto-generated
|
|
396
|
-
title (if any).
|
|
419
|
+
outside the allowlist is left dirty — secrets, WIP, etc. never get
|
|
420
|
+
bundled in.
|
|
421
|
+
3. Commit with an auto-generated \`chore: release <version>\` message.
|
|
397
422
|
4. Run \`npm version <bump>\` to bump package.json, tag, push, run
|
|
398
423
|
the publish workflow, and reinstall locally.
|
|
399
424
|
|
|
@@ -406,19 +431,12 @@ Network failures mid-bump (e.g. \`git push\` fails) leave the local
|
|
|
406
431
|
commit + tag intact. Inspect with \`git log -1\` and rerun
|
|
407
432
|
\`git push origin main --tags\` to recover.`,
|
|
408
433
|
|
|
409
|
-
set: `dotmd set <status>
|
|
434
|
+
set: `dotmd set <status> <file> — change a document's status
|
|
410
435
|
|
|
411
|
-
|
|
436
|
+
Writes the new status into the file's frontmatter. Nothing else — no plan
|
|
437
|
+
checkout, no session locks.
|
|
412
438
|
- target is an archive status → archive the file (move + ref update)
|
|
413
|
-
-
|
|
414
|
-
- everything else → plain frontmatter status bump
|
|
415
|
-
|
|
416
|
-
When <file> is omitted, dotmd infers it from the calling session's held
|
|
417
|
-
lease. With zero or multiple leases, you must pass <file> explicitly.
|
|
418
|
-
|
|
419
|
-
To acquire an in-session lease, use \`dotmd pickup <file>\` instead —
|
|
420
|
-
\`dotmd set in-session\` is refused so the asymmetric lease-acquisition
|
|
421
|
-
path is never skipped silently.
|
|
439
|
+
- everything else → plain frontmatter status bump
|
|
422
440
|
|
|
423
441
|
Options:
|
|
424
442
|
--no-index Skip index regen (see \`dotmd archive --help\`).
|
|
@@ -426,9 +444,11 @@ Options:
|
|
|
426
444
|
--dry-run, -n Preview without writing.
|
|
427
445
|
|
|
428
446
|
Examples:
|
|
429
|
-
dotmd set
|
|
430
|
-
dotmd set
|
|
431
|
-
dotmd set
|
|
447
|
+
dotmd set in-session docs/plans/x # mark a plan in-session
|
|
448
|
+
dotmd set partial docs/plans/x # mark partial
|
|
449
|
+
dotmd set archived docs/plans/x # archive a specific plan
|
|
450
|
+
|
|
451
|
+
To open a plan (mark in-session AND print its body), use \`dotmd use <file>\`.`,
|
|
432
452
|
|
|
433
453
|
status: `dotmd status <file> <new-status> — transition document status
|
|
434
454
|
|
|
@@ -514,12 +534,10 @@ Options:
|
|
|
514
534
|
|
|
515
535
|
hud: `dotmd hud — actionable triage for session start
|
|
516
536
|
|
|
517
|
-
Prints
|
|
518
|
-
|
|
519
|
-
▶ N pending prompts: <slugs> (saved prompts in docs/prompts/)
|
|
520
|
-
⚠ N stuck leases >24h (suggest \`dotmd release --stale\`)
|
|
537
|
+
Prints the dotmd command primer (the verb cheat-sheet) plus, in --json mode,
|
|
538
|
+
pending prompts and the check-error count for programmatic callers.
|
|
521
539
|
|
|
522
|
-
Silent when
|
|
540
|
+
Silent when there's nothing actionable — designed for SessionStart hooks where
|
|
523
541
|
zero noise is the right default. Distinct from \`dotmd briefing\`, which
|
|
524
542
|
dumps the full plan-status pipeline and per-plan next_step bodies (kilobytes
|
|
525
543
|
on large repos). Use hud for ergonomic session boot; use briefing for
|
|
@@ -1073,14 +1091,13 @@ the order of the children comes from the array.
|
|
|
1073
1091
|
Usage:
|
|
1074
1092
|
dotmd runlist <hub> Show children + their statuses, in order.
|
|
1075
1093
|
The first non-archived child is marked \`→\`.
|
|
1076
|
-
dotmd runlist next <hub>
|
|
1077
|
-
Stops if it's not in a
|
|
1078
|
-
(active / planned / in-session)
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
Flags (only meaningful with \`next
|
|
1082
|
-
--
|
|
1083
|
-
--full Print full plan body instead of the pickup card.
|
|
1094
|
+
dotmd runlist next <hub> Open the first non-archived child (marks it
|
|
1095
|
+
in-session + prints it). Stops if it's not in a
|
|
1096
|
+
workable status (active / planned / in-session)
|
|
1097
|
+
so you resolve the blocker first.
|
|
1098
|
+
|
|
1099
|
+
Flags (only meaningful with \`next\`):
|
|
1100
|
+
--full Print full plan body instead of the card.
|
|
1084
1101
|
--no-index Skip index regeneration.
|
|
1085
1102
|
--show-files Emit \`files: …\` footer.
|
|
1086
1103
|
|
|
@@ -1226,8 +1243,6 @@ async function main() {
|
|
|
1226
1243
|
if (config.presets[command]) {
|
|
1227
1244
|
const { buildIndex } = await import('../src/index.mjs');
|
|
1228
1245
|
const { runQuery } = await import('../src/query.mjs');
|
|
1229
|
-
const { scrubStaleSilently } = await import('../src/lease-scrub.mjs');
|
|
1230
|
-
scrubStaleSilently(config);
|
|
1231
1246
|
const index = buildIndex(config);
|
|
1232
1247
|
runQuery(index, [...config.presets[command], ...restArgs], config, { preset: command });
|
|
1233
1248
|
return;
|
|
@@ -1240,8 +1255,6 @@ async function main() {
|
|
|
1240
1255
|
if (command === 'plans') {
|
|
1241
1256
|
const { buildIndex } = await import('../src/index.mjs');
|
|
1242
1257
|
const { runQuery } = await import('../src/query.mjs');
|
|
1243
|
-
const { scrubStaleSilently } = await import('../src/lease-scrub.mjs');
|
|
1244
|
-
scrubStaleSilently(config);
|
|
1245
1258
|
const index = buildIndex(config);
|
|
1246
1259
|
const sub = restArgs[0];
|
|
1247
1260
|
let defaults;
|
|
@@ -1262,7 +1275,7 @@ async function main() {
|
|
|
1262
1275
|
}
|
|
1263
1276
|
// Top-level `dotmd use [file]` — the single "start engaging with this doc"
|
|
1264
1277
|
// verb. Dispatches by the target doc's type: prompt → consume + archive,
|
|
1265
|
-
// plan →
|
|
1278
|
+
// plan → mark in-session + print, doc → print. With no file: consume oldest
|
|
1266
1279
|
// pending prompt. See src/use.mjs for the dispatch table.
|
|
1267
1280
|
if (command === 'use') {
|
|
1268
1281
|
const { runUse } = await import('../src/use.mjs');
|
|
@@ -1291,9 +1304,12 @@ async function main() {
|
|
|
1291
1304
|
|
|
1292
1305
|
// Lifecycle commands
|
|
1293
1306
|
if (command === 'hud') { const { runHud } = await import('../src/hud.mjs'); runHud(restArgs, config); return; }
|
|
1307
|
+
if (command === 'guard') { const { runGuard } = await import('../src/guard.mjs'); await runGuard(restArgs, config); return; }
|
|
1308
|
+
if (command === 'misuse') { const { runMisuse } = await import('../src/misuse-read.mjs'); runMisuse(restArgs, config); return; }
|
|
1294
1309
|
if (command === 'journal') { const { runJournal } = await import('../src/journal-read.mjs'); runJournal(restArgs, config); return; }
|
|
1295
|
-
if (command === 'pickup'
|
|
1296
|
-
|
|
1310
|
+
if (command === 'pickup' || command === 'unpickup' || command === 'release' || command === 'finish') {
|
|
1311
|
+
die(`\`dotmd ${command}\` was removed — dotmd no longer checks plans in/out. Status is just frontmatter:\n dotmd use <file> # mark in-session + print the plan\n dotmd set <status> <file> # change status\n dotmd archive <file> # close out`);
|
|
1312
|
+
}
|
|
1297
1313
|
if (command === 'runlist') { const { runRunlist } = await import('../src/runlist.mjs'); await runRunlist(restArgs, config, { dryRun }); return; }
|
|
1298
1314
|
if (command === 'handoff') { die('`dotmd handoff` was removed in 0.31.0. Use `dotmd prompts new <name>` to create a saved prompt instead. The .dotmd/handoffs/ sidecar mechanism no longer exists; see CHANGELOG.'); }
|
|
1299
1315
|
if (command === 'status') { const { runStatus } = await import('../src/lifecycle.mjs'); await runStatus(restArgs, config, { dryRun }); return; }
|
|
@@ -1336,21 +1352,14 @@ async function main() {
|
|
|
1336
1352
|
const { buildIndex } = await import('../src/index.mjs');
|
|
1337
1353
|
const { renderCompactList, renderVerboseList, renderContext, renderBriefing, renderCheck, renderCoverage, buildCoverage } = await import('../src/render.mjs');
|
|
1338
1354
|
const { runFocus, runQuery } = await import('../src/query.mjs');
|
|
1339
|
-
// Opportunistic stale-lease scrub for user-facing "what's actionable now"
|
|
1340
|
-
// views. Diagnostic commands (`check`, `coverage`, `stats`, `index`) are
|
|
1341
|
-
// intentionally excluded — they should surface drift, not silently fix it.
|
|
1342
|
-
const SCRUB_READ_COMMANDS = new Set(['list', 'briefing', 'context', 'agent-context', 'focus', 'query', 'modules', 'module', 'surfaces']);
|
|
1343
|
-
if (SCRUB_READ_COMMANDS.has(command)) {
|
|
1344
|
-
const { scrubStaleSilently } = await import('../src/lease-scrub.mjs');
|
|
1345
|
-
scrubStaleSilently(config);
|
|
1346
|
-
}
|
|
1347
1355
|
// `dotmd check` is the one shared-buildIndex command that should auto-heal a
|
|
1348
1356
|
// drifted index block (frontmatter edits by direct Edit/Write, `lint --fix`,
|
|
1349
1357
|
// etc. leave the README out of sync; demanding the user run `dotmd index`
|
|
1350
1358
|
// each time was pure noise). Print/dry-run/read-only callers (`json`, `list`,
|
|
1351
1359
|
// `query`, `index --print`, ...) stay opt-out so they never mutate disk.
|
|
1360
|
+
const checkHasPathScope = command === 'check' && restArgs.some(arg => !arg.startsWith('-'));
|
|
1352
1361
|
const AUTO_HEAL_INDEX_COMMANDS = new Set(['check']);
|
|
1353
|
-
const index = buildIndex(config, { autoHealIndex: AUTO_HEAL_INDEX_COMMANDS.has(command) });
|
|
1362
|
+
const index = buildIndex(config, { autoHealIndex: AUTO_HEAL_INDEX_COMMANDS.has(command) && !checkHasPathScope });
|
|
1354
1363
|
|
|
1355
1364
|
// Apply --root and --type filters
|
|
1356
1365
|
const rootFilter = rootArg;
|
|
@@ -1405,6 +1414,11 @@ async function main() {
|
|
|
1405
1414
|
const errorsOnly = args.includes('--errors-only');
|
|
1406
1415
|
const noCollapse = args.includes('--no-collapse');
|
|
1407
1416
|
const verbose = args.includes('--verbose');
|
|
1417
|
+
const checkTargets = restArgs.filter(arg => !arg.startsWith('-'));
|
|
1418
|
+
|
|
1419
|
+
if (fix && checkTargets.length > 0) {
|
|
1420
|
+
die('`dotmd check --fix` does not support path-scoped checks yet. Run `dotmd check <path>` to validate a subset, or `dotmd check --fix` to fix the whole docs tree.');
|
|
1421
|
+
}
|
|
1408
1422
|
|
|
1409
1423
|
if (fix) {
|
|
1410
1424
|
// Auto-fix: broken refs, then lint, then rebuild index
|
|
@@ -1425,6 +1439,7 @@ async function main() {
|
|
|
1425
1439
|
// Show remaining issues
|
|
1426
1440
|
const freshIndex = buildIndex(config);
|
|
1427
1441
|
applyIndexFilters(freshIndex);
|
|
1442
|
+
applyPathScopeToIndex(freshIndex, config, checkTargets);
|
|
1428
1443
|
if (args.includes('--json')) {
|
|
1429
1444
|
process.stdout.write(JSON.stringify({
|
|
1430
1445
|
docsScanned: freshIndex.docs.length,
|
|
@@ -1441,6 +1456,8 @@ async function main() {
|
|
|
1441
1456
|
return;
|
|
1442
1457
|
}
|
|
1443
1458
|
|
|
1459
|
+
applyPathScopeToIndex(index, config, checkTargets);
|
|
1460
|
+
|
|
1444
1461
|
if (args.includes('--json')) {
|
|
1445
1462
|
process.stdout.write(JSON.stringify({
|
|
1446
1463
|
docsScanned: index.docs.length,
|
package/package.json
CHANGED