Test-drive pure functions. Cluster them into classes later.
Source runs full-1, rep-1 and rep-2 at tdd-experiment c3dd988
Contents
Structure first
What it changes: The map. Drawn after the code exists.
GoldBest overallrank sum 11.0
GoldMost cost effectiveM 470,362, M_io 441,270
BronzeFewest errorsE 5
One method of the study, from runs full-1 and rep-1. The survey build is the method's single build in run full-1. A replicate is one of its builds in run rep-1, which ran in a different batch. Claims carry a tag. Recomputed means redone from source data. Verified means checked against a primary source. Unchecked means written or judged by an agent and not checked.
How it builds code
The procedure supplied to the agents. Indented lines run under the line above them.
Family: Structure first. Idea: Test-drive pure functions. Cluster them into classes later. What it changes in the search: The map. Drawn after the code exists.
once:1. List the spec's verbs as a test backlog. Leave nouns for later.2. Model state as plain data records with no behaviour.3. Test-drive each verb as a pure function over that data.4. Keep input and output in one thin shell that calls the pure functions.5. Tabulate each function's callees and the record fields it reads or writes.6. Assign each function to the record it touches most. Else join its main caller.7. Split any cluster over the lint method cap. Move each into a noun-named class.8. Keep tests green. Regenerate the noun-verb map. Count cross-cluster calls. Lint.
Once means one agent runs the steps. Repeat per unit means one agent per unit until no unit is open. Throughout means the rule joins every prompt. For up to n starts means separate attempts with the best kept. The harness does means the harness runs the step without an agent. If names the event that starts a stage, and with adds a rule borrowed from another method. Steps are quoted from args.json with their numbers. Tag: verified.
Figure 14.1
Stage flow of Functions first, classes later
Once: step 1.Lane: Agent.
Next: Once: step 2.
Once: step 2.Lane: Agent.
Next: Once: step 3.
Once: step 3. Commit cap 40. Lane: Agent.
Next: Once: step 4.
Once: step 4.Lane: Agent.
Next: Once: step 5.
Once: step 5.Lane: Agent.
Next: Once: step 6.
Once: step 6.Lane: Agent.
Next: Once: step 7.
Once: step 7.Lane: Agent.
Next: Once: step 8.
Once: step 8.Lane: Agent.
8 stages from the run facts, each checked against its block of the steps above. Details and notes are in the stage table. Tag: recomputed.
0.927Median adjusted mutation, over 5 replicates in rep-1
6Median classes, over 5 replicates in rep-1
1,339,770Median cost to completion, over 5 replicates in rep-1
15Median CQP, over 5 replicates in rep-1
against Noun-verb map: U 16, Holm p 0.548Planned contrast, exact, Holm, rep-1
1.14Consistency score C, exploratory over rep-1 and rep-2, lower is more consistent
Verdict
One survey build and 5 replicates. All 6 builds passed strict PMD compliance with 5 to 7 classes. Its replicates' median adjusted mutation was 0.927 against Noun-verb map's 0.964. The test did not detect a difference (Holm p 0.548). Its survey build rewrote 43 percent of the src lines it added, the highest share of the survey. Tag: recomputed.
Report summary
The report's class diagram for this design is identical to Figure 14.2 below, so it is shown once. Tag: recomputed.
Functional mark /15
15/15
Hidden pass /76
76/76
Unseen pass /69
69/69
Strict PMD compliance
pass
Mutation score
117/132, 0 not run, adjusted 117/122 (5 of 15 survivors fail hidden tests)
Classes
6
Simple Constructs /5
4
Minimal Duplication /5
4
Modular Structure /5
4
Problem Alignment (tentative) /5
4
Weighted tokens
1,273,066
Bench median ms
521
Score card
Survey tiles place the survey build among the single survey builds. One build per method describes that build and does not rank the methods. Contrast tiles are medians over the replicates. Tag: recomputed.
Adjusted mutation0.9597th highest of 11 single survey builds, one per method
Classes63rd highest of 11 single survey builds, one per method
Cost to completion1,195,4248th lowest of 11 single survey builds, one per method
Strict PMD violations0joint 1st lowest of 11 single survey builds, one per method
Median adjusted mutation0.927over 5 replicates in rep-1
Median classes6over 5 replicates in rep-1
Median cost to completion1,339,770over 5 replicates in rep-1
Median CQP15over 5 replicates in rep-1
Planned contrastagainst Noun-verb map: U 16, Holm p 0.548exact, Holm, rep-1
Consistency score C1.14exploratory over rep-1 and rep-2, lower is more consistent
Builds
Best, median and worst order a method's replicates by adjusted mutation, highest first, then lower cost to completion. The median build is the representative. Tag: recomputed.
Same family comes from the methods doc. Nearest and farthest average the rank differences over adjusted mutation, classes and cost to completion of the survey builds, over the metrics both methods have. Anchor Engineering has no survey build, so the replicate medians of adjusted mutation and classes stand in. Cost to completion leaves out such a method, since costs of a different batch do not compare. Tag: recomputed.
Branch method/functions-first. Claims carry a tag. Recomputed means redone from source data. Verified means checked against a primary source. Unchecked means written or judged by an agent and not checked.
Headline
Functional mark /15
15/15
Hidden pass /76
76/76
Unseen pass /69
69/69
Acceptance pass /6
6/6
Strict PMD compliance
pass
Recomputed from the grader's grade.tsv and summary.json and from metrics.json. The functional mark follows A02-RUBRIC.md: 2 for the original set with no failure, 3 per other set less one per failure, and 4 when every test passes. A02 is the course assignment whose spec and rubric the experiment uses. STA means anticlockwise and STC clockwise sowing. 6-4 and 7-5 are houses and seeds. A02-RUBRIC.md names the original set "Clockwise 6 Houses 4 Seeds". This page names each set by the spec folder its tests play, so the original set reads STA 6-4. Strict PMD compliance passes when PMD with the starter's kalah_pmd.xml finds no violation in src/ and otherwise fails with the count of distinct violations. The lint sets no minimum priority, so rules of every priority count. It covers src/ only, the scope of the starter's ./run.sh lint. A02-RUBRIC.md awards no marks for PMD, so the functional mark leaves it out.
Rubric set
Tests
Failures
Marks
Original set, STA 6-4
testST64SKHHSH_
0
2/2
STC 6-4
testSTC64SKHHSH_
0
3/3
STA 7-5
testST75SKHHSH_
0
3/3
STC 7-5
testSTC75SKHHSH_
0
3/3
All requirements
all tests
0
4/4
Summary metrics
Metric
Value
Tag
Total lines
707 (src 375, own tests 332)
recomputed
Lines of code
538 (src 270, own tests 268)
recomputed
Classes
6
recomputed
Methods
45
recomputed
Fields
14
recomputed
Bench median ms
521
recomputed
Bench volume
100614 inputs, 40083576 output chars
recomputed
Own tests leave out the starter's two Example tests and their ExampleSpec helper. Classes count top-level, nested and anonymous classes in src/.
Quality, costs and audit
Metric
Value
Tag
Mutation score
117/132, 0 not run, adjusted 117/122 (5 of 15 survivors fail hidden tests)
recomputed
PMD lines
0 (0 violations)
recomputed
Dependency edges
8
recomputed
Review scores (superseded)
Alterability 4.5, Code quality principles 3.6, Test quality 4
PMD lines count violations once per file and line. Agent time sums each agent's first to last timestamp, so agents that ran side by side add up. Weighted tokens count input 1, cache creation 1.25, cache read 0.05 and output 5 per token. These are claude-opus-5-5 prices relative to an input token and state no dollar cost. Costs, times and agent calls cover this clone only. The comparison row covers every clone of the method as its count basis states.
Review scores
A blind reviewer agent scored src/ and test/ from 1, worst, to 5, best, so the scores are unchecked. Reasons are the reviewer's own words, verbatim. This review is superseded by the blind rating of three raters in rating.json, whose medians fill the comparison's principle columns.
Reason: Direction lives only in BoardDrawing.draw, where one flag mirrors both rows and swaps the outer cells. The rules take no direction, and a test pins that both directions give the same move.
Alterability. Item: Change Case 2: 7 houses with 5 seeds. Score /5: 4.
Reason: The 7-5 variant is one constructor call in Kalah.play. Board and the drawing size themselves from the array length, but the 6/4 and 7/5 literals sit inline in play() and are not named constants.
Reason: Classes and functions are short and laid out the same way, and play() reads as a simple loop. The circuit-index arithmetic in Board (pit - size - 1, house - 1 + step) is compact and takes some effort to follow.
Reason: Names such as sow, capture, lastPit, opposite and nextMover use the game's own words, and the javadoc explains why. Some names are vague or misleading, such as the generic 'pit' for a circuit index and 'ascending' in houses().
Reason: Board mixes instance getters with static functions that take a Board. play() draws with the clockwise parameter while end() reads it from variant.isClockwise(), so the same fact comes from two sources.
Code quality principles. Item: 4. Used Content. Score /5: 2.
Reason: Phase.QUIT and Phase.OVER are never used, and phase never changes from PLAYING. The clockwise field in Variant only feeds one drawing call, so phase is dead state.
Reason: Plain loops, an immutable copy-on-write board and a modular circuit index keep the code simple. Using one shared TIE/QUIT value of -1 and the immutable copying add a little ceremony.
Reason: There is little repetition. The houses[player][house - 1] indexing appears in several places, and the 'lift seeds' count is read in both sow and lastPit.
Reason: The split is clean: Board holds the rules, GameState sequences a turn, BoardDrawing renders and Kalah handles input and output. Kalah also holds the winner and score logic, which belongs with the rules, and the unused Phase dilutes GameState.
Code quality principles. Item: 8. Problem Alignment. Score /5: 4.
Reason: The code models houses, stores, sowing, capture and extra turns directly, and the board ends the game when the mover has no seeds. Treating direction purely as a drawing mirror fits the problem but is stated only in a comment.
Test quality. Item: Test quality. Score /5: 4.
Reason: Focused, independent tests with descriptive names cover sowing laps, capture edge cases, extra turns, game end, scoring, ties and drawing in both directions and both sizes. End-to-end coverage is one anti-clockwise 7-5 spec, and the clockwise and 6-4 full flows are left to the course suite.
Audit flags
0 high and 6 low flags from agent tool calls and the method's commits. Only audit.json lists the low flags.
Step log
Method agents wrote the LOG.md cells. Agent-written text is shown verbatim and unchecked. Words such as recomputed inside it are the agent's own and are not tags of this page. Its Measure lines describe measurement during the run. Harness scores were measured separately after the run. Agent text may quote git commit times in the host's local zone. Harness times are UTC. Agent, tokens and seconds are recomputed from transcripts by row time. A dash in Agent means no agent run spans the row.
Actions: Wrote BACKLOG.md with 13 rule verbs and 7 shell verbs from README, SPEC and example specs. Step names no checks so no ./run.sh call. Standing guidance wants tests for every change. This step is a listing only so none were added
2026-10-15T23:54:39Z. Step: s2. Unit: plain data records. Tests added: 0. Cycles: 0. Sha: d24b2e1. Agent: functions-first:s2. Tokens: 236883. Seconds: 32.215.
Actions: Added Variant, Board, GameState and Phase as behaviour-free data classes with public final fields. Step names no checks so no ./run.sh call and the records are not compiled yet. Standing guidance wants tests for every change. Step 3 sets test timing so none were added. PMD DataClass will likely flag these records at the step 8 lint
2026-10-15T23:59:13Z. Step: s3. Unit: rule verbs 1 to 13 as pure functions. Tests added: 32. Cycles: 13. Sha: 5054f72. Agent: functions-first:s3. Tokens: 2559830. Seconds: 275.772.
Actions: Test-drove the 13 rule verbs in BACKLOG.md as static functions in Rules over Board, GameState and Variant, one verb per commit, each red then green with ./run.sh test RulesTest. Verb 4 moved after verb 10 because its direction test needs move(), which composes sow, capture and nextMover. lastPit came in with capture because capture needs the landing pit. Shell verbs 14 to 20 wait for step 4. Rules now has 16 methods, over the lint cap of 10, which step 7 splits. Full ./run.sh test and lint not run because the step names no other checks and the example tests are expected red
Actions: Test-drove pure text functions in Display (draw for both directions, prompt, result) red then green with ./run.sh test DisplayTest. Pinned the 7-house board with a test that passed first time. Rewrote Kalah.play as a loop that only prints Display output, reads input and calls Rules. Its red-green ran KalahTest over a new anti-clockwise 7-5 spec under test/, the one variant the examples lack. ExampleClockwiseTest and ExampleAnticlockwiseTest then ran as related tests of the changed Kalah.java and all 6 pass. The step names no checks, so full ./run.sh test and lint were not run. Standing guidance bans shell edits. The Edit and Write tools strip trailing spaces, so one sed call restored the required trailing space after the prompt in the new spec file
2026-10-16T00:11:36Z. Step: s5. Unit: function table of callees and record fields. Tests added: 0. Cycles: 0. Sha: d60f230. Agent: functions-first:s5. Tokens: 250596. Seconds: 48.071.
Actions: Wrote FUNCTIONS.md with one row per method in Rules, Display and Kalah giving project callees, record fields read and written and per-record R/W counts, plus a field use table and cross-class call sites. It notes GameState.phase is written but never branched on. Step names no checks so no ./run.sh call. Standing guidance wants tests for every change. This step is a table only and changes no code so none were added
Actions: Wrote CLUSTERS.md assigning each function to the record with the most R plus W touches in FUNCTIONS.md, zero-touch functions to their main caller's cluster. Board gets 17 methods (rules plus draw and its helpers), over the cap of 10 for step 7 to split. GameState gets start, move and nextMover. The Kalah shell keeps main, play, end and print as step 4 requires, though play touches Variant most, and takes prompt, result and winner through their callers. Variant gets none. Previewed 13 cross-cluster call sites. Assignment only, the code moves in step 7. Step names no checks so no ./run.sh call. Standing guidance wants tests for every change. This step changes no code so none were added
2026-10-16T00:16:40Z. Step: s7. Unit: split the Board cluster and move clusters into noun-named classes. Tests added: 0. Cycles: 0. Sha: 6b12bb7. Agent: functions-first:s7. Tokens: 1597487. Seconds: 228.472.
Actions: Board cluster (17) split in two. Its 10 rule functions moved from Rules into Board and draw with its 6 helpers moved from Display into the new class BoardDrawing. Read "move each" as every cluster, so start, move and nextMover moved into GameState and prompt, result, winner and TIE moved into the Kalah shell as package-private. Rules and Display are empty and deleted. Signatures stay static and unchanged. One commit per moved cluster. Tests moved with their functions into BoardTest, BoardDrawingTest, GameStateTest and KalahTest with a shared BoardFixture, none added. The step names the lint method cap, so ./run.sh lint ran and reports no TooManyMethods, GodClass or DataClass, only 9 FieldVisibility on the record fields and 10 LineLength in GameState, left for step 8. Standing guidance wants related tests run after a change. The step names no test check, so ./run.sh test was not run and compile is unverified until step 8. Pure moves with no red phase, so 0 red-green cycles
Actions: ./run.sh test was green at the s7 head with 46 tests in 6 classes including both example tests. ./run.sh lint gave 9 FieldVisibility on the public record fields and 10 LineLength hits on two GameState lines. Made the Board, GameState and Variant fields private behind get and is accessors, which TooManyMethods skips, and wrapped the long lines. Board exposes getSeeds and getStore per pit so no internal array leaves it, and BoardDrawing and the test fixture read through them. ./run.sh test stayed green after one compile fix to a test import, and ./run.sh lint now reports nothing. Wrote NOUNVERB.md from the code and ./run.sh deps. It counts 27 cross-cluster call sites, 13 verb calls matching the s6 preview and 14 new accessor calls. Behaviour-preserving lint fixes with no new behaviour, so no tests added and 0 red-green cycles
Raw inputs
metrics.json
summary.json
grade.tsv
costs.json
steps.csv
audit.json
review.json
LOG.md
MEASURE.md
classdiagram.uml
classdiagram.png
classdiagram.svg
collect.tsv
rating.json
MEASURE.md
The method's measure agent answered its Measure line from LOG.md and git. Agent-written text is shown verbatim and unchecked. Words such as recomputed inside it are the agent's own and are not tags of this page. Its Measure lines describe measurement during the run. Harness scores were measured separately after the run. Agent text may quote git commit times in the host's local zone. Harness times are UTC.
# Measure
Method: functions first, classes later. Sources are LOG.md, git log up to a54f7dd and the code and tests at that head. GAP.md and ATTEMPTS.md do not exist in this clone. Credits and time are measured by the harness and are not recorded here.
## Answer
One of 40 project-added tests needs a test double. Inside-cluster calls equal cross-cluster calls at 27 each, and verb calls alone run 27 inside against 13 across. The move into classes forced no change to any assertion or expected value. It rewrote call qualifiers in all 40 tests at s7 and accessor reads in 13 tests at s8.
## Tests that need test doubles
| Test group | Tests | Use a double | Double |
|---|---|---|---|
| Project-added rule and text tests (BoardTest, BoardDrawingTest, GameStateTest, KalahTest text functions) | 39 | 0 | none |
| Project-added shell spec test (KalahTest anticlockwise 7-5) | 1 | 1 | starter MockIO through ExampleSpec |
| Starter example tests (ExampleClockwiseTest, ExampleAnticlockwiseTest) | 6 | 6 | starter MockIO through ExampleSpec |
| Total | 46 | 7 | |
Project-added share is 1 of 40 or 2.5% `recomputed`. No mock, stub, spy or fake was written in the project. Every rule and text verb is a pure static function tested on plain values `recomputed`.
## Calls inside clusters against calls across them
Call sites of project methods at a54f7dd. Constructor calls, field reads and IO library calls are left out. Cross-cluster counts come from NOUNVERB.md and inside counts were recounted from `src/kalah`.
| Cluster | Inside call sites | Cross call sites out (verbs) | Cross call sites out (accessors) |
|---|---|---|---|
| Board | 6 (copy 2, circuit 2, drop 1, opposite 1) | 0 | 0 |
| BoardDrawing | 11 (row 2, store 2, houses 2, border 2, middle 1, repeat 2) | 0 | 5 |
| GameState | 1 (nextMover) | 3 | 3 |
| Kalah shell | 9 (print 3, end 2, result 1, prompt 1, winner 1, play 1) | 10 | 6 |
| Total | 27 | 13 | 14 |
Inside to cross is 27 to 27 overall and 27 to 13 for verb calls `recomputed`. The 13 cross verb sites match the s6 preview in CLUSTERS.md `recomputed`. The 14 accessor sites appeared at s8 when lint forced private fields. All cross traffic flows from the shell and GameState toward Board, and Board calls nothing outside itself `recomputed`.
## Test edits forced by the move into classes
| Stage | Commits | Tests touched | Kind of edit | Assertions or expected values changed |
|---|---|---|---|---|
| s7 move into classes | edfc145, b992500, 8f06663, 6b12bb7 | 40 of 40 | Tests moved from RulesTest and DisplayTest into BoardTest, BoardDrawingTest, GameStateTest and KalahTest. 62 `Rules.` and `Display.` qualifiers became the new class names. `TIE` became `Kalah.TIE` in 2 lines. The board and assertBoard helpers moved to a shared BoardFixture. One long line was rewrapped | 0 |
| s8 private fields for lint | d9ba8cb | 13 of 40 | 21 direct field reads became `getBoard`, `getVariant`, `getMover` and `getPhase`. assertBoard now compares through `getSeeds` and `getStore` | 0 |
Diffing the pre-move tests against the s7 tests with class qualifiers stripped leaves only headers, imports, helper placement, `Kalah.TIE` and the rewrap `recomputed`. No test was added, deleted or weakened by either stage `recomputed`. The s8 edits come from the lint field visibility rule and would not arise from the move alone.
## Context from LOG.md
| Stage | Red-green cycles | Tests added |
|---|---|---|
| s1, s2, s5, s6 | 0 | 0 |
| s3 rule verbs | 13 | 32 |
| s4 shell | 5 | 8 |
| s7, s8 moves and lint | 0 | 0 |
| Total | 18 | 40 |
At the 5 minutes per cycle convention the 18 cycles stand for 90 minutes. Wall time is measured by the harness.
Branch method/rep-1-r03. Claims carry a tag. Recomputed means redone from source data. Verified means checked against a primary source. Unchecked means written or judged by an agent and not checked.
Headline
Functional mark /15
15/15
Hidden pass /76
76/76
Unseen pass /69
69/69
Acceptance pass /6
6/6
Strict PMD compliance
pass
Recomputed from the grader's grade.tsv and summary.json and from metrics.json. The functional mark follows A02-RUBRIC.md: 2 for the original set with no failure, 3 per other set less one per failure, and 4 when every test passes. A02 is the course assignment whose spec and rubric the experiment uses. STA means anticlockwise and STC clockwise sowing. 6-4 and 7-5 are houses and seeds. A02-RUBRIC.md names the original set "Clockwise 6 Houses 4 Seeds". This page names each set by the spec folder its tests play, so the original set reads STA 6-4. Strict PMD compliance passes when PMD with the starter's kalah_pmd.xml finds no violation in src/ and otherwise fails with the count of distinct violations. The lint sets no minimum priority, so rules of every priority count. It covers src/ only, the scope of the starter's ./run.sh lint. A02-RUBRIC.md awards no marks for PMD, so the functional mark leaves it out.
Rubric set
Tests
Failures
Marks
Original set, STA 6-4
testST64SKHHSH_
0
2/2
STC 6-4
testSTC64SKHHSH_
0
3/3
STA 7-5
testST75SKHHSH_
0
3/3
STC 7-5
testSTC75SKHHSH_
0
3/3
All requirements
all tests
0
4/4
Summary metrics
Metric
Value
Tag
Total lines
596 (src 382, own tests 214)
recomputed
Lines of code
478 (src 295, own tests 183)
recomputed
Classes
6
recomputed
Methods
42
recomputed
Fields
13
recomputed
Bench median ms
506
recomputed
Bench volume
100614 inputs, 40083576 output chars
recomputed
Own tests leave out the starter's two Example tests and their ExampleSpec helper. Classes count top-level, nested and anonymous classes in src/.
Quality, costs and audit
Metric
Value
Tag
Mutation score
106/139, 0 not run, adjusted 106/125 (19 of 33 survivors fail hidden tests)
recomputed
PMD lines
0 (0 violations)
recomputed
Dependency edges
9
recomputed
Review scores (superseded)
Alterability 5, Code quality principles 3.9, Test quality 4
PMD lines count violations once per file and line. Agent time sums each agent's first to last timestamp, so agents that ran side by side add up. Weighted tokens count input 1, cache creation 1.25, cache read 0.05 and output 5 per token. These are claude-opus-5-5 prices relative to an input token and state no dollar cost. Costs, times and agent calls cover this clone only. The comparison row covers every clone of the method as its count basis states.
Review scores
A blind reviewer agent scored src/ and test/ from 1, worst, to 5, best, so the scores are unchecked. Reasons are the reviewer's own words, verbatim. This review is superseded by the blind rating of three raters in rating.json, whose medians fill the comparison's principle columns.
Reason: Rules work on an abstract row order and need no change for direction. Direction lives only in one if/else in Board.lines that mirrors both rows, so it works with any house count.
Alterability. Item: Change Case 2: 7 houses with 5 seeds. Score /5: 5.
Reason: House and seed counts sit in one line of Variant.fromFlags. Sowing, capture and rendering all read the count from the state, so both directions work with 7/5 unchanged.
Reason: Small classes with consistent indentation and short methods are easy to scan. The concatenated string literals in Board.lines are dense but readable.
Reason: Names like sow, capture, nextMover and Pit.atPosition are descriptive, and the javadoc explains index and position conventions. Bare 0/1 player indexes and the 1 - mover idiom have no named constants, and one class comment justifies itself by a lint cap.
Reason: Immutable value classes with getters are used throughout and text helpers follow one style. Board takes a raw clockwise boolean while setup takes a Variant, which is a small inconsistency.
Code quality principles. Item: 4. Used Content. Score /5: 3.
Reason: Variant.isClockwise is used only by a test, and Pit stores a dummy index 0 for stores. The emptyHouse() method only wraps a constant string, which suggests code shaped for tests.
Reason: Loops and early returns keep nesting shallow, and the modular position arithmetic in sow is compact. The capture-then-rebuild step in move and the sentinel ints QUIT and INVALID add a little indirection.
Reason: Row rendering, repeat and the position-to-pit mapping are shared. The position decoding logic appears twice, in sow and in Pit.atPosition, and the board-plus-header array copying is light.
Reason: Rules, rendering, variant settings and I/O are cleanly split with package-private access. MoveResult and Pit add small classes whose split from GameState is partly driven by a lint cap rather than by the domain.
Code quality principles. Item: 8. Problem Alignment. Score /5: 4.
Reason: Houses as two rows, stores, a mover and immutable move steps mirror the game well, and treating direction as a matter of display is a sound insight. Encoding players as 0/1 ints rather than a type weakens the alignment slightly.
Test quality. Item: Test quality. Score /5: 4.
Reason: Clearly named independent tests pin setup, sowing wrap, store skip, extra turn, capture, the exact-lap capture, end and scoring, input parsing and both board orientations including 7 houses. No test covers anticlockwise with 7/5 rendering or a full play() run through MockIO.
Audit flags
0 high and 3 low flags from agent tool calls and the method's commits. Only audit.json lists the low flags.
Step log
Method agents wrote the LOG.md cells. Agent-written text is shown verbatim and unchecked. Words such as recomputed inside it are the agent's own and are not tags of this page. Its Measure lines describe measurement during the run. Harness scores were measured separately after the run. Agent text may quote git commit times in the host's local zone. Harness times are UTC. Agent, tokens and seconds are recomputed from transcripts by row time. A dash in Agent means no agent run spans the row.
Actions: Read README, SPEC, EXAMPLES and specs. Wrote BACKLOG.md with 18 verbs. No code, so no red-green cycle and no ./run.sh call. Standing guidance asks for tests with every change. The step asks only for a backlog, so the step wins.
2026-10-26T10:04:00Z. Step: s2. Unit: plain data records. Tests added: 0. Cycles: 0. Sha: 2c96bbf. Agent: rep-1-r03:s2. Tokens: 275004. Seconds: 46.165.
Actions: Added Variant, GameState, Pit and MoveResult in src/kalah as final classes with package-private final fields and no methods. Records hold data only, so no red-green cycle, no test and no ./run.sh call since the step names no check. Standing guidance asks for tests and checks with every change. The step wins. PMD DataClass may flag these records until behaviour moves in at step 6.
Actions: Test-drove backlog verbs 1 to 17 as static pure functions in Rules (variant, setUp, sow, nextMover, capture, isEmptyHouse, isOver, score, move) and Text (board, prompt, parseMove, gameOver, result, emptyHouse), one verb per commit. Each cycle ran ./run.sh test RulesTest or TextTest red then green. Deviations: sowing order by index is the same in both directions, so verbs 3 and 4 became the clockwise and anti-clockwise board layouts. Verb 5 lap passed at once on the sow loop, so it is a pinning test with no red cycle. Added a move function that composes sow, capture and next mover for the shell. Verb 18 play is the step 4 shell and is left for that stage. No full ./run.sh test and no lint since the step names neither. Example tests still fail on the stub play().
Actions: Replaced the stub play() with a thin shell in Kalah that loops board, prompt via readFromKeyboard, parseMove, empty house check and Rules.move, then prints Game over and the result. An invalid reply re-prompts. Red was ./run.sh test ExampleAnticlockwiseTest failing 3 of 3 on the stub. Green was ./run.sh test with all 30 tests passing, including the six example tests. One red-green cycle. The six example tests already cover the shell, so no new test was added. Standing guidance asks for new tests with every change. The step asks only for the shell, so the step wins. The invalid input re-prompt has no spec and no test. No lint since the step names none.
2026-10-26T10:13:33Z. Step: s5. Unit: function callee and field table. Tests added: 0. Cycles: 0. Sha: 55d185d. Agent: rep-1-r03:s5. Tokens: 189347. Seconds: 46.549.
Actions: Wrote FUNCTIONS.md tabulating the callees, record fields read, record fields written and per-record touch counts for all 24 functions in Rules, Text and Kalah, read from the source by hand. No code change, so no red-green cycle, no test and no ./run.sh call since the step names no check. Standing guidance asks for tests with every change. The step asks only for a table, so the step wins.
Actions: Moved each function onto the record it touches most per FUNCTIONS.md, one red-green cycle per group. Each cycle first pointed RulesTest or TextTest at the new home and ran ./run.sh test on that class red as a compile failure, then moved the code and ran it green. variant went to Variant.of, nextMover to MoveResult, pitAt to Pit.at, and setUp, sow, capture, isEmptyHouse, isOver, score, move, copy, board, row, seeds and repeat to GameState. parseMove, gameOver, result, emptyHouse, prompt and the QUIT and INVALID constants touch no record and joined their main caller Kalah. Rules and Text are deleted. Deviations: capture stays a static GameState.capture(MoveResult) since it reads 5 GameState fields and 2 MoveResult fields. readMove touches GameState 2 but stays in Kalah because it does I/O and step 4 keeps I/O in the shell. Test files keep their RulesTest and TextTest names and only their call sites changed. The last cycle ran the full ./run.sh test with 4 classes and 0 failures. GameState now has 12 methods over the lint cap of 10, which step 7 splits. No lint since the step names none. FUNCTIONS.md is left as the s5 table for step 8 to regenerate.
Actions: Counted methods per class against the TooManyMethods cap of 10 in kalah_pmd.xml. Only GameState was over with 12. Moved board and its helpers row, seeds and repeat into a new noun-named class Board as the static Board.lines(GameState, boolean), leaving GameState with 8 and Kalah with 9. One red-green cycle. Red was ./run.sh test TextTest failing to compile after the three board tests were pointed at Board.lines. Green was the full ./run.sh test with 4 classes and 0 failures, which also covers the Kalah call sites through the example tests. No new test since existing tests cover the moved code. Standing guidance asks for new tests with every change. The step asks only for the split, so the step wins. The three Kalah call sites and its class comment were updated with sed, which the standing guidance forbids. Logged here as a deviation. No lint since step 8 lints.
Actions: Started green with ./run.sh test at 4 classes and 0 failures. First ./run.sh lint gave 42 violations: 29 LineLength, 11 FieldVisibility and 2 ShortMethodName. Cycle 1 renamed Variant.of to fromFlags and Pit.at to atPosition and wrapped long lines. Red was ./run.sh test RulesTest failing to compile on fromFlags. Green was the full ./run.sh test. Cycle 2 made every record field private behind one-line get and is accessors, with Board and Kalah reading through them. Red was ./run.sh test RulesTest failing to compile after its field reads were pointed at the getters. Green was the full ./run.sh test with 4 classes and 0 failures. Lint then reported no violations. Regenerated FUNCTIONS.md as a noun-verb map with 29 cross-cluster call sites over nine class edges, which match ./run.sh deps. No new test since behaviour is unchanged and the rewritten RulesTest asserts the same values through getters. Standing guidance asks for new tests with every change. The step asks only to keep tests green, so the step wins. ./run.sh lint ran four times and ./run.sh test five times, one call at a time.
Class diagram
Class diagrams show class names and relations only. A solid line ending in a hollow triangle points to a parent class and a dashed one to an implemented interface. A line with a diamond at the owning class marks a field and a plain arrow a use. Method and field totals are in the tables.
The method's measure agent answered its Measure line from LOG.md and git. Agent-written text is shown verbatim and unchecked. Words such as recomputed inside it are the agent's own and are not tags of this page. Its Measure lines describe measurement during the run. Harness scores were measured separately after the run. Agent text may quote git commit times in the host's local zone. Harness times are UTC.
# Measure
Method: functions first, classes later. Sources are LOG.md, git log, FUNCTIONS.md and the test diffs. GAP.md and ATTEMPTS.md do not exist in this clone. Credits and time are measured by the harness and are not reported here.
## Tests that need test doubles
| tests | count | test doubles |
|---|---|---|
| project tests in RulesTest and TextTest | 24 | 0 |
| given example tests in ExampleClockwiseTest and ExampleAnticlockwiseTest | 6 | MockIO through ExampleSpec |
| total | 30 | 6 |
All 24 tests written in this run call pure functions and need no double. The 6 that use MockIO are the starter kit tests and drive the I/O shell. `recomputed` from a grep of test/kalah.
## Calls inside clusters against calls across them
Call sites at stage s8, constructors included, library and I/O calls excluded, counted from the noun-verb map in FUNCTIONS.md.
| class | calls inside | calls across |
|---|---|---|
| GameState | 7 | 10 |
| Kalah | 14 | 12 |
| Board | 7 | 5 |
| MoveResult | 0 | 2 |
| Variant | 1 | 0 |
| Pit | 1 | 0 |
| total | 30 | 29 |
Inside and across are about even at 30 to 29. Kalah to GameState with 8 calls and Board to GameState with 5 are the heaviest cross edges. `recomputed` from FUNCTIONS.md. That map was written by hand at s8 and its edges match `./run.sh deps`. The per-call counts are `unchecked` against the source.
## Test edits forced by the move into classes
| stage | commits | test files | lines changed | tests edited | tests added or removed |
|---|---|---|---|---|---|
| s6 assign functions to records | 5 | RulesTest, TextTest | 38 | 24 | 0 |
| s7 split Board out of GameState | 1 | TextTest | included above | included above | 0 |
| s6 and s7 together | 6 | 2 | 38 replaced lines | 24 of 24 | 0 |
| s8 lint renames and private fields | 2 | RulesTest | 43 removed, 50 added | 11 of 24 | 0 |
Every project test was edited by the move. Each edit changed only the call site, such as `Rules.sow(before, 2)` becoming `before.sow(2)`. No assertion value changed and no test was added or dropped. The s8 edits are lint-driven and follow from the classes. They are listed apart from the move itself. `recomputed` from `git diff 7b6f7c8 c2bcf38 -- test` and `git diff c2bcf38 3716850 -- test`.
## Cycles and commits
LOG.md records 26 red-green cycles over s1 to s8. 17 were in s3 to test-drive the functions, 1 in s4, 5 in s6, 1 in s7 and 2 in s8. Git log holds 39 commits after the starter kit before this file. `recomputed` from LOG.md and git log.
Branch method/rep-1-r05. Claims carry a tag. Recomputed means redone from source data. Verified means checked against a primary source. Unchecked means written or judged by an agent and not checked.
Headline
Functional mark /15
15/15
Hidden pass /76
76/76
Unseen pass /69
69/69
Acceptance pass /6
6/6
Strict PMD compliance
pass
Recomputed from the grader's grade.tsv and summary.json and from metrics.json. The functional mark follows A02-RUBRIC.md: 2 for the original set with no failure, 3 per other set less one per failure, and 4 when every test passes. A02 is the course assignment whose spec and rubric the experiment uses. STA means anticlockwise and STC clockwise sowing. 6-4 and 7-5 are houses and seeds. A02-RUBRIC.md names the original set "Clockwise 6 Houses 4 Seeds". This page names each set by the spec folder its tests play, so the original set reads STA 6-4. Strict PMD compliance passes when PMD with the starter's kalah_pmd.xml finds no violation in src/ and otherwise fails with the count of distinct violations. The lint sets no minimum priority, so rules of every priority count. It covers src/ only, the scope of the starter's ./run.sh lint. A02-RUBRIC.md awards no marks for PMD, so the functional mark leaves it out.
Rubric set
Tests
Failures
Marks
Original set, STA 6-4
testST64SKHHSH_
0
2/2
STC 6-4
testSTC64SKHHSH_
0
3/3
STA 7-5
testST75SKHHSH_
0
3/3
STC 7-5
testSTC75SKHHSH_
0
3/3
All requirements
all tests
0
4/4
Summary metrics
Metric
Value
Tag
Total lines
740 (src 346, own tests 394)
recomputed
Lines of code
561 (src 256, own tests 305)
recomputed
Classes
7
recomputed
Methods
33
recomputed
Fields
17
recomputed
Bench median ms
660
recomputed
Bench volume
100614 inputs, 40083576 output chars
recomputed
Own tests leave out the starter's two Example tests and their ExampleSpec helper. Classes count top-level, nested and anonymous classes in src/.
Quality, costs and audit
Metric
Value
Tag
Mutation score
132/145, 0 not run, adjusted 132/133 (1 of 13 survivors fail hidden tests)
recomputed
PMD lines
0 (0 violations)
recomputed
Dependency edges
17
recomputed
Review scores (superseded)
Alterability 5, Code quality principles 3.9, Test quality 4
PMD lines count violations once per file and line. Agent time sums each agent's first to last timestamp, so agents that ran side by side add up. Weighted tokens count input 1, cache creation 1.25, cache read 0.05 and output 5 per token. These are claude-opus-5-5 prices relative to an input token and state no dollar cost. Costs, times and agent calls cover this clone only. The comparison row covers every clone of the method as its count basis states.
Review scores
A blind reviewer agent scored src/ and test/ from 1, worst, to 5, best, so the scores are unchecked. Reasons are the reviewer's own words, verbatim. This review is superseded by the blind rating of three raters in rating.json, whose medians fill the comparison's principle columns.
Reason: Direction is a single Variant flag read in one branch of Screen.renderBoard. The sowing order is the same in both directions and only the drawing is mirrored, so the change case touches one method and nothing in the rules.
Alterability. Item: Change Case 2: 7 houses with 5 seeds. Score /5: 5.
Reason: House count and seed count are two numbers in Variant.newGame. Board, GameState and Screen all derive sizes from the array length or houseCount, and tests exercise 7 houses in rules, rendering and full games.
Reason: Small files with consistent four-space indentation and short methods that read top to bottom. Wrapped expressions and the two-branch board assembly in renderBoard are slightly dense but still easy to follow.
Reason: Names such as lastPit, canCapture, quitLines and the Outcome enum state intent, and javadoc explains the pit numbering. The mix of 1-based house numbers and 0-based pit indexes (capture(last + 1)) is documented only partly and the literals 7, 5, 6, 4 are not named.
Reason: Static functions taking the object as first argument are used throughout, which is uniform but unidiomatic Java. protected fields on final classes, and capture taking a 1-based house while canCapture takes a 0-based pit, are inconsistent choices.
Code quality principles. Item: 4. Used Content. Score /5: 4.
Reason: Almost everything is used. Variant.initialSeeds is stored only to be read once at construction, and the CAPTURE, EXTRA_TURN and PASS_TURN outcomes are never read by production code, only by tests.
Reason: Flow is flat with early returns and a simple modular pit counter replaces direction-specific branching. The play loop with while(true) and two returns is clear enough for this size.
Reason: Board drawing builds rows from shared cell and houseCells helpers and pit arithmetic lives once in Board. lastPit and nextPit repeat the same modulus expression, and quit and game-end paths share quitLines well.
Reason: Clear split between rules (Board, GameState), text (Screen, MoveResult.outcomeLines), configuration (Variant) and a thin IO shell. Variant.newGame building a GameState and MoveResult holding output text blur the boundaries slightly.
Code quality principles. Item: 8. Problem Alignment. Score /5: 4.
Reason: Concepts map well to the game: houses, stores, sow, capture, extra turn and game over. Modelling direction as presentation only is an insightful fit, though the abstract pit numbering needs explanation to connect to the README rules.
Test quality. Item: Test quality. Score /5: 4.
Reason: RulesTest pins each rule with clear names, independent fixtures and golden board strings for both directions and both sizes, including laps, store skipping, capture edge cases and ties. ShellTest builds expected output from the model itself, so it checks sequencing but not exact text, and ScriptedIO ignores the input bounds.
Audit flags
0 high and 1 low flags from agent tool calls and the method's commits. Only audit.json lists the low flags.
Step log
Method agents wrote the LOG.md cells. Agent-written text is shown verbatim and unchecked. Words such as recomputed inside it are the agent's own and are not tags of this page. Its Measure lines describe measurement during the run. Harness scores were measured separately after the run. Agent text may quote git commit times in the host's local zone. Harness times are UTC. Agent, tokens and seconds are recomputed from transcripts by row time. A dash in Agent means no agent run spans the row.
Actions: Read README, SPEC and example specs. Wrote BACKLOG.md with 18 verbs. No code or tests this step, so no checks ran. Standing guidance asks for tests on every change; the step asks only for a backlog, so the step was followed.
2026-10-26T10:44:56Z. Step: s2. Unit: plain data records. Tests added: 0. Cycles: 0. Sha: b6efe38. Agent: rep-1-r05:s2. Tokens: 261739. Seconds: 45.15.
Actions: Added Variant, Board, GameState and MoveResult in src/kalah as field-only classes with constructors and no behaviour. The step names no check, so no ./run.sh call ran and compile is unchecked until s3. Standing guidance asks for tests on new behaviour; records carry none, so no tests were added. PMD DataClass may flag these records; the method defers lint to step 8.
Actions: Added src/kalah/Rules.java with static pure functions over the s2 records and test/kalah/RulesTest.java. One commit per verb for 16 of 18 backlog verbs: set up (newGame), take, sow, skip store, lap (lastPit), pass turn (move), extra turn, capture (canCapture, capture), skip capture, reject empty, detect game end (hasMoves), score, declare result (resultLines), render board (renderBoard), prompt, quit (quitLines). Skip store and skip capture passed on arrival and are pinned without a red run, so they count as tests but not cycles. Choose and play are IO only and wait for the s4 shell. Sow order is the same in both directions; only rendering reads clockwise. Checks: ./run.sh test RulesTest per cycle (27 OK) and one ./run.sh test (RulesTest OK; the six Example tests still error on the starter stub until s4). Lint deferred to step 8 per the method, so Rules may exceed the 10-method cap for now. Standing guidance asks for small classes; the step asks for pure functions first, so the step was followed.
Actions: Replaced the Kalah.play stub with a loop that only calls Rules: render, game end (board, Game over, result), prompt via io.readInteger with q as cancel, move, and outcome text. Added Rules.outcomeLines so the empty house message also comes from a pure function. Added test/kalah/ShellTest.java with test/kalah/ScriptedIO.java, an IO fake that records lines; expected transcripts are built from Rules calls so the tests check sequencing only. A spec-file approach was dropped because the edit tools strip the prompt's trailing space and the brief forbids scripted edits. Four cycles: quit at start (7-5 anticlockwise), move then quit, empty house, game end across all four variants. Checks: ./run.sh test ShellTest per cycle and one ./run.sh test (4 classes, 37 tests, all OK including the six Example tests). Lint deferred to step 8 per the method.
Actions: Wrote CALLMAP.md at the clone root: one row per function in Rules and Kalah with its callees and the record fields it reads or writes, plus a touches-per-record count for step 6. Analysis only, so no code, no tests and no ./run.sh call; the step names no check. Standing guidance asks for tests on every change; this step changes no behaviour, so the step was followed.
Actions: Added a cluster assignment section to CALLMAP.md. Each function joins the record it touches most. Functions with no touches or a tie join their main caller. The Kalah shell stays put as the public API, and play routes its callees as GameState. Result: GameState 10, Board 9, Kalah 3, Variant 1, MoveResult 1. GameState sits at the PMD cap of 10. Analysis only: no code moved, so no tests and no ./run.sh call ran, and the step names no check. Code moves into noun-named classes in step 7. Standing guidance asks for tests on every change. This step changes no behaviour, so the step was followed.
Actions: Moved each stage s6 cluster out of Rules into the class it is named after and deleted Rules, in three commits. Board got take, drop, nextPit, lastPit, canCapture, capture, copy, hasMoves and score (9). Variant got newGame (1). MoveResult got outcomeLines (1). GameState got move, result, sow, renderBoard, houseCells, cell, repeat, quitLines, resultLines and prompt (10). No cluster is over the PMD TooManyMethods cap of 10, so none was split. GameState sits at the cap, and its rules and text groups stay the natural split if step 8 lint flags it. The functions stay static with unchanged signatures, so behaviour and the shell are the same. Tests and Kalah only changed the class prefix. copy, nextPit and drop became package-private because sow in GameState calls them. RulesTest keeps its name. The step names no check, so no ./run.sh call ran and compile and tests are unchecked until step 8. Standing guidance asks to run checks after a change; the brief allows only the checks the step names, so the brief was followed. No red-green cycles because this is a move with no new behaviour.
Actions: ./run.sh test first: 4 classes, 37 tests, all OK, so the s7 move compiled. ./run.sh lint found 84 violations: 72 LineLength, 11 FieldVisibility and NcssCount on GameState (79 against the class cap of 75). Fixed in two commits. The GameState text group (renderBoard, resultLines, quitLines, prompt, houseCells, cell, repeat) moved unchanged into a new Screen class, with Kalah and the tests renamed to call it. Record fields became protected, which keeps package access, and long code lines wrapped. ./run.sh test again: 37 OK. ./run.sh lint: clean, exit 0. ./run.sh deps backs the regenerated noun-verb map in CALLMAP.md: Board 9, Screen 7, GameState 3, Variant 1, MoveResult 1, Kalah shell 3. Cross-cluster calls: 18 call sites over 16 caller-callee pairs. The shell makes 8 and the model classes make 10, all into Board. Moving sow into Board would bring that to 8 but was left as a note because the step asks only for a count. No new behaviour, so no tests and no red-green cycles. Standing guidance asks for tests on every change; this step is a behaviour-preserving lint fix that the existing 37 tests cover, so the step was followed.
Class diagram
Class diagrams show class names and relations only. A solid line ending in a hollow triangle points to a parent class and a dashed one to an implemented interface. A line with a diamond at the owning class marks a field and a plain arrow a use. Method and field totals are in the tables.
The method's measure agent answered its Measure line from LOG.md and git. Agent-written text is shown verbatim and unchecked. Words such as recomputed inside it are the agent's own and are not tags of this page. Its Measure lines describe measurement during the run. Harness scores were measured separately after the run. Agent text may quote git commit times in the host's local zone. Harness times are UTC.
# Measure
Method: functions first, classes later. Sources are LOG.md, git log and a grep of `src/` and `test/` at c8ef4d7. GAP.md and ATTEMPTS.md do not exist in this clone. Credits and time are measured by the harness and are not reported here.
## Tests that need test doubles
4 of the 31 tests this run wrote need a test double. The aim was near zero.
| test class | tests | double | why |
|---|---|---|---|
| RulesTest | 27 | none | pure functions over plain records |
| ShellTest | 4 | ScriptedIO fake | Kalah.play reads and prints through IO |
| Example*Test (starter, not written here) | 6 | MockIO from the kit | spec transcripts drive IO |
All 4 doubled tests cover the thin IO shell. No rule or rendering test needs one. Counting the starter tests too gives 10 of 37.
## Calls inside clusters against calls across them
Static call sites in `src/` after stage s8. Constructor calls and field reads are left out. The cross count matches CALLMAP.md and `./run.sh deps`.
| class | inside own class | across to another class |
|---|---|---|
| Board | 2 | 0 |
| GameState | 4 | 8, all into Board |
| Screen | 13 | 2, into Board.score |
| Variant | 0 | 0 |
| MoveResult | 0 | 0 |
| Model total | 19 | 10 |
| Kalah shell | 5 | 8 |
| All classes | 24 | 18 |
Inside the model 19 of 29 call sites (66%) stay in their cluster. Every model cross call goes into Board. GameState.sow makes 3 of them and touches only Board, so moving it there would bring the model to 21 inside and 8 across.
## Test edits forced by the move into classes
The move ran in four commits: 8dcd442, d5ee897 and 84eca5d in stage s7 and f496c61 for the Screen split in stage s8. Diff of `test/` from 1a71ae2 (before the move) to 812ca14 (lint clean).
| measure | value |
|---|---|
| test files edited | 2 (RulesTest, ShellTest) |
| changed lines | 74 (47 in RulesTest, 27 in ShellTest) |
| call prefixes renamed from Rules | 82 |
| class doc comments reworded | 2 |
| assertions or expected values changed | 0 |
| tests added, removed or renamed | 0 |
Every forced edit was a class prefix or a doc comment. The 37 tests passed unchanged in behaviour after the move.
Branch method/rep-1-r11. Claims carry a tag. Recomputed means redone from source data. Verified means checked against a primary source. Unchecked means written or judged by an agent and not checked.
Headline
Functional mark /15
15/15
Hidden pass /76
76/76
Unseen pass /69
69/69
Acceptance pass /6
6/6
Strict PMD compliance
pass
Recomputed from the grader's grade.tsv and summary.json and from metrics.json. The functional mark follows A02-RUBRIC.md: 2 for the original set with no failure, 3 per other set less one per failure, and 4 when every test passes. A02 is the course assignment whose spec and rubric the experiment uses. STA means anticlockwise and STC clockwise sowing. 6-4 and 7-5 are houses and seeds. A02-RUBRIC.md names the original set "Clockwise 6 Houses 4 Seeds". This page names each set by the spec folder its tests play, so the original set reads STA 6-4. Strict PMD compliance passes when PMD with the starter's kalah_pmd.xml finds no violation in src/ and otherwise fails with the count of distinct violations. The lint sets no minimum priority, so rules of every priority count. It covers src/ only, the scope of the starter's ./run.sh lint. A02-RUBRIC.md awards no marks for PMD, so the functional mark leaves it out.
Rubric set
Tests
Failures
Marks
Original set, STA 6-4
testST64SKHHSH_
0
2/2
STC 6-4
testSTC64SKHHSH_
0
3/3
STA 7-5
testST75SKHHSH_
0
3/3
STC 7-5
testSTC75SKHHSH_
0
3/3
All requirements
all tests
0
4/4
Summary metrics
Metric
Value
Tag
Total lines
743 (src 334, own tests 409)
recomputed
Lines of code
614 (src 255, own tests 359)
recomputed
Classes
5
recomputed
Methods
37
recomputed
Fields
10
recomputed
Bench median ms
459
recomputed
Bench volume
100614 inputs, 40083576 output chars
recomputed
Own tests leave out the starter's two Example tests and their ExampleSpec helper. Classes count top-level, nested and anonymous classes in src/.
Quality, costs and audit
Metric
Value
Tag
Mutation score
114/120, 0 not run, adjusted 114/114 (0 of 6 survivors fail hidden tests)
recomputed
PMD lines
0 (0 violations)
recomputed
Dependency edges
9
recomputed
Review scores (superseded)
Alterability 5, Code quality principles 4.1, Test quality 4
PMD lines count violations once per file and line. Agent time sums each agent's first to last timestamp, so agents that ran side by side add up. Weighted tokens count input 1, cache creation 1.25, cache read 0.05 and output 5 per token. These are claude-opus-5-5 prices relative to an input token and state no dollar cost. Costs, times and agent calls cover this clone only. The comparison row covers every clone of the method as its count basis states.
Review scores
A blind reviewer agent scored src/ and test/ from 1, worst, to 5, best, so the scores are unchecked. Reasons are the reviewer's own words, verbatim. This review is superseded by the blind rating of three raters in rating.json, whose medians fill the comparison's principle columns.
Reason: Direction is a single Variant flag that only BoardView reads, because sowing order on the ring is the same and only the drawn board mirrors. Changing direction touches no game logic and works with either board size.
Alterability. Item: Change Case 2: 7 houses with 5 seeds. Score /5: 5.
Reason: House and seed counts live only in Variant.variant, and every other module derives sizes from getHouses() or array lengths. That includes the ring modulus, capture opposite and border rendering, so 7-5 works in both directions.
Reason: Formatting is consistent with short methods and a sensible order inside each file. Some wrapped calls and the stores[0] on the P2 row in BoardView.render make the layout a little harder to follow.
Reason: Class Javadocs and the pit() comment explain intent well, and names like capture, hasMoves and verdict are clear. The 0/1 player indices, repeated 1 - mover and house - 1 offsets, and a top row that takes P1's store need more explanation.
Reason: The plain-data records with pure static functions follow one style throughout. Static functions that take the record as the first argument sit beside ordinary getters, and the split between Board statics and GameState statics is somewhat arbitrary.
Code quality principles. Item: 4. Used Content. Score /5: 5.
Reason: Every class, getter and constant is used by production code. No dead code or leftover stub output remains.
Reason: Loops and conditionals are shallow and the modular ring index keeps sowing compact. The ring arithmetic in pit() with the three-way branch in sow and the last < count / last == count checks takes some decoding.
Reason: Rendering, scoring and IO printing are shared through small helpers. The house - 1 and 1 - mover idioms repeat in many places, and move recomputes the seed count that sow also reads.
Reason: Variant, Board, GameState, BoardView and the thin Kalah IO shell have clear responsibilities with low coupling. Capture sits on Board while sowing sits on GameState, which splits the move rules across two classes, and getHouses copies the arrays on every call even for isEmpty.
Code quality principles. Item: 8. Problem Alignment. Score /5: 4.
Reason: Board, Variant and game state map to domain concepts, and mirroring the view for direction reflects the real game well. A raw int[][] with numeric player indices and a ring-position integer is more abstract than the houses, stores and players it stands for.
Test quality. Item: Test quality. Score /5: 4.
Reason: Unit tests pin sowing, wrap-around that skips the opponent store, exact-lap capture, both no-capture cases, extra turn, game end, verdict and all render orientations, each with a descriptive name. The KalahTest expectations come from production render and move calls, which makes them partly tautological, and the SIX and SEVEN fixtures hide their direction.
Audit flags
2 high and 6 low flags from agent tool calls and the method's commits. Only audit.json lists the low flags.
Channel
Agent or commit
Tool
Path
outside_path
rep-1-r11:measure
Bash
~
outside_path
rep-1-r11:measure
Bash
~
Channel
Marker
Disposition
outside_path
~
false positive
outside_path
~
false positive
Channel
outside_path
Definition
A path outside the clone. It is high under a home or temp dir and low elsewhere.
Dispositions from the run's findings.json judge every high flag of their channel.
Evidence: Both flags are the ~ match operator of awk in measure Bash calls at agent-ad442309644ebb021.jsonl:73 and 90. They name no path outside the clone.
Step log
Method agents wrote the LOG.md cells. Agent-written text is shown verbatim and unchecked. Words such as recomputed inside it are the agent's own and are not tags of this page. Its Measure lines describe measurement during the run. Harness scores were measured separately after the run. Agent text may quote git commit times in the host's local zone. Harness times are UTC. Agent, tokens and seconds are recomputed from transcripts by row time. A dash in Agent means no agent run spans the row.
Actions: Listed 28 verbs from README.md, docs/SPEC.md and examples/specs as a test backlog in BACKLOG.md. No code or tests per step 1. No checks named so ./run.sh was not run.
2026-10-26T10:03:50Z. Step: s2. Unit: plain data records. Tests added: 0. Cycles: 0. Sha: dfae63d. Agent: rep-1-r11:s2. Tokens: 224836. Seconds: 38.508.
Actions: Added Variant (houses, seeds, clockwise), Board (houses per player, stores) and GameState (variant, board, player, over) as final classes with final fields and no behaviour. Java 8 has no records. No tests since the records have no behaviour. No checks named so ./run.sh was not run.
Actions: Test-drove 12 verbs as static pure functions in GameFunctions over Variant, Board and GameState, one commit each: variant, start, isEmpty, sow (with pit ring), capture, hasMoves, move, score, verdict, prompt, scoreLines, render. Each cycle ran ./run.sh test GameFunctionsTest red then green. Three extra cases passed on first run and are not counted as cycles (anti-clockwise 7-house sow, 7-house render, input unchanged). I/O verbs (read house, quit, empty-house message, play) are left for the step 4 shell. Full ./run.sh test and lint were not named so not run. GameFunctions is over the lint method cap until step 7 splits it. One BACKLOG tick was made with sed, against the no-scripted-edits guidance.
Actions: Replaced the Kalah stub with a thin shell in play() that only reads input, prints lines and calls GameFunctions (variant, start, prompt, isEmpty, move, render, scoreLines). Test-drove it in KalahTest through a new ScriptedIO test double, one commit per cycle: quit with the board, moves until quit, empty house asks again, game end with board, scores and verdict. Each cycle ran ./run.sh test KalahTest red then green; the first red ran against the stashed stub. A first attempt used spec files under test/specs but the Write and Edit tools strip the trailing space the prompt needs, so it was dropped for ScriptedIO. One ./run.sh test ExampleAnticlockwiseTest call ran while diagnosing that (its quit test passed). Full ./run.sh test and lint were not named so not run. Backlog shell verbs ticked.
Actions: Wrote CALLMAP.md with one row per function in GameFunctions and Kalah: project callees, record fields read and written (constructing a record counts as writing its fields) and distinct fields touched per record, plus a callers table for step 6. No code or tests change in this step. No checks named so ./run.sh was not run. LOG row appended with printf against the no-scripted-edits guidance.
Actions: Added Assignment and Clusters sections to CALLMAP.md. Each function joins the record with the most distinct fields touched, else its main caller. Tie-breaks added where the step gives none: a record tie (render) goes to GameState since it holds board and variant, a split caller (copy) follows the board arrays it copies, and a function only the shell calls (scoreLines) joins its callees because the Kalah shell owns no record. Result: Variant 1, Board 6, GameState 11 (over the cap of 10 so step 7 splits it), shell 5. Code moves wait for step 7. No code or tests change. No checks named so ./run.sh was not run.
Actions: Moved each step 6 cluster into a noun-named class as pure static functions with unchanged bodies and signatures: variant into Variant, the Board cluster into Board (copy now package-private for sow), and split the 11-function GameState cluster (over the TooManyMethods cap of 10) into GameState (start move sow pit isEmpty prompt, 6) and a new BoardView (render row houses pad repeat, 5). Deleted the empty GameFunctions and repointed Kalah, KalahTest and GameFunctionsTest call sites. Two commits. No checks named so ./run.sh was not run, against the standing guidance to run static checks and related tests; bodies were diffed against the old GameFunctions to confirm a pure move. GameFunctionsTest keeps its name and CALLMAP.md is stale until step 8 regenerates the map.
Actions: ./run.sh test green at start (4 classes, 37 tests; run twice, once to trim output). ./run.sh lint found 10 FieldVisibility hits on the record fields and one UnusedPrivateMethod false positive on the List overload of Kalah.print. Made all record fields private behind get/is accessors (TooManyMethods skips them so GameState sits at 10), Board accessors return copies for MethodReturnsInternalArray so copy is private again, renamed the overload to printLines and repointed tests. ./run.sh test green and ./run.sh lint clean after. Rewrote CALLMAP.md as the noun-verb map of the five classes with a call table: 27 distinct cross-cluster call pairs (9 verbs, 18 accessors), 16 between the four domain classes of which 2 are verbs (GameState to Board.capture and Board.hasMoves). Refactor only, so no red-green cycles and no new tests.
Raw inputs
metrics.json
summary.json
grade.tsv
costs.json
steps.csv
audit.json
review.json
LOG.md
MEASURE.md
classdiagram.uml
classdiagram.png
classdiagram.svg
collect.tsv
rating.json
MEASURE.md
The method's measure agent answered its Measure line from LOG.md and git. Agent-written text is shown verbatim and unchecked. Words such as recomputed inside it are the agent's own and are not tags of this page. Its Measure lines describe measurement during the run. Harness scores were measured separately after the run. Agent text may quote git commit times in the host's local zone. Harness times are UTC.
# Measure
Method: functions first, classes later. Sources are LOG.md, git log, CALLMAP.md and the test diffs. GAP.md and ATTEMPTS.md do not exist in this clone. Credits and time are measured by the harness and are not reported here.
## Tests that need test doubles
| suite | tests | need a double | double |
|---|---|---|---|
| GameFunctionsTest (pure verbs) | 27 | 0 | none |
| KalahTest (I/O shell) | 4 | 4 | ScriptedIO, a scripted IO implementation |
| all project tests | 31 | 4 (13%) | one hand-written double |
The starter Example tests are excluded. Every domain verb is tested with plain data in and plain data out. Only the thin shell needs a double, and it needs one only for console input and output. No mocking library is used.
## Calls inside clusters against calls across them
Distinct caller to callee pairs at the function level, taken from CALLMAP.md at 1d1def7.
| scope | inside a cluster | across clusters | of which verbs | of which accessors |
|---|---|---|---|---|
| four domain classes | 13 | 16 | 2 | 14 |
| with the Kalah shell | 18 | 27 | 9 | 18 |
All 18 inside calls are verb calls. Between domain classes only 2 crossings are verbs (GameState.move to Board.capture and Board.hasMoves). The rest read a record through an accessor. Counted by verbs alone the clusters are cohesive at 18 inside against 9 across. Counted with accessors more calls cross than stay inside, because GameState and BoardView read Variant and Board fields.
## Test edits forced by the move into classes
| commit | step | change | test lines changed | tests touched |
|---|---|---|---|---|
| d4c71f3 | s7 | Variant and Board clusters move into their records | 15 | 9 |
| caf4bd1 | s7 | GameState split into GameState and BoardView | 48 | 22 |
| d6e55ad | s8 | record fields private behind accessors for lint | 68 | 23 |
| all three | | | 131 | 30 of 31 |
Lines count replaced lines in GameFunctionsTest and KalahTest. Each commit replaced as many lines as it removed. Every edit is mechanical. The s7 edits repoint `GameFunctions.x` call sites to the owning class. The s8 edits swap field reads such as `state.board` for accessors such as `state.getBoard()`. No assertion, expected value or test setup changed meaning, and no test was added or deleted. The one untouched test is testCaptureByP2UsesTheMirroredHouse. GameFunctionsTest kept its old name after the move.
The class move alone (s7) touched 30 tests over 63 lines. The accessor change (s8) came from the lint rule FieldVisibility on the record fields. It touched 23 tests over 68 lines.
## Context from LOG.md
| step | cycles | tests added |
|---|---|---|
| s3 pure verb functions | 14 | 27 |
| s4 thin I/O shell | 4 | 4 |
| s1, s2, s5 to s8 | 0 | 0 |
| all | 18 | 31 |
At 5 minutes per red-green cycle the 18 cycles stand for 90 minutes. `./run.sh test` was green with 37 tests and `./run.sh lint` clean at the end of s8.
Branch method/rep-1-r12. Claims carry a tag. Recomputed means redone from source data. Verified means checked against a primary source. Unchecked means written or judged by an agent and not checked.
Headline
Functional mark /15
15/15
Hidden pass /76
76/76
Unseen pass /69
69/69
Acceptance pass /6
6/6
Strict PMD compliance
pass
Recomputed from the grader's grade.tsv and summary.json and from metrics.json. The functional mark follows A02-RUBRIC.md: 2 for the original set with no failure, 3 per other set less one per failure, and 4 when every test passes. A02 is the course assignment whose spec and rubric the experiment uses. STA means anticlockwise and STC clockwise sowing. 6-4 and 7-5 are houses and seeds. A02-RUBRIC.md names the original set "Clockwise 6 Houses 4 Seeds". This page names each set by the spec folder its tests play, so the original set reads STA 6-4. Strict PMD compliance passes when PMD with the starter's kalah_pmd.xml finds no violation in src/ and otherwise fails with the count of distinct violations. The lint sets no minimum priority, so rules of every priority count. It covers src/ only, the scope of the starter's ./run.sh lint. A02-RUBRIC.md awards no marks for PMD, so the functional mark leaves it out.
Rubric set
Tests
Failures
Marks
Original set, STA 6-4
testST64SKHHSH_
0
2/2
STC 6-4
testSTC64SKHHSH_
0
3/3
STA 7-5
testST75SKHHSH_
0
3/3
STC 7-5
testSTC75SKHHSH_
0
3/3
All requirements
all tests
0
4/4
Summary metrics
Metric
Value
Tag
Total lines
635 (src 320, own tests 315)
recomputed
Lines of code
513 (src 245, own tests 268)
recomputed
Classes
7
recomputed
Methods
35
recomputed
Fields
9
recomputed
Bench median ms
218
recomputed
Bench volume
100614 inputs, 40083576 output chars
recomputed
Own tests leave out the starter's two Example tests and their ExampleSpec helper. Classes count top-level, nested and anonymous classes in src/.
Quality, costs and audit
Metric
Value
Tag
Mutation score
115/129, 0 not run, adjusted 115/124 (9 of 14 survivors fail hidden tests)
recomputed
PMD lines
0 (0 violations)
recomputed
Dependency edges
17
recomputed
Review scores (superseded)
Alterability 4.5, Code quality principles 3.5, Test quality 4
PMD lines count violations once per file and line. Agent time sums each agent's first to last timestamp, so agents that ran side by side add up. Weighted tokens count input 1, cache creation 1.25, cache read 0.05 and output 5 per token. These are claude-opus-5-5 prices relative to an input token and state no dollar cost. Costs, times and agent calls cover this clone only. The comparison row covers every clone of the method as its count basis states.
Review scores
A blind reviewer agent scored src/ and test/ from 1, worst, to 5, best, so the scores are unchecked. Reasons are the reviewer's own words, verbatim. This review is superseded by the blind rating of three raters in rating.json, whose medians fill the comparison's principle columns.
Reason: Direction is one Rules flag. Sowing stays in pit-index order, so only the renderBoard mirroring reads it. The mirroring logic is terse (left = cw ? 1 : 2, label count flags) and hard to follow, which makes it harder to change safely.
Alterability. Item: Change Case 2: 7 houses with 5 seeds. Score /5: 5.
Reason: Houses and seeds come only from the Rules constructor in play(). Pit geometry, set-up, scoring and board width are all computed from rules.houses, so the 7-5 variant needs no other code change.
Reason: Formatting and indentation are consistent and the methods are short. The placement is the weak point: board drawing sits in Rules while its label helper sits in Game.
Reason: Most names are descriptive and the pit layout comment is helpful. Magic expressions such as 3 - mover and 2 * houses + 2 recur, Kalah uses literal 6/4/7/5, and class comments talk about a lint method cap, which is development history and does not explain intent.
Reason: Visibility is inconsistent: some helpers are public and others package-private for no clear reason. Final data classes use protected fields. Rules holds data yet every function takes it as a static parameter instead of being an instance method.
Code quality principles. Item: 4. Used Content. Score /5: 4.
Reason: Almost everything is used. Game.quit builds a MoveResult on the no-moves path only to read its message, and pickUp is public but used only by sow, which is minor indirection.
Reason: Functions are small with shallow nesting and a clear play loop. The boolean count flag passed to Game.label and the inverted cw flags in renderBoard make the board code hard to read.
Reason: lastPit repeats sow's traversal loop. The modulus 2 * houses + 2 is written twice in nextPit, and 3 - mover appears several times without a helper.
Reason: The data, turn, score and lifecycle split is reasonable, but the files depend on each other in a tangle. Rules calls Game.label and Turn.chooseHouse, Game calls Rules and Score, and the classes were split to satisfy a method cap instead of by responsibility.
Code quality principles. Item: 8. Problem Alignment. Score /5: 4.
Reason: The pit array, the sow and capture steps and the immutable state records follow the game well. Handling direction only as a display mirror is valid but hides the clockwise concept from the sowing algorithm.
Test quality. Item: Test quality. Score /5: 4.
Reason: FunctionsTest pins each rule with clear names, including laps, store skipping, capture edge cases, game end, tie and both directions of rendering. The play() tests cover only the anticlockwise 7-5 variant and no full game ending with a declared result.
Audit flags
0 high and 0 low flags from agent tool calls and the method's commits. Only audit.json lists the low flags.
Step log
Method agents wrote the LOG.md cells. Agent-written text is shown verbatim and unchecked. Words such as recomputed inside it are the agent's own and are not tags of this page. Its Measure lines describe measurement during the run. Harness scores were measured separately after the run. Agent text may quote git commit times in the host's local zone. Harness times are UTC. Agent, tokens and seconds are recomputed from transcripts by row time. A dash in Agent means no agent run spans the row.
Actions: Read README.md, docs/SPEC.md and examples. Wrote BACKLOG.md with 19 verbs. No code or tests per step 1. No checks named so none run.
2026-10-26T10:03:46Z. Step: s2. Unit: plain data records. Tests added: 0. Cycles: 0. Sha: 30d1527. Agent: rep-1-r12:s2. Tokens: 331180. Seconds: 46.229.
Actions: Added Rules (houses, seeds, clockwise), GameState (pits, mover, over) and MoveResult (state, message) as final-field classes with constructors only. No behaviour so no tests and no red-green cycles. Step names no checks so none run and compile is unverified. Conflict logged: PMD DataClass rule in kalah_pmd.xml may flag these records at the step 8 lint.
Actions: Test-drove all 19 backlog verbs as static pure functions in src/kalah/Functions.java over Rules, GameState and MoveResult, one verb per commit, plus a composing move verb the backlog lacked. 18 verbs went red then green. Sow anti-clockwise and lap the board passed on first run because direction only changes the drawing, so those two commits pin behaviour with no red step. Checks run were ./run.sh test FunctionsTest only, 23 tests green. Conflict logged: standing guidance asks for lint and the full suite, the step names neither, so lint and ./run.sh test were not run. Kalah.play is still the stub so the example tests stay red until the step 4 shell.
Actions: Replaced the Kalah.play stub with a thin shell that builds Rules from the two flags and loops prompt, quit or move, message, board, then on no moves prints Game over, board and declareResult. Shell holds no game logic and calls only Functions. Four red-green cycles: quit before any move, move then quit, empty house asks again (KalahTest with a test-only ScriptedIO), and game end, whose red was the two existing full-game example tests. Deviation: spec files under test/ were dropped because the Write tool strips the prompt's trailing space, so KalahTest records output through ScriptedIO instead. Checks run were ./run.sh test KalahTest per cycle and ./run.sh test at the end, 32 tests green in 4 classes. Conflict logged: the step names no lint, so ./run.sh lint was not run.
Actions: Wrote CALLMAP.md tabulating all 28 functions in Functions and Kalah with callees, direct Rules, GameState and MoveResult field reads and writes, per-record touch counts and a reverse caller table for step 6. A pits array or mover int passed as a parameter counts as the GameState field. No code change so no tests and no red-green cycles. Step names no checks so none run. Conflict logged: standing guidance asks for checks on every change, the step names none and only a doc changed.
2026-10-26T10:16:40Z. Step: s6. Unit: function to record clusters. Tests added: 0. Cycles: 0. Sha: 2d6bbd4. Agent: rep-1-r12:s6. Tokens: 239025. Seconds: 52.463.
Actions: Wrote CLUSTERS.md assigning all 28 functions from CALLMAP.md. Most touched record by R, G and M counts wins. Ties and zero counts join the main caller cluster and tied callers defer to their shared caller. Result GameState 17 functions (over the TooManyMethods cap of 10), Rules 8, MoveResult 0, Kalah shell 3. No code moved because step 7 moves clusters into classes. No tests and no red-green cycles. Step names no checks so none run. Conflict logged: standing guidance asks for checks on every change, the step names none and only a doc changed.
Actions: GameState cluster (17 functions, over the TooManyMethods cap of 10) split into Turn (9), Game (5) and Score (3) as static pure-function classes. Rules cluster (8, under cap) moved onto the Rules record as static functions so Functions could be deleted. Kalah and FunctionsTest call sites renamed and CLUSTERS.md gained the class table. Function bodies unchanged so no tests added and no red-green cycles. Step names no checks so none run and compile is unverified. Conflict logged: standing guidance asks for tests and lint after a refactor, the step names neither, so step 8 must run ./run.sh test and lint.
Actions: ./run.sh test green before and after, 32 tests in 4 classes. First ./run.sh lint found 8 FieldVisibility and 16 LineLength lines. Record fields in Rules, GameState and MoveResult went from public to protected, which the rule allows and which keeps them plain data with no accessors, so DataClass and MethodReturnsInternalArray stay quiet. Long lines were wrapped or split with a local. Lint now clean. Wrote NOUNVERB.md from ./run.sh deps and a call-site scan: 15 distinct cross-cluster edges (18 sites) between core classes, 7 edges (10 sites) from the Kalah shell, Rules the hub, with Rules-Game and Rules-Turn cycles left. Behaviour-preserving refactor so no tests added and no red-green cycles. Checks run were ./run.sh test, ./run.sh lint and ./run.sh deps. No conflict with standing guidance.
Raw inputs
metrics.json
summary.json
grade.tsv
costs.json
steps.csv
audit.json
review.json
LOG.md
MEASURE.md
classdiagram.uml
classdiagram.png
classdiagram.svg
collect.tsv
rating.json
MEASURE.md
The method's measure agent answered its Measure line from LOG.md and git. Agent-written text is shown verbatim and unchecked. Words such as recomputed inside it are the agent's own and are not tags of this page. Its Measure lines describe measurement during the run. Harness scores were measured separately after the run. Agent text may quote git commit times in the host's local zone. Harness times are UTC.
# Measure
Method: functions first, classes later.
Sources are LOG.md, git log and the source and test tree at a5397b8. GAP.md and ATTEMPTS.md do not exist in this clone.
Credits and time are measured by the harness and are not reported here.
## Tests that need test doubles
3 of the 26 tests added in this run need a test double. The target was near zero.
| Test class | Tests | Double | Added by |
|---|---|---|---|
| FunctionsTest | 23 | none | run, s3 |
| KalahTest | 3 | ScriptedIO, a hand-written fake IO | run, s4 |
| ExampleClockwiseTest | 3 | MockIO from the course test support | starter kit |
| ExampleAnticlockwiseTest | 3 | MockIO from the course test support | starter kit |
All 23 game-logic tests call pure static functions on plain records and need no double.
Only the IO shell tests need one, because the shell is the only code that reads and writes.
## Calls inside clusters against calls across them
A cluster is one class after the s7 split. Counts are call sites in `src/kalah`. Cross-cluster counts come from NOUNVERB.md. Inside counts come from a scan of unqualified calls to same-class functions.
| Class | Calls inside | Calls out to other classes |
|---|---|---|
| Turn | 9 | 6 |
| Rules | 5 | 5 |
| Score | 3 | 3 |
| Game | 0 | 4 |
| Core total | 17 | 18 |
| Kalah shell | 4 | 10 |
| All classes | 21 | 28 |
Core classes make about as many calls across clusters as inside them (17 inside and 18 across).
Rules is the hub with 10 of the 15 distinct core edges pointing at it. Two class cycles remain, Rules with Game and Rules with Turn.
Game makes no internal calls, so it groups related functions without being cohesive.
## Test edits forced by the move into classes
The move into classes in s7 (d46093f) forced 55 changed lines in one test file. None changed what a test checks.
| Commit | Test file | Lines removed | Lines added | Kind |
|---|---|---|---|---|
| d46093f | FunctionsTest.java | 55 | 55 | `Functions.` qualifier renamed to `Rules.`, `Turn.`, `Game.` or `Score.` |
| 8e75bde | none | 0 | 0 | lint fixes touched source only |
With the class qualifiers stripped and whitespace removed the old and new test lines are byte-identical, so 0 assertions changed and 0 tests were added or removed.
KalahTest, ScriptedIO and the example tests needed no edits.
The test class still carries the old name FunctionsTest after the Functions class was deleted.
Branch method/rep-1-r16. Claims carry a tag. Recomputed means redone from source data. Verified means checked against a primary source. Unchecked means written or judged by an agent and not checked.
Headline
Functional mark /15
15/15
Hidden pass /76
76/76
Unseen pass /69
69/69
Acceptance pass /6
6/6
Strict PMD compliance
pass
Recomputed from the grader's grade.tsv and summary.json and from metrics.json. The functional mark follows A02-RUBRIC.md: 2 for the original set with no failure, 3 per other set less one per failure, and 4 when every test passes. A02 is the course assignment whose spec and rubric the experiment uses. STA means anticlockwise and STC clockwise sowing. 6-4 and 7-5 are houses and seeds. A02-RUBRIC.md names the original set "Clockwise 6 Houses 4 Seeds". This page names each set by the spec folder its tests play, so the original set reads STA 6-4. Strict PMD compliance passes when PMD with the starter's kalah_pmd.xml finds no violation in src/ and otherwise fails with the count of distinct violations. The lint sets no minimum priority, so rules of every priority count. It covers src/ only, the scope of the starter's ./run.sh lint. A02-RUBRIC.md awards no marks for PMD, so the functional mark leaves it out.
Rubric set
Tests
Failures
Marks
Original set, STA 6-4
testST64SKHHSH_
0
2/2
STC 6-4
testSTC64SKHHSH_
0
3/3
STA 7-5
testST75SKHHSH_
0
3/3
STC 7-5
testSTC75SKHHSH_
0
3/3
All requirements
all tests
0
4/4
Summary metrics
Metric
Value
Tag
Total lines
618 (src 356, own tests 262)
recomputed
Lines of code
492 (src 276, own tests 216)
recomputed
Classes
5
recomputed
Methods
44
recomputed
Fields
13
recomputed
Bench median ms
268
recomputed
Bench volume
100614 inputs, 40083576 output chars
recomputed
Own tests leave out the starter's two Example tests and their ExampleSpec helper. Classes count top-level, nested and anonymous classes in src/.
Quality, costs and audit
Metric
Value
Tag
Mutation score
116/146, 0 not run, adjusted 116/138 (22 of 30 survivors fail hidden tests)
recomputed
PMD lines
0 (0 violations)
recomputed
Dependency edges
9
recomputed
Review scores (superseded)
Alterability 5, Code quality principles 3.8, Test quality 4
PMD lines count violations once per file and line. Agent time sums each agent's first to last timestamp, so agents that ran side by side add up. Weighted tokens count input 1, cache creation 1.25, cache read 0.05 and output 5 per token. These are claude-opus-5-5 prices relative to an input token and state no dollar cost. Costs, times and agent calls cover this clone only. The comparison row covers every clone of the method as its count basis states.
Review scores
A blind reviewer agent scored src/ and test/ from 1, worst, to 5, best, so the scores are unchecked. Reasons are the reviewer's own words, verbatim. This review is superseded by the blind rating of three raters in rating.json, whose medians fill the comparison's principle columns.
Reason: Direction only mirrors the drawing, so the clockwise flag reaches nothing but Board.row through Variant.render. Sowing and capture logic need no change, and the flag combines freely with board size.
Alterability. Item: Change Case 2: 7 houses with 5 seeds. Score /5: 5.
Reason: Size and seed count sit in one line of Variant.select. Board, Pit, rendering and input bounds all read the house count from the variant or array length.
Reason: Classes are short with consistent formatting and clear vertical grouping. Minor blemishes include the fully qualified java.util.Arrays calls and the dense boolean expression 'owner == 1 != clockwise'.
Reason: Names such as hasNoMoves, lastPit, nextPlayer and opposite say what they do, and short doc comments give the rule behind capture and extra turns. Player indices 0 and 1 and literals 6, 4, 7, 5 have no named constants.
Reason: All records are immutable final classes with getters and value-returning operations. Rendering is split oddly: Variant draws borders while Board draws rows, and visibility varies between public and package-private.
Code quality principles. Item: 4. Used Content. Score /5: 4.
Reason: Nearly everything is used. Variant.isClockwise is called only from tests, and Pit carries both a store flag and a house of 0 for stores.
Reason: Control flow is flat with early returns and small loops, and the play loop is easy to follow. Pit.next's mover-relative path and the label/store swap boolean ask the reader to do some work.
Reason: Pit.sow and Pit.lastPit both walk the same sowing path separately. Board copies its arrays defensively in several places, and score and hasNoMoves loop the same houses.
Reason: Kalah is a thin IO shell over the rule classes, which helps. Responsibilities blur, though: sowing is a static method on Pit that builds Boards, Game.newBoard builds Boards, and drawing the board is shared between Variant and Board.
Code quality principles. Item: 8. Problem Alignment. Score /5: 4.
Reason: Board, Pit, Game and Variant map onto Kalah concepts, and direction is correctly treated as presentation only. Placing the sowing path on Pit and drawing on Variant fits the domain less well.
Test quality. Item: Test quality. Score /5: 4.
Reason: Focused, clearly named unit tests pin sowing, skipping the opponent store, exact laps, capture and its exceptions, extra turns, game end, scoring and rendering in both directions and at both sizes. No test drives play() end to end through MockIO, and the clockwise 7-5 render and the empty-house message are not checked.
Audit flags
0 high and 5 low flags from agent tool calls and the method's commits. Only audit.json lists the low flags.
Step log
Method agents wrote the LOG.md cells. Agent-written text is shown verbatim and unchecked. Words such as recomputed inside it are the agent's own and are not tags of this page. Its Measure lines describe measurement during the run. Harness scores were measured separately after the run. Agent text may quote git commit times in the host's local zone. Harness times are UTC. Agent, tokens and seconds are recomputed from transcripts by row time. A dash in Agent means no agent run spans the row.
Actions: Wrote BACKLOG.md with 25 verbs from README.md, docs/SPEC.md and examples/specs. No code and no checks run because the step asks for a backlog only. Conflict logged: standing guidance asks for tests on every change; the step governs and adds none.
2026-10-26T10:44:54Z. Step: s2. Unit: plain data records. Tests added: 0. Cycles: 0. Sha: 8957cc2. Agent: rep-1-r16:s2. Tokens: 221485. Seconds: 47.033.
Actions: Added Variant, Board, Pit and Game as immutable data records with private final fields and getters only (FieldVisibility lint needs private fields). Board copies arrays in and out. No tests and no checks run because the step names none and records carry no behaviour. Conflict logged: standing guidance asks for tests and checks on every change; the step governs. DataClass lint hits are expected until stage s7.
Actions: Test-drove backlog verbs 1 to 24 as static pure functions in src/kalah/Rules.java over the s2 records, one verb per commit (24 commits), each seen red (compile error or assertion) then green with ./run.sh test RulesTest. move(Game, house) composes pick up, sow, last pit, capture, next player and the end check, and returns the same game for an empty house. Sowing order is direction-free; direction only changes render. Verb 25 (IO reads and prints) is left to the s4 shell. Full ./run.sh test, lint and mutate not run because the step names only the red-green cycles; the six example tests still fail on the stub play() until s4. Conflict logged: standing guidance asks for lint and the full suite before commit; the step governs.
Actions: Replaced the stub play() in src/kalah/Kalah.java with a thin shell over Rules: print board, read house with io.readInteger (q cancels), empty-house message, Rules.move, game end with score lines and outcome. Three red-green cycles driven by the existing example tests (quit, then moves and empty house, then full-game end), one commit each. ./run.sh test now passes all 32 tests in 3 classes. No new tests because the six example specs already pin the shell. Rules.isQuit is now unused because readInteger handles the q cancel; left for s5 to tabulate. Lint, mutate and specs not run because the step names none. Conflict logged: standing guidance asks for new tests and lint on every change; the step governs.
Actions: Wrote CALLMAP.md tabulating all 28 functions in src/kalah (23 in Rules, 5 in Kalah) with callers, callees, record fields read and record fields written (constructors count as writes, getters as reads). Rules.isQuit has no production caller and only RulesTest uses it. No code changed, no tests and no checks run because the step asks for a table only. Conflict logged: standing guidance asks for tests and checks on every change; the step governs.
2026-10-26T10:56:48Z. Step: s6. Unit: function to record assignment. Tests added: 0. Cycles: 0. Sha: f4ab762. Agent: rep-1-r16:s6. Tokens: 317295. Seconds: 85.284.
Actions: Added a Stage s6 assignment section to CALLMAP.md. Each of the 28 functions has its touch count per record from the s5 reads and writes cells and its cluster. No touches or a tie means the main caller cluster. Clusters are Variant 3, Board 8, Pit 4, Game 4 and Kalah 9. The five shell functions stay in Kalah because s4 keeps IO there. Rules.isQuit has no production caller and goes to Kalah as a deletion candidate. No cluster passes the TooManyMethods cap of 10. No code moved because s7 moves clusters into classes. No tests and no checks run because the step names none. Conflict logged: standing guidance asks for tests and checks on every change. The step governs.
Actions: No cluster passes the TooManyMethods cap of 10, so no split. Moved each s6 cluster out of Rules into its noun class, one cluster per commit: Variant (select, render), Board (pickUp, capture, opposite, score, hasNoMoves, row), Pit (sow, lastPit, nextPlayer, private next), Game (newBoard, newGame, isEmptyHouse, move) and Kalah (scoreLine, outcome, prompt). Rules.java is deleted. Each move was one refactor cycle: tests moved to the new call sites in VariantTest, BoardTest, PitTest, GameTest and KalahTest and seen red (compile), then code moved and ./run.sh test green (31 tests in 7 classes). Shared test helpers live in test/kalah/Fixtures.java. Rules.isQuit and its test are deleted as s6 marked them dead, so the count is 31 where s4 had 32. Ran ./run.sh lint once to check the cap: 0 TooManyMethods and 0 DataClass hits, 37 LineLength remain for s8. A sixth cycle renamed Variant.of to Variant.select for a ShortMethodName hit the move added. Deviation: one test call-site rename used sed in place of Edit. Conflict logged: standing guidance asks for new tests on every change; this step moves existing tests and adds none.
Actions: Wrapped the 37 LineLength hits (7 lines in Board, Kalah and Variant; Variant.select became an if) in one refactor cycle, then ./run.sh lint reports no hits and ./run.sh test passes 7 classes with 0 failures. Rewrote CALLMAP.md as the stage s8 noun-verb map of 27 methods over Variant, Board, Pit, Game and Kalah, replacing the stale Rules tables from s5 and s6. Cross-cluster calls: 32 edges (14 behaviour, 16 getter, 2 constructor) over 39 sites. Game.move is the hub with 5 behaviour edges. ./run.sh deps lists 9 class edges with one cycle, Board and Pit. Cross-cluster counts were done by hand from the source. No tests added because the step changes layout only. Conflict logged: standing guidance asks for new tests on every change; the step governs and adds none.
Raw inputs
metrics.json
summary.json
grade.tsv
costs.json
steps.csv
audit.json
review.json
LOG.md
MEASURE.md
classdiagram.uml
classdiagram.png
classdiagram.svg
collect.tsv
rating.json
MEASURE.md
The method's measure agent answered its Measure line from LOG.md and git. Agent-written text is shown verbatim and unchecked. Words such as recomputed inside it are the agent's own and are not tags of this page. Its Measure lines describe measurement during the run. Harness scores were measured separately after the run. Agent text may quote git commit times in the host's local zone. Harness times are UTC.
# Measure
Method: functions first, classes later. Sources are LOG.md, git log, CALLMAP.md and the test tree at 5a30598. GAP.md and ATTEMPTS.md do not exist in this clone. Credits and time are measured by the harness and are not recorded here.
## Tests that need test doubles
Target is near zero. Result is 0 of 25 project unit tests.
| test group | tests | use a test double |
|---|---|---|
| unit tests (Variant, Board, Pit, Game, Kalah) | 25 | 0 |
| starter-kit example tests (MockIO through ExampleSpec) | 6 | 6 |
| total | 31 | 6 |
Every unit test builds plain records and calls a pure function or method. The only double is the starter kit's MockIO in the six example tests, which drive the IO shell end to end. Those files are read only for this run. `recomputed` from a grep of test/kalah for mock, stub, fake and spy, and a count of test methods per file.
## Calls inside clusters against calls across them
Edges from the stage s8 noun-verb map in CALLMAP.md. One edge is one caller method and one callee method.
| kind | inside a cluster | across clusters |
|---|---|---|
| behaviour calls | 16 | 14 |
| getter calls | 6 | 16 |
| constructor calls | 0 | 2 |
| total | 22 | 32 |
Behaviour calls split close to even at 16 inside and 14 across. Getter calls run mostly across clusters (16 against 6) because functions were placed by the record they touch most and still read fields of other records. Game.move is the hub with 5 cross-cluster behaviour edges. `./run.sh deps` lists one class cycle, Board and Pit, through capture and sow. Across counts are `unchecked` hand counts from stage s8. Inside counts are `recomputed` here from the same-cluster column of the map. Sites inside clusters were not counted.
## Test edits forced by the move into classes
Stage s7 moved five clusters out of Rules, one commit each (9ae80ef, 5509cd5, df71b6f, 3185f44, 2e0e4fc), plus one rename (34b2577).
| item | count |
|---|---|
| RulesTest tests before the move | 26 |
| tests moved to a new test class with new call sites | 25 |
| tests deleted with dead code (Rules.isQuit) | 1 |
| Rules. call sites in the old RulesTest | 57 |
| test files added (five noun tests and Fixtures) | 6 |
| test lines added and removed by the moves | 262 added, 221 removed |
| test lines changed by the Variant.of to Variant.select rename | 22 in 4 files |
| example test lines changed | 0 |
| expected values changed | 0 |
Every surviving unit test needed an edit. Each edit is a call-site change only. Static calls such as `score(board, 0)` became receiver calls such as `board.score(0)`, the test helper `variant(...)` became `Variant.select(...)`, and two private assert helpers moved into Fixtures. A diff of the assert lines before and after with class prefixes stripped shows no changed expected value. Edits were mechanical and none changed what a test checks. The six example tests needed no edit because they pin only the IO shell. `recomputed` from git show and git diff on the commits above.