Benchmarking any2bazel

A report from the any2bazel lab · migrations of CMake projects to Bazel by Claude Opus 5, with the any2bazel skill and without it · written 2026-09-26 by the lab's supervising agent (Claude Opus 5.5) for Mark Schaller · every number on this page is rendered from the lab's run records by reports/v2/facts.py

This report uses a single strategy for interpreting the lab's records:

The alternative readings that the records also support are in appendix C.

Citations such as F47, P17, E23, A15 or V0886 point into the lab's numbered records: findings, proposals to the skill, errata, amendments and verification runs (appendix A).

1. Summary

The lab gives the same instruction ("port this CMake project to Bazel") to the same model in the same container twice: once with the any2bazel skill installed, once with it removed. It then measures both results against the project's own CMake build. It did this on 14 open-source projects, from graphics and numerical libraries of a few dozen sources to Blender and MySQL at several thousand.

Where the skill arm produced a build, it usually left fewer counted behavioral causes: decisions classed as changing what the built program does. This is H1, the study's main hypothesis, and it is supported. Of the 13 pairs it counts (yugabyte-db's is left out, because its skill arm declined; below), the skill arm has fewer behavioral causes on 7, more on 1, and the same number on 5. The one "more" is mysql-server, where the skill arm has no build to judge and the rule counts that as worse.

But the skill arm failed to produce a build more often. The no-skill arm's bazel build //... succeeded on all 14 projects. The skill arm's succeeded on 10. Of the rest:

So M0, "the skill arm makes an additive migration aiming at parity", is inconclusive. A migration counts as additive only if it adds a Bazel build whose bazel build //... succeeds and leaves CMake working: 10 of 14. In xbmc and mysql-server, the skill arm's failing targets include the project's main program (kodi, mysqld).

The skill arm was cheaper and faster, and often did less. It cost less on 13 of 14 pairs and finished sooner on 13.

These are single runs. Each project was migrated once per arm. The two projects run twice show how much a second run can differ (§7). On mysql-server, the spread between two runs of the same arm was larger than the difference between the arms. Read every per-project statement as "in this run".

The 14 pairs

with the skillwith the skillwithoutwithouthollow: no whole build · ×: no build to judgejansson34 sources · Slensfun40 sources · Spono94 sources · Sgraphicsgems308 sources · Mopenttd535 sources · Mbitcoin548 sources · Mcmake1,158 sources · Lgdal1,554 sources · Lxbmc1,871 sources · Lyugabyte-db2,137 sources · Lfreecad2,751 sources · Lqgis3,866 sources · XLmysql-server4,680 sources · XLblender5,101 sources · XLsource coverage0%50%100%jansson, without (run 053): 100%jansson, with the skill (run 052): 100%lensfun, without (run 046): 100%lensfun, with the skill (run 045): 100%pono, without (run 051): 93%pono, with the skill (run 050): 93%graphicsgems, without (run 044): 100%graphicsgems, with the skill (run 042): 100%openttd, without (run 056): 100%openttd, with the skill (run 055): 100%bitcoin, without (run 058): 89%bitcoin, with the skill (run 057): 89%cmake, without (run 074): 91%cmake, with the skill (run 073): 91%gdal, without (run 063): 94%gdal, with the skill (run 062): 93%xbmc, without (run 066): 96%xbmc, with the skill (run 065): 87%yugabyte-db, without (run 080): 85%×freecad, without (run 071): 26%freecad, with the skill (run 070): 8%qgis, without (run 068): 72%qgis, with the skill (run 067): 69%mysql-server, without (run 061): 9%×blender, without (run 078): 51%blender, with the skill (run 077): 51%reference test cases ported0%50%100%jansson, without (run 053): 100% (215 of 215 cases)jansson, with the skill (run 052): 100% (215 of 215 cases)lensfun, without (run 046): 100% (27 of 27 cases)lensfun, with the skill (run 045): 100% (27 of 27 cases)pono, without (run 051): 100% (416 of 416 cases)pono, with the skill (run 050): 100% (416 of 416 cases)openttd, without (run 056): 100% (95 of 95 cases)openttd, with the skill (run 055): 100% (95 of 95 cases)bitcoin, without (run 058): 79% (827 of 1,049 cases)bitcoin, with the skill (run 057): 79% (825 of 1,049 cases)cmake, without (run 074): 0% (0 of 726 cases)cmake, with the skill (run 073): 0% (0 of 726 cases)gdal, without (run 063): 99% (1,215 of 1,229 cases)xbmc, without (run 066): 100% (1,687 of 1,689 cases)yugabyte-db, without (run 080): 100% (573 of 574 cases)×freecad, without (run 071): 87% (1,257 of 1,440 cases)freecad, with the skill (run 070): 0% (0 of 1,440 cases)qgis, without (run 068): 40% (1,981 of 4,905 cases)qgis, with the skill (run 067): 0% (0 of 4,892 cases)mysql-server, without (run 061): 21% (2,350 of 11,385 cases)×blender, without (run 078): 0% (0 of 718 cases)blender, with the skill (run 077): 0% (0 of 718 cases)behavioral causes04812jansson, without (run 053): 0jansson, with the skill (run 052): 0lensfun, without (run 046): 1lensfun, with the skill (run 045): 0pono, without (run 051): 0pono, with the skill (run 050): 0graphicsgems, without (run 044): 1graphicsgems, with the skill (run 042): 0openttd, without (run 056): 2openttd, with the skill (run 055): 1bitcoin, without (run 058): 5bitcoin, with the skill (run 057): 2cmake, without (run 074): 3cmake, with the skill (run 073): 3gdal, without (run 063): 4gdal, with the skill (run 062): 4xbmc, without (run 066): 7xbmc, with the skill (run 065): 3yugabyte-db, without (run 080): 11×freecad, without (run 071): 8freecad, with the skill (run 070): 3qgis, without (run 068): 7qgis, with the skill (run 067): 7mysql-server, without (run 061): 7×blender, without (run 078): 10blender, with the skill (run 077): 7notional cost (log scale)$1$10$100jansson, without (run 053): $3.65jansson, with the skill (run 052): $3.27lensfun, without (run 046): $7.93lensfun, with the skill (run 045): $5.12pono, without (run 051): $10.81pono, with the skill (run 050): $9.82graphicsgems, without (run 044): $5.44graphicsgems, with the skill (run 042): $3.87openttd, without (run 056): $26.24openttd, with the skill (run 055): $8.70bitcoin, without (run 058): $13.87bitcoin, with the skill (run 057): $9.00cmake, without (run 074): $16.13cmake, with the skill (run 073): $6.69gdal, without (run 063): $23.35gdal, with the skill (run 062): $9.64xbmc, without (run 066): $33.64xbmc, with the skill (run 065): $11.85yugabyte-db, without (run 080): $42.23yugabyte-db, with the skill (run 079): $1.19freecad, without (run 071): $42.32freecad, with the skill (run 070): $18.51qgis, without (run 068): $41.59qgis, with the skill (run 067): $21.70mysql-server, without (run 061): $14.56mysql-server, with the skill (run 060): $23.01blender, without (run 078): $103.98blender, with the skill (run 077): $26.52time, minutes (log scale)31030100jansson, without (run 053): 9.3 minjansson, with the skill (run 052): 7.2 minlensfun, without (run 046): 17.8 minlensfun, with the skill (run 045): 11.1 minpono, without (run 051): 24.0 minpono, with the skill (run 050): 16.5 mingraphicsgems, without (run 044): 14.9 mingraphicsgems, with the skill (run 042): 10.5 minopenttd, without (run 056): 48.3 minopenttd, with the skill (run 055): 17.1 minbitcoin, without (run 058): 32.0 minbitcoin, with the skill (run 057): 24.1 mincmake, without (run 074): 31.1 mincmake, with the skill (run 073): 17.5 mingdal, without (run 063): 39.7 mingdal, with the skill (run 062): 24.1 minxbmc, without (run 066): 57.1 minxbmc, with the skill (run 065): 22.6 minyugabyte-db, without (run 080): 95.4 minyugabyte-db, with the skill (run 079): 2.7 minfreecad, without (run 071): 54.5 minfreecad, with the skill (run 070): 29.1 minqgis, without (run 068): 89.3 minqgis, with the skill (run 067): 63.6 minmysql-server, without (run 061): 22.4 minmysql-server, with the skill (run 060): 103.1 minblender, without (run 078): 194.7 minblender, with the skill (run 077): 67.3 min
Each row is a project, smallest first; each dot is one arm's run, joined to its pair. Coverage and tests ported are shares of the reference; behavioral causes are distinct decisions that change what the program does; cost is notional and time is launch to exit, both on log scales. A hollow dot is a run without a whole build: judged on the part that builds, or, for cost, a skill run that failed or declined; × marks a skill run with no build to judge. The tests panel is also blank where a run had no tests to match: a part-build, or a project without a suite. Hover a dot for its value; the table below gives them all.
projecttierreference sourcesskill armno-skill armH1: the skill arm has
buildcoveragebehavioral causescosttimebuildcoveragebehavioral causescosttime
graphicsgemsM308built100.0%0$3.8711 minbuilt100.0%1$5.4415 minfewer
lensfunS40built100.0%0$5.1211 minbuilt100.0%1$7.9318 minfewer
ponoS94built92.5%0$9.8216 minbuilt92.5%0$10.8124 minthe same number
janssonS34built100.0%0$3.277 minbuilt100.0%0$3.659 minthe same number
openttdM535built99.6%1$8.7017 minbuilt99.6%2$26.2448 minfewer
bitcoinM548built88.9%2$9.0024 minbuilt89.2%5$13.8732 minfewer
mysql-serverXL4,680failed——$23.01103 minbuilt9.4%7$14.5622 minmore
gdalL1,554partial †92.7%4$9.6424 minbuilt93.6%4$23.3540 minthe same number
xbmcL1,871partial †86.5%3$11.8523 minbuilt95.8%7$33.6457 minfewer
qgisXL3,866built69.2%7$21.7064 minbuilt72.2%7$41.5989 minthe same number
freecadL2,751built8.2%3$18.5129 minbuilt26.1%8$42.3255 minfewer
cmakeL1,158built90.9%3$6.6918 minbuilt90.8%3$16.1331 minthe same number
blenderXL5,101built51.0%7$26.5267 minbuilt51.0%10$103.98195 minfewer
yugabyte-dbL2,137declined——$1.193 minbuilt84.6%11$42.2395 minleft out (declined)

