Kalah TDD study · Part 14 of 29 · Designs

Functions first, classes later

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.

  • Gold Best overall rank sum 11.0
  • Gold Most cost effective M 470,362, M_io 441,270
  • Bronze Fewest errors E 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

Stage flow of Functions first, classes later8 stages in run order across the lanes Agent.AgentAgentOnce: step 1Once: step 2Once: step 3Commit cap 40Once: step 4Once: step 5Once: step 6Once: step 7Once: step 8
  1. Once: step 1. Lane: Agent.

    Next: Once: step 2.

  2. Once: step 2. Lane: Agent.

    Next: Once: step 3.

  3. Once: step 3. Commit cap 40. Lane: Agent.

    Next: Once: step 4.

  4. Once: step 4. Lane: Agent.

    Next: Once: step 5.

  5. Once: step 5. Lane: Agent.

    Next: Once: step 6.

  6. Once: step 6. Lane: Agent.

    Next: Once: step 7.

  7. Once: step 7. Lane: Agent.

    Next: Once: step 8.

  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.

Text version: Stage flow of Functions first, classes later
  1. Once: step 1. Lane: Agent.

    Next: Once: step 2.

  2. Once: step 2. Lane: Agent.

    Next: Once: step 3.

  3. Once: step 3. Commit cap 40. Lane: Agent.

    Next: Once: step 4.

  4. Once: step 4. Lane: Agent.

    Next: Once: step 5.

  5. Once: step 5. Lane: Agent.

    Next: Once: step 6.

  6. Once: step 6. Lane: Agent.

    Next: Once: step 7.

  7. Once: step 7. Lane: Agent.

    Next: Once: step 8.

  8. Once: step 8. Lane: Agent.

Stages

Each stage as the run facts list it. Runs as and Note are quoted verbatim and Steps repeats the steps above. Tag: verified.

StageRuns asStepsDetailsNote
Once: step 1once:1. List the spec's verbs as a test backlog. Leave nouns for later.nonenone
Once: step 2once:2. Model state as plain data records with no behaviour.nonenone
Once: step 3once:3. Test-drive each verb as a pure function over that data.commit cap 40Test-drive one verb per commit.
Once: step 4once:4. Keep input and output in one thin shell that calls the pure functions.nonenone
Once: step 5once:5. Tabulate each function's callees and the record fields it reads or writes.nonenone
Once: step 6once:6. Assign each function to the record it touches most. Else join its main caller.nonenone
Once: step 7once:7. Split any cluster over the lint method cap. Move each into a noun-named class.nonenone
Once: step 8once:8. Keep tests green. Regenerate the noun-verb map. Count cross-cluster calls. Lint.nonenone

Survey build

One build in run full-1.

15/15Functional mark
5E
1,195,424Cost to completion
470,362M
441,270M_io
0PMD findings
noAlignment

Replicates

Repeat builds: 5 in run rep-1.

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 /1515/15
Hidden pass /7676/76
Unseen pass /6969/69
Strict PMD compliancepass
Mutation score117/132, 0 not run, adjusted 117/122 (5 of 15 survivors fail hidden tests)
Classes6
Simple Constructs /54
Minimal Duplication /54
Modular Structure /54
Problem Alignment (tentative) /54
Weighted tokens1,273,066
Bench median ms521

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.

Survey build (full-1)

Buildfunctions-first
Adjusted mutation0.959
Classes6
Cost to completion1,195,424
Strict PMD violations0

Figure 14.2

Class diagram of method/functions-first

kalahBoardBoardDrawingGameStateKalahPhaseVariant

6 classes and 11 links. Tag: recomputed.

Text version: Class diagram of method/functions-first
  1. Package kalah: Board, BoardDrawing, GameState, Kalah, Phase, Variant
  2. BoardDrawing uses Board
  3. GameState uses Board
  4. GameState holds Board
  5. GameState uses Phase
  6. GameState holds Phase
  7. GameState uses Variant
  8. GameState holds Variant
  9. Kalah uses Board
  10. Kalah uses BoardDrawing
  11. Kalah uses GameState
  12. Kalah uses Variant

