From 246252bf195c0cad6807362792f25a005e07bff9 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Tue, 18 Aug 2026 13:10:51 -0700 Subject: [PATCH] feat: gpt-5.6-sol bounded-scope overlay + scope-aware resolvers The Sol profile pins the explicit task as the lake: adjacent work is report-only, investigation is bounded, runs terminate on one clean verification pass, and the AskUserQuestion decision-brief format is never trimmed. The overlay wrapper grants scope-interpretation precedence while concrete workflow steps, gates, and skill-mandated re-verification loops still win. Sol-specific Completeness Principle and first-run intro copy. New SETUP_COMMAND resolver renders './setup --host ' for every non-claude host so generated upgrade skills reinstall their own host. --- gstack-upgrade/SKILL.md.tmpl | 6 +- model-overlays/gpt-5.6-sol.md | 28 ++++++ scripts/resolvers/index.ts | 3 +- scripts/resolvers/model-overlay.ts | 18 +++- scripts/resolvers/preamble.ts | 2 +- .../preamble/generate-completeness-section.ts | 7 ++ .../resolvers/preamble/generate-lake-intro.ts | 13 ++- scripts/resolvers/utility.ts | 6 ++ test/model-overlay-gpt-5.6-sol.test.ts | 86 +++++++++++++++++++ 9 files changed, 159 insertions(+), 10 deletions(-) create mode 100644 model-overlays/gpt-5.6-sol.md create mode 100644 test/model-overlay-gpt-5.6-sol.test.ts diff --git a/gstack-upgrade/SKILL.md.tmpl b/gstack-upgrade/SKILL.md.tmpl index adbf29adc..53479a7e8 100644 --- a/gstack-upgrade/SKILL.md.tmpl +++ b/gstack-upgrade/SKILL.md.tmpl @@ -138,7 +138,7 @@ git checkout -- 'SKILL.md' '*/SKILL.md' '*/sections/*.md' 2>/dev/null || true STASH_OUTPUT=$(git stash 2>&1) git fetch origin git reset --hard origin/main -./setup +{{SETUP_COMMAND}} ``` If `$STASH_OUTPUT` contains "Saved working directory", warn the user: "Note: local changes were stashed (any modified generated SKILL.md/sections files were discarded first — they regenerate on setup). Run `git stash pop` in the skill directory to restore your own changes." @@ -149,7 +149,7 @@ TMP_DIR=$(mktemp -d) git clone --depth 1 https://github.com/garrytan/gstack.git "$TMP_DIR/gstack" mv "$INSTALL_DIR" "$INSTALL_DIR.bak" mv "$TMP_DIR/gstack" "$INSTALL_DIR" -cd "$INSTALL_DIR" && ./setup +cd "$INSTALL_DIR" && {{SETUP_COMMAND}} rm -rf "$INSTALL_DIR.bak" "$TMP_DIR" ``` @@ -189,7 +189,7 @@ Tell user: "Removed vendored copy at `$LOCAL_GSTACK` (team mode active — globa mv "$LOCAL_GSTACK" "$LOCAL_GSTACK.bak" cp -Rf "$INSTALL_DIR" "$LOCAL_GSTACK" rm -rf "$LOCAL_GSTACK/.git" -cd "$LOCAL_GSTACK" && ./setup +cd "$LOCAL_GSTACK" && {{SETUP_COMMAND}} rm -rf "$LOCAL_GSTACK.bak" ``` Tell user: "Also updated vendored copy at `$LOCAL_GSTACK` — commit `.claude/skills/gstack/` when you're ready." diff --git a/model-overlays/gpt-5.6-sol.md b/model-overlays/gpt-5.6-sol.md new file mode 100644 index 000000000..5d6ccc557 --- /dev/null +++ b/model-overlays/gpt-5.6-sol.md @@ -0,0 +1,28 @@ +**The explicit task is the lake.** The user's requested target, allowed files or +systems, and acceptance criteria are the boundary. Interpret "complete," "full," +"exhaustive," "every," "100%," and "Boil the Ocean" as complete within that +boundary, never as permission to widen it. + +**Keep adjacent work report-only.** Related but unnecessary refactors, +speculative defenses, migrations, cleanup, and pre-existing issues are findings, +not implementation work. Mention them briefly at handoff without changing them. + +**Bound investigation.** Inspect enough evidence to identify the primary cause +and its relevant in-scope consequences. Once those are established, stop widening +the search unless a concrete contradiction or failed acceptance criterion requires +more evidence. + +**Terminate on verified completion.** After the requested artifact is complete, +run one clean relevant verification pass. If it passes, stop and report. Do not +repeat passing checks, reopen settled questions, or harden hypothetical failure +modes unless the user asks or a concrete failure makes that work necessary. + +**Completeness still matters inside scope.** Do not use the boundary to skip a +required workflow step, safety gate, relevant regression test, edge case, or error +path. Finish the whole requested job, then stop. + +**AskUserQuestion is never trimmed.** Bounded scope does not compress decision +briefs. Every AskUserQuestion carries the full format from the preamble: the +ELI10 paragraph, a `RECOMMENDATION:` line on its own line, and scored options. +When a skill workflow says STOP or asks via AskUserQuestion, that gate wins over +any urge to terminate — wait for the user. diff --git a/scripts/resolvers/index.ts b/scripts/resolvers/index.ts index 98c7dfdda..91b8890a5 100644 --- a/scripts/resolvers/index.ts +++ b/scripts/resolvers/index.ts @@ -23,7 +23,7 @@ import { generateCommandReference, generateSnapshotFlags, generateBrowseSetup } import { generateDesignMethodology, generateDesignHardRules, generateDesignOutsideVoices, generateDesignReviewLite, generateDesignSketch, generateDesignSetup, generateDesignMockup, generateDesignShotgunLoop, generateTasteProfile, generateUXPrinciples } from './design'; import { generateTestBootstrap, generateTestCoverageAuditPlan, generateTestCoverageAuditShip } from './testing'; import { generateReviewDashboard, generatePlanFileReviewReport, generateExitPlanModeGate, generateAntiShortcutClause, generateSpecReviewLoop, generateBenefitsFrom, generateCodexSecondOpinion, generateAdversarialStep, generateCodexPlanReview, generateCodexDocReview, generatePlanCompletionAuditShip, generatePlanCompletionAuditReview, generatePlanVerificationExec, generateScopeDrift, generateCrossReviewDedup } from './review'; -import { generateSlugEval, generateSlugSetup, generateBaseBranchDetect, generateDeployBootstrap, generateQAMethodology, generateCoAuthorTrailer, generateChangelogWorkflow, generateCodexWebSearchFlag } from './utility'; +import { generateSlugEval, generateSlugSetup, generateBaseBranchDetect, generateDeployBootstrap, generateQAMethodology, generateCoAuthorTrailer, generateChangelogWorkflow, generateCodexWebSearchFlag, generateSetupCommand } from './utility'; import { generateLearningsSearch, generateLearningsLog } from './learnings'; import { generateConfidenceCalibration } from './confidence'; import { generateInvokeSkill } from './composition'; @@ -78,6 +78,7 @@ export const RESOLVERS: Record = { PLAN_COMPLETION_AUDIT_REVIEW: generatePlanCompletionAuditReview, PLAN_VERIFICATION_EXEC: generatePlanVerificationExec, CO_AUTHOR_TRAILER: generateCoAuthorTrailer, + SETUP_COMMAND: generateSetupCommand, LEARNINGS_SEARCH: generateLearningsSearch, LEARNINGS_LOG: generateLearningsLog, CONFIDENCE_CALIBRATION: generateConfidenceCalibration, diff --git a/scripts/resolvers/model-overlay.ts b/scripts/resolvers/model-overlay.ts index 4bbd9641b..2a6242e2c 100644 --- a/scripts/resolvers/model-overlay.ts +++ b/scripts/resolvers/model-overlay.ts @@ -49,12 +49,22 @@ export function generateModelOverlay(ctx: TemplateContext): string { const content = readOverlay(ctx.model); if (!content) return ''; - return `## Model-Specific Behavioral Patch (${ctx.model}) - -The following nudges are tuned for the ${ctx.model} model family. They are + const precedence = ctx.model === 'gpt-5.6-sol' + ? `The following instructions disambiguate scope for the ${ctx.model} model. +They govern ambiguous completeness words such as \`complete\`, \`full\`, \`every\`, +\`exhaustive\`, \`100%\`, and \`Boil the Ocean\`, and when to stop iterating on +work the user did not ask for. Concrete skill workflow steps, STOP points, +AskUserQuestion gates, plan-mode safety, required tests, skill-mandated +re-verification and re-review loops, and /ship review gates still win. +Never use this patch to skip a concrete requirement.` + : `The following nudges are tuned for the ${ctx.model} model family. They are **subordinate** to skill workflow, STOP points, AskUserQuestion gates, plan-mode safety, and /ship review gates. If a nudge below conflicts with skill instructions, -the skill wins. Treat these as preferences, not rules. +the skill wins. Treat these as preferences, not rules.`; + + return `## Model-Specific Behavioral Patch (${ctx.model}) + +${precedence} ${content}`; } diff --git a/scripts/resolvers/preamble.ts b/scripts/resolvers/preamble.ts index 2c6e1fceb..fb8fe33f4 100644 --- a/scripts/resolvers/preamble.ts +++ b/scripts/resolvers/preamble.ts @@ -97,7 +97,7 @@ export function generatePreamble(ctx: TemplateContext): string { generatePlanModeInfo(ctx), generateUpgradeCheck(ctx), generateWritingStyleMigration(ctx), - generateLakeIntro(), + generateLakeIntro(ctx), generateTelemetryPrompt(ctx), generateProactivePrompt(ctx), generateFirstRunGuidance(ctx), diff --git a/scripts/resolvers/preamble/generate-completeness-section.ts b/scripts/resolvers/preamble/generate-completeness-section.ts index 3a8665812..f87932b4e 100644 --- a/scripts/resolvers/preamble/generate-completeness-section.ts +++ b/scripts/resolvers/preamble/generate-completeness-section.ts @@ -2,6 +2,13 @@ import type { TemplateContext } from '../types'; export function generateCompletenessSection(ctx?: TemplateContext): string { if (ctx?.explainLevel === 'terse') return ''; + if (ctx?.model === 'gpt-5.6-sol') { + return `## Completeness Principle — Boil the Ocean Within Scope + +AI makes completeness cheap, so do the complete thing **inside the user's explicit task boundary**. The requested target, allowed files or systems, and acceptance criteria define the lake. Within that lake, cover the relevant tests, edge cases, and error paths. Related but unnecessary refactors, speculative hardening, cleanup, and migrations are separate scope: report them, do not implement them. + +When options differ in in-scope coverage, include \`Completeness: X/10\` (10 = all relevant in-scope edge cases, 7 = happy path, 3 = shortcut). When options differ in kind, write: \`Note: options differ in kind, not coverage — no completeness score.\` Do not fabricate scores or expand the lake to raise one.`; + } return `## Completeness Principle — Boil the Ocean AI makes completeness cheap, so the complete thing is the goal. Recommend full coverage (tests, edge cases, error paths) — boil the ocean one lake at a time. The only thing out of scope is genuinely unrelated work (rewrites, multi-quarter migrations); flag that as separate scope, never as an excuse for a shortcut. diff --git a/scripts/resolvers/preamble/generate-lake-intro.ts b/scripts/resolvers/preamble/generate-lake-intro.ts index b6cca9e13..c0442a1ad 100644 --- a/scripts/resolvers/preamble/generate-lake-intro.ts +++ b/scripts/resolvers/preamble/generate-lake-intro.ts @@ -1,6 +1,17 @@ +import type { TemplateContext } from '../types'; -export function generateLakeIntro(): string { +export function generateLakeIntro(ctx: TemplateContext): string { + if (ctx.model === 'gpt-5.6-sol') { + return `If \`LAKE_INTRO\` is \`no\`: say "gstack follows the **Boil the Ocean** principle — do the complete thing within the user's explicit task boundary when AI makes marginal cost near-zero. Do not widen that boundary to adjacent cleanup or speculative hardening. Read more: https://garryslist.org/posts/boil-the-ocean" Offer to open: + +\`\`\`bash +open https://garryslist.org/posts/boil-the-ocean +touch ~/.gstack/.completeness-intro-seen +\`\`\` + +Only run \`open\` if yes. Always run \`touch\`.`; + } return `If \`LAKE_INTRO\` is \`no\`: say "gstack follows the **Boil the Ocean** principle — do the complete thing when AI makes marginal cost near-zero. Read more: https://garryslist.org/posts/boil-the-ocean" Offer to open: \`\`\`bash diff --git a/scripts/resolvers/utility.ts b/scripts/resolvers/utility.ts index 74e364cf1..911b01d8f 100644 --- a/scripts/resolvers/utility.ts +++ b/scripts/resolvers/utility.ts @@ -385,6 +385,12 @@ export function generateCoAuthorTrailer(ctx: TemplateContext): string { return hostConfig.coAuthorTrailer || 'Co-Authored-By: Claude Opus 4.7 '; } +export function generateSetupCommand(ctx: TemplateContext): string { + // Every non-claude host must reinstall ITSELF on upgrade — bare `./setup` + // defaults to the claude host and would leave the invoking host stale. + return ctx.host === 'claude' ? './setup' : `./setup --host ${ctx.host}`; +} + export function generateChangelogWorkflow(_ctx: TemplateContext): string { return `## Step 13: CHANGELOG (auto-generate) diff --git a/test/model-overlay-gpt-5.6-sol.test.ts b/test/model-overlay-gpt-5.6-sol.test.ts new file mode 100644 index 000000000..9d1d83e77 --- /dev/null +++ b/test/model-overlay-gpt-5.6-sol.test.ts @@ -0,0 +1,86 @@ +import { describe, expect, test } from 'bun:test'; +import { resolveModel } from '../scripts/models'; +import { generateModelOverlay, readOverlay } from '../scripts/resolvers/model-overlay'; +import { generateCompletenessSection } from '../scripts/resolvers/preamble/generate-completeness-section'; +import { generateLakeIntro } from '../scripts/resolvers/preamble/generate-lake-intro'; +import { generateSetupCommand } from '../scripts/resolvers/utility'; +import type { TemplateContext } from '../scripts/resolvers/types'; + +function ctx(model: TemplateContext['model']): TemplateContext { + return { + skillName: 'investigate', + tmplPath: 'investigate/SKILL.md.tmpl', + host: 'codex', + paths: { + skillRoot: '$GSTACK_ROOT', + localSkillRoot: '.agents/skills/gstack', + binDir: '$GSTACK_BIN', + browseDir: '$GSTACK_BROWSE', + designDir: '$GSTACK_DESIGN', + makePdfDir: '$GSTACK_MAKE_PDF', + }, + preambleTier: 3, + model, + }; +} + +describe('GPT-5.6 Sol model profile', () => { + test('only the exact Sol ID selects the Sol profile', () => { + expect(resolveModel('gpt-5.6-sol')).toBe('gpt-5.6-sol'); + expect(resolveModel('gpt-5.6-terra')).toBe('gpt'); + expect(resolveModel('gpt-5.6-luna')).toBe('gpt'); + expect(resolveModel('gpt-5.6-sol-preview')).toBe('gpt'); + expect(resolveModel('gpt-5.7')).toBe('gpt'); + }); + + test('standalone overlay does not inherit generic GPT completion bias', () => { + const raw = readOverlay('gpt-5.6-sol'); + expect(raw).toContain('The explicit task is the lake'); + expect(raw).toContain('one clean relevant verification pass'); + expect(raw).toContain('report-only'); + expect(raw).not.toContain('{{INHERIT:gpt}}'); + expect(raw).not.toContain('make your best judgment and proceed'); + }); + + test('wrapper gives scope interpretation precedence but preserves concrete gates', () => { + const out = generateModelOverlay(ctx('gpt-5.6-sol')); + expect(out).toContain('disambiguate scope'); + expect(out).toContain('Concrete skill workflow steps'); + expect(out).toContain('Never use this patch to skip a concrete requirement'); + }); + + test('completeness and first-run copy stay inside the explicit task boundary', () => { + const completeness = generateCompletenessSection(ctx('gpt-5.6-sol')); + const intro = generateLakeIntro(ctx('gpt-5.6-sol')); + expect(completeness).toContain("inside the user's explicit task boundary"); + expect(completeness).toContain('report them, do not implement them'); + expect(completeness).toContain('all relevant in-scope edge cases'); + expect(intro).toContain("within the user's explicit task boundary"); + expect(intro).toContain('Do not widen that boundary'); + }); + + test('generic GPT copy remains unchanged', () => { + const generic = generateModelOverlay(ctx('gpt')); + const completeness = generateCompletenessSection(ctx('gpt')); + const intro = generateLakeIntro(ctx('gpt')); + expect(generic).toContain('make your best judgment and proceed'); + expect(completeness).toContain('the complete thing is the goal'); + expect(intro).toContain('do the complete thing when AI makes marginal cost near-zero'); + expect(intro).not.toContain('Do not widen that boundary'); + }); + + test('terse mode still suppresses the completeness section for Sol', () => { + // Terse short-circuits before the Sol branch — a check-order flip would + // ship Sol completeness prose to terse users (a token regression). + expect(generateCompletenessSection({ ...ctx('gpt-5.6-sol'), explainLevel: 'terse' })).toBe(''); + }); +}); + +describe('SETUP_COMMAND resolver', () => { + test('claude keeps bare ./setup; every other host reinstalls itself', () => { + expect(generateSetupCommand({ ...ctx('claude'), host: 'claude' })).toBe('./setup'); + expect(generateSetupCommand({ ...ctx('gpt'), host: 'codex' })).toBe('./setup --host codex'); + expect(generateSetupCommand({ ...ctx('claude'), host: 'kiro' })).toBe('./setup --host kiro'); + expect(generateSetupCommand({ ...ctx('claude'), host: 'factory' })).toBe('./setup --host factory'); + }); +});