8000 feat(evidence): every public number is a ledger row with its reproduce command; run-summary by adeelahmad · Pull Request #35 · adeelahmad/aloop · GitHub
[go: up one dir, main page]

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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ tag in lockstep. Tag releases as `vMAJOR.MINOR.PATCH`.
## [Unreleased]

### Added
- Evidence ledger (ROADMAP §4.11): `docs/evidence/README.md` lists every live run behind a public
number (intent, plugin version, checks, wall, cost, wakes, plugin reads, gate blocks, reproduce
command); `bin/run-summary <run-dir>` writes a run's `summary.json`, `summary.md` and ledger row from
the session's `result` lines and the gate log, and `e2e-live.sh` calls it. README and the site link
every figure to the ledger; `scripts/test/evidence.test.py` fails on an unsourced figure.
- Natural-language auto-trigger (ROADMAP §4.11): `bin/intent-trigger` on UserPromptSubmit adds context
to run a plain build/fix request as `/aladdin "<intent>"` when it lands in an aloop project with no
sprint running. Quiet for questions, slash commands, short replies, a live sprint lock and non-aloop
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ test-hosts: ## Budget/worktree regression + Codex hooks emulation + OpenCode plu
python3 scripts/test/ensure-tools.test.py
python3 scripts/test/fail-closed.test.py
python3 scripts/test/intent-trigger.test.py
python3 scripts/test/evidence.test.py
@if command -v node >/dev/null; then node scripts/test/opencode-plugin.test.mjs; else echo "node not found — skipped OpenCode test (CI runs it)"; fi