Best replicate (rep-1-r11)

Replicaterep-1-r11
MethodFunctions first, classes later
Adjusted mutation1.000
Unadjusted mutation0.950
Classes5
Cost to completion1,339,770
CQP16
CQP accepted12
Strict PMD violations0
Buildbest

Figure 14.3

Class diagram of rep-1-r11

kalahBoardBoardViewGameStateKalahVariant

5 classes and 11 links. Tag: recomputed.

Text version: Class diagram of rep-1-r11
  1. Package kalah: Board, BoardView, GameState, Kalah, Variant
  2. BoardView uses Board
  3. BoardView uses GameState
  4. BoardView uses Variant
  5. GameState uses Board
  6. GameState holds Board
  7. GameState uses Variant
  8. GameState holds Variant
  9. Kalah uses Board
  10. Kalah uses BoardView
  11. Kalah uses GameState
  12. Kalah uses Variant

Median replicate (rep-1-r12)

Replicaterep-1-r12
MethodFunctions first, classes later
Adjusted mutation0.927
Unadjusted mutation0.891
Classes7
Cost to completion1,271,901
CQP14
CQP accepted11
Strict PMD violations0
Buildmedian

Figure 14.4

Class diagram of rep-1-r12

kalahGameGameStateKalahMoveResultRulesScoreTurn

7 classes and 18 links. Tag: recomputed.

Text version: Class diagram of rep-1-r12
  1. Package kalah: Game, GameState, Kalah, MoveResult, Rules, Score, Turn
  2. Game uses GameState
  3. Game uses MoveResult
  4. Game uses Rules
  5. Rules uses Game
  6. Game uses Score
  7. Kalah uses Game
  8. Kalah uses GameState
  9. Kalah uses MoveResult
  10. Kalah uses Rules
  11. Kalah uses Score
  12. Kalah uses Turn
  13. MoveResult uses GameState
  14. MoveResult holds GameState
  15. Rules uses Turn
  16. Turn uses Rules
  17. Score uses Rules
  18. Turn uses GameState
  19. Turn uses MoveResult

Worst replicate (rep-1-r16)

Replicaterep-1-r16
MethodFunctions first, classes later
Adjusted mutation0.841
Unadjusted mutation0.795
Classes5
Cost to completion1,385,387
CQP13
CQP accepted10
Strict PMD violations0
Buildworst

Figure 14.5

Class diagram of rep-1-r16

kalahBoardGameKalahPitVariant

5 classes and 11 links. Tag: recomputed.

Text version: Class diagram of rep-1-r16
  1. Package kalah: Board, Game, Kalah, Pit, Variant
  2. Board uses Pit
  3. Pit uses Board
  4. Game uses Board
  5. Game holds Board
  6. Game uses Pit
  7. Game uses Variant
  8. Game holds Variant
  9. Kalah uses Board
  10. Kalah uses Game
  11. Kalah uses Variant
  12. Variant uses Board

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.

Records

Replicates (rep-1)

Replicates (rep-1), 5 rows and 10 columns
ReplicateMethodAdjusted mutationUnadjusted mutation
rep-1-r03Functions first, classes later0.8480.763
rep-1-r05Functions first, classes later0.9920.910
rep-1-r11Functions first, classes later1.0000.950
rep-1-r12Functions first, classes later0.9270.891
rep-1-r16Functions first, classes later0.8410.795
ReplicateClassesCost to completionCQP
rep-1-r0361,367,02917
rep-1-r0571,337,29515
rep-1-r1151,339,77016
rep-1-r1271,271,90114
rep-1-r1651,385,38713
ReplicateCQP acceptedStrict PMD violationsBuild
rep-1-r03130-
rep-1-r05110-
rep-1-r11120best
rep-1-r12110median
rep-1-r16100worst

Consistency

MethodFunctions first, classes later
Adjusted mutation1.37
Unadjusted mutation1.98
Classes1.14
Cost to completion0.30
Strict PMD violationsat bound
CQP accepted1.68
Consistency score C1.14

Clone reports

Each clone's own report, as in its REPORT.md.

