Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.PHONY: sandbox-setup sandbox-clean dev build web update-analytics-data test clear-data

sandbox-setup: build
./build/skillwalker sandbox setup
./build/skillwalker sandbox create

sandbox-clean:
sbx rm --force claude-skills-skillwalker
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,16 @@ All commands run from the **repository root**.

```bash
sbx login
./build/skillwalker sandbox setup
./build/skillwalker sandbox create
```

Complete the login in the Claude TUI. If you aren't prompted, run `/login`.
When setup finishes, exit with `/exit`.

To move to a newer Claude Code release later, run
`./build/skillwalker sandbox update`. It deletes the sandbox and recreates it
from the latest template, so you will log in again.

You're ready. Now choose what you want to measure.

## What do you want to measure?
Expand Down
2 changes: 1 addition & 1 deletion docs/agent-call-improvement-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ At the end of every iteration, ACIL prints a progress summary. When the loop exi

```bash
make build
./build/skillwalker sandbox setup
./build/skillwalker sandbox create
```

## Eval Requirements
Expand Down
20 changes: 12 additions & 8 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The `@testdouble/skillwalker-cli` package is the command-line entry point for Sk

## Summary

- Six top-level commands exposed via the `skillwalker` binary: `test-run`, `test-eval`, `scil`, `acil`, `update-analytics-data`, and `sandbox`. The Test Sandbox lifecycle lives under `sandbox` as three sub-commands: `sandbox setup`, `sandbox clean`, and `sandbox shell`
- Six top-level commands exposed via the `skillwalker` binary: `test-run`, `test-eval`, `scil`, `acil`, `update-analytics-data`, and `sandbox`. The Test Sandbox lifecycle lives under `sandbox` as four sub-commands: `sandbox create`, `sandbox update`, `sandbox clean`, and `sandbox shell`
- All test execution happens inside a Test Sandbox via `@testdouble/sandbox-integration`, with Claude invoked through `@testdouble/claude-integration`
- Two test runner types handle different test kinds: prompt tests (full Claude sessions) and skill-call tests (trigger detection with temporary stripped-down plugins)
- The SCIL (Skill Call Improvement Loop) command iteratively improves skill descriptions by running evaluation cycles and using Claude to generate better descriptions
Expand All @@ -38,15 +38,16 @@ flowchart TB
acil["acil"]
analytics["update-analytics"]
sandbox["sandbox"]
setup["sandbox setup"]
create["sandbox create"]
update["sandbox update"]
clean["sandbox clean"]
shell["sandbox shell"]

prompt["prompt runner<br>skill-call runner"]
evals["skillwalker-evals<br>evaluate TestRun"]
scilsteps["scil steps 1-10<br>loop.ts"]
acilsteps["acil steps 1-10<br>loop.ts"]
si["sandbox-integration<br>openShell · removeSandbox · createSandbox"]
si["sandbox-integration<br>openShell · removeSandbox · createSandbox · updateSandbox"]

data["<b>@testdouble/skillwalker-data</b><br>types, config, JSONL I/O, analytics, SCIL, ACIL"]