.PHONY: conformance
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ given up front — reproducible with `make e2e-live YES=1` (spends about a dolla

| Intent | Result | Time | Cost |
|---|---|---|---|
| a semver parser (run 6) | 11/11 checks, tests on main, worktree branches merged `--no-ff` | 11m10s | $0.65 |
| a word-frequency tool, two dependent stories (run 9) | 12/12 checks | 19m42s | $1.56 |
| a semver parser ([run 6](docs/evidence/README.md)) | 11/11 checks, tests on main, worktree branches merged `--no-ff` | 11m10s | $0.65 |
| a word-frequency tool, two dependent stories ([run 9](docs/evidence/README.md)) | 12/12 checks | 19m42s | $1.56 |
| the same tool on v0.12.0 ([run 19](docs/evidence/README.md)) | 13/13 checks, 1 supervisor wake, every merged commit traces to its task | 15m24s | $1.01 |

Every run and what it found is logged in [ROADMAP §0](docs/ROADMAP.md#0-at-a-glance) (M0.8). The
numbers come from the harness's own `stats.md`, not from the model.
Every public number is a row in [docs/evidence/README.md](docs/evidence/README.md), with the command
that reproduces it; what each run found and fixed is logged in [ROADMAP §0](docs/ROADMAP.md#0-at-a-glance)
(M0.8). The numbers come from the harness (`run-summary` reads the session's own `result` lines), not from the model.

## Test-driven agents

Expand Down Expand Up @@ -192,7 +194,7 @@ structural, not luck:
These mechanisms are codebase-agnostic — greenfield is simply what's measured here.

Live runs: `make smoke-live YES=1` (hook wiring, ~$0.004) and `make e2e-live YES=1` (the whole
loop, ~$1). Offline: `make ci` — Rust fmt/clippy/tests, shellcheck, 30 Python/Node suites,
loop, ~$1; each run writes `summary.json` and a row for [docs/evidence/](docs/evidence/README.md)). Offline: `make ci` — Rust fmt/clippy/tests, shellcheck, 30 Python/Node suites,
the gate conformance fixtures (`make conformance`), eval-suite validation. Every ROADMAP
requirement is tracked with its test in [docs/TRACKING.md](docs/TRACKING.md).

Expand Down
4 changes: 2 additions & 2 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ original plan.
| **M5 — Narrow context** | v0.16 | Session 6: semantic execution map, shadow workspace, `target` / `context` CLI (§4.6) | done: targets, shadow workspace, `target`/`context`, LSP seam, GREEN scope from targets, and tasks.md/validate.md/plan.md rendered from the semantic plan (`targets render`; tests: `semantic-targets`, `lsp-adapter`, `targets-scope`, `plan-render`) |
| **M6 — Audit** | v0.17 | Sessions 7–8: event stream, rationale, authority, lineage query (§4.7); provider seam (§4.8) | done: `aladdin.event/1` stream, decisions, authority, lineage, `events trace SHA`, commit messages and graph export (§4.7); impact provider seam (§4.8) |
| **M7 — Any host** | v0.18 | Session 9: `HostAdapter`, capability matrix, `.aladdin/config.yaml`, host swap, metrics (§4.9–4.10) | done: `HostAdapter` + `host check` in CI, capability matrix, usage accuracy, `.aladdin/config.yaml` with migration, `host swap`, the full metrics table incl. context-window share (tests: `host-adapter`, `portable-config`, `host-swap`, `metrics`) |
| **M8 — Aladdin 1.0** | v1.0 | Session 10: `/aladdin` + `aladdin` CLI with aliases, site, measured public runs (§4.11). The rename to aloop shipped early in v0.11.0 | in progress: `bin/aladdin` + `/aladdin` with `aloop`/`agentic` aliases (test: `aladdin-cli.test.py`); natural-language auto-trigger on every host (`intent-trigger`, test: `intent-trigger.test.py`); README reordered product-first; site at `docs/index.html` (Pages switch is the owner's). Open: evidence links from every public claim to a measured run |
| **M8 — Aladdin 1.0** | v1.0 | Session 10: `/aladdin` + `aladdin` CLI with aliases, site, measured public runs (§4.11). The rename to aloop shipped early in v0.11.0 | in progress: `bin/aladdin` + `/aladdin` with `aloop`/`agentic` aliases (test: `aladdin-cli.test.py`); natural-language auto-trigger on every host (`intent-trigger`, test: `intent-trigger.test.py`); README reordered pro 8000 duct-first; site at `docs/index.html` (Pages switch is the owner's). evidence ledger `docs/evidence/README.md` + `run-summary` (test: `evidence.test.py`). Open: measured public runs on Codex and OpenCode |

**Order rationale:** identity and conformance fixtures first, so every later change is
measured against today's behaviour; budgets and handoff next, because they shape every run;
Expand Down Expand Up @@ -292,7 +292,7 @@ No extra model calls are made just for telemetry.
11. Architecture
12. Limitations
- **A site / GitHub Pages** with an original lamp motif (no Disney visual language), the RED explainer ("Give the agent something to make false.") and the audit tree.
- **Evidence:** every public claim links to a reproducible measured run.
- **Evidence:** every public claim links to a reproducible measured run. ✅ *(Unreleased) `docs/evidence/README.md` is the ledger: one row per live run with intent, plugin version, checks, wall, cost, wakes, plugin reads, gate blocks and the `make e2e-live` command; `bin/run-summary` writes each run's `summary.json` and its row from the session's `result` lines and the gate log (called by `e2e-live.sh`). The README demo table and the site link every figure to it. Test: `scripts/test/evidence.test.py` (a public figure without a row fails).*

## 5. Delivery sessions

Expand Down
2 changes: 2 additions & 0 deletions docs/TRACKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ Status of every requirement in [ROADMAP.md](ROADMAP.md), in the four states §8
| M1 | Run data in `. 8000 aladdin/` (guarded by its `.gitignore`), `.agentic/` read as legacy, `aloop layout show\|check\|migrate`, `ALADDIN_*` aliases, contract and worktree markers found in either dir | IMPLEMENTED_TESTED | `scripts/test/layout.test.py` |
| 4.11 | README reordered product-first (intent → demo → TDD agents → loop → gates → install → hosts → evidence → audit → architecture → limitations) | IMPLEMENTED_TESTED | `scripts/test/readme.test.py` (section order, named tools exist, install names, links, demo figures sourced from §0) |
| 4.11 | Site (`docs/index.html`, GitHub Pages from `/docs`) with the lamp motif, RED explainer and audit tree; numbers sourced from §0 | IMPLEMENTED_TESTED | `scripts/test/site.test.py` |
| 4.11 | Natural-language auto-trigger inside hosts (`intent-trigger` on UserPromptSubmit; Claude Code, Codex, OpenCode) | IMPLEMENTED_TESTED | `scripts/test/intent-trigger.test.py` |
| 4.11 | Evidence: every public claim links to a reproducible measured run (`docs/evidence/README.md`, `run-summary`) | IMPLEMENTED_TESTED | `scripts/test/evidence.test.py` |
32 changes: 32 additions & 0 deletions docs/evidence/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Evidence: the measured runs behind every public number

Every time or cost figure in the [README](../../README.md), on the [site](../index.html) and in
[ROADMAP §0](../ROADMAP.md#0-at-a-glance) is a row here. Each row is one live run of the whole
loop (`scripts/smoke/e2e-live.sh`): a one-line intent through intake, standards, planning,
RED/SCAFFOLD/GREEN per task, structural review and the final gate, to merged, tested code in a
real Claude Code session with the approval given up front. The numbers come from the harness
(`run-summary` reads the session's `result` lines and the gate log), never from the model.
`scripts/test/evidence.test.py` fails if a public figure has no row, or a row has no reproduce
command.

**Reproduce:** install the plugin, then run the command in the last column (spends about a
dollar, 11–22 minutes). The run leaves `summary.json`, `summary.md` and a ready-to-paste row in
its run dir; add the row here. Intents: `semver` (one story) and `wordfreq` (two dependent
stories). What each run found and fixed is in ROADMAP M0.8.

| run | intent | plugin | checks | wall | cost | wakes | plugin reads | gate blocks | reproduce |
|---|---|---|---|---|---|---|---|---|---|
| run 6 | semver | v0.10.x (master, 2026-10-17) | 11/11 | 11m10s | $0.65 | — | 2 | — | `make e2e-live YES=1 INTENT=semver` |
| run 9 | wordfreq | v0.10.x (master, 2026-10-17) | 12/12 | 19m42s | $1.56 | — | — | 29 | `make e2e-live YES=1 INTENT=wordfreq` |
| run 14 | wordfreq | v0.11.x (master, 2026-10-18) | 12/12 | 13m47s | $0.88 | 2 | — | 11 | `make e2e-live YES=1 INTENT=wordfreq` |
| run 15 | wordfreq | v0.11.x (master, 2026-10-18) | 13/13 | 13m13s | $1.42 | 16 | 6 | 6 | `make e2e-live YES=1 INTENT=wordfreq` |
| run 16 | wordfreq | v0.11.x (master, 2026-10-18) | 12/13 | 13m04s | $0.79 | 4 | 4 | 9 | `make e2e-live YES=1 INTENT=wordfreq` |
| run 17 | wordfreq | v0.11.x (master, 2026-10-18) | 13/13 | 12m58s | $0.95 | 2 | 0 | 6 | `make e2e-live YES=1 INTENT=wordfreq` |
| run 18 | wordfreq | v0.12.0 (fd1fd31) | 13/13 | 21m21s | $1.03 | 6 | 2 | 6 | `make e2e-li 8BD0 ve YES=1 INTENT=wordfreq` |
| run 19 | wordfreq | v0.12.0 + #29 (b93e6fd) | 13/13 | 15m24s | $1.01 | 1 | 5 | 3 | `make e2e-live YES=1 INTENT=wordfreq` |

Columns: *checks* are the e2e script's own assertions (12 for `semver`, 13 for `wordfreq` since
the commit-trace check); *wakes* are supervisor turns that ended and resumed (each re-reads
context; the main cost driver); *plugin reads* are sub-agent tool calls into the plugin's own
source; *gate blocks* are `exit 2` verdicts in the run's `gates.jsonl` (host hooks and
self-checks). A dash means the run predates that counter.
2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ <h2 id="audit">The audit tree</h2>
<p>Decisions carry a one-line reason, evidence refs and an authority ref. Usage is reported by class (fresh input, cache read, cache write, output, context) and marked <em>estimated</em> where a host does not report it.</p>

<h2 id="evidence">Evidence, not claims</h2>
<p>Every public number comes from the harness's own <code>stats.md</code> and is reproducible with <code>make e2e-live YES=1</code>: a one-line intent through intake, standards, planning, RED/SCAFFOLD/GREEN per task, structural review and the final gate, to merged and tested code in a real session. The semver intent ran end to end in 11m10s for $0.65 (run 6); a two-story word-frequency tool in 19m42s for $1.56 (run 9). Every requirement on the roadmap is tracked with the test that proves it in <a href="https://github.com/adeelahmad/agentic-agile/blob/master/docs/TRACKING.md">docs/TRACKING.md</a>.</p>
<p>Every public number comes from the harness's own <code>stats.md</code> and is reproducible with <code>make e2e-live YES=1</code>: a one-line intent through intake, standards, planning, RED/SCAFFOLD/GREEN per task, structural review and the final gate, to merged and tested code in a real session. The semver intent ran end to end in 11m10s for $0.65 (run 6); a two-story word-frequency tool in 19m42s for $1.56 (run 9). Every number here is a row in <a href="https://github.com/adeelahmad/agentic-agile/blob/master/docs/evidence/README.md">docs/evidence/README.md</a> with the command that reproduces it, and every requirement on the roadmap is tracked with the test that proves it in <a href="https://github.com/adeelahmad/agentic-agile/blob/master/docs/TRACKING.md">docs/TRACKING.md</a>.</p>

<footer>MIT. <code>md-db</code> is vendored under AGPL-3.0. Formerly <em>agentic-agile</em>; the old names keep working. The lamp is an original drawing: a lantern whose flame is a red-to-green loop.</footer>
</main>
Expand Down
1 change: 1 addition & 0 deletions plugin/bin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ ctx-symbols) → WARN + grep fallback; never a silent pass.
| `worktree-hygiene` | SubagentStart · exec roles — sparse-checks `docs/agents/` out of the new worktree (workers reach it via the absolute `STORY_DIR` back into the main tree, never their own copy) + bootstraps the main tree's `.gitignore` with `.agentic/`/`.transcripts/` if missing. Runs regardless of whether the harness's built-in isolation or `worktree-create` made the worktree. |
| `worktree-create` | OPTIONAL `git worktree add` helper — wire into **user `settings.json`** as a `WorktreeCreate` hook for a non-git VCS (NOT a valid plugin-manifest event). Built-in `isolation: "worktree"` covers plain git. Also applies the same sparse-checkout + `.gitignore` bootstrap as `worktree-hygiene`. |
| `worktree-remove` | OPTIONAL `git worktree remove` cleanup companion for the above (user `settings.json` `WorktreeRemove`) |
| `run-summary` | `<run-dir> [--intent] [--checks N/M] [--secs] [--plugin]` — a live run's measured record: `summary.json`, `summary.md` and a `docs/evidence/README.md` row from the session's `result` lines and `gates.jsonl` (cost, wakes, plugin reads, blocks); called by `e2e-live.sh` |
| `log-execution` | `wave-K activity SN-NN transition [detail…] [execution.log]` — append one transition line (default ledger: the latest sprint's); `--help` |
| `ensure-tools` | SessionStart — builds md-db + ctx-symbols into `$CLAUDE_PLUGIN_DATA/bin` on first run / version change (background), tells the model where the tools are. `--sync` build now · `--wait` · `--resolve T` real binary path |
| `md-db`, `ctx-symbols` | PATH shims (this `bin/` is on PATH in every session): exec the real binary, building it once if needed. Gates test presence with `ensure-tools --resolve`, never `command -v` |
Expand Down
107 changes: 107 additions & 0 deletions plugin/bin/run-summary
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""run-summary — the measured record of one live run (ROADMAP §4.11: evidence, not claims).

run-summary <run-dir> [--intent NAME] [--checks N/M] [--secs WALL] [--plugin DIR]

Reads <run-dir>/out.jsonl (the supervisor's stream-json transcript) and the run repo's
gate log (<run-dir>/repo/.aladdin/logs/gates.jsonl, or .agentic/), writes
<run-dir>/summary.json and <run-dir>/summary.md, and prints the summary with a ready-to-paste
row for docs/evidence/README.md. Numbers come from the harness, never from the model:
cost_usd / supervisor_turns the LAST `result` line (a resumed session reports again)
wakes how many `result` lines: one per supervisor wake
plugin_reads sub-agent tool calls naming a plugin path, except
`--schema <plugin>/schemas/…` (a legitimate md-db use)
gate_verdicts / gate_blocks lines in gates.jsonl / those with "exit":2
Exit 0; a missing out.jsonl is a usage error (64)."""
import json
import os
import re
import subprocess
import sys
import time


def arg(flag, default=None):
return sys.argv[sys.argv.index(flag) + 1] if flag in sys.argv[:-1] else default


def wall(secs):
return "%dm%02ds" % (int(secs) // 60, int(secs) % 60)


def main():
if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help"):
print(__doc__.strip()); return 0 if len(sys.argv) > 1 else 64
run = os.path.abspath(sys.argv[1])
out = os.path.join(run, "out.jsonl")
if not os.path.isfile(out):
print("run-summary: no out.jsonl in %s" % run, file=sys.stderr); return 64
plugin = os.path.abspath(arg("--plugin") or os.path.join(os.path.dirname(os.path.realpath(__file__)), ".."))
pat = re.compile(r"--schema[= ]+" + re.escape(plugin) + r"/schemas/\S*")
cost = turns = wakes = reads = 0
secs = arg("--secs")
for l in open(out):
try:
e = json.loads(l)
except ValueError:
continue
if not isinstance(e, dict):
continue
if e.get("type") == "result":
wakes += 1
cost, turns = e.get("total_cost_usd") or 0, e.get("num_turns") or 0
if secs is None and e.get("duration_ms"):
secs = int(e["duration_ms"]) // 1000
m = e.get("message")
content = m.get("content") if isinstance(m, dict) else None
for c in content if isinstance(content, list) else []:
if not isinstance(c, dict) or c.get("type") != "tool_use":
continue
i = c.get("input") or {}
text = i.get("command") or i.get("file_path") or i.get("path") or ""
if plugin + "/" in pat.sub("", text):
reads += 1
repo = os.path.join(run, "repo")
verdicts = blocks = 0
for h in (".aladdin", ".agentic"):
g = os.path.join(repo, h, "logs", "gates.jsonl")
if os.path.isfile(g):
for l in open(g):
if l.strip():
verdicts += 1
blocks += '"exit":2' in l or '"exit": 2' in l
break
try:
head = subprocess.run(["git", "-C", repo, "rev-parse", "HEAD"], capture_output=True, text=True).stdout.strip()
except OSError:
head = ""
try:
version = json.load(open(os.path.join(plugin, ".claude-plugin", "plugin.json"))).get("version", "")
except (OSError, ValueError):
version = ""
intent = arg("--intent") or ""
s = {"run_dir": run, "intent": intent, "plugin_version": version, "checks": arg("--checks") or "",
"wall": wall(secs or 0), "cost_usd": round(float(cost), 2), "supervisor_turns": turns, "wakes": wakes,
"plugin_reads": reads, "gate_verdicts": verdicts, "gate_blocks": blocks, "run_repo_head": head,
"reproduce": "make e2e-live YES=1" + (" INTENT=%s" % intent if intent else ""),
"recorded_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())}
row = "| %s | %s | v%s | %s | %s | $%.2f | %s | %s | %s | `%s` |" % (
os.path.basename(run), intent or "?", version, s["checks"] or "?", s["wall"], s["cost_usd"], wakes, reads, blocks, s["reproduce"])
md = ("# Live run %s\n\n| run | intent | plugin | checks | wall | cost | wakes | plugin reads | gate blocks | reproduce |\n"
"|---|---|---|---|---|---|---|---|---|---|\n%s\n\nrun repo head: `%s` · gate verdicts: %d · supervisor turns: %d\n"
% (os.path.basename(run), row, head, verdicts, turns))
with open(os.path.join(run, "summary.json"), "w") as f:
json.dump(s, f, indent=1)
with open(os.path.join(run, "summary.md"), "w") as f:
f.write(md)
print("cost: $%.2f · supervisor turns: %s · wall: %s" % (s["cost_usd"], turns, s["wall"]))
print("supervisor wakes: %d · sub-agent reads of the plugin's own source: %d" % (wakes, reads))
if verdicts:
print("gate verdicts: %d (%d blocks)" % (verdicts, blocks))
print("evidence row (docs/evidence/README.md):")
print(row)
return 0


if __name__ == "__main__":
sys.exit(main())
Loading
Loading
0