Functions first, classes later: functional mark 15/15, strict PMD pass, classes 6, weighted tokens 1,273,066

Functions first, classes later

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 /1515/15
Hidden pass /7676/76
Unseen pass /6969/69
Acceptance pass /66/6
Strict PMD compliancepass

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 setTestsFailuresMarks
Original set, STA 6-4testST64SKHHSH_02/2
STC 6-4testSTC64SKHHSH_03/3
STA 7-5testST75SKHHSH_03/3
STC 7-5testSTC75SKHHSH_03/3
All requirementsall tests04/4
Summary metrics
MetricValueTag
Total lines707 (src 375, own tests 332)recomputed
Lines of code538 (src 270, own tests 268)recomputed
Classes6recomputed
Methods45recomputed
Fields14recomputed
Bench median ms521recomputed
Bench volume100614 inputs, 40083576 output charsrecomputed

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
MetricValueTag
Mutation score117/132, 0 not run, adjusted 117/122 (5 of 15 survivors fail hidden tests)recomputed
PMD lines0 (0 violations)recomputed
Dependency edges8recomputed
Review scores (superseded)Alterability 4.5, Code quality principles 3.6, Test quality 4unchecked
Tokens9,108,626 (input 376, cache creation 490,543, cache read 8,571,520, output 46,187)recomputed
Weighted tokens1,273,066recomputed
Wall time0:28:51recomputed
Agent time0:27:53recomputed
Agent calls9recomputed
Audit flags0 high, 6 lowrecomputed

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.

Review scores, 11 rows and 4 columns

Alterability. Item: Change Case 1: sow seeds anti-clockwise. Score /5: 5.

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.

Code quality principles. Item: 1. Clear Presentation. Score /5: 4.

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.

Code quality principles. Item: 2. Explanatory Language. Score /5: 4.

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().

Code quality principles. Item: 3. Consistent Code. Score /5: 3.

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.

Code quality principles. Item: 5. Simple Constructs. Score /5: 4.

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.

Code quality principles. Item: 6. Minimal Duplication. Score /5: 4.

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.

Code quality principles. Item: 7. Modular Structure. Score /5: 4.

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.

Step log, 8 rows and 10 columns

2026-10-15T23:54:04Z. Step: s1. Unit: spec verb test backlog. Tests added: 0. Cycles: 0. Sha: 12cc542. Agent: functions-first:s1. Tokens: 318141. Seconds: 57.228.

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

2026-10-16T00:10:50Z. Step: s4. Unit: thin input and output shell, shell verbs 14 to 20. Tests added: 8. Cycles: 5. Sha: 4884857. Agent: functions-first:s4. Tokens: 1802913. Seconds: 693.378.

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

2026-10-16T00:12:43Z. Step: s6. Unit: assign functions to records. Tests added: 0. Cycles: 0. Sha: 0f998b9. Agent: functions-first:s6. Tokens: 278450. Seconds: 69.222.

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

2026-10-16T00:19:45Z. Step: s8. Unit: keep green, noun-verb map, cross-cluster count, lint. Tests added: 0. Cycles: 0. Sha: b306f9d. Agent: functions-first:s8. Tokens: 1603538. Seconds: 184.137.

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.
Replicate rep-1-r03: functional mark 15/15, strict PMD pass, classes 6, weighted tokens 1,454,277

Replicate rep-1-r03

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 /1515/15
Hidden pass /7676/76
Unseen pass /6969/69
Acceptance pass /66/6
Strict PMD compliancepass

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 setTestsFailuresMarks
Original set, STA 6-4testST64SKHHSH_02/2
STC 6-4testSTC64SKHHSH_03/3
STA 7-5testST75SKHHSH_03/3
STC 7-5testSTC75SKHHSH_03/3
All requirementsall tests04/4
Summary metrics
MetricValueTag
Total lines596 (src 382, own tests 214)recomputed
Lines of code478 (src 295, own tests 183)recomputed
Classes6recomputed
Methods42recomputed
Fields13recomputed
Bench median ms506recomputed
Bench volume100614 inputs, 40083576 output charsrecomputed

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
MetricValueTag
Mutation score106/139, 0 not run, adjusted 106/125 (19 of 33 survivors fail hidden tests)recomputed
PMD lines0 (0 violations)recomputed
Dependency edges9recomputed
Review scores (superseded)Alterability 5, Code quality principles 3.9, Test quality 4unchecked
Tokens9,563,486 (input 360, cache creation 419,976, cache read 9,047,839, output 95,311)recomputed
Weighted tokens1,454,277recomputed
Wall time0:23:04recomputed
Agent time0:22:19recomputed
Agent calls9recomputed
Audit flags0 high, 3 lowrecomputed

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.