Expand All @@ -57,7 +58,8 @@ flowchart TB
dispatch --> acil --> acilsteps --> data
dispatch --> analytics --> data
dispatch --> sandbox
sandbox --> setup --> si
sandbox --> create --> si
sandbox --> update --> si
sandbox --> clean --> si
sandbox --> shell --> si
```
Expand All @@ -73,10 +75,11 @@ flowchart TB
| `packages/cli/src/commands/scil.ts` | `scil` command — entry point for the Skill Call Improvement Loop |
| `packages/cli/src/commands/acil.ts` | `acil` command — entry point for the Agent Call Improvement Loop |
| `packages/cli/src/commands/update-analytics.ts` | `update-analytics-data` command — imports JSONL to Parquet |
| `packages/cli/src/commands/sandbox.ts` | `sandbox` parent command — registers the three sub-commands below |
| `packages/cli/src/commands/sandbox.ts` | `sandbox` parent command — registers the four sub-commands below |
| `packages/cli/src/commands/sandbox/shell.ts` | `sandbox shell` sub-command — opens interactive shell in Test Sandbox |
| `packages/cli/src/commands/sandbox/clean.ts` | `sandbox clean` sub-command — removes the Test Sandbox |
| `packages/cli/src/commands/sandbox/setup.ts` | `sandbox setup` sub-command — creates sandbox and authenticates via OAuth |
| `packages/cli/src/commands/sandbox/create.ts` | `sandbox create` sub-command — creates sandbox and authenticates via OAuth |
| `packages/cli/src/commands/sandbox/update.ts` | `sandbox update` sub-command — deletes the sandbox and recreates it from the latest Claude Code template |

## Core Types

Expand All @@ -93,7 +96,7 @@ Each command module in `packages/cli/src/commands/` is a thin Yargs wrapper that
- **scil** — Calls `runScilLoop()` from the execution package for iterative skill description improvement
- **acil** — Calls `runAcilLoop()` from the execution package for iterative agent description improvement
- **update-analytics-data** — Calls the execution package's analytics ingestion
- **sandbox setup** / **sandbox clean** / **sandbox shell** — Delegate to `@testdouble/sandbox-integration` for sandbox lifecycle. The `sandbox` parent holds no logic of its own; it registers the three sub-commands and requires one of them
- **sandbox create** / **sandbox update** / **sandbox clean** / **sandbox shell** — Delegate to `@testdouble/sandbox-integration` for sandbox lifecycle. The `sandbox` parent holds no logic of its own; it registers the four sub-commands and requires one of them

See [execution.md](./execution.md) for implementation details of each pipeline (test-run steps, test-eval steps, SCIL loop, ACIL loop, concurrency pool, scoring, error hierarchy).

Expand Down Expand Up @@ -128,7 +131,8 @@ The CLI catches `SkillwalkerError` at the top level (`index.ts`) and writes the
- `packages/cli/src/commands/scil.test.ts` — Tests `scil` command builder and handler
- `packages/cli/src/commands/acil.test.ts` — Tests `acil` command builder and handler
- `packages/cli/src/commands/update-analytics.test.ts` — Tests `update-analytics-data` command
- `packages/cli/src/commands/sandbox/setup.test.ts` — Tests `sandbox setup` sub-command
- `packages/cli/src/commands/sandbox/create.test.ts` — Tests `sandbox create` sub-command
- `packages/cli/src/commands/sandbox/update.test.ts` — Tests `sandbox update` sub-command
- `packages/cli/src/commands/sandbox/clean.test.ts` — Tests `sandbox clean` sub-command
- `packages/cli/src/commands/sandbox/shell.test.ts` — Tests `sandbox shell` sub-command

Expand Down
37 changes: 27 additions & 10 deletions docs/sandbox-integration-package.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The `@testdouble/sandbox-integration` package is the single point of contact for
No other package in Skillwalker spawns `sbx` processes directly. All Sandbox CLI access is funneled through this package, which provides two categories of functionality:

1. **Sandbox execution** -- verifying the sandbox exists and running commands inside it (`ensureSandboxExists`, `execInSandbox`)
2. **Lifecycle management** -- creating, removing, and opening interactive shells in the sandbox (`createSandbox`, `removeSandbox`, `openShell`)
2. **Lifecycle management** -- creating, removing, and opening interactive shells in the sandbox (`createSandbox`, `updateSandbox`, `removeSandbox`, `openShell`)

The package returns a clean `SandboxResult` type instead of exposing raw `Bun.spawn` process handles, giving consumers a stable interface decoupled from the process spawning implementation.

Expand All @@ -26,7 +26,7 @@ All public symbols are re-exported from the barrel file `index.ts`:

```typescript
export { SANDBOX_NAME, ensureSandboxExists, execInSandbox } from './src/sandbox.js'
export { createSandbox, removeSandbox, openShell } from './src/lifecycle.js'
export { createSandbox, openShell, removeSandbox, updateSandbox } from './src/lifecycle.js'
export { SandboxError } from './src/errors.js'
export type { SandboxResult } from './src/types.js'
```
Expand Down Expand Up @@ -72,7 +72,7 @@ class SandboxError extends Error {
async function ensureSandboxExists(): Promise<void>
```

Pre-flight check that the sandbox is running. Runs `sbx ls --quiet` and verifies `SANDBOX_NAME` exactly matches one output line. Throws `SandboxError` with `exitCode: null` if the sandbox is not found, with a message directing the user to run `./build/skillwalker sandbox setup`.
Pre-flight check that the sandbox is running. Runs `sbx ls --quiet` and verifies `SANDBOX_NAME` exactly matches one output line. Throws `SandboxError` with `exitCode: null` if the sandbox is not found, with a message directing the user to run `./build/skillwalker sandbox create`.

