/** * Test discipline scanner. * * Deterministic static analysis of test files. Detects common test * anti-patterns without trusting the LLM. Each heuristic is a small pure * function with its own tests; the aggregate report is the only public * surface most callers use. * * Heuristics implemented: * 1. zero-assertion — test runs but never calls assert/expect/should * 2. over-mocking — more than N test-double calls in one test * 3. mirror-logic — assertion duplicates the production expression * 4. flaky-timing — sleep/setTimeout not wrapped in a polling helper * 5. no-aaa — multi-statement test with no AAA separators * 6. mystery-guest — file read with a string path not in fixturePaths * 7. private-method — call to method starting with _ or @private * 8. god-test — too many asserts OR too long a body * 9. weakened-assertion — skipped/disabled test, commented-out or * tautological assertion (deterministic NEVER-list backing) * 10. swallowed-error — empty catch / catch-and-return-null * 11. deleted-test-file — planned test file missing on disk (standalone * detector — a deleted file has no content to scan) * * Severity levels (set per-heuristic below): * - blocking — should halt advance in Phase 3 strict mode * - actionable — surfaced in approval summary; user may override * - informational — surfaced as a hint; no action required * * Bugfix runs escalate weakened-assertion / deleted-test-file to blocking * (and swallowed-error when the file is the fix target) — that gating lives * in signals.ts, which knows the mission type; severities here are the * non-bugfix defaults. */ export type FindingSeverity = "blocking" | "actionable" | "informational"; export type HeuristicId = "zero-assertion" | "over-mocking" | "mirror-logic" | "flaky-timing" | "no-aaa" | "mystery-guest" | "private-method" | "god-test" | "deleted-test-file" | "weakened-assertion" | "swallowed-error"; export interface Finding { heuristic: HeuristicId; severity: FindingSeverity; file: string; line: number; column: number; endLine: number; endColumn: number; message: string; excerpt: string; } export interface ScanReport { scannedFiles: number; findings: Finding[]; byHeuristic: Record; bySeverity: Record; blockingCount: number; } export interface HeuristicConfig { overMockingThreshold: number; godTestAssertThreshold: number; godTestBodyLengthThreshold: number; } export declare const DEFAULT_HEURISTIC_CONFIG: HeuristicConfig; export declare function detectZeroAssertion(content: string, filePath: string): Finding[]; export declare function detectOverMocking(content: string, filePath: string, threshold?: number): Finding[]; export declare function detectMirrorLogic(content: string, filePath: string): Finding[]; export declare function detectFlakyTiming(content: string, filePath: string): Finding[]; export declare function detectNoAAA(content: string, filePath: string): Finding[]; export declare function detectMysteryGuest(content: string, filePath: string, fixturePaths?: string[]): Finding[]; export declare function detectPrivateMethod(content: string, filePath: string): Finding[]; export declare function detectGodTest(content: string, filePath: string, assertThreshold?: number, bodyLengthThreshold?: number): Finding[]; export declare function detectWeakenedAssertion(content: string, filePath: string): Finding[]; export declare function detectSwallowedError(content: string, filePath: string): Finding[]; /** True for paths that look like a test file (test dir or .test/.spec suffix). */ export declare function isTestFilePath(p: string): boolean; /** Standalone detector: a planned test file (from the plan's `## Files` * manifest) that does not exist on disk. A deleted file has no content to * scan, so this runs against the manifest + disk instead of file bodies. */ export declare function detectDeletedTestFiles(plannedTestPaths: readonly string[], cwd: string): Finding[]; export declare function scanTestFiles(files: Array<{ path: string; content: string; }>, config?: Partial): ScanReport; export declare function scanTestFilesOnDisk(paths: string[], config?: Partial): ScanReport; /** * Resolve the test file paths for a run. Reads files.json codePaths + testPaths * and joins them with the cwd. Returns absolute paths. Returns an empty array * if files.json does not exist or has no test paths. */ export declare function resolveTestPaths(cwd: string, _runId?: string): string[];