Review scores, 11 rows and 4 columns

Alterability. Item: Change Case 1: sow seeds anti-clockwise. Score /5: 5.

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.

Code quality principles. Item: 1. Clear Presentation. Score /5: 4.

Reason: Small classes with consistent indentation and short methods are easy to scan. The concatenated string literals in Board.lines are dense but readable.

Code quality principles. Item: 2. Explanatory Language. Score /5: 4.

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.

Code quality principles. Item: 3. Consistent Code. Score /5: 4.

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.

Code quality principles. Item: 5. Simple Constructs. Score /5: 4.

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.

Code quality principles. Item: 6. Minimal Duplication. Score /5: 4.

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.

Code quality principles. Item: 7. Modular Structure. Score /5: 4.

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.

Step log, 8 rows and 10 columns

2026-10-26T10:03:10Z. Step: s1. Unit: verb test backlog. Tests added: 0. Cycles: 0. Sha: 1b9c705. Agent: rep-1-r03:s1. Tokens: 247464. Seconds: 44.729.

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.

2026-10-26T10:11:28Z. Step: s3. Unit: verbs as pure functions. Tests added: 24. Cycles: 17. Sha: 598c858. Agent: rep-1-r03:s3. Tokens: 4453148. Seconds: 457.601.

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().

2026-10-26T10:12:47Z. Step: s4. Unit: thin I/O shell. Tests added: 0. Cycles: 1. Sha: 7b6f7c8. Agent: rep-1-r03:s4. Tokens: 454691. Seconds: 66.468.

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.

2026-10-26T10:17:24Z. Step: s6. Unit: assign functions to records. Tests added: 0. Cycles: 5. Sha: 8cc1a52. Agent: rep-1-r03:s6. Tokens: 1344630. Seconds: 227.59.

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.

2026-10-26T10:18:46Z. Step: s7. Unit: split clusters over the method cap. Tests added: 0. Cycles: 1. Sha: c2bcf38. Agent: rep-1-r03:s7. Tokens: 480440. Seconds: 82.962.

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.

2026-10-26T10:23:20Z. Step: s8. Unit: green tests, noun-verb map, cross-cluster calls, lint. Tests added: 0. Cycles: 2. Sha: 0ea5c5b. Agent: rep-1-r03:s8. Tokens: 1718675. Seconds: 275.983.

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.

Figure 14.6

Class diagram of method/rep-1-r03
kalahBoardGameStateKalahMoveResultPitVariant

6 classes and 11 links. Tag: recomputed.

Text version: Class diagram of method/rep-1-r03
  1. Package kalah: Board, GameState, Kalah, MoveResult, Pit, Variant
  2. Board uses GameState
  3. GameState uses MoveResult
  4. MoveResult uses GameState
  5. MoveResult holds GameState
  6. GameState uses Pit
  7. GameState uses Variant
  8. Kalah uses Board
  9. Kalah uses GameState
  10. Kalah uses Variant
  11. MoveResult uses Pit
  12. MoveResult holds Pit
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, 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.
Replicate rep-1-r05: functional mark 15/15, strict PMD pass, classes 7, weighted tokens 1,423,177

