dotmd-cli 0.62.0 → 0.64.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 +26 -5
- package/bin/dotmd.mjs +110 -76
- package/dotmd.config.example.mjs +2 -0
- package/package.json +2 -2
- package/scripts/postinstall.mjs +6 -1
- package/src/commands.mjs +1 -1
- package/src/completions.mjs +53 -28
- package/src/config.mjs +8 -2
- package/src/health.mjs +5 -4
- package/src/init.mjs +36 -4
- package/src/lifecycle.mjs +83 -31
- package/src/new.mjs +166 -4
- package/src/query.mjs +17 -2
- package/src/runlist.mjs +88 -11
- package/src/ship.mjs +6 -0
package/README.md
CHANGED
|
@@ -21,7 +21,9 @@ If you drive dotmd from Claude Code, install the **dotmd plugin**. It teaches ev
|
|
|
21
21
|
/plugin install dotmd@dotmd
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
The plugin bundles the hooks (`SessionStart`/`SubagentStart` priming, a `PreToolUse` guard) and a canonical workflow skill, so guidance travels to **every** repo automatically — no per-repo setup.
|
|
24
|
+
The plugin bundles the hooks (`SessionStart`/`SubagentStart` priming, a `PreToolUse` guard) and a canonical workflow skill, so guidance travels to **every** repo automatically — no per-repo setup. (Source: `plugins/dotmd/` in this repo.)
|
|
25
|
+
|
|
26
|
+
> **The plugin requires a _global_ CLI install.** Its hooks resolve `dotmd` from your `PATH`, so run `npm install -g dotmd-cli`. A project devDependency (`npm install -D dotmd-cli`) lives at `./node_modules/.bin/dotmd` — off `PATH` — so the hooks silently no-op (no errors, just no priming/guarding). Use the devDep for `npm run` scripts; use the global install for the plugin.
|
|
25
27
|
|
|
26
28
|
> **Upgrading to 0.57.0+:** per-repo `.claude/commands/{plans,docs,baton}.md` scaffolding is retired — that guidance now ships via the plugin's workflow skill and `/plans`, `/docs`, `/prompts`, `/baton` commands. On the next `dotmd hud` (SessionStart), dotmd removes those generated files (only banner-stamped `<!-- dotmd-generated -->` ones — your hand-authored command files are never touched). If you'd committed them, you'll see deletions to commit — that's expected. Run `claude plugin update dotmd@dotmd` to pick up `/baton`.
|
|
27
29
|
|
|
@@ -195,15 +197,34 @@ runlist:
|
|
|
195
197
|
---
|
|
196
198
|
```
|
|
197
199
|
|
|
200
|
+
Scaffold the whole sprint in one command instead of hand-writing the hub + children:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
$ dotmd new plan auth-revamp --runlist extract,rewrite,cleanup
|
|
204
|
+
Created: plans/auth-revamp.md (plan (runlist hub))
|
|
205
|
+
Created: plans/auth-revamp-01-extract.md (plan · runlist child, planned)
|
|
206
|
+
Created: plans/auth-revamp-02-rewrite.md (plan · runlist child, planned)
|
|
207
|
+
Created: plans/auth-revamp-03-cleanup.md (plan · runlist child, planned)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
That writes the hub above (the `runlist:` array + an `## Order of operations` link list) plus one `planned` child stub per slug, each carrying a `parent_plan: auth-revamp.md` back-ref. Then walk the sequence:
|
|
211
|
+
|
|
198
212
|
```bash
|
|
199
|
-
dotmd runlist
|
|
200
|
-
|
|
201
|
-
|
|
213
|
+
$ dotmd runlist auth-revamp # the sequence + statuses; → marks the next pickup
|
|
214
|
+
runlist: plans/auth-revamp.md
|
|
215
|
+
→ 1. [planned] plans/auth-revamp-01-extract.md
|
|
216
|
+
2. [planned] plans/auth-revamp-02-rewrite.md
|
|
217
|
+
3. [planned] plans/auth-revamp-03-cleanup.md
|
|
218
|
+
|
|
219
|
+
$ dotmd runlist next auth-revamp # pick up the → child (planned → in-session) + print its card
|
|
220
|
+
▶ Started: plans/auth-revamp-01-extract.md (planned → in-session)
|
|
221
|
+
|
|
222
|
+
$ dotmd runlists # dashboard of coordination-hub runlists (--json, --limit N)
|
|
202
223
|
```
|
|
203
224
|
|
|
204
225
|
`runlist next` stops with a runlist-aware error if the next child isn't in a workable status (`active` / `planned` / `in-session`), so you resolve the blocker before continuing. Each child should set `parent_plan:` pointing back at the hub — `dotmd check` warns when it doesn't. There's no separate doc type: a runlist hub is just a plan with the array.
|
|
205
226
|
|
|
206
|
-
In `dotmd plans`,
|
|
227
|
+
In `dotmd plans`, the hub folds its children under one tagged row — `auth-revamp runlist · 0/3 · next → 01-extract [RUNLIST]` — so a multi-plan sprint reads as one runlist instead of cluttering the triage list. A child whose hub is filtered out of the view (e.g. `--status active` when the hub is `planned`) still renders on its own.
|
|
207
228
|
|
|
208
229
|
**Coordination runlists.** A `runlist:` array fits a small ordered *sprint*. For a large, prose-first *coordination map* (a domain hub pointing at many plans, with gating/sequence rationale, sometimes unordered), set `execution_mode: coordination` instead — or just name it `*-runlist`. These aren't folded: `dotmd plans` lifts them into a pinned `Runlists` section and out of the active count, and `dotmd runlists` shows that dashboard standalone. `dotmd briefing` and `dotmd health` do the same — coordination hubs are pulled out of the live/active counts into a `runlists` bucket (briefing) and a held-out `Runlists:` tally (health), so they don't inflate the actionable-plan or aging numbers. The per-hub "N related" count comes from `related_plans:`. `dotmd check` nudges a `*-runlist` hub missing `execution_mode: coordination`.
|
|
209
230
|
|
package/bin/dotmd.mjs
CHANGED
|
@@ -451,21 +451,21 @@ Composes with the usual query flags:
|
|
|
451
451
|
dotmd grep retries --limit 5 cap results (default: unlimited)
|
|
452
452
|
dotmd grep retries --json machine-readable (bodyMatches per doc)`,
|
|
453
453
|
|
|
454
|
-
ship: `dotmd ship [patch|minor|major] —
|
|
454
|
+
ship: `dotmd ship [patch|minor|major] — commit + bump in one step
|
|
455
455
|
|
|
456
456
|
Bundles the release steps into a single command:
|
|
457
|
-
1.
|
|
458
|
-
(
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
bundled in.
|
|
465
|
-
3. Commit with an auto-generated \`chore: release <version>\` message.
|
|
466
|
-
4. Run \`npm version <bump>\` to bump package.json, tag, push, run
|
|
457
|
+
1. Auto-stage every dirty file matching the release allowlist
|
|
458
|
+
(src/, test/, bin/, docs/, plugins/, .claude-plugin/,
|
|
459
|
+
.claude/commands/, package*.json, dotmd.config*.mjs, README.md,
|
|
460
|
+
CLAUDE.md, .gitignore). Anything outside the allowlist is left
|
|
461
|
+
dirty — secrets, WIP, etc. never get bundled in.
|
|
462
|
+
2. Commit with an auto-generated \`chore: release <version>\` message.
|
|
463
|
+
3. Run \`npm version <bump>\` to bump package.json, tag, push, run
|
|
467
464
|
the publish workflow, and reinstall locally.
|
|
468
465
|
|
|
466
|
+
(Per-repo \`.claude/commands\` scaffolding is retired — the dotmd plugin's
|
|
467
|
+
SKILL.md is canonical now — so ship no longer regenerates anything.)
|
|
468
|
+
|
|
469
469
|
Options:
|
|
470
470
|
--dry-run, -n Show what would happen without staging or bumping.
|
|
471
471
|
|
|
@@ -758,10 +758,10 @@ Modes:
|
|
|
758
758
|
in their Version History would be misleading).
|
|
759
759
|
--migrate-template --json Machine-readable result.
|
|
760
760
|
--frontmatter-fix Auto-fix the long-frontmatter warnings that
|
|
761
|
-
\`dotmd check\` flags: \`current_state\` >
|
|
762
|
-
or \`next_step\` >
|
|
761
|
+
\`dotmd check\` flags: \`current_state\` >1500 chars
|
|
762
|
+
or \`next_step\` >800 chars. Truncates the
|
|
763
763
|
frontmatter field at the nearest sentence
|
|
764
|
-
boundary under the target (
|
|
764
|
+
boundary under the target (1200 / 600) and
|
|
765
765
|
appends the remainder to a \`## Current State\`
|
|
766
766
|
/ \`## Next Step\` body section (created above
|
|
767
767
|
the first H2 if absent, appended otherwise).
|
|
@@ -843,6 +843,20 @@ Examples:
|
|
|
843
843
|
EOF
|
|
844
844
|
dotmd new plan auth-revamp "Investigation findings before scoping…"
|
|
845
845
|
|
|
846
|
+
Scaffolding runlists (plans only):
|
|
847
|
+
--runlist <a,b,c> Create a sprint runlist hub plus one child plan per slug.
|
|
848
|
+
The hub carries \`runlist: [<hub>-01-a.md, <hub>-02-b.md, …]\`
|
|
849
|
+
and an \`## Order of operations\` list; each child is a
|
|
850
|
+
\`planned\` stub with a \`parent_plan:\` back-ref. Children
|
|
851
|
+
are named by the documented \`<hub>-NN-<slug>\` convention.
|
|
852
|
+
--coordination Create a prose-first coordination hub: \`execution_mode:
|
|
853
|
+
coordination\` + a \`## Ranked queue\` skeleton (no children).
|
|
854
|
+
Surfaces in \`dotmd runlists\`, held out of the active count.
|
|
855
|
+
(\`--runlist\` and \`--coordination\` are mutually exclusive.)
|
|
856
|
+
|
|
857
|
+
dotmd new plan auth-revamp --runlist extract,rewrite,cleanup
|
|
858
|
+
dotmd new plan platform --coordination
|
|
859
|
+
|
|
846
860
|
Other options:
|
|
847
861
|
--status <s> Set initial status (defaults to first valid status for the type)
|
|
848
862
|
--title <t> Override the auto-derived title
|
|
@@ -853,7 +867,7 @@ Other options:
|
|
|
853
867
|
|
|
854
868
|
For plans, the default status vocabulary is: in-session, active, planned,
|
|
855
869
|
blocked, partial, paused, awaiting, queued-after, archived.
|
|
856
|
-
For prompts: pending (default), claimed, archived.
|
|
870
|
+
For prompts: pending (default), held, shelved, claimed, archived.
|
|
857
871
|
|
|
858
872
|
Use --dry-run (-n) to preview without creating the file.`,
|
|
859
873
|
|
|
@@ -1225,14 +1239,19 @@ when the hub is \`planned\`) still render on their own.
|
|
|
1225
1239
|
Larger, prose-first "coordination" runlists (a domain map pointing at many
|
|
1226
1240
|
plans, marked \`execution_mode: coordination\` or named \`*-runlist\`) aren't
|
|
1227
1241
|
folded — they're lifted into a separate \`Runlists\` section in \`dotmd plans\`
|
|
1228
|
-
and out of the active count. \`dotmd runlists\` shows that dashboard on its own
|
|
1242
|
+
and out of the active count. \`dotmd runlists\` shows that dashboard on its own.
|
|
1243
|
+
For these, \`runlist\`/\`runlist next\` also read order from the body when there's
|
|
1244
|
+
no \`runlist:\` array — a \`## Ranked queue\` table or \`## Order of operations\`
|
|
1245
|
+
list of markdown links (the first \`.md\` link per row/item, in order).`,
|
|
1229
1246
|
|
|
1230
1247
|
runlists: `dotmd runlists — the coordination-hub dashboard
|
|
1231
1248
|
|
|
1232
1249
|
Lists every *coordination runlist*: a prose-first plan that sits above a
|
|
1233
1250
|
cluster of others (a domain map), detected by \`execution_mode: coordination\`
|
|
1234
1251
|
or a \`*-runlist\` / \`runlist\` slug. Each row shows the hub, its age, the rough
|
|
1235
|
-
size of its \`related_plans:\` cluster,
|
|
1252
|
+
size of its \`related_plans:\` cluster, a \`next → <child>\` when the hub's body
|
|
1253
|
+
encodes order as markdown links (\`## Ranked queue\` table / \`## Order of
|
|
1254
|
+
operations\` list), and a one-line descriptor.
|
|
1236
1255
|
|
|
1237
1256
|
This is the standalone form of the \`Runlists\` section that \`dotmd plans\`
|
|
1238
1257
|
pins beneath the leaf-plan triage list.
|
|
@@ -1240,7 +1259,7 @@ pins beneath the leaf-plan triage list.
|
|
|
1240
1259
|
dotmd runlists All runlists (a small bounded set), most stale first.
|
|
1241
1260
|
dotmd runlists --sort recent Order by recency instead (age|recent|related|title|status).
|
|
1242
1261
|
dotmd runlists --limit N Cap the list at N.
|
|
1243
|
-
dotmd runlists --json Structured rows (path, status, childCount, …).`,
|
|
1262
|
+
dotmd runlists --json Structured rows (path, status, childCount, nextPickup, …).`,
|
|
1244
1263
|
|
|
1245
1264
|
'bulk-tag': `dotmd bulk-tag [files...] — fill in type/status frontmatter on pre-existing markdown
|
|
1246
1265
|
|
|
@@ -1268,7 +1287,6 @@ the whole docs tree is scanned.`,
|
|
|
1268
1287
|
|
|
1269
1288
|
async function main() {
|
|
1270
1289
|
const args = process.argv.slice(2);
|
|
1271
|
-
let command = args[0] ?? 'list';
|
|
1272
1290
|
|
|
1273
1291
|
// Pre-config flags
|
|
1274
1292
|
if (args.includes('--version') || args.includes('-v')) {
|
|
@@ -1276,8 +1294,70 @@ async function main() {
|
|
|
1276
1294
|
return;
|
|
1277
1295
|
}
|
|
1278
1296
|
|
|
1297
|
+
// Normalize global flags from ANYWHERE in argv (before OR after the command)
|
|
1298
|
+
// so `dotmd --config x list` resolves `list` as the command, not `--config`.
|
|
1299
|
+
// Value flags (--config/--root/--type) consume the next token; the booleans
|
|
1300
|
+
// (--dry-run/-n/--verbose) are read positionally below. --help/-h stay in the
|
|
1301
|
+
// leftover stream and are handled by the blocks just below.
|
|
1302
|
+
let explicitConfig = null;
|
|
1303
|
+
let rootArg = null;
|
|
1304
|
+
let typeArg = null;
|
|
1305
|
+
const normalized = [];
|
|
1306
|
+
for (let i = 0; i < args.length; i++) {
|
|
1307
|
+
const a = args[i];
|
|
1308
|
+
if (a === '--config' && args[i + 1]) { explicitConfig = args[++i]; continue; }
|
|
1309
|
+
if (a === '--type' && args[i + 1]) { typeArg = args[++i]; continue; }
|
|
1310
|
+
if (a === '--root' && args[i + 1]) { rootArg = args[++i]; continue; }
|
|
1311
|
+
if (a === '--dry-run' || a === '-n' || a === '--verbose') continue;
|
|
1312
|
+
normalized.push(a);
|
|
1313
|
+
}
|
|
1314
|
+
const dryRun = args.includes('--dry-run') || args.includes('-n');
|
|
1315
|
+
const verbose = args.includes('--verbose');
|
|
1316
|
+
let command = normalized[0] ?? 'list';
|
|
1317
|
+
const restArgs = normalized.slice(1);
|
|
1318
|
+
|
|
1319
|
+
// Reconstruct the active global flags for proxy commands (e.g. `watch`) that
|
|
1320
|
+
// re-invoke the CLI in a child process and must propagate them through.
|
|
1321
|
+
const globalFlagArgs = () => {
|
|
1322
|
+
const out = [];
|
|
1323
|
+
if (explicitConfig) out.push('--config', explicitConfig);
|
|
1324
|
+
if (rootArg) out.push('--root', rootArg);
|
|
1325
|
+
if (typeArg) out.push('--type', typeArg);
|
|
1326
|
+
if (dryRun) out.push('--dry-run');
|
|
1327
|
+
if (verbose) out.push('--verbose');
|
|
1328
|
+
return out;
|
|
1329
|
+
};
|
|
1330
|
+
|
|
1331
|
+
// Apply global --root / --type filters to an index in place. Shared by the
|
|
1332
|
+
// common index path below AND the early-dispatched commands (plans, runlists,
|
|
1333
|
+
// presets) that build their own index, so filtering is consistent everywhere.
|
|
1334
|
+
// Recomputes BOTH countsByStatus and countsByType so filtered JSON never
|
|
1335
|
+
// reports corpus-wide tallies.
|
|
1336
|
+
function applyIndexFilters(idx) {
|
|
1337
|
+
if (rootArg) {
|
|
1338
|
+
idx.docs = idx.docs.filter(d => d.root === rootArg || d.root.endsWith('/' + rootArg) || d.root.split('/').pop() === rootArg);
|
|
1339
|
+
}
|
|
1340
|
+
if (typeArg) {
|
|
1341
|
+
const types = typeArg.split(',').map(t => t.trim()).filter(Boolean);
|
|
1342
|
+
idx.docs = idx.docs.filter(d => types.includes(d.type));
|
|
1343
|
+
}
|
|
1344
|
+
if (rootArg || typeArg) {
|
|
1345
|
+
idx.errors = idx.errors.filter(e => idx.docs.some(d => d.path === e.path));
|
|
1346
|
+
idx.warnings = idx.warnings.filter(w => idx.docs.some(d => d.path === w.path));
|
|
1347
|
+
idx.countsByStatus = {};
|
|
1348
|
+
idx.countsByType = {};
|
|
1349
|
+
for (const doc of idx.docs) {
|
|
1350
|
+
const status = doc.status ?? 'unknown';
|
|
1351
|
+
idx.countsByStatus[status] = (idx.countsByStatus[status] ?? 0) + 1;
|
|
1352
|
+
const type = doc.type || 'unknown';
|
|
1353
|
+
if (!idx.countsByType[type]) idx.countsByType[type] = {};
|
|
1354
|
+
idx.countsByType[type][status] = (idx.countsByType[type][status] ?? 0) + 1;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1279
1359
|
if (command === 'help' || command === '--help' || command === '-h') {
|
|
1280
|
-
const topic =
|
|
1360
|
+
const topic = restArgs[0];
|
|
1281
1361
|
if (topic) {
|
|
1282
1362
|
const key = `help:${topic}`;
|
|
1283
1363
|
if (HELP[key]) { process.stdout.write(`${HELP[key]}\n`); return; }
|
|
@@ -1305,22 +1385,10 @@ async function main() {
|
|
|
1305
1385
|
|
|
1306
1386
|
if (command === 'completions') {
|
|
1307
1387
|
const { runCompletions } = await import('../src/completions.mjs');
|
|
1308
|
-
runCompletions(
|
|
1388
|
+
runCompletions(restArgs);
|
|
1309
1389
|
return;
|
|
1310
1390
|
}
|
|
1311
1391
|
|
|
1312
|
-
// Extract --config flag
|
|
1313
|
-
let explicitConfig = null;
|
|
1314
|
-
for (let i = 0; i < args.length; i++) {
|
|
1315
|
-
if (args[i] === '--config' && args[i + 1]) {
|
|
1316
|
-
explicitConfig = args[i + 1];
|
|
1317
|
-
break;
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
const dryRun = args.includes('--dry-run') || args.includes('-n');
|
|
1322
|
-
const verbose = args.includes('--verbose');
|
|
1323
|
-
|
|
1324
1392
|
const config = await resolveConfig(process.cwd(), explicitConfig);
|
|
1325
1393
|
_resolvedConfig = config;
|
|
1326
1394
|
|
|
@@ -1332,20 +1400,9 @@ async function main() {
|
|
|
1332
1400
|
return;
|
|
1333
1401
|
}
|
|
1334
1402
|
|
|
1335
|
-
// Watch is a
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
// Strip global flags from restArgs so commands don't have to filter them
|
|
1339
|
-
const restArgs = [];
|
|
1340
|
-
let rootArg = null;
|
|
1341
|
-
let typeArg = null;
|
|
1342
|
-
for (let i = 1; i < args.length; i++) {
|
|
1343
|
-
if (args[i] === '--config') { i++; continue; }
|
|
1344
|
-
if (args[i] === '--type' && args[i + 1]) { typeArg = args[++i]; continue; }
|
|
1345
|
-
if (args[i] === '--root' && args[i + 1]) { rootArg = args[++i]; continue; }
|
|
1346
|
-
if (args[i] === '--dry-run' || args[i] === '-n' || args[i] === '--verbose') continue;
|
|
1347
|
-
restArgs.push(args[i]);
|
|
1348
|
-
}
|
|
1403
|
+
// Watch is a proxy — re-inject the active globals so the child re-resolves
|
|
1404
|
+
// the same config/filters.
|
|
1405
|
+
if (command === 'watch') { const { runWatch } = await import('../src/watch.mjs'); runWatch([...globalFlagArgs(), ...restArgs], config); return; }
|
|
1349
1406
|
|
|
1350
1407
|
// Hook commands (`hud`, `guard`) fire in EVERY repo via the globally-enabled
|
|
1351
1408
|
// plugin — `guard` runs on every Bash/Read/Edit. They must stay silent where
|
|
@@ -1376,7 +1433,8 @@ async function main() {
|
|
|
1376
1433
|
const { buildIndex } = await import('../src/index.mjs');
|
|
1377
1434
|
const { runQuery } = await import('../src/query.mjs');
|
|
1378
1435
|
const index = buildIndex(config);
|
|
1379
|
-
|
|
1436
|
+
applyIndexFilters(index);
|
|
1437
|
+
runQuery(index, [...config.presets[command], ...restArgs], config, { preset: command, type: typeArg, root: rootArg });
|
|
1380
1438
|
return;
|
|
1381
1439
|
}
|
|
1382
1440
|
|
|
@@ -1388,6 +1446,7 @@ async function main() {
|
|
|
1388
1446
|
const { buildIndex } = await import('../src/index.mjs');
|
|
1389
1447
|
const { runQuery } = await import('../src/query.mjs');
|
|
1390
1448
|
const index = buildIndex(config);
|
|
1449
|
+
applyIndexFilters(index);
|
|
1391
1450
|
const sub = restArgs[0];
|
|
1392
1451
|
let defaults;
|
|
1393
1452
|
let extras = restArgs;
|
|
@@ -1397,7 +1456,7 @@ async function main() {
|
|
|
1397
1456
|
} else {
|
|
1398
1457
|
defaults = ['--type', 'plan', '--exclude-archived', '--sort', 'updated', '--limit', '10'];
|
|
1399
1458
|
}
|
|
1400
|
-
runQuery(index, [...defaults, ...extras], config, { preset: 'plans' });
|
|
1459
|
+
runQuery(index, [...defaults, ...extras], config, { preset: 'plans', type: typeArg, root: rootArg });
|
|
1401
1460
|
return;
|
|
1402
1461
|
}
|
|
1403
1462
|
// `dotmd runlists` (plural) — the coordination-hub dashboard (the `Runlists`
|
|
@@ -1407,6 +1466,7 @@ async function main() {
|
|
|
1407
1466
|
const { buildIndex } = await import('../src/index.mjs');
|
|
1408
1467
|
const { runRunlists } = await import('../src/query.mjs');
|
|
1409
1468
|
const index = buildIndex(config);
|
|
1469
|
+
applyIndexFilters(index);
|
|
1410
1470
|
runRunlists(index, restArgs, config);
|
|
1411
1471
|
return;
|
|
1412
1472
|
}
|
|
@@ -1512,34 +1572,8 @@ async function main() {
|
|
|
1512
1572
|
const AUTO_HEAL_INDEX_COMMANDS = new Set(['check']);
|
|
1513
1573
|
const index = buildIndex(config, { autoHealIndex: AUTO_HEAL_INDEX_COMMANDS.has(command) && !checkHasPathScope });
|
|
1514
1574
|
|
|
1515
|
-
// Apply --root and --type filters
|
|
1516
|
-
const rootFilter = rootArg;
|
|
1517
|
-
const typeFilter = typeArg;
|
|
1518
|
-
|
|
1519
|
-
function applyIndexFilters(idx) {
|
|
1520
|
-
if (rootFilter) {
|
|
1521
|
-
idx.docs = idx.docs.filter(d => d.root === rootFilter || d.root.endsWith('/' + rootFilter) || d.root.split('/').pop() === rootFilter);
|
|
1522
|
-
}
|
|
1523
|
-
if (typeFilter) {
|
|
1524
|
-
const types = typeFilter.split(',').map(t => t.trim()).filter(Boolean);
|
|
1525
|
-
idx.docs = idx.docs.filter(d => types.includes(d.type));
|
|
1526
|
-
}
|
|
1527
|
-
if (rootFilter || typeFilter) {
|
|
1528
|
-
idx.errors = idx.errors.filter(e => idx.docs.some(d => d.path === e.path));
|
|
1529
|
-
idx.warnings = idx.warnings.filter(w => idx.docs.some(d => d.path === w.path));
|
|
1530
|
-
idx.countsByStatus = {};
|
|
1531
|
-
for (const doc of idx.docs) {
|
|
1532
|
-
const s = doc.status ?? 'unknown';
|
|
1533
|
-
idx.countsByStatus[s] = (idx.countsByStatus[s] ?? 0) + 1;
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
1575
|
applyIndexFilters(index);
|
|
1539
1576
|
|
|
1540
|
-
if (rootFilter || typeFilter) {
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
1577
|
if (verbose) {
|
|
1544
1578
|
process.stderr.write(`Docs found: ${index.docs.length}\n`);
|
|
1545
1579
|
}
|
|
@@ -1666,7 +1700,7 @@ async function main() {
|
|
|
1666
1700
|
}
|
|
1667
1701
|
|
|
1668
1702
|
if (command === 'focus') { runFocus(index, restArgs, config); return; }
|
|
1669
|
-
if (command === 'query') { runQuery(index, restArgs, config); return; }
|
|
1703
|
+
if (command === 'query') { runQuery(index, restArgs, config, { type: typeArg, root: rootArg }); return; }
|
|
1670
1704
|
// `dotmd grep <term>` — ergonomic alias for `query --keyword <term> --body`.
|
|
1671
1705
|
// Unlimited by default (grep semantics) unless the caller bounds it themselves.
|
|
1672
1706
|
if (command === 'grep') {
|
package/dotmd.config.example.mjs
CHANGED
|
@@ -76,6 +76,8 @@ export const excludeDirs = ['evidence'];
|
|
|
76
76
|
// // pending prompts on session start; `dotmd prompts next` claims the oldest.
|
|
77
77
|
// statuses: {
|
|
78
78
|
// 'pending': { context: 'expanded', staleDays: 30 },
|
|
79
|
+
// 'held': { context: 'counted', quiet: true }, // saved but not next: hidden from hud/briefing, skipped by no-arg `use`
|
|
80
|
+
// 'shelved': { context: 'counted', quiet: true }, // legacy alias for held
|
|
79
81
|
// 'claimed': { context: 'counted', quiet: true },
|
|
80
82
|
// 'archived': { context: 'counted', archive: true, terminal: true, quiet: true },
|
|
81
83
|
// },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dotmd-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.64.0",
|
|
4
4
|
"description": "CLI for managing markdown documents with YAML frontmatter — index, query, validate, graph, export, Notion sync, AI summaries.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"test": "node --test test/*.test.mjs",
|
|
43
43
|
"postinstall": "node scripts/postinstall.mjs",
|
|
44
44
|
"preversion": "npm test",
|
|
45
|
-
"version": "node bin/dotmd.mjs hud >/dev/null 2>&1; node scripts/sync-plugin-version.mjs; git add .claude/commands docs/docs.md plugins
|
|
45
|
+
"version": "node bin/dotmd.mjs hud >/dev/null 2>&1; node scripts/sync-plugin-version.mjs; git add .claude/commands docs/docs.md plugins .claude-plugin 2>/dev/null; true",
|
|
46
46
|
"postversion": "bash scripts/postversion.sh"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -28,7 +28,12 @@ try {
|
|
|
28
28
|
spawnSync('claude', ['plugin', 'update', 'dotmd@dotmd'], { stdio: 'ignore', timeout: 60000 });
|
|
29
29
|
process.stdout.write('dotmd: refreshed the Claude Code plugin — restart your session (or /reload-plugins) to apply.\n');
|
|
30
30
|
} else {
|
|
31
|
-
|
|
31
|
+
// The CLI just installed fresh, so only the plugin can be stale — point at
|
|
32
|
+
// the targeted refresh rather than the full `dotmd update` (CLI + plugin).
|
|
33
|
+
const nudge = hasClaude
|
|
34
|
+
? 'dotmd CLI installed. Using the Claude Code plugin? Run `dotmd update --plugin-only` to refresh it, then restart.'
|
|
35
|
+
: 'dotmd CLI installed.';
|
|
36
|
+
process.stdout.write(`${nudge}\n`);
|
|
32
37
|
}
|
|
33
38
|
} catch {
|
|
34
39
|
// Best effort only — never break the install.
|
package/src/commands.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
export const KNOWN_COMMANDS = [
|
|
6
6
|
'list', 'json', 'check', 'coverage', 'stats', 'graph', 'deps', 'briefing', 'context', 'agent-context', 'hud',
|
|
7
7
|
'focus', 'query', 'grep', 'plans', 'prompts', 'stale', 'actionable', 'index', 'status', 'set', 'use', 'next', 'archive', 'bulk', 'bulk-tag', 'touch', 'doctor', 'runlist', 'runlists',
|
|
8
|
-
'unblocks', 'health', 'glossary', 'modules', 'module',
|
|
8
|
+
'unblocks', 'health', 'glossary', 'modules', 'module', 'surfaces',
|
|
9
9
|
'fix-refs', 'lint', 'rename', 'migrate', 'notion', 'export', 'summary',
|
|
10
10
|
'watch', 'diff', 'new', 'init', 'completions', 'statuses', 'journal',
|
|
11
11
|
'guard', 'misuse', 'update',
|
package/src/completions.mjs
CHANGED
|
@@ -1,52 +1,77 @@
|
|
|
1
1
|
import { die } from './util.mjs';
|
|
2
|
+
import { KNOWN_COMMANDS } from './commands.mjs';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
// Derive the completable command list from the dispatcher's canonical verb list
|
|
5
|
+
// so completions never drift behind new commands. A tiny denylist drops verbs
|
|
6
|
+
// that exist but shouldn't be tab-completed (internal self-test).
|
|
7
|
+
const COMPLETION_DENYLIST = new Set(['self-check']);
|
|
8
|
+
const COMMANDS = KNOWN_COMMANDS.filter(c => !COMPLETION_DENYLIST.has(c));
|
|
8
9
|
|
|
9
10
|
const GLOBAL_FLAGS = ['--config', '--dry-run', '--verbose', '--root', '--type', '--help', '--version'];
|
|
10
11
|
|
|
12
|
+
// Shared filter flags for the query-style commands (mirrors QUERY_FLAGS in
|
|
13
|
+
// bin/dotmd.mjs). Kept as one array so query/grep/stale/actionable/plans stay
|
|
14
|
+
// in lockstep.
|
|
15
|
+
const QUERY_FLAGS = [
|
|
16
|
+
'--type', '--status', '--keyword', '--body', '--owner', '--surface', '--module',
|
|
17
|
+
'--domain', '--audience', '--execution-mode', '--updated-since', '--limit', '--sort',
|
|
18
|
+
'--group', '--all', '--include-archived', '--exclude-archived', '--stale',
|
|
19
|
+
'--has-next-step', '--has-blockers', '--checklist-open', '--json', '--git',
|
|
20
|
+
'--summarize', '--summarize-limit', '--model',
|
|
21
|
+
];
|
|
22
|
+
|
|
11
23
|
const COMMAND_FLAGS = {
|
|
12
|
-
query:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
'--module', '--surface', '--domain', '--owner'],
|
|
18
|
-
index: ['--write'],
|
|
24
|
+
query: QUERY_FLAGS,
|
|
25
|
+
grep: QUERY_FLAGS,
|
|
26
|
+
stale: QUERY_FLAGS,
|
|
27
|
+
actionable: QUERY_FLAGS,
|
|
28
|
+
plans: ['status', ...QUERY_FLAGS],
|
|
19
29
|
list: ['--verbose', '--json'],
|
|
30
|
+
briefing: ['--json'],
|
|
31
|
+
context: ['--json', '--compact', '--summarize', '--model'],
|
|
32
|
+
'agent-context': ['--json'],
|
|
33
|
+
hud: ['--json', '--subagent'],
|
|
34
|
+
index: ['--write'],
|
|
20
35
|
coverage: ['--json'],
|
|
21
|
-
new: ['--status', '--title', '--template', '--list-templates', '--root'],
|
|
22
|
-
diff: ['--stat', '--since', '--summarize', '--model'],
|
|
23
|
-
check: ['--errors-only', '--fix', '--json'],
|
|
24
36
|
stats: ['--json'],
|
|
25
37
|
graph: ['--dot', '--json', '--status', '--module', '--surface'],
|
|
26
38
|
deps: ['--json', '--depth'],
|
|
27
39
|
unblocks: ['--json'],
|
|
28
40
|
health: ['--json'],
|
|
29
41
|
glossary: ['--list', '--json'],
|
|
30
|
-
|
|
42
|
+
modules: ['--sort', '--json'],
|
|
43
|
+
module: ['--json'],
|
|
44
|
+
surfaces: ['--json'],
|
|
45
|
+
runlists: ['--json', '--limit', '--sort'],
|
|
46
|
+
runlist: ['next', '--json', '--full', '--no-index', '--show-files'],
|
|
47
|
+
prompts: ['list', 'next', 'use', 'show', 'archive', 'new', 'hold', 'unhold',
|
|
48
|
+
'--json', '--status', '--include-archived', '--sort', '--limit', '--all'],
|
|
49
|
+
use: [],
|
|
50
|
+
next: [],
|
|
51
|
+
baton: ['--status', '--note', '--body', '--message', '--dry-run'],
|
|
52
|
+
set: [],
|
|
53
|
+
status: [],
|
|
54
|
+
archive: ['--note', '--no-index', '--show-files', '--closeout-template'],
|
|
55
|
+
bulk: ['archive', 'tag'],
|
|
56
|
+
statuses: ['list', 'add', '--type', '--like', '--json'],
|
|
57
|
+
update: ['--check', '--cli-only', '--plugin-only'],
|
|
58
|
+
misuse: ['--json', '--tail', '--by-rule', '--repo'],
|
|
59
|
+
journal: ['--tail', '--errors', '--session', '--since', '--by-command', '--json'],
|
|
60
|
+
new: ['--status', '--title', '--template', '--list-templates', '--root', '--message', '--body'],
|
|
31
61
|
notion: ['import', 'export', 'sync', '--force', '--dry-run'],
|
|
32
62
|
export: ['--format', '--output', '--status', '--module', '--root', '--type'],
|
|
33
63
|
focus: ['--json'],
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
doctor: [],
|
|
41
|
-
watch: [],
|
|
64
|
+
summary: ['--model', '--max-tokens', '--json'],
|
|
65
|
+
diff: ['--stat', '--since', '--summarize', '--model'],
|
|
66
|
+
touch: ['--git'],
|
|
67
|
+
check: ['--fix', '--errors-only', '--no-collapse', '--json', '--verbose'],
|
|
68
|
+
doctor: ['--apply', '--yes', '--dry-run', '--statuses', '--migrate-template',
|
|
69
|
+
'--migrate-prompts', '--frontmatter-fix', '--project', '--json', '--include-archived'],
|
|
42
70
|
lint: ['--fix'],
|
|
71
|
+
ship: [],
|
|
43
72
|
rename: [],
|
|
44
73
|
migrate: [],
|
|
45
74
|
'fix-refs': [],
|
|
46
|
-
summary: ['--model', '--max-tokens', '--json'],
|
|
47
|
-
context: ['--summarize', '--model', '--json'],
|
|
48
|
-
touch: ['--git'],
|
|
49
|
-
journal: ['--tail', '--errors', '--session', '--since', '--by-command', '--json'],
|
|
50
75
|
};
|
|
51
76
|
|
|
52
77
|
function bashCompletion() {
|
package/src/config.mjs
CHANGED
|
@@ -307,8 +307,14 @@ function validateConfig(userConfig, config, validStatuses, indexPath) {
|
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
-
// staleDays keys must exist in validStatuses
|
|
311
|
-
|
|
310
|
+
// staleDays keys must exist in validStatuses — but only validate the map when
|
|
311
|
+
// the user actually wrote `statuses.staleDays`. When it's inherited from
|
|
312
|
+
// defaults (user overrode `statuses.order` but left staleDays unset), the
|
|
313
|
+
// default map is keyed by default statuses (`ready`, `scoping`, …) that the
|
|
314
|
+
// user's order may not include — warning there blames the user for keys they
|
|
315
|
+
// never authored and makes brownfield repos noisy on every command, hook
|
|
316
|
+
// included. Real typos in a user-provided map are still caught.
|
|
317
|
+
if (userConfig.statuses?.staleDays && config.statuses?.staleDays) {
|
|
312
318
|
for (const key of Object.keys(config.statuses.staleDays)) {
|
|
313
319
|
if (!validStatuses.has(key)) {
|
|
314
320
|
warnings.push(`Config: statuses.staleDays contains unknown status '${key}'.`);
|
package/src/health.mjs
CHANGED
|
@@ -87,7 +87,7 @@ export function runHealth(argv, config) {
|
|
|
87
87
|
ready: { count: readyPlans.length },
|
|
88
88
|
planned: { count: plannedPlans.length },
|
|
89
89
|
recentlyArchived: { count: recentlyArchived.length, last30d: recentlyArchived.map(d => path.basename(d.path, '.md')) },
|
|
90
|
-
runlists: { count: runlistHubs.length, hubs: runlistHubs.map(d => ({ path: d.path, title: d.title, status: d.status, childCount: coordination.get(d.path)?.childCount ?? 0 })) },
|
|
90
|
+
runlists: { count: runlistHubs.length, hubs: runlistHubs.map(d => ({ path: d.path, title: d.title, status: d.status, childCount: coordination.get(d.path)?.childCount ?? 0, nextPickup: coordination.get(d.path)?.nextPickup ?? null })) },
|
|
91
91
|
}, null, 2) + '\n');
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
@@ -122,9 +122,10 @@ export function runHealth(argv, config) {
|
|
|
122
122
|
for (const doc of runlistHubs.slice(0, 8)) {
|
|
123
123
|
const slug = hubLabel(doc).padEnd(28);
|
|
124
124
|
const age = doc.daysSinceUpdate != null ? `${doc.daysSinceUpdate}d` : '?d';
|
|
125
|
-
const
|
|
126
|
-
const relStr =
|
|
127
|
-
|
|
125
|
+
const info = coordination.get(doc.path);
|
|
126
|
+
const relStr = info?.childCount ? ` ${dim(`${info.childCount} related`)}` : '';
|
|
127
|
+
const nextStr = info?.nextPickup ? ` ${green('→')} ${info.nextPickup.label}` : '';
|
|
128
|
+
process.stdout.write(` ${slug} ${dim(age.padStart(4))}${relStr}${nextStr}\n`);
|
|
128
129
|
}
|
|
129
130
|
if (runlistHubs.length > 8) {
|
|
130
131
|
process.stdout.write(` ${dim(`...and ${runlistHubs.length - 8} more`)}\n`);
|