A HARNESS FOR YOUR REPOS, OPERATED BY AGENTS.

What it is
A reusable harness that turns a repository into an operating environment for coding agents — opinionated, inspectable, reviewable.
What it gives you
A jig contract every tool agrees on, work gates backed by append-only receipts, an MCP runtime, and CI workflows. An optional Makefile adapter when humans want it.
For
Rust-first today — Cargo workspaces, optional SQLx, plus first-class JavaScript/TypeScript web apps via Bun. A portless dev proxy and a sealed vault when the agent loop needs them. Modular language adapters in flight.
01

Why a jig.

01

Drop-in AGENTS.md

An opinionated AGENTS.md + agent-map lands in the repo and tells the agent what the surface is, where to look, and what is in-bounds.

02

Stable contract surface

The agent calls into scripts/jig, which dispatches to manifest-defined tools — checks, tests, schema verification, migrations, release steps — and writes a receipt for every call. A Makefile adapter ships for humans; the contract doesn't depend on it.

03

Typed jig runtime

Each subcommand declares its arguments and return shape in .agent/jig-contract.json — the agent sends validated input and gets structured JSON back, not parsed stdout. An MCP server (Model Context Protocol) lets models discover and call those tools directly. No shell flags to memorize, no targets to guess.

04

Work gates + receipts

Required gates run on every change. Every run writes append-only JSONL receipts to .agent/state/ so you can audit what an agent attempted, when, and what passed.

02

Init → adopt → update.

→ stage 01 / init
$ jig init .
Bootstrap a new repository from the template. Renders .jig.toml, AGENTS.md, agent-map, Makefile, scripts, and CI workflows in one shot.
→ stage 02 / adopt
$ jig adopt .
Drop the harness into an existing repo. Only the marked managed blocks are touched; everything else you've written stays exactly as it was.
→ stage 03 / update
$ jig update --recopy
Advance to a newer template, or re-render from stored answers. --force required to overwrite locally edited managed files. Refuses by default.
03

The contract, on paper.

Rendered into your repo

Files jig writes and maintains. Everything outside this list stays repo-owned.

target-repo/
├── .jig.toml            — public config + renderer answers
├── .mcp.json            — MCP wiring for agents
├── AGENTS.md            — managed block; rest is yours
├── agent-map.md         — surface area, in plain text
├── .agent/
│   ├── PLANS.md         — in-flight plans
│   ├── jig-contract.json— machine-readable surface
│   └── state/            — append-only receipts (JSONL)
├── Makefile             — optional adapter over scripts/jig
├── scripts/
│   ├── jig              — launcher; pins runtime version
│   ├── install-jig.sh
│   └── enforce-coverage.js
└── .github/workflows/
    ├── ci.yml
    └── release.yml

What an agent leaves behind

A streaming tail of .agent/state/receipts.jsonl. Reviewable. Diffable. Auditable.

10:42:11 gate · fmt · ok · cargo fmt --check
10:42:14 gate · clippy · ok · 0 warnings, 0 errors
10:42:18 work · edit · crates/api/src/routes.rs (+38 −12)
10:42:42 gate · test · ok · 184 passed · 12.4s
10:43:01 gate · coverage · fail · 71.8% (< 80% threshold)
10:43:09 plan · update · "add table-level coverage for routes::auth"
10:44:02 gate · coverage · ok · 83.2%
10:44:11 gate · schema-check · ok · .sqlx/ matches HEAD
10:44:14 commit · staged · 1 file · sig 9f3c81…
04

Drive it from the terminal.

What you're watching

  • scripts/jig agent doctor — verifies your environment is wired up for jig and its expected agent skills.
  • scripts/jig agent bootstrap — installs the Codex-side skills the harness expects, pulling from the complementary jig-skills marketplace by default. No separate checkout required.
  • Pass --marketplace <source> to override: a GitHub shorthand (bpcakes/jig-skills) or a local path (../jig-skills) both work.
~/repos/target-repo — zsh tty 02
05

Hostnames, not ports.

scripts/jig dev brings up every app in your repo behind a single local reverse proxy — no port juggling, no hand-typed localhost:5173, no stale tabs. Each app gets a stable hostname scoped to your repo.

