Skip to content

[ge_arrow] Update to JAX and compare runtime - #717

Open
xuanguang-li wants to merge 19 commits into
mainfrom
update_ge_arrow
Open

[ge_arrow] Update to JAX and compare runtime#717
xuanguang-li wants to merge 19 commits into
mainfrom
update_ge_arrow

Conversation

@xuanguang-li

@xuanguang-li xuanguang-li commented Nov 17, 2025

Copy link
Copy Markdown
Contributor

Updated the ge_arrow.md to JAX and complemented the styling consistent with the operation manual.

Key changes:

  • Rewrite the RecurCompetitive class as a NamedTuple.
  • Complete all computations inside the compute_rc_model function. Inside this function, arguments of sub-functions can be written in the same way as the definitions in the theory part.
  • Partially jitted the main computation function, and used jax.lax.fori_loop to conduct loops.
  • Fixed some typos and styling.

Update: Runtime Comparison Between JAX (GPU), JAX (CPU), and NumPy

Methodology: nearly the same as in #654

  • The JAX version uses the code in this PR, while the NumPy version uses the code in main.
  • The runtime for JAX (GPU) is measured using Google Colab T4 GPU runtime.
  • Runtime is collected using qe.timeit over 1,000 iterations.
  • Each iteration consists of solving Example 3 (solving the wealth distribution for 100 kinds of transition matrices).

Results:

  • Average runtime: JAX (CPU) > NumPy > JAX (GPU).
  • More details are shown in the attached plots.
runtime_compare_average runtime_compare_boxplot

@github-actions

github-actions Bot commented Dec 2, 2025

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Dec 2, 2025

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Dec 2, 2025

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Dec 2, 2025

Copy link
Copy Markdown

@xuanguang-li
xuanguang-li marked this pull request as ready for review December 11, 2025 00:52
 - Replace `@partial(jax.jit)` with `jax.jit` on the main function `compute_rc_model`.

- Write a function to compute example 3 and add `jax.jit` decorator.
@xuanguang-li xuanguang-li changed the title [ge_arrow] Update to JAX [ge_arrow] Update to JAX and compare runtime Feb 10, 2026
@github-actions

github-actions Bot commented Feb 11, 2026

Copy link
Copy Markdown

📖 Netlify Preview Ready!

Preview URL: https://pr-717--sunny-cactus-210e3e.netlify.app

Commit: 8c2d0d7

📚 Changed Lectures


Build Info

@xuanguang-li

Copy link
Copy Markdown
Contributor Author

Due to differences in the runtime environment, the runtime of JAX (GPU) may not be comparable to others.

Moreover, the code for both JAX and NumPy is adjusted to match each other for the test function.

Here is the runtime comparison including Numba; all runs were on the local CPU.

While JAX (CPU) accelerates the NumPy code much, Numba is about 15 times faster than JAX (CPU) for this test function.

runtime_compare_average

@xuanguang-li

Copy link
Copy Markdown
Contributor Author

A Quantitative Evaluation System for JAX Rewrites of QuantEcon Lectures

This document defines a reusable, quantitative system for deciding whether rewriting a QuantEcon lecture's code (e.g., converting NumPy → JAX) actually improves the lecture. It was designed against the first such change, lectures/ge_arrow.md (branch update_ge_arrow vs main).

The guiding principle: these are teaching lectures first and programs second. A rewrite that makes the code faster or more "modern" but harder for a learner to read, or that silently changes the numbers, is not automatically an improvement. The system therefore weights pedagogy heavily and never treats "uses JAX" as a goal in itself — JAX must earn its place on each lecture.


1. The seven dimensions

# Dimension Weight What it answers
1 Correctness & numerical fidelity 0.20 Does the new code compute the same economics, at the same precision?
2 Readability & pedagogical clarity 0.25 Can a learner follow it? Does the code mirror the math?
3 Computational efficiency (as used) 0.15 Is it faster in the regime the lecture actually runs?
4 Logic & design 0.15 Are functions natural, pure, non-repetitive, bug-free?
5 Coding style & idiom 0.10 Idiomatic Python/JAX and consistent with house style?
6 API ergonomics & reusability 0.10 How easy is the object to call, compose, and reuse?
7 Maintainability & robustness 0.05 How easy to test, debug, and safely extend later?

Weights sum to 1.0. Readability (0.25) outranks efficiency (0.15) on purpose: the audience is learners, and most lecture models are tiny. Adjust the weights per lecture family if needed (e.g. a "performance" lecture could raise dimension 3), but record any change.

Each dimension is scored 1–5 against the anchors below, then combined:

weighted_total = Σ  weight_d × score_d        (range 1–5)

Interpreting the total

Total Meaning
≥ 4.0 Clear improvement — merge.
3.0 – 3.9 Net positive but with fixable regressions — merge after addressing them.
2.5 – 2.9 Mixed / wash — improvements offset by real regressions; revisit before merging.
< 2.5 Net regression — do not merge as-is.

2. Scoring anchors + worked high/low examples

For each dimension, we give (a) the metric(s) that quantify it, (b) the 1–5 anchors, and (c) a HIGH-scoring and LOW-scoring example so reviewers agree on what "good" looks like.

Dimensions 1, 2, 3, 6 carry numeric score thresholds (a measured number maps directly to 1–5); dimensions 4, 5, 7 are structural and scored against criteria + cited evidence. The numeric thresholds were calibrated against two real, measured end points: a HIGH case (the aiyagari Bellman pattern, 25× faster as-used) and a LOW case (the full ge_arrow lecture, 45× slower as-used).

Every example below is real code already in lecture-python.myst, cited by file:line, not a hypothetical. The HIGH examples are mostly drawn from lectures the QuantEcon team has already converted well to JAX — aiyagari.md, lake_model.md — and the LOW examples from ge_arrow.md and the older class-based odu.py. (Line numbers are as of branch update_ge_arrow / main at the time of writing; search the cited symbol if they drift.)


Dimension 1 — Correctness & numerical fidelity · weight 0.20

Metrics (from check_equivalence.py):

  • all_equivalent — do all equilibrium objects (Q, R, A, V, α, ψ, J) match the original across every example economy?
  • max_abs_err — largest absolute deviation from the original numbers.
  • default dtype / precision (float32 vs float64).

Anchors (numeric — keyed to max|Δ| vs the original, as the lecture ships)

Score as-shipped max|Δ| precision
5 ≤ 1e-10 float64 preserved; any diff explained
4 ≤ 1e-8 preserved
3 logic matches under x64 (≤1e-10) but ships float32 → 1e-5…1e-3 drift, unflagged
2 1e-3 … 1e-1 on some object, or instability in edge cases
1 > 1e-1 / NaN where equality is expected — wrong economics

