diff --git a/src/tools/preflight-check.ts b/src/tools/preflight-check.ts index 8c9121a..f5e6dc2 100644 --- a/src/tools/preflight-check.ts +++ b/src/tools/preflight-check.ts @@ -17,10 +17,27 @@ import { loadPatterns, matchPatterns, formatPatternMatches } from "../lib/patter // Helpers // --------------------------------------------------------------------------- +/** Known non-file tokens that look like file paths */ +const FALSE_POSITIVE_PATHS = new Set([ + 'node.js', 'next.js', 'vue.js', 'react.js', 'express.js', 'nest.js', + 'bun.js', 'deno.js', 'nuxt.js', 'svelte.js', 'ember.js', 'angular.js', + 'three.js', 'd3.js', 'p5.js', 'e2e.js', +]); + /** Extract file paths from prompt text */ -function extractFilePaths(prompt: string): string[] { +export function extractFilePaths(prompt: string): string[] { + // Match sequences that look like paths: must contain a slash OR a known code extension + const CODE_EXTENSIONS = /\.(ts|tsx|js|jsx|mjs|cjs|json|yaml|yml|toml|md|css|scss|html|sql|sh|py|rb|go|rs|java|c|cpp|h|hpp|xml|env|lock|config|conf)$/; const matches = prompt.match(/[\w\-./\\]+\.\w{1,6}/g) || []; - return [...new Set(matches)]; + return [...new Set( + matches.filter(m => { + // Must have a slash (looks like a path) or a known code extension + if (FALSE_POSITIVE_PATHS.has(m.toLowerCase())) return false; + // Filter out version-like patterns: v1.2.3, 2.5x, etc. + if (/^v?\d+\.\d+/i.test(m)) return false; + return m.includes('/') || m.includes('\\') || CODE_EXTENSIONS.test(m); + }) + )]; } /** Verify files exist and return stats */ diff --git a/tests/tools/preflight-check.test.ts b/tests/tools/preflight-check.test.ts new file mode 100644 index 0000000..bab231d --- /dev/null +++ b/tests/tools/preflight-check.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect } from "vitest"; +// We exported extractFilePaths so we can test it directly +import { extractFilePaths } from "../../src/tools/preflight-check.js"; + +describe("extractFilePaths", () => { + it("extracts paths with directories", () => { + expect(extractFilePaths("fix src/auth/jwt.ts line 42")).toEqual(["src/auth/jwt.ts"]); + }); + + it("extracts standalone files with code extensions", () => { + expect(extractFilePaths("update README.md and package.json")).toEqual([ + "README.md", + "package.json", + ]); + }); + + it("filters out Node.js and other framework names", () => { + expect(extractFilePaths("use Node.js and Vue.js")).toEqual([]); + }); + + it("filters out version numbers", () => { + expect(extractFilePaths("upgrade to v3.2.0 for 2.5x speed")).toEqual([]); + }); + + it("handles mixed real paths and false positives", () => { + const result = extractFilePaths( + "in Node.js v18.0, fix src/lib/triage.ts and update config.yaml" + ); + expect(result).toEqual(["src/lib/triage.ts", "config.yaml"]); + }); + + it("deduplicates paths", () => { + expect(extractFilePaths("check src/index.ts then revisit src/index.ts")).toEqual([ + "src/index.ts", + ]); + }); + + it("returns empty for no file references", () => { + expect(extractFilePaths("fix the auth bug")).toEqual([]); + }); + + it("handles deeply nested paths", () => { + expect(extractFilePaths("look at src/lib/utils/helpers.ts")).toEqual([ + "src/lib/utils/helpers.ts", + ]); + }); +});