Skip to main content

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.toml is never overwritten once it exists, not even with --force — it encodes your policy, not ctx's.
  • .claude/settings.json is never edited by ctx at all; init prints a snippet for you to merge manually.

init --mode local (default)

Wires the current project's .claude/ directory:

FilePurpose
.claude/hooks/ctx/session-start.shSessionStart hook: prints ctx map --budget 2000 for the model
.claude/hooks/ctx/post-tool-use.shPostToolUse (Edit|Write) hook: ctx index, then ctx check --against HEAD --json
.claude/hooks/ctx/stop.shStop hook: ctx score --against <default branch> scorecard (non-blocking by default; see Blocking gates)
.ctx/rules.tomlCommented starter rules file (only when absent)
.ctx/harness.lockManifest of generated files and their checksums

It then prints two blocks to stdout (instructions go to stderr):

  1. A settings snippet to merge into .claude/settings.json — hook wiring plus a permissions block that allows Bash(ctx *) and denies Bash(ctx self-update*), Edit(.ctx/rules.toml), Edit(.claude/hooks/ctx/**), and Edit(.claude/settings.json).
  2. A guidance block for your CLAUDE.md so the model knows to use ctx.

init --mode plugin

Scaffolds a distributable Claude Code plugin in the current directory:

FilePurpose
.claude-plugin/plugin.jsonPlugin manifest (version = the generating ctx version)
.claude-plugin/marketplace.jsonLocal marketplace (ctx-local) for manual installs
hooks/hooks.jsonHook wiring via ${CLAUDE_PLUGIN_ROOT}
hooks/*.shThe same three hook scripts as local mode
settings.jsonThe permissions block (allow/deny above)
skills/ctx/SKILL.mdSkill teaching the model the ctx workflow
README.mdManual install walkthrough (/plugin marketplace add ./, /plugin install ctx@ctx-local)
.mcp.jsonMCP 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 resultCTX_GATE_BLOCKINGHook exitEffect
Gates pass (exit 0)any0Session stops normally
Gate condition fired (exit 1)unset / anything but 10Non-blocking; a stderr note points at the scorecard
Gate condition fired (exit 1)12Blocking stop; Claude keeps working on the findings
Operational error (exit 2, compat mismatch, ctx not on PATH)any0Fail 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.sh predates CTX_GATE_BLOCKING, re-run ctx harness init after updating ctx to regenerate it (ctx harness doctor's templates_stale check 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):

CodeChecks
binary_versionctx version and whether the mcp feature is compiled in (info)
harness_not_initializednothing scaffolded yet (info)
templates_stalegenerated files come from a different ctx version
index_missing / index_schema / index_staleindex existence, schema version, freshness vs file mtimes
rules_missing (info) / rules_invalid.ctx/rules.toml presence and validity
hooks_missing / hooks_modifiedgenerated 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

CodeMeaning
0Success (init), requirement satisfied (compat), healthy (doctor)
1doctor found problems (errors or warnings)
2Operational error (unknown --target, bad arguments, IO failure)
3Version 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 doctor

Build a distributable plugin

mkdir ctx-plugin && cd ctx-plugin
ctx harness init --mode plugin
claude
# /plugin marketplace add ./
# /plugin install ctx@ctx-local

Regenerate 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