Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "local-skills",
"description": "Local development marketplace for the pandavas skill",
"owner": { "name": "pandavas", "email": "noreply@example.com" },
"plugins": [
{
"name": "pandavas",
"description": "Autonomous, test-gated multi-agent code resolution where deterministic verbs rule on correctness.",
"version": "0.1.0",
"source": "./",
"author": { "name": "pandavas", "email": "noreply@example.com" }
}
]
}
8 changes: 8 additions & 0 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "pandavas",
"description": "Autonomous, test-gated multi-agent code resolution: a /pandavas command + skill where the host model plays the research/worker/judge agents and deterministic Python verbs rule on whether the code is correct.",
"version": "0.1.0",
"author": { "name": "pandavas", "email": "noreply@example.com" },
"license": "MIT",
"keywords": ["multi-agent", "coding-agent", "tdd", "test-gated", "orchestration", "claude-code", "cursor"]
}
10 changes: 10 additions & 0 deletions .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "pandavas",
"displayName": "Pandavas",
"description": "Autonomous, test-gated multi-agent code resolution where deterministic verbs rule on correctness. (Cursor has no subagent primitive, so the judge runs as a fresh-pass self-review; the deterministic gates are identical to Claude Code.)",
"version": "0.1.0",
"author": { "name": "pandavas", "email": "noreply@example.com" },
"license": "MIT",
"keywords": ["multi-agent", "coding-agent", "tdd", "test-gated", "orchestration", "cursor"],
"skills": "./skills/"
}
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Keep shell installers LF (must run under bash) and PowerShell installers CRLF.
*.sh text eol=lf
*.ps1 text eol=crlf
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ Thumbs.db
runs/
traces/
*.log

# --- Skill-mode working/state dir ---
.pandavas/
15 changes: 12 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ moving to the next.

## Learned Rules

> Accumulate project-specific conventions here as they're discovered. Empty for now.

-
> Accumulate project-specific conventions here as they're discovered.

