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
84 changes: 51 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
<div align="center">

<img src="docs/images/logo.png" alt="Chorus" width="120" />
<img src="docs/images/logo.png" alt="Polyphony" width="120" />

# Chorus
# Polyphony

**A second opinion (and a third) before you ship AI-written code — using the AI subscriptions you already pay for.**

2–3 _different_ AI tools review the same change in parallel, only green-lighting when they agree. Runs on your existing Claude Pro / ChatGPT Plus / Gemini Advanced — typical review costs **$0** out of pocket.

[![CI](https://github.com/chorus-codes/chorus/actions/workflows/ci.yml/badge.svg)](https://github.com/chorus-codes/chorus/actions/workflows/ci.yml)
[![npm](https://img.shields.io/npm/v/chorus-codes?color=22c55e)](https://www.npmjs.com/package/chorus-codes)
> Formerly **Chorus**. The `chorus` CLI binary, `~/.chorus/` config directory, and `chorus` MCP registration all still work — see [Compatibility](#compatibility) below.

[![CI](https://github.com/crypticpy/chorus/actions/workflows/ci.yml/badge.svg)](https://github.com/crypticpy/chorus/actions/workflows/ci.yml)
[![npm](https://img.shields.io/npm/v/@crypticpy/polyphony?color=22c55e)](https://www.npmjs.com/package/@crypticpy/polyphony)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue)](./LICENSE)
[![Status](https://img.shields.io/badge/status-v0.8-brightgreen)](./ROADMAP.md)
[![Node](https://img.shields.io/badge/node-%E2%89%A520-339933)](https://nodejs.org/)
Expand All @@ -22,15 +24,15 @@

<div align="center">

<img src="docs/images/hero-demo.gif" alt="Six AI reviewers streaming verdicts in parallel — Chorus running live" width="800" />
<img src="docs/images/hero-demo.gif" alt="Six AI reviewers streaming verdicts in parallel — Polyphony running live" width="800" />

**One AI writes. Three review. You ship only when they agree — using AI subscriptions you already pay for.**

</div>

---

## The problem Chorus solves
## The problem Polyphony solves

🤖 **AI coding tools are confident — and wrong about 5% of the time** in subtle ways that are hard to spot until production.

Expand All @@ -40,16 +42,16 @@ Asking GPT to review GPT's work is theatre — same training, same biases.
💸 **Multi-AI review on raw API keys gets expensive fast.**
Every diff × 3 reviewers × pay-per-token = real money. So nobody does it routinely.

### Chorus fixes all three
### Polyphony fixes all three

✅ **Different vendors review each other.**
Claude writes, GPT and Gemini check it. Different blind spots cover each other. Disagreement = red flag _before_ you merge.

✅ **Uses your existing AI subscriptions.**
You're already paying for Claude Pro / ChatGPT Plus / Gemini Advanced (~$20/mo each). Chorus drives them headlessly through their CLIs — every multi-AI review costs **$0 out of pocket**, just counts against the quota you already have. Per-token API users save 10-100× vs running the same prompts directly.
You're already paying for Claude Pro / ChatGPT Plus / Gemini Advanced (~$20/mo each). Polyphony drives them headlessly through their CLIs — every multi-AI review costs **$0 out of pocket**, just counts against the quota you already have. Per-token API users save 10-100× vs running the same prompts directly.

✅ **Local-first, zero markup.**
Your code never reaches a new vendor. Chorus runs on your laptop, talks to the AI tools you already trust, and shuts up. Open source, Apache-2.0.
Your code never reaches a new vendor. Polyphony runs on your laptop, talks to the AI tools you already trust, and shuts up. Open source, Apache-2.0.

That's the whole pitch.

Expand All @@ -59,35 +61,37 @@ That's the whole pitch.

🚨 **You asked Claude to write a `divide(a, b)` helper.**
It says "looks correct!" You ship. Production crashes at 2am because nobody handled `b = 0`.
_With Chorus: GPT or Gemini would have flagged it in the review pass before you merged._
_With Polyphony: GPT or Gemini would have flagged it in the review pass before you merged._

🔧 **You're refactoring a critical path.**
Your AI rewrote 200 lines and says it's behaviour-equivalent. You're tired and skeptical.
_Run it through Chorus. Three different AIs all saying "yes, equivalent" lets you sleep._
_Run it through Polyphony. Three different AIs all saying "yes, equivalent" lets you sleep._

🏗️ **Big architectural call** — queue vs polling, sync vs async, this DB vs that one.
Write a paragraph, hit Chorus. _Three different models give you three angles you hadn't thought of._
Write a paragraph, hit Polyphony. _Three different models give you three angles you hadn't thought of._

📝 **Reviewing a 600-line PR.**
You're short on time. Paste the diff into Chorus. _Three reviewers spot the obvious bugs in 90 seconds. Your job becomes the 5% they couldn't catch._
You're short on time. Paste the diff into Polyphony. _Three reviewers spot the obvious bugs in 90 seconds. Your job becomes the 5% they couldn't catch._

⚔️ **Test-driven development where neither AI cheats.**
_One AI writes tests blind to the code; another AI writes code to pass them._ Use the `red-green` template.

🐛 **Hunting a flaky bug.**
Reproduces 1-in-20, no obvious pattern. Drop the failing test + suspect code into Chorus.
Reproduces 1-in-20, no obvious pattern. Drop the failing test + suspect code into Polyphony.
_Each reviewer attacks the bug from a different angle — race? clock skew? off-by-one? — and you land on the cause faster than walking it alone._

---

## Quick start

```bash
npm i -g chorus-codes # install (no sudo — see below)
chorus init # finds AI tools you already have, wires up MCP
chorus start --ui # opens http://localhost:5050
npm i -g @crypticpy/polyphony # install (no sudo — see below)
chorus init # finds AI tools you already have, wires up MCP
chorus start --ui # opens http://localhost:5050
```

> The CLI binary is `chorus` (kept stable for muscle memory + existing docs). `polyphony` is also installed as an alias — both run the same code. Use whichever you like.

Paste a task. Hit submit. Watch the AIs argue.

> **Don't `sudo npm install -g`** if you use nvm, fnm, asdf, or any per-user
Expand All @@ -104,7 +108,7 @@ expects regardless of how you installed.

### Or drive it from any AI CLI you already use

`chorus init` registers Chorus as an MCP server with every CLI / IDE it detects (Claude Code, Codex, Gemini CLI, Cursor, Windsurf, Kimi, OpenCode). After that, just ask the assistant in plain English:
`chorus init` registers Polyphony as an MCP server with every CLI / IDE it detects (Claude Code, Codex, Gemini CLI, Cursor, Windsurf, Kimi, OpenCode). The MCP server is registered under the name `chorus` so existing prompts keep working. After init, just ask the assistant in plain English:

```
> Use chorus to review the staged diff against main
Expand Down Expand Up @@ -182,8 +186,8 @@ Pick whichever vendor you already pay for. Or skip CLIs entirely and add an Open
</td>
<td width="50%" align="center">
<b>From inside Claude / Cursor</b><br/>
<img src="docs/images/mcp.png" alt="Claude Code calling Chorus" width="100%" /><br/>
<sub>Any AI tool that speaks MCP can trigger a Chorus run.</sub>
<img src="docs/images/mcp.png" alt="Claude Code calling Polyphony" width="100%" /><br/>
<sub>Any AI tool that speaks MCP can trigger a Polyphony run.</sub>
</td>
</tr>
</table>
Expand All @@ -200,7 +204,7 @@ function divide(a, b) {
}
```

Submit to Chorus with the **Code Review** template (1 writer + 2 reviewers, both must agree to ship):
Submit to Polyphony with the **Code Review** template (1 writer + 2 reviewers, both must agree to ship):

| Step | What happens |
| ----------------------------- | ----------------------------------------------------------- |
Expand Down Expand Up @@ -233,7 +237,7 @@ Make your own by dropping a YAML file in `~/.chorus/templates/`. Or duplicate on

The `audit-*` templates run in two phases:

1. **Audit phase** — point Chorus at a repo, pick a preset (code review, architecture review, de-slopify, monolith breakdown, engineering review), and one writer produces a checklist of concrete changes. You approve the items you want.
1. **Audit phase** — point Polyphony at a repo, pick a preset (code review, architecture review, de-slopify, monolith breakdown, engineering review), and one writer produces a checklist of concrete changes. You approve the items you want.
2. **Orchestrate phase** — approved items fan out to worker voices on per-worker git branches. Each worker gets its own slot, model, and persona. Output: an `orchestrate-manifest.json` with diff stats and per-worker status, plus Checkout / Open-PR buttons in the cockpit.

### Verify phase + TDD loop
Expand Down Expand Up @@ -269,7 +273,7 @@ quorum:

## Reviewer personas

Each reviewer can wear a "hat" — a focus area Chorus prepends to their prompt:
Each reviewer can wear a "hat" — a focus area Polyphony prepends to their prompt:

| Persona | What they look for |
| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
Expand All @@ -285,7 +289,7 @@ Different personas reviewing the same change = wider net.

## Why "different vendors" matters

You can run Chorus with three Claudes. We let you. But the value drops a lot.
You can run Polyphony with three Claudes. We let you. But the value drops a lot.

A second Claude reviewing the first Claude's work is theatre — same training, same blind spots. Mix vendors (Claude + GPT + Gemini) and you get genuinely different angles, because they were trained on different data with different biases.

Expand All @@ -303,7 +307,7 @@ A typical review = **$0** out of pocket. Counts against the quota you already ha
**Using API keys** (pay-per-use)
A typical code-review run = **$0.30 to $1.50**, depending on diff size. If reviewers disagree and retry, 2–3× worst case.

Chorus adds **zero markup**. We don't see your tokens.
Polyphony adds **zero markup**. We don't see your tokens.

---

Expand All @@ -325,15 +329,15 @@ Configure on first run, or anytime at _Settings → Permissions_.

## Compared to other code-review tools

| | **Chorus** | CodeRabbit | Greptile | Cursor Review | GitHub Copilot |
| | **Polyphony** | CodeRabbit | Greptile | Cursor Review | GitHub Copilot |
| -------------------------------------------------------------- | :-----------: | :--------: | :------: | :-----------: | :------------: |
| Multiple AI vendors review the same change | ✅ | ❌ | ❌ | ❌ | ❌ |
| Uses your existing AI subscriptions | ✅ | ❌ | ❌ | ❌ | ❌ |
| Runs locally (your code never leaves your existing AI vendors) | ✅ | ❌ | ❌ | partial | ❌ |
| Open source (modify + self-host) | ✅ Apache-2.0 | ❌ | ❌ | ❌ | ❌ |
| Custom review patterns | ✅ | partial | ❌ | ❌ | ❌ |

**The unique thing:** your code never goes to a new vendor. Chorus just orchestrates the AI tools you already use.
**The unique thing:** your code never goes to a new vendor. Polyphony just orchestrates the AI tools you already use.

---

Expand Down Expand Up @@ -384,7 +388,7 @@ older Node + Windows combos), a self-contained crash log is written to

## Telemetry

Chorus pings home once on startup and once every 24h. The payload is fixed:
Polyphony pings home once on startup and once every 24h. The payload is fixed:

```json
{
Expand Down Expand Up @@ -433,7 +437,7 @@ Full picture in [ROADMAP.md](./ROADMAP.md).
flowchart TB
User([👤 You])
Cockpit[Cockpit<br/>:5050 · web UI]
Daemon[Chorus daemon<br/>:7707 · local server]
Daemon[Polyphony daemon<br/>:7707 · local server]
DB[(SQLite<br/>~/.chorus/chorus.db)]
MCP[MCP server<br/>for editor integrations]

Expand All @@ -447,7 +451,7 @@ flowchart TB
Daemon -->|spawn| Claude
Daemon -->|spawn| Codex
Daemon -->|spawn| Gemini
User -.->|"call Chorus from your AI"| MCP --> Daemon
User -.->|"call Polyphony from your AI"| MCP --> Daemon

classDef user fill:#fef3c7,stroke:#f59e0b,color:#000
classDef chorus fill:#dbeafe,stroke:#3b82f6,color:#000
Expand All @@ -461,9 +465,9 @@ flowchart TB

- **Daemon** — small local server (port 7707) that spawns AI tools as subprocesses, parses their output, and tracks state in a SQLite database at `~/.chorus/chorus.db`.
- **Cockpit** — the web UI at port 5050 (Next.js). Templates, chats, voices, settings.
- **MCP server** — lets _other_ AI tools (Claude Code, Cursor, etc.) call Chorus programmatically.
- **MCP server** — lets _other_ AI tools (Claude Code, Cursor, etc.) call Polyphony programmatically.

Each AI runs as an isolated subprocess. Chorus reads their structured output (stream-JSON), compares against the template's quorum rule, and emits a verdict. Nothing leaves your machine except the calls to the AI vendors you already use.
Each AI runs as an isolated subprocess. Polyphony reads their structured output (stream-JSON), compares against the template's quorum rule, and emits a verdict. Nothing leaves your machine except the calls to the AI vendors you already use.

Code layout:

Expand All @@ -490,7 +494,7 @@ pnpm test # full suite

Read [`AGENTS.md`](./AGENTS.md) first — Next.js 16 has breaking changes from older versions. Coverage target on new code: 80%+.

We dogfood: PRs to Chorus go through Chorus before merging.
We dogfood: PRs to Polyphony go through Polyphony before merging.

See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full guide.

Expand All @@ -504,6 +508,20 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full guide.

---

## Compatibility

Polyphony was published as `chorus-codes` in v0.7. The v0.8 rebrand keeps everything you typed before working:

- **CLI binary** — `chorus` still works. `polyphony` is installed as an alias; both run the same code.
- **Config directory** — `~/.chorus/` is still the canonical location for `chorus.db`, templates, logs, secrets, and the install-id.
- **MCP server** — `chorus init` still registers under the name `chorus` in your editors (Claude Code, Codex, Cursor, etc.), so any prompt that says "ask chorus to…" or any `chorus.create_chat` tool call keeps working.
- **Env vars** — `CHORUS_TELEMETRY`, `CHORUS_DB_PATH`, `CHORUS_CODEX_HOME`, `CHORUS_LOG_LEVEL` are unchanged.
- **GitHub repo** — still `crypticpy/chorus`. The repo name will move in a follow-up.

If you're on the old `chorus-codes` npm package, switch with: `npm uninstall -g chorus-codes && npm i -g @crypticpy/polyphony`. No other change required.

---

## License

[Apache-2.0](./LICENSE). Use it however you want, including commercially.
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
{
"name": "chorus-codes",
"name": "@crypticpy/polyphony",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Point self-update at the renamed npm package

Now that the published package is renamed to @crypticpy/polyphony, the existing update paths still target chorus-codes: chorus update fetches that package and runs npm install -g chorus-codes@..., chorus start uses the same default check, and /update-check explicitly calls fetchLatestVersion('chorus-codes'). For users who install this scoped package, update banners will compare against the wrong registry entry and the update command will install/conflict with the old package instead of upgrading Polyphony.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ef877bffetchLatestVersion now defaults to @crypticpy/polyphony and the daemon's /update-check route passes the same scoped name, so chorus update, the start-up nudge, and the cockpit banner all compare/install the right package. Update command now runs npm install -g @crypticpy/polyphony@<latest>.

"version": "0.8.26",
"description": "Driver-agnostic multi-LLM peer review for code decisions. Bring your own CLI; Chorus convenes 2-4 other LLMs to review the work before you ship.",
"description": "Driver-agnostic multi-LLM peer review for code decisions. Bring your own CLI; Polyphony convenes 2-4 other LLMs to review the work before you ship.",
"license": "Apache-2.0",
"author": "99x Agency",
"homepage": "https://chorus.codes",
"author": "crypticpy",
"homepage": "https://github.com/crypticpy/chorus",
"repository": {
"type": "git",
"url": "https://github.com/chorus-codes/chorus.git"
"url": "https://github.com/crypticpy/chorus.git"
},
"bin": {
"polyphony": "./bin/chorus.mjs",
"chorus": "./bin/chorus.mjs"
},
"publishConfig": {
Expand Down
12 changes: 5 additions & 7 deletions src/app/api/daemon/[...path]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,9 @@ async function proxy(req: NextRequest, ctx: ProxyContext): Promise<Response> {
// while the daemon itself only exposes the versioned shape. Exact
// segment check — `startsWith("api/v1")` would naively match
// `api/v10/...` or `api/v1foo/...` and skip prepending.
const isPrefixed = segments === API_PREFIX || segments.startsWith(`${API_PREFIX}/`);
const versionedSegments = isPrefixed
? segments
: `${API_PREFIX}/${segments}`;
const isPrefixed =
segments === API_PREFIX || segments.startsWith(`${API_PREFIX}/`);
const versionedSegments = isPrefixed ? segments : `${API_PREFIX}/${segments}`;
const search = req.nextUrl.search;
const daemonUrl = await getDaemonUrl();
const target = `${daemonUrl}/${versionedSegments}${search}`;
Expand All @@ -87,8 +86,7 @@ async function proxy(req: NextRequest, ctx: ProxyContext): Promise<Response> {
// see DELETE through fetchFromDaemon. We also drop the upstream
// Content-Type for empty requests because Fastify rejects
// application/json + empty body with FST_ERR_CTP_EMPTY_JSON_BODY.
const hasContentLength =
Number(req.headers.get("content-length") ?? "0") > 0;
const hasContentLength = Number(req.headers.get("content-length") ?? "0") > 0;
const isChunked = (req.headers.get("transfer-encoding") ?? "")
.toLowerCase()
.includes("chunked");
Expand Down Expand Up @@ -142,7 +140,7 @@ async function proxy(req: NextRequest, ctx: ProxyContext): Promise<Response> {
error: {
code: "daemon_unreachable",
message:
"Chorus daemon is not running on this host. Start it with `chorus start`.",
"Polyphony daemon is not running on this host. Start it with `chorus start`.",
},
},
{ status: 502 },
Expand Down
Loading