A separate crate, crates/jig-dev-proxy, owns route storage, HTTP / HTTPS forwarding, HTTP/2 & WebSockets, a local CA, and optional launchd / systemd service files. It runs alongside the harness — not inside the jig contract — because routes, ports, and certs are mutable machine-local state, not auditable work.

your apps
web vite :random
api env-port :random
admin vite :random
PORT injected per process · Vite flags rewritten
jig proxy
HTTP:1355
HTTPS:1443
HTTP/2on
WebSocketson
TLDlocalhost
LAN modeopt-in
state · ~/.jig/proxy
stable hostnames
https://web.demo.localhost
https://api.demo.localhost
https://admin.demo.localhost
routes are repo-scoped to demo
01
Auto-port allocation. The proxy picks a free port per app, then injects PORT + HOST for env-port apps, or rewrites --port --host --strictPort for Vite.
02
One local URL, every time. <app>.<repo>.localhost. Restart the dev server, the bookmark still works.
03
HTTPS without ceremony. Generate a local CA + leaf, trust it once, then everything is https://. HTTP/2 + WebSocket forwarding included.
04
LAN mode for device testing. Flip lan = true and your phone can hit the same hostnames on the same Wi-Fi.
05
Ad-hoc aliases. Point a hostname at an already-running process: proxy alias api 8080. Useful for binaries you didn't start with dev.
06
Optional user service. Install a launchd / systemd user service and the proxy boots with your session — no per-shell start, no orphaned tabs.
.jig.toml cfg
[dev]
proxy_port          = 1355
https_port          = 1443
https               = false
http2               = true
lan                 = false
tld                 = "localhost"
workspace_discovery = false

[[dev.apps]]
name    = "api"
kind    = "env-port"
command = "cargo run --bin api"

[[dev.apps]]
name = "web"
dir  = "apps/web"
kind = "vite"
argv = ["bun", "run", "dev"]
commands · run from the repo root
$ scripts/jig dev
$ scripts/jig dev --app web
$ scripts/jig proxy start
$ scripts/jig proxy list
$ scripts/jig proxy run web -- vite
$ scripts/jig proxy alias api 8080
$ scripts/jig proxy cert generate
$ scripts/jig proxy cert trust
$ scripts/jig proxy service install
state · ~/.jig/proxy  ·  override with JIG_PROXY_STATE_DIR runtime-owned · not in .agent/jig-contract.json
06

Sealed. Audited. Redacted.

vault run --env TOKEN=gh_token -- gh release create resolves a named secret from an encrypted local vault, injects it into the child's environment only, redacts known forms of it from output, and records the run in a tamper-evident audit log.

A separate crate, crates/jig-vault, owns the encrypted store, broker, and audit chain. Argon2id passphrase derivation, XChaCha20-Poly1305 AEAD, owner-only filesystem permissions. Plaintext never lands in repo state, MCP results, command receipts, or Debug output.

