feat(gen-skill-docs): add --plan-mode=off to omit the plan-mode preamble (#3168)

Generated skills ship a ~1.8 KB "## Plan Mode Safe Operations" +
"## Skill Invocation During Plan Mode" block on every skill, for hosts
and users who never enter plan mode. This adds a --plan-mode=off flag to
gen-skill-docs that drops that block at gen time. Default builds
(planMode undefined or true) keep the block byte-identically.

The "PLAN MODE EXCEPTION — ALWAYS RUN" markers inside skill bodies
still apply when the flag is off.
This commit is contained in:
xingzihai committed 2026-10-16 00:43:11 +00:00
1 parent 47d639abff
commit 51edee9915
4 files changed
+107 -3

No files matched your search

+10 -2
View File
@@ -51,6 +51,7 @@ export interface GenerationOptions {
explainLevel?: 'default' | 'terse';
respectDetection?: boolean;
log?: (message: string) => void;
planMode?: boolean;
}
interface RenderOptions {
@@ -62,6 +63,7 @@ interface RenderOptions {
catalogMode: 'trim' | 'full';
explainLevel: 'default' | 'terse';
gbrainDetected: boolean;
planMode: boolean;
}
export interface GeneratedArtifact {
@@ -129,6 +131,11 @@ function parseGenerationArgs(args: string[]): GenerationOptions {
if (explainLevel !== 'default' && explainLevel !== 'terse') {
throw new Error(`Unknown explain level: ${explainLevel}. Use 'default' or 'terse'.`);
}
const planModeValue = value('--plan-mode');
if (planModeValue !== undefined && planModeValue !== 'on' && planModeValue !== 'off') {
throw new Error(`Unknown plan mode: ${planModeValue}. Use 'on' or 'off'.`);
}
const planMode = planModeValue === 'off' ? false : true;
const outDir = value('--out-dir');
const linkRoot = value('--link-root');
const disabledValue = value('--disabled-skills');
@@ -142,7 +149,7 @@ function parseGenerationArgs(args: string[]): GenerationOptions {
// Swap-in callers use --link-root for the FINAL serving path (#2692).
// Direct --out-dir callers retain their existing links into the render.
return {
host, model, catalogMode, explainLevel,
host, model, catalogMode, explainLevel, planMode,
dryRun: args.includes('--dry-run'),
respectDetection: args.includes('--respect-detection'),
outputRoot: outDir === undefined ? ROOT : path.resolve(outDir),
@@ -734,7 +741,7 @@ function buildContext(
const interactive = interactiveMatch ? interactiveMatch[1] === 'true' : undefined;
return {
skillName, tmplPath, benefitsFrom, host, paths: HOST_PATHS[host],
preambleTier, model: options.model ?? getHostConfig(host).defaultModel, interactive, explainLevel: options.explainLevel, installRoot: options.installRoot,
preambleTier, model: options.model ?? getHostConfig(host).defaultModel, interactive, explainLevel: options.explainLevel, installRoot: options.installRoot, planMode: options.planMode,
};
}
@@ -967,6 +974,7 @@ export async function runGeneration(settings: GenerationOptions = {}): Promise<G
catalogMode: settings.catalogMode ?? 'trim',
explainLevel: settings.explainLevel ?? 'default',
gbrainDetected: loadGbrainOverride(settings.respectDetection ?? false),
planMode: settings.planMode ?? true,
};
const hosts = settings.host === 'all' ? ALL_HOST_NAMES as Host[] : [settings.host ?? 'claude'];
const log = settings.log ?? (() => {});
@@ -20,6 +20,7 @@ import type { TemplateContext } from '../types';
* the full rationale.
*/
export function generatePlanModeInfo(ctx: TemplateContext): string {
if (ctx.planMode === false) return '';
const entryOrder = ctx.skillName === 'plan-eng-review'
? 'starting from the Scope gate, then follow its Startup sequence'
: 'starting from the Scope gate (current target announcement or answered question), then preamble and Step 0';
+9 -1
View File
@@ -116,7 +116,15 @@ export interface TemplateContext {
* root a per-install render serves (`gen-skill-docs --install-root`).
* null/undefined = the host's default root (committed render, unchanged bytes).
*/
installRoot?: string | null;
installRoot?: string | null;
/**
* Plan-mode preamble block. Defaults to true (block ships).
* false (opt-in via `--plan-mode=off`): the "## Plan Mode Safe Operations" +
* "## Skill Invocation During Plan Mode" section is dropped from the
* preamble. "PLAN MODE EXCEPTION — ALWAYS RUN" markers inside skill bodies
* still apply.
*/
planMode?: boolean;
}
/** Resolver function signature. args is populated for parameterized placeholders like {{INVOKE_SKILL:name}}. */
+87
View File
@@ -0,0 +1,87 @@
/**
* Unit tests for the --plan-mode=off flag (#3168).
*
* `--plan-mode=off` drops the "## Plan Mode Safe Operations" +
* "## Skill Invocation During Plan Mode" preamble block from generated
* skills, for hosts and users who never enter plan mode. Default builds
* (planMode undefined or true) keep the block byte-identically.
*
* Pure-function tests against the resolvers — fast, free, no subprocess.
*/
import { describe, test, expect } from 'bun:test';
import type { TemplateContext } from '../scripts/resolvers/types';
import { generatePlanModeInfo, generateCompletionStatus } from '../scripts/resolvers/preamble/generate-completion-status';
import { generatePreamble } from '../scripts/resolvers/preamble';
function makeCtx(planMode?: boolean, tier: number = 4): TemplateContext {
return {
skillName: 'test-skill',
tmplPath: '/tmp/test/SKILL.md.tmpl',
host: 'claude',
paths: {
skillRoot: '~/.claude/skills/gstack',
localSkillRoot: '.claude/skills',
binDir: '~/.claude/skills/gstack/bin',
browseDir: '~/.claude/skills/gstack/browse/dist',
designDir: '~/.claude/skills/gstack/design/dist',
makePdfDir: '~/.claude/skills/gstack/make-pdf/dist',
},
preambleTier: tier,
planMode,
};
}
describe('plan-mode off — generatePlanModeInfo', () => {
test('planMode undefined: emits both plan-mode sections (default behavior)', () => {
const out = generatePlanModeInfo(makeCtx(undefined));
expect(out).toContain('## Plan Mode Safe Operations');
expect(out).toContain('## Skill Invocation During Plan Mode');
});
test('planMode true: emits both plan-mode sections', () => {
const out = generatePlanModeInfo(makeCtx(true));
expect(out).toContain('## Plan Mode Safe Operations');
expect(out).toContain('## Skill Invocation During Plan Mode');
});
test('planMode false: returns empty string', () => {
expect(generatePlanModeInfo(makeCtx(false))).toBe('');
});
test('planMode false: no plan-mode heading survives', () => {
const out = generatePlanModeInfo(makeCtx(false));
expect(out).not.toContain('Plan Mode Safe Operations');
expect(out).not.toContain('Skill Invocation During Plan Mode');
});
});
describe('plan-mode off — generatePreamble integration', () => {
test('default tier-2 preamble includes the plan-mode block', () => {
const out = generatePreamble(makeCtx(undefined, 2));
expect(out).toContain('## Plan Mode Safe Operations');
expect(out).toContain('## Skill Invocation During Plan Mode');
});
test('planMode false tier-2 preamble drops the plan-mode block', () => {
const out = generatePreamble(makeCtx(false, 2));
expect(out).not.toContain('## Plan Mode Safe Operations');
expect(out).not.toContain('## Skill Invocation During Plan Mode');
});
test('planMode false preamble is measurably smaller than default', () => {
const defaultLen = generatePreamble(makeCtx(undefined, 2)).length;
const offLen = generatePreamble(makeCtx(false, 2)).length;
// The plan-mode block is ~1.8 KB; assert at least 1 KB saved.
expect(defaultLen - offLen).toBeGreaterThan(1024);
});
test('planMode undefined preamble is byte-identical to planMode true', () => {
expect(generatePreamble(makeCtx(undefined, 2))).toBe(generatePreamble(makeCtx(true, 2)));
});
test('planMode false: PLAN MODE EXCEPTION markers inside skill bodies still apply (completion status keeps its telemetry exception)', () => {
const out = generateCompletionStatus(makeCtx(false));
expect(out).toContain('PLAN MODE EXCEPTION — ALWAYS RUN');
});
});