xchecker 1.2.0

Spec pipeline with receipts and gateable JSON contracts
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
# Configuration Guide


This guide walks you through configuring xchecker for common scenarios. For a
complete reference of every config key, environment variable, and CLI flag, see
[Configuration Reference](../reference/CONFIGURATION.md).

---

## Where xchecker looks for configuration


xchecker resolves settings in this order (highest priority first):

1. **CLI flags** -- `--model sonnet`, `--phase-timeout 1200`, etc.
2. **Environment variables** -- `XCHECKER_LLM_PROVIDER`, `XCHECKER_OPENROUTER_BUDGET`, etc.
3. **Configuration file** -- `.xchecker/config.toml`
4. **Built-in defaults**

### Config file discovery


xchecker searches upward from the current working directory for
`.xchecker/config.toml`. The search stops at the filesystem root or a Git
repository root (`.git` directory). Override with `--config <path>`.

### XCHECKER_HOME


By default, xchecker stores all state in `./.xchecker` relative to your working
directory. Override this with the `XCHECKER_HOME` environment variable:

```bash
# Isolate state per CI build

export XCHECKER_HOME=/tmp/xchecker-build-${BUILD_ID}

# Or inline for a single command

XCHECKER_HOME=/tmp/test xchecker status my-feature
```

The resolution order (highest priority first):

1. **Thread-local override** -- `with_isolated_home()` for test isolation
2. **`XCHECKER_HOME` environment variable** -- explicit directory override
3. **Default** -- `./.xchecker` relative to working directory

The state directory structure:

```
.xchecker/
  config.toml              # Configuration file (optional)
  specs/<spec-id>/
    artifacts/             # Generated phase outputs
    receipts/              # Execution audit trails
    context/               # Packet previews for debugging
```

For test isolation, `with_isolated_home()` sets a thread-local override that
takes precedence over the environment variable, avoiding process-global
`set_var` races in parallel tests.

---

## Basic setup


Create `.xchecker/config.toml` with a provider and model:

```toml
[llm]
provider = "claude-cli"

[defaults]
model = "sonnet"
```

That is enough to run `xchecker spec my-feature`. Everything else has sensible
defaults.

Verify your setup:

```bash
xchecker doctor
```

---

## File selectors


Selectors control which files are gathered into the context packet sent to the
LLM. Use glob patterns:

```toml
[selectors]
include = [
    "src/**/*.rs",
    "docs/**/*.md",
    "Cargo.toml",
]

exclude = [
    "target/**",
    "node_modules/**",
    ".git/**",
    "*.log",
]
```

### Pattern syntax


| Pattern | Meaning |
|---------|---------|
| `*` | Any characters except `/` |
| `**` | Any characters including `/` (recursive) |
| `?` | Any single character |
| `[abc]` | Any character in the set |
| `{a,b}` | Either `a` or `b` |

### Tips


- Prefer specific `include` patterns over broad exclusions.
- If you hit a "packet overflow" error, narrow your includes or increase
  `packet_max_bytes`:

```bash
xchecker spec my-feature --packet-max-bytes 131072
```

---

## LLM provider configuration


xchecker supports four providers. Set the provider in config or via CLI flag:

```toml
[llm]
provider = "claude-cli"   # or "gemini-cli", "openrouter", "anthropic"
```

### Claude CLI (default)


No additional config needed if `claude` is on your PATH:

```toml
[llm]
provider = "claude-cli"

# Optional: custom binary path

[llm.claude]
binary = "/usr/local/bin/claude"
```

### Gemini CLI


```toml
[llm]
provider = "gemini-cli"

[llm.gemini]
default_model = "gemini-2.0-flash-lite"
```

Requires `GEMINI_API_KEY` in your environment.

### OpenRouter


```toml
[llm]
provider = "openrouter"

[llm.openrouter]
model = "google/gemini-2.0-flash-lite"
max_tokens = 2048
temperature = 0.2
budget = 50
```

Requires `OPENROUTER_API_KEY` in your environment.

### Anthropic API