How to read the table.

2. What was tested, and by whom

The tool. any2bazel is a skill for Claude Code: instructions and scripts that tell a coding agent how to port a CMake project to Bazel and how to check the port. Its central idea is parity. The skill extracts two models, one of CMake's build (from CMake's File API) and one of Bazel's (from bazel aquery), and a differ compares them compile command by compile command. The agent iterates until the differ reports nothing, which the skill calls converged. Every run in this report, S01's and the pilot's included, used the skill at commit 210b25a (2026-09-11), Claude Code 2.1.270, and Claude Opus 5 as the migrating model.

The lab. It is a separate repository that runs migrations and measures them. A supervising agent chooses a project and builds the project's own CMake build as the reference. It then launches an isolated migrating agent, does not help it, and judges what the agent left. Mark Schaller, who consults for EngFlow, directs the lab; the supervising agent was Claude Opus 5 until 2026-09-22 and Claude Opus 5.5 since.

What "without the skill" means. The skill's description triggers on a request to port a CMake project, so leaving its name out of the prompt does not keep it out. In the no-skill arm, the skill is moved off the disk before the run, and a check after every run confirms the agent never saw it. So the two arms of a pair run one after the other, not at the same time.

Unattended. Every run is non-interactive: nobody answers the agent. The skill's first step asks the user to confirm the plan, and in this mode the question goes unanswered and the agent carries on (F2). Its later instruction to stop at constructs it does not support is likewise only as strong as the agent's reading of it.

Terms. The rest of the report uses these terms.

3. The design

3.1 The question

Does the skill change what an agent produces when asked to port a CMake project to Bazel? The counterfactual is the same agent, on the same project, without the skill. So every project is run in pairs: the same prompt (the no-skill prompt lacks only the line invoking the skill), the same model, the same commit, the same container image, and the same resource caps. The arms differ in two things: one prompt line, and whether the skill is on disk.

3.2 What is held constant

3.3 One reference, configured before either arm runs

Each project's reference is its own CMake build, configured the way the lab's target specification says. The specification is written and the reference built before either arm runs, and both arms are judged against the same one. An agent may build a CMake comparison of its own, and many did, with different options. That changes what the agent compares itself against, not what the lab compares it against. Whether the agent kept the reference's options is recorded (the pair tables' "kept the reference's CMake options" row; H4).

3.4 One measure for each question

questionmeasure
Did it build?bazel build //... in the migration's own tree, rebuilt by the lab. Where it failed, how many of the migration's declared targets build with --keep_going.
How much of the project did it port?Source coverage, against the reference's distinct compiled sources. Sources are matched by path, so a generated source the migration writes to a different path counts as missing: on blender, where both arms laid generated sources out differently, this undercounts both.
Does the migrated build do what the reference does?The migration's behavioral causes (§4).
Did it port the tests, and do they pass?The share of the reference's passing test cases that have a counterpart in the migration, and whether those pass.
What did it cost?Notional list-price cost of the tokens the run used, and elapsed time from launch to exit.

Two further checks cover what parity does not compare. The lab byte-compares the files the build generates. It also records the optimization level, which the skill's differ discards.

3.5 The hypotheses, and the rules that decide them

S02 was pre-registered: its targets, measures, hypotheses and decision rules were written and frozen before its first run. Its rules were later amended 16 times, each amendment dated and recorded (studies/S02-large/PREREGISTRATION.md). The rules below are the ones in force after all of them. Two, A15 and A16, were adopted after the analysis, one with its effect already known. Each corrected a measure that described migrations wrongly, and appendices B and C say what each moved.

With one run per arm per project, these are statements about patterns in this set of projects, not statistical tests.

4. How a migration is measured

4.1 Measured, not read

Every migrating agent ends with a report of what it did, and the reports are often accurate. But an accurate report can still describe a different job from the one the lab asked for. An agent that turned many of the project's features off in its own CMake comparison can truthfully report that it converged, with nothing left to fix, against that comparison (qgis's skill arm, §6). So the lab measures each migration against the reference itself. Its own copy of the skill's extractors and differ is pinned at the version under test and runs as a separate user. It does not use the agent's comparison, its settings, or its account of the result.

The measure is the skill's own instrument. That is deliberate: parity is what the skill promises, so it is what the lab checks. But it means the skill arm's agent iterated against this differ until it reported nothing, and the no-skill arm's agent never saw it. Three things limit what that asymmetry can do:

What the asymmetry can still do is favor the skill arm on exactly the differences the differ is good at seeing, and leave both arms unexamined where it is blind (§4.3). Source coverage, the tests, the generated-file comparison and the optimization-level record do not come from the differ, and on those the no-skill arm often did better (§6).

4.2 From differences to causes

The differ compares the two builds command by command: for each source the reference compiles, whether the migration compiles it, and with the same defines, include roots and flags; for each target, whether it exists and links the same way. The comparison is not symmetric. What the reference has and the migration lacks is an error. A flag only the migration adds is usually tolerated, and a define only the migration adds is a warning (§4.3).

Error counts are not used in this report. One decision can raise one error or thousands, depending only on how many units it touches. Dropping one define that every unit carries is one decision. The count also depends on how the two builds' paths are mapped onto each other: which directory, target or generated header on one side stands for which on the other. Under this report's strategy both maps may say only that, but their entries still come from different places. The skill arm's are its agent's own, and the no-skill arm's were written by the lab before either ran. So a directory one agent invented can be mapped in one arm and not in the other (F47).