Replicate rep-1-r05

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 /1515/15
Hidden pass /7676/76
Unseen pass /6969/69
Acceptance pass /66/6
Strict PMD compliancepass

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 setTestsFailuresMarks
Original set, STA 6-4testST64SKHHSH_02/2
STC 6-4testSTC64SKHHSH_03/3
STA 7-5testST75SKHHSH_03/3
STC 7-5testSTC75SKHHSH_03/3
All requirementsall tests04/4
Summary metrics
MetricValueTag
Total lines740 (src 346, own tests 394)recomputed
Lines of code561 (src 256, own tests 305)recomputed
Classes7recomputed
Methods33recomputed
Fields17recomputed
Bench median ms660recomputed
Bench volume100614 inputs, 40083576 output charsrecomputed

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
MetricValueTag
Mutation score132/145, 0 not run, adjusted 132/133 (1 of 13 survivors fail hidden tests)recomputed
PMD lines0 (0 violations)recomputed
Dependency edges17recomputed
Review scores (superseded)Alterability 5, Code quality principles 3.9, Test quality 4unchecked
Tokens8,825,296 (input 350, cache creation 423,344, cache read 8,305,932, output 95,670)recomputed
Weighted tokens1,423,177recomputed
Wall time0:23:30recomputed
Agent time0:22:24recomputed
Agent calls9recomputed
Audit flags0 high, 1 lowrecomputed

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.

Review scores, 11 rows and 4 columns

Alterability. Item: Change Case 1: sow seeds anti-clockwise. Score /5: 5.

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.

Code quality principles. Item: 1. Clear Presentation. Score /5: 4.

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.

Code quality principles. Item: 2. Explanatory Language. Score /5: 4.

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.

Code quality principles. Item: 3. Consistent Code. Score /5: 3.

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.

Code quality principles. Item: 5. Simple Constructs. Score /5: 4.

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.

Code quality principles. Item: 6. Minimal Duplication. Score /5: 4.

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.

Code quality principles. Item: 7. Modular Structure. Score /5: 4.

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.

Step log, 8 rows and 10 columns

2026-10-26T10:44:11Z. Step: s1. Unit: verb test backlog. Tests added: 0. Cycles: 0. Sha: 3d5a476. Agent: rep-1-r05:s1. Tokens: 361158. Seconds: 65.032.

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.

2026-10-26T10:51:53Z. Step: s3. Unit: pure verb functions. Tests added: 27. Cycles: 14. Sha: 87e3c91. Agent: rep-1-r05:s3. Tokens: 3111938. Seconds: 419.864.

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.

2026-10-26T10:55:51Z. Step: s4. Unit: thin IO shell. Tests added: 4. Cycles: 4. Sha: 1a71ae2. Agent: rep-1-r05:s4. Tokens: 1793065. Seconds: 235.769.

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.

2026-10-26T10:56:52Z. Step: s5. Unit: callee and field map. Tests added: 0. Cycles: 0. Sha: 52debd9. Agent: rep-1-r05:s5. Tokens: 264669. Seconds: 59.548.

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.

2026-10-26T10:57:59Z. Step: s6. Unit: cluster assignment. Tests added: 0. Cycles: 0. Sha: c7b5927. Agent: rep-1-r05:s6. Tokens: 276553. Seconds: 58.455.

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.

2026-10-26T11:01:09Z. Step: s7. Unit: split clusters into noun classes. Tests added: 0. Cycles: 0. Sha: 84eca5d. Agent: rep-1-r05:s7. Tokens: 1213913. Seconds: 198.643.

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.

2026-10-26T11:04:12Z. Step: s8. Unit: green tests, noun-verb map, cross-cluster count, lint. Tests added: 0. Cycles: 0. Sha: 179823c. Agent: rep-1-r05:s8. Tokens: 1131799. Seconds: 176.898.

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.

Figure 14.7

Class diagram of method/rep-1-r05
kalahBoardGameStateKalahMoveResultMoveResult$OutcomeScreenVariant

7 classes and 20 links. Tag: recomputed.

