Skip to content

Fix analyzer blind spot for constant mutation via add/remove/clear - #674

Merged
logbie merged 9 commits into
mainfrom
claude/issue-671-8ufs93
Jul 31, 2026
Merged

Fix analyzer blind spot for constant mutation via add/remove/clear#674
logbie merged 9 commits into
mainfrom
claude/issue-671-8ufs93

Conversation

@logbie

@logbie logbie commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes issue #671: the analyzer failed to report mutations of constants via add ... to, remove ... from, and clear statements. These statements parse to AddToListStatement, RemoveFromListStatement, and ClearListStatement (not Assignment), and the analyzer never checked their targets for constness. A program mutating a constant five ways (via change, add, subtract, multiply, divide) received only four error reports.

Key Changes

  • Analyzer constant tracking (src/analyzer/mod.rs):

    • Added constant_bindings: HashSet<SymbolBindingKey> to track symbols declared with store new constant. This is deliberately not the mutable: false flag, which is overloaded — action parameters, container-method parameters, loop variables, try/when error bindings, predefined globals, and REPL parent-scope variables are all immutable without being constants. Keyed by binding rather than by name so an inner-scope shadow is not mistaken for the constant.
    • Populate the set in VariableDeclaration when is_constant is true.
    • Added report_constant_mutation() to check bare-name mutation targets against the set and emit the same message as the assignment path, applied to the AddToListStatement, RemoveFromListStatement, and ClearListStatement arms.
    • Carry the marker across scope merges: a constant declared on both arms of a check is re-defined into the parent scope under a new binding key, so promote_constant_marker moves the marker with it. Without this, change still reported while add/remove/clear went silent — Analyzer drops the add ... to CONST report when combined with other constant mutations #671 again, one scope up.
    • Guard against over-reporting: parameters, loop variables, and container members remain unaffected, with negative tests for each.
  • Documentation (Docs/03-language-basics/variables-and-types.md, Docs/06-best-practices/naming-conventions.md):

  • Tests (tests/constant_mutation_analyzer_test.rs, tests/diagnostics_fixtures_test.rs):

  • Documentation examples (TestPrograms/docs_examples/basic_syntax/):

    • constants_01.wfl (executable) and constants_immutable_01.wfl (error example, carries the // CI-SKIP: directive the CI sweep requires). Both registered in the manifest and validated.
  • Dev Diary (History/dev-diary/2026/2026-07-31-issue-671-constant-mutation-reports.md).

Compatibility

No shipped program mutates a constant. The one genuine behavior change is constant lists: add/remove/clear on one previously succeeded silently at runtime — the interpreter mutates the Rc<RefCell<Vec<Value>>> in place, so Environment::assign's constant check was never reached — and is now rejected at analysis time. That is the documented meaning of constant, and no shipped example relied on it.

Risk class: R3. An earlier revision said R1 on the grounds that this only adds a front-end diagnostic; that was wrong. testing.md §5 places backward compatibility in R3 outright and forbids lowering the class, and the constant-list change above is a backward-compatibility change. §11.3 does not apply — no concurrency, cancellation, lifecycle, streaming, untrusted input, or crypto. The applicable evidence is failure-path and negative coverage, listed in the dev diary.

Testing

Figures are for the current head, after merging main twice — #672 (Repository Hygiene and Layout Policy) and then #675 (its CI follow-ups).

  • Red→Green TDD: constant_mutation_analyzer_test.rs added as a test-only Red commit (1be7188, ancestor of the Green 29c9ddf).
  • cargo test --all --no-fail-fast — 151 test binaries, 0 failures.
  • cargo clippy --all-targets --all-features -- -D warnings, cargo fmt --all -- --check.
  • CI program sweep over TestPrograms/ against the release binary — 142 passed, 0 failed, 49 skipped.
  • python scripts/validate_docs_examples.py --force — 21/21.
  • python3 -m unittest discover -s tests/tooling — 33 passed; hygiene static and working-tree both exit 0.

CI is green on ed3b36d: 17 checks passed, 1 correctly skipped (Bump Version does not run on PRs).

Note on scope

Intermediate revisions of this branch also carried CI repairs (Windows setup-python, the extension lockfile version, and archive-manifest CRLF drift). #675 landed equivalent fixes on main while this was open, so the merge dropped all of them in favour of what is already merged — including removing the .gitattributes approach this branch used, since main instead hashes the staged blob in the checker. The diff is analyzer-only again.

Follow-up filed

#673push with CONST and value still mutates a constant list. PushStatement carries its target as an Expression rather than a bare name, so the check added here has nothing to resolve; fixing it needs write-target resolution through expressions.

https://claude.ai/code/session_01H9AnrAH4d8sTni73GWgkjR


``<img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open in Devin Review">``

Summary by CodeRabbit

  • New Features

    • Constants now prevent reassignment and add, remove, and clear mutations across supported scopes, including conditional branches.
    • Added examples demonstrating constant declarations, usage, and invalid mutation attempts.
  • Bug Fixes

    • Improved diagnostics when attempting to modify constants.
  • Documentation

    • Expanded guidance on constant behavior, naming conventions, and list-content considerations.
  • Chores

    • Improved repository hygiene checks and cross-platform test consistency.

claude added 2 commits July 31, 2026 05:01
`add <value> to <name>` parses to `AddToListStatement`, which the analyzer
never checked for constness, so a program mutating a constant five ways got
only four `Cannot modify constant` reports. The same hole covers `remove
... from CONST` and `clear CONST`.

Red evidence: `add_to_constant_is_rejected_on_its_own` (0 reports, expected
1), `every_mutation_form_of_a_constant_is_reported` (4, expected 5) and
`list_mutation_statements_reject_constant_targets` (0, expected 3) all fail;
`mutable_targets_are_still_accepted` already passes and guards the fix
against over-reporting.

Refs #671

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9AnrAH4d8sTni73GWgkjR
`add <value> to <name>` parses to `AddToListStatement` rather than
`Assignment` — the target's type is unknown at parse time, so the interpreter
decides between arithmetic and list-append at runtime. The analyzer's
`AddToListStatement` arm only checked that the target name was defined, never
that it was writable, so `add 10 to MAX_SIZE` on a constant produced no
report. A program mutating one constant five ways got four reports instead of
five; the `add` line was the silent one. `remove ... from` and `clear` had the
same hole.

The analyzer now tracks constants explicitly in a `constant_bindings` set of
`SymbolBindingKey`s, populated when a `store new constant` declaration is
defined, and `report_constant_mutation` consults it from all three bare-name
mutation statements. Keying by binding rather than name keeps an inner-scope
shadow from being mistaken for the constant.

`SymbolKind::Variable { mutable: false }` deliberately is NOT the test: action
parameters, container-method parameters, loop variables, try/when error
bindings, predefined globals, and REPL parent-scope variables are all
registered immutable without being constants, and appending to a list
parameter has always been legal.

This also closes a runtime hole for constant lists. A constant number was
already rejected at runtime via `Environment::assign`, but `add`/`remove`/
`clear` on a constant list mutated the `Rc<RefCell<Vec<Value>>>` in place
without ever reassigning the binding, so the constant check was bypassed.

Docs: `Docs/03-language-basics/variables-and-types.md` claimed constants were
"planned for future versions" and `Docs/06-best-practices/naming-conventions.md`
that "true immutability is limited today" — both false since `store new
constant` shipped. Both now document the real syntax and the rejected mutation
forms, with two validated examples registered in the docs-examples manifest.

Verified: `cargo test --all --no-fail-fast` (150 suites, 0 failures), clippy
`-D warnings`, `cargo fmt --check`, 111 `TestPrograms/` programs against the
release binary, and `scripts/validate_docs_examples.py` (21/21).

Fixes #671

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9AnrAH4d8sTni73GWgkjR
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The analyzer now tracks constants by binding identity and rejects scalar and supported list mutations. Documentation and executable examples describe the behavior. Regression tests cover scope propagation and mutable exceptions. CI and repository hygiene checks now preserve file integrity across platforms.

Changes

Constant mutation analysis

Layer / File(s) Summary
Track and report constant mutations
src/analyzer/mod.rs
The analyzer records successful constant bindings, propagates constant status across conditional and child scopes, and reports add, remove, and clear mutations. Other immutable bindings remain mutable.
Validate mutation diagnostics
tests/constant_mutation_analyzer_test.rs, tests/diagnostics_fixtures_test.rs
Tests cover scalar and list constants, branch promotion, mutable parameters and loop variables, container members, and all five diagnostic forms.
Document and validate constant usage
Docs/03-language-basics/variables-and-types.md, Docs/06-best-practices/naming-conventions.md, TestPrograms/docs_examples/*, History/dev-diary/2026/2026-07-31-issue-671-constant-mutation-reports.md
Documentation, examples, manifests, and the development diary describe constant syntax, rejected mutations, naming guidance, and the unchecked push with form.

Repository hygiene and CI setup

Layer / File(s) Summary
Configure repository integrity checks
.gitattributes, .repo-hygiene.toml, .github/workflows/ci.yml, tests/tooling/test_check_repo_hygiene.py
Repository rules preserve archive line endings, CI installs Python 3.x for hygiene jobs, and fixture writes use explicit UTF-8 bytes with SHA-256 coverage.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary analyzer fix for constant mutations through add, remove, and clear statements.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-671-8ufs93

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

# Conflicts:
#	History/dev-diary/2026/2026-07-31-issue-671-constant-mutation-reports.md
#	TestPrograms/docs_examples/_meta/validation_cache.json

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 29c9ddf46c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +294 to +296
A constant is read exactly like any other variable — the only difference is
that WFL refuses to modify it. Every mutation form is rejected before the
program runs, so you get the error at check time, not halfway through a run:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Limit the claim to mutation forms actually checked

push with ALLOWED_ROLES and "guest" still passes analysis and mutates the constant list: the analyzer's Statement::PushStatement arm only analyzes its expressions, while the interpreter pushes directly into the list without checking whether its binding is constant. Therefore the new claim that every mutation form is rejected before execution is false; either enforce constness for PushStatement targets or document this exception.

Useful? React with 👍 / 👎.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

Comment on lines +294 to +296
A constant is read exactly like any other variable — the only difference is
that WFL refuses to modify it. Every mutation form is rejected before the
program runs, so you get the error at check time, not halfway through a run:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Documentation promises constants can never be changed, but one list-modifying statement still changes them

The constants documentation states that WFL refuses every attempt to modify a constant (Docs/03-language-basics/variables-and-types.md:294-296), yet the push with <list> and <value> form still modifies a constant list at run time without any complaint, so readers are told a guarantee the language does not provide.

Impact: A user can follow the documentation, rely on a constant list being unchangeable, and still have it silently modified at run time.

Why the claim is inaccurate: PushStatement bypasses the new analyzer check

The new analyzer check only covers AddToListStatement, RemoveFromListStatement and ClearListStatement (src/analyzer/mod.rs:2444-2484). Statement::PushStatement takes an arbitrary expression target and is only expression-analyzed (src/analyzer/mod.rs:2358-2361), and the interpreter pushes directly into the list's Rc<RefCell<Vec<Value>>> so Environment::assign's constant check never runs. The PR's own dev diary acknowledges this (Dev diary/2026-07-31-issue-671-constant-mutation-reports.md:182-188, "Follow-up not taken").

The same overclaim appears in Docs/06-best-practices/naming-conventions.md:210-212 ("WFL then rejects every attempt to modify it"). CLAUDE.md's mandatory "Docs Must Be Honest" rule requires docs to describe what actually ships and to explicitly mark gaps/planned behavior.

Suggested change
A constant is read exactly like any other variable — the only difference is
that WFL refuses to modify it. Every mutation form is rejected before the
program runs, so you get the error at check time, not halfway through a run:
A constant is read exactly like any other variable — the only difference is
that WFL rejects the mutation forms below before the program runs, so you get
the error at check time, not halfway through a run:
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/analyzer/mod.rs`:
- Around line 958-963: Update all scope-merge and binding-promotion paths around
the `Symbol` merges at the locations corresponding to lines 1143 and 1219 so
`constant_bindings` markers move from each child-scope key to the promoted
parent-scope key. Preserve the marker when resolving later mutations through the
parent binding, and add a regression program declaring a constant list in both
conditional branches before mutating it after the branch, exercising the actual
scope-merge boundary.

In `@tests/constant_mutation_analyzer_test.rs`:
- Around line 103-106: Update the test’s loop body so the loop variable entry is
the mutation target, while gathered is only the source collection. Iterate over
list values and invoke add, remove, and clear using entry as the target,
ensuring the test verifies loop bindings are excluded from constant_bindings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f12fff2a-91e3-44fe-a4a8-5ed2084ca15f

📥 Commits

Reviewing files that changed from the base of the PR and between 438780a and 29c9ddf.

📒 Files selected for processing (9)
  • Dev diary/2026-07-31-issue-671-constant-mutation-reports.md
  • Docs/03-language-basics/variables-and-types.md
  • Docs/06-best-practices/naming-conventions.md
  • TestPrograms/docs_examples/_meta/manifest.json
  • TestPrograms/docs_examples/_meta/validation_cache.json
  • TestPrograms/docs_examples/basic_syntax/constants_01.wfl
  • TestPrograms/docs_examples/basic_syntax/constants_immutable_01.wfl
  • src/analyzer/mod.rs
  • tests/constant_mutation_analyzer_test.rs

Comment thread src/analyzer/mod.rs
Comment thread tests/constant_mutation_analyzer_test.rs Outdated
`tests/diagnostics_fixtures_test.rs::constant_mutation_is_rejected_for_every_mutation_form`
landed with #672 pinning `>= 4` reports and a comment pointing at #671. With
the analyzer gap closed the fixture reports all five, so the assertion is now
an exact `== 5`.

Also updates the dev diary for the new repository layout: the file moved to
`History/dev-diary/2026/`, and the prose now points at
`tests/fixtures/diagnostics/constant_immutability.wfl` rather than the retired
`syntax_test/` tree.

Refs #671

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9AnrAH4d8sTni73GWgkjR
@blacksmith-sh

This comment has been minimized.

Addresses three review findings on #674.

Constant markers survive a branch merge. `IfStatement`/`SingleLineIf`
re-define a binding created on both arms into the parent scope under a new
binding key, and `pop_scope_promoting_except` does the same for promoted
scopes — neither moved the `constant_bindings` entry. The symbol keeps
`mutable: false`, so `change` still reported while `add`/`remove`/`clear`
went silent: the #671 defect again, one scope up. Both paths now carry the
marker, mirroring the existing `mutable: then && else` merge (constant on
either arm means constant after). Before the fix, `add 1 to LIMIT` after such
a branch escaped analysis entirely and was caught only at runtime; for a
constant list it would not have been caught at all.

Docs no longer overclaim. "WFL refuses to modify it" / "every mutation form is
rejected" is false while `push with <list> and <value>` is unchecked. Both
pages now enumerate the forms that are actually checked and carry an explicit
known-gap callout pointing at #673, plus a note that a constant fixes the
binding rather than the contents reached through an alias.

The loop-variable negative test now tests its claim. It previously used
`add entry to gathered`, where the loop variable is the value and `gathered`
is the target. Rewritten so the loop variable is the mutation target, scoped to
the bare-name statements this change touches: `subtract`/`multiply` on a loop
variable do report, via the `Assignment` path's long-standing `mutable: false`
check, which is pre-existing and out of scope.

Also adds a `// CI-SKIP:` directive to `constants_immutable_01.wfl`. The CI
"Run WFL Programs" sweep covers `TestPrograms/docs_examples/` (unlike
run_integration_tests.sh) and expects exit 0, so the intentional error example
failed it. The docs validator still checks it statically and asserts it fails
semantic analysis.

Refs #671, #673

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9AnrAH4d8sTni73GWgkjR

logbie commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

Round 1 of review addressed in 4cc10ba. All four findings were real; three needed code or doc changes, and one was a genuine CI failure of mine.

1. Docs overclaimed — fixed (@chatgpt-codex-connector, @devin-ai-integration)

Both correct, and this was the worst of the batch: I filed the push gap as #673 but then wrote docs claiming a guarantee the language does not provide. Fixed on both pages — they now enumerate the forms that are checked, and variables-and-types.md carries an explicit known-gap callout:

Known gap: push with <list> and <value> is not yet checked. Pushing onto a constant list currently succeeds silently, at both check time and run time — see issue #673.

I also added the aliasing caveat, which neither reviewer raised but is the same class of overclaim: store alias as CONST_LIST then mutating alias changes the underlying list, because a constant fixes the binding, not the contents reached through it.

2. Constant marker lost across a branch merge — fixed (@coderabbitai)

Confirmed before fixing. On the previous head:

check if yes:
    store new constant LIMIT as 10
otherwise:
    store new constant LIMIT as 20
end check
change LIMIT to 5      // reported at analysis (exit 3)
add 1 to LIMIT         // NOT reported — only caught at runtime (exit 1)

IfStatement/SingleLineIf re-define the merged binding into the parent scope under a new SymbolBindingKey, so constant_bindings lost it while the symbol kept mutable: false — which is exactly why change still reported and add did not. #671 again, one scope up. And for a constant list the runtime would not have caught it either, since push-in-place never reaches Environment::assign.

Both merge paths now carry the marker, mirroring the existing mutable: then && else rule (constant on either arm ⇒ constant after). pop_scope_promoting_except migrates it too, alongside the alias state it already moved. Regression test constants_promoted_out_of_a_branch_are_still_constants asserts all five mutation forms report exactly once after the branch.

3. Loop-variable test didn't test its claim — fixed (@coderabbitai)

Right — entry was the value and gathered was the target. Rewritten so the loop variable is the mutation target.

Worth recording what that surfaced: with entry as the target, subtract 1 from entry and multiply entry by 2 do report Cannot modify constant 'entry'. That is not a regression from this PR — those desugar to Assignment, whose long-standing mutable: false check treats loop variables and action parameters as constants. Pre-existing and out of scope here, so the test now covers only the bare-name statements this change touches. I'll file that misleading message separately.

4. CI: Run WFL Programs (ubuntu) — fixed

This one was mine. The CI sweep walks TestPrograms/docs_examples/ (unlike run_integration_tests.sh, which excludes it) and expects exit 0, so my intentional error example constants_immutable_01.wfl failed it. Added the sanctioned // CI-SKIP: first-line directive; the docs validator still checks it statically and asserts it fails semantic analysis. Local CI-style sweep now reports 142 passed / 0 failed / 49 skipped, matching CI's shape.

Remaining red is not from this PR

Main's own CI run for 438780a (run 30607638911) fails the same three jobs:

Job Failure Verified pre-existing
Repository Hygiene (ubuntu-arm / windows) version-drift: vscode-extension/package-lock.json: 26.7.59 disagrees with Cargo.toml (26.7.60), and on Windows test_archive_valid_manifest_passes sha256 drift on Archive/old/plan.md (CRLF checkout) ✅ reproduced on a clean origin/main worktree
Integration Tests (windows) HYGIENE-ERROR: Python 3.11+ with tomllib is required — the Windows step uses python, the Linux step python3 ✅ fails on main
Run WFL Programs (windows) same Windows hygiene precondition ✅ fails on main

Happy to take any of those in a separate PR — the lock-file bump and the pythonpython3 step are both small — but they belong to #672's tooling, not to this change.

Verification on 4cc10ba

cargo test --all --no-fail-fast — 151 suites; cargo clippy --all-targets --all-features -- -D warnings; cargo fmt --all -- --check; CI-style TestPrograms/ sweep 142/0/49; validate_docs_examples.py 21/21.

One flake to flag honestly: interpreter::process_tests::test_capture_process_output failed once under the full parallel run in my sandbox and passed 4/4 in isolation. It is a subprocess-capture test unrelated to this change and green in CI; I did not touch it.


Generated by Claude Code

@chatgpt-codex-connector

Copy link
Copy Markdown

Review Follow-up

The changes in 4cc10ba address the four reported findings:

No additional code changes were needed, so I did not create a commit or follow-up pull request.

Testing

  • cargo test --test constant_mutation_analyzer_test — 7 passed, 0 failed.
  • git status --short --branch — working tree remained clean.

View task →

Three failures that predate this branch and reproduce on main's own run for
438780a. They are unrelated to #671 but block this PR from going green.

1. Windows jobs had no Python. `integration-tests` and `run-wfl-programs` call
   scripts/check_repo_hygiene.py as their final step but never ran
   actions/setup-python, so on Windows they got the runner's bundled `python`
   (pre-3.11) and died with "HYGIENE-ERROR: Python 3.11+ with tomllib is
   required" regardless of what the job actually tested — the WFL sweep
   (141 passed, 0 failed), the docs-example validation (21/21) and the web
   tests (3/3) had all passed by then. Both jobs now set up Python 3.x, as
   `repo-hygiene` and `bump-version` already did. Switching Windows to
   `python3` was the other option and was rejected: on Windows that name can
   resolve to the Microsoft Store app-execution alias.

   `nightly.yml` also runs Python on Windows without setup-python, but only
   calls bump_version.py, which is stdlib-only and needs no tomllib — left
   alone.

2. Fixture writes were not byte-exact. FixtureTree.write used
   Path.write_text, which applies the platform newline translation, so on
   Windows every "\n" landed as "\r\n". test_archive_valid_manifest_passes
   computes its expected sha256 from the LF form, so it could never match the
   file the checker hashed — green on Linux, red on Windows. It now writes the
   caller's exact UTF-8 bytes (which also stops the locale encoding leaking
   in), with test_fixture_write_is_byte_exact pinning the behaviour.

3. The extension lockfile was stale. vscode-extension/package-lock.json still
   read 26.7.59 against Cargo.toml's 26.7.60, failing the static gate with
   "version-drift". bump_version.py already knows to update both mirrors; the
   26.7.60 bump landed without it. Set to the values that script would write.

Verified: `python3 -m unittest discover -s tests/tooling` 33 passed;
`check_repo_hygiene.py --mode static` now exits 0 (was 1 on a clean
origin/main worktree).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9AnrAH4d8sTni73GWgkjR

logbie commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

b609baf clears the three CI failures that predate this branch, at Brad's request. They are unrelated to #671 — flagging that clearly since this PR now carries CI changes alongside the analyzer fix, and they should be read as a separate concern when reviewing.

All three reproduce on main's own run for 438780a (run 30607638911).

1. Windows jobs never set up Python

integration-tests and run-wfl-programs end with scripts/check_repo_hygiene.py, but neither ran actions/setup-python — so on Windows they got the runner's bundled pre-3.11 python and died with HYGIENE-ERROR: Python 3.11+ with tomllib is required, no matter what the job actually tested. The logs make that plain: by the time it failed, Integration Tests (windows) had already passed docs validation 21/21 and web tests 3/3, and Run WFL Programs (windows) had reported Passed: 141 Failed: 0 Skipped: 50.

Both jobs now set up Python 3.x, matching what repo-hygiene and bump-version already did. I deliberately did not switch the Windows steps to python3: on Windows that name can resolve to the Microsoft Store app-execution alias, which would trade one confusing failure for another.

nightly.yml also runs Python on Windows without setup-python, but only calls bump_version.py, which is stdlib-only and needs no tomllib — left alone.

2. FixtureTree.write was not byte-exact

test_archive_valid_manifest_passes failed on Windows only. Path.write_text applies the platform newline translation, so "ancient plan\n" hit disk as ancient plan\r\n, while the fixture's expected sha256 is computed from the LF form — the hash could never match the file the checker reads. Green on Linux, red on Windows, for a reason that has nothing to do with the archive-manifest logic being tested.

It now writes the caller's exact UTF-8 bytes, which also stops the locale encoding leaking in on Windows. test_fixture_write_is_byte_exact pins it. Honest note on evidence: Red for this one is only observable on Windows — the CI log linked above is the Red, since the assertion cannot fail on a Linux runner either way.

3. Stale extension lockfile

vscode-extension/package-lock.json still read 26.7.59 against Cargo.toml's 26.7.60, failing the static gate with version-drift. bump_version.py already knows to update both mirrors in that file; the 26.7.60 bump just landed without it. Set to exactly the values that script would write, so the next --update-all is a no-op.

Verification

  • python3 -m unittest discover -s tests/tooling — 33 passed (was 32; +1 new test).
  • check_repo_hygiene.py --mode static — now exits 0. It exits 1 on a clean origin/main worktree, which is the before/after for finding 3.
  • Workflow YAML parses; setup-python@v5 confirmed present in both jobs' step lists.
  • No Rust changed since 4cc10ba, whose full verification is in the comment above.

Happy to split these three into their own PR against main instead if you'd rather keep #674 to the analyzer change — the commit is self-contained and cherry-picks cleanly.


Generated by Claude Code

The Windows Repository Hygiene job failed with sha256 drift on all 48 archived
files at once. The manifest hashes are computed from the committed (LF) bytes,
but check_repo_hygiene.py hashes the file as it sits in the working tree, and
the Windows runner checks out with core.autocrlf=true — so every LF became
CRLF and every hash missed. Confirmed rather than assumed: re-hashing each
file's CRLF-converted bytes reproduces the exact "actual" digests from the CI
log.

This was latent, not new. The job previously died earlier, at the checker's
unit tests; fixing those in the parent commit let it reach the static check and
surface the next Windows-only failure behind it.

Archived files are immutable blobs pinned by hash, so a root .gitattributes
marks Archive/** as `-text`: no end-of-line translation on any platform, making
the working tree byte-identical to the commit. Fixing the checkout rather than
the checker keeps the hash a pin on repository content instead of on whatever
the local platform happened to write, and leaves the checker's semantics alone.

`.gitattributes` is added to the root allowlist in .repo-hygiene.toml. It is a
legitimate root-level Git config file alongside the already-allowed .gitignore,
and root is the only place it applies from — this is a gap in the allowlist,
not an exception carved out to silence a violation.

Verified by simulating the Windows checkout on Linux — clone with
core.autocrlf=true, then run the checker:
  before: 48 archive-manifest drift violations, exit 1
  after:  clean, exit 0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9AnrAH4d8sTni73GWgkjR

logbie commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

a028cda fixes the next Windows failure, which the previous commit exposed rather than caused.

What happened

Repository Hygiene (windows) went from failing at the checker's unit tests to failing at the static check, reporting sha256 drift on all 48 archived files at once. That is not 48 problems — it is one.

Archive/manifest.json pins each archived file by the sha256 of its committed (LF) bytes, but check_repo_hygiene.py hashes the file as it sits in the working tree, and the Windows runner checks out with core.autocrlf=true. Every LF became CRLF, so every hash missed.

Confirmed rather than assumed — re-hashing each file's CRLF-converted bytes reproduces the exact digests from the CI log:

File manifest sha256(LF) CI "actual" sha256(CRLF)
nexus/test_minimal.wfl 099c4d5b89ce 099c4d5b89ce 6197fe563a94 6197fe563a94
nexus/test_factorial.wfl 7c5418772fd0 7c5418772fd0 039b405bac69 039b405bac69
reports/rust_loc_report.md a07b23b86f2c a07b23b86f2c d762778de290 d762778de290

This was latent on main, not introduced here: the job used to die earlier, at the unit tests, so it never reached the static check. Fixing those in b609baf let it get one step further and surface what was waiting behind them.

The fix

A root .gitattributes marking Archive/** -text — no end-of-line translation on any platform, so the working tree is byte-identical to the commit. Archived files are immutable blobs pinned by hash; checking them out verbatim is what the policy already implies.

I chose to fix the checkout rather than the checker deliberately. Making the checker normalize line endings before hashing, or hash git cat-file output instead, would both work — but the first weakens the pin (it would stop distinguishing content that genuinely differs only by line ending) and the second makes the check depend on git plumbing per file. Fixing the checkout keeps the hash meaning "the bytes in the repository" instead of "whatever the local platform happened to write," and leaves the checker's semantics untouched. Say the word if you'd rather have it the other way.

.gitattributes is added to the root allowlist in .repo-hygiene.toml. Flagging that explicitly given §"do not widen an allowlist to make a violation go away": it sits alongside the already-allowed .gitignore, root is the only place it applies from, and the violation it addresses is fixed at the source rather than suppressed. If you read that differently, I'll take it out and fix the checker instead.

Verification — real Red→Green, not a claim

The Windows checkout is reproducible on Linux: clone with core.autocrlf=true, then run the checker.

before (b609baf, no .gitattributes):  48 archive-manifest drift violations, exit 1
after  (a028cda):                     clean, exit 0

Working-tree mode is also clean in that same CRLF checkout, and git status shows no spurious modifications. Plus python3 -m unittest discover -s tests/tooling — 33 passed, and --mode static exits 0 on a normal checkout.

No Rust changed since 4cc10ba.


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@logbie Confirmed. The branch-promotion fix preserves the constant marker for the promoted binding. The corrected regression test now covers the bare-name mutation statements that this PR changes.

The existing Assignment behavior for loop variables and action parameters is separate from this PR.

🐇 ✅


✏️ Learnings added
Learnt from: logbie
URL: https://github.com/WebFirstLanguage/wfl/pull/674

Timestamp: 2026-07-31T07:06:25.514Z
Learning: In the Rust analyzer, arithmetic mutation statements such as `subtract 1 from entry` and `multiply entry by 2` desugar to `Assignment`. The existing `Assignment` mutability check reports `Cannot modify constant` for immutable loop variables and action parameters. This behavior is separate from the bare-name `add`, `remove`, and `clear` constant-mutation checks.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

You are interacting with an AI system.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
History/dev-diary/2026/2026-07-31-issue-671-constant-mutation-reports.md (1)

126-134: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Risk classification conflicts with the stated guideline.

The diary reports "Risk class: R1" at Line 166, but the same document states at Lines 132-134 that "The one genuine behavior change is the constant-list case described above, which moves from "silently allowed" to "rejected at analysis time"." This is a backward-compatibility change. The coding guidelines classify backward compatibility changes as R3, not R1. Correct the risk classification or add a justification for why this specific change does not require R3 treatment despite the behavior change.

As per coding guidelines, "Classify risk R0–R3 before implementation; concurrency, cancellation, lifecycle, streaming, untrusted input, crypto/secrets, and backward compatibility are R3."

Also applies to: 166-168

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@History/dev-diary/2026/2026-07-31-issue-671-constant-mutation-reports.md`
around lines 126 - 134, The diary’s risk classification conflicts with its
documented backward-incompatible constant-list behavior. Update the risk
classification at the “Risk class” entry to R3, or add an explicit
guideline-based justification if retaining R1; ensure the classification
accurately accounts for the change from silently allowing to rejecting
constant-list mutation.

Source: Coding guidelines

.github/workflows/ci.yml (2)

695-698: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Run the static check after every retry bump.

The static check runs only before the first push attempt. If a concurrent merge rejects that push, Line 719 creates new generated output and the next iteration pushes it without this check. Run scripts/check_repo_hygiene.py --mode static after each retry bump and before its next push.

Proposed fix
             git fetch origin "$BRANCH"
             git reset --hard "origin/$BRANCH"
             python scripts/bump_version.py --update-all
+            python scripts/check_repo_hygiene.py --mode static
           done
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 695 - 698, Move or repeat the static
hygiene check from the one-time pre-push step into the retry loop after the
retry bump and generated-output update, before every subsequent push attempt.
Ensure each iteration running after the conflict-handling logic invokes
scripts/check_repo_hygiene.py --mode static immediately before pushing.

49-51: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Update the Python setup action.

actions/setup-python@v4 is reported as too old for the GitHub Actions runner. Use actions/setup-python@v5, as this workflow already does in the other jobs.

Proposed fix
-      - uses: actions/setup-python@v4
+      - uses: actions/setup-python@v5
#!/bin/bash
set -euo pipefail
rg -n 'actions/setup-python@v4' .github/workflows
actionlint .github/workflows/ci.yml
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 49 - 51, Update the
actions/setup-python action version from v4 to v5 in the workflow step shown in
the diff. This aligns with the v5 version already in use in other jobs within
the same workflow and addresses the compatibility issue with the GitHub Actions
runner.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/constant_mutation_analyzer_test.rs`:
- Around line 124-154: Expand constant-mutation regression coverage to exercise
both promotion paths: add a try/when case that declares a constant and mutates
it in finally after pop_scope_promoting_except promotion, and include remove and
clear in the existing branch mutation list. Update the test comment to
accurately describe branch merging via promote_constant_marker and distinguish
it from when-scope promotion.

---

Outside diff comments:
In @.github/workflows/ci.yml:
- Around line 695-698: Move or repeat the static hygiene check from the one-time
pre-push step into the retry loop after the retry bump and generated-output
update, before every subsequent push attempt. Ensure each iteration running
after the conflict-handling logic invokes scripts/check_repo_hygiene.py --mode
static immediately before pushing.
- Around line 49-51: Update the actions/setup-python action version from v4 to
v5 in the workflow step shown in the diff. This aligns with the v5 version
already in use in other jobs within the same workflow and addresses the
compatibility issue with the GitHub Actions runner.

In `@History/dev-diary/2026/2026-07-31-issue-671-constant-mutation-reports.md`:
- Around line 126-134: The diary’s risk classification conflicts with its
documented backward-incompatible constant-list behavior. Update the risk
classification at the “Risk class” entry to R3, or add an explicit
guideline-based justification if retaining R1; ensure the classification
accurately accounts for the change from silently allowing to rejecting
constant-list mutation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 14b88491-3d65-44f0-b7dd-afeab9c5f5a5

📥 Commits

Reviewing files that changed from the base of the PR and between 29c9ddf and a028cda.

⛔ Files ignored due to path filters (1)
  • vscode-extension/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • .gitattributes
  • .github/workflows/ci.yml
  • .repo-hygiene.toml
  • Docs/03-language-basics/variables-and-types.md
  • Docs/06-best-practices/naming-conventions.md
  • History/dev-diary/2026/2026-07-31-issue-671-constant-mutation-reports.md
  • TestPrograms/docs_examples/basic_syntax/constants_immutable_01.wfl
  • src/analyzer/mod.rs
  • tests/constant_mutation_analyzer_test.rs
  • tests/diagnostics_fixtures_test.rs
  • tests/tooling/test_check_repo_hygiene.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • Docs/03-language-basics/variables-and-types.md
  • Docs/06-best-practices/naming-conventions.md
  • TestPrograms/docs_examples/basic_syntax/constants_immutable_01.wfl

Comment thread tests/constant_mutation_analyzer_test.rs Outdated
Round 2 of review. Three of four findings were valid.

Test comment was wrong and coverage was thin. The branch test named
`pop_scope_promoting_except`, but a `check` merge goes through
`promote_constant_marker` — different function. Corrected, and the test now
also covers a constant *list* through `add`/`remove`/`clear`, the two forms
with no `Assignment` fallback behind them.

The other migration site is unreachable, and now says so. The reviewer asked
for a try/when regression exercising `pop_scope_promoting_except`. That test
cannot be written: a binding declared inside a try statement does not survive
it at all — even when declared on every path, a later reference reports
`Variable '<name>' is not defined` rather than resolving to a promoted key.
Rather than write a test that does not exercise what it claims, this pins the
premise (`try_scoped_declarations_do_not_escape_the_statement`) so the
migration gets real coverage the day try-scoping changes, and documents at the
call site why the code is kept: `constant_bindings` should not silently
diverge from the four alias maps migrated for the same keys beside it.
`outer_constants_survive_an_intervening_try` covers what a program can
actually observe.

Risk class R1 was wrong; it is R3. testing.md §5 puts backward compatibility
in R3 outright and forbids lowering the class, and this change does alter
behavior (`add`/`remove`/`clear` on a constant list: silently allowed →
rejected at analysis). The diary now records R3, notes that §11.3 does not
apply (no concurrency, lifecycle, streaming, untrusted input, or crypto), and
lists the failure-path and negative coverage that does.

Also bumps the two remaining `actions/setup-python@v4` uses to v5, matching
the jobs fixed in the previous commit and clearing the Node 20 deprecation
warning.

Not taken: running the static hygiene check inside the bump-version push-retry
loop. That job is pre-existing code this branch does not touch, and the
concern (a concurrent merge racing the retry) is real but belongs in its own
change rather than riding along with an analyzer fix.

Verified: 151 test binaries, clippy -D warnings, fmt --check, tooling tests 33
passed, static hygiene exit 0, CI program sweep 142/0/49, docs examples 21/21.

Refs #671

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9AnrAH4d8sTni73GWgkjR

logbie commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

0e91738 — round 2 of review. Three of four findings were valid; one I'm declining with a reason, and one I'm answering with evidence rather than a test.

1. Test comment was wrong, coverage was thin — fixed

Correct catch. The test named pop_scope_promoting_except, but a check merge goes through promote_constant_marker — a different function entirely. Comment corrected.

remove/clear added as asked, via a constant list declared on both arms. Those two matter most here: unlike change/subtract/multiply/divide, they have no Assignment fallback behind them, so if the marker were lost they'd fail silently rather than still reporting.

2. The try/when regression can't be written — and that's the finding

You asked for a try/when case exercising pop_scope_promoting_except. I tried to write it and it doesn't hold up: a binding declared inside a try statement doesn't survive the statement at all. Every shape fails the same way:

try:
    store new constant LIM as 3
catch:
    store new constant LIM as 5     // declared on every path
end try
add 1 to LIM        // error: Variable 'LIM' is not defined

Not "Cannot modify constant" — not defined. Same for change, same when declared only in try, same when mutated in finally. Nothing can reach a promoted key, so that migration is unreachable from WFL source today.

I'd rather say that than write a test that passes while exercising nothing. What's there instead:

  • try_scoped_declarations_do_not_escape_the_statement pins the premise. If try-scoping ever changes so those bindings do survive, that test fails and the migration gets real coverage on the spot.
  • outer_constants_survive_an_intervening_try covers what a program can actually observe.
  • The call site now documents why the code is kept rather than deleted: constant_bindings shouldn't silently diverge from the four alias maps migrated for the same keys directly beside it.

If you'd rather I delete the unreachable migration outright, say so — it's three lines and I have no attachment to it.

3. Risk class R1 → R3 — you're right, I got that wrong

testing.md §5 puts backward compatibility in R3 outright and says the class must not be lowered to dodge a gate. My own diary records a real behavior change (add/remove/clear on a constant list: silently allowed → rejected at analysis). Calling that R1 was me reading "only adds a diagnostic" and ignoring what the diagnostic changes. Now R3, with §11.3 explicitly noted as not applicable (no concurrency, cancellation, lifecycle, streaming, untrusted input, or crypto) and the failure-path/negative coverage listed.

4. setup-python@v4v5 — done

Both remaining uses bumped, matching the jobs from b609baf and clearing the Node 20 deprecation warning.

Declined: static hygiene check inside the bump-version retry loop

The concern is real — a concurrent merge rejects the push, the loop re-bumps, and the retry pushes generated output that was never re-checked. But that job is pre-existing code this branch doesn't touch, and the fix deserves its own change and its own test rather than riding along inside an analyzer PR that already carries more CI work than I'd like. Happy to open it as an issue.

Verification

151 test binaries · clippy -D warnings · fmt --check · tooling tests 33 passed · static hygiene exit 0 · CI program sweep 142 passed / 0 failed / 49 skipped · docs examples 21/21.

For the record on the previous round: on a028cda, Repository Hygiene passed on both platforms and Run WFL Programs passed on Windows — the first time either has been green on this branch.


Generated by Claude Code

#675 landed the same three CI repairs this branch carried, so the merge drops
mine in favour of the versions already on main:

- ci.yml: main sets up Python 3.12 in the same two jobs. Taken as-is, and the
  v4→v5 setup-python bump this branch added is dropped with it — v4 works and
  the bump only silenced a deprecation warning, which is not worth diverging
  from main for. It can be its own change.
- test_check_repo_hygiene.py: main's `write_text(content, newline="\n")`
  replaces this branch's `write_bytes(content.encode())`. Same effect for the
  fixtures; main's is already merged. The byte-exactness regression test added
  here is kept — it is additive and now pins main's fix.
- package-lock.json: taken from main, which is at 26.7.61 after the latest
  bump.

The .gitattributes approach is removed entirely. This branch stopped the
archive-manifest CRLF drift by checking archived files out verbatim; main
instead hashes the staged blob (`git show :path`) in the checker, which fixes
the same failure at the other end and is already merged. Keeping both would be
redundant, and mine required widening the root allowlist in .repo-hygiene.toml
— a change that needed maintainer judgement and is now unnecessary. The
allowlist is restored to main's.

What remains from this branch is the #671 analyzer fix and its tests, docs,
examples, and dev diary, plus one additive tooling test.

Verified after the merge: tooling tests 33 passed, static hygiene exit 0,
`fmt --check`, and both constant-mutation suites green (9 + 3).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9AnrAH4d8sTni73GWgkjR

logbie commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

Conflicts resolved in ed3b36d. #675 landed the same three CI repairs while this branch was carrying them, so the merge drops mine in favour of what's already on main — this PR is back to being just the #671 analyzer fix.

What was dropped, and why

Conflict Resolution
ci.yml setup-python Took main's (v4, Python 3.12, same two jobs). The v4→v5 bump this branch added went with it — v4 works and the bump only silenced a deprecation warning, not worth diverging from main.
test_check_repo_hygiene.py write() Took main's write_text(content, newline="\n") over my write_bytes(content.encode()). Same effect on the fixtures; main's is merged.
package-lock.json Took main's, now at 26.7.61.

.gitattributes removed entirely. I stopped the archive-manifest CRLF drift by checking archived files out verbatim; main fixed the same failure at the other end, by hashing the staged blob (git show :path) in the checker. Both work, main's is merged, and keeping both would be redundant. It also means the root-allowlist widening I flagged as needing your judgement is no longer needed at all — .repo-hygiene.toml is restored to main's version untouched.

Worth noting we independently reached opposite ends of the same fix and both were sound; main's is arguably the better one, since it makes the hash platform-independent by construction rather than relying on checkout attributes staying correct.

One thing kept

test_fixture_write_is_byte_exact — purely additive, and it now pins main's newline="\n" fix so a future edit to write() can't silently reintroduce the drift.

The PR is now focused

 Docs/03-language-basics/variables-and-types.md     |  51 +++-
 Docs/06-best-practices/naming-conventions.md       |  16 +-
 History/dev-diary/2026/...-issue-671-...md         | 253 +++++++++++
 TestPrograms/docs_examples/_meta/manifest.json     |  36 +++
 TestPrograms/docs_examples/.../constants_01.wfl    |  13 +
 TestPrograms/docs_examples/.../constants_immutable_01.wfl | 19 ++
 src/analyzer/mod.rs                                | 108 +++++++-
 tests/constant_mutation_analyzer_test.rs           | 287 ++++++++++++++
 tests/diagnostics_fixtures_test.rs                 |  15 +-
 tests/tooling/test_check_repo_hygiene.py           |  19 ++

No CI infrastructure left in the diff — which addresses the scope concern I raised when those changes went in.

Verified after the merge

151 test binaries · clippy -D warnings · fmt --check · tooling tests 33 passed · static and working-tree hygiene exit 0 · CI program sweep 142 passed / 0 failed / 49 skipped · docs examples 21/21 (--force, no cache).


Generated by Claude Code

@logbie
logbie merged commit 0096844 into main Jul 31, 2026
19 checks passed
@logbie
logbie deleted the claude/issue-671-8ufs93 branch July 31, 2026 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Analyzer drops the add ... to CONST report when combined with other constant mutations

2 participants