So the lab groups the errors into causes. A fixed rule table names each cause from what its errors have in common, for example build-type, feature-defines or include-roots. A cause is a kind of decision: usually one decision, sometimes several of the same kind (cmake's no-skill include-roots gathers two declarations, F47). The report counts distinct causes.

Each cause is classed by its effect on the built program:

classmeaningexamples from these runs
behavioralThe built program does, or can do, something different from the reference's. assertions or the build type switched (build-type); a feature switch decided differently (feature-defines); code the reference compiles and the migration does not (tu-absent); hardening or visibility flags dropped (codegen-flags)
structuralThe same program, arranged differently. the same headers reached through differently spelled include roots (include-roots); a target's link dependencies declared differently (link-deps)
benignNo effect on the program. warning flags (warnings); the compiler driver's path (driver)

A few causes can only be classed by looking at the artifacts, so a check runs for each:

A check that cannot run leaves its cause behavioral, and it is counted. Absent sources are structural rather than behavioral when every one of them belongs to a dependency the reference compiles and the migration takes, at the same version, as an external module instead (A1; pono, §6.3). A source the differ can match to the reference only by its file name, because the paths differ, may be the same source moved or a different one, so it is not classed and not counted. The class describes what the difference does, not whether the agent could have avoided it.

The behavioral causes the tables name, with the rule table's own description of each. The differ discards -O and -g before comparing (§4.3), so where a description mentions optimization, the cause can record only what survives that: defines such as NDEBUG or QT_NO_DEBUG, and hardening, visibility, link-time or architecture flags.

causewhat differs
allocator-choicewhich memory allocator is compiled in
build-typeCMake build type (optimization and assertions)
codegen-flagsoptimization, hardening and ABI flags
configure-check-definesconfigure-check results (HAVE_*)
cxx-stdlibwhich C++ standard library is compiled against
exports-definesCMake's automatic <target>_EXPORTS / *_STATIC_DEFINE defines
feature-definesfeature defines
fortify-levelhardening level differs (_FORTIFY_SOURCE)
isa-flagsinstruction-set flags
library-configa vendored library's configuration macro that changes what it compiles
pieposition-independent code flags
platform-selectionplatform macros the sources branch on
pthread-pthread
rpathruntime library search path (-rpath)
stdlanguage standard flags
target-absenttargets not built
tu-absentsources not compiled at all
version-stampsa build date or stamp that gates code

Both arms are judged the same way, on the comparable reading:

4.3 What parity does not compare, and how the lab covers it

4.4 Tests, counted by case

The lab runs the reference's test suite and records which tests pass. Then it asks which of those have a counterpart in the migration. The unit is the test case, from each binary's own list of the cases it registers; a CMake test that runs a script, or a binary that lists no cases, counts as one case. On freecad, CMake registers every gtest case separately; on bitcoin, every Boost.Test suite; on mysql-server, one merged binary of many test files. Counting CMake's registrations instead of cases made ported tests invisible, and amendment A15 corrected that (F57). A case counts as ported in either of two ways:

It passes if its Bazel target passes. If the target failed, the case's own line in the output decides, for GoogleTest; other frameworks keep the target's result.

4.5 Cost and time

Cost is notional. It prices the tokens a run used at list price: input at $5.00 and output at $25.00 per million tokens, with cache writes at 1.25× (five-minute) or 2.0× (one-hour) the input price, and cache reads at 0.1×. Messages are de-duplicated by id before summing. Time is wall-clock from launch to exit. Both measure the whole run: an agent that did more of the project spent more on it. So a cost comparison between the arms of a pair compares two different jobs, unless their coverage says they covered the same reference sources; even then one arm may have done work coverage does not count, such as tests or tools the reference does not build.

5. The verdicts

hypothesisresultverdict
M0the skill arm makes an additive migration aiming at parity10 of 14 skill runs additive (71%)inconclusive
H1the skill arm leaves fewer distinct behavioral causesof 13 pairs: fewer 7, more 1, same 5supported
H2at L and above, the no-skill arm wires up the project's own test harness1 eligible run, not wiredrefuted
H3in checkable runs, the Bazel build passes the tests the reference passestrue in 5 of 5 skill runs, 8 of 9 no-skillsupported
H4changing the reference's CMake options grows with sizeXL minus M+L: skill +0.38, no-skill -0.19inconclusive
H5cost grows with sizeSpearman ρ: skill 0.714, no-skill 0.820supported

5.1 M0: the skill arm does not reliably produce a migration

10 of 14 skill runs are additive, 71%, between the refutation line (50%) and the support line (80%). The 4 that are not are the runs without a whole build (§1): gdal, xbmc, mysql-server and yugabyte-db. The no-skill arm built all 14. M0 asks about the skill arm alone, so that contrast is not part of its rule, but it is the study's most unfavorable result for the skill (F49).

5.2 H1: fewer behavioral causes where the skill arm built

supported. Of the 8 pairs that are not tied, the skill arm has fewer behavioral causes in 7 (87.5%; the rule needs 70%) and more in 1 (12.5%; the rule allows 15%). The pair where it has more is mysql-server. Its skill arm has no build to judge, and a run without one counts as worse than any number.

projectskill arm's behavioral causesno-skill arm'sthe skill arm has
graphicsgemsnonecodegen-flagsfewer
lensfunnonecodegen-flagsfewer
ponononenonethe same number
janssonnonenonethe same number
openttdtu-absentbuild-type, tu-absentfewer
bitcointarget-absent, tu-absentcodegen-flags, feature-defines, library-config, target-absent, tu-absentfewer
mysql-serverno build to judge (counts as worse than any number)codegen-flags, configure-check-defines, feature-defines, fortify-level, library-config, target-absent, tu-absentmore
gdalbuild-type, codegen-flags, target-absent, tu-absent †codegen-flags, configure-check-defines, target-absent, tu-absentthe same number
xbmcconfigure-check-defines, target-absent, tu-absent †build-type, configure-check-defines, feature-defines, library-config, platform-selection, target-absent, tu-absentfewer
qgisbuild-type, exports-defines, feature-defines, platform-selection, std, target-absent, tu-absentbuild-type, codegen-flags, feature-defines, platform-selection, std, target-absent, tu-absentthe same number
freecadbuild-type, target-absent, tu-absentbuild-type, codegen-flags, configure-check-defines, exports-defines, feature-defines, library-config, target-absent, tu-absentfewer
cmakebuild-type, target-absent, tu-absentstd, target-absent, tu-absentthe same number
blenderbuild-type, configure-check-defines, feature-defines, library-config, target-absent, tu-absent, version-stampsbuild-type, codegen-flags, configure-check-defines, feature-defines, library-config, pie, platform-selection, target-absent, tu-absent, version-stampsfewer

The table has two limits. First, the counts are of distinct causes, so a pair's comparison says nothing about how widely each cause spread. Second, a pair can tie while the causes differ, as qgis does.

Under the rule as first frozen, H1 is inconclusive. That rule counts every run without a whole build as worse, gdal's and xbmc's skill arms included, and keeps yugabyte-db's pair. It gives 6 fewer, 4 more and 4 tied. The pre-registration requires this reading to be reported beside the one in force. Amendments A12 and A13 are why the two differ (appendix C).

A16 moved 2 pairs. On gdal and cmake, the skill arm's migration switched assertions off in a way the parity measure does not record. A16 counts it as the build-type cause it is, and those pairs become ties. Before A16 the tally was 9 fewer, 1 more and 3 tied, also supported (appendix B).

5.3 H2: refuted, on the one run it applies to

Of the L and XL projects, only mysql-server's specification names a test driver other than ctest (mysql-test-run.pl). Its no-skill run neither wired that driver up nor ran it: neither its patch nor its transcript mentions it. H2 is refuted on 1 run, which says less than its label suggests.

5.4 H3: the tests that were ported pass

supported. All 5 of the skill arm's checkable runs are true, and 8 of 9 of the no-skill arm's. The false one is yugabyte-db's no-skill arm: its servers abort at startup, and 68.6% of its matched cases fail (§6.14; its tests run through a wrapper script, so each counts as one case). The skill arm's checkable runs are lensfun, pono, jansson, openttd and bitcoin. The skill arm ported none of the reference's test cases on any L or XL project where it produced a build. So H3's skill-arm half is a statement about small and medium projects.

5.5 H4: the arms point opposite ways

inconclusive. The skill arm changed the reference's CMake options in 2 of 2 XL runs and 5 of 8 at M and L. The no-skill arm changed them in 2 of 3 XL runs and 6 of 7 at M and L. The measure reads the agent's own CMake configuration, which says what it compared itself against, not how much of the project its Bazel build covers. The measure reads the latest CMake cache configured from the agent's source tree, in the workspace, beside it, or in the container's temporary directory. 2 runs left none and are left out: runs 060 and 071, mysql-server's skill arm and freecad's no-skill arm. yugabyte-db's skill arm, which configured CMake and then declined, counts as having kept the options. Source coverage (§6) is the better guide to how much each migration did.

5.6 H5: cost rises with size, in both arms

supported. Spearman's ρ between notional cost and the reference's distinct compiled sources is 0.714 in the skill arm and 0.820 in the no-skill arm, across all 14 pairs.

with the skillwith the skillwithoutwithout301003001,0003,000$1$3$10$30$100size: the reference's distinct compiled sources (log scale)notional cost (log scale)graphicsgems, without (run 044): $5.44, 308 sourcesgraphicsgems, with the skill (run 042): $3.87, 308 sourceslensfun, without (run 046): $7.93, 40 sourceslensfun, with the skill (run 045): $5.12, 40 sourcespono, without (run 051): $10.81, 94 sourcespono, with the skill (run 050): $9.82, 94 sourcesjansson, without (run 053): $3.65, 34 sourcesjansson, with the skill (run 052): $3.27, 34 sourcesopenttd, without (run 056): $26.24, 535 sourcesopenttd, with the skill (run 055): $8.70, 535 sourcesbitcoin, without (run 058): $13.87, 548 sourcesbitcoin, with the skill (run 057): $9.00, 548 sourcesmysql-server, without (run 061): $14.56, 4,680 sourcesmysql-server, with the skill (run 060): $23.01, 4,680 sourcesgdal, without (run 063): $23.35, 1,554 sourcesgdal, with the skill (run 062): $9.64, 1,554 sourcesxbmc, without (run 066): $33.64, 1,871 sourcesxbmc, with the skill (run 065): $11.85, 1,871 sourcesqgis, without (run 068): $41.59, 3,866 sourcesqgis, with the skill (run 067): $21.70, 3,866 sourcesfreecad, without (run 071): $42.32, 2,751 sourcesfreecad, with the skill (run 070): $18.51, 2,751 sourcescmake, without (run 074): $16.13, 1,158 sourcescmake, with the skill (run 073): $6.69, 1,158 sourcesblender, without (run 078): $103.98, 5,101 sourcesblender, with the skill (run 077): $26.52, 5,101 sourcesyugabyte-db, without (run 080): $42.23, 2,137 sourcesyugabyte-db, with the skill (run 079): $1.19, 2,137 sourcesgraphicsgemslensfunponojanssonopenttdbitcoinmysql-servergdalxbmcqgisfreecadcmakeblenderyugabyte-db
Each pair's two runs, at the size of the project, joined by a line. The skill arm's run is the cheaper one on every pair but mysql-server. Hollow: a skill run without a whole build, as in the figure in §1. Where two projects are close in size, as openttd and bitcoin are, their dots can overlap; hover separates them.

6. The fourteen pairs

One paragraph and one table per project. Each is written from the pair's two run records (runs/NNN-*/RESULTS.md) and cause tables; the tables are rendered from them. In the tables, "reference test cases ported" counts the reference's passing test cases that have a counterpart in the migration, and "pass" means at least 99% of those pass. A dash means the measure does not apply: no build, no test suite, or no generated files the lab could pair. A run judged on the part that builds (†) has source coverage and causes, which the lab can extract from the targets that build. It has no test or generated-file figures, because the lab's test and generated-file steps run on a whole build. Each arm is judged against its own run of the reference's test suite, so a flaky reference test can make the two arms' denominators differ slightly.

6.1 graphicsgems (M, 308 reference sources)

The happy path for the skill. Its migration compiles every reference source with no behavioral cause, and its agent kept the reference's CMake options. The no-skill arm's run replaced CMake rather than adding to it. It dropped -Werror, compiled with -w, and carries one behavioral cause, a code-generation flag (-fcommon). The project has no test suite.

skill armno-skill arm
run042044
migration kindadditivereplacement
buildbuiltbuilt
source coverage100.0% (308 of 308)100.0% (308 of 308)
behavioral causesnonecodegen-flags
structural causes01
benign causes04
reference test cases ported——
generated files identical——
optimization level (reference → migration)none (both)none (both)
kept the reference's CMake optionsyesno
notional cost$3.87$5.44
time11 min15 min

6.2 lensfun (S, 40 reference sources)

Both arms compile every reference source and pass every reference test case. The skill's documentation lists lensfun's custom command as a place to stop, and the skill arm migrated it anyway, as a genrule. The no-skill arm's one behavioral cause is OpenMP applied to test binaries where the reference does not apply it.

skill armno-skill arm
run045046
migration kindadditiveadditive
buildbuiltbuilt
source coverage100.0% (40 of 40)100.0% (40 of 40)
behavioral causesnonecodegen-flags
structural causes33
benign causes23
reference test cases ported27 of 27, pass27 of 27, pass
generated files identical2 of 22 of 2
optimization level (reference → migration)none (both)none (both)
kept the reference's CMake optionsnono
notional cost$5.12$7.93
time11 min18 min

6.3 pono (S, 94 reference sources)

The closest pair in the study. Both arms leave out the same dependency sources, which pono pins as external modules (a structural cause). Both pass every reference test case. Both compile at -O2 where the reference uses -O3, because both mapped CMake's Release to Bazel's opt (P18). Neither has a behavioral cause.

skill armno-skill arm
run050051
migration kindadditiveadditive
buildbuiltbuilt
source coverage92.5% (87 of 94)92.5% (87 of 94)
behavioral causesnonenone
structural causes69
benign causes66
reference test cases ported416 of 416, pass416 of 416, pass
generated files identical4 of 54 of 5
optimization level (reference → migration)-O3 → -O2-O3 → -O2
kept the reference's CMake optionsyesyes
notional cost$9.82$10.81
time16 min24 min

6.4 jansson (S, 34 reference sources)

The cleanest target. The skill arm's migration leaves no cause of any class, and every reference test case passes. The no-skill arm's also passes every case. Its remaining causes are structural or benign: it expresses jansson's configuration mechanism differently, and its linker options differ. None of its 2 generated headers is byte-identical to the reference's.

skill armno-skill arm
run052053
migration kindadditiveadditive
buildbuiltbuilt
source coverage100.0% (34 of 34)100.0% (34 of 34)
behavioral causesnonenone
structural causes03
benign causes02
reference test cases ported215 of 215, pass215 of 215, pass
generated files identical2 of 20 of 2
optimization level (reference → migration)none (both)none (both)
kept the reference's CMake optionsyesno
notional cost$3.27$3.65
time7 min9 min

6.5 openttd (M, 535 reference sources)

Both arms build, pass every reference test case, and carry one behavioral cause in common. That cause is the stub CMake generates to compile a precompiled header, which neither migration compiles. The no-skill arm adds a second: it configured its own CMake comparison as Release, and its Bazel build compiles every unit at -O2 with assertions off, where the reference sets neither. Both arms handled a hazard the action graph cannot show. OpenTTD registers drivers through static constructors that nothing references, so an archive lets the linker drop them. The skill arm's agent found this when its converged binary failed at startup, and added alwayslink. The no-skill arm's agent added it while wiring up the build. Both binaries also report no version, because neither build hands the version generator git information (F32). The no-skill run cost 3.0× as much and took 2.8× as long, for the same coverage.

skill armno-skill arm
run055056
migration kindadditiveadditive
buildbuiltbuilt
source coverage99.6% (533 of 535)99.6% (533 of 535)
behavioral causestu-absentbuild-type, tu-absent
structural causes26
benign causes44
reference test cases ported95 of 95, pass95 of 95, pass
generated files identical227 of 228227 of 228
optimization level (reference → migration)none (both)none → -O2
kept the reference's CMake optionsyesno
notional cost$8.70$26.24
time17 min48 min

6.6 bitcoin (M, 548 reference sources)

Both arms leave out nearly the same reference sources. The no-skill arm compiled more of the project than its coverage shows, a benchmark suite the reference does not build. The skill arm's defines match the reference on every unit (V2). The no-skill arm has 3 more behavioral causes:

Both arms ported the same test binary, and its cases match 825 of 1,049 and 827 of 1,049 of the reference's cases, all passing. Neither ported secp256k1's or the IPC tests. Both compile with no optimization level where the reference uses -O2. Most of the skill arm's generated files differ from the reference's in bytes, by layout and by a -dirty suffix that its version script adds because the migrated checkout is modified. Most of the no-skill arm's are identical, and the rest differ only in layout (F33).

skill armno-skill arm
run057058
migration kindadditiveadditive
buildbuiltbuilt
source coverage88.9% (487 of 548)89.2% (489 of 548)
behavioral causestarget-absent, tu-absentcodegen-flags, feature-defines, library-config, target-absent, tu-absent
structural causes38
benign causes47
reference test cases ported825 of 1,049, pass827 of 1,049, pass
generated files identical1 of 1456 of 67
optimization level (reference → migration)-O2 → none-O2 → none
kept the reference's CMake optionsnono
notional cost$9.00$13.87
time24 min32 min

6.7 mysql-server (XL, 4,680 reference sources)

The skill arm's migration cannot be rebuilt from its own repository. Its agent said so itself: code generation was left to CMake, and the Bazel build consumes CMake's output directory as sources. That directory is not in the migration's repository, and holds links to CMake-built libraries that exist only after a CMake build. A rebuild from the migration's bundle fails to link (F35). In the lab's rebuild of the agent's own workspace, restored with CMake's output still in place, 581 of 585 declared targets build (the table's figure), and mysqld is among those that do not. This is the one pair where the skill arm was the slower and the more expensive: 103 min and $23.01, against 22 min and $14.56.

