diff --git a/src/resources/extensions/sf/prompt-loader.js b/src/resources/extensions/sf/prompt-loader.js index 695b7f0ef..9aca18461 100644 --- a/src/resources/extensions/sf/prompt-loader.js +++ b/src/resources/extensions/sf/prompt-loader.js @@ -43,6 +43,7 @@ function resolveExtensionDir() { } const __extensionDir = resolveExtensionDir(); const promptsDir = join(__extensionDir, "prompts"); +const fragmentsDir = join(__extensionDir, "prompts", "fragments"); const templatesDir = join(__extensionDir, "templates"); /** * Return the resolved templates directory path for use in prompts. @@ -95,6 +96,26 @@ function warmCache() { ); } } + try { + for (const file of readdirSync(fragmentsDir)) { + if (!file.endsWith(".md")) continue; + const cacheKey = `frg:${file.slice(0, -3)}`; + if (!templateCache.has(cacheKey)) { + templateCache.set( + cacheKey, + readFileSync(join(fragmentsDir, file), "utf-8"), + ); + } + } + } catch { + // fragments/ may not exist in test environments or older installs. + if (!process.env.VITEST && !process.env.NODE_TEST) { + logWarning( + "prompt", + `warmCache: fragments dir not found: ${fragmentsDir}`, + ); + } + } } // Snapshot all templates at module load time warmCache(); @@ -116,6 +137,29 @@ export function loadPrompt(name, vars = {}) { "If a `SF Skill Preferences` block is present in system context, use it and the `` catalog in your system prompt to decide which skills to load and follow for this unit, without relaxing required verification or artifact rules.", ...vars, }; + // Resolve {{include:fragment-name}} directives before variable validation. + // Fragment content can itself declare {{varName}} placeholders, which become + // visible to the validator below and are substituted in the normal pass. + // The include pattern uses ":" which is outside the validator regex [a-zA-Z0-9_], + // so unresolved includes never accidentally pass validation. + content = content.replace(/\{\{include:([a-zA-Z][a-zA-Z0-9_-]*)\}\}/g, (_, fragName) => { + let frag = templateCache.get(`frg:${fragName}`); + if (frag === undefined) { + // Lazy-load fallback (mirrors the prompt lazy-load path): allows fragments + // added after warmCache() ran (e.g. in test environments) to be resolved. + try { + frag = readFileSync(join(fragmentsDir, `${fragName}.md`), "utf-8"); + templateCache.set(`frg:${fragName}`, frag); + } catch { + throw new SFError( + SF_PARSE_ERROR, + `loadPrompt("${name}"): unknown fragment "{{include:${fragName}}}". ` + + `Create prompts/fragments/${fragName}.md and rebuild.`, + ); + } + } + return frag; + }); // Check BEFORE substitution: find all {{varName}} placeholders the template // declares and verify every one has a value in vars. Checking after substitution // would also flag {{...}} patterns injected by inlined content (e.g. template diff --git a/src/resources/extensions/sf/prompts/challenge.md b/src/resources/extensions/sf/prompts/challenge.md index 105fc58c5..9b40c5887 100644 --- a/src/resources/extensions/sf/prompts/challenge.md +++ b/src/resources/extensions/sf/prompts/challenge.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Challenge (Adversarial Review) — {{milestoneId}} -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. +{{include:working-directory-ops}} {{inlinedContext}} diff --git a/src/resources/extensions/sf/prompts/complete-milestone.md b/src/resources/extensions/sf/prompts/complete-milestone.md index df6bed82a..42f223778 100644 --- a/src/resources/extensions/sf/prompts/complete-milestone.md +++ b/src/resources/extensions/sf/prompts/complete-milestone.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Complete Milestone {{milestoneId}} ("{{milestoneTitle}}") -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory. +{{include:working-directory}} ## Your Role in the Pipeline diff --git a/src/resources/extensions/sf/prompts/complete-slice.md b/src/resources/extensions/sf/prompts/complete-slice.md index e4a14cc60..dcedbb260 100644 --- a/src/resources/extensions/sf/prompts/complete-slice.md +++ b/src/resources/extensions/sf/prompts/complete-slice.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Complete Slice {{sliceId}} ("{{sliceTitle}}") — Milestone {{milestoneId}} -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory. +{{include:working-directory}} ## Your Role in the Pipeline diff --git a/src/resources/extensions/sf/prompts/deploy.md b/src/resources/extensions/sf/prompts/deploy.md index ab661b44d..b9a0e6f42 100644 --- a/src/resources/extensions/sf/prompts/deploy.md +++ b/src/resources/extensions/sf/prompts/deploy.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Deploy — {{milestoneId}} -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. +{{include:working-directory-ops}} {{inlinedContext}} diff --git a/src/resources/extensions/sf/prompts/execute-task.md b/src/resources/extensions/sf/prompts/execute-task.md index 80a96fd05..3a684d1f9 100644 --- a/src/resources/extensions/sf/prompts/execute-task.md +++ b/src/resources/extensions/sf/prompts/execute-task.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Execute Task {{taskId}} ("{{taskTitle}}") — Slice {{sliceId}} ("{{sliceTitle}}"), Milestone {{milestoneId}} -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory. +{{include:working-directory}} A researcher explored the codebase and a planner decomposed the work — you are the executor. The task plan below is your authoritative contract for the slice goal and verification bar, but it is not a substitute for local reality. Verify the referenced files and surrounding code before changing them. Do not do broad re-research or spontaneous re-planning. Small factual corrections, file-path fixes, and local implementation adaptations are part of execution. Escalate to `blocker_discovered: true` only when the slice contract or downstream task graph no longer holds. @@ -38,6 +36,7 @@ Then: 0. Narrate step transitions, key implementation decisions, and verification outcomes as you work. Keep it terse — one line between tool-call clusters, not between every call — but write complete sentences in user-facing prose, not shorthand notes or scratchpad fragments. 0a. **Batch independent tool calls in parallel.** When the next step needs to read or grep multiple files/paths that don't depend on each other's results, issue them in a single tool-call message (multiple tool uses in one assistant turn) rather than one-at-a-time. Examples: reading the handler + the test file + the schema file to triangulate a bug; greping for two unrelated symbols. Sequential tool calls are only correct when each call's input genuinely depends on the previous call's output. Talking-then-doing is also dead weight — if the next action is unambiguous, just take it; describe what you found in the result, not what you plan to look at. 0b. **Swarm opportunity check.** Before implementation, decide whether this task can be split into a 2-3 worker same-model swarm. Swarm only if the shards have disjoint file/directory ownership, no shared-interface or lockfile edits, shard-local verification, and clear wall-clock savings. If it passes, dispatch `subagent({ tasks: [...] })` with explicit write scopes, expected output files, and verification per worker; then inspect `git status --short`, synthesize results, resolve conflicts, and run final task verification yourself. If it does not pass, continue single-agent execution without ceremony. +0c. **Rubber-duck check (non-trivial tasks only).** If the task touches more than two files, introduces a new abstraction, changes an API boundary, or has a non-obvious failure mode — dispatch a `rubber-duck` subagent with the task plan and any relevant existing code as context. Summarise its verdict in one line. If it returns a **Blocking** finding, address it before writing code. Skip this step for simple edits, test fixes, or renaming tasks. 1. {{skillActivation}} Follow any activated skills before writing code. If no skills match this task, skip this step. 2. **Verify file existence before editing.** The task plan references specific files. Before reading or editing any file mentioned in the plan, confirm it exists with `ls`, `find`, or `existsSync`. If a referenced file does NOT exist, stop immediately — do not attempt to create it based on the plan's description of what "should" be there. The file may have been deleted, renamed, or moved. Escalate as `blocker_discovered: true` with a clear description of which file is missing and what the plan expected to find. This prevents phantom work on stale file paths. 3. Execute the steps in the inlined task plan, adapting minor local mismatches when the surrounding code differs from the planner's snapshot diff --git a/src/resources/extensions/sf/prompts/fragments/working-directory-ops.md b/src/resources/extensions/sf/prompts/fragments/working-directory-ops.md new file mode 100644 index 000000000..22abdefb2 --- /dev/null +++ b/src/resources/extensions/sf/prompts/fragments/working-directory-ops.md @@ -0,0 +1,3 @@ +## Working Directory + +Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. \ No newline at end of file diff --git a/src/resources/extensions/sf/prompts/fragments/working-directory.md b/src/resources/extensions/sf/prompts/fragments/working-directory.md new file mode 100644 index 000000000..ab01e9b75 --- /dev/null +++ b/src/resources/extensions/sf/prompts/fragments/working-directory.md @@ -0,0 +1,3 @@ +## Working Directory + +Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory. \ No newline at end of file diff --git a/src/resources/extensions/sf/prompts/plan-milestone.md b/src/resources/extensions/sf/prompts/plan-milestone.md index 421846414..e985b43b1 100644 --- a/src/resources/extensions/sf/prompts/plan-milestone.md +++ b/src/resources/extensions/sf/prompts/plan-milestone.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Plan Milestone {{milestoneId}} ("{{milestoneTitle}}") -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory. +{{include:working-directory}} All relevant context has been preloaded below - start working immediately without re-reading these files. diff --git a/src/resources/extensions/sf/prompts/plan-slice.md b/src/resources/extensions/sf/prompts/plan-slice.md index f904b3f57..74f281e34 100644 --- a/src/resources/extensions/sf/prompts/plan-slice.md +++ b/src/resources/extensions/sf/prompts/plan-slice.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Plan Slice {{sliceId}} ("{{sliceTitle}}") — Milestone {{milestoneId}} -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory. +{{include:working-directory}} All relevant context has been preloaded below — start working immediately without re-reading these files. diff --git a/src/resources/extensions/sf/prompts/reassess-roadmap.md b/src/resources/extensions/sf/prompts/reassess-roadmap.md index 565f5b241..b07b02dca 100644 --- a/src/resources/extensions/sf/prompts/reassess-roadmap.md +++ b/src/resources/extensions/sf/prompts/reassess-roadmap.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Reassess Roadmap — Milestone {{milestoneId}} after {{completedSliceId}} -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory. +{{include:working-directory}} ## Your Role in the Pipeline diff --git a/src/resources/extensions/sf/prompts/refine-slice.md b/src/resources/extensions/sf/prompts/refine-slice.md index 881ec8a4a..ae419eac9 100644 --- a/src/resources/extensions/sf/prompts/refine-slice.md +++ b/src/resources/extensions/sf/prompts/refine-slice.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Refine Slice {{sliceId}} ("{{sliceTitle}}") — Milestone {{milestoneId}} -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory. +{{include:working-directory}} This unit **expands an approved sketch into a full plan**. It is not a blank-sheet planning pass — the sketch's scope is the authoritative boundary, and the prior slice's real outcomes are the authoritative context. Your job is to produce a detailed plan that fits inside the sketch while reflecting what actually shipped in earlier slices. diff --git a/src/resources/extensions/sf/prompts/release.md b/src/resources/extensions/sf/prompts/release.md index 02afcc44d..3d9cbb947 100644 --- a/src/resources/extensions/sf/prompts/release.md +++ b/src/resources/extensions/sf/prompts/release.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Release — {{milestoneId}} -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. +{{include:working-directory-ops}} {{inlinedContext}} diff --git a/src/resources/extensions/sf/prompts/replan-slice.md b/src/resources/extensions/sf/prompts/replan-slice.md index bf56968fb..a8a97c82d 100644 --- a/src/resources/extensions/sf/prompts/replan-slice.md +++ b/src/resources/extensions/sf/prompts/replan-slice.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Replan Slice {{sliceId}} ("{{sliceTitle}}") — Milestone {{milestoneId}} -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory. +{{include:working-directory}} A completed task reported `blocker_discovered: true`, meaning the current slice plan cannot be executed as-is. Your job is to rewrite the remaining tasks in the slice plan to address the blocker while preserving all completed work. diff --git a/src/resources/extensions/sf/prompts/research-milestone.md b/src/resources/extensions/sf/prompts/research-milestone.md index 01009b5e1..609ac84c8 100644 --- a/src/resources/extensions/sf/prompts/research-milestone.md +++ b/src/resources/extensions/sf/prompts/research-milestone.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Research Milestone {{milestoneId}} ("{{milestoneTitle}}") -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory. +{{include:working-directory}} All relevant context has been preloaded below — start working immediately without re-reading these files. diff --git a/src/resources/extensions/sf/prompts/research-slice.md b/src/resources/extensions/sf/prompts/research-slice.md index b3f0374d1..2c02915cf 100644 --- a/src/resources/extensions/sf/prompts/research-slice.md +++ b/src/resources/extensions/sf/prompts/research-slice.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Research Slice {{sliceId}} ("{{sliceTitle}}") — Milestone {{milestoneId}} -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory. +{{include:working-directory}} All relevant context has been preloaded below — start working immediately without re-reading these files. diff --git a/src/resources/extensions/sf/prompts/rollback.md b/src/resources/extensions/sf/prompts/rollback.md index 77002885b..3247962f7 100644 --- a/src/resources/extensions/sf/prompts/rollback.md +++ b/src/resources/extensions/sf/prompts/rollback.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Rollback — {{milestoneId}} -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. +{{include:working-directory-ops}} {{inlinedContext}} diff --git a/src/resources/extensions/sf/prompts/run-uat.md b/src/resources/extensions/sf/prompts/run-uat.md index 7841b2390..74f62c619 100644 --- a/src/resources/extensions/sf/prompts/run-uat.md +++ b/src/resources/extensions/sf/prompts/run-uat.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Run UAT — {{milestoneId}}/{{sliceId}} -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory. +{{include:working-directory}} All relevant context has been preloaded below. Start working immediately without re-reading these files. diff --git a/src/resources/extensions/sf/prompts/smoke-production.md b/src/resources/extensions/sf/prompts/smoke-production.md index 1ad58cb7c..ba14cad8e 100644 --- a/src/resources/extensions/sf/prompts/smoke-production.md +++ b/src/resources/extensions/sf/prompts/smoke-production.md @@ -2,9 +2,7 @@ You are executing SF autonomous mode. ## UNIT: Smoke Test Production — {{milestoneId}} -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. +{{include:working-directory-ops}} {{inlinedContext}} diff --git a/src/resources/extensions/sf/prompts/validate-milestone.md b/src/resources/extensions/sf/prompts/validate-milestone.md index 3e8defc3b..5deb5e242 100644 --- a/src/resources/extensions/sf/prompts/validate-milestone.md +++ b/src/resources/extensions/sf/prompts/validate-milestone.md @@ -2,9 +2,7 @@ You are the validation orchestrator for **{{milestoneId}} — {{milestoneTitle}}**. -## Working Directory - -Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory. +{{include:working-directory}} ## Mission diff --git a/src/resources/extensions/sf/tests/prompt-loader-fragments.test.mjs b/src/resources/extensions/sf/tests/prompt-loader-fragments.test.mjs new file mode 100644 index 000000000..26916673a --- /dev/null +++ b/src/resources/extensions/sf/tests/prompt-loader-fragments.test.mjs @@ -0,0 +1,108 @@ +/** + * prompt-loader-fragments.test.mjs — fragment include resolution contracts. + * + * Purpose: verify that {{include:name}} directives in prompt templates are + * resolved before variable validation so fragment-provided placeholder text + * participates in normal {{var}} substitution. + * + * Consumer: prompt-loader.js warmCache + loadPrompt. + */ +import { rmSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { afterEach, describe, expect, test } from "vitest"; +import { loadPrompt } from "../prompt-loader.js"; + +const promptsDir = join(import.meta.dirname, "..", "prompts"); +const fragmentsDir = join(promptsDir, "fragments"); + +// Temp fixture files written during tests — cleaned up in afterEach. +const tempFiles = []; + +function writeFixturePrompt(name, content) { + const path = join(promptsDir, `${name}.md`); + writeFileSync(path, content); + tempFiles.push(path); + return name; +} + +function writeFixtureFragment(name, content) { + const path = join(fragmentsDir, `${name}.md`); + writeFileSync(path, content); + tempFiles.push(path); + return name; +} + +afterEach(() => { + for (const p of tempFiles.splice(0)) rmSync(p, { force: true }); +}); + +describe("fragment include resolution", () => { + test("fragment_resolved_and_var_substituted_in_one_pass", () => { + // Write a minimal fragment and a minimal prompt that includes it. + writeFixtureFragment("testfrag-wd-simple", "## WD\n\nPath: `{{workingDirectory}}`."); + writeFixturePrompt("testprompt-include-simple", "{{include:testfrag-wd-simple}}\n"); + + const result = loadPrompt("testprompt-include-simple", { + workingDirectory: "/my/project", + }); + + expect(result).toContain("## WD"); + expect(result).toContain("`/my/project`"); + }); + + test("working_directory_fragment_resolves_in_real_prompt", () => { + // Use a real migrated prompt that includes {{include:working-directory}}. + // Write a minimal wrapper prompt so we control the exact var set. + writeFixturePrompt( + "testprompt-wd-real", + "# Header\n\n{{include:working-directory}}\n", + ); + + const result = loadPrompt("testprompt-wd-real", { + workingDirectory: "/real/path", + }); + + expect(result).toContain("## Working Directory"); + expect(result).toContain("`/real/path`"); + expect(result).toContain("Do NOT `cd` to any other directory"); + }); + + test("ops_fragment_omits_no_cd_restriction", () => { + writeFixturePrompt( + "testprompt-wd-ops", + "# Header\n\n{{include:working-directory-ops}}\n", + ); + + const result = loadPrompt("testprompt-wd-ops", { + workingDirectory: "/ops/path", + }); + + expect(result).toContain("## Working Directory"); + expect(result).toContain("`/ops/path`"); + expect(result).not.toContain("Do NOT `cd`"); + }); + + test("unknown_fragment_throws_parse_error", () => { + writeFixturePrompt( + "testprompt-unknown-frag", + "{{include:this-fragment-does-not-exist-xyzzy}}\n", + ); + + expect(() => loadPrompt("testprompt-unknown-frag", {})).toThrow( + /unknown fragment.*this-fragment-does-not-exist-xyzzy/, + ); + }); + + test("prompts_without_includes_unaffected", () => { + // A plain template with no {{include:}} must still load and substitute cleanly. + writeFixturePrompt( + "testprompt-no-include", + "# Plain\n\nValue: {{someVar}}.\n", + ); + + const result = loadPrompt("testprompt-no-include", { someVar: "hello" }); + + expect(result).toBe("# Plain\n\nValue: hello."); + expect(result).not.toContain("{{include:"); + }); +}); diff --git a/src/resources/extensions/sf/unit-context-manifest.js b/src/resources/extensions/sf/unit-context-manifest.js index eddbd297a..e0a496888 100644 --- a/src/resources/extensions/sf/unit-context-manifest.js +++ b/src/resources/extensions/sf/unit-context-manifest.js @@ -116,6 +116,12 @@ export const KNOWN_UNIT_TYPES = [ "discuss-requirements", "research-project", "workflow-preferences", + // ─── Ops / deployment pipeline ──────────────────────────────────────── + "deploy", + "smoke-production", + "release", + "rollback", + "challenge", ]; export const UNIT_MANIFESTS = { // ─── Milestone-scoped ──────────────────────────────────────────────── @@ -508,6 +514,77 @@ export const UNIT_MANIFESTS = { }, maxSystemPromptChars: COMMON_BUDGET_MEDIUM, }, + // ─── Ops / deployment pipeline ──────────────────────────────────────── + "deploy": { + skills: { mode: "all" }, + knowledge: "critical-only", + memory: "critical-only", + codebaseMap: false, + preferences: "active-only", + tools: TOOLS_ALL, + artifacts: { + inline: ["project"], + excerpt: [], + onDemand: [], + }, + maxSystemPromptChars: COMMON_BUDGET_SMALL, + }, + "smoke-production": { + skills: { mode: "all" }, + knowledge: "critical-only", + memory: "critical-only", + codebaseMap: false, + preferences: "active-only", + tools: TOOLS_ALL, + artifacts: { + inline: ["project"], + excerpt: [], + onDemand: [], + }, + maxSystemPromptChars: COMMON_BUDGET_SMALL, + }, + "release": { + skills: { mode: "all" }, + knowledge: "critical-only", + memory: "critical-only", + codebaseMap: false, + preferences: "active-only", + tools: TOOLS_ALL, + artifacts: { + inline: ["project"], + excerpt: [], + onDemand: [], + }, + maxSystemPromptChars: COMMON_BUDGET_SMALL, + }, + "rollback": { + skills: { mode: "all" }, + knowledge: "critical-only", + memory: "critical-only", + codebaseMap: false, + preferences: "active-only", + tools: TOOLS_ALL, + artifacts: { + inline: ["project"], + excerpt: [], + onDemand: [], + }, + maxSystemPromptChars: COMMON_BUDGET_SMALL, + }, + "challenge": { + skills: { mode: "all" }, + knowledge: "critical-only", + memory: "critical-only", + codebaseMap: false, + preferences: "active-only", + tools: TOOLS_ALL, + artifacts: { + inline: ["project"], + excerpt: [], + onDemand: [], + }, + maxSystemPromptChars: COMMON_BUDGET_SMALL, + }, }; // ─── Lookup helper ──────────────────────────────────────────────────────── /**