Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .claude/skills/refactoring-entry/LESSONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,40 @@ brief.md or workflow.js and deleted here. Keep the calibration table current.
marker-ended arrows entirely inside that gutter, and low-alpha highlight fills with
no stroke. Render the actual SVG at desktop and mobile widths before assembly.
→ folded into workflow.js's diagram prompt.

## 2026-09-04 — 03 Replace global state with parameter

- **Toolchain.** The system gem dir is not writable on this machine: `bundle install`
fails building native extensions. `bundle config set --local path vendor/bundle`
works; `/vendor/` added to `.gitignore` in this PR. Headless
`google-chrome-stable` exists for layout screenshots; a scratch
`python3 -m http.server` must be started with `setsid nohup ... &` or it dies
when the terminal command returns (first screenshot attempt was
ERR_CONNECTION_REFUSED).
- **Rule.** In scala-hedgehog `withTests` is a member of `Test`, not `Property`:
`property("name", prop).withTests(500)`. The brief's old line misleads into a
compile error. -> reworded in brief.md.
- **Rule.** Haskell model of a global mutable cell: top-level `IORef` CAF
(`{-# NOINLINE x #-}` + `unsafePerformIO (newIORef ...)`). Before's entry point
lives in IO, After's is pure; the spec compares projections (label lists), per
the lens entry's compare-results rule. A spec whose properties touch the same
top-level cell must use `checkSequential` — `checkParallel` races the cell.
- **Harness.** `edit_file` cannot create files inside a fresh untracked directory
tree (VFS error "path changed from ... to ..."); write sources with a terminal
heredoc and `git add` them immediately — the mirror then tracks them. Recorded
so the next run loses no time to it.
- **Reviewer (self-run mutation round).** Every mutant caught, none survived:
ex1 fee ignoring its parameter (caught by the purpose property only), fee
applied twice (agreement); ex2 `<` vs `<=` in both languages (shrunk to
`List(-30)` within 5 tests), limit hard-coded (purpose property only); ex3
increment-by-2 and no-increment (Scala), right-subtree-first numbering
(Haskell), forcing the `Lit` payload (Haskell, laziness probe only).
- **Citation skeptic.** martinfowler.com/bliki GlobalVariables.html and
Singleton.html are 404; refactoring.com/catalog/globalData.html is 404 — the
smells live in the 2e book, cite the book. misko.hevery.com is unreachable —
cite the Google Testing Blog post that quotes the same claims. "State-passing
style" does not occur verbatim in State in Haskell; it says "state
transformer" — write it that way.
- **Time.** Single agent, no batch runner (workflow.js is Claude-Code-only):
research + citation checks ~15 min, examples + specs ~25 min, mutation round
~15 min, page + diagrams + screenshots ~30 min.
7 changes: 6 additions & 1 deletion .claude/skills/refactoring-entry/brief.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ pages/refactorings/<slug>/
val r = Property.check(t.withConfig(PropertyConfig.default), t.result, Seed.fromTime())
println(Test.renderReport("Props", t, r, ansiCodesSupported = false)); r.status }
if !results.forall(_ == Status.ok) then sys.exit(1)
`.withTests(500)` on a property raises its count.
Bump a property's count with `property("name", prop).withTests(500)`:
`withTests` is a member of `Test`, not `Property`.
- Haskell: GHC 9.8.4 + hedgehog. No ghc on this machine; docker image `cp-hedgehog` (run.sh builds it if missing):
docker run --rm -v "$PWD/pages/refactorings/<slug>:/w" -w /w cp-hedgehog runghc -iNN-ex NN-ex/Spec.hs
(from the repo root). Spec.hs: `main = do ok <- checkParallel (Group "Props" [...]); unless ok exitFailure`.
Expand All @@ -86,6 +87,10 @@ pages/refactorings/<slug>/
- A second property per example is expected when natural (the extracted piece on its own; a law it obeys).
- In Haskell probe laziness/strictness with `undefined` where the refactoring could change what is forced;
in Scala reason about evaluation count (def vs val, by-name) and say so in a comment when it matters.
- If an example needs a global MUTABLE cell in Haskell: a top-level `IORef` CAF
(`{-# NOINLINE x #-}` + `unsafePerformIO (newIORef ...)`); Before's entry point lives
in IO, After's is pure — the type change is the point, say so on the page. Compare
projections (the lens rule), and use `checkSequential` when properties touch the cell.

## Style
- Site prose: crisp, editorial, no hype; sentences, not bullets, in body text (the caveats section may use a list).
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,6 @@ dist-newstyle/

# Local linting venv (see README "Lint")
/.venv/

# Bundler local gem path (bundle config set --local path vendor/bundle)
/vendor/
1 change: 1 addition & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ plugins: [jekyll-paginate, jekyll-seo-tag, jekyll-feed, jekyll-remote-theme]
exclude:
- pages/refactorings/extract-method/
- pages/refactorings/replace-mutable-fields-with-lenses/
- pages/refactorings/replace-global-state-with-parameter/
2 changes: 1 addition & 1 deletion _data/refactorings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
items:
- { name: "Extract / Inline method", slug: extract-method }
- { name: "Replace mutable fields with lenses", slug: replace-mutable-fields-with-lenses }
- { name: "Replace global state with parameter" }
- { name: "Replace global state with parameter", slug: replace-global-state-with-parameter }
- { name: "Introduce ReaderT (Kleisli)" }
- { name: "Replace loop with fold" }
- { name: "Replace recursion with fold" }
Expand Down
Loading
Loading