The no-skill arm wrote its own Starlark configure_file and migrated 9.4% of the reference's sources. Its other behavioral causes are real:

It ported 2,350 of 11,385 of the reference's test cases, all passing: too few to be checkable, far more than none.

skill armno-skill arm
run060061
migration kindnone (not rebuildable)additive
buildfailed (581 of 585 declared targets)built
source coverage—9.4% (440 of 4,680)
behavioral causes—codegen-flags, configure-check-defines, feature-defines, fortify-level, library-config, target-absent, tu-absent
structural causes—10
benign causes—9
reference test cases ported—2,350 of 11,385
generated files identical—15 of 15
optimization level (reference → migration)—-O2 (both)
kept the reference's CMake optionsno CMake cache foundyes
notional cost$23.01$14.56
time103 min22 min

6.8 gdal (L, 1,554 reference sources)

The skill arm's whole build stops on one fuzz harness, where a driver-registration function is not declared in scope. With --keep_going, 280 of 283 of its declared targets build. Judged on that part (†), it compiles 92.7% of the reference's sources, against the no-skill arm's 93.6%. The two carry the same number of behavioral causes. One of the skill arm's is build-type: its part-build compiles every unit with -DNDEBUG, which the parity measure does not record, and amendment A16 counts (appendix B). Its agent also configured its own comparison with tests off and a Release build type (P13). The no-skill arm checked in a frozen cpl_config.h, the result of CMake's probe on the agent's machine. The configure check finds it differs from the reference's. The file carries a note to its maintainers to mirror any new feature check by hand (F39). It ported 1,215 of 1,229 of the reference's test cases, all passing.

