dotmd-cli 0.39.0 → 0.39.2
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/package.json +1 -1
- package/src/claude-commands.mjs +9 -5
- package/src/new.mjs +59 -10
package/package.json
CHANGED
package/src/claude-commands.mjs
CHANGED
|
@@ -20,7 +20,7 @@ const VERSION_REGEX = /<!-- dotmd-generated: ([\d.]+) -->/;
|
|
|
20
20
|
const SLASH_DESCRIPTIONS = {
|
|
21
21
|
plans: "dotmd-managed plan briefing for this repo. Use when the user asks what's on the plate, references a plan slug, queues work, or wants to pick up / release / archive a plan.",
|
|
22
22
|
docs: "dotmd-managed docs briefing for this repo. Use when the user asks to list, scaffold, query, validate, archive, or rename non-plan docs (reference docs, ADRs, RFCs, design notes), or asks how the dotmd doc lifecycle works here.",
|
|
23
|
-
baton: "
|
|
23
|
+
baton: "Save a resume prompt for the held plan and release the lease — the minimum handoff. Use when the user says hand off / save a resume / wrap up, or when context is getting tight.",
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
function frontmatterFor(name) {
|
|
@@ -67,13 +67,17 @@ function generatePlansCommand(config) {
|
|
|
67
67
|
|
|
68
68
|
function generateBatonCommand() {
|
|
69
69
|
const lines = [...frontmatterFor('baton'), VERSION_MARKER, ''];
|
|
70
|
-
lines.push('
|
|
70
|
+
lines.push('Wrap this session. Minimum required (two commands):');
|
|
71
71
|
lines.push('');
|
|
72
|
-
lines.push('1. **
|
|
72
|
+
lines.push('1. **Save the resume prompt.** `dotmd new prompt resume-<plan-slug>` with a 10-20 line body via heredoc: the next concrete decision plus any gotchas. NOT a recap of the plan body. The saved prompt IS the handoff — never print it into chat for copy-paste.');
|
|
73
73
|
lines.push('');
|
|
74
|
-
lines.push('2. **
|
|
74
|
+
lines.push('2. **Release the lease.** `dotmd release` — or `dotmd archive <file>` if the work is fully shipped (archive auto-releases).');
|
|
75
75
|
lines.push('');
|
|
76
|
-
lines.push('
|
|
76
|
+
lines.push('Optional, only when genuinely needed:');
|
|
77
|
+
lines.push('- Status really changed (paused / awaiting / partial / blocked): `dotmd status <file> <status>` BEFORE `dotmd release`.');
|
|
78
|
+
lines.push('- Plan dashboard text is misleading the user: edit `next_step:` in the plan frontmatter. Cosmetic — the next session reads the resume prompt, not the plan frontmatter.');
|
|
79
|
+
lines.push('');
|
|
80
|
+
lines.push('If you don\'t already know which plan you hold: `dotmd hud --json` and read `.owned`. Do NOT use `dotmd plans --status in-session` — that lists every session\'s holdings, not just yours.');
|
|
77
81
|
lines.push('');
|
|
78
82
|
lines.push('The next session\'s `dotmd hud` (SessionStart hook) surfaces the pending prompt automatically.');
|
|
79
83
|
lines.push('');
|
package/src/new.mjs
CHANGED
|
@@ -265,17 +265,37 @@ export async function runNew(argv, config, opts = {}) {
|
|
|
265
265
|
|
|
266
266
|
// Fail-fast when the user passes body input to a template that doesn't
|
|
267
267
|
// consume it — silently discarding heredoc content is the worst UX.
|
|
268
|
-
// Templates opt in via `acceptsBody: true` or `requiresBody: true`.
|
|
269
|
-
// built-in templates (doc, plan, prompt) accept body; this guard fires
|
|
270
|
-
// only for custom templates that opt out.
|
|
268
|
+
// Templates opt in via `acceptsBody: true` or `requiresBody: true`.
|
|
271
269
|
if (bodyInput !== null && !template.acceptsBody && !template.requiresBody) {
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
270
|
+
const configTemplates = config.raw?.templates ?? {};
|
|
271
|
+
// Compute the accepting list from the RESOLVED set (config merged over
|
|
272
|
+
// built-ins) so the hint doesn't contradict the rejection.
|
|
273
|
+
const resolvedNames = new Set([...Object.keys(BUILTIN_TEMPLATES), ...Object.keys(configTemplates)]);
|
|
274
|
+
const accepting = [...resolvedNames]
|
|
275
|
+
.filter(n => n !== typeName)
|
|
276
|
+
.filter(n => {
|
|
277
|
+
const t = resolveTemplate(n, config);
|
|
278
|
+
return t.acceptsBody || t.requiresBody;
|
|
279
|
+
});
|
|
275
280
|
const hint = accepting.length > 0
|
|
276
281
|
? ` Templates that accept body input: ${accepting.join(', ')}.`
|
|
277
282
|
: '';
|
|
278
|
-
|
|
283
|
+
|
|
284
|
+
// Override-of-builtin diagnosis: the most common cause is a project
|
|
285
|
+
// dotmd.config.mjs that copy-pasted a stripped-down `plan` template
|
|
286
|
+
// and dropped the body-acceptance contract. Name that explicitly so
|
|
287
|
+
// an agent can self-fix without spelunking the config.
|
|
288
|
+
const builtin = BUILTIN_TEMPLATES[typeName];
|
|
289
|
+
const isOverride = Boolean(configTemplates[typeName] && builtin);
|
|
290
|
+
const builtinAccepts = Boolean(builtin && (builtin.acceptsBody || builtin.requiresBody));
|
|
291
|
+
let cause;
|
|
292
|
+
if (isOverride && builtinAccepts) {
|
|
293
|
+
const where = config.configPath ? toRepoPath(config.configPath, config.repoRoot) : 'dotmd.config.mjs';
|
|
294
|
+
cause = `Your config (${where}) overrides the built-in \`${typeName}\` template, and the override drops body acceptance.\nFix: in that override, add \`acceptsBody: true\` AND interpolate \`\${ctx?.bodyInput?.trim() ?? ''}\` into your \`body\` fn (e.g., inside \`## Problem\`). Or drop the override to use the built-in.`;
|
|
295
|
+
} else {
|
|
296
|
+
cause = `Either drop the body, switch to a template that accepts it, or set \`acceptsBody: true\` on your custom \`${typeName}\` template in dotmd.config.mjs.`;
|
|
297
|
+
}
|
|
298
|
+
die(`\`${typeName}\` template does not accept body input, but body was passed via ${bodyInputSource}.${hint}\n${cause}`);
|
|
279
299
|
}
|
|
280
300
|
|
|
281
301
|
// If name contains path separators, split into directory prefix and basename
|
|
@@ -379,10 +399,39 @@ export async function runNew(argv, config, opts = {}) {
|
|
|
379
399
|
}
|
|
380
400
|
|
|
381
401
|
function resolveTemplate(name, config) {
|
|
382
|
-
// Config templates take priority
|
|
383
402
|
const configTemplates = config.raw?.templates ?? {};
|
|
384
|
-
|
|
385
|
-
|
|
403
|
+
const override = configTemplates[name];
|
|
404
|
+
const builtin = BUILTIN_TEMPLATES[name];
|
|
405
|
+
|
|
406
|
+
if (override) {
|
|
407
|
+
if (!builtin) return { ...override, _overridesBuiltin: false };
|
|
408
|
+
// Partial-override DX: shallow-merge built-in under override so missing
|
|
409
|
+
// fields (description, dir, targetRoot, defaultStatus, frontmatter, body,
|
|
410
|
+
// acceptsBody, requiresBody) fall back to the built-in. Anything the
|
|
411
|
+
// override explicitly declares wins.
|
|
412
|
+
const merged = { ...builtin, ...override, _overridesBuiltin: true };
|
|
413
|
+
|
|
414
|
+
// Body-loss guard: if the override supplies its OWN body fn but doesn't
|
|
415
|
+
// explicitly opt in to body acceptance, the inherited built-in
|
|
416
|
+
// `acceptsBody`/`requiresBody` could let body input flow into a custom
|
|
417
|
+
// body fn that doesn't honor `ctx.bodyInput` — silently discarding the
|
|
418
|
+
// heredoc, the worst-UX bug fix #9 was added to prevent. Strip the
|
|
419
|
+
// inherited flags so the fail-fast guard fires. EXCEPT when the custom
|
|
420
|
+
// body fn references `bodyInput` itself, in which case it's clearly
|
|
421
|
+
// body-aware and inheriting acceptsBody is the agent-first move.
|
|
422
|
+
const overrodeBody = typeof override.body === 'function';
|
|
423
|
+
const declaredAcceptance = override.acceptsBody !== undefined || override.requiresBody !== undefined;
|
|
424
|
+
if (overrodeBody && !declaredAcceptance) {
|
|
425
|
+
const bodyAware = /bodyInput/.test(override.body.toString());
|
|
426
|
+
if (!bodyAware) {
|
|
427
|
+
merged.acceptsBody = undefined;
|
|
428
|
+
merged.requiresBody = undefined;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return merged;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (builtin) return builtin;
|
|
386
435
|
|
|
387
436
|
const available = [...new Set([...Object.keys(BUILTIN_TEMPLATES), ...Object.keys(configTemplates)])];
|
|
388
437
|
die(`Unknown type: ${name}\nAvailable: ${available.join(', ')}`);
|