HIGH (5): lectures/aiyagari.md:72 opens the JAX section with

jax.config.update("jax_enable_x64", True)

so its linear solves and value iteration run in double precision — published capital/interest numbers match a NumPy reference to machine epsilon. (lectures/newton_method.md does the same.)

LOW (3, the ge_arrow case): lectures/ge_arrow.md has no such line. Under float64 the rewrite matches the original to 1.4e-14, proving the logic is identical — but as shipped it runs in float32, so example 2's printed α, ψ, J move by 1.7e-4. Correct math, quietly degraded precision. (Reproduce: run check_equivalence.py with and without JAX_ENABLE_X64=1.)


Dimension 2 — Readability & pedagogical clarity · weight 0.25

Metrics (from static_metrics.py) + reviewer reading:

  • n_prerequisite_concepts — distinct ideas a reader must already know.
  • docstring_coverage — fraction of defs with a docstring.
  • code_lines, n_defs, closure-nesting depth.
  • Math-to-code distance (judgement): does a code line look like the equation it implements?

Anchors (numeric — keyed to Δ prerequisite-concepts vs the original and to docstring coverage; both from static_metrics.py)

Score new prerequisite concepts docstring coverage &
5 +0 ≥ 0.80 code lines read like the math
4 +1–2 ≥ 0.75 still transparent
3 +3–4 0.60–0.75 readable if you know the framework
2 +5–6 < 0.60 core formula obscured by plumbing
1 +7 or more learner can't map a cell to its economics

(Use the worse of the two columns; the "&" column is the tie-breaker.)

HIGH (5): lectures/aiyagari.md:288-300 builds the Bellman right-hand side with broadcasting that visibly mirrors the math $r(a,z,a') + \beta,E,v$, and a single branchless feasibility test:

a  = jnp.reshape(a_grid, (a_size, 1, 1))     # a[i]   -> a[i, j, ip]
z  = jnp.reshape(z_grid, (1, z_size, 1))     # z[j]   -> z[i, j, ip]
ap = jnp.reshape(a_grid, (1, 1, a_size))     # ap[ip] -> ap[i, j, ip]
c = w * z + (1 + r) * a - ap
...
return jnp.where(c > 0, u(c) + β * EV, -jnp.inf)

A reader sees the budget constraint and the Bellman equation directly.

LOW (2, the ge_arrow case): lectures/ge_arrow.md:938-959 expands the one-line kernel $Q_{ij}=\beta,(y_j/y_i)^{-\gamma}P_{ij}$ into two nested jax.lax.fori_loops with q.at[j].set(...) carries:

def body_fun_i(i, Q):
    def body_fun_j(j, q):
        ratio = u_prime(c[j]) / u_prime(c[i])
        return q.at[j].set(β * ratio * P[i, j])
    q = jax.lax.fori_loop(0, n, body_fun_j, jnp.zeros((n,)))
    return Q.at[i, :].set(q)
Q = jax.lax.fori_loop(0, n, body_fun_i, jnp.zeros((n, n)))

Prerequisite concepts rise 7 → 13, docstring coverage falls 0.90 → 0.55, and the simple "ratio of marginal utilities" idea is buried under functional-update plumbing.

Also LOW (2), pre-JAX style: the Bellman operator in lectures/_static/lecture_specific/odu/odu.py:114-123 loops for i in range(N) over flattened grid points doing a fixed_quad integral per cell — the value-iteration math is hard to see through the Python scaffolding. (This is the kind of code a good JAX rewrite should improve; contrast with the aiyagari HIGH example above.)


Dimension 3 — Computational efficiency (as actually used) · weight 0.15

Crucial rule: measure efficiency in the regime the lecture runs, not a hypothetical large-scale one. For JAX that means including trace+compile time whenever the lecture hits a new shape or static_argnames value, because each of those triggers a recompile.

Metrics (from benchmark.py, cold_start.py, sweep_bench.py):

  • as-used latency (cold, lecture problem size);
  • warm/amortized latency;
  • scaling curve + the crossover n where JAX overtakes NumPy;
  • recompile cost per distinct static-arg value.

The metric that decides the score is the as-used speedup

as_used_speedup = (total NumPy wall time) / (total JAX wall time)

measured over the lecture's actual sequence of solver calls, at its actual problem sizes, in a fresh interpreter (so JAX's compiles count). >1 = JAX faster, <1 = JAX slower.

Anchors (numeric)

Score as-used speedup meaning
5 ≥ 3× materially faster as the lecture runs it
4 1.3× – 3× clearly faster
3 0.8× – 1.3× wash; JAX only wins warm / at sizes the lecture never reaches
2 < 0.8× measurably slower as used; stated goal not met, but correct & fixable
1 < 0.8× and worse (wrong/unstable, or no fix path) slower with no redemption

HIGH (5) — MEASURED. aiyagari.md is JAX on both branches (no NumPy baseline in-repo), so we benchmarked its computational pattern — the vectorised Bellman of aiyagari.md:288-300 solved by value-function iteration on a 200×7 grid, then re-solved 20× as an equilibrium loop would (scripts/bellman_bench.py, results in results/bellman_bench.json):

NumPy JAX speedup
one solve (397 VFI iters), warm 1664 ms 69 ms 24×
equilibrium loop, R=20 (as-used, incl. compile) 29.3 s 1.16 s 25×

Results agree to 1.1e-14. Large array + many fixed-shape re-solves → the one-time compile is amortised and JAX wins by 25×. *(Representative single-CPU medians; ±15% run-to-run — the decisive fact is the order of magnitude.)* as-used speedup ≈25 ≥ 3 → score 5.

LOW (2) — MEASURED. Replaying the entire ge_arrow solver sequence (all examples + the λ-sweep + finite/T=10000) once in a fresh process (scripts/as_used_total.py):

NumPy total JAX total as-used speedup
0.035 s 1.56 s 0.022× (≈45× slower)

Every economy is 2×2/3×3 and each call uses fresh static args (s0_idx, T) → a fresh compile each time (first solve 286 ms, recompile 133 ms, λ-sweep 300 ms cold). JAX would win warm at n ≳ 25 (see benchmark.py scaling), but the lecture's economics fix the size tiny. as-used 0.022 < 0.8 → score 2 (correct and fixable, so not a 1).


Dimension 4 — Logic & design · weight 0.15

Metrics: explicit_loops, repetition/DRY review, statefulness, latent bugs.

Anchors

Score Criterion
5 Pure, single-responsibility functions; no repetition; no order-dependence; no global reliance; fixes prior bugs.
4 Mostly clean; minor redundancy.
3 Works but has some duplication or awkward coupling.
2 Order-dependent mutation, duplicated computation, or reliance on globals.
1 Tangled control flow or logic that is hard to reason about / buggy.