skill armno-skill arm
run062063
migration kindnone (the whole build fails)additive
buildpartial † (280 of 283 declared targets)built
source coverage92.7% (1,440 of 1,554)93.6% (1,455 of 1,554)
behavioral causesbuild-type, codegen-flags, target-absent, tu-absentcodegen-flags, configure-check-defines, target-absent, tu-absent
structural causes57
benign causes28
reference test cases ported—1,215 of 1,229, pass
generated files identical—58 of 61
optimization level (reference → migration)none (both)none (both)
kept the reference's CMake optionsnono
notional cost$9.64$23.35
time24 min40 min

6.9 xbmc (L, 1,871 reference sources)

Same image, opposite strategies, and the pair shows which one was a choice. Kodi's CMake downloads and builds FFmpeg, crossguid and libdvdnav for itself; the image does not carry them at the versions Kodi needs.

Judged on the part that builds, the skill arm covers 86.5% against 95.8%. It carries 3 behavioral causes to its pair's 7. Among the no-skill arm's is NDEBUG dropped, so its assertions are live where the reference compiles them out. The no-skill arm ported the suite as one test binary, and 1,687 of 1,689 reference cases match and pass. The skill arm's agent also ran a negative control on its own parity claim, unprompted: it planted differences, saw the differ report them, and restored them.

skill armno-skill arm
run065066
migration kindnone (the whole build fails)additive
buildpartial † (261 of 269 declared targets)built
source coverage86.5% (1,619 of 1,871)95.8% (1,793 of 1,871)
behavioral causesconfigure-check-defines, target-absent, tu-absentbuild-type, configure-check-defines, feature-defines, library-config, platform-selection, target-absent, tu-absent
structural causes29
benign causes38
reference test cases ported—1,687 of 1,689, pass
generated files identical—14 of 15
optimization level (reference → migration)-O3 → none-O3 → none
kept the reference's CMake optionsnono
notional cost$11.85$33.64
time23 min57 min

6.10 qgis (XL, 3,866 reference sources)

The skill arm's parity is a claim about a job it defined. It configured its own comparison with Python, 3D, WebKit, the tests and more turned off, and converged against that. Against the lab's reference, the cut is visible as absent sources and targets, feature defines decided differently, and the other causes in the table. The no-skill arm attempted more of the project: the core, analysis, GUI, 3D, server and application libraries, provider modules, and 1,981 of 4,905 of the reference's test cases, all passing (the no-skill arm's run of the reference suite passed one network test binary that the skill arm's did not, and that binary registers the 13 cases by which the denominators differ). It covers 72.2% against the skill arm's 69.2%. The two arms tie at 7 behavioral causes, but not the same ones. Both have a build-type cause, for different reasons: the skill arm added NDEBUG, and the no-skill arm dropped Qt's QT_NO_DEBUG, which turns Qt's own assertions on. The added NDEBUG is recorded here, unlike on gdal and cmake, because the same units already differ in other defines, and a migration-only define rides along into that cause (appendix B). Export macros that sources read are the skill arm's alone. -fvisibility=hidden dropped is the no-skill arm's alone. The skill arm's agent also found the differ's hash-order nondeterminism independently, and fixed its cause (P14).

skill armno-skill arm
run067068
migration kindadditiveadditive
buildbuiltbuilt
source coverage69.2% (2,676 of 3,866)72.2% (2,790 of 3,866)
behavioral causesbuild-type, exports-defines, feature-defines, platform-selection, std, target-absent, tu-absentbuild-type, codegen-flags, feature-defines, platform-selection, std, target-absent, tu-absent
structural causes78
benign causes67
reference test cases ported0 of 4,8921,981 of 4,905
generated files identical600 of 2,246564 of 2,073
optimization level (reference → migration)none (both)none (both)
kept the reference's CMake optionsnono
notional cost$21.70$41.59
time64 min89 min

6.11 freecad (L, 2,751 reference sources)

Both arms narrowed the job. The skill arm migrated FreeCAD's non-GUI core, 8.2% of the reference's sources, and named what it left out and why rather than dropping it silently. OpenCASCADE has no Bazel packaging, and PySide/shiboken, which generates the GUI bindings, has no rule. It compiles at -O3 where the reference sets no level, which parity does not see. Its one behavioral cause beyond the scope cut is build-type: it drops Qt's QT_NO_DEBUG, so Qt's assertions are on where the reference has them off. The no-skill arm went further, 26.1%, and by its own account produced an install tree that does real CAD work. It ported 1,257 of 1,440 of the reference's test cases, all passing. It carries more behavioral causes, including link-time optimization dropped and configure checks that differ. It cost 2.3× as much, for 3.2× the coverage.

skill armno-skill arm
run070071
migration kindadditiveadditive
buildbuiltbuilt
source coverage8.2% (226 of 2,751)26.1% (717 of 2,751)
behavioral causesbuild-type, target-absent, tu-absentbuild-type, codegen-flags, configure-check-defines, exports-defines, feature-defines, library-config, target-absent, tu-absent
structural causes68
benign causes37
reference test cases ported0 of 1,4401,257 of 1,440, pass
generated files identical104 of 112328 of 338
optimization level (reference → migration)none → -O3none (both)
kept the reference's CMake optionsnono CMake cache found
notional cost$18.51$42.32
time29 min55 min

6.12 cmake (L, 1,158 reference sources)

The same job in both arms. Coverage is 90.9% and 90.8%, and the two arms share two behavioral causes, absent targets and absent sources, and each adds one. The no-skill arm's: a few of CMake's C sources compile at -std=gnu11 where the reference uses -std=c11. The skill arm's is one the parity measure does not record: it pins -c opt, so every unit gets -O2 and -DNDEBUG where the reference sets neither. Amendment A16 counts that as build-type, and the pair ties (appendix B). The configure step was done two ways:

Neither arm ported CMake's test suite, which is itself made of CMake projects. The skill arm was faster and cheaper here, and in the replicate (§7).

skill armno-skill arm
run073074
migration kindadditiveadditive
buildbuiltbuilt
source coverage90.9% (1,053 of 1,158)90.8% (1,052 of 1,158)
behavioral causesbuild-type, target-absent, tu-absentstd, target-absent, tu-absent
structural causes33
benign causes34
reference test cases ported0 of 7260 of 726
generated files identical28 of 28132 of 162
optimization level (reference → migration)none → -O2none (both)
kept the reference's CMake optionsnono
notional cost$6.69$16.13
time18 min31 min

6.13 blender (XL, 5,101 reference sources)

