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