HIGH (5): lectures/lake_model.md:216-275 declares parameters as a frozen LakeModel(NamedTuple) with defaults, then computes everything with pure jitted functions that take the model as an argument:

class LakeModel(NamedTuple):
    λ: float = 0.283; α: float = 0.013; b: float = 0.0124; d: float = 0.00822

@jax.jit
def compute_matrices(model: LakeModel):
    λ, α, b, d = model.λ, model.α, model.b, model.d
    ...

No instance is mutated, no call ordering matters, no globals. (The ge_arrow rewrite adopts this same pattern — its strongest aspect.)

LOW (2, the ge_arrow original): wealth_distribution(s0)continuation_wealths()value_functionss() must be called in that order because each mutates self; risk_free_rate recomputes sum(Q) instead of reusing PRF; pricing_kernel references the module-level P; and the public method is misspelled value_functionss. The rewrite fixing these is exactly why it scores well here.


Dimension 5 — Coding style & idiom · weight 0.10

Metrics: PEP 8 / project-style conformance, and — for JAX — whether the code uses idiomatic JAX (vectorisation, vmap, where) rather than mechanically porting Python loops.

Anchors

Score Criterion
5 Idiomatic in both languages; vectorised where natural; consistent naming.
4 Idiomatic with minor nits.
3 Correct but mixes idioms or ports loops literally where vectorisation fits.
2 Anti-idiomatic constructs that a JAX reviewer would flag.
1 Fights the framework throughout.

HIGH (5): lectures/aiyagari.md:300 uses branchless jnp.where(c > 0, u(c) + β * EV, -jnp.inf) to impose feasibility, and lectures/lake_model.md: 261 iterates a time series with jax.lax.scan (the idiomatic carry/collect primitive) instead of hand-rolled index updates.

LOW (3, the ge_arrow case): nested fori_loop scalar scatter for the pricing kernel (vectorisation was a one-liner), and jax.lax.cond(T==0, …) that traces both branches every call where T is already static and a plain Python if would do.


Dimension 6 — API ergonomics & reusability · weight 0.10

Metrics: statements_for_one_result (calls needed to obtain α, ψ, J); composability (jit/vmap-friendly?); immutability.

Anchors (numeric — keyed to statements_for_one_result, i.e. the calls a user must write to obtain α, ψ, J for one economy)

Score statements &
5 1 immutable result, trivially jit/vmap-composable
4 ≤ 2 one object + minor setup
3 3 order-independent
2 ≥ 3 ordered, side-effecting calls (wrong order → silent garbage)
1 fragile protocol, easy to misuse silently

HIGH (5): lectures/lake_model.mdmodel = LakeModel() then compute_matrices(model) / simulate_path(...); the model is an immutable argument passed to stateless functions, trivially vmap-able over parameters. The ge_arrow rewrite matches this: m = compute_rc_model(s, P, ys, s0_idx=1, T=10) returns one immutable bundle (m.Q, m.α, m.ψ, m.J, …), statements_for_one_result = 1.

LOW (2): odu.py's SearchProblem and the ge_arrow original both require build object → call mutating methods in the correct order. For ge_arrow that is wealth_distribution → continuation_wealths → value_functionss; statements_for_one_result = 4, and calling them out of order silently gives wrong/garbage results.


Dimension 7 — Maintainability & robustness · weight 0.05

Metrics: testability (pure vs stateful), debuggability (can you step through it?), and "footguns" left for future editors.

Anchors

Score Criterion
5 Pure & easily unit-tested; no silent traps; easy to extend.
4 Testable; small caveats.
3 Testable but harder to debug, or leaves a minor trap.
2 Hard to debug or carries a silent correctness trap (e.g. dtype).
1 Brittle; changes likely to break silently.

HIGH (5): lectures/aiyagari.md pairs pure jitted functions with the explicit jax.config.update("jax_enable_x64", True) at :72, so a future editor reusing the functions gets full precision by default and can unit-test each @jax.jit function in isolation.

LOW (3, the ge_arrow case): purity helps testing, but jit + static_argnames + 3-deep closures make stepping hard, and the float32 default is a silent trap for the next person who reuses the function.


3. Limitations

  • Benchmarks are CPU-only (jax.devices() == [CpuDevice]). On GPU/TPU the crossover n shifts left and JAX's warm advantage grows — but the lecture's models are still tiny, so the as-used verdict is unlikely to change.
  • Dimension scores 2/5/6 are partly judgement; the rubric anchors and the cited metrics make them auditable, not arbitrary.

@xuanguang-li

Copy link
Copy Markdown
Contributor Author

Evaluation Report — ge_arrow.md: NumPy (main) → JAX (update_ge_arrow)

Applies the evaluation system to the only code change on branch update_ge_arrow.

TL;DR — weighted score 2.95 / 5net mixed, slightly negative for this lecture

The rewrite is better software (one-call pure API, real bug fixes) but a worse lecture on the two axes that matter most here: it is harder to read and — contrary to the stated motivation — slower in every regime this lecture actually runs, while silently dropping numerical precision.

Dimension Wt Score Weighted
Correctness & numerical fidelity 0.20 3 0.60
Readability & pedagogical clarity 0.25 2 0.50
Computational efficiency (as used) 0.15 2 0.30
Logic & design 0.15 4 0.60
Coding style & idiom 0.10 3 0.30
API ergonomics & reusability 0.10 5 0.50
Maintainability & robustness 0.05 3 0.15
Total 1.00 2.95

What changed

Original (main) Rewrite (update_ge_arrow)
Library NumPy JAX (jnp, lax, jit)
Container mutable class with methods immutable NamedTuple of results
Entry point build object + 3 ordered method calls one @jit function compute_rc_model
Loops Python for (×6) jax.lax.fori_loop / lax.cond (0 Python loops)
Infinite-horizon flag T=None T=0
Notable typo value_functionss; uses global P,n,K fixes both

Evidence by dimension

1 · Correctness & numerical fidelity → 3/5

check_equivalence.py over all 11 example/initial-state combinations:

  • Under float64: every object matches, max|Δ| = 1.4e-14 → the rewrite's logic is identical. ✅
  • As the lecture actually runs (float32 default, no jax_enable_x64): ex2 deviates by 1.7e-4; several others ~1e-4. The published tables move in the 4th–5th significant figure. ❌ unflagged precision loss.

→ Correct economics, silently reduced precision. Score capped at 3.

2 · Readability & pedagogical clarity → 2/5

static_metrics.py:

metric old new
prerequisite concepts 7 13
docstring coverage 0.90 0.55
code lines (model def) 119 161
sub-definitions 10 22
Python loops a reader parses 6 0 (replaced by fori_loop closures)