01 the vault
path~/.jig/vault/
permsowner-only · 0600
kdfArgon2id 128MiB
aeadXChaCha20-Poly1305
min pass12 bytes
contents · names only
GH_TOKEN
DEPLOY_KEY
DB_PASSWORD
02 the broker
unlock$JIG_VAULT_PASSPHRASE
resolvein-memory · locked
envcleaned allowlist
injectVAR=secret-bytes
timeout30 minutes
caps1 MiB stdout · 1 MiB stderr
child sees only
PATH · HOME · TMPDIR · locale · + your env vars
03 the trace
stdoutredacted · returned
stderrredacted · returned
exitpropagated
auditHMAC-chained JSONL
run idpairs start ↔ finish
plaintext never reaches
.agent/stateMCP resultsreceiptsDebug logs
01
Strong crypto, no knobs. Argon2id (128 MiB · 3 iter · 4 lanes) wraps a random data key; XChaCha20-Poly1305 seals state with role-tagged AEAD. Nothing to weaken.
02
Brokered, not granted. Secrets resolve in-memory and inject only into the child's env. jig never returns plaintext through public APIs, MCP, receipts, or Debug.
03
Cleaned environment. The child sees a strict allowlist — PATH, HOME, TMPDIR, locale — plus the secret vars you named. No SSH_AUTH_SOCK, no XDG_*, no surprise inheritance.
04
Output redaction. Known forms of resolved secrets are scrubbed from captured stdout and stderr before display. A backup control — never the boundary.
05
Tamper-evident audit. Every mutation, every run, every verify is an HMAC-chained JSONL event. vault audit verify walks the chain and reports edits, torn tails, or broken links.
06
Local-only state. Vault lives at ~/.jig/vault (or JIG_VAULT_HOME) with owner-only perms — never inside the repo, never inside .agent/state.
~/.jig/vault/audit.jsonl  ·  tail HMAC-chained
10:42:11vault.init · ok · vault[v1] · home=~/.jig/vault
10:42:18secret.set · GH_TOKEN · 40 B
10:42:21secret.set · DEPLOY_KEY · 512 B
10:42:23brokered.start · run_id=01HG6X… · env={TOKEN→GH_TOKEN}
10:42:24brokered.finish · ok · exit=0 · stdout=24 B · stderr=0 B
10:43:01brokered.failed · stage=resolve · run_id=01HG7P… · unknown secret name
10:43:05audit.verify · ok · 7 events · head b3f1…9c2e
commands · passphrase via $JIG_VAULT_PASSPHRASE — cleared after capture · no CLI flag (leaks via shell history)
$ scripts/jig vault init
$ scripts/jig vault status
$ scripts/jig vault audit verify
$ scripts/jig vault secret list
$ scripts/jig vault secret set GH_TOKEN --value-stdin
$ scripts/jig vault secret remove GH_TOKEN
$ scripts/jig vault run --env TOKEN=gh_token -- gh release create v0.3.0
state · ~/.jig/vault  ·  override with JIG_VAULT_HOME runtime-owned · not in .agent/jig-contract.json · not in MCP · not in receipts
⚠  blast-radius reducer, not a sandbox

Once a child process receives a secret in its environment, that process can use or disclose it. Redaction catches accidental output, not malicious transformations or side channels. The vault shrinks what your repo, your receipts, and your MCP transcripts can leak — it doesn't replace a real secret manager for production.

07

What it gates, what it leaves alone.

Stacks
Rust-first today. JavaScript / TypeScript web apps already supported alongside. Modular language adapters in flight so other stacks can plug in without forking the harness.
roadmap
Rust path
Cargo workspaces, cargo fmt, cargo clippy — all assumed present on the default path.
required
Database
SQLx + shared .sqlx/ metadata + forward-only migrations.
optional
Web apps
Each configured app directory exposes lint, typecheck, build:bundle, test:coverage. Bun by default.
optional
Coverage
Hard threshold enforced by scripts/enforce-coverage.js. Default 80%.
gate
Schema
Schema dumps remain project-owned. Jig only verifies they match a checked state.
gate
CI
Generated GitHub Actions for ci + release, with trusted publishing to crates.io.
workflow
Dev proxy
jig-dev-proxy serves repo-scoped hostnames over HTTP/HTTPS w/ HTTP/2 + WebSockets. State lives in ~/.jig/proxy, outside .agent/state.
runtime-owned
TLS · LAN
Local CA + leaf certs with explicit trust/untrust. LAN mode exposes the same hostnames to devices on the same network.
opt-in
Vault · broker
jig-vault seals local secrets with Argon2id + XChaCha20-Poly1305 and runs commands with env-only injection + redaction. Tamper-evident audit log.
runtime-owned
Out of scope
Application code, crate-level AGENTS.md files, and schema implementations remain entirely repo-owned.
repo-owned
08

Quickstart, two paths.

New repo · init

Bootstrap a target repo from the template, committed mode, with SQLx migrations.

# from anywhere
jig init /path/to/target-repo \
  --template /path/to/jig-sh \
  --template-mode committed \
  --repo-name target-repo \
  --rust-migration-dir migrations

# tooling-only? skip sqlx:
jig init /path/to/target-repo \
  --template /path/to/jig-sh \
  --sqlx-enabled false

