dotmd-cli 0.57.0 → 0.58.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 +7 -0
- package/dotmd.config.example.mjs +3 -0
- package/package.json +1 -1
- package/src/config.mjs +10 -1
- package/src/guard.mjs +10 -4
- package/src/lifecycle.mjs +20 -4
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ If you drive dotmd from Claude Code, install the **dotmd plugin**. It teaches ev
|
|
|
23
23
|
|
|
24
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. It calls the `dotmd` CLI, so keep `npm install -g dotmd-cli` installed too. (Source: `plugins/dotmd/` in this repo.)
|
|
25
25
|
|
|
26
|
+
> **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
|
+
|
|
26
28
|
## Quick Start
|
|
27
29
|
|
|
28
30
|
```bash
|
|
@@ -803,6 +805,11 @@ export const lifecycle = {
|
|
|
803
805
|
skipStaleFor: ['archived'],
|
|
804
806
|
skipWarningsFor: ['archived'],
|
|
805
807
|
terminalStatuses: ['archived'],
|
|
808
|
+
// Types that archive into their own <typeDir>/<archiveDir> (e.g.
|
|
809
|
+
// docs/prompts/archived/) instead of the shared <root>/<archiveDir>.
|
|
810
|
+
// Defaults to ['prompt'] so session-local prompt churn doesn't bury
|
|
811
|
+
// plans and docs in the shared archive. Set to [] to disable.
|
|
812
|
+
archiveNestedTypes: ['prompt'],
|
|
806
813
|
};
|
|
807
814
|
|
|
808
815
|
export const taxonomy = {
|
package/dotmd.config.example.mjs
CHANGED
|
@@ -130,6 +130,9 @@ export const statuses = {
|
|
|
130
130
|
// skipStaleFor: ['archived'], // skip staleness checks
|
|
131
131
|
// skipWarningsFor: ['archived'], // skip validation warnings (summary, etc.)
|
|
132
132
|
// terminalStatuses: ['archived', 'deprecated', 'reference'], // skip current_state/next_step warnings, exclude from stats scope
|
|
133
|
+
// archiveNestedTypes: ['prompt'], // types that archive under <typeDir>/<archiveDir>
|
|
134
|
+
// // (e.g. docs/prompts/archived/) instead of the
|
|
135
|
+
// // shared <root>/<archiveDir>; set [] to disable
|
|
133
136
|
// };
|
|
134
137
|
|
|
135
138
|
// Taxonomy validation — set fields to null to skip validation.
|
package/package.json
CHANGED
package/src/config.mjs
CHANGED
|
@@ -63,6 +63,12 @@ const DEFAULTS = {
|
|
|
63
63
|
// statuses file under the owning type folder; archive remains a separate
|
|
64
64
|
// primitive untouched.
|
|
65
65
|
filedStatuses: { held: 'held', shelved: 'held', paused: 'held' },
|
|
66
|
+
// Types whose archive nests under their own type dir (<typeDir>/<archiveDir>,
|
|
67
|
+
// e.g. docs/prompts/archived/) instead of the shared <root>/<archiveDir>.
|
|
68
|
+
// Prompts are session-local churn — keeping their archive out of the shared
|
|
69
|
+
// docs/archived/ stops them from burying plans/docs there. Set to [] to send
|
|
70
|
+
// every type to the shared archive.
|
|
71
|
+
archiveNestedTypes: ['prompt'],
|
|
66
72
|
},
|
|
67
73
|
|
|
68
74
|
taxonomy: {
|
|
@@ -476,6 +482,9 @@ export async function resolveConfig(cwd, explicitConfigPath) {
|
|
|
476
482
|
// F15: filedStatuses keyed by status name, value = directory name. Empty
|
|
477
483
|
// object when no status opts in via `filed: true` (or `filed: '<dirname>'`).
|
|
478
484
|
const filedStatuses = new Map(Object.entries(lifecycle.filedStatuses ?? {}));
|
|
485
|
+
// Types that archive into their own <typeDir>/<archiveDir> rather than the
|
|
486
|
+
// shared <root>/<archiveDir> (default: prompt).
|
|
487
|
+
const archiveNestedTypes = new Set(lifecycle.archiveNestedTypes ?? []);
|
|
479
488
|
|
|
480
489
|
// Warn if rootStatuses keys don't match any configured root
|
|
481
490
|
for (const rootKey of Object.keys(rootStatusesRaw)) {
|
|
@@ -507,7 +516,7 @@ export async function resolveConfig(cwd, explicitConfigPath) {
|
|
|
507
516
|
rootValidStatuses,
|
|
508
517
|
staleDaysByStatus,
|
|
509
518
|
|
|
510
|
-
lifecycle: { archiveStatuses, skipStaleFor, skipWarningsFor, terminalStatuses, filedStatuses },
|
|
519
|
+
lifecycle: { archiveStatuses, skipStaleFor, skipWarningsFor, terminalStatuses, filedStatuses, archiveNestedTypes },
|
|
511
520
|
|
|
512
521
|
validSurfaces,
|
|
513
522
|
validModules,
|
package/src/guard.mjs
CHANGED
|
@@ -25,11 +25,17 @@ const pkg = JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json'),
|
|
|
25
25
|
|
|
26
26
|
const SHELL_READERS = new Set(['cat', 'less', 'more', 'head', 'tail', 'bat', 'view', 'open']);
|
|
27
27
|
|
|
28
|
-
// A path that ends in .md and sits under a `prompts/` directory is a
|
|
29
|
-
// prompt regardless of which doc root it belongs to —
|
|
30
|
-
// without needing the resolved config.
|
|
28
|
+
// A path that ends in .md and sits under a `prompts/` directory is a
|
|
29
|
+
// session-local saved prompt regardless of which doc root it belongs to —
|
|
30
|
+
// robust across repos without needing the resolved config. Archived prompts
|
|
31
|
+
// (`…/prompts/archived/…`, the default nested archive for the prompt type) are
|
|
32
|
+
// committable history, NOT session-local, so they're explicitly excluded — the
|
|
33
|
+
// guard must not block committing or reading them.
|
|
31
34
|
function isPromptPath(p) {
|
|
32
|
-
|
|
35
|
+
if (typeof p !== 'string' || !p.endsWith('.md')) return false;
|
|
36
|
+
if (!/(^|\/)prompts\//.test(p)) return false;
|
|
37
|
+
if (/(^|\/)archived\//.test(p)) return false;
|
|
38
|
+
return true;
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
// Loose "is this a dotmd-managed doc" test: a .md file under one of the
|
package/src/lifecycle.mjs
CHANGED
|
@@ -32,6 +32,17 @@ function findFilingRoot(filePath, fileRoot, docType, config) {
|
|
|
32
32
|
return fileRoot;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// Base directory a doc archives under. Types in lifecycle.archiveNestedTypes
|
|
36
|
+
// (default: prompt) archive into their own <typeDir>/ — yielding
|
|
37
|
+
// <typeDir>/<archiveDir> (e.g. docs/prompts/archived/) — so session-local
|
|
38
|
+
// prompt churn doesn't bury plans/docs in the shared <root>/<archiveDir>.
|
|
39
|
+
// Everything else archives under fileRoot. Used by both runStatus (set
|
|
40
|
+
// archived) and runArchive so the two paths stay in lockstep.
|
|
41
|
+
function archiveBaseFor(filePath, fileRoot, docType, config) {
|
|
42
|
+
const nest = config.lifecycle?.archiveNestedTypes?.has(docType) ?? false;
|
|
43
|
+
return nest ? findFilingRoot(filePath, fileRoot, docType, config) : fileRoot;
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
// Best-effort index regen for any doc-set or doc-status mutation. The
|
|
36
47
|
// generated block groups by status and embeds per-doc snapshots, so any
|
|
37
48
|
// change that affects what would render leaves the index stale. Wrapped
|
|
@@ -172,8 +183,11 @@ export async function runStatus(argv, config, opts = {}) {
|
|
|
172
183
|
}
|
|
173
184
|
|
|
174
185
|
const today = nowIso();
|
|
175
|
-
const archiveDir = path.join(fileRoot, config.archiveDir);
|
|
176
186
|
const filingRoot = findFilingRoot(filePath, fileRoot, docType, config);
|
|
187
|
+
// Type-aware archive base (prompts nest under their type dir by default);
|
|
188
|
+
// unarchive reuses archiveBase so a prompt restores to its type dir.
|
|
189
|
+
const archiveBase = archiveBaseFor(filePath, fileRoot, docType, config);
|
|
190
|
+
const archiveDir = path.join(archiveBase, config.archiveDir);
|
|
177
191
|
const relFromFilingRoot = path.relative(filingRoot, filePath);
|
|
178
192
|
const relSegments = relFromFilingRoot.split(path.sep);
|
|
179
193
|
const inArchive = isArchivedPath(toRepoPath(filePath, config.repoRoot), config);
|
|
@@ -202,7 +216,7 @@ export async function runStatus(argv, config, opts = {}) {
|
|
|
202
216
|
finalPath = targetPath;
|
|
203
217
|
}
|
|
204
218
|
if (isUnarchiving) {
|
|
205
|
-
const targetPath = path.join(
|
|
219
|
+
const targetPath = path.join(archiveBase, path.basename(filePath));
|
|
206
220
|
process.stdout.write(`${prefix} Would move: ${toRepoPath(filePath, config.repoRoot)} → ${toRepoPath(targetPath, config.repoRoot)}\n`);
|
|
207
221
|
finalPath = targetPath;
|
|
208
222
|
}
|
|
@@ -235,7 +249,7 @@ export async function runStatus(argv, config, opts = {}) {
|
|
|
235
249
|
}
|
|
236
250
|
|
|
237
251
|
if (isUnarchiving) {
|
|
238
|
-
const targetPath = path.join(
|
|
252
|
+
const targetPath = path.join(archiveBase, path.basename(filePath));
|
|
239
253
|
if (existsSync(targetPath)) { die(`Target already exists: ${toRepoPath(targetPath, config.repoRoot)}`); }
|
|
240
254
|
const result = gitMv(filePath, targetPath, config.repoRoot);
|
|
241
255
|
if (result.status !== 0) { die(result.stderr || 'git mv failed.'); }
|
|
@@ -436,7 +450,9 @@ export function runArchive(argv, config, opts = {}) {
|
|
|
436
450
|
const closeoutAction = closeoutTemplate ? planCloseoutInjection(body) : null;
|
|
437
451
|
|
|
438
452
|
const today = nowIso();
|
|
439
|
-
|
|
453
|
+
// Type-aware: prompts archive under docs/prompts/archived/ by default (see
|
|
454
|
+
// archiveBaseFor); plans/docs keep the shared <root>/archived/.
|
|
455
|
+
const targetDir = path.join(archiveBaseFor(filePath, archiveFileRoot, asString(parsed.type), config), config.archiveDir);
|
|
440
456
|
const targetPath = uniqueArchiveTarget(targetDir, path.basename(filePath));
|
|
441
457
|
const oldRepoPath = toRepoPath(filePath, config.repoRoot);
|
|
442
458
|
const newRepoPath = toRepoPath(targetPath, config.repoRoot);
|