How It Works
testpilot-ai uses a four-stage pipeline to generate tests:
Source File → Analyzer → Prompt Engine → LLM → Test Writer → Test File
1. Code Analysis
The analyzer uses the TypeScript compiler API (ts.createSourceFile) to parse your source file into an AST. It extracts:
- Exported functions — name, parameters (types, optional, defaults), return type, async flag
- Exported classes — name, method signatures
- Arrow functions — treated like regular functions
- Interfaces & types — skipped for test generation but included as context
- Enums — included
- Imports — tracked to understand dependencies
- JSDoc comments — passed to the LLM for better understanding
This AST-based approach is far more reliable than regex-based or heuristic parsing. It handles TypeScript generics, overloads, decorators, and complex type annotations correctly.
2. Prompt Engineering
The prompt engine builds two prompts:
System Prompt
Establishes the LLM's role as an expert test engineer with specific rules:
- Framework-specific syntax (Vitest vs Jest)
- Import conventions
- Mocking strategies
- Test structure guidelines
User Prompt
Contains the actual analysis with:
- File metadata (name, language)
- Each exported symbol with full type information
- The exact import line to use
- Edge case and error handling requirements
- The complete source code
The prompt explicitly tells the LLM which import line to use and which functions to test, reducing hallucination.
3. LLM Generation
The LLM client sends prompts via aiclientjs to any supported provider. It supports:
- Streaming — chunks are printed to the terminal in real-time
- Any provider — OpenAI, Anthropic, Google, Ollama
- Token tracking — reports total tokens used
4. Test Writing
The test writer post-processes the LLM output:
- Strips markdown fences — LLMs often wrap code in
```typescriptblocks - Strips preamble text — removes "Here are the tests:" type commentary
- Counts tests — parses
it()andtest()calls - Categorizes — groups tests by
describe()blocks - Writes the file — creates directories if needed, respects
--overwrite
Architecture
src/
├── analyzer/ # TypeScript AST-based code analysis
├── prompt/ # Context-rich prompt generation
├── llm/ # LLM client (via aiclientjs)
├── writer/ # Test output parser & file writer
├── frameworks/ # Vitest & Jest adapters
├── config/ # Config file resolution & merging
├── generate.ts # Main orchestrator
├── cli.ts # Commander-based CLI
├── types.ts # Core type definitions
└── index.ts # Public API exports
Design Principles
- Single Responsibility — Each module has one clear job
- Open/Closed — Add new providers or frameworks without modifying existing code
- Dependency Inversion — Core logic depends on abstractions (aiclientjs handles provider details)
- Zero magic — Transparent AST analysis, no hidden heuristics