**Consumers:**
- `cli/src/commands/test-run.ts` -- before the per-eval test loop
Expand Down Expand Up @@ -110,7 +110,7 @@ async function createSandbox(repoRoot: string): Promise<void>

Checks whether the sandbox already exists via an internal `sandboxExists()` helper (runs `sbx ls --quiet`). If found, prints a help message to stderr explaining how to recreate it, and returns early. Otherwise, spawns `sbx run --name claude-skills-skillwalker claude <repoRoot>` with inherited stdio for interactive OAuth login. Prints progress messages to stderr.

**Consumer:** `cli/src/commands/sandbox/setup.ts`
**Consumer:** `cli/src/commands/sandbox/create.ts`

#### removeSandbox()

Expand All @@ -122,6 +122,22 @@ Runs `sbx rm --force claude-skills-skillwalker`. Drains stdout and stderr in par

**Consumer:** `cli/src/commands/sandbox/clean.ts` -- catches `SandboxError` and re-throws as `SkillwalkerError`

#### updateSandbox()

```typescript
async function updateSandbox(repoRoot: string): Promise<void>
```

Replaces the sandbox with one built from the latest Claude Code template. `sbx` has no pull command and reuses a cached template image, so this function:

1. Removes the sandbox with `removeSandbox()`, if it exists.
2. Lists templates with `sbx template ls` and removes each cached image whose repository is `docker/sandbox-templates` and whose tag starts with `claude-code`, using `sbx template rm <image id>`.
3. Calls `createSandbox(repoRoot)`, which makes `sbx run` fetch the current template.

`sbx template ls` can list one image under several IDs, and removing the first ID removes them all. A later `rm` that reports `no template image` is therefore treated as already removed. Any other listing or removal failure throws `SandboxError`.

**Consumer:** `cli/src/commands/sandbox/update.ts` -- catches `SandboxError` and re-throws as `SkillwalkerError`

#### openShell()

