From 523debee6c91ef09f0f54ed95b36e7ecef98893b Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Mon, 16 Mar 2026 15:23:39 -0400 Subject: [PATCH] fix: provide executorContextConstraints to plan-slice template (#677) The plan-slice.md template declares {{executorContextConstraints}} but buildPlanSlicePrompt() never passed this variable, causing loadPrompt() to throw: 'template declares {{executorContextConstraints}} but no value was provided.' Add formatExecutorConstraints() that uses the budget engine (computeBudgets + resolveExecutorContextWindow) to generate the executor context constraints block with task count ranges and inline context budgets based on the configured executor model's context window. Pass the formatted string to loadPrompt() as executorContextConstraints. Closes #677 --- src/resources/extensions/gsd/auto-prompts.ts | 36 +++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/resources/extensions/gsd/auto-prompts.ts b/src/resources/extensions/gsd/auto-prompts.ts index 3f02bb26a..0dddb841a 100644 --- a/src/resources/extensions/gsd/auto-prompts.ts +++ b/src/resources/extensions/gsd/auto-prompts.ts @@ -15,11 +15,41 @@ import { relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, resolveGsdRootFile, relGsdRootFile, } from "./paths.js"; -import { resolveSkillDiscoveryMode, resolveInlineLevel } from "./preferences.js"; +import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js"; import type { GSDState, InlineLevel } from "./types.js"; import type { GSDPreferences } from "./preferences.js"; import { join } from "node:path"; import { existsSync } from "node:fs"; +import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js"; + +// ─── Executor Constraints ───────────────────────────────────────────────────── + +/** + * Format executor context constraints for injection into the plan-slice prompt. + * Uses the budget engine to compute task count ranges and inline context budgets + * based on the configured executor model's context window. + */ +function formatExecutorConstraints(): string { + let windowTokens: number; + try { + const prefs = loadEffectiveGSDPreferences(); + windowTokens = resolveExecutorContextWindow(undefined, prefs?.preferences); + } catch { + windowTokens = 200_000; // safe default + } + const budgets = computeBudgets(windowTokens); + const { min, max } = budgets.taskCountRange; + const execWindowK = Math.round(windowTokens / 1000); + const perTaskBudgetK = Math.round(budgets.inlineContextBudgetChars / 1000); + return [ + `## Executor Context Constraints`, + ``, + `The agent that executes each task has a **${execWindowK}K token** context window.`, + `- Recommended task count for this slice: **${min}–${max} tasks**`, + `- Each task gets ~${perTaskBudgetK}K chars of inline context (plans, code, decisions)`, + `- Keep individual tasks completable within a single context window — if a task needs more context than fits, split it`, + ].join("\n"); +} // ─── Inline Helpers ─────────────────────────────────────────────────────── @@ -603,6 +633,9 @@ export async function buildPlanSlicePrompt( const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`; + // Build executor context constraints from the budget engine + const executorContextConstraints = formatExecutorConstraints(); + const outputRelPath = relSliceFile(base, mid, sid, "PLAN"); return loadPrompt("plan-slice", { workingDirectory: base, @@ -613,6 +646,7 @@ export async function buildPlanSlicePrompt( outputPath: join(base, outputRelPath), inlinedContext, dependencySummaries: depContent, + executorContextConstraints, }); }