Both arms made the same scope decision, independently. Each built Blender's blender_lite preset rather than the default configuration, and they cover nearly the same share of the reference: 2,601 of 5,101 and 2,604 of 5,101 sources. Most of their behavioral causes follow from that preset: features off, their sources uncompiled, and assertions kept on in a release build, which the preset asks for.

The no-skill arm adds 3:

The reference builds with Blender's C++ unit tests off, so its passing tests are Python and whole-program tests. Neither arm ported any of those. The no-skill arm ported Blender's gtest suites, which the reference does not run, so there is nothing to match them against. The skill arm's agent found two things about the skill itself. The differ's verdict on its own final models changed with Python's hash seed, and it wrote a deterministic wrapper (P14). Its diff had converged while Blender's Python modules failed to load, for want of an export flag the parity model does not read (P25). The skill arm cost $26.52 and the no-skill arm $103.98.

skill armno-skill arm
run077078
migration kindadditiveadditive
buildbuiltbuilt
source coverage51.0% (2,601 of 5,101)51.0% (2,604 of 5,101)
behavioral causesbuild-type, configure-check-defines, feature-defines, library-config, target-absent, tu-absent, version-stampsbuild-type, codegen-flags, configure-check-defines, feature-defines, library-config, pie, platform-selection, target-absent, tu-absent, version-stamps
structural causes88
benign causes67
reference test cases ported0 of 7180 of 718
generated files identical2,609 of 2,6642,614 of 2,664
optimization level (reference → migration)-O3 (both)-O3 (both)
kept the reference's CMake optionsnono
notional cost$26.52$103.98
time67 min195 min

6.14 yugabyte-db (L, 2,137 reference sources)

The skill arm declined, and was right. After 162 s and $1.19, its agent stopped with a report instead of a migration. It named the blockers, the hardest a PostgreSQL fork built by autotools outside the CMake model, which the servers link; the lab confirmed the reference model compiles nothing under src/postgres. The no-skill arm did the migration, and broke exactly there:

The skill arm's report was an accurate forecast, at a small fraction of the cost, and it left nothing a user could mistake for a finished migration. The skill's instruction to stop at unsupported constructs was passed over in every earlier run it applied to (F12). The project ships its own coding-agent instructions, and they reached both arms. Their directive against running CMake directly did not prompt the decline: the skill arm never read the file it is in, and the no-skill arm read it and went ahead. Whether the rest of that configuration had any influence is not known (F53). The pair is left out of H1.

skill armno-skill arm
run079080
migration kindnone (declined)additive
builddeclinedbuilt
source coverage—84.6% (1,807 of 2,137)
behavioral causes—allocator-choice, build-type, codegen-flags, configure-check-defines, cxx-stdlib, feature-defines, isa-flags, pthread, rpath, target-absent, tu-absent
structural causes—8
benign causes—7
reference test cases ported—573 of 574, fail (31.4% pass)
generated files identical—388 of 388
optimization level (reference → migration)—-O3 → none
kept the reference's CMake optionsyesyes
notional cost$1.19$42.23
time3 min95 min

7. Repeated runs: how much one run can say

Every result in §5 and §6 rests on one run per arm per project. Two projects were run twice per arm, and S01 ran four small projects three times per arm. Both show how far a second run can move.

7.1 cmake, run twice under identical conditions

The pair was run again with the same commit, image, caps and byte-identical prompts (runs 075 and 076; A5).

skill armno-skill arm
run073075074076
buildbuiltbuiltbuiltbuilt
source coverage90.9%90.9%90.8%90.7%
behavioral causesbuild-type, target-absent, tu-absentbuild-type, target-absent, tu-absentstd, target-absent, tu-absentbuild-type, target-absent, tu-absent
notional cost$6.69$5.91$16.13$7.68
time17.5 min15.8 min31.1 min20.7 min

What held. All four runs build, and coverage stayed within a few sources. Both runs of the skill arm have the same behavioral causes. The skill arm was faster and cheaper both times.

What moved.

No test executes those headers, and the differ never reads them. The difference is on the record only because the lab byte-compares generated files, and between the two skill runs only because the replicate exists.

7.2 mysql-server, run twice, and the runs disagreed more than the arms

A pilot pair ran at the same commit, in the same image, before S02's rules were final (runs 038 and 039; a third pilot run, 037, is not counted: its session ended mid-migration when its agent backgrounded a build and ended its turn, F27). The pilot was judged before the comparable reading existed, and its tree cannot be rebuilt, because the registry snapshot it resolved against is gone. So it is compared only on measures that do not depend on the reading.

with the skillwith the skillwithoutwithoutwith the skill, pilot (038)with the skill, study run (060)without, pilot (039)without, study run (061)source coverage0%50%100%mysql-server, pilot (038): 74%×no rebuildable buildmysql-server, pilot (039): 99%mysql-server, study run (061): 9%minutes (log scale)1030100300mysql-server, pilot (038): 98mysql-server, study run (060): 103mysql-server, pilot (039): 313mysql-server, study run (061): 22notional cost (log scale)$5$10$30$100mysql-server, pilot (038): $83mysql-server, study run (060): $23mysql-server, pilot (039): $73mysql-server, study run (061): $15
The pilot and the study run of each arm on mysql-server, at the same commit and in the same image. Coverage, time and cost for each run; time and cost on log scales.
skill armno-skill arm
run060038061039
buildfailedbuiltbuiltbuilt
source coverage—73.8%9.4%98.8%
notional cost$23.01$82.53$14.56$73.42
time103.1 min98.2 min22.4 min313.1 min

The skill arm built at 73.8% coverage in the pilot, and failed its whole build in the study run. The no-skill arm's two runs differ 10.5× in coverage, 14.0× in time and 5.0× in cost. Those spreads within an arm are larger than the difference between the arms on the same project (F41). On this project, how much an agent decided to finish varied more than anything the protocol controls. The pilot's registry snapshot can no longer be recovered, so module resolution may also have differed between the runs, and the spread may not be purely the agent's. No XL statement in this report should be read as more than one draw.

7.3 S01: four small projects, three runs per arm

S01 was the lab's first study. It ran graphicsgems, lensfun, pono and jansson three times per arm on the host, before the lab moved runs into containers. Its runs were first judged under S01's own rules. For this report they were re-judged under S02's comparable reading from their stored models, with every tree rebuilt so the checks could run (appendix C). Run on S02's own runs, which were judged this way from the start, the same procedure reproduces their error counts and cause lists exactly (V0880).

with the skillwith the skillwithoutwithout$1$2$5$10$20$50notional cost per run (log scale)graphicsgemsgraphicsgems, with the skill (run 011): $1.88, 0 behavioral causesgraphicsgems, with the skill (run 023): $2.31, 0 behavioral causesgraphicsgems, with the skill (run 027): $2.17, 0 behavioral causesgraphicsgems, without (run 015): $3.20, 0 behavioral causesgraphicsgems, without (run 019): $2.46, 0 behavioral causesgraphicsgems, without (run 031): $2.27, 0 behavioral causeslensfunlensfun, with the skill (run 012): $3.37, 0 behavioral causeslensfun, with the skill (run 024): $3.56, 0 behavioral causeslensfun, with the skill (run 028): $3.31, 0 behavioral causeslensfun, without (run 016): $3.24, 1 behavioral causelensfun, without (run 020): $3.08, 1 behavioral causelensfun, without (run 032): $3.48, 1 behavioral causejanssonjansson, with the skill (run 014): $1.72, 0 behavioral causesjansson, with the skill (run 026): $1.74, 0 behavioral causesjansson, with the skill (run 030): $2.01, 0 behavioral causesjansson, without (run 018): $2.70, 0 behavioral causesjansson, without (run 022): $2.19, 0 behavioral causesjansson, without (run 034): $1.53, 0 behavioral causesponopono, with the skill (run 013): $5.03, 0 behavioral causespono, with the skill (run 025): $4.99, 2 behavioral causespono, with the skill (run 029): $4.29, 0 behavioral causespono, without (run 017): $51.97, 3 behavioral causespono, without (run 021): $17.46, 1 behavioral causepono, without (run 033): $8.55, 0 behavioral causes
Each dot is one S01 run; the line spans an arm's cheapest to most expensive run. Log scale. Hover a dot for its cost and number of behavioral causes.
projectskill arm, three runsno-skill arm, three runs
behavioral causes, by runcosttimebehavioral causes, by runcosttime
graphicsgems011: none; 023: none; 027: none$1.88–$2.31 (1.23×)4 min–6 min015: none; 019: none; 031: none$2.27–$3.20 (1.41×)7 min–12 min
lensfun012: none; 024: none; 028: none$3.31–$3.56 (1.08×)7 min–8 min016: codegen-flags; 020: codegen-flags; 032: codegen-flags$3.08–$3.48 (1.13×)7 min–9 min
pono013: none; 025: build-type, tu-absent; 029: none$4.29–$5.03 (1.17×)9 min–10 min017: pie, target-absent, tu-absent; 021: tu-absent; 033: none$8.55–$51.97 (6.08×)17 min–81 min
jansson014: none; 026: none; 030: none$1.72–$2.01 (1.17×)3 min–4 min018: none; 022: none; 034: none$1.53–$2.70 (1.76×)4 min–7 min

