fix: sum turns and time across result events; security-finding extraction; tokens --since

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 28a5fc7667202b6b43c0d3fbcc6e8fc0ea76fdaf parent d23b6ed
20 files changed, +588 −6
  • builders/post-build.sh +2 −1
  • builders/reviews/go-rastrillo.1.md +7 −0
  • builders/reviews/go-rastrillo.2.md +1 −0
  • builders/reviews/rust.1.md +3 −0
  • builders/reviews/rust.2.md +3 −0
  • builders/reviews/swift.1.md +3 −0
  • builders/reviews/swift.2.md +1 −0
  • builders/reviews/zig.1.md +3 −0
  • builders/reviews/zig.2.md +1 −0
  • builders/reviews/zig.3.md +1 −0
  • builders/security-findings.py +10 −0
  • harness/cmd/bakeoff/main.go +9 −2
  • harness/harness_test.go +12 −0
  • harness/testdata/claude-multi.jsonl +3 −0
  • harness/tokens.go +10 −3
  • results/go-rastrillo.json +104 −0
  • results/ocaml.json +102 −0
  • results/rust.json +105 −0
  • results/swift.json +104 −0
  • results/zig.json +104 −0
diff --git a/builders/post-build.sh b/builders/post-build.sh
index bc072dd..83b987d 100755
--- a/builders/post-build.sh
+++ b/builders/post-build.sh
@@ -28,7 +28,8 @@ fi
builders/run-review.sh "$slug" 2
last=2
if ! bakeoff findings --slug "$slug" --file "builders/reviews/$slug.2.md"; then
- grep -E '^\s*[0-9]+\.\s+\[security\]' -A20 "builders/reviews/$slug.2.md" | awk '/^\s*[0-9]+\.\s+\[/{keep=/\[security\]/} keep' > "$TMPDIR/$slug.security.md"
+ builders/security-findings.py "builders/reviews/$slug.2.md" > "$TMPDIR/$slug.security.md"
+ [ -s "$TMPDIR/$slug.security.md" ] || { echo "$slug: could not extract security findings" >&2; exit 1; }
builders/run-fix.sh "$slug" security "$TMPDIR/$slug.security.md"
rm -f "$TMPDIR/$slug.security.md"
bakeoff tokens --slug "$slug" --phase security
diff --git a/builders/reviews/go-rastrillo.1.md b/builders/reviews/go-rastrillo.1.md
new file mode 100644
index 0000000..566c37d
--- /dev/null
+++ b/builders/reviews/go-rastrillo.1.md
@@ -0,0 +1,7 @@
+1. [spec] `apps/go-rastrillo/internal/todo/origin.go:28`: Delegating to `csrf.Protect` lets `Sec-Fetch-Site: same-origin` or `none` override a foreign `Origin`. Such POSTs execute instead of returning the required 403. Compare the supplied Origin directly with the configured origin.
+
+2. [correctness] `apps/go-rastrillo/internal/todo/handlers.go:79`: Form parsing errors are ignored. An edit body exceeding the 1 MiB limit yields an empty title, so the existing todo is deleted and the response reports success. Reject parsing errors before changing data.
+
+3. [correctness] `apps/go-rastrillo/internal/todo/static/app.js:36`: An add response discards the current input unconditionally. Submit a todo, then type a replacement title while the request is pending: the response erases that new text. Clear the submitted value when submitting and preserve subsequent typing.
+
+4. [spec] `apps/go-rastrillo/cmd/todo/main.go:42`: Explicit listen addresses are never restricted to loopback. `--listen 0.0.0.0:8080` passes through to Rastrillo’s TCP listener and exposes the app on every interface, contrary to the brief. Reject addresses whose host is not `127.0.0.1`.
\ No newline at end of file
diff --git a/builders/reviews/go-rastrillo.2.md b/builders/reviews/go-rastrillo.2.md
new file mode 100644
index 0000000..c54809c
--- /dev/null
+++ b/builders/reviews/go-rastrillo.2.md
@@ -0,0 +1 @@
+1. [correctness] `apps/go-rastrillo/internal/todo/static/app.js:42` — Every action response replaces the entire app, preserving only the new-todo input. With a request pending, double-click another todo and start editing: when the earlier response arrives, the editor disappears and its unsaved text is lost. Preserve the active editor’s value and focus across replacement, or defer replacement until editing finishes.
\ No newline at end of file
diff --git a/builders/reviews/rust.1.md b/builders/reviews/rust.1.md
new file mode 100644
index 0000000..a310c2a
--- /dev/null
+++ b/builders/reviews/rust.1.md
@@ -0,0 +1,3 @@
+1. [correctness] [apps/rust/static/app.js:32](/home/paulca/amadan.net/carlos/todomvc-bakeoff/.worktrees/rust/apps/rust/static/app.js:32): Every action response replaces the entire list, discarding edits started while the request was pending. With a delayed response, toggle one todo, then edit another: when the toggle response arrives, the edit disappears without saving. Preserve the active edit and its draft across updates.
+
+2. [spec] [apps/rust/Makefile:33](/home/paulca/amadan.net/carlos/todomvc-bakeoff/.worktrees/rust/apps/rust/Makefile:33): `clean-deps` removes Cargo’s target directory but leaves Zig’s compiled libraries in its shared cache. `build-arm64` uses `cargo zigbuild` without configuring an app-local Zig cache, so subsequent cold builds can reuse compiled dependencies. Put Zig’s compiled cache under this app’s `.cache/` and remove it in `clean-deps`.
\ No newline at end of file
diff --git a/builders/reviews/rust.2.md b/builders/reviews/rust.2.md
new file mode 100644
index 0000000..661e316
--- /dev/null
+++ b/builders/reviews/rust.2.md
@@ -0,0 +1,3 @@
+1. [correctness] [apps/rust/static/app.js:10](/home/paulca/amadan.net/carlos/todomvc-bakeoff/.worktrees/rust/apps/rust/static/app.js:10): Filter navigation can discard pending changes. Requests wait in a promise queue, but filter links navigate immediately. With a toggle request pending, edit another todo and click a filter: blur queues the save, then navigation destroys the queue before that save reaches the server. Wait for pending actions before navigating.
+
+2. [spec] [apps/rust/src/main.rs:45](/home/paulca/amadan.net/carlos/todomvc-bakeoff/.worktrees/rust/apps/rust/src/main.rs:45): `--listen` accepts every loopback address, including `::1` and `127.0.0.2`. The brief explicitly restricts binding to `127.0.0.1`. Validate that exact address.
\ No newline at end of file
diff --git a/builders/reviews/swift.1.md b/builders/reviews/swift.1.md
new file mode 100644
index 0000000..07893fb
--- /dev/null
+++ b/builders/reviews/swift.1.md
@@ -0,0 +1,3 @@
+1. [correctness] `apps/swift/static/app.js:10` and `:20` — Adding a todo can erase the next title. Submit “first”, then type “second” before the response arrives: the first response resets the input, deleting “second”. Pressing Enter meanwhile also silently discards that submission because `dataset.sending` is set.
+
+2. [correctness] `apps/swift/static/app.js:19` — Concurrent actions replace the list in response-arrival order, allowing an older snapshot to overwrite newer state. Toggle two different items quickly: if the earlier response arrives last, an item can appear unchecked and the counter and toggle-all state become stale despite both changes being saved. Serialize actions or prevent stale responses from replacing newer state.
\ No newline at end of file
diff --git a/builders/reviews/swift.2.md b/builders/reviews/swift.2.md
new file mode 100644
index 0000000..e4dc4d9
--- /dev/null
+++ b/builders/reviews/swift.2.md
@@ -0,0 +1 @@
+1. [correctness] [apps/swift/static/app.js:32](/home/paulca/amadan.net/carlos/todomvc-bakeoff/.worktrees/swift/apps/swift/static/app.js:32): Every action response removes the entire list, including any active editor. Toggle one item, then start editing another before the response arrives: the replacement discards the draft and focus without saving. Serializing requests does not protect unsent edits. Preserve active editor contents and focus across updates, or defer replacement until editing finishes.
\ No newline at end of file
diff --git a/builders/reviews/zig.1.md b/builders/reviews/zig.1.md
new file mode 100644
index 0000000..e70a7bc
--- /dev/null
+++ b/builders/reviews/zig.1.md
@@ -0,0 +1,3 @@
+1. [correctness] `apps/zig/static/app.js:24–25`: Every response replaces the entire list, including any open editor. With a request pending, double-click another todo and start editing; when the earlier response arrives, the editor disappears and its unsaved text is lost. Preserve pending edits when applying responses.
+
+2. [spec] `apps/zig/src/app.zig:172` and `:185`: Adding and editing trim only ASCII whitespace. Unicode whitespace such as U+00A0 or U+2003 remains, so whitespace-only titles create items, and editing an item to whitespace does not delete it. Both handlers need Unicode whitespace trimming.
\ No newline at end of file
diff --git a/builders/reviews/zig.2.md b/builders/reviews/zig.2.md
new file mode 100644
index 0000000..d72fc1b
--- /dev/null
+++ b/builders/reviews/zig.2.md
@@ -0,0 +1 @@
+1. [security] `apps/zig/src/app.zig:218`: A POST to `/todos` with `title=a%80%80%80%80` makes the backward trimming scan pass five bytes to `std.unicode.utf8Decode`, which reaches `unreachable` and aborts the ReleaseSafe server. One unauthenticated request can therefore terminate the shared instance. Validate UTF-8 before trimming, or check the sequence length and leading byte before decoding.
\ No newline at end of file
diff --git a/builders/reviews/zig.3.md b/builders/reviews/zig.3.md
new file mode 100644
index 0000000..ea30bf8
--- /dev/null
+++ b/builders/reviews/zig.3.md
@@ -0,0 +1 @@
+1. [security] [apps/zig/src/app.zig:218](apps/zig/src/app.zig#L218): A POST to `/todos` with body `title=a%80%80%80%80` passes a five-byte slice to `std.unicode.utf8Decode`, whose unsupported-length branch is `unreachable`. In the configured ReleaseSafe build, this aborts the entire server, denying service to every visitor. Validate UTF-8 before trimming and reject malformed titles without panicking.
\ No newline at end of file
diff --git a/builders/security-findings.py b/builders/security-findings.py
new file mode 100755
index 0000000..9312db9
--- /dev/null
+++ b/builders/security-findings.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python3
+"""Prints only the [security] items of a numbered review list."""
+import re, sys
+items, cur = [], None
+for line in open(sys.argv[1]).read().splitlines():
+ if re.match(r"\s*\d+\.\s+", line):
+ cur = [line]; items.append(cur)
+ elif cur is not None:
+ cur.append(line)
+print("\n".join("\n".join(i) for i in items if re.match(r"\s*\d+\.\s+\[security\]", i[0])))
diff --git a/harness/cmd/bakeoff/main.go b/harness/cmd/bakeoff/main.go
index 398c420..0b69614 100644
--- a/harness/cmd/bakeoff/main.go
+++ b/harness/cmd/bakeoff/main.go
@@ -35,6 +35,7 @@ func main() {
levels := fs.String("levels", "1,16,64", "load: concurrency levels")
phase := fs.String("phase", "", "tokens: build|fix|security|review")
logs := fs.String("logs", "builders/logs", "tokens: log directory")
+ since := fs.String("since", "", "tokens: log whose cumulative totals this resumed phase starts from (default: the previous phase's)")
file := fs.String("file", "", "behaviour: Playwright JSON report")
var rec b.Record
fs.StringVar(&rec.Language, "language", "", "record: display name")
@@ -70,7 +71,7 @@ func main() {
case "build":
err = build(*slug, *dir, *runs)
case "tokens":
- err = tokens(*slug, *logs, *phase)
+ err = tokens(*slug, *logs, *phase, *since)
case "behaviour":
err = behaviour(*slug, *file)
case "report":
@@ -215,7 +216,7 @@ func build(slug, dir string, runs int) error {
return b.Update(slug, func(r *b.AppResult) { r.Build = st })
}
-func tokens(slug, logs, phase string) error {
+func tokens(slug, logs, phase, since string) error {
parse := b.ParseClaudeLog
if phase == "review" {
parse = b.ParseCodexLog
@@ -246,8 +247,14 @@ func tokens(slug, logs, phase string) error {
if phase == "security" {
prev = []string{"fix", "build"}
}
+ if since != "" {
+ prev = []string{since}
+ }
for _, p := range prev {
f := filepath.Join(logs, slug+"."+p+".jsonl")
+ if since != "" {
+ f = since
+ }
fh, err := os.Open(f)
if err != nil {
continue
diff --git a/harness/harness_test.go b/harness/harness_test.go
index c5a5953..9be9ca3 100644
--- a/harness/harness_test.go
+++ b/harness/harness_test.go
@@ -262,3 +262,15 @@ func TestApplyRecordKeepsUnsetFields(t *testing.T) {
t.Fatalf("got %+v", r)
}
}
+
+func TestParseClaudeLogSumsTurnsAcrossResults(t *testing.T) {
+ f, _ := os.Open("testdata/claude-multi.jsonl")
+ defer f.Close()
+ tk, err := ParseClaudeLog(f)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if tk.Turns != 5 || tk.Seconds != 1.5 || tk.Output != 140 || tk.CacheRead != 30 || tk.CostUSD != 1.5 {
+ t.Fatalf("got %+v (usage and cost are cumulative: take the last; turns and time are per result: sum)", tk)
+ }
+}
diff --git a/harness/testdata/claude-multi.jsonl b/harness/testdata/claude-multi.jsonl
new file mode 100644
index 0000000..20ae861
--- /dev/null
+++ b/harness/testdata/claude-multi.jsonl
@@ -0,0 +1,3 @@
+{"type":"system","subtype":"init","session_id":"s"}
+{"type":"result","num_turns":3,"duration_ms":1000,"total_cost_usd":1.0,"usage":{"output_tokens":100},"modelUsage":{"claude-opus-5-5":{"inputTokens":1,"outputTokens":100,"cacheReadInputTokens":10,"cacheCreationInputTokens":5}}}
+{"type":"result","num_turns":2,"duration_ms":500,"total_cost_usd":1.5,"usage":{"output_tokens":40},"modelUsage":{"claude-opus-5-5":{"inputTokens":2,"outputTokens":140,"cacheReadInputTokens":30,"cacheCreationInputTokens":6}}}
diff --git a/harness/tokens.go b/harness/tokens.go
index ce45e5f..7d8fdea 100644
--- a/harness/tokens.go
+++ b/harness/tokens.go
@@ -30,9 +30,12 @@ func scanLines(r io.Reader, f func([]byte)) error {
return sc.Err()
}
-// ParseClaudeLog reads `claude -p --output-format stream-json` output and
-// returns the final result event's totals. Per-model usage (which includes
-// any subagents) is summed when present.
+// ParseClaudeLog reads `claude -p --output-format stream-json` output. A
+// session can emit several result events (it resumes itself when
+// background work finishes): their per-model usage and cost are
+// cumulative for the whole session, so the last one counts, while turns
+// and duration are per result, so they are summed. Per-model usage, which
+// includes any subagents, is preferred to the top-level usage.
func ParseClaudeLog(r io.Reader) (Tokens, error) {
type usage struct {
Input int64 `json:"input_tokens"`
@@ -47,6 +50,7 @@ func ParseClaudeLog(r io.Reader) (Tokens, error) {
CacheWrite int64 `json:"cacheCreationInputTokens"`
}
var last *Tokens
+ turns, seconds := 0, 0.0
err := scanLines(r, func(b []byte) {
var e struct {
Type string `json:"type"`
@@ -70,6 +74,8 @@ func ParseClaudeLog(r io.Reader) (Tokens, error) {
t.CacheWrite += m.CacheWrite
}
}
+ turns += e.Turns
+ seconds += e.DurationMs / 1000
last = &t
})
if err != nil {
@@ -78,6 +84,7 @@ func ParseClaudeLog(r io.Reader) (Tokens, error) {
if last == nil {
return Tokens{}, errors.New("no result event in claude log")
}
+ last.Turns, last.Seconds = turns, seconds
return *last, nil
}
diff --git a/results/go-rastrillo.json b/results/go-rastrillo.json
new file mode 100644
index 0000000..1b3e9b7
--- /dev/null
+++ b/results/go-rastrillo.json
@@ -0,0 +1,104 @@
+{
+ "Slug": "go-rastrillo",
+ "Language": "Go + Rastrillo",
+ "Status": "LIVE",
+ "URL": "https://todo-go-rastrillo.bfj.oncarlos.com",
+ "Version": "1364616",
+ "Stack": "",
+ "Source": "",
+ "BehaviourJSOn": {
+ "Passed": 35,
+ "Failed": 0,
+ "Skipped": 0
+ },
+ "BehaviourJSOff": {
+ "Passed": 32,
+ "Failed": 0,
+ "Skipped": 3
+ },
+ "TTFBEmpty": {
+ "N": 0,
+ "P50": 0,
+ "P95": 0,
+ "P99": 0,
+ "Min": 0,
+ "Max": 0
+ },
+ "TTFB50": {
+ "N": 0,
+ "P50": 0,
+ "P95": 0,
+ "P99": 0,
+ "Min": 0,
+ "Max": 0
+ },
+ "Wake": null,
+ "Load": null,
+ "Weight": {
+ "HTMLBytes": 0,
+ "JSBytes": 0
+ },
+ "Code": {
+ "Server": 0,
+ "Templates": 0,
+ "JS": 0,
+ "Tests": 0,
+ "Deps": 0
+ },
+ "Build": {
+ "ColdSeconds": 0,
+ "WarmSeconds": 0,
+ "EditSeconds": 0,
+ "Bytes": 0,
+ "Host": ""
+ },
+ "TokensBuild": {
+ "Input": 124,
+ "Output": 53908,
+ "CacheRead": 7871388,
+ "CacheWrite": 166924,
+ "Turns": 65,
+ "Seconds": 756.874,
+ "CostUSD": 3.9883256
+ },
+ "TokensFix": {
+ "Input": 22,
+ "Output": 8277,
+ "CacheRead": 2045481,
+ "CacheWrite": 13112,
+ "Turns": 12,
+ "Seconds": 209.902,
+ "CostUSD": 0.6796201999999991
+ },
+ "TokensSecurity": {
+ "Input": 0,
+ "Output": 0,
+ "CacheRead": 0,
+ "CacheWrite": 0,
+ "Turns": 0,
+ "Seconds": 0,
+ "CostUSD": 0
+ },
+ "TokensReview": {
+ "Input": 684516,
+ "Output": 3432,
+ "CacheRead": 582400,
+ "CacheWrite": 0,
+ "Turns": 2,
+ "Seconds": 0,
+ "CostUSD": 0
+ },
+ "Readability": {
+ "Naming": 4,
+ "Structure": 4,
+ "Traceability": 4,
+ "Errors": 4,
+ "Comments": 5,
+ "Overall": 8,
+ "Notes": "Strongest: comments consistently explain why (Resolve vs Run and DBPath reset, the 1 MiB form cap preventing an over-long edit reading as delete, originCheck vs csrf.Protect, toggleAll as one statement). The owned()/afterItemWrite/done seam makes a toggle easy to follow from route to SQL to fragment or 303. Weakest: renderStatus swallows template errors without logging while fail() logs with a ref, so one 500 path is silent; several discarded second returns (sessions.UserID, ParseInt) rely on reader trust in middleware; App/Router/Mux is a thin three-layer wrapper and ErrorPage/errorPage differ only by case; the \"make bump edits this line\" aside is noise."
+ },
+ "ReviewFindings": [
+ "[correctness] `apps/go-rastrillo/internal/todo/static/app.js:42` — Every action response replaces the entire app, preserving only the new-todo input. With a request pending, double-click another todo and start editing: when the earlier response arrives, the editor disappears and its unsaved text is lost. Preserve the active editor’s value and focus across replacement, or defer replacement until editing finishes."
+ ],
+ "Notes": null
+}
diff --git a/results/ocaml.json b/results/ocaml.json
new file mode 100644
index 0000000..67c94a8
--- /dev/null
+++ b/results/ocaml.json
@@ -0,0 +1,102 @@
+{
+ "Slug": "ocaml",
+ "Language": "",
+ "Status": "",
+ "URL": "",
+ "Version": "",
+ "Stack": "",
+ "Source": "",
+ "BehaviourJSOn": {
+ "Passed": 0,
+ "Failed": 0,
+ "Skipped": 0
+ },
+ "BehaviourJSOff": {
+ "Passed": 0,
+ "Failed": 0,
+ "Skipped": 0
+ },
+ "TTFBEmpty": {
+ "N": 0,
+ "P50": 0,
+ "P95": 0,
+ "P99": 0,
+ "Min": 0,
+ "Max": 0
+ },
+ "TTFB50": {
+ "N": 0,
+ "P50": 0,
+ "P95": 0,
+ "P99": 0,
+ "Min": 0,
+ "Max": 0
+ },
+ "Wake": null,
+ "Load": null,
+ "Weight": {
+ "HTMLBytes": 0,
+ "JSBytes": 0
+ },
+ "Code": {
+ "Server": 0,
+ "Templates": 0,
+ "JS": 0,
+ "Tests": 0,
+ "Deps": 0
+ },
+ "Build": {
+ "ColdSeconds": 0,
+ "WarmSeconds": 0,
+ "EditSeconds": 0,
+ "Bytes": 0,
+ "Host": ""
+ },
+ "TokensBuild": {
+ "Input": 202,
+ "Output": 83667,
+ "CacheRead": 12568981,
+ "CacheWrite": 175533,
+ "Turns": 103,
+ "Seconds": 1110.2859999999998,
+ "CostUSD": 5.5922082
+ },
+ "TokensFix": {
+ "Input": 0,
+ "Output": 0,
+ "CacheRead": 0,
+ "CacheWrite": 0,
+ "Turns": 0,
+ "Seconds": 0,
+ "CostUSD": 0
+ },
+ "TokensSecurity": {
+ "Input": 0,
+ "Output": 0,
+ "CacheRead": 0,
+ "CacheWrite": 0,
+ "Turns": 0,
+ "Seconds": 0,
+ "CostUSD": 0
+ },
+ "TokensReview": {
+ "Input": 0,
+ "Output": 0,
+ "CacheRead": 0,
+ "CacheWrite": 0,
+ "Turns": 0,
+ "Seconds": 0,
+ "CostUSD": 0
+ },
+ "Readability": {
+ "Naming": 0,
+ "Structure": 0,
+ "Traceability": 0,
+ "Errors": 0,
+ "Comments": 0,
+ "Overall": 0,
+ "Notes": ""
+ },
+ "ReviewFindings": null,
+ "Notes": null
+}
diff --git a/results/rust.json b/results/rust.json
new file mode 100644
index 0000000..96dc9ea
--- /dev/null
+++ b/results/rust.json
@@ -0,0 +1,105 @@
+{
+ "Slug": "rust",
+ "Language": "Rust",
+ "Status": "LIVE",
+ "URL": "https://todo-rust.bfj.oncarlos.com",
+ "Version": "f78a916",
+ "Stack": "",
+ "Source": "",
+ "BehaviourJSOn": {
+ "Passed": 35,
+ "Failed": 0,
+ "Skipped": 0
+ },
+ "BehaviourJSOff": {
+ "Passed": 32,
+ "Failed": 0,
+ "Skipped": 3
+ },
+ "TTFBEmpty": {
+ "N": 0,
+ "P50": 0,
+ "P95": 0,
+ "P99": 0,
+ "Min": 0,
+ "Max": 0
+ },
+ "TTFB50": {
+ "N": 0,
+ "P50": 0,
+ "P95": 0,
+ "P99": 0,
+ "Min": 0,
+ "Max": 0
+ },
+ "Wake": null,
+ "Load": null,
+ "Weight": {
+ "HTMLBytes": 0,
+ "JSBytes": 0
+ },
+ "Code": {
+ "Server": 0,
+ "Templates": 0,
+ "JS": 0,
+ "Tests": 0,
+ "Deps": 0
+ },
+ "Build": {
+ "ColdSeconds": 0,
+ "WarmSeconds": 0,
+ "EditSeconds": 0,
+ "Bytes": 0,
+ "Host": ""
+ },
+ "TokensBuild": {
+ "Input": 94,
+ "Output": 46373,
+ "CacheRead": 3981174,
+ "CacheWrite": 102899,
+ "Turns": 48,
+ "Seconds": 1019.941,
+ "CostUSD": 2.5472628
+ },
+ "TokensFix": {
+ "Input": 14,
+ "Output": 6770,
+ "CacheRead": 839821,
+ "CacheWrite": 8710,
+ "Turns": 7,
+ "Seconds": 397.575,
+ "CostUSD": 0.3731002000000001
+ },
+ "TokensSecurity": {
+ "Input": 0,
+ "Output": 0,
+ "CacheRead": 0,
+ "CacheWrite": 0,
+ "Turns": 0,
+ "Seconds": 0,
+ "CostUSD": 0
+ },
+ "TokensReview": {
+ "Input": 619228,
+ "Output": 2898,
+ "CacheRead": 529920,
+ "CacheWrite": 0,
+ "Turns": 2,
+ "Seconds": 0,
+ "CostUSD": 0
+ },
+ "Readability": {
+ "Naming": 4,
+ "Structure": 4,
+ "Traceability": 4,
+ "Errors": 4,
+ "Comments": 5,
+ "Overall": 8,
+ "Notes": "Strongest: the `act` helper gives every action one pipeline (resolve visitor, transaction, 404/303/fragment), so a request is easy to follow from route to db to markup; comments explain real why (body-read before 403 behind the edge, mutex over a pool, append-only migrations, editing li preservation). db.rs is one query per function with scoped list ids. Weakest: handler params are single letters (s, h, q, f) and the `found`/`Outcome` indirection adds a hop; `swap` in app.js is dense DOM surgery that needs careful reading; errors are only rusqlite wrapped in AppError and logged with eprintln, with a few `expect`s standing in for proper failure paths."
+ },
+ "ReviewFindings": [
+ "[correctness] [apps/rust/static/app.js:10](/home/paulca/amadan.net/carlos/todomvc-bakeoff/.worktrees/rust/apps/rust/static/app.js:10): Filter navigation can discard pending changes. Requests wait in a promise queue, but filter links navigate immediately. With a toggle request pending, edit another todo and click a filter: blur queues the save, then navigation destroys the queue before that save reaches the server. Wait for pending actions before navigating.",
+ "[spec] [apps/rust/src/main.rs:45](/home/paulca/amadan.net/carlos/todomvc-bakeoff/.worktrees/rust/apps/rust/src/main.rs:45): `--listen` accepts every loopback address, including `::1` and `127.0.0.2`. The brief explicitly restricts binding to `127.0.0.1`. Validate that exact address."
+ ],
+ "Notes": null
+}
diff --git a/results/swift.json b/results/swift.json
new file mode 100644
index 0000000..28dc2c2
--- /dev/null
+++ b/results/swift.json
@@ -0,0 +1,104 @@
+{
+ "Slug": "swift",
+ "Language": "Swift",
+ "Status": "LIVE",
+ "URL": "https://todo-swift.bfj.oncarlos.com",
+ "Version": "354c88a",
+ "Stack": "",
+ "Source": "",
+ "BehaviourJSOn": {
+ "Passed": 35,
+ "Failed": 0,
+ "Skipped": 0
+ },
+ "BehaviourJSOff": {
+ "Passed": 32,
+ "Failed": 0,
+ "Skipped": 3
+ },
+ "TTFBEmpty": {
+ "N": 0,
+ "P50": 0,
+ "P95": 0,
+ "P99": 0,
+ "Min": 0,
+ "Max": 0
+ },
+ "TTFB50": {
+ "N": 0,
+ "P50": 0,
+ "P95": 0,
+ "P99": 0,
+ "Min": 0,
+ "Max": 0
+ },
+ "Wake": null,
+ "Load": null,
+ "Weight": {
+ "HTMLBytes": 0,
+ "JSBytes": 0
+ },
+ "Code": {
+ "Server": 0,
+ "Templates": 0,
+ "JS": 0,
+ "Tests": 0,
+ "Deps": 0
+ },
+ "Build": {
+ "ColdSeconds": 0,
+ "WarmSeconds": 0,
+ "EditSeconds": 0,
+ "Bytes": 0,
+ "Host": ""
+ },
+ "TokensBuild": {
+ "Input": 120,
+ "Output": 68280,
+ "CacheRead": 6750077,
+ "CacheWrite": 154900,
+ "Turns": 67,
+ "Seconds": 1813.84,
+ "CostUSD": 3.9552954
+ },
+ "TokensFix": {
+ "Input": 24,
+ "Output": 6564,
+ "CacheRead": 2080923,
+ "CacheWrite": 11946,
+ "Turns": 12,
+ "Seconds": 434.384,
+ "CostUSD": 0.6431286000000007
+ },
+ "TokensSecurity": {
+ "Input": 0,
+ "Output": 0,
+ "CacheRead": 0,
+ "CacheWrite": 0,
+ "Turns": 0,
+ "Seconds": 0,
+ "CostUSD": 0
+ },
+ "TokensReview": {
+ "Input": 688335,
+ "Output": 3734,
+ "CacheRead": 591488,
+ "CacheWrite": 0,
+ "Turns": 2,
+ "Seconds": 0,
+ "CostUSD": 0
+ },
+ "Readability": {
+ "Naming": 4,
+ "Structure": 4,
+ "Traceability": 5,
+ "Errors": 4,
+ "Comments": 5,
+ "Overall": 8,
+ "Notes": "Strongest: a toggle is trivially traceable (route closure → `action` → `Store.toggle` → `changed` → `Page.body`), each file has one clear job, and comments explain decisions (single SQLite connection, append-only migrations, 400-day cookie, the partial header) without restating code. Weakest: `OriginCheck` lives in Visitors.swift and `trimmingWhitespace` is split across Routes.swift and Store.swift; `action` is a generic name for the central request helper; `Store.todos` silently defaults malformed rows (`?? 0`, `?? \"\"`) rather than failing; a few `try?` cleanups are justified but unlogged, and the logger is otherwise unused. `bumpNote` is build tooling leaking into app code, though documented."
+ },
+ "ReviewFindings": [
+ "[correctness] [apps/swift/static/app.js:32](/home/paulca/amadan.net/carlos/todomvc-bakeoff/.worktrees/swift/apps/swift/static/app.js:32): Every action response removes the entire list, including any active editor. Toggle one item, then start editing another before the response arrives: the replacement discards the draft and focus without saving. Serializing requests does not protect unsent edits. Preserve active editor contents and focus across updates, or defer replacement until editing finishes."
+ ],
+ "Notes": null
+}
diff --git a/results/zig.json b/results/zig.json
new file mode 100644
index 0000000..89cd706
--- /dev/null
+++ b/results/zig.json
@@ -0,0 +1,104 @@
+{
+ "Slug": "zig",
+ "Language": "Zig",
+ "Status": "LIVE",
+ "URL": "https://todo-zig.bfj.oncarlos.com",
+ "Version": "576c3b8",
+ "Stack": "",
+ "Source": "",
+ "BehaviourJSOn": {
+ "Passed": 35,
+ "Failed": 0,
+ "Skipped": 0
+ },
+ "BehaviourJSOff": {
+ "Passed": 32,
+ "Failed": 0,
+ "Skipped": 3
+ },
+ "TTFBEmpty": {
+ "N": 0,
+ "P50": 0,
+ "P95": 0,
+ "P99": 0,
+ "Min": 0,
+ "Max": 0
+ },
+ "TTFB50": {
+ "N": 0,
+ "P50": 0,
+ "P95": 0,
+ "P99": 0,
+ "Min": 0,
+ "Max": 0
+ },
+ "Wake": null,
+ "Load": null,
+ "Weight": {
+ "HTMLBytes": 0,
+ "JSBytes": 0
+ },
+ "Code": {
+ "Server": 0,
+ "Templates": 0,
+ "JS": 0,
+ "Tests": 0,
+ "Deps": 0
+ },
+ "Build": {
+ "ColdSeconds": 0,
+ "WarmSeconds": 0,
+ "EditSeconds": 0,
+ "Bytes": 0,
+ "Host": ""
+ },
+ "TokensBuild": {
+ "Input": 172,
+ "Output": 62991,
+ "CacheRead": 9913615,
+ "CacheWrite": 158246,
+ "Turns": 92,
+ "Seconds": 1791.585,
+ "CostUSD": 4.509198999999999
+ },
+ "TokensFix": {
+ "Input": 26,
+ "Output": 8226,
+ "CacheRead": 2312162,
+ "CacheWrite": 15063,
+ "Turns": 13,
+ "Seconds": 285.943,
+ "CostUSD": 0.7475604000000002
+ },
+ "TokensSecurity": {
+ "Input": 8,
+ "Output": 1030,
+ "CacheRead": 749296,
+ "CacheWrite": 3731,
+ "Turns": 4,
+ "Seconds": 55.56,
+ "CostUSD": 0.20033920000000016
+ },
+ "TokensReview": {
+ "Input": 899608,
+ "Output": 3958,
+ "CacheRead": 760064,
+ "CacheWrite": 0,
+ "Turns": 3,
+ "Seconds": 0,
+ "CostUSD": 0
+ },
+ "Readability": {
+ "Naming": 4,
+ "Structure": 4,
+ "Traceability": 5,
+ "Errors": 4,
+ "Comments": 5,
+ "Overall": 8,
+ "Notes": "Strongest: traceability. A toggle is easy to follow from app.js's form submit through handle/post, itemAction, Store.toggle's single UPDATE, and back via view.render, with clear module boundaries (main/app/db/view) and good why-comments on header lifetime, keep-alive, token hashing, and signal-safe shutdown. Weakest: a few small opacities. post() is one long if-chain mixing routing and logic; readerExpectContinue errors are remapped to WriteFailed with no explanation; receiveHead failures and the ROLLBACK/deleteFile errors are silently dropped; sqlite errors collapse to a single error.Sqlite with logging only at some layers; the health_body comment references an unexplained 'make bump'; and the 'Each of these answers false' doc sits on toggle alone but means three functions."
+ },
+ "ReviewFindings": [
+ "[security] [apps/zig/src/app.zig:218](apps/zig/src/app.zig#L218): A POST to `/todos` with body `title=a%80%80%80%80` passes a five-byte slice to `std.unicode.utf8Decode`, whose unsupported-length branch is `unreachable`. In the configured ReleaseSafe build, this aborts the entire server, denying service to every visitor. Validate UTF-8 before trimming and reject malformed titles without panicking."
+ ],
+ "Notes": null
+}