diff --git a/src/agents/prompts/index.ts b/src/agents/prompts/index.ts index ae0e650d..36b1f3d7 100644 --- a/src/agents/prompts/index.ts +++ b/src/agents/prompts/index.ts @@ -70,6 +70,9 @@ export interface PromptContext { // Capacity / pipeline management maxInFlightItems?: number; + // Squint codebase intelligence + squintEnabled?: boolean; + // Future extensibility [key: string]: unknown; } @@ -340,6 +343,11 @@ export function getTemplateVariables(): Array<{ group: 'Capacity', description: 'Maximum number of items allowed in the active pipeline at once (default: 1)', }, + { + name: 'squintEnabled', + group: 'Squint', + description: 'Whether the repository has a Squint database (.squint.db) available', + }, ]; } diff --git a/src/agents/prompts/templates/implementation.eta b/src/agents/prompts/templates/implementation.eta index d6e683c4..6173c799 100644 --- a/src/agents/prompts/templates/implementation.eta +++ b/src/agents/prompts/templates/implementation.eta @@ -6,14 +6,15 @@ You are an expert software engineer implementing features and fixing issues base ### Phase 1: Understand 1. **Review the pre-loaded work item data** and verify your pre-populated todo list matches the implementation plan -2. **Consult the pre-loaded Squint overview** — identify which features and modules relate to the <%= it.workItemNoun || 'card' %> +<% if (it.squintEnabled) { %>2. **Consult the pre-loaded Squint overview** — identify which features and modules relate to the <%= it.workItemNoun || 'card' %> 3. **MANDATORY: Drill into features and modules before reading any source files:** - `squint features show --json` for each relevant feature — returns its flows, modules involved, and interactions - `squint flows show --json` for each relevant flow — returns the ordered interaction chain with entry point and definition-level call trace across modules - `squint modules show --json` for each module area the <%= it.workItemNoun || 'card' %> touches — returns members, outgoing/incoming interactions, flows, and features - This reveals cross-cutting dependencies (e.g., backend response shapes that frontend tests must match) that you will MISS by reading individual files 4. **Read codebase guidelines** - CLAUDE.md, AGENTS.md (these are meta-docs about conventions) -5. **THEN read source files** — only the files Squint identified as relevant. Use `squint symbols list --file --json` to understand a file's full architectural role before reading its source. +5. **THEN read source files** — only the files Squint identified as relevant. Use `squint symbols list --file --json` to understand a file's full architectural role before reading its source.<% } else { %>2. **Read codebase guidelines** - CLAUDE.md, AGENTS.md (these are meta-docs about conventions) +3. **Read source files** — use `ListDirectory`, `ReadFile`, and `RipGrep` to explore the codebase and identify relevant files before making changes.<% } %> ### Phase 2: Prepare @@ -23,8 +24,8 @@ You are an expert software engineer implementing features and fixing issues base ### Phase 3: Implement 6. **For each file to modify:** - - Use `squint symbols list --file --json` via Tmux first — this aggregates all symbols, relationships, interactions, and flows for the file - - Read 1-2 similar files for patterns and conventions +<% if (it.squintEnabled) { %> - Use `squint symbols list --file --json` via Tmux first — this aggregates all symbols, relationships, interactions, and flows for the file +<% } %> - Read 1-2 similar files for patterns and conventions - Make changes - Verify no diagnostics errors before moving to next file - Write tests leveraging patterns and helpers you found diff --git a/src/agents/prompts/templates/partials/squint-exploration.eta b/src/agents/prompts/templates/partials/squint-exploration.eta index 4c1590f4..d4d382f4 100644 --- a/src/agents/prompts/templates/partials/squint-exploration.eta +++ b/src/agents/prompts/templates/partials/squint-exploration.eta @@ -1,3 +1,4 @@ +<% if (it.squintEnabled) { %> ### Squint Codebase Intelligence Protocol (MANDATORY) **DO NOT read source files until you have completed these drill-down steps.** @@ -98,7 +99,15 @@ squint symbols list --kind function --domain --json - Searching for specific string patterns or regex - Config files, tests, or non-code files that squint doesn't index - Any file squint identified as relevant — squint shows structure, files show content - -#### If no SquintOverview was pre-loaded - -The repository has no Squint database. Skip these steps and proceed directly to reading files. +<% } else { %> +### Codebase Exploration Protocol + +The repository has no Squint database. Skip squint steps and proceed directly to reading files. + +Use these tools to explore the codebase: +- `ListDirectory` — list directory contents to understand structure +- `ReadFile` — read file contents for implementation details +- `RipGrep` — search for patterns in code (regex, respects gitignore) +- `AstGrep` — AST-aware code search (use $VAR for captures) +- `Tmux` — run shell commands (for exploration: grep, find, etc.) +<% } %> diff --git a/src/agents/prompts/templates/partials/tmux.eta b/src/agents/prompts/templates/partials/tmux.eta index 0fa99086..722e95ff 100644 --- a/src/agents/prompts/templates/partials/tmux.eta +++ b/src/agents/prompts/templates/partials/tmux.eta @@ -21,4 +21,4 @@ Use the Tmux gadget for ALL shell commands: - With pipes: `command="npm test 2>&1 | head -50"` - With globs: `command="find . -name '*.ts' | xargs wc -l"` -Use unique session names like: "npm-install", "test-run", "lint-check", "build", "typecheck", "squint-modules", "squint-features" +Use unique session names like: "npm-install", "test-run", "lint-check", "build", "typecheck"<% if (it.squintEnabled) { %>, "squint-modules", "squint-features"<% } %> diff --git a/src/agents/prompts/templates/planning.eta b/src/agents/prompts/templates/planning.eta index fceb224a..dd2eeebe 100644 --- a/src/agents/prompts/templates/planning.eta +++ b/src/agents/prompts/templates/planning.eta @@ -17,7 +17,7 @@ CRITICAL: You are running in a cloned copy of the project repository. Before creating your plan: 1. **Read the <%= it.workItemNoun || 'card' %>** to identify scope signals (file names, components, features, domain terms) -2. **Consult the pre-loaded Squint overview** — identify which features and modules relate to the <%= it.workItemNoun || 'card' %> +<% if (it.squintEnabled) { %>2. **Consult the pre-loaded Squint overview** — identify which features and modules relate to the <%= it.workItemNoun || 'card' %> 3. **MANDATORY: Drill into features, flows, and modules before reading any files:** - `squint features show --json` for each relevant feature — returns flows, modules involved, interactions - `squint flows show --json` for key flows — returns ordered interaction steps and the definition-level call trace showing which function calls which across module boundaries @@ -25,7 +25,9 @@ You are running in a cloned copy of the project repository. Before creating your - DO NOT SKIP — plans that miss cross-cutting dependencies (e.g., shared types, API contracts between backend and frontend) cause implementation failures 4. **THEN read specific files** — only files Squint identified as relevant. Use `squint symbols list --file --json` to understand a file's architectural role before reading source. 5. **Understand existing patterns** — how does the codebase already solve similar problems? -6. **Map terminology** — <%= it.workItemNoun || 'card' %> may use different terms than code +6. **Map terminology** — <%= it.workItemNoun || 'card' %> may use different terms than code<% } else { %>2. **Explore the codebase** using `ListDirectory`, `ReadFile`, `RipGrep`, and `Tmux` to identify relevant files and patterns. +3. **Understand existing patterns** — how does the codebase already solve similar problems? +4. **Map terminology** — <%= it.workItemNoun || 'card' %> may use different terms than code<% } %> <%~ include("partials/squint-exploration") %> diff --git a/src/agents/prompts/templates/respond-to-planning-comment.eta b/src/agents/prompts/templates/respond-to-planning-comment.eta index ddd9f1cb..2b37cc32 100644 --- a/src/agents/prompts/templates/respond-to-planning-comment.eta +++ b/src/agents/prompts/templates/respond-to-planning-comment.eta @@ -18,14 +18,15 @@ You are running in a cloned copy of the project repository. Before updating the 1. **Read the triggering comment** to understand what the user wants changed 2. **Read the current <%= it.workItemNoun || 'card' %>** to understand the existing plan -3. **Consult the pre-loaded Squint overview** — identify which features and modules relate to the requested changes +<% if (it.squintEnabled) { %>3. **Consult the pre-loaded Squint overview** — identify which features and modules relate to the requested changes 4. **MANDATORY: Drill into features, flows, and modules before reading any files:** - `squint features show --json` for each relevant feature — returns flows, modules involved, interactions - `squint flows show --json` for key flows — returns ordered interaction steps and the definition-level call trace showing which function calls which across module boundaries - `squint modules show --json` for each module area — returns members, outgoing/incoming interactions, flows - DO NOT SKIP — plans that miss cross-cutting dependencies (e.g., shared types, API contracts between backend and frontend) cause implementation failures 5. **THEN read specific files** — only files Squint identified as relevant. Use `squint symbols list --file --json` to understand a file's architectural role before reading source. -6. **Understand existing patterns** — how does the codebase already solve similar problems? +6. **Understand existing patterns** — how does the codebase already solve similar problems?<% } else { %>3. **Explore the codebase** using `ListDirectory`, `ReadFile`, `RipGrep`, and `Tmux` to understand the relevant areas. +4. **Understand existing patterns** — how does the codebase already solve similar problems?<% } %> <%~ include("partials/squint-exploration") %> diff --git a/src/agents/prompts/templates/review.eta b/src/agents/prompts/templates/review.eta index 94ea97e7..b5f373af 100644 --- a/src/agents/prompts/templates/review.eta +++ b/src/agents/prompts/templates/review.eta @@ -13,7 +13,7 @@ CRITICAL: **Accuracy over thoroughness**: A review with zero comments that correctly approves good code is better than a review that invents problems. Only report issues you can demonstrate. -**Architecture-aware**: Code can be correct yet harmful. A well-tested function in the wrong module, a clean implementation that duplicates an existing pattern, a performant solution that creates tight coupling — these are real problems even though every line compiles and every test passes. Use squint to see the forest, not just the trees. +**Architecture-aware**: Code can be correct yet harmful. A well-tested function in the wrong module, a clean implementation that duplicates an existing pattern, a performant solution that creates tight coupling — these are real problems even though every line compiles and every test passes.<% if (it.squintEnabled) { %> Use squint to see the forest, not just the trees.<% } %> ## Process @@ -21,7 +21,7 @@ CRITICAL: 1. **Understand the change**: Read the PR description and all modified files. Understand WHAT changed and WHY. An initial comment has already been posted on the PR acknowledging the review is in progress. -2. **Consult the pre-loaded Squint overview BEFORE reading files**: The overview maps module boundaries, dependencies (→ arrows), and feature flows. Use `squint modules show --json` via Tmux to drill into modules touched by the PR. This lets you verify changes fit established patterns without manually tracing the architecture. +<% if (it.squintEnabled) { %>2. **Consult the pre-loaded Squint overview BEFORE reading files**: The overview maps module boundaries, dependencies (→ arrows), and feature flows. Use `squint modules show --json` via Tmux to drill into modules touched by the PR. This lets you verify changes fit established patterns without manually tracing the architecture. <%~ include("partials/squint-exploration") %> @@ -52,6 +52,26 @@ Beyond general exploration, use squint specifically to detect conflicts and viol **Feature/UX Impact:** - Trace affected feature flows with `squint features show`. Does the change create inconsistent behavior? - What does the user see when this code fails? Edge cases in UX? +<% } else { %>2. **Explore the codebase** using `ListDirectory`, `ReadFile`, `RipGrep`, and `Tmux` to understand the context of the changes. Read CLAUDE.md and README.md for project conventions. + +3. **Analyze architectural impact**: Before reading implementation details, answer these strategic questions: + + **Boundary & Responsibility:** + - Is logic in the right layer? (business logic in controllers = bad) + - Do new dependencies point in the correct direction? + + **Pattern Consistency:** + - How does the codebase already solve this kind of problem? + - Does the PR introduce a "second way" to do something already done one way? + + **Design Simplicity:** + - Could the same goal be achieved with fewer files/abstractions/layers? + - Does the PR introduce indirection not justified by current complexity? + + **Feature/UX Impact:** + - Does the change create inconsistent behavior? + - What does the user see when this code fails? Edge cases in UX? +<% } %> ### Phase 2: Tactical Verification @@ -80,7 +100,7 @@ Issues that **must** be fixed before merge: - Correctness bugs (with failing scenario) - Data loss or corruption risks - Breaking changes to public APIs -- Dependency cycle introduction (confirmed via squint) +- Dependency cycle introduction<% if (it.squintEnabled) { %> (confirmed via squint)<% } %> - Responsibility violation (business logic where it can't be tested/reused) ### SHOULD_FIX (use REQUEST_CHANGES or COMMENT) @@ -90,7 +110,7 @@ Real issues worth addressing: - Incomplete implementations that will cause problems - Test coverage gaps for critical paths - Pattern conflict (second way to do something already done consistently one way) -- Abstraction misfit (new abstraction misaligned with module boundaries per squint) +- Abstraction misfit (new abstraction misaligned with module boundaries<% if (it.squintEnabled) { %> per squint<% } %>) - Unnecessary complexity (indirection not justified by current requirements) ### NITPICK (skip or brief COMMENT) @@ -138,13 +158,13 @@ Style and preferences - mention only if egregious: Answer these during Phase 1 — they catch design problems that line-by-line review misses: -1. **Does this change belong here?** — Is the code in the right module? Does it align with the feature boundaries squint shows? Would a developer looking for this functionality find it where it lives? +1. **Does this change belong here?** — Is the code in the right module?<% if (it.squintEnabled) { %> Does it align with the feature boundaries squint shows?<% } %> Would a developer looking for this functionality find it where it lives? -2. **Does this conflict with existing patterns?** — Use `squint symbols show` and `squint features show` to find analogous code. Does the PR follow the same conventions? Is it introducing a second way to do something the codebase already does one way? A single instance is not a "pattern" — look for consistent repetition before flagging a conflict. +2. **Does this conflict with existing patterns?** —<% if (it.squintEnabled) { %> Use `squint symbols show` and `squint features show` to find analogous code.<% } %> Does the PR follow the same conventions? Is it introducing a second way to do something the codebase already does one way? A single instance is not a "pattern" — look for consistent repetition before flagging a conflict. 3. **Is this the simplest solution?** — Could the same goal be achieved by extending existing code rather than creating new abstractions? Does every new file, class, or layer of indirection earn its keep against current (not hypothetical) complexity? -4. **What does the user experience?** — Trace user-facing flows affected by this change via `squint features show`. What does the user see when this code succeeds? When it fails? Are error states handled in a way the user can act on? +4. **What does the user experience?** — <% if (it.squintEnabled) { %>Trace user-facing flows affected by this change via `squint features show`. <% } %>What does the user see when this code succeeds? When it fails? Are error states handled in a way the user can act on? ## Anti-Patterns to Avoid @@ -177,7 +197,7 @@ Answer these during Phase 1 — they catch design problems that line-by-line rev - A 5-line bug fix doesn't need an architectural essay. ### Don't flag pattern conflicts without evidence -- Cite the existing pattern via squint — show the analogous code that does it differently. +- <% if (it.squintEnabled) { %>Cite the existing pattern via squint — show the analogous code that does it differently.<% } else { %>Cite the existing pattern — show the analogous code that does it differently.<% } %> - A single instance is not a "pattern." Look for at least 2-3 consistent examples before claiming a conflict. ## Review Decision @@ -207,8 +227,8 @@ Use CreatePRReview with: ## Architecture & Design [Only if strategic issues found — skip entirely for clean PRs] -- **[SHOULD_FIX] Pattern conflict**: ...with squint evidence -- **[BLOCKING] Dependency violation**: ...with squint evidence +- **[SHOULD_FIX] Pattern conflict**: ...with evidence<% if (it.squintEnabled) { %> (squint: `squint symbols show `)<% } %> +- **[BLOCKING] Dependency violation**: ...with evidence<% if (it.squintEnabled) { %> (squint: `squint modules show `)<% } %> ## Code Issues [Only if tactical issues found — skip entirely for clean PRs] diff --git a/src/agents/prompts/templates/splitting.eta b/src/agents/prompts/templates/splitting.eta index 607e2eea..b6ef3fd4 100644 --- a/src/agents/prompts/templates/splitting.eta +++ b/src/agents/prompts/templates/splitting.eta @@ -81,7 +81,7 @@ For technical/infrastructure work, adapt the format: You are running in a cloned copy of the project repository. Before creating stories: 1. **Read the <%= it.workItemNoun || 'card' %>** to identify scope signals (file names, components, features, domain terms) -2. **Consult the pre-loaded Squint overview** — identify which features and modules relate to the <%= it.workItemNoun || 'card' %> +<% if (it.squintEnabled) { %>2. **Consult the pre-loaded Squint overview** — identify which features and modules relate to the <%= it.workItemNoun || 'card' %> 3. **MANDATORY: Drill into features, flows, and modules before reading any files:** - `squint features show --json` for each relevant feature — returns flows, modules involved, interactions - `squint flows show --json` for key flows — returns ordered interaction steps and the definition-level call trace across module boundaries @@ -89,7 +89,9 @@ You are running in a cloned copy of the project repository. Before creating stor - DO NOT SKIP — accurate story sizing requires understanding cross-cutting dependencies 4. **THEN read specific files** — only files Squint identified as relevant. Use `squint symbols list --file --json` to understand a file's architectural role before reading source. 5. **Understand existing patterns** — how does the codebase already solve similar problems? -6. **Map terminology** — <%= it.workItemNoun || 'card' %> may use different terms than the code +6. **Map terminology** — <%= it.workItemNoun || 'card' %> may use different terms than the code<% } else { %>2. **Explore the codebase** using `ListDirectory`, `ReadFile`, `RipGrep`, and `Tmux` to identify relevant files and patterns. +3. **Understand existing patterns** — how does the codebase already solve similar problems? +4. **Map terminology** — <%= it.workItemNoun || 'card' %> may use different terms than the code<% } %> <%~ include("partials/squint-exploration") %> diff --git a/src/agents/shared/promptContext.ts b/src/agents/shared/promptContext.ts index 0d7a7a64..f07cafbb 100644 --- a/src/agents/shared/promptContext.ts +++ b/src/agents/shared/promptContext.ts @@ -1,6 +1,7 @@ import { getJiraConfig, getTrelloConfig } from '../../pm/config.js'; import { getPMProviderOrNull } from '../../pm/index.js'; import type { ProjectConfig } from '../../types/index.js'; +import { resolveSquintDbPath } from '../../utils/squintDb.js'; import type { PromptContext } from '../prompts/index.js'; function getListIds(project: ProjectConfig) { @@ -51,10 +52,12 @@ export function buildPromptContext( originalWorkItemUrl: string; detectedAgentType: string; }, + repoDir?: string, ): PromptContext { const pmProvider = getPMProviderOrNull(); const listIds = getListIds(project); const terminology = getPromptTerminology(pmProvider?.type); + const squintEnabled = repoDir ? resolveSquintDbPath(repoDir) !== null : false; return { workItemId, @@ -65,6 +68,7 @@ export function buildPromptContext( pmType: pmProvider?.type, ...terminology, maxInFlightItems: project.maxInFlightItems ?? 1, + squintEnabled, ...(prContext && { prNumber: prContext.prNumber, prBranch: prContext.prBranch, diff --git a/src/backends/adapter.ts b/src/backends/adapter.ts index ffa0af99..006c578a 100644 --- a/src/backends/adapter.ts +++ b/src/backends/adapter.ts @@ -162,6 +162,8 @@ async function buildExecutionPlan( project, input.triggerType, prContext, + undefined, + repoDir, ); // Load DB partials for template include resolution diff --git a/src/backends/codex/env.ts b/src/backends/codex/env.ts index b7ecd51e..1e11f897 100644 --- a/src/backends/codex/env.ts +++ b/src/backends/codex/env.ts @@ -18,6 +18,9 @@ const ALLOWED_ENV_EXACT = new Set([ // Codex auth 'OPENAI_API_KEY', + + // Squint + 'SQUINT_DB_PATH', ]); const ALLOWED_ENV_PREFIXES = SHARED_ALLOWED_ENV_PREFIXES; diff --git a/src/backends/opencode/env.ts b/src/backends/opencode/env.ts index 14d5f225..ceb5d944 100644 --- a/src/backends/opencode/env.ts +++ b/src/backends/opencode/env.ts @@ -20,6 +20,9 @@ const ALLOWED_ENV_EXACT = new Set([ 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'OPENROUTER_API_KEY', + + // Squint + 'SQUINT_DB_PATH', ]); const ALLOWED_ENV_PREFIXES = SHARED_ALLOWED_ENV_PREFIXES; diff --git a/tests/unit/agents/prompts.test.ts b/tests/unit/agents/prompts.test.ts index e5f43255..cbd49392 100644 --- a/tests/unit/agents/prompts.test.ts +++ b/tests/unit/agents/prompts.test.ts @@ -494,4 +494,146 @@ describe('getTemplateVariables', () => { expect(names).toContain('workItemId'); expect(names).toContain('projectId'); }); + + it('includes squintEnabled variable', () => { + const vars = getTemplateVariables(); + const names = vars.map((v) => v.name); + expect(names).toContain('squintEnabled'); + }); + + it('squintEnabled variable belongs to Squint group', () => { + const vars = getTemplateVariables(); + const squintVar = vars.find((v) => v.name === 'squintEnabled'); + expect(squintVar?.group).toBe('Squint'); + }); +}); + +describe('squintEnabled template gating', () => { + it('implementation prompt with squintEnabled=true includes squint instructions', () => { + const prompt = getSystemPrompt('implementation', { squintEnabled: true }); + expect(prompt).toContain('squint features show'); + expect(prompt).toContain('squint flows show'); + expect(prompt).toContain('squint modules show'); + expect(prompt).toContain('squint-modules'); + expect(prompt).toContain('squint-features'); + }); + + it('implementation prompt with squintEnabled=false excludes squint instructions', () => { + const prompt = getSystemPrompt('implementation', { squintEnabled: false }); + expect(prompt).not.toContain('squint features show'); + expect(prompt).not.toContain('squint flows show'); + expect(prompt).not.toContain('squint modules show'); + expect(prompt).not.toContain('squint-modules'); + expect(prompt).not.toContain('squint-features'); + }); + + it('implementation prompt with squintEnabled=false still contains core instructions', () => { + const prompt = getSystemPrompt('implementation', { squintEnabled: false }); + expect(prompt).toContain('CLAUDE.md'); + expect(prompt).toContain('Tmux'); + expect(prompt).toContain('conventional commits'); + }); + + it('planning prompt with squintEnabled=true includes squint instructions', () => { + const prompt = getSystemPrompt('planning', { squintEnabled: true }); + expect(prompt).toContain('squint features show'); + expect(prompt).toContain('squint flows show'); + expect(prompt).toContain('squint modules show'); + }); + + it('planning prompt with squintEnabled=false excludes squint instructions', () => { + const prompt = getSystemPrompt('planning', { squintEnabled: false }); + expect(prompt).not.toContain('squint features show'); + expect(prompt).not.toContain('squint flows show'); + expect(prompt).not.toContain('squint modules show'); + }); + + it('planning prompt with squintEnabled=false still contains core instructions', () => { + const prompt = getSystemPrompt('planning', { squintEnabled: false }); + expect(prompt).toContain('ReadWorkItem'); + expect(prompt).toContain('implementation plan'); + }); + + it('splitting prompt with squintEnabled=true includes squint instructions', () => { + const prompt = getSystemPrompt('splitting', { squintEnabled: true }); + expect(prompt).toContain('squint features show'); + expect(prompt).toContain('squint modules show'); + }); + + it('splitting prompt with squintEnabled=false excludes squint instructions', () => { + const prompt = getSystemPrompt('splitting', { squintEnabled: false }); + expect(prompt).not.toContain('squint features show'); + expect(prompt).not.toContain('squint modules show'); + }); + + it('review prompt with squintEnabled=true includes squint instructions', () => { + const prompt = getSystemPrompt('review', { squintEnabled: true }); + expect(prompt).toContain('squint modules show'); + expect(prompt).toContain('Squint for Conflict Detection'); + expect(prompt).toContain('squint features show'); + }); + + it('review prompt with squintEnabled=false excludes squint-specific instructions', () => { + const prompt = getSystemPrompt('review', { squintEnabled: false }); + expect(prompt).not.toContain('Squint for Conflict Detection'); + expect(prompt).not.toContain('squint modules show'); + expect(prompt).not.toContain('squint features show'); + expect(prompt).not.toContain('Use squint to see the forest'); + expect(prompt).not.toContain('with squint evidence'); + }); + + it('review prompt with squintEnabled=true includes philosophy squint reference', () => { + const prompt = getSystemPrompt('review', { squintEnabled: true }); + expect(prompt).toContain('Use squint to see the forest, not just the trees.'); + }); + + it('review prompt with squintEnabled=false still contains core review instructions', () => { + const prompt = getSystemPrompt('review', { squintEnabled: false }); + expect(prompt).toContain('BLOCKING'); + expect(prompt).toContain('APPROVE'); + expect(prompt).toContain('REQUEST_CHANGES'); + }); + + it('respond-to-planning-comment prompt with squintEnabled=true includes squint instructions', () => { + const prompt = getSystemPrompt('respond-to-planning-comment', { squintEnabled: true }); + expect(prompt).toContain('squint features show'); + expect(prompt).toContain('squint flows show'); + expect(prompt).toContain('squint modules show'); + }); + + it('respond-to-planning-comment prompt with squintEnabled=false excludes squint instructions', () => { + const prompt = getSystemPrompt('respond-to-planning-comment', { squintEnabled: false }); + expect(prompt).not.toContain('squint features show'); + expect(prompt).not.toContain('squint flows show'); + expect(prompt).not.toContain('squint modules show'); + }); + + it('squint-exploration partial with squintEnabled=true includes squint protocol', () => { + const partial = getRawPartial('squint-exploration'); + // The partial itself contains the conditional; render it with the context + const rendered = renderCustomPrompt(partial, { squintEnabled: true }); + expect(rendered).toContain('squint features show'); + expect(rendered).toContain('squint symbols show'); + }); + + it('squint-exploration partial with squintEnabled=false shows fallback message', () => { + const partial = getRawPartial('squint-exploration'); + const rendered = renderCustomPrompt(partial, { squintEnabled: false }); + expect(rendered).not.toContain('squint features show'); + expect(rendered).toContain('no Squint database'); + }); + + it('tmux partial with squintEnabled=true includes squint session name examples', () => { + const partial = getRawPartial('tmux'); + const rendered = renderCustomPrompt(partial, { squintEnabled: true }); + expect(rendered).toContain('squint-modules'); + expect(rendered).toContain('squint-features'); + }); + + it('tmux partial with squintEnabled=false excludes squint session name examples', () => { + const partial = getRawPartial('tmux'); + const rendered = renderCustomPrompt(partial, { squintEnabled: false }); + expect(rendered).not.toContain('squint-modules'); + expect(rendered).not.toContain('squint-features'); + }); }); diff --git a/tests/unit/agents/shared/promptContext.test.ts b/tests/unit/agents/shared/promptContext.test.ts index d445094a..11957c19 100644 --- a/tests/unit/agents/shared/promptContext.test.ts +++ b/tests/unit/agents/shared/promptContext.test.ts @@ -5,10 +5,18 @@ vi.mock('../../../../src/pm/index.js', () => ({ getPMProviderOrNull: vi.fn(), })); +// Mock resolveSquintDbPath to control squint availability +vi.mock('../../../../src/utils/squintDb.js', () => ({ + resolveSquintDbPath: vi.fn(), +})); + import { buildPromptContext } from '../../../../src/agents/shared/promptContext.js'; import { getPMProviderOrNull } from '../../../../src/pm/index.js'; +import { resolveSquintDbPath } from '../../../../src/utils/squintDb.js'; import { createMockPMProvider } from '../../../helpers/mockPMProvider.js'; +const mockResolveSquintDbPath = vi.mocked(resolveSquintDbPath); + const mockGetPMProvider = vi.mocked(getPMProviderOrNull); function makeProject(overrides: Record = {}) { @@ -430,4 +438,64 @@ describe('buildPromptContext', () => { expect(ctx.detectedAgentType).toBe('implementation'); }); }); + + describe('squintEnabled', () => { + beforeEach(() => { + const mockProvider = createMockPMProvider(); + mockProvider.type = 'trello'; + mockProvider.getWorkItemUrl = vi.fn((id: string) => `https://trello.com/c/${id}`); + mockGetPMProvider.mockReturnValue(mockProvider); + }); + + it('returns squintEnabled: true when resolveSquintDbPath returns a path', () => { + mockResolveSquintDbPath.mockReturnValue('/repo/.squint.db'); + const ctx = buildPromptContext( + 'card1', + makeProject() as never, + undefined, + undefined, + undefined, + '/repo', + ); + expect(ctx.squintEnabled).toBe(true); + }); + + it('returns squintEnabled: false when resolveSquintDbPath returns null', () => { + mockResolveSquintDbPath.mockReturnValue(null); + const ctx = buildPromptContext( + 'card1', + makeProject() as never, + undefined, + undefined, + undefined, + '/repo', + ); + expect(ctx.squintEnabled).toBe(false); + }); + + it('returns squintEnabled: false when repoDir is not provided', () => { + mockResolveSquintDbPath.mockReturnValue('/some/path.db'); + const ctx = buildPromptContext('card1', makeProject() as never); + expect(ctx.squintEnabled).toBe(false); + }); + + it('does not call resolveSquintDbPath when repoDir is undefined', () => { + mockResolveSquintDbPath.mockReturnValue('/some/path.db'); + buildPromptContext('card1', makeProject() as never); + expect(mockResolveSquintDbPath).not.toHaveBeenCalled(); + }); + + it('calls resolveSquintDbPath with the provided repoDir', () => { + mockResolveSquintDbPath.mockReturnValue(null); + buildPromptContext( + 'card1', + makeProject() as never, + undefined, + undefined, + undefined, + '/workspace/my-repo', + ); + expect(mockResolveSquintDbPath).toHaveBeenCalledWith('/workspace/my-repo'); + }); + }); });