The skill arm was the more repeatable.

Three runs per cell (one project in one arm), on the host rather than in containers, is still not a replication of S02's conditions. S02's phase A re-ran the same four projects under its own conditions, and those are pairs in §6.

8. What the record says the skill should change

Each proposal below cites the findings behind it. The full text, with its evidence, is in SKILL-FEEDBACK.md. The lab has also implemented them, one proposal per commit, on a local branch of the any2bazel repository (lab/skill-feedback, not pushed), which no run in this report used. Re-run on the lab's stored models, it gives the same error counts, kind by kind, as the version tested; the NDEBUG change reports migration-only defines separately rather than changing those counts. Its handling of defines is also checked against the compiler's own preprocessor.

8.1 "Converged" means less than it reads

An agent that converges has matched the reference's compile commands, as the differ sees them. Several things fall outside that, and the skill does not say so where the agent reads the result:

proposalwhatevidence
P1State the guarantee's boundary in the skill's own instructions.F1
P16The differ discards -O and -g; compare them, or report them.F30; pono, bitcoin, openttd, freecad, cmake
P17Flag and define comparison is one-directional; a define only the migration adds, such as NDEBUG, should be an error.F31, F56; appendix B
P25, P8Link-interface flags and shared-library link lines are outside the model; say so, and ask for a run of the binary.F14; blender (§6.13)
P2, P19Warn when configured files exist, and when a git-derived version stamp will be lost.F1, F32; openttd (§6.5)
P23A migration whose bazel build //... fails has not converged, whatever the differ says; and "the environment lacks it" is a choice when vendoring is available.F49, F35; xbmc, mysql-server

8.2 What the agent compares against

proposalwhatevidence
P13Fix the reference configuration before starting, state it, and do not change it to make the diff pass.F28, F21; qgis, gdal, the mysql-server pilot
P10Require parity at the default invocation, not only under the agent's own bazel_args (the build flags an agent declares in the skill's configuration for the differ to query with).F21; S01's run 025
P18Map CMake's build type to the flags it means, not to --compilation_mode alone, which brings its own optimization level and NDEBUG.F30; pono, cmake
P22Say what bazel_args is for, and validate it.F46; qgis

8.3 The scope boundary

proposalwhatevidence
P20, P21Model configure_file rather than leaving code generation to CMake. Name the three ways of doing the configure step (generate, freeze, leave to CMake) and what each costs. Require a checked-in probe result to be verified.F35, F36, F39, F51; mysql-server, gdal, cmake
P9, P4Reconsider "stop" where codegen has a direct Bazel mapping. Say what a non-interactive agent should do at the skill's first step, where it asks the user to confirm the plan.F12, F3, F2; lensfun, pono, yugabyte-db
P24Treat a project's own agent instructions as information, and say which ones are set aside.F53; yugabyte-db

8.4 Defects in the skill's scripts

proposalwhatevidence
P14The differ's verdict depends on Python's hash seed when a source is compiled in several variants.F28; mysql-server, and found independently by qgis's and blender's agents
P15The configure extractor misses CONFIGURE_FILE in upper case.F28, F15; mysql-server
P3, P11Match executables by basename when unambiguous; handle compile flags on link lines and unreferenced _EXPORTS macros.F4, F25

The remaining proposals (P5, P6, P7, P12) are marked medium or speculative in SKILL-FEEDBACK.md.

9. What the results support, and what they do not

claimwhat the record says
Where it produces a build, the skill arm has fewer behavioral causes. Supported on these projects (H1), with two conditions. Each project was run once per arm. And under the rule as first frozen, the verdict is inconclusive (§5.2). Counting a migration that switches assertions off (A16) reduces the skill arm's lead; it does not reverse it.
The skill makes an agent likelier to produce a working build. Not supported; the opposite was observed. The no-skill arm built on all 14 projects. The skill arm failed to on 3, and declined a further 1, correctly (§1, F49).
The skill makes migrations cheaper and faster. Observed on 13 of 14 pairs, but not as a like-for-like saving. The no-skill arm often did more of the project. On cmake, where the two arms did the same job, the skill arm was cheaper in both runs, by a margin that changed between them (§7.1).
The skill makes small-project migrations more repeatable. Supported for four small projects (S01, three runs per arm, §7.3), under earlier host conditions.
The skill helps an agent recognize a project it cannot migrate. One instance (yugabyte-db). In every earlier run the skill's stop instruction applied to, the agent went past it (F12).
Without the skill, agents port more of a project's tests. Observed at L and XL. On every L and XL project where either arm ported any of the reference's test cases, it was the no-skill arm. Where ported cases could be matched, they passed in both arms, except on yugabyte-db (§6, F57).
A converged diff means the migration matches the reference. Not supported. Parity misses the optimization level, assertions switched off, what generated files contain, and whether the program runs. On xbmc, an agent converged over a workspace that does not build (§4.3, §8.1).
Anything about XL projects in general. Not supported by single runs. On mysql-server, two runs of the same arm differed more than the arms did (§7.2).

10. Lessons for the next study

What S01 and S02 taught about running a study like this. Most lessons came from something going wrong first. The repository keeps each one where it was learned, in the runbook's warnings, the findings and the amendments.

10.1 Designing the comparison

10.2 Measuring

10.3 Recording

10.4 Writing it up

Appendix A. Every run, and how to reproduce the figures

Every run number the lab has allocated. A number is never reused. An abandoned run keeps its number, with the reason in its manifest. "In this report" names the section that uses the run. The others are shakedowns, pilots and validation runs, under conditions this report does not compare. S02's phase A re-ran S01's four small projects under S02's conditions; its phase B ran the ten larger ones.

runprojectarmstudystatusin this report
001graphicsgemsskillpre-S01 shakedowncomplete—
002lensfunskillpre-S01 shakedowncomplete—
003ponoskillpre-S01 paircomplete—
004ponono-skillpre-S01 paircomplete—
005graphicsgemsskillS01 phase 1complete—
006lensfunskillS01 phase 1complete—
007graphicsgemsskillS01 phase 1complete—
008lensfunskillS01 phase 1complete—
009ponoskillS01 phase 1complete—
010janssonskillS01 phase 1complete—
011graphicsgemsskillS01 phase 2complete§7
012lensfunskillS01 phase 2complete§7
013ponoskillS01 phase 2complete§7
014janssonskillS01 phase 2complete§7
015graphicsgemsno-skillS01 phase 2complete§7
016lensfunno-skillS01 phase 2complete§7
017ponono-skillS01 phase 2complete§7
018janssonno-skillS01 phase 2complete§7
019graphicsgemsno-skillS01 phase 2complete§7
020lensfunno-skillS01 phase 2complete§7
021ponono-skillS01 phase 2complete§7
022janssonno-skillS01 phase 2complete§7
023graphicsgemsskillS01 phase 2complete§7
024lensfunskillS01 phase 2complete§7
025ponoskillS01 phase 2complete§7
026janssonskillS01 phase 2complete§7
027graphicsgemsskillS01 phase 2complete§7
028lensfunskillS01 phase 2complete§7
029ponoskillS01 phase 2complete§7
030janssonskillS01 phase 2complete§7
031graphicsgemsno-skillS01 phase 2complete§7
032lensfunno-skillS01 phase 2complete§7
033ponono-skillS01 phase 2complete§7
034janssonno-skillS01 phase 2complete§7
035janssonskillS02 validationcomplete—
036mysql-serverskillS02 pilotabandoned—
037mysql-serverskillS02 pilotcomplete—
038mysql-serverskillS02 pilotcomplete§7
039mysql-serverno-skillS02 pilotcomplete§7
040graphicsgemsskillS02 shakedowncomplete—
041graphicsgemsno-skillS02 shakedowncomplete—
042graphicsgemsskillS02 phase Acomplete§6
043graphicsgemsno-skillS02 phase Aabandoned—
044graphicsgemsno-skillS02 phase Acomplete§6
045lensfunskillS02 phase Acomplete§6
046lensfunno-skillS02 phase Acomplete§6
047ponoskillS02 phase Aabandoned—
048ponoskillS02 phase Aabandoned—
049ponoskillS02 phase Aabandoned—
050ponoskillS02 phase Acomplete§6
051ponono-skillS02 phase Acomplete§6
052janssonskillS02 phase Acomplete§6
053janssonno-skillS02 phase Acomplete§6
054openttdskillS02 phase Babandoned—
055openttdskillS02 phase Bcomplete§6
056openttdno-skillS02 phase Bcomplete§6
057bitcoinskillS02 phase Bcomplete§6
058bitcoinno-skillS02 phase Bcomplete§6
059mysql-serverskillS02 phase Babandoned—
060mysql-serverskillS02 phase Bcomplete§6
061mysql-serverno-skillS02 phase Bcomplete§6
062gdalskillS02 phase Bcomplete§6
063gdalno-skillS02 phase Bcomplete§6
064xbmcskillS02 phase Babandoned—
065xbmcskillS02 phase Bcomplete§6
066xbmcno-skillS02 phase Bcomplete§6
067qgisskillS02 phase Bcomplete§6
068qgisno-skillS02 phase Bcomplete§6
069freecadskillS02 phase Babandoned—
070freecadskillS02 phase Bcomplete§6
071freecadno-skillS02 phase Bcomplete§6
072cmakeskillS02 phase Babandoned—
073cmakeskillS02 phase Bcomplete§6
074cmakeno-skillS02 phase Bcomplete§6
075cmakeskillS02 phase Bcomplete§7
076cmakeno-skillS02 phase Bcomplete§7
077blenderskillS02 phase Bcomplete§6
078blenderno-skillS02 phase Bcomplete§6
079yugabyte-dbskillS02 phase Bcomplete§6
080yugabyte-dbno-skillS02 phase Bcomplete§6

