Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe("Greeting agent", () => {
- **Classification Matcher**: Built-in `toBeClassifiedAs()` matcher to grade a response against a target label on a 1-5 scale
- **Concurrent Execution**: Runs eval files concurrently for faster test execution
- **Model Selection**: Support for specifying custom AI models
- **Runtime Selection**: Run prompts through GitHub Copilot (default) or Codex
- **Runtime Selection**: Run prompts through GitHub Copilot (default), Codex, or Claude Code
- **Configurable Timeouts**: Override prompt wait time per test or via `katt.json`

## Usage
Expand Down Expand Up @@ -157,11 +157,27 @@ Codex:
}
```

Claude Code:

```json
{
"agent": "claude-code",
"agentOptions": {
"model": "claude-sonnet-4",
"permissionMode": "acceptEdits"
},
"prompt": {
"timeoutMs": 240000
}
}
```

When this file exists:

- Supported agents are:
- `gh-copilot` (default when `agent` is missing or unsupported)
- `codex`
- `claude-code`
- `prompt("...")` and `promptFile("...")` merge `agentOptions` with call-time options
- `prompt("...", { model: "..." })` overrides the model from config
- `prompt.timeoutMs` sets the default wait timeout for long-running prompts
Expand All @@ -183,15 +199,18 @@ npm install
- `npm run format` - Format code using Biome
- `npm run lint` - Lint code using Biome
- `npm run test:build` - Test the built CLI
- `npm run test:build:codex` - Test the built CLI with `katt-codex.json`
- `npm run test:build:claude` - Test the built CLI with `katt-claude.json`

### Verification Process

To verify your changes before opening a pull request, run:

1. `npm test`
1. `npm run format`
2. `npm run typecheck`
3. `npm run lint`
4. `npm run format`
3. `npm run test`
4. `npm run build`
5. `npm run test:build`

For more details, see the [verification process section in CONTRIBUTING.md](./CONTRIBUTING.md#verification-process).
## How It Works
Expand Down Expand Up @@ -236,7 +255,7 @@ flowchart LR
Config --> Runtime
Runtime --> Assertions["Assertions + snapshots"]
Runtime --> Prompts["prompt() / promptFile()"]
Prompts --> AI["AI runtime (GitHub Copilot or Codex CLI)"]
Prompts --> AI["AI runtime (GitHub Copilot, Codex CLI, or Claude Code CLI)"]
Assertions --> Report["Terminal report + exit code"]
AI --> Report
```
Expand All @@ -246,6 +265,7 @@ flowchart LR
- Node.js
- For `gh-copilot` runtime: access to GitHub Copilot with a logged-in user
- For `codex` runtime: Codex CLI installed and authenticated (`codex login`)
- For `claude-code` runtime: Claude Code CLI installed and authenticated

## License

Expand Down
28 changes: 27 additions & 1 deletion docs/api-documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ This document lists the currently available Katt features and how to use them.
- AI-based classification matcher: `toBeClassifiedAs`
- Prompt execution with provider-specific runtime options
- Prompt loading from files with relative-path resolution
- Default runtime configuration via `katt.json` (or `--config-file`) for `gh-copilot`/`codex`
- Default runtime configuration via `katt.json` (or `--config-file`) for `gh-copilot`/`codex`/`claude-code`
- Configurable prompt timeout with a safer long-task default
- Automatic discovery and execution of `*.eval.js` and `*.eval.ts`
- Concurrent eval-file execution
Expand Down Expand Up @@ -157,6 +157,16 @@ Sends `input` to the AI model and returns the response string.
- `dangerouslyBypassApprovalsAndSandbox?: boolean`
- `config?: string | string[]` (forwarded as `codex exec --config`)
- `workingDirectory?: string`
- For `claude-code`: Claude Code CLI options:
- `model?: string`
- `permissionMode?: string` (forwarded as `--permission-mode`)
- `dangerouslySkipPermissions?: boolean`
- `maxTurns?: number`
- `allowedTools?: string | string[]`
- `disallowedTools?: string | string[]`
- `appendSystemPrompt?: string`
- `mcpConfig?: string`
- `workingDirectory?: string`
- `timeoutMs?: number` to control how long to wait for prompt completion
- Explicit options override matching keys from config `agentOptions`

Expand Down Expand Up @@ -221,10 +231,26 @@ Or use Codex:
}
```

Or use Claude Code:

```json
{
"agent": "claude-code",
"agentOptions": {
"model": "claude-sonnet-4",
"permissionMode": "acceptEdits"
},
"prompt": {
"timeoutMs": 240000
}
}
```

Behavior:
- Supported agents:
- `gh-copilot` (default when `agent` is missing or unsupported)
- `codex`
- `claude-code`
- Default config location is `<cwd>/katt.json`
- CLI override: `--config-file <path>` or `--config-file=<path>`
- Relative paths are resolved from `process.cwd()`
Expand Down
3 changes: 2 additions & 1 deletion docs/articles/introduction-to-katt.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ Katt runs as a Node.js CLI and executes prompts through a selectable runtime:

- `gh-copilot` (default): GitHub Copilot via `@github/copilot-sdk`
- `codex`: Codex via `codex exec`
- `claude-code`: Claude Code via `claude -p`

In practice that means your machine/CI runner should be configured for the
runtime you choose in `katt.json`.
Expand All @@ -254,7 +255,7 @@ Katt’s core idea—**agentic workflows deserve tests**—is broader than any s

Future directions that fit naturally with Katt’s model include:

- **Additional runtimes/adapters**: first-class integrations for other agentic tools (for example Claude Code and others), so the same eval files can validate workflows across environments.
- **Additional runtimes/adapters**: first-class integrations for more agentic tools, so the same eval files can validate workflows across environments.
- **Better eval ergonomics**: more helpers to standardize prompts, assertions, and repeatable fixtures.

These are intentionally framed as *roadmap ideas*: check the repo docs/specs for what’s supported today.
Expand Down
4 changes: 4 additions & 0 deletions katt-claude.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"agent": "claude-code",
"ignorePatterns": ["./examples/**"]
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"test": "vitest",
"typecheck": "tsc -p tsconfig.json --noEmit",
"test:build": "node ./dist/katt.js",
"test:build:codex": "node ./dist/katt.js --config-file=./katt-codex.json"
"test:build:codex": "node ./dist/katt.js --config-file=./katt-codex.json",
"test:build:claude": "node ./dist/katt.js --config-file=./katt-claude.json"
},
"types": "dist/index.d.ts",
"devDependencies": {
Expand Down
14 changes: 10 additions & 4 deletions specs/configuration.spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Top-level JSON object with optional `agent`, `agentOptions`, `prompt`, and
Supported keys:

- `agent?: string`
- Supported values: `"gh-copilot"`, `"codex"`
- Supported values: `"gh-copilot"`, `"codex"`, `"claude-code"`
- Missing/unsupported values default runtime selection to `"gh-copilot"`
- `agentOptions?: object`
- `agentOptions.model?: string`
Expand All @@ -53,6 +53,11 @@ Supported keys:
- `profile`, `sandbox`, `fullAuto`, `skipGitRepoCheck`,
`dangerouslyBypassApprovalsAndSandbox`, `config`, `workingDirectory`
- unsupported keys are ignored by the Codex runner
- `claude-code`: supported keys are translated to `claude -p` flags:
- `permissionMode`, `dangerouslySkipPermissions`, `maxTurns`,
`allowedTools`, `disallowedTools`, `appendSystemPrompt`, `mcpConfig`,
`workingDirectory`
- unsupported keys are ignored by the Claude Code runner
- `prompt?: object`
- `prompt.timeoutMs?: number` (positive values only)
- `ignorePatterns?: string[]`
Expand All @@ -78,7 +83,7 @@ value.
- `promptTimeoutMs: undefined`
4. Parses JSON content.
5. Resolves `agent`:
- Uses `agent` when it is `"gh-copilot"` or `"codex"`
- Uses `agent` when it is `"gh-copilot"`, `"codex"`, or `"claude-code"`
- Otherwise falls back to `"gh-copilot"`
6. Returns `agentOptions` only when:
- `agent` in file is a supported value, and
Expand All @@ -92,7 +97,7 @@ value.

1. Reads defaults via `getDefaultKattConfig()`.
2. Returns `agentOptions` only when selected `agent` is `"gh-copilot"`.
3. Returns `undefined` when selected `agent` is `"codex"`.
3. Returns `undefined` when selected `agent` is `"codex"` or `"claude-code"`.

`getDefaultPromptTimeoutMs()`:

Expand Down Expand Up @@ -148,7 +153,7 @@ value.
For `prompt(input, options?)` and `promptFile(filePath, options?)`:

1. Resolve active runtime from config `agent`:
- `"gh-copilot"` or `"codex"`
- `"gh-copilot"`, `"codex"`, or `"claude-code"`
- fallback `"gh-copilot"` when missing/unsupported
2. Start from config `agentOptions` when available for the selected
runtime.
Expand All @@ -158,6 +163,7 @@ For `prompt(input, options?)` and `promptFile(filePath, options?)`:
5. Execute with selected runtime:
- `gh-copilot`: create Copilot session with merged options
- `codex`: run `codex exec` with mapped supported options
- `claude-code`: run `claude -p` with mapped supported options
6. Resolve prompt timeout with precedence:
- `options.timeoutMs` (valid positive number)
- config `prompt.timeoutMs` (valid positive number)
Expand Down
13 changes: 12 additions & 1 deletion specs/feature-prompt.spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Execution/discovery flow for eval files and CLI exit handling is specified in
- default: `<cwd>/katt.json`
- CLI override: `--config-file <path>`
2. Resolves active runtime:
- `gh-copilot` or `codex`
- `gh-copilot`, `codex`, or `claude-code`
- defaults to `gh-copilot` when `agent` is missing/unsupported.
3. Resolves runtime options:
- merges config `agentOptions` (base) with explicit `options`
Expand Down Expand Up @@ -66,6 +66,14 @@ Execution/discovery flow for eval files and CLI exit handling is specified in
- `Error("Codex did not return a response.")` when no output is
available
- timeout/process errors when Codex fails to execute successfully.
- `claude-code`:
1. runs `claude -p` non-interactively with `--output-format json`
2. passes supported options as command flags
3. sends prompt through stdin
4. parses JSON response payload and extracts final result text
5. returns response text
6. throws for invalid/missing JSON output, missing result text, or
timeout/process failures.

### Cleanup guarantees for `prompt()`

Expand All @@ -77,6 +85,7 @@ Execution/discovery flow for eval files and CLI exit handling is specified in
- `Copilot cleanup encountered <N> error(s).`
- `codex` runtime always removes temporary output artifacts used for response
extraction.
- `claude-code` runtime has no temporary output artifact cleanup step.

Cleanup logging does not replace the primary operation result/error.

Expand All @@ -95,6 +104,8 @@ Cleanup logging does not replace the primary operation result/error.
- Copilot/session errors in `prompt()` are propagated to the caller.
- Codex process startup/exit/timeout errors in `prompt()` are propagated to the
caller.
- Claude Code process startup/exit/timeout errors in `prompt()` are propagated
to the caller.
- Missing response content in `prompt()` is converted to the explicit runtime
errors defined above.

Expand Down
37 changes: 37 additions & 0 deletions src/lib/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,19 @@ describe("getDefaultCopilotConfig", () => {
expect(result).toBeUndefined();
});

it("returns undefined when agent is claude-code", async () => {
readFileMock.mockResolvedValue(
JSON.stringify({
agent: "claude-code",
agentOptions: { model: "claude-sonnet-4" },
}),
);

const result = await getDefaultCopilotConfig();

expect(result).toBeUndefined();
});

it("returns undefined when agentOptions is not an object", async () => {
readFileMock.mockResolvedValue(
JSON.stringify({
Expand Down Expand Up @@ -303,6 +316,30 @@ describe("getDefaultKattConfig", () => {
});
});

it("returns claude-code defaults when configured", async () => {
readFileMock.mockResolvedValue(
JSON.stringify({
agent: "claude-code",
agentOptions: {
model: "claude-sonnet-4",
permissionMode: "acceptEdits",
},
prompt: { timeoutMs: 450000 },
}),
);

const result = await getDefaultKattConfig();

expect(result).toEqual({
agent: "claude-code",
agentOptions: {
model: "claude-sonnet-4",
permissionMode: "acceptEdits",
},
promptTimeoutMs: 450000,
});
});

it("reads custom config path when configured", async () => {
setKattConfigFilePath("./config/custom-katt.json");
readFileMock.mockResolvedValue(
Expand Down
4 changes: 2 additions & 2 deletions src/lib/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { readFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import type { SessionConfig } from "@github/copilot-sdk";

export type KattAgent = "gh-copilot" | "codex";
export type KattAgent = "gh-copilot" | "codex" | "claude-code";

type KattConfig = {
agent?: unknown;
Expand Down Expand Up @@ -112,7 +112,7 @@ async function readKattConfig(): Promise<KattConfig | undefined> {
}

function readSupportedAgent(agent: unknown): KattAgent | undefined {
if (agent === "gh-copilot" || agent === "codex") {
if (agent === "gh-copilot" || agent === "codex" || agent === "claude-code") {
return agent;
}

Expand Down
Loading
Loading