Existing repo · adopt

Drop the harness into a repo you already have. Only managed blocks change.

# inside the repo
jig adopt . \
  --template /path/to/jig-sh \
  --template-mode committed \
  --repo-name target-repo \
  --rust-migration-dir migrations

# later, pull in template updates
jig update
jig update --recopy   # full re-render
jig update --force    # overwrite managed
02
Agents don't need more freedom.
They need a contract — and a witness.
— design note · docs/why-a-harness.md
·

Likely questions.

Why not just put instructions in AGENTS.md? +
AGENTS.md is a starting point, not a contract. An agent that reads prose may still call the wrong commands, skip a gate, or quietly diverge from your conventions across runs. Jig encodes the contract as scripts/jig tools with declared inputs and outputs in .agent/jig-contract.json, so the agent's actions are checkable, and writes receipts so they are auditable.
Is jig opinionated? +
Yes — intentionally. The Rust path (Cargo workspaces, cargo fmt, cargo clippy, forward-only SQL migrations) is the most fleshed-out today. JavaScript/TypeScript web apps are supported via Bun (lint, typecheck, build:bundle, test:coverage). Other stacks land as the modular language adapters are extracted — if your stack isn't on either path yet, jig is currently the wrong tool, but the architecture is built so it doesn't have to stay that way.
What languages and stacks does jig support today? +
Rust is the primary path — Cargo workspaces with optional SQLx + Postgres, schema verification, forward-only migrations, and a release pipeline. JavaScript / TypeScript web apps ship as a first-class secondary path via the frontend_apps config, with lint, typecheck, build:bundle, and test:coverage scripts run through Bun. The dev proxy, vault, receipts, gates, and CI workflows are stack-agnostic — they work the same regardless of the language under them. A modular language-adapter design is underway so additional stacks can plug in without forking jig itself.
What does jig not generate? +
Application code, crate-level AGENTS.md files, your schema dump implementation, and the bulk of your repo. The template stays narrow on purpose — it only renders harness assets and the marked block of the root AGENTS.md.
How do I update an adopted repo? +
Run jig update. By default it refuses to overwrite managed files you've edited locally. Pass --force if you really want the template to win, or --recopy to re-render from the stored answers in .jig.toml.
Does it work without a database? +
Yes. Pass --sqlx-enabled false. The migration and schema-check contract pieces are skipped; the rest of the harness is unaffected.
Why is the dev proxy not part of the contract? +
Because routes, ports, PID files, and certificates are mutable machine-local state, not auditable repo work. The jig-dev-proxy crate stores its state under ~/.jig/proxy (or JIG_PROXY_STATE_DIR) and is intentionally absent from .agent/jig-contract.json. scripts/jig dev and scripts/jig proxy ... are runtime-owned conveniences that sit beside the contract, not inside it.
How do I get HTTPS locally without warnings? +
Run scripts/jig proxy cert generate to create a local CA + wildcard leaf, then scripts/jig proxy cert trust once to add the CA to your OS trust store. cert untrust reverses it. Browsers then see https://<app>.<repo>.localhost as a green padlock — no per-app cert juggling.
Why does the vault exist if I already have 1Password / Vault / AWS Secrets Manager? +
It doesn't replace them. The jig vault is for the agent loop: a place where a coding agent can ask for a named secret to run one specific command without ever seeing the plaintext through MCP, receipts, or Debug output. For production secret management, keep using your existing provider — the vault is a blast-radius reducer for local development, not a cloud KMS.
Why can't I pass the passphrase on the command line? +
Because it leaks through shell history and process listings. jig vault reads the passphrase only from JIG_VAULT_PASSPHRASE in the process environment, then clears the child-process variable after capture. Best-effort process hygiene — not a guarantee the OS overwrote every backing byte, but better than a flag any ps can see.
How are releases done? +
Through the generated Release GitHub Actions workflow. It prepares the release commit, updates CHANGELOG.md from conventional-commit prefixes, tags, publishes jig-sh to crates.io via trusted publishing, and creates a GitHub Release. A local release script remains the entry point for local validation and manual recovery.
© Banana Pancakes s.r.o. · all rights reserved