Text version: Class diagram of method/rep-1-r05
  1. Package kalah: Board, GameState, Kalah, MoveResult, Screen, Variant
  2. Outcome, nested in MoveResult
  3. GameState uses Board
  4. GameState holds Board
  5. GameState uses MoveResult
  6. MoveResult uses GameState
  7. MoveResult holds GameState
  8. GameState uses Outcome
  9. GameState uses Variant
  10. GameState holds Variant
  11. Variant uses GameState
  12. Kalah uses GameState
  13. Kalah uses MoveResult
  14. Kalah uses Screen
  15. Kalah uses Variant
  16. MoveResult uses Outcome
  17. MoveResult holds Outcome
  18. Outcome uses MoveResult
  19. Screen uses Board
  20. Screen uses GameState
  21. Screen uses Variant
  22. Variant uses Board
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 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.
Replicate rep-1-r11: functional mark 15/15, strict PMD pass, classes 5, weighted tokens 1,441,834

Replicate rep-1-r11

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 /1515/15
Hidden pass /7676/76
Unseen pass /6969/69
Acceptance pass /66/6
Strict PMD compliancepass

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 setTestsFailuresMarks
Original set, STA 6-4testST64SKHHSH_02/2
STC 6-4testSTC64SKHHSH_03/3
STA 7-5testST75SKHHSH_03/3
STC 7-5testSTC75SKHHSH_03/3
All requirementsall tests04/4
Summary metrics
MetricValueTag
Total lines743 (src 334, own tests 409)recomputed
Lines of code614 (src 255, own tests 359)recomputed
Classes5recomputed
Methods37recomputed
Fields10recomputed
Bench median ms459recomputed
Bench volume100614 inputs, 40083576 output charsrecomputed

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
MetricValueTag
Mutation score114/120, 0 not run, adjusted 114/114 (0 of 6 survivors fail hidden tests)recomputed
PMD lines0 (0 violations)recomputed
Dependency edges9recomputed
Review scores (superseded)Alterability 5, Code quality principles 4.1, Test quality 4unchecked
Tokens9,300,049 (input 370, cache creation 414,809, cache read 8,788,161, output 96,709)recomputed
Weighted tokens1,441,834recomputed
Wall time0:23:51recomputed
Agent time0:22:51recomputed
Agent calls9recomputed
Audit flags2 high, 6 low, 2 of 2 high have a dispositionrecomputed

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.

Review scores, 11 rows and 4 columns

Alterability. Item: Change Case 1: sow seeds anti-clockwise. Score /5: 5.

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.

Code quality principles. Item: 1. Clear Presentation. Score /5: 4.

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.

Code quality principles. Item: 2. Explanatory Language. Score /5: 4.

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.

Code quality principles. Item: 3. Consistent Code. Score /5: 4.

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.

Code quality principles. Item: 5. Simple Constructs. Score /5: 4.

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.

Code quality principles. Item: 6. Minimal Duplication. Score /5: 4.

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.

Code quality principles. Item: 7. Modular Structure. Score /5: 4.

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.

ChannelAgent or commitToolPath
outside_pathrep-1-r11:measureBash~
outside_pathrep-1-r11:measureBash~
ChannelMarkerDisposition
outside_path~false positive
outside_path~false positive
Channeloutside_path
DefinitionA 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.

outside_path. Verdict: false positive. Tag: verified.

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.

Step log, 8 rows and 10 columns

2026-10-26T10:03:08Z. Step: s1. Unit: spec verb backlog. Tests added: 0. Cycles: 0. Sha: bacc8ea. Agent: rep-1-r11:s1. Tokens: 293960. Seconds: 43.411.

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.

2026-10-26T10:11:05Z. Step: s3. Unit: pure verb functions. Tests added: 27. Cycles: 14. Sha: ba4f387. Agent: rep-1-r11:s3. Tokens: 3394223. Seconds: 436.934.

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.

2026-10-26T10:15:15Z. Step: s4. Unit: thin I/O shell. Tests added: 4. Cycles: 4. Sha: 804fddc. Agent: rep-1-r11:s4. Tokens: 2172827. Seconds: 252.228.

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.

2026-10-26T10:16:07Z. Step: s5. Unit: callee and field table. Tests added: 0. Cycles: 0. Sha: 7fbd64b. Agent: rep-1-r11:s5. Tokens: 190167. Seconds: 40.593.

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.

2026-10-26T10:17:14Z. Step: s6. Unit: assign functions to records. Tests added: 0. Cycles: 0. Sha: 4dab5a4. Agent: rep-1-r11:s6. Tokens: 284545. Seconds: 69.459.

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.