The pricing kernel — mathematically just $Q_{ij}=\beta(y_j/y_i)^{-\gamma}P_{ij}$ — becomes two nested fori_loops with .at[j].set(...) carries. For a lecture whose economies are 2×2, this is pure cognitive overhead. Biggest single driver of the negative verdict (and the heaviest-weighted dimension).

3 · Computational efficiency (as used) → 2/5

This was the stated motivation, so it matters that it is not achieved here.

Headline metric — replaying the entire lecture solver sequence once in a fresh process (as_used_total.py):

NumPy total JAX total as-used speedup
0.035 s 1.56 s 0.022× — i.e. ~45× slower

Per-regime detail explaining why:

Regime (n=2 unless noted) NumPy JAX Result
First solve (cold, incl. compile) 6.2 ms 286 ms 46× slower
Recompile per new s0_idx/T 133 ms each distinct call recompiles
Warm repeat 0.032 ms 0.022 ms 1.4× faster (never used)
λ-sweep (100 pts), as run once 1.8 ms 300 ms cold 170× slower
λ-sweep warm 0.37 ms 4.8× faster (never realized)

Scaling crossover (benchmark.py): NumPy and JAX-warm are even near n≈10; JAX wins 2–6× for n = 25…200. The lecture never exceeds n=3. For calibration, the same machinery on the large, repeatedly-solved aiyagari pattern (bellman_bench.py) is 25× faster — a score-5 case. ge_arrow's 0.022× maps to score 2 (< 0.8×, but correct and fixable).

4 · Logic & design → 4/5

Genuine improvements, all verified in the diff:

  • removes order-dependent stateful methods (old required wealth_distribution → continuation_wealths → value_functionss);
  • removes reliance on module-level P, n, K (a latent bug in the original);
  • fixes the value_functionss typo;
  • de-duplicates (R no longer recomputes sum(Q)); returns one result object.

Minus one point: the pricing kernel is ported as an O(n²) scalar loop instead of a vectorised outer product.

5 · Coding style & idiom → 3/5

NamedTuple + pure function is clean. But two anti-idiomatic JAX choices: the nested-fori_loop pricing kernel (vectorisation was trivial) and jax.lax.cond(T==0, …) which traces both branches although T is already a static argument — a plain if would compile only the needed branch.

6 · API ergonomics & reusability → 5/5

statements_for_one_result: 4 → 1. compute_rc_model(s, P, ys, s0_idx=1, T=10) returns an immutable bundle; fully jit/vmap-composable. Clear win.

7 · Maintainability & robustness → 3/5

Purity aids unit testing, but jit + static_argnames + 3-deep closures hinder step-debugging, and the float32 default is a silent trap for future reuse.


Recommendation

The conversion is not yet a net improvement for this particular lecture. Two paths:

A. Keep NumPy for ge_arrow. The models are 2×2/3×3; NumPy is faster as-used, more readable, and matches the published numbers. Reserve JAX for lectures with large, repeated, fixed-shape computation.

B. If JAX is kept, fix these before re-scoring (each maps to a dimension):

  1. Vectorise the pricing kernelQ = β*(y[None,:]/y[:,None])**(-γ)*P (D2 readability, D3 efficiency, D5 idiom).
  2. Enable float64: jax.config.update("jax_enable_x64", True) so published numbers are preserved (D1, D7).
  3. Reduce recompiles: avoid making s0_idx/T static, or vectorise over s0_idx, so the lecture doesn't pay a fresh compile per call (D3).
  4. Restore docstrings on the nested helpers; replace lax.cond on a static T with a Python if (D2, D5).

Re-running run_all.py after these fixes would likely lift readability to ~3, efficiency to ~3, and the total above the 3.0 "merge after fixes" line.

@mmcky

mmcky commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Hi @xuanguang-li — thank you for these two evaluation comments, they're excellent. The framework looks really interesting: the "as-used speedup" idea in particular (fresh process, the lecture's actual problem sizes and call sequence, compile time included) is exactly the right way to decide whether JAX, Numba, or plain NumPy is the right tool for a given lecture — and it neatly explains why warm %timeit numbers were telling us the wrong story. Scoring your own PR at 2.95 and recommending against merging it as-is is great scientific practice too. 😄

I'd like to build on this with you. One idea is to turn your evaluation system into a reusable agent skill — working name /eval-py-acceleration — that can run the full evaluation on any lecture conversion (old vs new implementation): the float32/float64 equivalence check, the static readability metrics, the as-used benchmark replay, and finally a scored report in the format of your comment above.

That would let us apply this consistently across the lecture series as we review conversion PRs, and I think it could grow into the start of a broader benchmarking project for the QuantEcon lectures. Once the skill has settled we'll distill the rubric into the QuantEcon manual alongside the existing JAX conversion style guide, so the thresholds and tooling stay in sync.

Would you be up for building this together? A great first step would be gathering the scripts you reference (check_equivalence.py, static_metrics.py, benchmark.py, cold_start.py, sweep_bench.py, as_used_total.py, bellman_bench.py, run_all.py) into one place — a zip attached to an email, or a gist all work. From there we can iterate together on generalizing them beyond ge_arrow and wrapping them in the skill.

@mmcky

mmcky commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

On this PR itself: given your own evaluation I'd suggest we hold off on merging for now. One path worth considering is keeping the structure of your rewrite — the NamedTuple, the pure one-call API, and the real bug fixes (order-dependent methods, the module-level P, the value_functionss typo) — but implemented in plain NumPy, which would capture the dimensions where your rewrite clearly won without the compile-time and precision costs.

And once the skill exists, this PR would make a perfect first test case for it.

@xuanguang-li

Copy link
Copy Markdown
Contributor Author

Thanks for your comments, @mmcky.

It's a fascinating idea to build the evaluation system into a skill. I'll package the test scripts soon and send them by email. From the related PRs, I've started to see the broader picture of the evaluation project, and I'm glad to be able to contribute to such a useful initiative.

One path worth considering is keeping the structure of your rewrite — the NamedTuple, the pure one-call API, and the real bug fixes (order-dependent methods, the module-level P, the value_functionss typo) — but implemented in plain NumPy.

Yes, I think that's a reasonable approach given the evaluation result. With only minor changes to the coding logic while preserving the NumPy structure, it should be possible to provide a clearer and more explicit implementation.

@jstac

jstac commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🤖 Status note for a future session — from a maintainer investigation on 2026-07-08 into why open-PR previews 404. Context only, not instructions.

Netlify preview: https://pr-717--sunny-cactus-210e3e.netlify.app/ currently returns 404.

Why previews are down (repo-wide findings)

