diff --git a/.changeset/bootstrap-system-prompt.md b/.changeset/bootstrap-system-prompt.md deleted file mode 100644 index 6ce341cc..00000000 --- a/.changeset/bootstrap-system-prompt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@gemstack/framework': minor ---- - -Add bootstrap mode (`--bootstrap`, and `StartRunOptions.bootstrap` for the dashboard): starting a brand-new project from an empty directory. It prepends a forceful preamble above the built-in #326 prompt so the first turn stops for a plan (ranked interpretations or a PLAN file) instead of charging ahead and scaffolding code. The #326 template is left byte-identical; the preamble only raises its "Unclear scope" / "Large scope" rules above Claude Code's default decisiveness, which otherwise overrides them on the append path (#297, #448). diff --git a/packages/framework/src/cli.test.ts b/packages/framework/src/cli.test.ts index 7bda070c..9ad037b2 100644 --- a/packages/framework/src/cli.test.ts +++ b/packages/framework/src/cli.test.ts @@ -61,11 +61,6 @@ test('parseArgs collects repeatable --context directories (#439)', () => { assert.deepEqual(parseArgs(['--context', '/work/api', '--context', '/work/ui', 'x']).context, ['/work/api', '/work/ui']) }) -test('parseArgs reads --bootstrap (#297/#448)', () => { - assert.equal(parseArgs(['x']).bootstrap, false) - assert.equal(parseArgs(['--bootstrap', 'x']).bootstrap, true) -}) - test('parseArgs reads --post-merge (#326)', () => { assert.equal(parseArgs(['x']).postMerge, false) assert.equal(parseArgs(['--post-merge', 'x']).postMerge, true) diff --git a/packages/framework/src/cli.ts b/packages/framework/src/cli.ts index fab882f7..c6579766 100644 --- a/packages/framework/src/cli.ts +++ b/packages/framework/src/cli.ts @@ -152,9 +152,6 @@ Options: --context Focus the agent on this directory (repeatable). Adds one "Context: " line to the system prompt; the agent can still reach every repo, this just narrows where it looks. - --bootstrap Bootstrap mode: a brand-new project from an empty dir. Makes - the first turn stop for a plan (interpretations / PLAN) before - writing any code, instead of charging ahead. --post-merge When the run signals setReadyForMerge(), fire the post-merge quality suite: maintainability, readability, and security-audit prompts, one after another (#326). @@ -230,8 +227,6 @@ export interface CliOptions { eco: Required /** `--context ` (repeatable): in-context directories added as one `Context:` line (#439). */ context: string[] - /** `--bootstrap`: bootstrap mode (#297/#448) — a new project from an empty dir; stop for a plan first. */ - bootstrap: boolean /** `--post-merge`: fire the #326 post-merge quality suite (maintainability/readability/security-audit) when the run signals setReadyForMerge(). */ postMerge: boolean /** `--browser`: give the agent a real browser via chrome-devtools-mcp (navigate, console, network, DOM, screenshot) during the run (#452). */ @@ -295,7 +290,6 @@ export function parseArgs(argv: string[]): CliOptions { vanilla: false, eco: { autoPlanning: false, autoResearch: false, autoMaintenance: false }, context: [], - bootstrap: false, postMerge: false, browser: false, dashboard: true, @@ -343,9 +337,6 @@ export function parseArgs(argv: string[]): CliOptions { case '--vanilla': opts.vanilla = true break - case '--bootstrap': - opts.bootstrap = true - break case '--post-merge': opts.postMerge = true break @@ -1009,7 +1000,6 @@ export async function runCli(argv: string[], io: CliIO = defaultIO): Promise eco[k as keyof EcoOptions]).join(', ')}`) if (opts.context.length) io.out(`◆ context: ${opts.context.join(', ')}`) - if (opts.bootstrap) io.out('◆ bootstrap: on (plan before building)') try { await runPrompt({ prompt: opts.directPrompt ? intent : renderResearchPrompt(intent), @@ -1030,7 +1020,6 @@ export async function runCli(argv: string[], io: CliIO = defaultIO): Promise { const link = chooseSessionLink(opts, fake) @@ -1118,7 +1107,6 @@ export async function runCli(argv: string[], io: CliIO = defaultIO): Promise eco[k as keyof EcoOptions]).join(', ')}`) if (opts.context.length) io.out(`◆ context: ${opts.context.join(', ')}`) - if (opts.bootstrap) io.out('◆ bootstrap: on (plan before building)') const runOpts: RunFrameworkOptions = { intent, @@ -1156,7 +1144,6 @@ export async function runCli(argv: string[], io: CliIO = defaultIO): Promise { const link = chooseSessionLink(opts, fake) return link ? { sessionLink: link } : {} diff --git a/packages/framework/src/client.ts b/packages/framework/src/client.ts index 0de6f4f9..791dc862 100644 --- a/packages/framework/src/client.ts +++ b/packages/framework/src/client.ts @@ -25,7 +25,6 @@ export { composeRunSystem, renderSystemPrompt, SYSTEM_PROMPT_TEMPLATE, - BOOTSTRAP_PREAMBLE, type SystemPromptOptions, type RunSystemOptions, type TfContext, diff --git a/packages/framework/src/daemon.test.ts b/packages/framework/src/daemon.test.ts index 8f0e6b88..7f098b46 100644 --- a/packages/framework/src/daemon.test.ts +++ b/packages/framework/src/daemon.test.ts @@ -56,8 +56,6 @@ test('startOptionFlags maps only enabled Global options to CLI flags (#314)', () '--context', '/work/ui', ]) - // Bootstrap mode (#297/#448): maps to --bootstrap. - assert.deepEqual(startOptionFlags({ bootstrap: true }), ['--bootstrap']) // Post-merge quality suite (#326): maps to --post-merge. assert.deepEqual(startOptionFlags({ postMerge: true }), ['--post-merge']) // Browser via chrome-devtools-mcp (#452): maps to --browser. diff --git a/packages/framework/src/daemon.ts b/packages/framework/src/daemon.ts index 451b9c8d..c647dcf1 100644 --- a/packages/framework/src/daemon.ts +++ b/packages/framework/src/daemon.ts @@ -68,7 +68,6 @@ export function startOptionFlags(options: StartRunOptions): string[] { if (options.eco?.autoResearch) flags.push('--eco-auto-research') if (options.eco?.autoMaintenance) flags.push('--eco-auto-maintenance') for (const dir of options.context ?? []) if (typeof dir === 'string' && dir.trim()) flags.push('--context', dir) - if (options.bootstrap) flags.push('--bootstrap') if (options.postMerge) flags.push('--post-merge') if (options.browser) flags.push('--browser') return flags diff --git a/packages/framework/src/dashboard/server.ts b/packages/framework/src/dashboard/server.ts index 11243532..ee961437 100644 --- a/packages/framework/src/dashboard/server.ts +++ b/packages/framework/src/dashboard/server.ts @@ -93,8 +93,6 @@ export interface StartRunOptions { eco?: EcoOptions /** In-context directories (#439): each becomes a `--context ` flag on the spawned run. */ context?: string[] - /** Bootstrap mode (#297/#448): a new project from an empty dir; maps to `--bootstrap`. */ - bootstrap?: boolean /** Post-merge quality suite (#326): on setReadyForMerge(), fire maintainability/readability/security-audit; maps to `--post-merge`. */ postMerge?: boolean /** Give the agent a real browser via chrome-devtools-mcp during the run (#452); maps to `--browser`. */ diff --git a/packages/framework/src/index.ts b/packages/framework/src/index.ts index 49c04a17..d089b939 100644 --- a/packages/framework/src/index.ts +++ b/packages/framework/src/index.ts @@ -224,7 +224,6 @@ export { composeRunSystem, renderSystemPrompt, SYSTEM_PROMPT_TEMPLATE, - BOOTSTRAP_PREAMBLE, type SystemPromptOptions, type RunSystemOptions, type TfContext, diff --git a/packages/framework/src/prompt-run.ts b/packages/framework/src/prompt-run.ts index cd0bde2d..4bcc133e 100644 --- a/packages/framework/src/prompt-run.ts +++ b/packages/framework/src/prompt-run.ts @@ -46,8 +46,6 @@ export interface RunPromptOptions { eco?: EcoOptions /** In-context directories (#439): added as one `Context:` line to the system prompt. */ context?: readonly string[] - /** Bootstrap mode (#297/#448): prepend the forceful preamble so the first turn stops for a plan. Default false. */ - bootstrap?: boolean /** Stop the run once the agent has spent this much, in USD (#322). */ budgetUsd?: number /** @@ -92,7 +90,7 @@ export async function runPrompt(opts: RunPromptOptions): Promise { + // The bootstrap preamble was the last text here that was neither the #326 doc nor the + // user's own. Measured on four live runs: #326 alone already stops an empty-dir build + // for a plan, so the override earned nothing and outranked the doc. + const block = systemPromptBlock({ user: 'Ship small PRs.', context: ['/work/api'] }) + assert.equal(block, ['Context: /work/api', renderSystemPrompt().system, 'Ship small PRs.'].join('\n\n')) +}) + test('systemPromptBlock ignores a whitespace-only user prompt', () => { assert.equal(systemPromptBlock({ user: ' ' }), renderSystemPrompt().system) assert.equal(systemPromptBlock({ antiLazyPill: false, user: ' \n ' }), '') @@ -100,21 +107,6 @@ test('systemPromptBlock threads tf through to the template', () => { assert.ok(block.includes('postpone a deep refactor')) }) -test('systemPromptBlock prepends the bootstrap preamble above the built-in prompt (#297/#448)', () => { - const off = systemPromptBlock() - assert.ok(!off.includes(BOOTSTRAP_PREAMBLE)) // default off: no preamble - const on = systemPromptBlock({ bootstrap: true }) - assert.ok(on.startsWith(BOOTSTRAP_PREAMBLE)) // preamble first - assert.ok(on.includes('# System prompt')) // then the byte-identical #326 template - assert.ok(on.indexOf(BOOTSTRAP_PREAMBLE) < on.indexOf('# System prompt')) // preamble outranks it -}) - -test('systemPromptBlock keeps the bootstrap preamble after the Context line (#439/#448)', () => { - const block = systemPromptBlock({ bootstrap: true, context: ['/work/api'] }) - assert.ok(block.startsWith('Context: /work/api')) // context frames everything - assert.ok(block.indexOf('Context: /work/api') < block.indexOf(BOOTSTRAP_PREAMBLE)) -}) - test('eco.autoPlanning drops only the Large scope section (#314)', () => { const { system } = renderSystemPrompt({ prompt: 'x', params: { eco: { autoPlanning: true } } }) assert.ok(!system.includes('## Large scope')) @@ -180,13 +172,11 @@ test('composeRunSystem appends nothing after the protocols, whatever the options const system = composeRunSystem({ user: 'Ship small PRs.', context: ['/work/api'], - bootstrap: true, tf: { prompt: 'build a todo app', params: { autopilot: true } }, }) const block = systemPromptBlock({ user: 'Ship small PRs.', context: ['/work/api'], - bootstrap: true, tf: { prompt: 'build a todo app', params: { autopilot: true } }, }) assert.equal(system, [block, AWAIT_PROTOCOL, SIGNAL_PROTOCOL].join('\n\n')) diff --git a/packages/framework/src/system-prompt.ts b/packages/framework/src/system-prompt.ts index c9429f34..5bb8205a 100644 --- a/packages/framework/src/system-prompt.ts +++ b/packages/framework/src/system-prompt.ts @@ -63,22 +63,6 @@ Before starting to write code, measure "variability": \${{tf.prompt}}` -/** - * Bootstrap mode's forceful preamble (#297/#448). The built-in #326 prompt already - * carries the "Unclear scope" / "Large scope" rules, but appended to Claude Code's own - * system prompt those lose to its default "be decisive, don't block the user" instinct — - * so a fresh-from-empty-dir build charges ahead instead of stopping for a plan (measured). - * This preamble states the override explicitly and forbids writing code before approval, - * which flips the behaviour without touching Rom's template. Prepended above the #326 - * prompt only when bootstrap mode is on. - */ -export const BOOTSTRAP_PREAMBLE = `# Bootstrap mode - -You are starting a brand-new project from an empty directory. This takes precedence over any default tendency to act decisively or to start building right away: - -- Do NOT write, scaffold, or edit any file, and do NOT run build or install commands, until the user has approved a plan. -- Your first reply MUST be either a list of interpretations sorted by plausibility (when the scope is unclear) or a plan the user can approve (when the scope is large), then stop and await the user's answer.` - /** * Eco fine-grained control (#314): each flag drops one whole `##` section from the * built-in #326 prompt to save tokens, letting the agent auto-handle that concern. @@ -187,12 +171,6 @@ export interface SystemPromptOptions { * the block. Empty/absent adds nothing. */ context?: readonly string[] | undefined - /** - * Bootstrap mode (#297/#448): starting a brand-new project from an empty directory. - * Prepends the forceful {@link BOOTSTRAP_PREAMBLE} above the built-in prompt so the - * first turn stops for a plan instead of charging ahead. Default off. - */ - bootstrap?: boolean | undefined } /** @@ -209,9 +187,6 @@ export function systemPromptBlock(opts: SystemPromptOptions = {}): string { // alone under `--vanilla`, where there is no built-in prompt to frame). const context = opts.context?.map(d => d.trim()).filter(Boolean) if (context && context.length) parts.push(`Context: ${context.join(', ')}`) - // Bootstrap's override sits above the #326 prompt so it frames (and outranks) its - // "Unclear scope" / "Large scope" rules. - if (opts.bootstrap) parts.push(BOOTSTRAP_PREAMBLE) if (opts.antiLazyPill !== false) parts.push(renderSystemPrompt(opts.tf).system) const user = opts.user?.trim() if (user) parts.push(user) @@ -228,8 +203,8 @@ export type RunSystemOptions = SystemPromptOptions * builds (#500): the two sites each inlined the composition and one nested the protocols * inside the built-in-prompt branch. * - * Order is fixed: the #326 prompt block (context / bootstrap / built-in prompt / user - * SYSTEM.md) first, then the always-on emit protocols. Nothing else is appended — a + * Order is fixed: the #326 prompt block (context / built-in prompt / user SYSTEM.md) + * first, then the always-on emit protocols. Nothing else is appended — a * build run's system channel is exactly this (#547), which is what lets the dashboard * show the whole prompt before a run starts (#520). The protocols are unconditional — * they are the *emit contract* (how the agent signals an awaited choice and the