2026-10-26T10:20:05Z. Step: s7. Unit: split over-cap cluster into noun classes. Tests added: 0. Cycles: 0. Sha: caf4bd1. Agent: rep-1-r11:s7. Tokens: 1121024. Seconds: 173.144.

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.

2026-10-26T10:23:39Z. Step: s8. Unit: green, noun-verb map, cross-cluster count, lint. Tests added: 0. Cycles: 0. Sha: 1d1def7. Agent: rep-1-r11:s8. Tokens: 1081428. Seconds: 214.035.

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.
Replicate rep-1-r12: functional mark 15/15, strict PMD pass, classes 7, weighted tokens 1,357,139

Replicate rep-1-r12

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 /1515/15
Hidden pass /7676/76
Unseen pass /6969/69
Acceptance pass /66/6
Strict PMD compliancepass

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 setTestsFailuresMarks
Original set, STA 6-4testST64SKHHSH_02/2
STC 6-4testSTC64SKHHSH_03/3
STA 7-5testST75SKHHSH_03/3
STC 7-5testSTC75SKHHSH_03/3
All requirementsall tests04/4
Summary metrics
MetricValueTag
Total lines635 (src 320, own tests 315)recomputed
Lines of code513 (src 245, own tests 268)recomputed
Classes7recomputed
Methods35recomputed
Fields9recomputed
Bench median ms218recomputed
Bench volume100614 inputs, 40083576 output charsrecomputed

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
MetricValueTag
Mutation score115/129, 0 not run, adjusted 115/124 (9 of 14 survivors fail hidden tests)recomputed
PMD lines0 (0 violations)recomputed
Dependency edges17recomputed
Review scores (superseded)Alterability 4.5, Code quality principles 3.5, Test quality 4unchecked
Tokens8,017,588 (input 316, cache creation 413,466, cache read 7,510,917, output 92,889)recomputed
Weighted tokens1,357,139recomputed
Wall time0:22:13recomputed
Agent time0:21:05recomputed
Agent calls9recomputed
Audit flags0 high, 0 lowrecomputed

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.

Review scores, 11 rows and 4 columns

Alterability. Item: Change Case 1: sow seeds anti-clockwise. Score /5: 4.

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.

Code quality principles. Item: 1. Clear Presentation. Score /5: 4.

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.

Code quality principles. Item: 2. Explanatory Language. Score /5: 3.

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.

Code quality principles. Item: 3. Consistent Code. Score /5: 3.

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.

Code quality principles. Item: 5. Simple Constructs. Score /5: 4.

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.

Code quality principles. Item: 6. Minimal Duplication. Score /5: 3.

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.

Code quality principles. Item: 7. Modular Structure. Score /5: 3.

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.

Step log, 8 rows and 10 columns

2026-10-26T10:03:03Z. Step: s1. Unit: verb test backlog. Tests added: 0. Cycles: 0. Sha: 39ad808. Agent: rep-1-r12:s1. Tokens: 195529. Seconds: 35.829.

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.

2026-10-26T10:11:01Z. Step: s3. Unit: pure functions per verb. Tests added: 23. Cycles: 18. Sha: 5ee6d9f. Agent: rep-1-r12:s3. Tokens: 3328667. Seconds: 433.777.

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.

2026-10-26T10:14:51Z. Step: s4. Unit: thin IO shell. Tests added: 3. Cycles: 4. Sha: 3152795. Agent: rep-1-r12:s4. Tokens: 1838618. Seconds: 224.568.

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.

2026-10-26T10:15:45Z. Step: s5. Unit: callee and field map. Tests added: 0. Cycles: 0. Sha: a1cd0a6. Agent: rep-1-r12:s5. Tokens: 191780. Seconds: 50.572.

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.

2026-10-26T10:19:05Z. Step: s7. Unit: split over-cap clusters into noun classes. Tests added: 0. Cycles: 0. Sha: d46093f. Agent: rep-1-r12:s7. Tokens: 510318. Seconds: 144.426.

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.