1. This branch is stale — 174 commits behind main. A preview build compiles the whole site from this branch. This branch's lectures/house_auction.md still has unpinned !pip install prettytable, which now breaks on a wcwidth incompatibility. main fixed this on 2026-06-28 by pinning prettytable<3.18 (#939). This alone fails any rebuild of this branch until it's updated to main.

2. The arviz failure was a red herring — do NOT pin arviz or rewrite plotting. A 2026-07-07 rebuild also failed in ar1_bayes/ar1_turningpts with an arviz_plots figsize ValueError. That was a transient bug in an intermediate arviz-plots 1.x release, already fixed in arviz 1.2.0. Verified locally on a clean latest-stack venv: the real az.plot_trace(trace) cell (pymc + numpyro InferenceData) runs green. The lectures use only 1.x-compatible arviz APIs (plot_trace, summary, from_numpyro, compare).

Note on recent timeline activity

This PR was close/reopened on 2026-07-07 by a maintainer session purely to trigger a rebuild test — not a content change. That rebuild failed on the stale-branch issue above. Apologies for the notification churn.

Recommended first step for this PR

Update this branch to main (merge or rebase — pulls in #939 plus ~174 other commits), then let CI rebuild. On today's latest libraries the site builds clean, so the preview should return. house_auction is the known blocker; updating also picks up other since-merged fixes — rebuild and address any remaining per-lecture failures. Verify with:

curl -sI https://pr-717--sunny-cactus-210e3e.netlify.app/ge_arrow.html

This PR touches: ge_arrow.md. Last CI build: failure@2026-07-07. Branch: 174 commits behind main as of 2026-07-08.

mmcky pushed a commit to QuantEcon/skills that referenced this pull request Jul 27, 2026
… cases

The quantitative evaluation system for lecture code rewrites developed
and validated on QuantEcon/lecture-python.myst#717 and #654:

- references/EVALUATION_FRAMEWORK.md — the standard in prose: 7 weighted
  dimensions, numeric scoring anchors, structural checklists, verdict
  bands, worked HIGH/LOW examples
- scripts/scoring/ — the standard as code: rubric.py (deterministic
  evidence -> score), score.py (engine/CLI), EVIDENCE_TEMPLATE.json
  (the judgement contract: measured numbers + cited yes/no answers)
- scripts/calibration/ — the shared aiyagari Bellman benchmark pinning
  the "25x as-used = score 5" efficiency anchor
- references/examples/{ge_arrow,markov_asset}/ — two complete worked
  evaluations (measurement scripts, results, evidence, reports):
  ge_arrow 2.85/5 mixed/wash; markov_asset 2.25/5 net regression
  (build-breaking bug)

Content as delivered 2026-07-21; placed at plugin-convention paths.
Path/link integration follows in a separate commit.
mmcky added a commit to QuantEcon/skills that referenced this pull request Jul 27, 2026
…, skill wired) (#5)

* Add the lecture evaluation system: rubric engine, calibration, worked cases

The quantitative evaluation system for lecture code rewrites developed
and validated on QuantEcon/lecture-python.myst#717 and #654:

- references/EVALUATION_FRAMEWORK.md — the standard in prose: 7 weighted
  dimensions, numeric scoring anchors, structural checklists, verdict
  bands, worked HIGH/LOW examples
- scripts/scoring/ — the standard as code: rubric.py (deterministic
  evidence -> score), score.py (engine/CLI), EVIDENCE_TEMPLATE.json
  (the judgement contract: measured numbers + cited yes/no answers)
- scripts/calibration/ — the shared aiyagari Bellman benchmark pinning
  the "25x as-used = score 5" efficiency anchor
- references/examples/{ge_arrow,markov_asset}/ — two complete worked
  evaluations (measurement scripts, results, evidence, reports):
  ge_arrow 2.85/5 mixed/wash; markov_asset 2.25/5 net regression
  (build-breaking bug)

Content as delivered 2026-07-21; placed at plugin-convention paths.
Path/link integration follows in a separate commit.

* Integrate the evaluation system into the plugin layout

Integration on top of the landed package (content authored by
@xuanguang-li; this commit is path/plumbing only plus docs):

- score.py takes a lecture directory path (works from any cwd) instead
  of a name resolved against the old package root
- ge_arrow scripts use local imports (import model_old), matching the
  markov_asset idiom, so every script runs directly from its directory
- run_all.py (both examples): scoring call updated to the new layout,
  lecture dir derived not hardcoded, and a provenance stamp written to
  results/env.json (python/platform/numpy/jax/quantecon versions) --
  the seed of the QuantEcon/meta#335 shared result schema
- All relative links in EVALUATION_FRAMEWORK.md and the two reports
  rewritten for the new layout (verified: no dangling references)
- scripts/README.md rewritten: engine layout, the three-step scoring
  contract, the evaluate-a-new-lecture recipe
- SKILL.md updated: system landed, operational procedure now points at
  the real engine/templates, worked cases become regression anchors
- benchmark plugin 0.1.0 -> 0.2.0 (marketplace kept in sync)

Verified: both example scorecards regenerate byte-identically from the
new layout; scripts/validate.py green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* scoring: verdict from the rounded total; fix stale paths in docstring/template

Addresses Copilot review on #5:
- rubric.py computes the verdict band from the rounded total, so the
  band always agrees with the number displayed (raw FP sums can land at
  2.4999999999999996 for combinations that are exactly 2.50 in exact
  arithmetic; 797/78125 score combinations were affected)
- rubric.py docstring points at ../../references/EVALUATION_FRAMEWORK.md
- EVIDENCE_TEMPLATE.json _how cites the actual CLI form
  (scripts/scoring/score.py <lecture-dir> from the plugin root)

Both committed scorecards regenerate unchanged (neither sits at a band
edge). The x64-divergence guard in score_correctness is deliberately
left as authored — rubric semantics stay with the standard's author.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Persist the headline metrics; fix two static-metrics inconsistencies

From the detailed logic review of the evaluation scripts:

- run_all.py (both examples) now captures the JSON lines printed by the
  fresh-process scripts (as_used_total, cold_start) into
  results/as_used.json / results/cold_start.json, with the derived
  as_used_speedup - the headline metric previously lived only on the
  console, though the docstrings already claimed aggregation
- ge_arrow static_metrics: remove the duplicated "@" pattern that
  double-counted concept token hits (informational metric only;
  regenerated results: old.concept_token_hits 110 -> 105)
- markov_asset static_metrics: rename statements_for_one_asset ->
  statements_for_one_result, matching EVIDENCE_TEMPLATE.json and the
  ge_arrow template vocabulary (values unchanged; results regenerated)

Both scorecards regenerate byte-identically - no scored value changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Document the reference examples: logic check and provenance

benchmark/references/examples/README.md explains both canonical cases
in detail - the economics, the two implementations, where every
evidence number comes from, and why each verdict is what it is - and
records the 2026-07-21 line-by-line verification (scorecard byte
reproduction, evidence-results cross-checks, rubric edge audit,
fairness audit) plus the known caveats (M1 hand-curated readability
inputs, m3 x64 stamping, n6 sweep asymmetry).

These examples are the regression baseline for the skill; their
accuracy is now auditable rather than asserted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Final-review fixes: harden run_all, share the env stamp, doc consistency

From the adversarially-verified final review (31-agent pass over the
full PR):

Robustness (both run_all.py files — all four confirmed by execution):
- guard against JSON-scalar stdout lines (json.loads succeeds on `42`/
  `null`; .get then raised AttributeError and aborted the pipeline)
- track per-step returncodes; failed step titles now go into the
  provenance stamp so a partial run cannot claim full provenance for
  stale results
- symmetric total_s guard in the as_used_speedup derivation (bare
  numpy-side index could KeyError)
- warn on duplicate mode keys instead of silently overwriting

Simplification (integrator-authored code only):
- the byte-identical 18-line write_env block duplicated in both
  run_all.py files becomes shared scripts/scoring/env_stamp.py,
  invoked like score.py (-26 LOC net; the shared meta#335 schema now
  has one definition)

Consistency:
- gitignore the per-run generated results (as_used.json,
  cold_start.json, env.json) and annotate their doc citations as
  generated-not-committed (committing them faithfully is impossible
  here: the local env is jax 0.10.1 vs the reports' 0.4.35)
- examples/README: fix 'logic 5' -> 4 (scorecard and its own
  arithmetic say 4; with 5 the listed scores sum to 3.00, not 2.85)
- REPORT link labels updated to match their (already-correct) targets;
  markov REPORT's stale statements_for_one_asset key renamed
- evidence.json _how strings and score.py's scorecard _note now cite
  scripts/scoring/... (scorecards regenerated; only the _note changed)
- SKILL.md no longer restates the weight vector and verdict bands --
  it points at EVALUATION_FRAMEWORK.md sections 1-2 and rubric.py, so
  recalibration cannot drift the copies
- scripts/README: commands documented as running from the plugin root

Both scorecards regenerate byte-identically under the updated engine;
validate.py green; env_stamp smoke-tested including steps_failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Docs: skill usage, repo setup, and validated triage mode (#6)

* Docs: plugin guide, skill usage, repo setup — with triage mode validated

- benchmark/README.md: the plugin's user guide — review mode (session
  walkthrough, report format), triage mode ("should this lecture be
  converted?"), manual pipeline quickstart, plugin map
- SKILL.md: triage-mode section (the prospective subset: baseline
  as-used total, pattern match against the calibrated poles, crossover
  check, readability-cost forecast, and the weight-algebra decision
  rule)
- docs/using-skills.md: consumer guide — setup paths, invocation forms,
  report-first expectations, troubleshooting
- docs/developing-skills.md: contributor guide — layout, conventions,
  dev loop, versioning, squash-merge/stacking and external-author
  attribution patterns
- README.md: documentation index

Triage mode is empirically validated before being documented: blind
triage using only baseline-side data (fresh runs: ge_arrow 0.028s,
markov_asset 0.087s; committed calibration: aiyagari pattern 54.3s)
reproduces all three known verdicts (don't convert / don't convert /
convert), and correctly cannot predict conversion-quality defects
(markov_asset's build bug) — that scope limit is documented with it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Docs: fix command count and map-table link text

Addresses Copilot review on #6: the manual-install snippet is three
commands, not two; the benchmark map row's link text now matches its
target (scripts/README.md) with the engine path named in the
description instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Docs: scale the evidence discipline by output tier, not blanket

The evidence-file + engine pattern applies to skills that aggregate
judgements into scored verdicts; findings-list skills need only cited
claims. developing-skills.md gets the three-tier discipline; CATALOG
gains the principle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Design review: corrections of record + merged three-way synthesis

Two independent design critiques of the evaluation system (a fresh
unframed session; a 36-agent adversarial workflow with steelman
defense) were merged in reviews/. Three of our own claims were
falsified by execution and are corrected here:

- markov_asset's lecture DOES build in notebook order: a stale global
  err masks the stray err.throw(), silently disabling the checkify
  stability validation (worse than a crash, but not a build failure).
  Erratum prepended to the REPORT; wording corrected in examples
  README, SKILL.md, plugin README; correction posted on
  lecture-python.myst#654
- "mirrors the lecture exactly": both reference replays deviate from
  the lectures' construction patterns; certification corrected
- "medians over repeats": false for the as-used totals (single pass
  per side); fairness-audit wording corrected; triage validation
  noted as in-sample

reviews/ holds the independent report and the merged synthesis:
which critiques survived the steelman defense (convention-only safety
couplings; readability instrument inverting its own exemplars; the
review/triage mode contradiction; band-label semantics; noise vs
resolution) and which were defended (ratio form, weighted total,
min() shape, per-consequence billing), plus the ranked v2 plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Update references after withdrawal of the #654 comments

The evaluation findings briefly posted to lecture-python.myst#654 were
withdrawn; the PR will receive one authoritative evaluation after the
rubric-v2 revision and a full skill run, rather than a
comment-and-correction trail. Erratum and merged review updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Rubric v2: enforced couplings, no-conversion verdict, sensitivity stamp, K-repeat

Implements items 1-4 of #7 (the changes that survived the
three-way design review), validated against both committed evidence files:

- score_all derives the logic&design bug-cap from the correctness evidence
  (builds / x64-divergence) instead of trusting a hand-set boolean, and gates
  the verdict: correctness 1 caps at net regression, correctness 2 at
  mixed/wash. The review's honest-evidence 4.2 hole (float32 catastrophe,
  no logic bug -> "merge") now gates to net regression.
- no-conversion verdict: baseline as-used under the 1 s materiality floor
  (a labeled policy choice) + slower as-used candidate -> the scorecard says
  don't convert instead of scoring the polish. Reconciles review with triage.
- sensitivity stamp in score.py: every scored input perturbed one at a time
  (bools flipped, counts +/-1, floats +/-10%); scorecard stamped
  robust/fragile with deciding flips listed.
- K-repeat as-used: run_all.py repeats each as-used side 3x in fresh
  processes; the headline speedup is a median, per-run speedups feed a
  contested-band annotation in the engine.

Re-validation: ge_arrow re-scores 2.85 (unchanged), verdict now
no-conversion (candidate band mixed/wash), stamped fragile with exactly the
review's demonstrated flips. markov_asset re-scores 2.25 (unchanged),
no-conversion + gated net regression, stamped robust across all 29
perturbations - the gate absorbs the one-concept band flip the review
demonstrated. Both band movements are deliberate v2 changes, noted in the
reports. Benchmark plugin bumped to 0.3.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Skill wiring: plugin-root anchoring and workspace evaluation directory

Operationalizes /benchmark:review-acceleration for installed-plugin runs
(#4 item 3): evaluations are built under
<workspace>/benchmark-eval/<lecture>/ with the plugin read-only at
CLAUDE_PLUGIN_ROOT (run_all.py already resolves the shared engine from that
env var); preconditions stated up front; extraction/replay diff check added
to scaffold; the v2 verdict outputs (gates, no-conversion, sensitivity
stamp) carried through the procedure, triage decision rule, and calibration
anchors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Docs: local testing tiers and the switch back to the production marketplace

Adds a Testing locally section to the contributor guide: --plugin-dir for
skill iteration, a local-path marketplace for full install simulation
before merging (test from a consuming project; the checkout's branch is
what gets served; the marketplace name collides with production), and the
remove/add/install sequence to return to the GitHub source afterwards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Validation run: ge_arrow re-evaluated from a fresh checkout, verdict reproduced

The skills#8 dry run, targeting the motivating PR the system was first
developed on (lecture-python.myst#717) rather than the reserved #654
acceptance case. Fresh partial clone at base 8cfba4c / head 8c2d0d7, wired
workspace procedure (benchmark-eval/<lecture>/ with CLAUDE_PLUGIN_ROOT),
jax 0.10.1 vs the reference 0.4.35: reproduces 2.85 / no-conversion /
fragile with the same three deciding flips; every measured quantity moved
only within its band. Full cross-comparison in
reviews/validation-run-ge_arrow-2026-07-22.md.

Fixes surfaced by the run:
- ge_arrow check_equivalence.py now writes equivalence_x64.json under
  JAX_ENABLE_X64 instead of clobbering the as-shipped results (the
  markov_asset template already did this per-regime)
- model_old.py fidelity note discloses the cosmetic whitespace
  normalisation found by diffing against a fresh extraction
- both evidence files now record source_pr + base/head SHAs (provenance
  was previously PR-number-and-branch only)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Docs: hands-on evaluation tutorial built on the ge_arrow validation run

Walks the review-acceleration procedure end-to-end by hand - checkout at
the recorded SHAs, workspace scaffold, K-repeat measurement, the two
precision regimes and why each exists, evidence, scoring, band-based
cross-comparison - with every command and number taken from the recorded
validation run so readers can check their results against a committed
reference. Linked from the README docs table and the benchmark guide.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Docs: AGENTS.md as the canonical agent guide, led by single source of truth

Adds the repo-level instructions file for AI agents and contributors,
following the QuantEcon.manual convention: AGENTS.md is canonical and
CLAUDE.md imports it.

Its governing principle is @jstac's — skills point to existing
documentation in the manual wherever possible instead of repeating what
the manual says — worked out concretely for this repo: rule text stays
upstream in style-guide, numbers live once, every topic has an owning
doc, and cross-boundary references are links rather than copies. The
rest of the file is a doc map plus the conventions that aren't written
down anywhere else (commit subjects, scratch notes, writing for the
GitHub renderer).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Install fix: co-located plugin sources, required owner, validator guards

Three install-surface bugs surfaced by @xuanguang-li while testing the
benchmark plugin (#10):

- marketplace.json omitted the required top-level `owner` object, so
  `/plugin marketplace add` failed schema validation for every user.
- Both plugins declared a remote github source pointing back at this
  repo, forcing an install-time re-clone over SSH (ED25519 failure) to
  reach a subdirectory already present in the added marketplace copy.
  Switched to the documented co-located pattern — relative-path sources
  (`./qe`, `./benchmark`) — so install uses the local copy: no SSH, no
  auth prerequisite.
- validate.py never checked `owner` and assumed an object source, so it
  passed a manifest the installer rejects. It now requires `owner.name`,
  resolves the relative-path source form, and hard-fails any plugin
  whose source points back at this repo.

Also documents the version-gated `/plugin:skill` slash form (v2.1.216+)
and the natural-language fallback in docs/using-skills.md. Marketplace
version 0.1.0 -> 0.1.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Validator: report manifest errors instead of crashing on them

The resolve_source refactor removed the `path` local from check_plugin,
but three error strings still referenced it — so the name-mismatch,
no-skills-dir, and empty-skills branches raised NameError instead of
printing the diagnostic the validator exists to print. CI stayed green
only because a healthy tree never enters those branches (review A1).

Hoist the repo-relative path once after resolve_source returns and
reuse it everywhere. Both reachable branches verified by hand: deleting
benchmark/skills/ and emptying it now yield the intended one-line
diagnostics with exit 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Line endings: normalize the two CRLF files, add .gitattributes

EVALUATION_FRAMEWORK.md and ge_arrow_REPORT.md were CRLF in an
otherwise-LF repo, so any future edit of either would render as a
whole-file diff burying the real change (review E3). Pure
normalization — zero content change, verified with
`git diff --ignore-cr-at-eol`. The .gitattributes makes the
normalization structural instead of a contributor convention.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Scoring: validate evidence before scoring; honest sensitivity stamp

Three guards from the PR #5 review, all closing the same failure
class — a contract that was documented but not enforced:

- validate_evidence() (review B3, B4): score.py now refuses evidence
  that omits a scored input the verdict gates read, or marks a
  structural criterion met without a citation. Both new v2 fields
  failed open: a missing baseline_as_used_seconds silently disarmed
  the no-conversion verdict, and stripping every citation left the
  score unchanged. as_used_runs must now be present; [] remains legal
  as an explicit single-run declaration. Both evidence files gain the
  key (score-neutral: same single-run path). The check is a separate
  pass over authored evidence, never inside a scorer, so score_all
  stays a pure function of evidence and the perturbation search never
  silently drops mutants that trip authoring checks.

- Honest perturbation count (review E5): tested increments only after
  a successful scoring call, and perturbations that raise are recorded
  in perturbations_skipped with the exception instead of being
  silently counted in the denominator the stamp is judged on.

- robust-at-floor (review C1, floor half): a verdict already in the
  bottom band cannot be perturbed downward, so zero deciding flips
  there is partly the band's geometry, not evidence strength.
  markov_asset now stamps robust-at-floor with the reason attached;
  SKILL.md carries the stamp verbatim into reports, so plain "robust"
  was asserting support the run never demonstrated.

Scorecards regenerated: totals, verdicts, and gates unchanged; the
diff is the new fields plus markov_asset's stamp wording. The rubric's
floor comment also stops restating the measured baselines (review E2)
and points at evidence.json as the value the gate reads.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Rubric: matches_under_x64 caps correctness on its own (review C3)

The x64-divergence cap required max_delta_shipped > 1e-8 as well, in
both score_correctness and the derived logic&design bug cap. That
conjunct made the guard structurally unable to fire when the shipped
float32 delta is small — which is exactly the "wrong economics masked
by low precision" case the framework's correctness section names as
the thing this dimension guards. A candidate with divergent logic and
a lucky 1e-12 shipped delta scored correctness 5 and total 3.25; it
now scores correctness 1, logic_design capped at 3, total 2.30 gated
to net regression.

The flag's semantics come from the repo's own usage: the worked
examples record TRUE for x64-noise residuals (~1e-14 to ~1e-11), so
FALSE asserts the economics genuinely differ — not a failure of
bitwise identity. Under that reading, agreement as shipped is luck,
not correctness, and the cap needs no second condition.

No committed scorecard changes: both worked examples have
max_delta_shipped > 1e-8, so they were already caught by the old
conjunct — verified byte-identical regeneration under both engines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Fixtures: synthetic evidence pinning the rubric v2 paths (review B1)

Neither worked example exercises the v2 headline features: both take
the single-run efficiency fallback (no as_used_runs), and markov_asset
hand-sets the correctness-bug flag, so the derived-cap path never runs
on committed data. The newest scoring behaviour was tested only by
hand-probing.

references/fixtures/rubric_v2 is a synthetic evidence file — not an
evaluation — whose one job is to make five untested paths execute on
every scoring run: the unconditional x64 cap, the derived bug cap
firing against a hand-set FALSE, the as_used_runs median, the
contested-band annotation (runs straddle the 1.3x edge), and a verdict
gate reporting the ungated total. The baseline sits above the 1 s
floor on purpose so no-conversion does not mask the paths under test;
every source string starts SYNTHETIC: so nobody cites the numbers as
evidence about a lecture. Kept out of references/examples/ because an
example records what was measured about a real PR and a fixture is a
test input — conflating them invites citing synthetic data.

Its own sensitivity line documents the stake: flipping
matches_under_x64 alone swings the outcome from gated net regression
to 4.70 clear improvement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* CI: scorecards must reproduce from evidence (review B2)

The system's central claim — no score is ever written by hand — was
verified only in reviewers' terminals. CI now regenerates all three
committed scorecards (both worked examples and the v2 fixture) and
fails on any diff, which catches a hand-edited scorecard, an
unintended scoring change, and — the important case — an intended
scoring change whose baselines were not regenerated. There the failure
is desirable: the fix is to re-run and commit, which forces the
verdict-moving diff into the PR where a reviewer sees it. Stdlib only,
so no install step.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Docs: single-source the baselines; state the new rubric behaviour

Review E2/E4 plus the doc side of the engine changes:

- The 0.028 s / 0.087 s vs 0.035 s / 0.18 s discrepancy was two honest
  measurements of the same quantity presented as one. The README table
  now labels its column triage-time (2026-07-21) and says the gate
  reads each lecture's own baseline_as_used_seconds; the framework and
  SKILL.md stop restating the numbers and point at evidence.json
  (review E2).
- README quotes markov_asset's verdict as the scorecard emits it —
  no-conversion with the banded quality alongside — instead of the
  stale "2.25 net regression" (review E4).
- Framework Sec. 1 states the unconditional x64 cap with its rationale,
  the three stamp values including robust-at-floor, and records the
  measured-vs-adjudicated conflation in the perturbation walk as a
  known limit for v3: read the deciding-flip list, not the stamp alone.
- SKILL.md step 5 forbids reporting robust-at-floor as robust;
  sanity anchors and the tutorial's quoted output line updated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Reviews: the PR #5 review of record, behind the filed item numbers

The 2026-07-25 external review of this PR, committed verbatim
alongside the repo's other review records so the item numbers cited
on #7 and #4 (C2, C4, C5, D, E1, E6, E7) resolve without the PR
comment. A labeled disposition note maps items to what was applied
(4fffbc9..8388e86) and where the remainder is tracked; the text is
otherwise as received. Every checkable claim in it was reproduced
against the branch before the fixes landed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Docs: loosen the skill-naming rule rather than replace it

"Verb-first skill names" reads as a rule about skills, but the thing it
was protecting is that the invocation scans as an imperative — and the
whole `/plugin:skill` string is what a user types. `/audit:issues`
satisfies that with the verb in the plugin, yet fails the rule as
written, so the rule had to move.

It moves by getting weaker, not by growing a taxonomy. Both shapes are
shown, neither is preferred, and the absence of a preference is stated
so a later contributor reads it as undecided rather than unspecified.
Ranking them now would mean generalising from three plugins and no
usage; the question is parked in FUTURE-IDEAS.md with the specific thing
worth watching — whether mixing shapes inside one plugin actually causes
trouble or merely looks untidy.

Depends on #11 for its example: `audit` must land before this does.

* Docs: catalog what shipped, and hold the conventions loosely

Two changes, one idea: this repo is three plugins old, and its docs were
describing more certainty than it has.

CATALOG.md becomes a list of what is merged and runnable, with each
plugin's state stated honestly (qe is scaffolding and says so) and a
tracking issue beside it. It was previously the active plan, which meant
it described skills nobody could run and drifted from the repo every
time work moved. Plans now live in the issues — #3, #4, and #12 for the
audit plugin — where they can change without anyone mistaking them for a
description of what exists. README.md drops its duplicate plugin table
and points here; the qe sub-skills point at #3 rather than at a
catalogue entry that no longer carries their plan.

The conventions are reframed as guidance. developing-skills.md now says
so at the top, says only SKILL.md is required — a skill with nothing
mechanical to run should be one file, not a directory tree — and marks
the three conventions that are genuinely load-bearing, each with its
reason. The rest is what one or two examples happened to need, and a
contributor with a reason to depart should depart and say so in the PR,
because a second example is how any of this becomes a real convention.

The test that separates the two: a rule earns firmness when it keeps a
skill's output checkable by someone who will not re-run it. Report-first,
cited claims, and the plugin-root constraint pass it. Report shapes,
phase divisions and naming forms do not.

* Docs: fold the audit plugin into the rebased docs

Post-#11 reconciliation. README gains audit in the plugin table and the
documentation index, and its layout note drops "bundle contract", which
the plugin no longer has.

The marketplace entry's move to the co-located `./audit` form is not
here — it belongs in "Install fix: co-located plugin sources", which is
the commit that established that form, and the rebase carried it there.

---------

Co-authored-by: Xuanguang Li <xuanguang-li@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Development

Successfully merging this pull request may close these issues.

3 participants