Skip to content

Libr-AI/CommonHarness

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CommonHarness

A drop-in governance protocol for AI-assisted iteration. harness init lays a harness CLI plus the .harness/ state machine into any repo so AI sessions (Claude Code, Codex, Cursor) follow a coordinator → implementer two-session split with checkpointed commits.

Language-agnostic. Three AI-platform integrations + GitHub. Designed so target repos can pull upstream updates without losing their own state.


What it does

  • Two-session protocol — coordinator session writes a task brief at .harness/active/<id>.md; implementer session executes one phase at a time, stops at every commit point and session boundary.
  • Three triage paths — coordinator routes each request to scaffold (greenfield bootstrap), light (small change), or full (non-trivial change). Greenfield is auto-detected at harness init (no language manifest + no source dirs + no architecture doc) or forced via --greenfield.
  • TODO gate — if CONTRIBUTING.md still has unfilled 🛠 TODO (project maintainers) blocks, the coordinator first runs a fill-contributing prep task that scans manifest/CI/formatter configs, proposes candidate answers, and writes only after you confirm. No surprise auto-fills.
  • Memory-import gate — adopting harness into a project an agent already worked on? On the first harness start the coordinator looks for prior agent memory about this project — both in the repo and in your user-level store for it (Claude Code's ~/.claude/projects/<repo>/, Codex/Cursor session stores) — and, only if it finds some, asks which source to import, then distils it into .harness/MEMORY.md (relocating any architecture doc into docs/ARCHITECTURE.md, rebuilding prior task history into .harness/archive/, reconciling same-name conflicts). Detection is the agent's job, not the CLI's. Gated by [adoption] on_init.
  • Architecture doc as global basisdocs/ARCHITECTURE.md (path configurable) is read at every entry point — coordinator, implementer, the Cursor rule, and the SessionStart hook — as the project map / "change X → which files" lookup. On scaffold, the coordinator asks whether you'll provide one and records the choice (harness arch).
  • Spec gate — for tasks that span cross-cutting dirs, introduce new external dependencies, change external interfaces, or affect the data model, the coordinator decides whether to produce a standalone design spec at docs/specs/<task-id>.md (in addition to the task brief). Phase 0 of the brief writes the spec; subsequent phases use it as the design authority.
  • State on disk — task state lives in .harness/ files, so sessions can crash and resume with no information loss. .harness/CURRENT.md is per-clone local state (which task this working copy is driving; git-ignored); the shared truth across a team is the committed .harness/active/ brief set. .harness/SCAFFOLD-PENDING is the marker that drives the scaffold gate.
  • Cross-task memory.harness/MEMORY.md accumulates conventions, pitfalls, and decisions over time, surfaced to every new session at every entry point (SessionStart hooks for Claude Code / Codex, the Cursor rule, and the coordinator/implementer prompts) so any tool reads the same memory.
  • Planning backlog.harness/TODO.md is the coordinator's cross-session todo list: tasks scoped/split/prioritised but not yet briefed. The in-chat plan dies with a session; this file survives it, so any fresh coordinator reads the backlog on startup, proposes a recommended order, and asks which to start. The coordinator is a resident planner across the whole project; phase-to-phase hand-offs are the implementer's own and always emit the next kickoff in full.
  • Three AI-platform hooks — Claude Code (slash command + SessionStart / PreToolUse hooks), Cursor (alwaysApply rule), Codex (MCP skill + SessionStart / PreToolUse hooks).
  • GitHub PR template with a required protocol-status field.

Conventions used in this README

Every code block below has a label telling you where to run it:

  • bash blocks → run in your terminal (zsh / bash). Copy the whole block; comments (# ...) are fine to keep.
  • text blocks → paste into your AI tool (Claude Code / Cursor chat / Codex CLI prompt) as a chat message.
  • File-edit instructions are spelled out in prose ("open harness.config.toml in your editor and …") — no code block.

If a block has no explicit "Paste into your AI tool" callout, treat it as a terminal command.


Install

CommonHarness is distributed as release tags. Every install pins to a specific version and lands in its own directory; a current symlink picks which version is active. Multiple versions can co-exist for safe upgrades and rollback.

One-liner (recommended)

Step 1 — Download and install

In your terminal:

curl -fsSL https://raw.githubusercontent.com/Libr-AI/CommonHarness/v0.7.2/install.sh | bash

When this finishes, the harness CLI is at ~/.local/bin/harness (symlinked through ~/.commonharness/current/).

Step 2 — Make sure ~/.local/bin is on your PATH

Check whether it's already there:

echo $PATH | tr ':' '\n' | grep -F "$HOME/.local/bin"
  • If it prints a path (e.g. /Users/you/.local/bin) → already set, skip to Step 3.

  • If it prints nothing → add it to your shell rc and reload it:

    echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
    source ~/.zshrc

    macOS default shell since Catalina is zsh (~/.zshrc). If you're on bash, use ~/.bashrc instead. If source doesn't seem to take effect, open a fresh terminal window.

Step 3 — Verify

harness --version

Expected: harness 0.7.2. If you see command not found: harness, redo Step 2 (most likely the PATH change didn't propagate to your current shell — open a new terminal window).

Pin to a different version

Note: env vars must come before bash, not before curl — otherwise the variable only reaches curl and is dropped before install.sh runs.

# Example: pin to an older release for rollback.
curl -fsSL https://raw.githubusercontent.com/Libr-AI/CommonHarness/v0.1.0/install.sh \
  | HARNESS_VERSION=v0.1.0 bash

Roll on main (always latest, unstable)

curl -fsSL https://raw.githubusercontent.com/Libr-AI/CommonHarness/main/install.sh \
  | HARNESS_VERSION=main bash

Manual install (if you don't want curl-pipe-bash)

git clone --depth 1 --branch v0.7.2 \
  https://github.com/Libr-AI/CommonHarness.git \
  ~/.commonharness/v0.7.2
~/.commonharness/v0.7.2/install.sh

To use SSH instead, set HARNESS_REPO_URL (note: env var goes before bash, not before curl):

curl -fsSL https://raw.githubusercontent.com/Libr-AI/CommonHarness/v0.7.2/install.sh \
  | HARNESS_REPO_URL=git@github.com:Libr-AI/CommonHarness.git bash

Requires: bash, git, python ≥ 3.9.

Layout produced

~/.commonharness/
├── v0.7.1/                ← pinned snapshot (shallow tag clone, can't switch branches)
├── v0.7.2/                ← later, after upgrade — old versions kept for rollback
└── current  →  v0.7.2     ← which version is active
~/.local/bin/harness  →  ~/.commonharness/current/bin/harness

harness --version reads from the active install's VERSION file. Each project records the version it was init'd against in its harness.config.toml (harness_version = "..."), so team consistency is auditable.


Quick start

1. Initialize harness in your project

In your terminal, from the project's repo root:

cd /path/to/your/project
harness init                       # prompts for a preset; pick the one matching your stack
# or pass it directly, e.g.:
#   harness init --preset python-uv      (Python + uv + ruff + pytest)
#   harness init --preset node           (JavaScript/TypeScript)
# The preset is only a starting template — it does NOT auto-detect. Whichever you
# pick, the coordinator adapts [verify]/[paths] to your real stack on first start.

That writes AGENTS.md, CONTRIBUTING.md (with TODO sections), harness.config.toml, the .harness/ state directory, and the AI integrations. The CLI prints "Next steps" telling you what to do next.

The preset is just a starting point. On the first harness start, the coordinator inspects your actual project (manifest, scripts, source layout) and proposes corrected [verify] and [paths] for you to confirm — so picking the "wrong" preset still converges on a config that matches your project. Run harness init with no --preset to see the list of available presets.

Greenfield projects. If you're initializing into an empty repo (no language manifest, no source dirs, no architecture doc), harness init auto-detects this and offers to bootstrap with the scaffold path. You can also force the decision:

harness init --preset python-uv --greenfield        # force greenfield bootstrap
harness init --preset python-uv --no-greenfield     # force brownfield (skip detection)
harness init --preset python-uv --non-interactive   # auto-confirm detection

When greenfield is on, a marker file .harness/SCAFFOLD-PENDING is written; your first harness start will route to a 4-phase scaffold task (architecture decisions → directory skeleton → first runnable module → CONTRIBUTING fill + CI). The marker clears automatically when the scaffold task archives.

2. (Brownfield only) Don't touch CONTRIBUTING.md yet

The first harness start will offer to fill CONTRIBUTING.md's 🛠 TODO (project maintainers) blocks for you (scans your manifest/CI/formatter configs, proposes candidates, writes only after you confirm). You can still hand-edit if you prefer — but the coordinator's TODO gate will detect unfilled blocks and offer the prep task either way.

For greenfield projects: same — but the TODO fill happens as Phase 4 of the scaffold task, not a separate prep task.

3. Start your first iteration

In your terminal:

harness start

This prints a long block of text — the coordinator session opening prompt. The next step happens in your AI tool.

Paste the printed text into your AI tool (Claude Code / Cursor / Codex chat) as your first chat message:

You are the **coordinator** for an iteration on this repository. Follow the harness governance protocol strictly.
…
(everything `harness start` printed)

The AI then walks you through the protocol: asks what you want to change, runs triage, writes a task brief at .harness/active/<task-id>.md, and tells you how to start the implementer session.

4. Continue, resume, end — daily commands

In your terminal:

harness implement <task-id>    # resume / enter the implementer session (also prints a prompt to paste into AI tool)
harness status                 # show CURRENT.md + the active brief (+ team view of all active briefs in team mode)
harness mode [solo|team]       # show or set the collaboration mode
harness lang [<bcp47>]         # show or set the language for repo artifacts (specs/briefs/docs)
harness arch [yes|no]          # show or set whether an architecture doc is provided (scaffold)
harness end                    # archive a finished task → CURRENT.md back to idle
harness remember "<text>"      # append a convention/pitfall/decision to MEMORY.md
harness curate-memory          # quarterly MEMORY.md cleanup (opens it in $EDITOR)

The full protocol (triage paths, commit-point markers, anti-garbage rules) is rendered into your repo as AGENTS.md and .harness/workflow.md. Read those.


Triage paths and gates

The coordinator routes each request through three sequential gates (.harness/triage.md in target repos):

Gate When What happens
Step 0 — Scaffold gate .harness/SCAFFOLD-PENDING marker exists Force scaffold path (4 pre-named phases). Skip the three-question rubric. Marker is removed when the scaffold task archives.
Steps 1–3 — Light/Full rubric Marker absent Three checks (schema / cross-cutting / tests). All NO → light (single phase). Any YES → full (multi-phase, fresh session per phase).
Step 4 — Spec gate After path decision Decide if the task needs a standalone design spec at docs/specs/<task-id>.md. Triggers: cross 2+ cross-cutting dirs, new external dep, external-interface change, data-model change, or explicit ask. Adds a Phase 0 to the brief.

There's also a TODO gate in the coordinator opening (before the rubric): if CONTRIBUTING.md still has unfilled 🛠 TODO (project maintainers) blocks and you're not in scaffold mode, the coordinator runs a fill-contributing prep task first — scans your manifest/CI/formatter configs, proposes candidates, writes only after you confirm.


Presets

Preset Format / test commands Default forbidden_without_brief Default spec_dir
python-uv uv run ruff format . / uv run pytest ["src", "tests"] docs/specs/

Adding a new preset is one TOML file under presets/ — copy presets/python-uv.toml and edit. After running init, harness.config.toml is yours to tune; the preset only seeds the defaults.

Key config keys you'll want to know about:

  • paths.architecture_doc — where the project's directory-layout doc lives (defaults to docs/ARCHITECTURE.md); coordinator/implementer always read this.
  • paths.spec_dir — where standalone design specs land when the spec gate fires (defaults to docs/specs/).
  • paths.cross_cutting — directories whose 2+-span triggers full path and spec gate.

File ownership in target repos

CommonHarness sorts files in the target repo into three layers, and each behaves differently on re-runs / upgrades:

Layer Files Behavior on harness upgrade / harness init --force
Managed AGENTS.md, .harness/{workflow,triage,README,templates/*}, .claude/{commands,hooks}/*, .cursor/rules/harness.mdc, mcp/skills/harness/SKILL.md, .codex/hooks/check-harness-state.sh Overwritten cleanly from upstream templates
Fenced fragment CONTRIBUTING.md, CLAUDE.md, .github/PULL_REQUEST_TEMPLATE.md Marker-aware merge: only the <!-- harness:begin --> … <!-- harness:end --> block is replaced; everything outside (your business content) is preserved
Hooks-merge JSON .claude/settings.json, .codex/hooks.json Only the hooks key is replaced from upstream; every other top-level key you add (permissions, env, …) is preserved — even under --force
Owned .harness/CURRENT.md, .harness/MEMORY.md, .harness/TODO.md, .harness/active/*, .harness/archive/*, .harness/SCAFFOLD-PENDING, docs/specs/* Never touched. The scaffold marker is created by --greenfield init and removed by harness end of a path: scaffold brief. Spec files are created by the implementer in Phase 0.

This is what makes the protocol upgradable without clobbering project-specific work.


Upgrade story

Step 1 — bump the CLI to a new version (per developer)

# Install a new version alongside the old one + flip 'current'.
# (Replace v0.7.2 with whichever release you're upgrading to.)
curl -fsSL https://raw.githubusercontent.com/Libr-AI/CommonHarness/v0.7.2/install.sh \
  | HARNESS_VERSION=v0.7.2 bash

harness --version    # confirms the new version is now active

# Roll back any time by flipping the symlink (older versions stay on disk):
ln -sfn ~/.commonharness/v0.7.1 ~/.commonharness/current

Old versions stay on disk; switching is a single symlink. Because each version dir is a shallow tag clone, you can't accidentally git checkout a different ref and produce inconsistent behavior across the team.

Step 2 — apply the new version to a project

From the project root:

cd /path/to/target/project
harness upgrade
git diff            # review what changed

harness upgrade re-renders the Managed protocol/template files (AGENTS.md, .harness/{workflow,triage,templates}, the Claude/Cursor/Codex integrations) to the installed version, while:

  • preserving harness.config.toml — your tuned [verify] / [paths] / [branch] / mode / lang values are kept; new sections from the new version are added with defaults; harness_version is bumped. (It reads the preset from your config, so no --preset needed.)
  • never touching your work.harness/{CURRENT,MEMORY,TODO}.md, active/, archive/, and docs/specs/ are left as-is. An in-progress task survives the upgrade.

Caveat: Managed files are refreshed from upstream, so manual edits to AGENTS.md / workflow.md / templates / integration hooks are replaced. Keep project-specific content out of those — put it in CONTRIBUTING.md (outside the harness:begin/end fence), your own .harness/ docs, or docs/ARCHITECTURE.md. Fenced fragments (CONTRIBUTING.md, CLAUDE.md, PR template) keep your out-of-fence content; settings.json / .codex/hooks.json keep every key except hooks.

What --force touches:

  • Managed files (AGENTS.md, .harness/{workflow,triage,README,templates/*}, .claude/*, .cursor/rules/*, mcp/skills/*) → re-rendered from upstream templates (your edits to these files are overwritten — they're not meant to be edited locally).
  • Fenced fragments (CONTRIBUTING.md, CLAUDE.md, .github/PULL_REQUEST_TEMPLATE.md) → marker-aware merged. Only the <!-- harness:begin --> … <!-- harness:end --> block is replaced; your business content outside the fence is preserved.
  • Owned files (.harness/CURRENT.md, .harness/MEMORY.md, .harness/TODO.md, .harness/active/*, .harness/archive/*, .harness/SCAFFOLD-PENDING, docs/specs/*) → never touched. In-progress tasks, accumulated memory, and existing specs all survive.
  • harness.config.toml → depends on the command: harness upgrade preserves it (overlays your tuned values onto the new structure, adds any new sections with defaults, bumps harness_version); harness init --force regenerates it from the preset (resetting [verify] / [paths] / etc.). For upgrades, prefer harness upgrade.

The renderer prints + wrote / ~ merged / · skipped for each file so the diff is auditable.

Whoever runs Step 2 then commits + pushes, and the rest of the team gets the new protocol on git pull of their project — they don't all need to run harness upgrade themselves.

Planned for future versions

  • harness upgrade --dry-run — show the diff before writing (today: just review with git diff afterwards).
  • harness doctor — reports drift, broken hooks, version mismatch (harness --version ≠ project's harness_version), and unfilled 🛠 TODO (project maintainers) markers (note: the v0.2.0 TODO gate already detects these at harness start time, but doctor surfaces them outside any coordinator session).

Repository layout

bin/
  harness            # bash CLI (init, start, implement, status, end, remember, curate-memory)
  _render.py         # template engine + marker-aware merge
  _toml.py           # zero-dep TOML loader (Python 3.9+ fallback)
  _cfg_get.py        # CLI: read one key from harness.config.toml
templates/           # .tmpl files; directory layout mirrors the target repo
presets/             # one .toml per preset
install.sh           # curl-pipe-bash bootstrap
.claude-plugin/      # Claude Code plugin manifest
tests/               # (placeholder for bats-core suite)

License

MIT.

About

No description, website, or topics provided.

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors