Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .changeset/bootstrap-system-prompt.md

This file was deleted.

5 changes: 0 additions & 5 deletions packages/framework/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
13 changes: 0 additions & 13 deletions packages/framework/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,6 @@ Options:
--context <dir> Focus the agent on this directory (repeatable). Adds one
"Context: <dirs>" 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).
Expand Down Expand Up @@ -230,8 +227,6 @@ export interface CliOptions {
eco: Required<EcoOptions>
/** `--context <dir>` (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). */
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1009,7 +1000,6 @@ export async function runCli(argv: string[], io: CliIO = defaultIO): Promise<num
if (noBuiltinPrompt) io.out(`◆ built-in system prompt: off (${opts.vanilla ? 'vanilla' : 'the-framework.yml'})`)
else if (eco) io.out(`◆ eco: dropping ${Object.keys(eco).filter(k => 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),
Expand All @@ -1030,7 +1020,6 @@ export async function runCli(argv: string[], io: CliIO = defaultIO): Promise<num
...(noBuiltinPrompt ? { antiLazyPill: false } : {}),
...(eco ? { eco } : {}),
...(opts.context.length ? { context: opts.context } : {}),
...(opts.bootstrap ? { bootstrap: true } : {}),
...(modeList.includes('autopilot') ? { autopilot: true } : {}),
...((): { sessionLink?: string } => {
const link = chooseSessionLink(opts, fake)
Expand Down Expand Up @@ -1118,7 +1107,6 @@ export async function runCli(argv: string[], io: CliIO = defaultIO): Promise<num
if (noBuiltinPrompt) io.out(`◆ built-in system prompt: off (${opts.vanilla ? 'vanilla' : 'the-framework.yml'})`)
else if (eco) io.out(`◆ eco: dropping ${Object.keys(eco).filter(k => 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,
Expand Down Expand Up @@ -1156,7 +1144,6 @@ export async function runCli(argv: string[], io: CliIO = defaultIO): Promise<num
...(noBuiltinPrompt ? { antiLazyPill: false } : {}),
...(eco ? { eco } : {}),
...(opts.context.length ? { context: opts.context } : {}),
...(opts.bootstrap ? { bootstrap: true } : {}),
...((): { sessionLink?: string } => {
const link = chooseSessionLink(opts, fake)
return link ? { sessionLink: link } : {}
Expand Down
1 change: 0 additions & 1 deletion packages/framework/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export {
composeRunSystem,
renderSystemPrompt,
SYSTEM_PROMPT_TEMPLATE,
BOOTSTRAP_PREAMBLE,
type SystemPromptOptions,
type RunSystemOptions,
type TfContext,
Expand Down
2 changes: 0 additions & 2 deletions packages/framework/src/daemon.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 0 additions & 1 deletion packages/framework/src/daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions packages/framework/src/dashboard/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ export interface StartRunOptions {
eco?: EcoOptions
/** In-context directories (#439): each becomes a `--context <dir>` 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`. */
Expand Down
1 change: 0 additions & 1 deletion packages/framework/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,6 @@ export {
composeRunSystem,
renderSystemPrompt,
SYSTEM_PROMPT_TEMPLATE,
BOOTSTRAP_PREAMBLE,
type SystemPromptOptions,
type RunSystemOptions,
type TfContext,
Expand Down
4 changes: 1 addition & 3 deletions packages/framework/src/prompt-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
/**
Expand Down Expand Up @@ -92,7 +90,7 @@ export async function runPrompt(opts: RunPromptOptions): Promise<RunPromptResult
prompt: opts.prompt,
params: { autopilot: opts.autopilot === true, ...(opts.eco ? { eco: opts.eco } : {}) },
}
const system = composeRunSystem({ antiLazyPill: opts.antiLazyPill, user: opts.systemPrompt, tf, context: opts.context, bootstrap: opts.bootstrap })
const system = composeRunSystem({ antiLazyPill: opts.antiLazyPill, user: opts.systemPrompt, tf, context: opts.context })
// The template's `# User prompt` half carries the prompt (today it renders to
// exactly `opts.prompt`; any framing Rom adds around the slot rides along). With
// the built-in prompt off, the raw prompt is sent as-is.
Expand Down
6 changes: 0 additions & 6 deletions packages/framework/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,6 @@ export interface RunFrameworkOptions {
eco?: EcoOptions
/** In-context directories (#439): added as one `Context:` line to the system prompt. */
context?: readonly string[]
/**
* Bootstrap mode (#297/#448): a brand-new project from an empty directory. Prepends a
* forceful preamble so the first turn stops for a plan instead of charging ahead. Default off.
*/
bootstrap?: boolean
/**
* A user-picked Open Loop domain preset ({loops, prompts}) to run the build
* under (#251). Its loops + prompts are materialized into a driver-backed {@link LoopEngine}
Expand Down Expand Up @@ -292,7 +287,6 @@ export async function runFramework(opts: RunFrameworkOptions): Promise<RunFramew
user: opts.systemPrompt,
tf,
context: opts.context,
bootstrap: opts.bootstrap,
})

// The session id is not known until the first driver turn returns, so a
Expand Down
26 changes: 8 additions & 18 deletions packages/framework/src/system-prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { mkdtemp, writeFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
BOOTSTRAP_PREAMBLE,
composeRunSystem,
renderSystemPrompt,
systemPromptBlock,
Expand Down Expand Up @@ -90,6 +89,14 @@ test('systemPromptBlock prepends a Context line for the selected directories (#4
assert.equal(systemPromptBlock({ antiLazyPill: false, user: 'x', context: [' '] }), 'x') // blank entries dropped
})

test('systemPromptBlock is the #326 prompt and the user prompt, in that order, and nothing else (#457)', () => {
// 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 ' }), '')
Expand All @@ -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'))
Expand Down Expand Up @@ -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'))
Expand Down
29 changes: 2 additions & 27 deletions packages/framework/src/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}

/**
Expand All @@ -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)
Expand All @@ -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
Expand Down
Loading