2026-10-26T10:22:14Z. Step: s8. Unit: green tests, noun-verb map, cross-cluster count, lint. Tests added: 0. Cycles: 0. Sha: 8e75bde. Agent: rep-1-r12:s8. Tokens: 1039887. Seconds: 198.038.

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.
Replicate rep-1-r16: functional mark 15/15, strict PMD pass, classes 5, weighted tokens 1,463,588

Replicate rep-1-r16

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 /1515/15
Hidden pass /7676/76
Unseen pass /6969/69
Acceptance pass /66/6
Strict PMD compliancepass

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 setTestsFailuresMarks
Original set, STA 6-4testST64SKHHSH_02/2
STC 6-4testSTC64SKHHSH_03/3
STA 7-5testST75SKHHSH_03/3
STC 7-5testSTC75SKHHSH_03/3
All requirementsall tests04/4
Summary metrics
MetricValueTag
Total lines618 (src 356, own tests 262)recomputed
Lines of code492 (src 276, own tests 216)recomputed
Classes5recomputed
Methods44recomputed
Fields13recomputed
Bench median ms268recomputed
Bench volume100614 inputs, 40083576 output charsrecomputed

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
MetricValueTag
Mutation score116/146, 0 not run, adjusted 116/138 (22 of 30 survivors fail hidden tests)recomputed
PMD lines0 (0 violations)recomputed
Dependency edges9recomputed
Review scores (superseded)Alterability 5, Code quality principles 3.8, Test quality 4unchecked
Tokens8,779,468 (input 316, cache creation 427,528, cache read 8,248,335, output 103,289)recomputed
Weighted tokens1,463,588recomputed
Wall time0:23:53recomputed
Agent time0:22:46recomputed
Agent calls9recomputed
Audit flags0 high, 5 lowrecomputed

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.

Review scores, 11 rows and 4 columns

Alterability. Item: Change Case 1: sow seeds anti-clockwise. Score /5: 5.

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.

Code quality principles. Item: 1. Clear Presentation. Score /5: 4.

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'.

Code quality principles. Item: 2. Explanatory Language. Score /5: 4.

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.

Code quality principles. Item: 3. Consistent Code. Score /5: 4.

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.

Code quality principles. Item: 5. Simple Constructs. Score /5: 4.

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.

Code quality principles. Item: 6. Minimal Duplication. Score /5: 3.

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.

Code quality principles. Item: 7. Modular Structure. Score /5: 3.

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.

Step log, 8 rows and 10 columns

2026-10-26T10:44:04Z. Step: s1. Unit: verb test backlog. Tests added: 0. Cycles: 0. Sha: e8a40b9. Agent: rep-1-r16:s1. Tokens: 255107. Seconds: 57.814.

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.

2026-10-26T10:53:13Z. Step: s3. Unit: verbs as pure functions. Tests added: 26. Cycles: 24. Sha: 8a57ef1. Agent: rep-1-r16:s3. Tokens: 4321775. Seconds: 505.583.

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.

2026-10-26T10:54:42Z. Step: s4. Unit: thin IO shell. Tests added: 0. Cycles: 3. Sha: a5e68ae. Agent: rep-1-r16:s4. Tokens: 563911. Seconds: 82.08.

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.

2026-10-26T10:55:31Z. Step: s5. Unit: callee and field table. Tests added: 0. Cycles: 0. Sha: c117996. Agent: rep-1-r16:s5. Tokens: 191181. Seconds: 43.902.

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.

2026-10-26T11:02:47Z. Step: s7. Unit: clusters into noun classes. Tests added: 0. Cycles: 6. Sha: 34b2577. Agent: rep-1-r16:s7. Tokens: 1986497. Seconds: 355.187.

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.

2026-10-26T11:04:50Z. Step: s8. Unit: green tests, noun-verb map, cross-cluster count, lint. Tests added: 0. Cycles: 1. Sha: 3b47a95. Agent: rep-1-r16:s8. Tokens: 615627. Seconds: 122.008.

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.

Terms and columns

See Methodology: measures and the Glossary for the terms and table columns this page uses.