dotmd-cli 0.49.3 → 0.49.5
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 +8 -7
- package/bin/dotmd.mjs +17 -12
- package/package.json +1 -1
- package/src/config.mjs +7 -6
- package/src/lifecycle.mjs +43 -19
- package/src/prompts.mjs +22 -14
- package/src/use.mjs +2 -2
- package/src/validate.mjs +5 -0
package/README.md
CHANGED
|
@@ -128,7 +128,7 @@ Every document can have a `type` field in its frontmatter. Types determine which
|
|
|
128
128
|
|------|---------|----------------|
|
|
129
129
|
| `plan` | Execution plans | `in-session`, `active`, `planned`, `blocked`, `partial`, `paused`, `awaiting`, `queued-after`, `archived` |
|
|
130
130
|
| `doc` | Design docs, specs, ADRs, RFCs, reference material | `draft`, `active`, `review`, `reference`, `deprecated`, `archived` |
|
|
131
|
-
| `prompt` | Saved prompts that seed future Claude sessions | `pending`, `shelved`, `claimed`, `archived` |
|
|
131
|
+
| `prompt` | Saved prompts that seed future Claude sessions | `pending`, `held`, `shelved`, `claimed`, `archived` |
|
|
132
132
|
|
|
133
133
|
Documents without a `type` field use the global `statuses.order` from config.
|
|
134
134
|
|
|
@@ -206,7 +206,7 @@ dotmd glossary <term> Look up domain terms + related docs
|
|
|
206
206
|
dotmd watch [command] Re-run a command on file changes
|
|
207
207
|
dotmd diff [file] Show changes since last updated date
|
|
208
208
|
dotmd new <type> <name> Create a new doc (type: doc, plan, or prompt)
|
|
209
|
-
dotmd prompts [sub] Manage saved prompts (list, next, use,
|
|
209
|
+
dotmd prompts [sub] Manage saved prompts (list, next, use, hold, archive, new)
|
|
210
210
|
dotmd journal [flags] View opt-in command-usage journal (DOTMD_JOURNAL=1)
|
|
211
211
|
dotmd init Create starter config + docs directory
|
|
212
212
|
dotmd completions <shell> Output shell completion script (bash, zsh)
|
|
@@ -303,16 +303,17 @@ dotmd prompts # list pending prompts (default)
|
|
|
303
303
|
dotmd prompts list --all # all statuses
|
|
304
304
|
dotmd prompts next # print body of oldest pending + auto-archive (one-shot)
|
|
305
305
|
dotmd prompts use <file> # print body of a specific prompt + auto-archive
|
|
306
|
-
dotmd prompts
|
|
307
|
-
# hidden from hud/briefing, skipped by `next`
|
|
308
|
-
dotmd prompts
|
|
306
|
+
dotmd prompts hold <file> # park a prompt (status → held) under prompts/held/:
|
|
307
|
+
# kept in list, hidden from hud/briefing, skipped by `next`
|
|
308
|
+
dotmd prompts unhold <file> # move a held prompt back to pending
|
|
309
|
+
dotmd prompts shelve <file> # legacy alias for `hold`
|
|
309
310
|
dotmd prompts archive <file> # archive without printing the body
|
|
310
311
|
dotmd prompts new <name> [body] # alias for `dotmd new prompt`
|
|
311
312
|
```
|
|
312
313
|
|
|
313
|
-
`dotmd hud` surfaces pending prompts on session start (alongside held leases), so a saved prompt acts as a self-addressed reminder: write it now, the next session sees it.
|
|
314
|
+
`dotmd hud` surfaces pending prompts on session start (alongside held leases), 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."
|
|
314
315
|
|
|
315
|
-
Statuses: `pending` (drafted, awaiting a session), `
|
|
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.
|
|
316
317
|
|
|
317
318
|
### Command Journal (opt-in)
|
|
318
319
|
|
package/bin/dotmd.mjs
CHANGED
|
@@ -44,7 +44,7 @@ const FLAG_SPECS = {
|
|
|
44
44
|
prompts: {
|
|
45
45
|
flags: new Set(['--json', '--status', '--include-archived', '--sort', '--limit', '--all', '--no-index', '--show-files', '--body', '--message', '--title']),
|
|
46
46
|
values: new Set(['--status', '--sort', '--limit', '--body', '--message', '--title']),
|
|
47
|
-
subcommands: new Set(['list', 'next', 'use', 'resume', 'archive', 'new', 'shelve', 'unshelve', 'status']),
|
|
47
|
+
subcommands: new Set(['list', 'next', 'use', 'resume', 'archive', 'new', 'hold', 'unhold', 'shelve', 'unshelve', 'status']),
|
|
48
48
|
},
|
|
49
49
|
};
|
|
50
50
|
|
|
@@ -65,12 +65,12 @@ const HELP = {
|
|
|
65
65
|
|
|
66
66
|
Common commands:
|
|
67
67
|
plans Live plans (excludes archived)
|
|
68
|
-
prompts Prompt queue/admin (list, next, archive, new,
|
|
68
|
+
prompts Prompt queue/admin (list, next, archive, new, hold)
|
|
69
69
|
briefing Full briefing with plan counts + next steps
|
|
70
70
|
agent-context Compact bounded JSON context for agents
|
|
71
71
|
set <status> [file] Transition status (start work, finish, archive — all via target status)
|
|
72
72
|
new <type> <name> Create plan/doc/prompt (pipe stdin or @path for body)
|
|
73
|
-
use [<file>]
|
|
73
|
+
use [<file-or-prompt-slug>] Open a doc by type: prompt → consume, plan → start, doc → read
|
|
74
74
|
(no file: consume oldest pending prompt)
|
|
75
75
|
archive <file> Close out a plan (status → archived, move, update refs)
|
|
76
76
|
|
|
@@ -95,8 +95,8 @@ View & Query:
|
|
|
95
95
|
focus [status] [--json] Detailed view for one status group
|
|
96
96
|
query [filters] [--json] Filtered search (--status, --keyword, --stale, etc.)
|
|
97
97
|
plans Live plans (excludes archived; --include-archived for all)
|
|
98
|
-
use [<file>]
|
|
99
|
-
prompts [list|archive|new|
|
|
98
|
+
use [<file-or-prompt-slug>] Open a doc by type: prompt → consume, plan → start, doc → read
|
|
99
|
+
prompts [list|archive|new|hold] Prompt admin (list / archive / save / hold). Use \`dotmd use\` to consume.
|
|
100
100
|
stale Stale docs (preset)
|
|
101
101
|
actionable Docs with next steps (preset)
|
|
102
102
|
|
|
@@ -235,9 +235,13 @@ prompt statuses
|
|
|
235
235
|
\`dotmd prompts use <file>\` prints body + archives atomically.
|
|
236
236
|
\`dotmd prompts next\` does the same for the oldest pending.
|
|
237
237
|
|
|
238
|
-
|
|
238
|
+
held Saved under prompts/held/ and hidden from \`hud\` /
|
|
239
|
+
\`briefing\` / \`prompts next\`.
|
|
239
240
|
Still listed by \`dotmd prompts list\`.
|
|
240
|
-
\`dotmd prompts
|
|
241
|
+
\`dotmd prompts unhold <file>\` → pending.
|
|
242
|
+
|
|
243
|
+
shelved Legacy spelling for held prompts. \`dotmd prompts shelve\`
|
|
244
|
+
now writes \`status: held\`.
|
|
241
245
|
|
|
242
246
|
claimed Legacy intermediate state (atomic use → archived now).
|
|
243
247
|
|
|
@@ -920,10 +924,11 @@ Subcommands:
|
|
|
920
924
|
resume <file-or-slug> Alias for \`use\` — same behavior, easier name
|
|
921
925
|
when continuing a session
|
|
922
926
|
archive <file-or-slug> Archive a prompt without printing its body
|
|
923
|
-
|
|
924
|
-
hidden from hud/briefing pending
|
|
925
|
-
by \`prompts next\`.
|
|
926
|
-
|
|
927
|
+
hold <file-or-slug> Park a prompt (status → held) under prompts/held/:
|
|
928
|
+
kept in list, hidden from hud/briefing pending
|
|
929
|
+
surfaces, skipped by \`prompts next\`.
|
|
930
|
+
unhold <file-or-slug> Move a held prompt back to pending.
|
|
931
|
+
shelve / unshelve Legacy aliases for hold / unhold.
|
|
927
932
|
new <slug> [body] Create a new prompt (alias for
|
|
928
933
|
\`dotmd new prompt <slug> [body]\`)
|
|
929
934
|
|
|
@@ -931,7 +936,7 @@ Subcommands:
|
|
|
931
936
|
slug matching a prompt basename, or a unique substring of a prompt
|
|
932
937
|
path. Ambiguous substrings error with the candidate list.
|
|
933
938
|
|
|
934
|
-
Default prompt statuses: pending, shelved, claimed, archived.
|
|
939
|
+
Default prompt statuses: pending, held, shelved, claimed, archived.
|
|
935
940
|
|
|
936
941
|
Examples:
|
|
937
942
|
dotmd prompts # pending prompts (default)
|
package/package.json
CHANGED
package/src/config.mjs
CHANGED
|
@@ -11,6 +11,7 @@ const CONFIG_FILENAMES = ['dotmd.config.mjs', '.dotmd.config.mjs', 'dotmd.config
|
|
|
11
11
|
const REPLACE_KEYS = new Set([
|
|
12
12
|
'statuses.staleDays',
|
|
13
13
|
'statuses.rootStatuses',
|
|
14
|
+
'lifecycle.filedStatuses',
|
|
14
15
|
'presets',
|
|
15
16
|
'context',
|
|
16
17
|
]);
|
|
@@ -36,8 +37,8 @@ const DEFAULTS = {
|
|
|
36
37
|
staleDays: { draft: 30, active: 14, review: 14 },
|
|
37
38
|
},
|
|
38
39
|
prompt: {
|
|
39
|
-
statuses: ['pending', 'shelved', 'claimed', 'archived'],
|
|
40
|
-
context: { expanded: ['pending'], listed: ['shelved'], counted: ['claimed', 'archived'] },
|
|
40
|
+
statuses: ['pending', 'held', 'shelved', 'claimed', 'archived'],
|
|
41
|
+
context: { expanded: ['pending'], listed: ['held', 'shelved'], counted: ['claimed', 'archived'] },
|
|
41
42
|
staleDays: { pending: 30 },
|
|
42
43
|
},
|
|
43
44
|
},
|
|
@@ -58,10 +59,10 @@ const DEFAULTS = {
|
|
|
58
59
|
skipStaleFor: ['archived', 'reference', 'partial', 'queued-after'],
|
|
59
60
|
skipWarningsFor: ['archived', 'partial', 'queued-after'],
|
|
60
61
|
terminalStatuses: ['archived', 'deprecated', 'reference'],
|
|
61
|
-
// F15:
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
filedStatuses: {},
|
|
62
|
+
// F15: per-status filing buckets (status → dirName). Built-in held/paused
|
|
63
|
+
// statuses file under the owning type folder; archive remains a separate
|
|
64
|
+
// primitive untouched.
|
|
65
|
+
filedStatuses: { held: 'held', shelved: 'held', paused: 'held' },
|
|
65
66
|
},
|
|
66
67
|
|
|
67
68
|
taxonomy: {
|
package/src/lifecycle.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { extractFrontmatter, parseSimpleFrontmatter } from './frontmatter.mjs';
|
|
4
|
-
import { asString, toRepoPath, die, warn, resolveDocPath, resolveRefPath, escapeRegex, nowIso, suggestCandidates, emitFilesFooter } from './util.mjs';
|
|
4
|
+
import { asString, toRepoPath, die, warn, resolveDocPath, resolveRefPath, escapeRegex, nowIso, suggestCandidates, emitFilesFooter, isArchivedPath } from './util.mjs';
|
|
5
5
|
import { gitMv, getGitLastModified, getGitLastModifiedBatch } from './git.mjs';
|
|
6
6
|
import { buildIndex, collectDocFiles } from './index.mjs';
|
|
7
7
|
import { renderIndexFile, writeIndex } from './index-file.mjs';
|
|
@@ -26,6 +26,23 @@ function findFileRoot(filePath, config) {
|
|
|
26
26
|
return roots.find(r => filePath.startsWith(r + '/')) ?? config.docsRoot;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function defaultTypeDir(docType, config) {
|
|
30
|
+
if (docType === 'plan') return 'plans';
|
|
31
|
+
if (docType === 'prompt') return 'prompts';
|
|
32
|
+
const templateDir = config.raw?.templates?.[docType]?.dir;
|
|
33
|
+
return typeof templateDir === 'string' && templateDir ? templateDir : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function findFilingRoot(filePath, fileRoot, docType, config) {
|
|
37
|
+
const dirName = defaultTypeDir(docType, config);
|
|
38
|
+
if (!dirName) return fileRoot;
|
|
39
|
+
if (path.basename(fileRoot) === dirName) return fileRoot;
|
|
40
|
+
|
|
41
|
+
const relSegments = path.relative(fileRoot, filePath).split(path.sep);
|
|
42
|
+
if (relSegments[0] === dirName) return path.join(fileRoot, dirName);
|
|
43
|
+
return fileRoot;
|
|
44
|
+
}
|
|
45
|
+
|
|
29
46
|
// Best-effort index regen for any doc-set or doc-status mutation. The
|
|
30
47
|
// generated block groups by status and embeds per-doc snapshots, so any
|
|
31
48
|
// change that affects what would render leaves the index stale. Wrapped
|
|
@@ -167,9 +184,10 @@ export async function runStatus(argv, config, opts = {}) {
|
|
|
167
184
|
|
|
168
185
|
const today = nowIso();
|
|
169
186
|
const archiveDir = path.join(fileRoot, config.archiveDir);
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const
|
|
187
|
+
const filingRoot = findFilingRoot(filePath, fileRoot, docType, config);
|
|
188
|
+
const relFromFilingRoot = path.relative(filingRoot, filePath);
|
|
189
|
+
const relSegments = relFromFilingRoot.split(path.sep);
|
|
190
|
+
const inArchive = isArchivedPath(toRepoPath(filePath, config.repoRoot), config);
|
|
173
191
|
const isArchiving = config.lifecycle.archiveStatuses.has(newStatus) && !inArchive;
|
|
174
192
|
const isUnarchiving = !config.lifecycle.archiveStatuses.has(newStatus) && inArchive;
|
|
175
193
|
|
|
@@ -200,12 +218,12 @@ export async function runStatus(argv, config, opts = {}) {
|
|
|
200
218
|
finalPath = targetPath;
|
|
201
219
|
}
|
|
202
220
|
if (isFiling) {
|
|
203
|
-
const targetPath = path.join(
|
|
221
|
+
const targetPath = path.join(filingRoot, newFiledDir, path.basename(filePath));
|
|
204
222
|
process.stdout.write(`${prefix} Would file: ${toRepoPath(filePath, config.repoRoot)} → ${toRepoPath(targetPath, config.repoRoot)}\n`);
|
|
205
223
|
finalPath = targetPath;
|
|
206
224
|
}
|
|
207
225
|
if (isUnfiling) {
|
|
208
|
-
const targetPath = path.join(
|
|
226
|
+
const targetPath = path.join(filingRoot, path.basename(filePath));
|
|
209
227
|
process.stdout.write(`${prefix} Would unfile: ${toRepoPath(filePath, config.repoRoot)} → ${toRepoPath(targetPath, config.repoRoot)}\n`);
|
|
210
228
|
finalPath = targetPath;
|
|
211
229
|
}
|
|
@@ -236,7 +254,7 @@ export async function runStatus(argv, config, opts = {}) {
|
|
|
236
254
|
}
|
|
237
255
|
|
|
238
256
|
if (isFiling) {
|
|
239
|
-
const targetDir = path.join(
|
|
257
|
+
const targetDir = path.join(filingRoot, newFiledDir);
|
|
240
258
|
mkdirSync(targetDir, { recursive: true });
|
|
241
259
|
const targetPath = path.join(targetDir, path.basename(filePath));
|
|
242
260
|
if (existsSync(targetPath)) { die(`Target already exists: ${toRepoPath(targetPath, config.repoRoot)}`); }
|
|
@@ -246,7 +264,7 @@ export async function runStatus(argv, config, opts = {}) {
|
|
|
246
264
|
}
|
|
247
265
|
|
|
248
266
|
if (isUnfiling) {
|
|
249
|
-
const targetPath = path.join(
|
|
267
|
+
const targetPath = path.join(filingRoot, path.basename(filePath));
|
|
250
268
|
if (existsSync(targetPath)) { die(`Target already exists: ${toRepoPath(targetPath, config.repoRoot)}`); }
|
|
251
269
|
const result = gitMv(filePath, targetPath, config.repoRoot);
|
|
252
270
|
if (result.status !== 0) { die(result.stderr || 'git mv failed.'); }
|
|
@@ -620,8 +638,15 @@ export function runArchive(argv, config, opts = {}) {
|
|
|
620
638
|
appendVersionHistory(filePath, `Archived (frontmatter healed in place from \`${oldStatus}\`).`);
|
|
621
639
|
if (!noIndex) regenIndex(config);
|
|
622
640
|
out.write(`${green('✓ Healed')}: ${repoPathHeal} (${oldStatus} → archived; file already under \`${config.archiveDir}/\`)\n`);
|
|
623
|
-
|
|
624
|
-
|
|
641
|
+
const touched = [repoPathHeal];
|
|
642
|
+
if (config.indexPath && !noIndex) touched.push(config.indexPath);
|
|
643
|
+
if (showFiles) emitFilesFooter(touched, config);
|
|
644
|
+
return {
|
|
645
|
+
action: 'healed',
|
|
646
|
+
oldRepoPath: repoPathHeal,
|
|
647
|
+
newRepoPath: repoPathHeal,
|
|
648
|
+
touched,
|
|
649
|
+
};
|
|
625
650
|
}
|
|
626
651
|
|
|
627
652
|
const closeoutAction = closeoutTemplate ? planCloseoutInjection(body) : null;
|
|
@@ -701,7 +726,12 @@ export function runArchive(argv, config, opts = {}) {
|
|
|
701
726
|
|
|
702
727
|
try { config.hooks.onArchive?.({ path: newRepoPath, oldStatus }, { oldPath: oldRepoPath, newPath: newRepoPath }); } catch (err) { warn(`Hook 'onArchive' threw: ${err.message}`); }
|
|
703
728
|
|
|
704
|
-
return {
|
|
729
|
+
return {
|
|
730
|
+
action: 'archived',
|
|
731
|
+
oldRepoPath,
|
|
732
|
+
newRepoPath,
|
|
733
|
+
touched,
|
|
734
|
+
};
|
|
705
735
|
}
|
|
706
736
|
|
|
707
737
|
// Unified status-transition verb. Collapses status/archive/release into one
|
|
@@ -763,9 +793,7 @@ export async function runSet(argv, config, opts = {}) {
|
|
|
763
793
|
const parsedFm = parseSimpleFrontmatter(fmRaw);
|
|
764
794
|
const oldStatus = asString(parsedFm.status);
|
|
765
795
|
|
|
766
|
-
const
|
|
767
|
-
const relFromRoot = path.relative(fileRoot, filePath);
|
|
768
|
-
const inArchive = relFromRoot.startsWith(config.archiveDir + '/') || relFromRoot.startsWith(config.archiveDir + path.sep);
|
|
796
|
+
const inArchive = isArchivedPath(toRepoPath(filePath, config.repoRoot), config);
|
|
769
797
|
|
|
770
798
|
if (config.lifecycle.archiveStatuses.has(newStatus) && !inArchive) {
|
|
771
799
|
const archiveArgs = [filePath];
|
|
@@ -807,11 +835,7 @@ export function runBulkArchive(argv, config, opts = {}) {
|
|
|
807
835
|
}
|
|
808
836
|
}
|
|
809
837
|
|
|
810
|
-
const unique = [...new Set(matched)].filter(f =>
|
|
811
|
-
const root = findFileRoot(f, config);
|
|
812
|
-
const rel = path.relative(root, f);
|
|
813
|
-
return !rel.startsWith(config.archiveDir + '/') && !rel.startsWith(config.archiveDir + path.sep);
|
|
814
|
-
});
|
|
838
|
+
const unique = [...new Set(matched)].filter(f => !isArchivedPath(toRepoPath(f, config.repoRoot), config));
|
|
815
839
|
if (unique.length === 0) die('No matching files found (already-archived files are excluded).');
|
|
816
840
|
|
|
817
841
|
process.stdout.write(`${unique.length} file(s) to archive:\n`);
|
package/src/prompts.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { green, dim } from './color.mjs';
|
|
|
11
11
|
// `resume` is an alias for `use` — agents reach for "resume" when continuing a
|
|
12
12
|
// session; `use` reads as internal mechanics. Both names stay valid; the
|
|
13
13
|
// canonical output ("Consumed: …") is unchanged.
|
|
14
|
-
const SUBCOMMANDS = new Set(['list', 'next', 'use', 'resume', 'archive', 'new', 'shelve', 'unshelve']);
|
|
14
|
+
const SUBCOMMANDS = new Set(['list', 'next', 'use', 'resume', 'archive', 'new', 'hold', 'unhold', 'shelve', 'unshelve']);
|
|
15
15
|
|
|
16
16
|
export async function runPrompts(argv, config, opts = {}) {
|
|
17
17
|
const sub = argv[0];
|
|
@@ -28,8 +28,10 @@ export async function runPrompts(argv, config, opts = {}) {
|
|
|
28
28
|
case 'resume': return runPromptsUse(rest, config, opts);
|
|
29
29
|
case 'archive': return runPromptsArchive(rest, config, opts);
|
|
30
30
|
case 'new': return runPromptsNew(rest, config, opts);
|
|
31
|
-
case '
|
|
32
|
-
case '
|
|
31
|
+
case 'hold': return runPromptsHold(rest, config, opts);
|
|
32
|
+
case 'unhold': return runPromptsUnhold(rest, config, opts);
|
|
33
|
+
case 'shelve': return runPromptsHold(rest, config, opts);
|
|
34
|
+
case 'unshelve': return runPromptsUnhold(rest, config, opts);
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
|
|
@@ -190,7 +192,8 @@ function runPromptsNext(argv, config, opts = {}) {
|
|
|
190
192
|
// path + '.md', exact basename match across type: prompt docs, substring
|
|
191
193
|
// match across type: prompt docs. Returns the absolute path or dies with a
|
|
192
194
|
// helpful message (no match / ambiguous match).
|
|
193
|
-
function resolvePromptInput(input, config) {
|
|
195
|
+
export function resolvePromptInput(input, config, options = {}) {
|
|
196
|
+
const dieOnMiss = options.dieOnMiss !== false;
|
|
194
197
|
const direct = resolveDocPath(input, config);
|
|
195
198
|
if (direct) return direct;
|
|
196
199
|
|
|
@@ -201,7 +204,10 @@ function resolvePromptInput(input, config) {
|
|
|
201
204
|
|
|
202
205
|
const index = buildIndex(config);
|
|
203
206
|
const prompts = index.docs.filter(d => d.type === 'prompt');
|
|
204
|
-
if (prompts.length === 0)
|
|
207
|
+
if (prompts.length === 0) {
|
|
208
|
+
if (dieOnMiss) die(`No prompts in the index.`);
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
205
211
|
|
|
206
212
|
const slug = input.replace(/\.md$/, '');
|
|
207
213
|
|
|
@@ -219,7 +225,8 @@ function resolvePromptInput(input, config) {
|
|
|
219
225
|
die(`Multiple prompts match "${input}":\n${bySubstring.map(d => ' ' + d.path).join('\n')}`);
|
|
220
226
|
}
|
|
221
227
|
|
|
222
|
-
die(`No prompt found matching: ${input}`);
|
|
228
|
+
if (dieOnMiss) die(`No prompt found matching: ${input}`);
|
|
229
|
+
return null;
|
|
223
230
|
}
|
|
224
231
|
|
|
225
232
|
function runPromptsUse(argv, config, opts = {}) {
|
|
@@ -243,7 +250,7 @@ export function consumePrompt(filePath, config, opts) {
|
|
|
243
250
|
if (docType !== 'prompt') {
|
|
244
251
|
die(`Not a prompt (type: ${docType ?? 'unknown'}): ${repoPath}`);
|
|
245
252
|
}
|
|
246
|
-
if (status === 'archived') {
|
|
253
|
+
if (status === 'archived' || isArchivedPath(repoPath, config)) {
|
|
247
254
|
die(`Already consumed: ${repoPath}`);
|
|
248
255
|
}
|
|
249
256
|
|
|
@@ -267,12 +274,13 @@ export function consumePrompt(filePath, config, opts) {
|
|
|
267
274
|
// ever being archived, and the next session sees the same prompt as pending.
|
|
268
275
|
// Body is already in memory from extractFrontmatter, so the source file
|
|
269
276
|
// can move out from under us safely.
|
|
270
|
-
runArchive([filePath], config, { noIndex, showFiles, out: process.stderr });
|
|
277
|
+
const archiveResult = runArchive([filePath], config, { noIndex, showFiles, out: process.stderr });
|
|
271
278
|
|
|
272
279
|
process.stdout.write(body);
|
|
273
280
|
if (!body.endsWith('\n')) process.stdout.write('\n');
|
|
274
281
|
|
|
275
|
-
|
|
282
|
+
const consumedPath = archiveResult?.newRepoPath ?? repoPath;
|
|
283
|
+
process.stderr.write(`${green('✓ Consumed')}: ${consumedPath}\n`);
|
|
276
284
|
}
|
|
277
285
|
|
|
278
286
|
function runPromptsArchive(argv, config, opts = {}) {
|
|
@@ -299,16 +307,16 @@ async function runPromptsNew(argv, config, opts = {}) {
|
|
|
299
307
|
return runNew(['prompt', ...argv], config, opts);
|
|
300
308
|
}
|
|
301
309
|
|
|
302
|
-
async function
|
|
310
|
+
async function runPromptsHold(argv, config, opts = {}) {
|
|
303
311
|
const input = argv.find(a => !a.startsWith('-'));
|
|
304
|
-
if (!input) die('Usage: dotmd prompts
|
|
312
|
+
if (!input) die('Usage: dotmd prompts hold <file-or-slug>');
|
|
305
313
|
const filePath = resolvePromptInput(input, config);
|
|
306
|
-
return runStatus([filePath, '
|
|
314
|
+
return runStatus([filePath, 'held'], config, opts);
|
|
307
315
|
}
|
|
308
316
|
|
|
309
|
-
async function
|
|
317
|
+
async function runPromptsUnhold(argv, config, opts = {}) {
|
|
310
318
|
const input = argv.find(a => !a.startsWith('-'));
|
|
311
|
-
if (!input) die('Usage: dotmd prompts
|
|
319
|
+
if (!input) die('Usage: dotmd prompts unhold <file-or-slug>');
|
|
312
320
|
const filePath = resolvePromptInput(input, config);
|
|
313
321
|
return runStatus([filePath, 'pending'], config, opts);
|
|
314
322
|
}
|
package/src/use.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { extractFrontmatter, parseSimpleFrontmatter } from './frontmatter.mjs';
|
|
3
3
|
import { asString, die, resolveDocPath, toRepoPath } from './util.mjs';
|
|
4
|
-
import { consumePrompt, pendingPromptsOldestFirst } from './prompts.mjs';
|
|
4
|
+
import { consumePrompt, pendingPromptsOldestFirst, resolvePromptInput } from './prompts.mjs';
|
|
5
5
|
import { runPickup } from './lifecycle.mjs';
|
|
6
6
|
|
|
7
7
|
// Top-level `dotmd use [file]` — the single "start engaging with this doc"
|
|
@@ -22,7 +22,7 @@ export async function runUse(argv, config, opts = {}) {
|
|
|
22
22
|
return consumePrompt(head.abs, config, opts);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const filePath = resolveDocPath(positional, config);
|
|
25
|
+
const filePath = resolveDocPath(positional, config) ?? resolvePromptInput(positional, config, { dieOnMiss: false });
|
|
26
26
|
if (!filePath) die(`File not found: ${positional}`);
|
|
27
27
|
|
|
28
28
|
const raw = readFileSync(filePath, 'utf8');
|
package/src/validate.mjs
CHANGED
|
@@ -49,6 +49,11 @@ function liveTypeDirsForRoots(config) {
|
|
|
49
49
|
for (const dirName of filedDirs) {
|
|
50
50
|
if (path.basename(rootRel) === dirName) continue;
|
|
51
51
|
set.add(rootRel ? `${rootRel}/${dirName}` : dirName);
|
|
52
|
+
for (const typeDir of BUILTIN_TYPE_DIR_NAMES) {
|
|
53
|
+
if (path.basename(rootRel) === typeDir) continue;
|
|
54
|
+
const typeRoot = rootRel ? `${rootRel}/${typeDir}` : typeDir;
|
|
55
|
+
set.add(`${typeRoot}/${dirName}`);
|
|
56
|
+
}
|
|
52
57
|
}
|
|
53
58
|
}
|
|
54
59
|
return set;
|