diff --git a/src/agents/atlas/default.ts b/src/agents/atlas/default.ts index 380606752..8bc360795 100644 --- a/src/agents/atlas/default.ts +++ b/src/agents/atlas/default.ts @@ -184,7 +184,7 @@ task( After EVERY delegation, complete ALL of these steps — no shortcuts: #### A. Automated Verification -1. 'lsp_diagnostics(filePath=".", extension=".ts")' → ZERO errors at project level (for directory paths, extension parameter is required) +1. 'lsp_diagnostics(filePath=".", extension=".ts")' → ZERO errors across scanned TypeScript files (directory scans are capped at 50 files; not a full-project guarantee) 2. \`bun run build\` or \`bun run typecheck\` → exit code 0 3. \`bun test\` → ALL tests pass @@ -346,7 +346,7 @@ You are the QA gate. Subagents lie. Verify EVERYTHING. **After each delegation — BOTH automated AND manual verification are MANDATORY:** -1. 'lsp_diagnostics(filePath=".", extension=".ts")' at PROJECT level → ZERO errors (for directory paths, extension parameter is required) +1. 'lsp_diagnostics(filePath=".", extension=".ts")' across scanned TypeScript files → ZERO errors (directory scans are capped at 50 files; not a full-project guarantee) 2. Run build command → exit 0 3. Run test suite → ALL pass 4. **\`Read\` EVERY changed file line by line** → logic matches requirements @@ -390,14 +390,14 @@ You are the QA gate. Subagents lie. Verify EVERYTHING. - Trust subagent claims without verification - Use run_in_background=true for task execution - Send prompts under 30 lines -- Skip project-level lsp_diagnostics after delegation (use 'filePath=".", extension=".ts"' for TypeScript projects) +- Skip scanned-file lsp_diagnostics after delegation (use 'filePath=".", extension=".ts"' for TypeScript projects; directory scans are capped at 50 files) - Batch multiple tasks in one delegation - Start fresh session for failures/follow-ups - use \`resume\` instead **ALWAYS**: - Include ALL 6 sections in delegation prompts - Read notepad before every delegation -- Run project-level QA after every delegation +- Run scanned-file QA after every delegation - Pass inherited wisdom to every subagent - Parallelize independent tasks - Verify with your own tools diff --git a/src/agents/atlas/gemini.ts b/src/agents/atlas/gemini.ts index 73048975e..48c5b1763 100644 --- a/src/agents/atlas/gemini.ts +++ b/src/agents/atlas/gemini.ts @@ -361,14 +361,14 @@ Subagents CLAIM "done" when: - Trust subagent claims without verification - Use run_in_background=true for task execution - Send prompts under 30 lines -- Skip project-level lsp_diagnostics (use 'filePath=".", extension=".ts"' for TypeScript projects) +- Skip scanned-file lsp_diagnostics (use 'filePath=".", extension=".ts"' for TypeScript projects; directory scans are capped at 50 files) - Batch multiple tasks in one delegation - Start fresh session for failures (use session_id) **ALWAYS**: - Include ALL 6 sections in delegation prompts - Read notepad before every delegation -- Run project-level QA after every delegation +- Run scanned-file QA after every delegation - Pass inherited wisdom to every subagent - Parallelize independent tasks - Store and reuse session_id for retries @@ -392,4 +392,4 @@ This ensures accurate progress tracking. Skip this and you lose visibility into export function getGeminiAtlasPrompt(): string { return ATLAS_GEMINI_SYSTEM_PROMPT -} \ No newline at end of file +} diff --git a/src/agents/atlas/gpt.ts b/src/agents/atlas/gpt.ts index 2a0fe8c80..2549075a5 100644 --- a/src/agents/atlas/gpt.ts +++ b/src/agents/atlas/gpt.ts @@ -55,7 +55,7 @@ Implementation tasks are the means. Final Wave approval is the goal. - Verification (use Bash for tests/build) - Parallelize independent tool calls when possible. - After ANY delegation, verify with your own tool calls: - 1. 'lsp_diagnostics(filePath=".", extension=".ts")' at project level (for directory paths, extension parameter is required) + 1. 'lsp_diagnostics(filePath=".", extension=".ts")' across scanned TypeScript files (directory scans are capped at 50 files; not a full-project guarantee) 2. \`Bash\` for build/test commands 3. \`Read\` for changed files @@ -364,14 +364,14 @@ Your job is to CATCH THEM. Assume every claim is false until YOU personally veri - Trust subagent claims without verification - Use run_in_background=true for task execution - Send prompts under 30 lines -- Skip project-level lsp_diagnostics (use 'filePath=".", extension=".ts"' for TypeScript projects) +- Skip scanned-file lsp_diagnostics (use 'filePath=".", extension=".ts"' for TypeScript projects; directory scans are capped at 50 files) - Batch multiple tasks in one delegation - Start fresh session for failures (use session_id) **ALWAYS**: - Include ALL 6 sections in delegation prompts - Read notepad before every delegation -- Run project-level QA after every delegation +- Run scanned-file QA after every delegation - Pass inherited wisdom to every subagent - Parallelize independent tasks - Store and reuse session_id for retries diff --git a/src/tools/lsp/directory-diagnostics.test.ts b/src/tools/lsp/directory-diagnostics.test.ts index 4c932e036..b46875a70 100644 --- a/src/tools/lsp/directory-diagnostics.test.ts +++ b/src/tools/lsp/directory-diagnostics.test.ts @@ -1,12 +1,53 @@ -import { describe, expect, it } from "bun:test" +import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test" import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "fs" import { join } from "path" import os from "os" +import * as configModule from "./config" +import { lspManager } from "./lsp-server" import { isDirectoryPath } from "./lsp-client-wrapper" import { aggregateDiagnosticsForDirectory } from "./directory-diagnostics" +import type { Diagnostic } from "./types" + +const diagnosticsMock = mock(async (_filePath: string) => ({ items: [] as Diagnostic[] })) +const getClientMock = mock(async () => ({ diagnostics: diagnosticsMock })) +const releaseClientMock = mock(() => {}) + +function createDiagnostic(message: string): Diagnostic { + return { + message, + severity: 1, + range: { + start: { line: 0, character: 0 }, + end: { line: 0, character: 1 }, + }, + } +} describe("directory diagnostics", () => { + beforeEach(() => { + diagnosticsMock.mockReset() + diagnosticsMock.mockImplementation(async (_filePath: string) => ({ items: [] })) + getClientMock.mockClear() + releaseClientMock.mockClear() + + spyOn(configModule, "findServerForExtension").mockReturnValue({ + status: "found", + server: { + id: "test-server", + command: ["test-server"], + extensions: [".ts"], + priority: 1, + }, + }) + spyOn(lspManager, "getClient").mockImplementation(getClientMock) + spyOn(lspManager, "releaseClient").mockImplementation(releaseClientMock) + }) + + afterEach(() => { + mock.restore() + }) + describe("isDirectoryPath", () => { it("returns true for existing directory", () => { const tmp = mkdtempSync(join(os.tmpdir(), "omo-isdir-")) @@ -52,5 +93,27 @@ describe("directory diagnostics", () => { "Directory does not exist" ) }) + + it("#given diagnostics from multiple files #when aggregating directory diagnostics #then each entry includes the source file path", async () => { + const tmp = mkdtempSync(join(os.tmpdir(), "omo-aggr-files-")) + try { + const firstFile = join(tmp, "first.ts") + const secondFile = join(tmp, "second.ts") + + writeFileSync(firstFile, "export const first = true\n") + writeFileSync(secondFile, "export const second = true\n") + + diagnosticsMock.mockImplementation(async (filePath: string) => ({ + items: [createDiagnostic(`problem in ${filePath}`)], + })) + + const result = await aggregateDiagnosticsForDirectory(tmp, ".ts") + + expect(result).toContain(`${firstFile}: error at 1:0: problem in ${firstFile}`) + expect(result).toContain(`${secondFile}: error at 1:0: problem in ${secondFile}`) + } finally { + rmSync(tmp, { recursive: true, force: true }) + } + }) }) }) diff --git a/src/tools/lsp/directory-diagnostics.ts b/src/tools/lsp/directory-diagnostics.ts index a99450fa8..b3dd96106 100644 --- a/src/tools/lsp/directory-diagnostics.ts +++ b/src/tools/lsp/directory-diagnostics.ts @@ -11,6 +11,11 @@ import type { Diagnostic } from "./types" const SKIP_DIRECTORIES = new Set(["node_modules", ".git", "dist", "build", ".next", "out"]) +type FileDiagnostic = { + filePath: string + diagnostic: Diagnostic +} + function collectFilesWithExtension(dir: string, extension: string, maxFiles: number): string[] { const files: string[] = [] @@ -95,7 +100,7 @@ export async function aggregateDiagnosticsForDirectory( const root = findWorkspaceRoot(absDir) - const allDiagnostics: Diagnostic[] = [] + const allDiagnostics: FileDiagnostic[] = [] const fileErrors: { file: string; error: string }[] = [] let client: LSPClient @@ -106,7 +111,12 @@ export async function aggregateDiagnosticsForDirectory( try { const result = await client.diagnostics(file) const filtered = filterDiagnosticsBySeverity(result.items, severity) - allDiagnostics.push(...filtered) + allDiagnostics.push( + ...filtered.map((diagnostic) => ({ + filePath: file, + diagnostic, + })) + ) } catch (e) { fileErrors.push({ file, @@ -138,8 +148,8 @@ export async function aggregateDiagnosticsForDirectory( if (displayDiagnostics.length > 0) { lines.push("") - for (const diag of displayDiagnostics) { - lines.push(formatDiagnostic(diag)) + for (const { filePath, diagnostic } of displayDiagnostics) { + lines.push(`${filePath}: ${formatDiagnostic(diagnostic)}`) } if (wasDiagCapped) { lines.push(