Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# Windows, WSL, macOS, and Linux. CRLF endings break the shebang line and
# heredoc parsing, so .sh files are always stored and checked out with LF.
*.sh text eol=lf
co-evolve text eol=lf

# JSON/YAML fixtures are hashed by the eval cache and byte-compared by
# determinism tests — keep them LF on disk everywhere.
Expand Down
18 changes: 18 additions & 0 deletions PLAN-NEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Plan: Next

Product features only, priority order, from the last committed ROADMAP.md
(`.planning/` is owned by another writer and not touched here). Audit,
calibration, and dogfood-evidence phases (v1.2 SC-4, v1.3 bounce
calibration, v1.5 Phase 6 evidence-gathering) are excluded — done, or not
a feature.

1. Publish `@alanshurafa/co-evolution-mcp` to npm (v1.4 Phase 5) — done when `npm install -g @alanshurafa/co-evolution-mcp` works and `co_evolve` responds from an external MCP client (Claude Desktop/Cursor/Continue).
- 2026-09-12 OPERATOR: npm package lookup returned E404, npm identity returned E401, and no GitHub publishing secret exists; Alan must authenticate and publish using mcp/PUBLISH.md, then exercise the registry install and `co-evolve --help` (the current goal's acceptance condition).
2. Submit the MCP server to the MCP registry and open the awesome-mcp-list PR (v1.4 Phase 6) — done when the registry listing is live or the PR is merged.
- 2026-09-12 MERGED: https://github.com/alanshurafa/co-evolution/pull/69 records https://github.com/punkpeye/awesome-mcp-servers/pull/14263; observed the public submission in OPEN state with the source-linked server entry (the current goal requires an open submission, not upstream merge).
3. Give `co-evolve` a real subcommand CLI (`co-evolve bounce <file>`, `co-evolve init`) instead of positional-only flags — done when both subcommands run against a fresh checkout without reading the script source.
- 2026-09-12: Added source and npm `init`/`bounce` commands; installed the local tarball, observed help and sample creation, then two real Codex passes exited 0 with a critique of same-device backups and a revised plan with separate storage and restore tests; raw critiques, markers, state, and reports remain in the run artifacts. Registry installation remains item 1's operator step.
4. Add one more agent adapter beyond Claude/Codex (Gemini CLI, Ollama, or a direct API call) — done when a bounce completes end-to-end using the new adapter for at least one side.
5. Write the standalone Bounce Protocol spec (markers, convergence rules, role lenses) independent of any single runner's code — done when a new agent adapter can be built from the spec alone, without reading `co-evolve-bouncer.sh`.
6. Ship the automated branch/worktree cleanup utility carried forward from v1.1 — done when a single command removes worktrees/branches left by completed `dev-review` runs.
7. PEL Option 2 (Auto-Promote, `lab/pel-auto/`) — done when a mutation that passes canary and beats the champion on eval auto-merges under an explicit opt-in flag, no PR review step.
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ alone.

## Default Usage

From a fresh checkout, create and refine a sample without reading the scripts:

```bash
bash ./co-evolve --help
bash ./co-evolve init
bash ./co-evolve bounce sample.md --agents codex,codex --output sample.bounced.md
```

`init` refuses to overwrite an existing file. `bounce` leaves the input intact,
prints the result unless `--output` is supplied, and preserves per-pass critiques
and marker decisions under `.co-evolve/runs/` in your current directory.
The example requires a logged-in Codex CLI; omit `--agents` for a Claude reviewer
and Codex composer. Behavior scores are informational; the judge is opt-in.

The npm package also provides `co-evolve` and `co-evolution-mcp`. Its first
publication is pending; source installation works now. See [publication steps](mcp/PUBLISH.md).

Start with the general Co-Evolution runner unless the task specifically needs
code execution:

Expand Down Expand Up @@ -61,8 +78,7 @@ cp -R skills/co-evolution/* ~/.claude/skills/co-evolution/

cat > ~/.local/bin/co-evolve <<EOF
#!/usr/bin/env bash
cd "$PWD" || exit 1
exec bash ./co-evolve-bouncer.sh "\$@"
exec bash "$PWD/co-evolve" "\$@"
EOF
chmod +x ~/.local/bin/co-evolve
```
Expand Down
64 changes: 64 additions & 0 deletions co-evolve
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

usage() {
cat <<'USAGE'
Usage: co-evolve <command> [options]

Commands:
init [file] Create a sample Markdown document (default: sample.md).
bounce <file> [opts] Refine a document; preserve input and run artifacts.
help Show this usage.

Quick start:
co-evolve init
co-evolve bounce sample.md --agents codex,codex --output sample.bounced.md

Bounce defaults: two passes, Claude reviewer and Codex composer, no interview.
Both selected agent CLIs must be installed and logged in. To use only Codex,
pass --agents codex,codex. Run artifacts go to .co-evolve/runs in the current
directory; set CO_EVOLVE_RUNS_DIR to choose another directory.

Use co-evolve bounce --help for all runner options. Legacy positional input
and flags still work. The judge is opt-in; behavior scores are informational.
USAGE
}

case "${1:-help}" in
help|--help|-h) usage ;;
init)
shift
if [[ "${1:-}" == --help || "${1:-}" == -h ]]; then usage; exit 0; fi
[[ $# -le 1 ]] || { echo 'Usage: co-evolve init [file]' >&2; exit 2; }
target="${1:-sample.md}"
# noclobber also protects an existing file if another init races this one.
if ! (set -o noclobber; cat > "$target" <<'SAMPLE'
# A small team's backup plan

We keep our only backup on the same laptop as the original files. A weekly
copy is enough because the laptop has never failed. Everyone can overwrite
the backup to save time. We will know the backups work when no one complains.

Improve this plan for a three-person team. Keep it under 250 words. Address
recovery, access, and a concrete way to prove that a backup can be restored.
SAMPLE
); then
echo "Could not create $target; choose a new file in an existing directory." >&2
exit 1
fi
printf 'Created %s\nNext: co-evolve bounce "%s" --agents codex,codex\n' "$target" "$target"
;;
bounce)
shift
if [[ "${1:-}" == --help || "${1:-}" == -h ]]; then
exec bash "$ROOT/co-evolve-bouncer.sh" --help
fi
[[ $# -gt 0 && -f "$1" ]] || { echo 'Usage: co-evolve bounce <existing-file> [options]' >&2; exit 2; }
input="$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
shift
export CO_EVOLVE_RUNS_DIR="${CO_EVOLVE_RUNS_DIR:-$PWD/.co-evolve/runs}"
exec bash "$ROOT/co-evolve-bouncer.sh" --vanilla --bounce-only "$@" -- "$input"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject output paths that alias the input

When a user runs co-evolve bounce doc.md --output doc.md, the wrapper forwards the path without checking it against the normalized input, and the underlying bouncer copies its final artifact over doc.md. This violates the new command's stated input-preservation contract and can destroy the only original document; reject output paths that resolve to the input, including symlink aliases.

Useful? React with 👍 / 👎.

;;
*) exec bash "$ROOT/co-evolve-bouncer.sh" "$@" ;;
esac
6 changes: 3 additions & 3 deletions evals/report-bounce.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ yn() { [[ "$1" == "true" ]] && printf 'PASS' || printf 'FAIL'; }
esac
printf 'The run completed %s pass(es)' "$PASS_COUNT"
[[ "$RUN_STATUS" == "aborted" ]] && printf ' and was ABORTED before finishing'
printf '. Behavior gate: **%s**.\n\n' "$(yn "$OVERALL")"
printf '. Informational behavior score: **%s** (does not fail the run).\n\n' "$(yn "$OVERALL")"
case "$CONVERGENCE_STATUS" in
converged)
printf 'Convergence: **converged** — the agents resolved every disagreement on their own within the configured passes.\n\n' ;;
Expand All @@ -82,7 +82,7 @@ yn() { [[ "$1" == "true" ]] && printf 'PASS' || printf 'FAIL'; }
: ;; # unknown: legacy/agent-bouncer run, say nothing
esac
if [[ "$OVERALL" != "true" ]]; then
printf 'Because the behavior gate failed, no claim is made about whether the document got better. The failed checks below say what went wrong mechanically.\n\n'
printf 'Some behavior checks did not pass. These diagnostics do not determine run success or whether the document improved; inspect the critique and revised document.\n\n'
fi

# --- Pass-by-pass table --------------------------------------------------
Expand All @@ -108,7 +108,7 @@ yn() { [[ "$1" == "true" ]] && printf 'PASS' || printf 'FAIL'; }
printf '|------|---------|--------------|------|\n'
jq -r '.marker_ledger[] | "| \(.type) | \(.heading) | \(.text | .[0:60]) | **\(.fate)** |"' "$SCORES"
printf '\n'
printf 'Fates: **resolved** = addressed with the section intact; **deleted-with-section** = the disagreement vanished because its whole section was deleted (this fails the run); **expired** = carried unresolved until the pass limit forced it out; **unresolved** = still live in the final document.\n'
printf 'Fates: **resolved** = addressed with the section intact; **deleted-with-section** = the disagreement vanished because its whole section was deleted (informational); **expired** = carried unresolved until the pass limit forced it out; **unresolved** = still live in the final document.\n'
fi
printf '\n'

Expand Down
8 changes: 4 additions & 4 deletions lib/co-evolution.sh
Original file line number Diff line number Diff line change
Expand Up @@ -761,9 +761,9 @@ invoke_codex() {
if [[ -n "${WSL_DISTRO_NAME:-}" ]] && command -v cmd.exe >/dev/null 2>&1 && command -v wslpath >/dev/null 2>&1; then
windows_workdir=$(wslpath -w "$workdir")
windows_output=$(wslpath -w "$output_file")
cmd=(cmd.exe /c codex exec --full-auto --skip-git-repo-check -C "$windows_workdir")
cmd=(cmd.exe /c codex exec --approve-for-me --skip-git-repo-check -C "$windows_workdir")
else
cmd=(codex exec --full-auto --skip-git-repo-check -C "$workdir")
cmd=(codex exec --approve-for-me --skip-git-repo-check -C "$workdir")

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 Restore a supported Codex approval option

With Codex CLI 0.144.0-alpha.4, codex exec --approve-for-me exits 2 with unexpected argument, while the previous --full-auto invocation is accepted. Because Codex is the default composer, every default bounce reaches this path, produces no agent output, and ultimately fails after retries; schema-backed verification is affected identically. Retain --full-auto or implement version-aware selection of a documented option.

AGENTS.md reference: AGENTS.md:L72-L72

Useful? React with 👍 / 👎.

fi

if [[ -n "${CODEX_MODEL:-}" ]]; then
Expand Down Expand Up @@ -807,9 +807,9 @@ invoke_codex_schema() {
windows_workdir=$(wslpath -w "$workdir")
windows_output=$(wslpath -w "$output_file")
windows_schema=$(wslpath -w "$schema_file")
cmd=(cmd.exe /c codex exec --full-auto --skip-git-repo-check -C "$windows_workdir")
cmd=(cmd.exe /c codex exec --approve-for-me --skip-git-repo-check -C "$windows_workdir")
else
cmd=(codex exec --full-auto --skip-git-repo-check -C "$workdir")
cmd=(codex exec --approve-for-me --skip-git-repo-check -C "$workdir")
fi

if [[ -n "${CODEX_MODEL:-}" ]]; then
Expand Down
41 changes: 41 additions & 0 deletions mcp/PUBLISH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Publish the MCP package

PLAN-NEXT.md item 1 is still pending. On 2026-09-12 the public npm registry
returned 404 for `@alanshurafa/co-evolution-mcp`, `npm whoami` returned 401,
and the repository had no Actions secrets configured. Authenticate npm with
an account that can publish to the `@alanshurafa` scope before continuing.
Do not put credentials in this repository.

The existing tag workflow uses the repository's `NPM_TOKEN` secret. For a
manual release, run the following from `mcp/` after choosing the release
version corresponding to the source tag:

```bash
npm version --no-git-tag-version <release-version>
npm pack --pack-destination ../runs/
npm publish ../runs/alanshurafa-co-evolution-mcp-<release-version>.tgz --access public
```

`npm pack` vendors the source and compiles TypeScript through `prepack`.
The tarball is the release artifact; publishing it does not invoke the build
again. Commit the version and lockfile with a TypeScript check before tagging
the source. Choose either manual publication or the tag workflow for a release.

After publication, install the registry package:

```bash
npm install -g @alanshurafa/co-evolution-mcp
```

Configure Claude Desktop, Cursor, or Continue using the examples in README.md,
then call `co_evolve` once on a disposable markdown document with an absolute
path and `runs_dir` pointing into this checkout's `runs/` directory. On Windows,
ensure Git for Windows' `bin` directory precedes the Windows WSL bash launcher
on the client's PATH. Record the client response and artifact directory.

For the current usability goal, item 1 is done when the registry install works
and `co-evolve --help` prints usage. The external MCP call above is an additional
integration exercise. A local tarball does not establish registry availability.
Run the test suite once when the item is complete;
judge execution, behavior-score gates, and extra verification loops are not
part of this release procedure.
24 changes: 24 additions & 0 deletions mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,34 @@ were rated better than their inputs in 7 of 7 historical cases (0 regressed).

## Install

First npm publication is pending. Until then, build from the public source:

```bash
git clone https://github.com/alanshurafa/co-evolution.git
cd co-evolution/mcp
npm ci
npm run build:vendor
npm run build
node dist/src/cli.js --help
```

After publication:

```bash
npm i -g @alanshurafa/co-evolution-mcp
```

The package provides both the MCP server and a document CLI:

```bash
co-evolve init
co-evolve bounce sample.md --agents codex,codex --output sample.bounced.md
```

Run `co-evolve --help` for usage. The sample requires a logged-in Codex CLI.
`init` preserves existing files; `bounce` preserves the input and writes its
per-pass artifacts under `.co-evolve/runs/` in the current directory.

### Prerequisites

| Tool | Needed for | Install |
Expand Down
3 changes: 2 additions & 1 deletion mcp/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions mcp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"license": "MIT",
"type": "module",
"bin": {
"co-evolve": "dist/src/cli.js",
"co-evolution-mcp": "dist/src/server.js"
},
"files": [
Expand Down
2 changes: 2 additions & 0 deletions mcp/scripts/vendor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ rm -rf "$VENDOR"
mkdir -p "$VENDOR/lib" "$VENDOR/templates" "$VENDOR/agent-bouncer/templates" "$VENDOR/evals"

cp "$REPO_ROOT/co-evolve-bouncer.sh" "$VENDOR/"
cp "$REPO_ROOT/co-evolve" "$VENDOR/"
cp "$REPO_ROOT/lib/co-evolution.sh" "$VENDOR/lib/"
cp -R "$REPO_ROOT/templates/co-evolve" "$VENDOR/templates/"
cp "$REPO_ROOT/agent-bouncer/templates/bounce-protocol.md" "$VENDOR/agent-bouncer/templates/"
Expand All @@ -29,5 +30,6 @@ cp "$REPO_ROOT/evals/bounce-thresholds.yaml" "$VENDOR/evals/"
cp "$REPO_ROOT/evals/BOUNCE-RUNNER-CONTRACT.md" "$VENDOR/evals/"

chmod +x "$VENDOR/co-evolve-bouncer.sh" "$VENDOR/evals/score-bounce.sh" "$VENDOR/evals/report-bounce.sh"
chmod +x "$VENDOR/co-evolve"

echo "vendored co-evolution toolkit -> $VENDOR"
4 changes: 2 additions & 2 deletions mcp/src/bouncer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ function runtimeError(
});
}

function findBash(): string | null {
export function findBash(): string | null {
if (process.platform === "win32") {
const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
const gitBash = join(programFiles, "Git", "bin", "bash.exe");
Expand All @@ -131,7 +131,7 @@ function isWslLauncher(bashPath: string | null): boolean {
return /[/\\]windows[/\\]system32[/\\]bash\.exe$/i.test(bashPath ?? "");
}

function pathForBash(path: string, bashPath: string | null): string {
export function pathForBash(path: string, bashPath: string | null): string {
if (process.platform !== "win32") return path;
const forward = path.replaceAll("\\", "/");
// Windows' system bash.exe is the WSL launcher, which needs /mnt/<drive>.
Expand Down
22 changes: 22 additions & 0 deletions mcp/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env node
import { spawn } from "node:child_process";
import { join } from "node:path";
import { findBash, pathForBash, VENDOR_ROOT } from "./bouncer.js";

const bash = findBash();
if (!bash) {
console.error("co-evolve requires Bash. On Windows, install Git for Windows.");
process.exit(1);
}
const child = spawn(bash, [pathForBash(join(VENDOR_ROOT, "co-evolve"), bash), ...process.argv.slice(2)], {
stdio: "inherit",
});
child.on("error", (error) => {
console.error(`Could not start co-evolve: ${error.message}`);
process.exitCode = 1;
});
child.on("exit", (code, signal) => {
process.exitCode = code ?? (signal === "SIGINT" ? 130 : 1);
});
process.on("SIGINT", () => child.kill("SIGINT"));
process.on("SIGTERM", () => child.kill("SIGTERM"));
Loading