```toml
[llm]
provider = "anthropic"

[llm.anthropic]
model = "sonnet"
```

Requires `ANTHROPIC_API_KEY` in your environment.

For detailed provider documentation (authentication, request formats, error
handling, prompt templates), see the
[LLM Providers guide](LLM_PROVIDERS.md).

---

## Per-phase overrides


Override model, max turns, or timeout for specific phases:

```toml
[phases.requirements]
model = "haiku"            # Cheaper model for requirements

[phases.design]
model = "sonnet"           # Better model for design
max_turns = 8
phase_timeout = 900
```

Phase keys: `requirements`, `design`, `tasks`, `review`, `fixup`, `final`.

---

## Phase timeouts


The default phase timeout is 600 seconds (10 minutes). Increase it for
complex specs or slower providers:

```toml
[defaults]
phase_timeout = 1200       # 20 minutes
```

Or override per run:

```bash
xchecker resume my-feature --phase design --phase-timeout 1200
```

The minimum timeout is 5 seconds.

---

## Hooks


Hooks let you run custom shell scripts before and after any phase in the
pipeline. Use them for linting, notifications, custom validation, or any
side-effect you need tied to the spec lifecycle.

### Hook points


There are two hook points per phase:

| Hook point | When it runs | Can abort the phase? |
|------------|-------------|---------------------|
| `pre_phase` | Before the LLM is invoked | Yes (when `on_fail = "fail"`) |
| `post_phase` | After artifacts and receipt are written | No (always treated as warning) |

Both hook points are available for every phase: `requirements`, `design`,
`tasks`, `review`, `fixup`, and `final`.

### Configuration syntax


Each hook is defined as a TOML table under `[hooks.pre_phase.<phase>]` or
`[hooks.post_phase.<phase>]`:

```toml
[hooks.pre_phase.design]
command = "./scripts/pre_design.sh"
on_fail = "warn"          # "warn" (default) or "fail"
timeout = 60              # seconds, default 60

[hooks.post_phase.requirements]
command = "./scripts/post_requirements.sh"
on_fail = "fail"
timeout = 30
```

| Key | Required | Default | Description |
|-----|----------|---------|-------------|
| `command` | Yes | -- | Shell command to execute |
| `on_fail` | No | `"warn"` | `"warn"`: log and continue; `"fail"`: abort the phase |
| `timeout` | No | `60` | Maximum execution time in seconds |

You can configure multiple hooks across different phases in the same config
file. Each phase supports at most one `pre_phase` hook and one `post_phase`
hook.

### How hooks receive context


Hooks receive context in two ways:

**Environment variables** -- always set for every hook invocation:

| Variable | Description | Example value |
|----------|-------------|---------------|
| `XCHECKER_SPEC_ID` | The spec identifier | `my-feature` |
| `XCHECKER_PHASE` | The phase name | `requirements`, `design`, `tasks`, etc. |
| `XCHECKER_HOOK_TYPE` | The hook point | `pre_phase` or `post_phase` |

**JSON payload on stdin** -- a JSON object with the same context:

```json
{"spec_id":"my-feature","phase":"design","hook_type":"pre_phase"}
```

If the hook does not read stdin, the payload is silently discarded. Hooks are
free to ignore stdin and rely entirely on environment variables.

### Execution environment


- Hooks run via the platform shell: `sh -c` on Unix, `cmd /C` on Windows.
- The working directory is the directory where `xchecker` was invoked (not the
  spec directory), so relative paths like `./scripts/...` work naturally.
- Stdout and stderr are captured (truncated to 2 KiB each).
- Hooks are subject to their configured `timeout`. A hook that exceeds its
  timeout is terminated and treated as a failure.

### Error handling


**Pre-phase hooks:**

- `on_fail = "warn"` (default): A non-zero exit code or timeout is logged as a
  warning. The warning is recorded in the phase receipt under the `warnings`
  array. The phase proceeds normally.