Reproducing the figures. From the repository root:

python3 studies/S02-large/analysis/primary.py      # the verdicts (writes primary.json, PRIMARY.md)
python3 reports/v2/facts.py                         # every figure in this report (writes facts.json)
python3 reports/v2/build.py                         # this page; refuses any number not rendered from facts.json

The per-run records these read are in runs/NNN-*/:

Each migration is stored as a git bundle against its upstream commit. Findings (F), proposals (P), errata (E), amendments (A) and verification entries (V) are in the repository's FINDINGS.md, SKILL-FEEDBACK.md, ERRATA.md, studies/S02-large/PREREGISTRATION.md and verification/.

Appendix B. Assertions, and amendment A16

This appendix explains a rule the body applies. Parity cannot see a migration that switches assertions off, unless another define error on the same units carries it along. Amendment A16 counts it anyway, reading the builds directly. The pre-A16 tally is kept in appendix C.

B.1 The blind spot

Parity compares the compile flags of each unit the two builds share. A define the reference has and the migration lacks is an error, and every error is attributed to a cause. A define that only the migration has is a warning, and causes are built from errors. So a migration that compiles with -DNDEBUG against a reference that does not is recorded as nothing at all. The exception is a unit that already has another define error: there the extra define rides along into that cause. NDEBUG is the define that turns assert off. Whether a build has assertions on is exactly the kind of difference the behavioral class exists to catch.

B.2 Which runs it touches

Among the 14 pairs, the migration's assertion state differs from the reference's in 8 runs. A build's state here is the one most of its compile actions have. Of those runs, three are mixed: qgis's skill arm compiles 98.7% of its actions with NDEBUG, yugabyte-db's no-skill arm 18.9%, and xbmc's no-skill arm 0.1%, in one build-time generator tool.

projectarmassertions, reference → migration (most compile actions)seen by parity?
openttdno-skillon → offyes
gdalskillon → offno
xbmcno-skilloff → onyes
qgisskillon → offyes
cmakeskillon → offno
blenderskilloff → onyes
blenderno-skilloff → onyes
yugabyte-dbno-skilloff → onyes

Turning assertions on removes a reference define, so it is always an error and always seen. Turning them off adds one, and is seen only by accident. The skill arm turned them off 3 times and on 1; the no-skill arm turned them off 1 and on 3. So the blind spot is not neutral between the arms. Parity saw 4 of the no-skill arm's 4 assertion changes and 2 of the skill arm's 4.

The two unseen cases came about differently. cmake's skill arm mapped CMake's build type to Bazel's --compilation_mode=opt, which brings -O2 -g0 -DNDEBUG whole, where the reference sets none of them (P18). gdal's skill arm passes -DNDEBUG itself, mirroring a Release build it configured for its own comparison.

B.3 What A16 does

A16 gives a run the behavioral cause build-type when three things hold. Its Bazel build defines NDEBUG on units the differ paired with the reference's. The reference does not define it. And no cause already records it. The runs it applies to: 062, 073, 075, 076.

skill fewerskill moretiedH1
before A16913supported
under A16 (§5)715supported (87.5% of untied pairs fewer)

gdal and cmake move from "fewer behavioral causes with the skill" to ties. The verdict holds. Counting assertion state alone, seen or not, the arms are even.

B.4 Why it is an amendment

The blind spot is a flaw in the measure, not a different reading of it, and it runs in one direction: it credits whichever arm prefers release defaults. Keeping it would have kept a tally known to describe two migrations wrongly. So A16 was adopted after the analysis, although its effect was already known, which is recorded with the amendment. It covers NDEBUG only, because its effect is known; other migration-only defines have not been classified one by one. The general fix belongs in the differ (P17): treat a migration-only define as an error, from the next study on.

Appendix C. The other readings the records support

The body of this report uses a single strategy for interpreting the records (page top). This appendix lists the alternative readings that change a stated result, so a reader who meets them in the repository knows how they relate. None of them determines a headline result; §5.2 and appendix B quote the two that bear on H1, for comparison.

C.1 H1 under the rule as first frozen

As first frozen, H1 counts every run without a whole build as worse than any number of causes, and keeps every pair. Amendment A12 judged gdal's and xbmc's skill arms on the part that builds. A13 made that the primary reading and left out yugabyte-db's pair, whose skill arm declined. Under the frozen rule: fewer 6, more 4, tied 4, which is inconclusive. The pre-registration requires this reading to be reported, and §5.2 does, in one sentence.

C.2 H1 before amendment A16

Before A16, a migration that switched assertions off without the parity measure recording it had no cause for it. H1's primary reading was then fewer 9, more 1, tied 3, supported. The frozen-rule reading was fewer 7, more 4, tied 3, inconclusive. A16 moved gdal and cmake from the skill arm's column to ties (appendix B).

C.3 H3 before amendment A15

Before A15, tests were counted by CMake's test registrations rather than by case. Registrations are one case on some projects and a whole suite or merged binary on others. That reading made bitcoin's two arms and freecad's no-skill arm non-checkable, although their ported tests pass. H3 was supported either way: skill 4 of 4, no-skill 6 of 7 checkable runs true, against 5 of 5 and 8 of 9 under A15. A15 was made after the analysis of record, because the earlier count described ported tests as not ported (F57, E29). PRIMARY.md gives A15's H3 as the result and the earlier one beside it.

C.4 S01 as S01 judged it

S01's runs were originally judged under S01's own policy. The skill arm was judged against its agent's own configuration, including the suppressions the agent chose. The no-skill arm was judged against a map the lab authored. Bazel was queried with any build flags the agent declared. For this report every S01 run was re-judged under S02's comparable reading (§4.2): the 24 runs of its second phase, which are the ones this report uses. This used the stored models, rebuilt trees for the checks, and, for runs 024 and 025, Bazel's default invocation. The original records are unchanged beside the new ones (judge/s02-reading/; audits/s01-rejudge/).

The runs whose behavioral causes change:

runprojectarmbehavioral causes under S01's policyunder this report's reading
017ponono-skilltarget-absentpie, target-absent, tu-absent
021ponono-skillnonetu-absent
025ponoskillnonebuild-type, tu-absent
029ponoskillexports-defines, pthread, rpathnone

C.5 Error counts

Every run's record carries raw error counts under several readings. This report uses none of them, because a count measures how widely one decision spread, and it depends on a path map that is not the same for both arms (§4.2, F47). On cmake, the map alone moves the skill arm's count by more than any decision in either migration weighs (F47). And within one arm, cmake's no-skill count moved several-fold between two runs whose coverage is nearly the same, mostly because of one include declaration (F51, which also records the configure-header change in §7.1).