- **Skill mode is additive (keyless `/pandavas`).** The host harness's model
(Claude Code / Cursor) plays the LLM agents while deterministic CLI verbs in
`src/pandavas/spine.py` (`baseline`, `resolve-brief`, `run-tests`, `judge-gate`,
`decide`, `restore`, `commit`, ...) own every gate. The verbs reuse `run`'s logic
(`nodes.make_bhima_test` / `_better` / `_restore_snapshot`) and pass state through a
gitignored `.pandavas/` dir; `decide` is the sole writer of `loop.json`. The
standalone `python -m pandavas run` path and the six deterministic modules are
unchanged. Packaging (`commands/`, `skills/`, `agents/`, `scripts/install-*`) is
top-level and excluded from the wheel (pyproject packages `src/` only). Keep verb
stdout ASCII-only (`json.dumps(..., ensure_ascii=True)`). Design: `docs/SKILL_MODE.md`.
35 changes: 32 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,17 +176,46 @@ no API key:
python examples/demo.py
```

## Skill mode (keyless, in-editor)

pandavas also runs **without an API key**, as a `/pandavas` command inside a coding
harness (Claude Code, Cursor). The harness's own model plays the three LLM agents;
deterministic `python -m pandavas` verbs play the test runner and the orchestrator, so
the same regression-aware gate still rules on correctness. You get the in-editor
ergonomics; the engine keeps the guarantees.

Install the engine plus the skill, then use it in any repo whose test suite runs:

```bash
pip install -e . # so `python -m pandavas` works
bash scripts/install-claude.sh # Claude Code (or scripts/install-claude.ps1)
bash scripts/install-cursor.sh # Cursor (or scripts/install-cursor.ps1)
```

```
/pandavas . Fix the off-by-one in paginate()
```

The host model researches into an anchored brief (resolve-gated) and writes the change;
the verbs decide: `run-tests` (exit 0 only if no new failures vs baseline), `judge-gate`,
then `decide` (converge / retry / stop). On Claude Code the judge runs as a genuine
fresh-context subagent; on Cursor it degrades to a fresh-pass self-review (the
deterministic gates are identical). One honest caveat: skill mode can't *structurally*
force the gates the way `run` does — the model must call them and obey their exit codes
— so `python -m pandavas run` stays the hard-determinism path. Full design:
[`docs/SKILL_MODE.md`](docs/SKILL_MODE.md).

## Tests

```bash
python -m pytest
```

93 tests cover the deterministic core directly: the executor and test-command
121 tests cover the deterministic core directly: the executor and test-command
detection, the JUnit parser, the resolve gate, the path-safe edit applier,
snapshot and diffing, the regression and red→green logic, oscillation and
best-attempt restore, the git helpers, the CLI, and an end-to-end red→green run on
the fixture. 92 run offline and pass; one is a live-API smoke test that runs only
best-attempt restore, the git helpers, the CLI, the keyless verb spine and a skill-mode end-to-end run, and an end-to-end red→green run on
the fixture. 120 run offline and pass; one is a live-API smoke test that runs only
when `GROQ_API_KEY` is set. CI runs the suite on Python 3.10, 3.11, and 3.12.

## Tech stack
Expand Down
20 changes: 20 additions & 0 deletions agents/pandavas-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: pandavas-judge
description: Independent fresh-context reviewer (Sahadeva) for pandavas skill mode. Judges a proposed change against acceptance criteria and the true test state and returns {"approved", "feedback"}. Invoked by the pandavas skill behind the deterministic judge-gate - it must never see the worker's reasoning.
tools: Read, Grep
---

You are Sahadeva, an INDEPENDENT code reviewer. You did NOT write this code and must not assume it is correct.

You are given only: the acceptance criteria, the cumulative diff under review (often written to `.pandavas/diffs/cumulative.diff`), and the list of task-relevant tests still failing (if any). A deterministic gate has already confirmed the suite has no NEW failures versus baseline, so your job is correctness against intent - not re-running tests.

Review:
- Does the change actually satisfy EVERY acceptance criterion?
- If a test was added or modified, does it genuinely verify the requirement, or is it vacuous (trivially passing, asserts nothing meaningful)?
- Is any test relevant to the task still failing? If so, the change is INCOMPLETE.

Approve ONLY if the change satisfies the acceptance criteria AND no task-relevant test remains failing.

Output ONLY a JSON object - no prose, no markdown, no code fences:

{"approved": <bool>, "feedback": "<specific and actionable; when rejecting, name what is wrong and where (file:line)>"}
12 changes: 12 additions & 0 deletions commands/pandavas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
description: Autonomously resolve one test-gated bug fix or small feature on a local repo - research, worker, test, judge, orchestrate, where deterministic Python verbs rule on correctness.
argument-hint: <repo path or "."> <bug to fix / feature to add>
allowed-tools: Bash, Read, Edit, Write, Grep, Glob, Task
---
Use the **pandavas** skill.

Task (repo + what to fix or build): $ARGUMENTS

Baseline the suite, research once into an anchored brief and resolve-gate it, then loop worker -> run-tests -> judge-gate -> fresh-context judge -> decide until the verbs converge (`decide` exit 0) or honestly report they could not. The Python verbs rule on correctness: never claim success without `run-tests` exit 0, never claim convergence without `decide` exit 0.

Close by showing the diff, the red->green tests, and the deterministic verdicts (real exit codes). STOP for my approval before committing.
115 changes: 115 additions & 0 deletions docs/SKILL_MODE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Skill mode (keyless `/pandavas`)

> Design source of truth is [`SPEC.md`](SPEC.md). This document describes the
> additive **skill mode** layered on top of it. The standalone
> `python -m pandavas run` path is unchanged.

## Why two modes

pandavas runs in two ways that share one deterministic core:

| | Standalone (`python -m pandavas run`) | Skill (`/pandavas`) |
|---|---|---|
| LLM agents | pandavas's own LLM client + API key | the **host harness's model** (Claude Code / Cursor) - **no API key** |
| Orchestration | LangGraph state machine, one process | the host model follows a protocol, calling verbs via the shell |
| Determinism | **hard** - the process forces every gate | **soft** - the model must call the verbs and obey their exit codes |
| Best for | a CI-able, reproducible proof of the system | a frictionless, keyless fix from inside your editor |

Both call the **same** deterministic code, so a fix delivered in skill mode is gated
by the same regression-aware test logic the standalone mode is CI-tested against.

## The split across the process boundary

A subprocess cannot borrow the harness's model, so skill mode does not run the agents
as a subprocess. Instead it splits the work:

- **The host model owns the LLM-shaped steps:** research (Nakula), writing the edit
(Arjuna), judging (Sahadeva).
- **The deterministic decisions stay in pandavas's Python**, exposed as LLM-free CLI
verbs the skill calls: the resolve gate, the regression-aware test verdict,
oscillation detection, the iteration cap, best-attempt restore, git delivery.

The model is the hands and eyes; the verbs are the rules. The skill protocol lives in
[`../skills/pandavas/SKILL.md`](../skills/pandavas/SKILL.md).

## The verb surface

Every verb takes `--repo`, prints one ASCII JSON line, and sets a process exit code.
They are implemented in [`../src/pandavas/spine.py`](../src/pandavas/spine.py), reusing
the same functions as `run` (`executor`, `testresults`, `edits`, `diffing`, `gitutils`,
`brief`, plus `nodes.make_bhima_test` / `_better` / `_restore_snapshot`).

| Verb | Role | Exit codes |
|---|---|---|
| `baseline` | Bhima | run the suite once; capture baseline + snapshots; init run state | 0 / 1 no test cmd |
| `detect-test` | - | print the auto-detected test command | 0 / 1 none |
| `resolve-brief` | gate | every anchor must resolve to real files/lines | 0 resolved / 1 dangling |
| `run-tests` | Bhima | regression-aware verdict vs baseline | **0 iff no new failures** / 1 |
| `diff` | - | render the cumulative / per-iteration diff to a file | 0 |
| `judge-gate` | Sahadeva | deterministic pre-LLM reject (failing tests / empty diff) | 0 review / 30 reject |
| `decide` | Yudhishthira | trace, best-attempt, oscillation, cap, restore-on-stop | 0 converged / 10 continue / 20 stop |
| `restore` | - | restore the best or baseline snapshot | 0 / 1 |
| `commit` | - | deliver a converged change to a new branch | 0 / 1 |
| `apply-edits` | - | optional path-safe full-file writer | 0 / 1 rejected |

## The `.pandavas/` working directory

Because the verbs are stateless processes, run state persists in a `.pandavas/`
directory under the target repo:

```
.pandavas/
baseline.json test command, junit path, baseline per-test, exit code
brief.json the resolved ResearchBrief (research-once cache)
loop.json iteration / signatures / best-attempt / trace / status
last_test.json the latest regression verdict
snapshots/ baseline.json, pre_iter.json, best.json
diffs/ cumulative.diff, iter.diff
```

It is kept out of your repo three ways: `baseline` appends `.pandavas/` to
`<git-dir>/info/exclude` (so a `commit` never stages it), every snapshot drops
`.pandavas/` keys, and `.pandavas` is in `retrieval.IGNORE_DIRS`. `decide` is the sole
writer of `loop.json` and recomputes diffs/signatures from snapshots, so a fabricated
model diff cannot game the loop.

## Fresh-context judge, per harness

The judge (Sahadeva) must not review its own work. Behind the deterministic
`judge-gate`:

- **Claude Code** spawns the **`pandavas-judge` subagent**
([`../agents/pandavas-judge.md`](../agents/pandavas-judge.md)) with only the
acceptance criteria, the cumulative diff, and the still-failing tests - genuine
fresh context.
- **Cursor** has no equal subagent primitive, so the judge runs as a fresh-pass
self-review in shared context. This is **honestly weaker** (the reviewer has seen
the worker's reasoning). The deterministic gates are identical on both; only the
LLM-review independence degrades. For strict isolation, use Claude Code or the
standalone `run`.

## Honest limitation: soft vs hard determinism

In standalone `run`, the process calls Yudhishthira unconditionally - the gates cannot
be skipped. In skill mode the host model must *choose* to call `run-tests` /
`judge-gate` / `decide` and *obey* their exit codes. An over-eager model could skip a
gate and claim success. This is mitigated (the Iron Law, the mandatory procedure, a
proof step that pastes real exit codes, `commit` gated behind `decide` exit 0 plus
human approval, and `decide` recomputing from snapshots) but not eliminated.

**`python -m pandavas run` remains the hard-determinism proof; `/pandavas` is the
keyless ergonomic path.**

## Install & use

```bash
pip install -e . # the engine, so `python -m pandavas` works
bash scripts/install-claude.sh # or scripts/install-claude.ps1 (Claude Code)
bash scripts/install-cursor.sh # or scripts/install-cursor.ps1 (Cursor)
```

Then, in any repo whose test suite already runs:

```
/pandavas . Fix the off-by-one in paginate()
```
12 changes: 12 additions & 0 deletions scripts/install-claude.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Install the pandavas skill into Claude Code (run from the repo root).
$dest = Join-Path $HOME ".claude"
New-Item -ItemType Directory -Force -Path "$dest\commands","$dest\skills","$dest\agents" | Out-Null
Copy-Item -Recurse -Force commands\* "$dest\commands\"
Copy-Item -Recurse -Force skills\* "$dest\skills\"
Copy-Item -Recurse -Force agents\* "$dest\agents\"
Write-Host "Installed the pandavas skill into Claude Code -> $dest"
Write-Host ""
Write-Host "Also install the engine so 'python -m pandavas' works:"
Write-Host " pip install -e . # from this repo, or: pip install pandavas"
Write-Host ""
Write-Host "Then open Claude Code in any repo and type: /pandavas . <task>"
15 changes: 15 additions & 0 deletions scripts/install-claude.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Install the pandavas skill into Claude Code (run from the repo root).
set -euo pipefail
DEST="${HOME}/.claude"
mkdir -p "$DEST/commands" "$DEST/skills" "$DEST/agents"
cp -r commands/* "$DEST/commands/"
cp -r skills/* "$DEST/skills/"
cp -r agents/* "$DEST/agents/"
echo "Installed the pandavas skill into Claude Code."
echo " commands -> $DEST/commands skills -> $DEST/skills agents -> $DEST/agents"
echo
echo "Also install the engine so 'python -m pandavas' works:"
echo " pip install -e . # from this repo, or: pip install pandavas"
echo
echo "Then open Claude Code in any repo and type: /pandavas . <task>"
15 changes: 15 additions & 0 deletions scripts/install-cursor.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Install the pandavas skill into Cursor (run from the repo root).
$dest = Join-Path $HOME ".cursor"
New-Item -ItemType Directory -Force -Path "$dest\commands","$dest\skills" | Out-Null
Copy-Item -Recurse -Force commands\* "$dest\commands\"
Copy-Item -Recurse -Force skills\* "$dest\skills\"
Write-Host "Installed the pandavas skill into Cursor -> $dest"
Write-Host ""
Write-Host "Note: Cursor has no subagent primitive, so the judge (Sahadeva) runs as a"
Write-Host "fresh-pass self-review in shared context - weaker independence than Claude"
Write-Host "Code's subagent. The deterministic gates (run-tests, judge-gate, decide) are"
Write-Host "identical on both harnesses. For strict judge isolation use Claude Code, or"
Write-Host "run the standalone engine: python -m pandavas run --repo . --task '...'"
Write-Host ""
Write-Host "Also install the engine: pip install -e . (so 'python -m pandavas' works)"
Write-Host "Then in Cursor, in any repo, type: /pandavas . <task>"
18 changes: 18 additions & 0 deletions scripts/install-cursor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Install the pandavas skill into Cursor (run from the repo root).
set -euo pipefail
DEST="${HOME}/.cursor"
mkdir -p "$DEST/commands" "$DEST/skills"
cp -r commands/* "$DEST/commands/"
cp -r skills/* "$DEST/skills/"
echo "Installed the pandavas skill into Cursor."
echo " commands -> $DEST/commands skills -> $DEST/skills"
echo
echo "Note: Cursor has no subagent primitive, so the judge (Sahadeva) runs as a"
echo "fresh-pass self-review in shared context - weaker independence than Claude"
echo "Code's subagent. The deterministic gates (run-tests, judge-gate, decide) are"
echo "identical on both harnesses. For strict judge isolation use Claude Code, or"
echo "run the standalone engine: python -m pandavas run --repo . --task \"...\""
echo
echo "Also install the engine: pip install -e . (so 'python -m pandavas' works)"
echo "Then in Cursor, in any repo, type: /pandavas . <task>"
Loading
Loading