- `on_fail = "fail"`: A non-zero exit code or timeout aborts the phase
  immediately. A failure receipt is written with a `hook_failure: "pre_phase"`
  flag for audit purposes. The LLM is never invoked.

**Post-phase hooks:**

Post-phase hooks run after the phase has already succeeded -- artifacts have
been written and the receipt has been committed. Because of this, post-phase
hook failures are **always treated as warnings** regardless of the `on_fail`
setting. This ensures that completed work is never discarded due to a
notification script failing.

**Hook execution errors** (e.g., command not found, spawn failure) are handled
the same way as non-zero exit codes: they respect `on_fail` for pre-phase
hooks and are always warnings for post-phase hooks.

### Receipt integration


Hook outcomes are recorded in the phase receipt:

- Pre-phase hook warnings appear in the receipt's `warnings` array as strings
  like `hook_failed:pre_phase:design:./scripts/lint.sh:exit_code=1` or
  `hook_timeout:pre_phase:design:./scripts/lint.sh`.
- Pre-phase hook failures set the receipt flag `hook_failure: "pre_phase"`.
- Pre-phase hook execution errors set the receipt flag
  `hook_error: "pre_phase"`.
- Post-phase hook warnings are logged but not written to the receipt (the
  receipt is already committed when the post-phase hook runs).

### Practical examples


**Lint before fixup:**

```toml
[hooks.pre_phase.fixup]
command = "cargo clippy --workspace --all-targets -- -D warnings"
on_fail = "fail"
timeout = 120
```

If `clippy` finds warnings, the fixup phase is aborted so you can address lint
issues before applying LLM-proposed changes.

**Notify after review:**

```toml
[hooks.post_phase.review]
command = "curl -s -X POST https://hooks.slack.com/services/T.../B.../xxx -d '{\"text\":\"Review phase completed for spec '\"$XCHECKER_SPEC_ID\"'\"}'"
on_fail = "warn"
timeout = 10
```

Sends a Slack notification when the review phase finishes. The short timeout
and `on_fail = "warn"` ensure a network hiccup does not block the workflow.

**Custom validation after requirements:**

```toml
[hooks.post_phase.requirements]
command = "./scripts/validate_requirements.sh"
on_fail = "warn"
timeout = 30
```

Where `validate_requirements.sh` reads stdin for context:

```bash
#!/usr/bin/env bash

# Read the JSON context from stdin

CONTEXT=$(cat)
SPEC_ID=$(echo "$CONTEXT" | jq -r .spec_id)

# Check that the requirements artifact exists and has content

ARTIFACT=".xchecker/specs/${SPEC_ID}/artifacts/00-requirements.md"
if [ ! -s "$ARTIFACT" ]; then
  echo "ERROR: requirements artifact is empty or missing" >&2
  exit 1
fi

echo "Requirements validation passed for ${SPEC_ID}"
```

**Gate design on requirements quality:**

```toml
[hooks.pre_phase.design]
command = "./scripts/check_requirements_quality.sh"
on_fail = "fail"
timeout = 30
```

This blocks the design phase from starting unless the requirements artifact
passes your quality checks.

**All phases -- universal logging:**

```toml
[hooks.pre_phase.requirements]
command = "echo \"Starting $XCHECKER_PHASE for $XCHECKER_SPEC_ID\" >> /tmp/xchecker.log"

[hooks.pre_phase.design]
command = "echo \"Starting $XCHECKER_PHASE for $XCHECKER_SPEC_ID\" >> /tmp/xchecker.log"

[hooks.pre_phase.tasks]
command = "echo \"Starting $XCHECKER_PHASE for $XCHECKER_SPEC_ID\" >> /tmp/xchecker.log"

[hooks.post_phase.requirements]
command = "echo \"Finished $XCHECKER_PHASE for $XCHECKER_SPEC_ID\" >> /tmp/xchecker.log"

[hooks.post_phase.design]
command = "echo \"Finished $XCHECKER_PHASE for $XCHECKER_SPEC_ID\" >> /tmp/xchecker.log"

[hooks.post_phase.tasks]
command = "echo \"Finished $XCHECKER_PHASE for $XCHECKER_SPEC_ID\" >> /tmp/xchecker.log"
```

