ctx harness
Package ctx as an AI coding harness integration (Claude Code).
Synopsis
ctx harness init [--target claude] [--mode local|plugin] [--force] [--json]
ctx harness compat --require <SEMVER>
ctx harness doctor [--json]Description
The harness command wires the ctx quality suite into an AI coding harness. Today the only supported target is Claude Code (--target claude, the default); unknown targets are rejected with exit code 2 and a list of supported values.
Everything init writes comes from templates embedded in the ctx binary, and every generated file carries a header:
# generated by ctx v0.3.0 — regenerate with 'ctx harness init'
# ctx:checksum sha256:<hex>The checksum makes regeneration safe: on re-run, files that still match their recorded checksum are regenerated in place, while files you have modified (or files ctx never generated) are skipped with a warning. --force overwrites them. JSON files cannot carry comments, so they are tracked in a manifest, .ctx/harness.lock, instead.
Two files get special treatment:
.ctx/rules.tomlis never overwritten once it exists, not even with--force— it encodes your policy, not ctx's..claude/settings.jsonis never edited by ctx at all;initprints a snippet for you to merge manually.
init --mode local (default)
Wires the current project's .claude/ directory:
| File | Purpose |
|---|---|
.claude/hooks/ctx/session-start.sh | SessionStart hook: prints ctx map --budget 2000 for the model |
.claude/hooks/ctx/post-tool-use.sh | PostToolUse (Edit|Write) hook: ctx index, then ctx check --against HEAD --json |
.claude/hooks/ctx/stop.sh | Stop hook: ctx score --against <default branch> scorecard (non-blocking by default; see Blocking gates) |
.ctx/rules.toml | Commented starter rules file (only when absent) |
.ctx/harness.lock | Manifest of generated files and their checksums |
It then prints two blocks to stdout (instructions go to stderr):
- A settings snippet to merge into
.claude/settings.json— hook wiring plus a permissions block that allowsBash(ctx *)and deniesBash(ctx self-update*),Edit(.ctx/rules.toml),Edit(.claude/hooks/ctx/**), andEdit(.claude/settings.json). - A guidance block for your
CLAUDE.mdso the model knows to use ctx.
init --mode plugin
Scaffolds a distributable Claude Code plugin in the current directory:
| File | Purpose |
|---|---|
.claude-plugin/plugin.json | Plugin manifest (version = the generating ctx version) |
.claude-plugin/marketplace.json | Local marketplace (ctx-local) for manual installs |
hooks/hooks.json | Hook wiring via ${CLAUDE_PLUGIN_ROOT} |
hooks/*.sh | The same three hook scripts as local mode |
settings.json | The permissions block (allow/deny above) |
skills/ctx/SKILL.md | Skill teaching the model the ctx workflow |
README.md | Manual install walkthrough (/plugin marketplace add ./, /plugin install ctx@ctx-local) |
.mcp.json | MCP server wiring (ctx serve --mcp) — only when this binary was built with the mcp feature; otherwise a stderr note explains how to enable it |
Hook behavior (fail open, loudly)
Every generated hook script starts with a compatibility guard:
ctx harness compat --require "<version baked at generation>"If the installed binary is older than the templates (exit code 3), the script prints a warning to stderr and exits 0 without performing its action — the session is never blocked, but you are told why. Model-bound content (map, check JSON, scorecard) goes to stdout; human-facing notices always go to stderr.
Blocking gates (CTX_GATE_BLOCKING)
The generated Stop hook runs ctx score --against <default branch> --fail-on "check_violations>0,new_duplication>0". By default a failed gate only prints the scorecard and a stderr note — the session stops normally. Set CTX_GATE_BLOCKING=1 (exactly 1) in the environment Claude Code runs in to turn gate failures into a blocking stop: the hook exits 2, which Claude Code treats as "keep working", so the session continues until the failed conditions in the scorecard are addressed.
Only a genuine gate failure ever blocks. Everything else fails open:
ctx score result | CTX_GATE_BLOCKING | Hook exit | Effect |
|---|---|---|---|
| Gates pass (exit 0) | any | 0 | Session stops normally |
| Gate condition fired (exit 1) | unset / anything but 1 | 0 | Non-blocking; a stderr note points at the scorecard |
| Gate condition fired (exit 1) | 1 | 2 | Blocking stop; Claude keeps working on the findings |
| Operational error (exit 2, compat mismatch, ctx not on PATH) | any | 0 | Fail open with a stderr warning |
Pair it with CTX_GATE_LOG (consumed by ctx score itself, not the hook) to record every gate evaluation — including whether blocking mode was on — in a local JSONL log; see ctx score — Gate logging.
Upgrading: hook scripts are generated files. If your
.claude/hooks/ctx/stop.shpredatesCTX_GATE_BLOCKING, re-runctx harness initafter updating ctx to regenerate it (ctx harness doctor'stemplates_stalecheck tells you when this is due).
compat --require <SEMVER>
Exits 0 when the running binary satisfies the requirement, otherwise prints exactly one line to stderr and exits 3 (reserved exclusively for this subcommand).
A bare version (0.2, 0.2.1) is a floor: the binary must be at least that version. An expression with operators (^0.2, >=0.2, <0.4, 0.2.*) is parsed as a full semver requirement. Unparseable input is an operational error (exit 2), never 3.
doctor
Runs independent checks and reports findings (error, warning, info):
| Code | Checks |
|---|---|
binary_version | ctx version and whether the mcp feature is compiled in (info) |
harness_not_initialized | nothing scaffolded yet (info) |
templates_stale | generated files come from a different ctx version |
index_missing / index_schema / index_stale | index existence, schema version, freshness vs file mtimes |
rules_missing (info) / rules_invalid | .ctx/rules.toml presence and validity |
hooks_missing / hooks_modified | generated hook scripts exist and match their checksums |
settings_not_wired | .claude/settings.json does not reference .claude/hooks/ctx/ |
mcp_unavailable / mcp_not_wired | .mcp.json vs the binary's mcp feature |
Checks do not cascade: a missing index and an invalid rules file are both reported in the same run.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success (init), requirement satisfied (compat), healthy (doctor) |
| 1 | doctor found problems (errors or warnings) |
| 2 | Operational error (unknown --target, bad arguments, IO failure) |
| 3 | Version requirement not met — reserved exclusively for harness compat |
Examples
Wire ctx into Claude Code
ctx harness init
# merge the printed snippet into .claude/settings.json,
# add the printed block to CLAUDE.md, then:
ctx harness doctorBuild a distributable plugin
mkdir ctx-plugin && cd ctx-plugin
ctx harness init --mode plugin
claude
# /plugin marketplace add ./
# /plugin install ctx@ctx-localRegenerate after updating ctx
ctx harness doctor # templates_stale tells you regeneration is due
ctx harness init # regenerates unmodified files, skips your edits
ctx harness init --force # overwrites your edits too (rules.toml excepted)Machine-readable output
ctx harness init --json # file actions + settings snippet in the envelope
ctx harness doctor --json # health report (see docs/json-output.md)See Also
ctx check— the architecture rules the hooks enforcectx score— the scorecard the Stop hook prints- Claude Code integration guide