dotmd-cli 0.39.1 → 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/new.mjs +59 -10
package/package.json
CHANGED
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(', ')}`);
|