Each phase must be configured individually -- there is no wildcard hook that
applies to all phases.

---

## Security settings


xchecker scans every packet for secrets before sending it to an LLM. The
built-in patterns cover AWS, GCP, Azure, SSH keys, platform tokens, and more.

### Add custom patterns


```toml
[security]
extra_secret_patterns = [
    "INTERNAL_TOKEN_[A-Z0-9]{32}",
    "my-company-secret-prefix-.*",
]
```

Or via CLI:

```bash
xchecker spec my-feature --extra-secret-pattern "SECRET_[A-Z0-9]{32}"
```

### Suppress false positives


```toml
[security]
ignore_secret_patterns = ["ghp_"]
```

---

## Strict validation


When enabled, phase outputs must pass quality checks (minimum length, required
sections, no meta-summaries). Failures abort the phase instead of logging
warnings.

```toml
[defaults]
strict_validation = true
```

Override per run:

```bash
xchecker spec my-feature --strict-validation
xchecker spec my-feature --no-strict-validation
```

Applies to generative phases: Requirements, Design, Tasks.

---

## Fallback providers


Configure a fallback provider that activates if the primary fails to
initialize (missing binary, missing API key, etc.):

```toml
[llm]
provider = "claude-cli"
fallback_provider = "openrouter"
```

Fallback triggers only on construction failures, not on runtime errors like
timeouts or quota exhaustion.

---

## Common recipes


### Use OpenRouter with budget control


```toml
[llm]
provider = "openrouter"

[llm.openrouter]
model = "google/gemini-2.0-flash-lite"
budget = 30
```

Override budget per run:

```bash
XCHECKER_OPENROUTER_BUDGET=100 xchecker spec my-feature
```

### Development configuration (fast iteration)


```toml
[defaults]
model = "haiku"
packet_max_bytes = 32768
max_turns = 3
phase_timeout = 300

[selectors]
include = ["src/**/*.rs", "Cargo.toml", "README.md"]
exclude = ["target/**", "tests/**"]
```

### Production configuration (best quality)


```toml
[defaults]
model = "sonnet"
packet_max_bytes = 65536
max_turns = 6

[selectors]
include = [
    "src/**/*.rs",
    "tests/**/*.rs",
    "docs/**/*.md",
    "Cargo.toml",
    "*.yaml",
]
exclude = ["target/**", ".git/**"]
```

### CI/CD configuration


```toml
[defaults]
runner_mode = "native"
phase_timeout = 900

[selectors]
include = [".github/**/*.yml", "Cargo.toml", "README.md"]
```

```bash
# Skip LLM calls in CI for validation-only runs

xchecker spec my-feature --dry-run
```

---

## Troubleshooting


### Configuration not found


```
Error: Failed to load configuration
```

Create `.xchecker/config.toml` or pass `--config /path/to/config.toml`.

### Invalid TOML syntax


```
Error: Failed to parse TOML config file
Caused by: TOML parse error at line 5, column 12
```

Check quoting, bracket nesting, and string escaping in your TOML file.

### Packet overflow


Your context is too large. Either narrow your file selectors or increase the
limit:

```bash
xchecker spec my-feature --packet-max-bytes 131072
```

### WSL not available


```
Error: WSL runner requested but not available
```

Install WSL with `wsl --install`, or switch to native runner:

```toml
[runner]
mode = "native"
```

### Phase timeout


```
Error: Phase 'design' exceeded 600s timeout
```

Increase the timeout:

```bash
xchecker resume my-feature --phase design --phase-timeout 1200
```

---

## See also


- [Configuration Reference]../reference/CONFIGURATION.md -- every key, env var, and CLI flag
- [LLM Providers]LLM_PROVIDERS.md -- detailed provider setup
- [CI Setup]CI_SETUP.md -- xchecker in CI/CD pipelines
- [Debugging]DEBUGGING.md -- diagnostics and troubleshooting