```typescript
Expand All @@ -146,7 +162,7 @@ Contains the `SandboxResult` interface (see Core Types above).
flowchart TB
subgraph cli["@testdouble/skillwalker-cli"]
direction LR
commands["<b>commands/</b><br>sandbox/setup · sandbox/clean<br>sandbox/shell · test-run"]
commands["<b>commands/</b><br>sandbox/create · sandbox/clean<br>sandbox/shell · test-run"]
scil["<b>scil/</b><br>loop"]
end

Expand All @@ -155,7 +171,7 @@ flowchart TB
subgraph si["@testdouble/sandbox-integration"]
direction TB
sandboxts["<b>sandbox.ts</b><br>ensureSandboxExists()<br>execInSandbox()<br>SANDBOX_NAME"]
lifecycle["<b>lifecycle.ts</b><br>createSandbox()<br>removeSandbox()<br>openShell()"]
lifecycle["<b>lifecycle.ts</b><br>createSandbox()<br>updateSandbox()<br>removeSandbox()<br>openShell()"]
types["<b>types.ts</b><br>SandboxResult"]
errors["<b>errors.ts</b><br>SandboxError"]

Expand All @@ -178,7 +194,8 @@ flowchart TB

| Consumer | Imports |
|----------|---------|
| `cli/src/commands/sandbox/setup.ts` | `createSandbox` |
| `cli/src/commands/sandbox/create.ts` | `createSandbox` |
| `cli/src/commands/sandbox/update.ts` | `updateSandbox`, `SandboxError` |
| `cli/src/commands/sandbox/clean.ts` | `removeSandbox`, `SANDBOX_NAME`, `SandboxError` |
| `cli/src/commands/sandbox/shell.ts` | `openShell` |
| `cli/src/commands/test-run.ts` | `ensureSandboxExists` |
Expand All @@ -189,7 +206,7 @@ flowchart TB

| Scenario | Error Type | Behavior |
|----------|------------|----------|
| Sandbox not found by `ensureSandboxExists` | `SandboxError` (exitCode: `null`) | Thrown with message suggesting `./build/skillwalker sandbox setup` |
| Sandbox not found by `ensureSandboxExists` | `SandboxError` (exitCode: `null`) | Thrown with message suggesting `./build/skillwalker sandbox create` |
| `sbx rm` fails | `SandboxError` (exitCode: process code) | Thrown with stdout+stderr in message |
| Non-zero exit from `execInSandbox` | No error thrown | Returned in `SandboxResult.exitCode`; caller decides |
| `proc.exitCode` is null in `execInSandbox` | No error thrown | Defaults to `1` in `SandboxResult` |
Expand All @@ -202,7 +219,7 @@ Three test files with full coverage of the public API:
|------|--------|
| `src/errors.test.ts` | `SandboxError` construction, properties, inheritance |
| `src/sandbox.test.ts` | `ensureSandboxExists`, `execInSandbox` |
| `src/lifecycle.test.ts` | `removeSandbox`, `createSandbox`, `openShell` |
| `src/lifecycle.test.ts` | `removeSandbox`, `createSandbox`, `updateSandbox`, `openShell` |

### Test Patterns

Expand Down Expand Up @@ -245,7 +262,7 @@ function makeStream(content: string): ReadableStream<Uint8Array> {
| `src/types.ts` | `SandboxResult` interface |
| `src/errors.ts` | `SandboxError` class |
| `src/sandbox.ts` | `SANDBOX_NAME`, `ensureSandboxExists`, `execInSandbox` |
| `src/lifecycle.ts` | `createSandbox`, `removeSandbox`, `openShell` |
| `src/lifecycle.ts` | `createSandbox`, `updateSandbox`, `removeSandbox`, `openShell` |
| `src/errors.test.ts` | Unit tests for `SandboxError` |
| `src/sandbox.test.ts` | Unit tests for sandbox execution functions |
| `src/lifecycle.test.ts` | Unit tests for lifecycle management functions |
Expand Down
32 changes: 22 additions & 10 deletions docs/sandbox-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ Centralized package for all Test Sandbox interactions in Skillwalker — creatin
## Summary

- The `@testdouble/sandbox-integration` package is the single point of contact for all Sandbox CLI commands in Skillwalker. No other package spawns `sbx` processes directly.
- Provides two categories of functions: **sandbox execution** (`ensureSandboxExists`, `execInSandbox`) for running Claude inside the sandbox, and **lifecycle management** (`createSandbox`, `removeSandbox`, `openShell`) for managing the sandbox itself.
- Provides two categories of functions: **sandbox execution** (`ensureSandboxExists`, `execInSandbox`) for running Claude inside the sandbox, and **lifecycle management** (`createSandbox`, `updateSandbox`, `removeSandbox`, `openShell`) for managing the sandbox itself.
- Uses Docker Desktop sandboxes (not traditional containers) via the `sbx` CLI subcommands.
- Returns a clean `SandboxResult` type instead of exposing raw `Bun.spawn` process handles.

Key files:
- `packages/sandbox-integration/index.ts` — Public API barrel export
- `packages/sandbox-integration/src/sandbox.ts` — `ensureSandboxExists`, `execInSandbox`, `SANDBOX_NAME`
- `packages/sandbox-integration/src/lifecycle.ts` — `createSandbox`, `removeSandbox`, `openShell`
- `packages/sandbox-integration/src/lifecycle.ts` — `createSandbox`, `updateSandbox`, `removeSandbox`, `openShell`
- `packages/sandbox-integration/sandbox-run.sh` — Shell script executed inside the sandbox to prepare the working directory and invoke Claude

## Architecture
Expand All @@ -29,15 +29,15 @@ Key files:
flowchart TB
subgraph cli["@testdouble/skillwalker-cli"]
direction LR
commands["<b>commands/</b><br>sandbox/clean · sandbox/shell<br>sandbox/setup · test-run"]
commands["<b>commands/</b><br>sandbox/clean · sandbox/shell<br>sandbox/create · test-run"]
runners["<b>test-runners/</b><br>prompt/ · skill-call/"]
scil["<b>scil/</b><br>loop · step-5 · step-7"]
end

subgraph si["@testdouble/sandbox-integration"]
direction TB
sandboxts["<b>sandbox.ts</b><br>ensureSandboxExists()<br>execInSandbox()<br>SANDBOX_NAME"]
lifecycle["<b>lifecycle.ts</b><br>createSandbox()<br>removeSandbox()<br>openShell()"]
lifecycle["<b>lifecycle.ts</b><br>createSandbox()<br>updateSandbox()<br>removeSandbox()<br>openShell()"]
runsh["<b>sandbox-run.sh</b><br>(runs inside Docker)"]

lifecycle --> sandboxts
Expand All @@ -59,7 +59,7 @@ flowchart TB
| `packages/sandbox-integration/package.json` | Package metadata (`@testdouble/sandbox-integration`) |
| `packages/sandbox-integration/index.ts` | Barrel re-export of all public symbols |
| `packages/sandbox-integration/src/sandbox.ts` | `SANDBOX_NAME`, `ensureSandboxExists`, `execInSandbox` |
| `packages/sandbox-integration/src/lifecycle.ts` | `createSandbox`, `removeSandbox`, `openShell` |
| `packages/sandbox-integration/src/lifecycle.ts` | `createSandbox`, `updateSandbox`, `removeSandbox`, `openShell` |
| `packages/sandbox-integration/src/types.ts` | `SandboxResult` interface |
| `packages/sandbox-integration/src/errors.ts` | `SandboxError` class |
| `packages/sandbox-integration/sandbox-run.sh` | Scaffold setup and Claude invocation inside the sandbox |
Expand Down Expand Up @@ -171,7 +171,17 @@ export async function createSandbox(repoRoot: string): Promise<void>

Checks if the sandbox already exists via an internal `sandboxExists()` helper. If it does, prints a help message to stderr and returns. Otherwise, spawns `sbx run --name claude-skills-skillwalker claude <repoRoot>` with inherited stdio for interactive OAuth login.

Called by `commands/sandbox/setup.ts`.
Called by `commands/sandbox/create.ts`.

#### updateSandbox

```typescript
export async function updateSandbox(repoRoot: string): Promise<void>
```

Removes the sandbox if it exists, then removes every cached `docker/sandbox-templates` image tagged `claude-code*` (found with `sbx template ls`). Finally it calls `createSandbox`, so `sbx run` fetches the latest Claude Code template. `sbx` has no pull command, so deleting the cached image is the only way to get a newer one. An `rm` that reports `no template image` counts as already removed, because `sbx template ls` can list one image under several IDs. Any other listing or removal failure throws `SandboxError`.

Called by `commands/sandbox/update.ts`, which catches `SandboxError` and re-throws as `SkillwalkerError`.

#### removeSandbox

Expand Down Expand Up @@ -212,7 +222,7 @@ See [Cross-Runtime Meta Property Resolution](coding-standards/cross-runtime-meta

| Scenario | Error Type | Behavior |
|----------|------------|----------|
| Sandbox not found by `ensureSandboxExists` | `SandboxError` (exitCode: `null`) | Thrown with message suggesting `./build/skillwalker sandbox setup` |
| Sandbox not found by `ensureSandboxExists` | `SandboxError` (exitCode: `null`) | Thrown with message suggesting `./build/skillwalker sandbox create` |
| `sbx rm` fails | `SandboxError` (exitCode: process code) | Thrown with stdout+stderr in message |
| Non-zero exit code from `execInSandbox` | No error thrown | Returned in `SandboxResult.exitCode`; caller decides |
| `execInSandbox` with `proc.exitCode` null | No error thrown | `exitCode` defaults to `1` in `SandboxResult` |
Expand All @@ -231,7 +241,7 @@ See [Cross-Runtime Meta Property Resolution](coding-standards/cross-runtime-meta

- `packages/sandbox-integration/src/errors.test.ts` — `SandboxError` construction and properties
- `packages/sandbox-integration/src/sandbox.test.ts` — `ensureSandboxExists` and `execInSandbox` with mocked `Bun.spawn`
- `packages/sandbox-integration/src/lifecycle.test.ts` — `removeSandbox`, `createSandbox`, `openShell` with mocked `Bun.spawn` and `sandbox.js`
- `packages/sandbox-integration/src/lifecycle.test.ts` — `removeSandbox`, `createSandbox`, `updateSandbox`, `openShell` with mocked `Bun.spawn` and `sandbox.js`

### Test Patterns

Expand Down Expand Up @@ -268,15 +278,17 @@ Modules under test are imported dynamically inside each `it` block via `await im

If `ensureSandboxExists` throws `SandboxError`, run:

1. `./build/skillwalker sandbox setup` — creates the sandbox and completes OAuth
1. `./build/skillwalker sandbox create` — creates the sandbox and completes OAuth
2. Verify with `sbx ls --quiet` — should list `claude-skills-skillwalker`

### Sandbox already exists during setup

`createSandbox` returns early with a help message. To recreate:

1. `sbx rm --force claude-skills-skillwalker`
2. `./build/skillwalker sandbox setup`
2. `./build/skillwalker sandbox create`

To recreate it from the latest Claude Code template instead, run `./build/skillwalker sandbox update`.

### Tests fail with "Cannot read properties of undefined (reading 'exited')"

Expand Down
Loading
Loading