From 6754cc06f94bd4e918fc79c4fa39a1203f2e0054 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:38:13 -0400 Subject: [PATCH 01/17] docs(code-tidying): plan the dissolve-comments aggressive dial and eval suite Brief and approved plan for an `aggressive` posture and a `strip` argument on /code-tidying:dissolve-comments, calibrated by a claude plugin eval suite run in WSL2 against expected outputs the owner writes. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KMAbPZjcSzfppwu1AoCpag --- .../dissolve-comments-aggressive-dial/PLAN.md | 469 ++++++++++++++++++ .../design/design-resolution.md | 41 ++ 2 files changed, 510 insertions(+) create mode 100644 docs/topics/dissolve-comments-aggressive-dial/PLAN.md create mode 100644 docs/topics/dissolve-comments-aggressive-dial/design/design-resolution.md diff --git a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md new file mode 100644 index 000000000..f7108ad56 --- /dev/null +++ b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md @@ -0,0 +1,469 @@ +# dissolve-comments aggressive dial + +## Brief + +### TLDR + +Add an `aggressive` posture and a `strip` argument to `/code-tidying:dissolve-comments` so a run +removes nearly every comment and rewrites the code to carry what it can, without loosening any +proof gate, then calibrate it against real and invented sections through a `claude plugin eval` +suite run in WSL2. + +### Goal + +Four runs of the current skill on this repository removed 2 of about 2,360 comment lines, because +every posture keeps rationale that passes the class-C test. The owner wants a dial that removes all +removable comments and makes the code self-describing for each one removed, plus a mode that +removes all comments except the ones that must survive, and a repeatable way to check the output +matches what the owner expects. + +### Constraints + +- No knob loosens a gate. Deletions carry the COMMENT-ONLY proof; function-local renames carry the + RENAME-ONLY proof; tier-2 and tier-3 moves need a discovered test net covering the touched file; + Introduce Assertion is test-net-only. A syntax check never substitutes for a gate. +- The exempt surfaces in `plugins/code-tidying/skills/dissolve-comments/reference/safety.md` hold in + every mode, including `strip`, and so does a comment paired with a regression test. +- An identifier that appears in a repo-local marker row (for example + `scripts/silent-revert-incidents.txt`) is never renamed. +- A non-interactive run on a widened rung still runs in safe mode. +- `change-shape.py` reads a Python docstring as a string token, so deleting any docstring, private + ones included, reads CODE-CHANGED and cannot apply; under `aggressive`, Python docstring removals + are proposals. +- The survivor list and gate statement live in `SKILL.md`, because a `claude plugin eval` with-arm + cannot read the skill's reference files. +- `claude plugin eval` cannot set plugin `userConfig`, so every behavior the suite measures is + reachable by an argument token. +- The per-skill `evals/evals.json` stays the CI-gated format; skill bodies state current rules only + (`.claude/rules/skill-bodies-state-current-rules.md`); no em dashes in plugin prose. + +### Acceptance criteria + +Format: `free-text` (default; no convention surface resolved). + +- `comment_posture` accepts `aggressive`, and `aggressive [target]` is accepted as a per-run token + that wins over the standing value. +- Under `aggressive`, a comment passing today's class-C test but not on the exempt-surface list is + deleted with its narrative staged, except a load-bearing warning, which is kept within + `class_c_max_lines`; a class-B comment is dissolved when its move's gate passes or kept with a + proposal when it does not. +- `strip [target]` deletes every comment except exempt surfaces, rewrites no code, and certifies + every deletion COMMENT-ONLY. +- `safe` wins over `aggressive` and `strip`; `./aggressive` and `./strip` resolve as paths. +- The staged block carries an `Intentional-removal:` line when the target repository's own scripts + or CI reference that trailer, and the optional `--notes ` appends + the block to an untracked or out-of-repo file and refuses a tracked path. +- IF an `aggressive` or `strip` run targets already-committed code, THEN removals apply and the + report names the next commit touching that code as the block's landing place, as `strict` does + today. +- IF a scoped file is UNPROVABLE against itself, THEN the run names it before the census and every + edit in it is a proposal. +- WHILE the run is non-interactive on a widened rung, it runs in safe mode whatever the posture. +- The "posture ladder only descends" wording is replaced in `SKILL.md`, `reference/safety.md`, + `README.md`, and the `plugin.json` option description, and the tier lists in `safety.md` and + `dissolving-moves.md` agree. +- A `claude plugin eval` suite under `plugins/code-tidying/evals/` runs in WSL2 with `--scaffold` + and `--allow-tools Edit Bash Write`, covering the three frozen real sections, a minimal + `lib/hook-utils.sh` excerpt that is UNPROVABLE (the whole file run once and recorded), one + invented case per triage class, and the `aggressive`/`strip`/`safe` interactions, each graded on + file contents. +- Expected outputs for every calibration case are written with the owner, one section at a time, + before the suite's scores are read as a verdict. + +### Captured assumptions + +- WSL2 distro `Ubuntu-26.04` stays the eval host; it has `claude` 2.1.270, `bwrap`, and `socat`, and + needs the CI-pinned pygments and tree-sitter wheels installed before the first run. +- `claude plugin eval` graders on `{ source: file, path }` read post-edit contents of a scaffolded + file; this is documented but unprobed, and the first case run confirms it. +- The with-arm can execute the skill's `${CLAUDE_SKILL_DIR}/scripts/*` wrappers; unprobed, confirmed + by the first case run. +- Unwanted-behaviour and state-driven coverage were examined: the IF and WHILE criteria above. + +### Out-of-scope + +- A separate command for destructive comment removal; `strip` keeps the exempt-surface floor. +- New proof tooling for tier-2 moves; they stay behind a test net. +- A tree-sitter-bash grammar fix for base-N arithmetic. +- Running the dial across this repository; calibration comes first. + +### Deferred questions + +- Q7 Which side of the Martin versus Ousterhout split `aggressive` takes on internal-interface + comments that are not on the exempt list. arbiter: /planning:plan +- Q8 Whether `evals.json` validation follows skill-creator's `expectations` or `assertions` field + name. arbiter: /planning:plan +- Q9 Under `aggressive`, whether a tier-2 move with no discovered test net applies as a labelled + unproven edit instead of a proposal. This is a risk-appetite call: the Brief defaults to proposal, + which means that on this repository's shell scripts (no covering net, a net over the 600-second + tool cap, or an UNPROVABLE file) `aggressive` deletes rationale and proposes the rewrites. + arbiter: USER-RESERVED + +## Plan + +### Goal + +Ship `aggressive` and `strip` in `/code-tidying:dissolve-comments` 0.20.0 and a `claude plugin eval` +suite whose expected outputs the owner wrote, with the suite passing in WSL2. Design: +[design/design-resolution.md](design/design-resolution.md) (Tier B early-exit, argument grammar +and mode table). + +### Resolved deferred questions + +- **Q7 (arbiter /planning:plan, owner agreed).** `aggressive` takes Martin's side on + internal-interface comments: a non-exempt comment describing a private function's contract is + deleted with its narrative staged. Public-API doc comments stay exempt under the + leading-underscore rule. Basis: the owner asked for "ALL or most ALL comments" removed, and Q1 + keeps only the exempt surfaces and load-bearing warnings. +- **Q8 (arbiter /planning:plan, owner agreed).** New `evals.json` entries use `expectations`. + Basis: all 15 existing entries use it, and `plugins/skill-quality/scripts/check-evals-quality.sh` + treats `expectations` and `assertions` as equivalent and warns when a case carries both. +- **Q9 (USER-RESERVED, owner agreed to the default).** A tier-2 move with no discovered test net is + a proposal. + +### Brief amendments (owner approved 2026-09-14, applied to the Brief) + +Stress-test findings that changed a Brief line. + +1. **Whole-file case becomes a recorded manual run.** The suite's UNPROVABLE case uses the smallest + `lib/hook-utils.sh` excerpt that still exits 21 from `change-shape.sh` (a function using + `10#$var`); the whole 4,086-line file is run once with `--runs 1` and its result recorded in this + file. Basis: an UNPROVABLE file still gets full triage and a `git log -L` per rationale comment + (`SKILL.md:187-214`), 3 runs times up to 1,800 s each, and the fixture would ship inside the + plugin. +2. **`Intentional-removal:` only where the target repository reads it.** The staged block carries + that line when the target repository's own scripts or CI reference the trailer (a grep over the + repository), and omits it otherwise. Basis: it is this repository's own trailer + (`scripts/check-silent-revert.sh:445`), and `code-tidying` is installed in other repositories. +3. **Paired records survive every mode.** A comment that is one half of a comment-plus-regression- + test pair is kept under `aggressive` and `strip`. Basis: `reference/safety.md:224-227` calls + deleting half a paired record a correctness bug, which no mode may loosen under "no knob loosens a + gate". +4. **The eval grant adds `Write`.** `--allow-tools Edit Bash Write`, because `--notes` creates a + file and `Edit` cannot create one (`case-authoring.md:82`). + +### Standards grounding + +Loaded for the surfaces touched: `.claude/rules/skill-bodies-state-current-rules.md` (verification +records, `## Next` placement before `## Gotchas`), `.claude/rules/pr-body-contract.md`, root +`AGENTS.md` (draft PRs), `plugins/evals/skills/plugin-eval/SKILL.md` and +`reference/case-authoring.md`, `reference/reading-results.md` (case layout, graders, sandbox, +results JSON), `docs/specs/plugin-evals-pilot-measurement.md` (with-arm cannot read spokes). No +consumer standards index beyond these. + +### Test strategy + +The skill is prose, so its tests are eval cases, written red first. + +- **Boundary driven:** the existing public interface, the invocation + `/code-tidying:dissolve-comments [tokens] `, run through `claude plugin eval` against the + working-tree plugin at `plugins/code-tidying`. No new interface is introduced for testability. +- **Red:** Phase 2 runs the suite against the unchanged skill. Each case lists its expected-red + graders (the ones encoding behavior new to `aggressive` or `strip`); graders encoding today's + behavior (class-A deletion, exempt surfaces) are expected to pass red and are recorded as such. +- **Green:** Phase 4 re-runs the suite after the Phase 3 skill change with `--threshold 0.8`. +- **Graders per case:** + - survivors: `regex` `contains` on `{ source: file, path }`, anchored to comment syntax around an + owner-chosen key phrase (`#[^\n]*`), so a terser rewrite of a kept warning still passes; + - deletions: `regex` `not_contains` on the same anchored form; + - code preserved: one `regex` `contains` per non-comment code line, or a `count:N` over them, so + deleted or rewritten code fails the case (for `strip`, "rewrites no code"); + - process: `tool_used` on `Bash` with `input_match: change-shape`, `arm: both`, so a run that skips + the proof scores lower; + - UNPROVABLE case: `tool_order` with `change-shape` before `comment-census`. +- **Delta caveat:** in the without-arm the slash command does not resolve, so the delta partly + measures whether the command exists. Read pass rates in the with-arm as the calibration verdict. +- **Static checks in CI:** `check-evals-quality.sh` over `evals.json`, `sync-plugin-options-docs.py + --check`, `allowed-tools-pairing.test.sh`, `check-purged-em-dashes.sh`. The paid suite does not + run in CI. + +### Phase 1: Eval harness tracer in WSL2 [TODO] + +Proves the runner mechanics every later phase depends on, against the unchanged skill. All WSL2 +commands run in the main checkout at `/mnt/d/repos/github.com/melodic-software/claude-code-plugins` +on the task branch, never in a worktree, because WSL git cannot read a worktree `.git` file that +holds a `D:/` path. `$TEMP` is unset in WSL2; WSL2 commands use `${TMPDIR:-/tmp}`. + +1. Create branch `feat/dissolve-comments-aggressive-dial` from `main`; commit `PLAN.md` and + `design/design-resolution.md`. +2. WSL2 tooling `[EXEC-SHAPE]`: `uv venv ~/.venvs/code-tidying --python 3.14` then `uv pip install + --python ~/.venvs/code-tidying/bin/python --require-hashes -r .github/requirements-ci.txt`, and + launch every `claude plugin eval` with `~/.venvs/code-tidying/bin` first on `PATH`. The skill + wrappers exec `python3` from `PATH` (`skills/dissolve-comments/scripts/change-shape.sh:17`), and + the distro's current `python3` is uv's CPython 3.14 at `~/.local/bin`. +3. Author `plugins/code-tidying/evals/probe-environment/case.yaml`: prompt `Run: command -v python3; + python3 -c 'import pygments, tree_sitter, tree_sitter_bash'; command -v make; echo "$PATH"`, + `execution: { allowed_tools: [Bash], max_turns: 5, timeout_seconds: 120 }`, graders `regex` on + `trace` for `tree_sitter_bash` import success. No skill involved. +4. Author `plugins/code-tidying/evals/probe-explicit-target/`: `case.yaml` with + `context.scaffold_script` that copies `fixture/app.sh.txt` (from `$(dirname "$0")`) to `app.sh`, + runs `git init`, and commits with `-c user.name=eval -c user.email=eval@example.invalid` and a + commit message carrying no rationale. `app.sh` holds one class-A comment `# increment counter` + above `counter=$((counter + 1))` and one `# shellcheck disable=SC2034` directive. Prompt: + `/code-tidying:dissolve-comments app.sh`. `execution: { allowed_tools: [Read, Glob, Grep, Skill, + Bash, Edit, Write], max_turns: 80, timeout_seconds: 1200 }`. Graders per the test strategy. +5. Run both probes in WSL2: `claude plugin eval plugins/code-tidying --case 'probe-*' --scaffold + --allow-tools Edit Bash Write --trust-plugin --runs 1 --ablation none --keep-temp --no-publish + --json "${TMPDIR:-/tmp}/dc-probe.json"`. From the result and the kept traces, record in this file: + the `python3` the run resolved and whether the imports succeeded; whether the slash invocation + fired the skill; whether `change-shape.sh` executed or was denied; whether the file-content + graders read the post-edit file; whether the scaffold resolved its own directory. +6. **Stop conditions** `[FALLBACK — confirm or override]`: + - the run cannot import the wheels, or `change-shape.sh` is denied in the with-arm: stop, record + the evidence here, and return to `/planning:plan review` before Phase 2; + - the slash invocation does not fire the skill: prompts become `Use the Skill tool with skill + code-tidying:dissolve-comments and args " "`, which keeps token parsing; + - the scaffold cannot resolve its own directory: each scaffold embeds its fixture as a quoted + heredoc. + +**Sanity Check:** + +- `python3 plugins/evals/skills/validate/scripts/validate-cases.py plugins/code-tidying/evals` exits 0. +- `jq '[.cases[].arms.with[0].graders[] | .passed] | all' "${TMPDIR:-/tmp}/dc-probe.json"` prints + `true`, or this file records the failing grader and the stop condition taken. +- `git log --oneline main..HEAD -- docs/topics/dissolve-comments-aggressive-dial/PLAN.md` lists at + least one commit. + +### Phase 2: Expected outputs with the owner, then the red run [TODO] + +Main session, interactive, one fixture per round. Fixtures are committed with a `.txt` suffix +(`app.sh.txt`, `mod.py.txt`) and renamed by the scaffold, so CI shellcheck, ruff, and the +comment-hygiene scan do not lint deliberately commented fixtures. + +1. Freeze the real sections at commit `0a676a578` as fixture copies, each a self-parsing block: + `plugins/rate-limit-guard/scripts/statusline-tee.sh` lines 308-324 (`_rlg_read_stamp` and its + header comment); the `WHY BLAME-OF-DELETED-LINES, AND NOT THE ALTERNATIVES` comment block in + `scripts/check-silent-revert.sh` with the function that follows it; the `lib/hook-utils.sh` + header through its calling-convention block plus one helper, and separately the smallest + `lib/hook-utils.sh` excerpt that exits 21 (amendment 1). +2. Baseline `[EXEC-SHAPE]`: `comment-census.sh --json` over each real fixture; record each file's + comment-line count here as the before value. +3. Write invented fixtures: + - class A: a restating comment and commented-out code; + - class B: a magic literal and a vague local name, with a `Makefile` `test:` target the scaffold + writes, so the skill's test-net discovery finds a net; + - class C: a load-bearing warning, a rationale comment whose text is absent from git history, and + a rationale comment the scaffold's commit message does repeat; + - exempt surfaces: license header, `# noqa` with justification, `TODO(#12)`, + `dissolve-comments-ignore`; + - marker row: a repo-local marker file listing an identifier that a comment would otherwise + rename; + - Python: a private function with a docstring (expected proposal, not applied); + - paired record: a comment paired with a regression test (amendment 3). +4. For each fixture, show it to the owner with the doctrine's prediction under `aggressive` and + under `strip`; the owner marks every comment keep, delete, or dissolve and picks the key phrase + each grader anchors on. Record the marks as graders plus `expected_outcome`, and list the case's + expected-red graders in this file. `/planning:interview` round format. +5. Author the interaction and criterion cases on the class-C fixture unless noted: + - `aggressive`, `strip`, `safe aggressive`, `safe strip` (both expect class-A deletions only); + - `./aggressive` as a target (scaffold creates a directory named `aggressive`; expects it triaged + as a path under the default posture); + - `--notes notes/dc-notes.md` with `notes/` untracked: `file_exists` plus `regex` on `{ source: + file, path: notes/dc-notes.md }` for the staged block; + - `--notes` on a tracked path: expects a refusal in `last_message` and the file unchanged; + - committed-code landing place: `last_message` regex for the next-commit landing statement; + - WHILE criterion: scaffold with a clean tree and history, prompt `/code-tidying:dissolve-comments + aggressive` with no target; expects no class-C deletion applied (safe mode); + - the standing `comment_posture: aggressive` route goes to `evals.json`, because eval runs cannot + set `userConfig`. +6. Cost gate: print the suite estimate per `plugins/evals/skills/plugin-eval/SKILL.md` "cost + estimate"; the owner sets `--max-cost-usd` before the red run (user-approval gate). +7. Red run: full suite, `--runs 1`, against the unchanged skill. Record per case which graders + passed and whether that matches its expected-red list. +8. Whole-file record (amendment 1): one `--runs 1` run on all of `lib/hook-utils.sh`; record the + outcome, turns, and cost here. + +**Sanity Check:** + +- `validate-cases.py plugins/code-tidying/evals` exits 0. +- `for c in plugins/code-tidying/evals/*/; do case "$c" in *probe-*) continue;; esac; grep -rqs + "source: file" "$c" || echo "$c"; done` prints nothing. +- For each shell fixture `f` in `plugins/code-tidying/evals/*/fixture/*.sh.txt` other than + `Makefile`-target test scripts, `bash -n "$f"` exits 0 and + `plugins/code-tidying/skills/dissolve-comments/scripts/change-shape.sh "$f" "$f"` exits 0, except + the UNPROVABLE excerpt, which exits 21. (Check whether `change-shape.py` maps `.txt`; if not, copy + to a `.sh` name under `${TMPDIR:-/tmp}` first and record that in this command.) +- `git ls-files plugins/code-tidying/evals | grep -E '\.(sh|py|bash)$'` lists only scaffold scripts. +- Every case's expected-red list in this file matches its red-run record. + +### Phase 3: Skill change [TODO] + +1. Consumer and claim sweep first: `grep -rn "comment_posture\|only descends\|narrows\|widened\|loosen + nothing\|ceiling\|of 15" plugins/code-tidying docs scripts`; record every hit and its disposition + here before editing. Known hits: `SKILL.md:39-40, 88-92, 107`, `reference/safety.md:58-61`, + `reference/triage.md:84-96`, `reference/scope.md:29-30`, `reference/dissolving-moves.md:57, 61`, + `README.md:129-136`. +2. Per-mode treatment of the hub rules that `aggressive` and `strip` touch, written into + `SKILL.md` so the with-arm sees them: + + | Rule | Where | `aggressive` | `strip` | + |---|---|---|---| + | Information moves into code before a class-B comment goes | `SKILL.md:114-117` | holds | exception: deleted with narrative staged | + | Class B misread as A is the information-destroying failure | `SKILL.md:255-256`, `triage.md:43-44` | holds | narrative staged for every deletion | + | Staging with no landing place is not a deletion licence | `safety.md:206-209` | holds (next commit is the landing place) | holds | + | Paired comment-plus-regression-test record | `safety.md:224-227` | kept | kept | + | Shape used at scale is proposed | `SKILL.md:140-141` | holds | holds | + | SSOT copy never edited; source edit forcing consumer bumps proposed | `SKILL.md:165-169` | holds | holds | + | Marker-row identifier never renamed | new, workflow step 2 | holds | not applicable (no renames) | + | Exempt surfaces | `SKILL.md:125-130` | hold | hold | + +3. `skills/dissolve-comments/SKILL.md`: + - frontmatter `description` names `aggressive` and `strip`; `argument-hint` becomes + `[safe] [aggressive|strip] [override] [--notes ] [target]`; + - `allowed-tools` adds `Bash(git ls-files:*)` `[EXEC-SHAPE]` for the tracked-path refusal; + - Variables: posture accepts `aggressive`; + - action router rows for `aggressive [target]`, `strip [target]`, `--notes `, and the + precedence from the design resolution; + - the "posture ladder only descends" paragraph becomes "no knob loosens a gate", the survivor + list (exempt surfaces, load-bearing warnings within `class_c_max_lines`, paired records), and + the gate statement; + - triage table and workflow steps 1, 2, 6, and 7 carry the `aggressive` and `strip` branches, + the table in item 2, the conditional `Intentional-removal:` line (amendment 2), and the Python + docstring ceiling; + - doubt rule `[EXEC-SHAPE]`: under `aggressive`, doubt between A and B resolves to B, and doubt + between B and C resolves to B (dissolve when the gate passes, else keep with a proposal); under + `strip`, every doubted deletion stages its narrative; under both, doubt whether a comment is an + exempt surface, a load-bearing warning, or a paired record keeps it; + - "What this skill is NOT" first bullet names `strip` and its exempt-surface floor; + - `## Next` naming `/source-control:commit`, placed before `## Gotchas`. +4. `reference/safety.md`: mode ladder rows for `aggressive` and `strip`; the knob paragraph at lines + 58-61; the staging section gains the conditional `Intentional-removal:` line and `--notes`; tier + table reconciled with `reference/dissolving-moves.md` `[EXEC-SHAPE]` to the union of both, tier + by tier (tier 2 adds Replace Nested Conditional with Guard Clauses and Introduce Special Case; + tier 3 adds Inline Function; `dissolving-moves.md` tier 3 adds Extract Class). +5. The "of 15" counts at `SKILL.md:107` and `reference/dissolving-moves.md:57, 61` are recounted + against the merged tables. +6. `reference/triage.md:84-96` and `reference/scope.md:29-30` name the new modes. +7. `plugin.json`: `comment_posture` description names `aggressive`; top-level description names + `strip`; version `0.20.0`. Then `python3 scripts/sync-plugin-options-docs.py` regenerates the + README options block; hand-edit `README.md:129-136`. +8. `CHANGELOG.md` `## [0.20.0]` entry. +9. `evals/evals.json`: entries 16 (`aggressive-deletes-non-exempt-rationale-with-staging`), 17 + (`strip-keeps-exempt-surfaces-and-rewrites-nothing`), 18 + (`standing-aggressive-posture-yields-to-safe`), each `narration: true` with `expectations`. + +**Sanity Check:** + +- `grep -rn "only descends" plugins/code-tidying` prints nothing, and every hit recorded in item 1 + has a disposition line in this file. +- `grep -n '^## ' plugins/code-tidying/skills/dissolve-comments/SKILL.md` shows `## Next` + immediately before `## Gotchas`. +- Tier tables match tier by tier. With `d=plugins/code-tidying/skills/dissolve-comments/reference` + and `p='Rename Variable|Rename Field|Extract Variable|Replace Magic Literal|Introduce Assertion|Slide Statements|Decompose Conditional|Replace Nested Conditional with Guard Clauses|Introduce Special Case|Extract Function|Change Function Declaration|Extract Class|Introduce Parameter Object|Move Statements into Function|Replace Inline Code with Function Call|Inline Function'`, + `for t in 1 2 3; do diff <(grep -E "^\| \*\*$t\*\*" $d/safety.md | cut -d'|' -f3 | grep -oE "$p" | sort -u) <(grep -E "^\| $t," $d/dissolving-moves.md | cut -d'|' -f3 | grep -oE "$p" | sort -u) || echo "tier $t differs"; done` + prints nothing. On `main` today it prints tier 2 and tier 3 differences (run 2026-09-14). +- `python3 scripts/sync-plugin-options-docs.py --check`, `bash + plugins/skill-quality/scripts/check-evals-quality.sh + plugins/code-tidying/skills/dissolve-comments/evals/evals.json`, `bash + plugins/code-tidying/scripts/allowed-tools-pairing.test.sh`, and `scripts/check-purged-em-dashes.sh` + each exit 0. +- `jq -r .version plugins/code-tidying/.claude-plugin/plugin.json` prints `0.20.0`. + +### Phase 4: Green run, calibration loop, PR [TODO] + +1. Run the full suite in WSL2 with `--runs 3 --threshold 0.8 --max-cost-usd `. +2. For each failing case, classify the cause from the `--keep-temp` trace before editing: a denied + `reference/` read (the rule belongs in `SKILL.md`), doctrine wording, or grader anchoring. Fix the + skill for the first two. A grader change is allowed only for anchoring (the key phrase or its + comment-syntax anchor) or with the owner's agreement in session; log each grader change here with + its reason. +3. Re-run `comment-census.sh --json` on each real fixture's post-run workspace and record comment + lines before and after beside the Phase 2 baseline, distilled. +4. Commit, open a draft PR with the body contract (`No related issue: ` or `Closes #`, + Summary, Fix, Verification, Related), run the Phase 3 static checks plus + `plugins/code-tidying/scripts/*.test.sh`, then mark ready. + +**Sanity Check:** + +- The suite command in item 1 exits 0. +- `gh pr view --json isDraft,body -q '.isDraft, (.body | test("## Verification"))'` prints `false` + and `true` after the flip. + +## Decisions made (gate-passed) + +| Decision | What it changes in the plan | Basis (evidence) | +|---|---|---| +| WSL2 wheels go into a uv venv put first on `PATH` | Phase 1 item 2; no system-wide install, no sudo | WSL2 `python3` is uv CPython 3.14 at `~/.local/bin`; `/usr/bin/python3` has no pip (probed); wrappers exec `python3` from `PATH` | +| Probe failures stop the work and return to planning; a slash-command failure switches prompts to an explicit Skill-tool instruction; an unresolvable scaffold directory switches fixtures to heredocs | Phase 1 item 6 | Pilot record: with-arm reads under the plugin directory were denied (`docs/specs/plugin-evals-pilot-measurement.md`) | +| Doubt under `aggressive` resolves A/B and B/C to B; under `strip` every doubted deletion stages narrative; doubt about exempt, warning, or paired record keeps | Phase 3 item 3, `SKILL.md` doubt paragraph | Brief AC: class B kept with a proposal when its gate fails; `SKILL.md:255-256` B-misread-as-A is the information-destroying failure | +| Tier tables merge to the union, tier by tier | Phase 3 item 4; `safety.md` gains three moves, `dissolving-moves.md` gains Extract Class | Tier comparison command output above (tier 2 and 3 differ today) | +| `Bash(git ls-files:*)` added to the skill's `allowed-tools` | Phase 3 item 3 | `--notes` must refuse a tracked path; `git ls-files --error-unmatch` answers that | +| `## Next` names `/source-control:commit`, before `## Gotchas` | Phase 3 item 3 | `.claude/rules/skill-bodies-state-current-rules.md`; the staged block is that skill's input (`SKILL.md:116`) | +| Fixtures committed with a `.txt` suffix | Phase 2 preamble | CI shellcheck lints changed `*.sh` (`ci.yml:381-386`); comment-hygiene scan excludes only audit-comment-residue | +| Baseline is a census over the real fixtures | Phase 2 item 2 | Brief goal is a measured comment-line count | +| Sequential, main-session execution | Execution shape | Phase 2 and 4 need the owner; Phase 3 files interlock | + +## Blast radius + +MEDIUM. One plugin, about 12 tracked files plus the new eval suite, reversible by revert. It still +triggers a stress-test: the change rewrites agent instructions for a skill that deletes text, and +it reverses a documented invariant. + +## Stress-test summary + +Two fresh-context reviewers ran on the first draft: a plan reviewer (1 critical, 13 important, 4 +suggestions) and `/planning:devils-advocate` (1 critical, 5 high, 6 medium, 2 low). Findings were +checked against the files before applying. Confirmed and applied: + +- Hub rules left contradicting `strip` and `aggressive` now have a per-mode table (Phase 3 item 2). +- The doubt rule names which class wins. +- `$TEMP` is unset in WSL2 (probed); the notes case moved into the workspace with a `Write` grant. +- The sudo fallback was dropped. WSL2 `python3` is uv's CPython at `~/.local/bin` (probed); a uv + venv plus an environment probe case replaces it. +- A denied script in the with-arm is a hard stop (the pilot recorded denied plugin-directory reads). +- Code-preservation graders were added and the proof grader set to `arm: both`. +- The red run lists expected-red graders per case. +- Graders anchor on key phrases. +- The whole-file case became a manual record (amendment 1), and a cost gate precedes the red run. +- Each uncovered acceptance criterion got a case. +- Fixtures carry a `.txt` suffix. +- A slash-command fallback was added. +- `case.yaml` uses `execution:` nesting. +- The results `jq` path was corrected. +- Repo-relative script paths were fixed. +- The baseline moved to Phase 2. +- A Makefile test net was added (`make` present in WSL2, `bats` absent). +- The claim sweep was widened. +- The "of 15" counts were added. +- The `## Next` order check was added. +- `Intentional-removal:` became conditional (amendment 2). +- WSL runs use the main checkout. + +Brief AC line 46 was corrected to match Q1 (load-bearing warnings kept). + +## Execution shape + +Fully sequential, all main session. Phase 1 gates Phase 2 (runner mechanics), Phase 2 gates Phase 3 +(red cases exist first), Phase 3 gates Phase 4. Phase 2 is an owner interview and Phase 4 a +calibration loop, so neither suits parallel workers, and Phase 3's files interlock (hub rules, +references, tier tables). + +| Phase | Surface | Basis | +|---|---|---| +| 1 | main session | WSL2 probing with stop conditions that re-plan | +| 2 | main session | owner marks every expected output | +| 3 | main session | interlocking prose edits to one skill | +| 4 | main session | failure classification and owner-gated grader changes | + +## Open questions + +None. Plan and Brief amendments approved by the owner 2026-09-14. + +## Handoff to implementation + +### User-approval gates + +- Phase 1 item 6: a stop condition returns to `/planning:plan review`. +- Phase 2 item 4: every expected output is the owner's call, one fixture per round. +- Phase 2 item 6 and Phase 4 item 1: the owner sets the cost ceiling before each paid run. +- Phase 4 item 2: any grader change beyond anchoring needs the owner's agreement. + +### Execution shape ([EXEC-SHAPE] tagged) + +Sequential, main session, per the table above. Sanity Checks per phase as written. + +### Mechanical work + +- One commit per phase; `PLAN.md` phase tags and records ride each phase's commit. +- Version bump and CHANGELOG land in Phase 3's commit. +- Commit messages via heredoc with the attribution trailer; PR opened as a draft. diff --git a/docs/topics/dissolve-comments-aggressive-dial/design/design-resolution.md b/docs/topics/dissolve-comments-aggressive-dial/design/design-resolution.md new file mode 100644 index 000000000..6ac87bec9 --- /dev/null +++ b/docs/topics/dissolve-comments-aggressive-dial/design/design-resolution.md @@ -0,0 +1,41 @@ +# Design resolution: dissolve-comments aggressive dial + +outcome: early-exit +tier: B (light design) +reason: The change adds one posture value, two per-run argument tokens, and one flag to an existing +prose skill. No script, type, module, or package boundary changes; the skill's scripts are reused +as they are. + +## Argument grammar sketch + +```text +/code-tidying:dissolve-comments [safe] [aggressive | strip] [override] [--notes ] [target] +``` + +- Tokens are matched whole, in any order, and stripped before the target is read. `./aggressive`, + `./strip`, `./safe`, and `./override` are paths. +- `--notes` consumes the next word as its path. +- `aggressive` and `strip` together: `strip` wins, since it is the narrower edit set (no rewrites). + +## Effective mode resolution + +| Inputs | Effective mode | +|---|---| +| `safe` token present | safe (whatever else is set) | +| non-interactive run on a widened rung | safe (whatever else is set) | +| `strip` token | strip | +| `aggressive` token | aggressive | +| no dial token, `comment_posture` = `aggressive` | aggressive | +| no dial token, `comment_posture` = `strict`, `balanced`, `conservative`, empty, or unknown | as today | + +## What each mode applies + +| Mode | Class A | Class B | Class C (non-exempt) | Exempt surfaces | +|---|---|---|---|---| +| strict (today) | delete, COMMENT-ONLY | per tier gate, else proposal | earn-its-keep test; failures deleted, over-budget rewritten | untouched | +| aggressive | delete, COMMENT-ONLY | per tier gate, else proposal and the comment stays | deleted with narrative staged, except a load-bearing warning, which is kept at `class_c_max_lines` | untouched | +| strip | delete, COMMENT-ONLY | deleted as a comment, no rewrite, COMMENT-ONLY, narrative staged | deleted, COMMENT-ONLY, narrative staged | untouched | +| safe | delete, COMMENT-ONLY | proposal | proposal | untouched | + +Every applied deletion in every mode carries the COMMENT-ONLY proof; an UNPROVABLE file yields +proposals only. From 8878e9f6b6bbc56ff4648bf307e7077d4ca921c0 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 14 Sep 2026 15:01:18 -0400 Subject: [PATCH 02/17] test(code-tidying): probe claude plugin eval mechanics for dissolve-comments Two probe cases prove the eval harness in WSL2 before any calibration case is written: the sandboxed Bash resolves a python3 with the comment-tooling wheels, the slash invocation fires the skill, change-shape.sh returns COMMENT-ONLY inside the with-arm, and file-content graders read the edited file. The plan records the Docker credential-store symlink that blocks Bash-granting runs and the temporary-HOME workaround. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KMAbPZjcSzfppwu1AoCpag --- .../dissolve-comments-aggressive-dial/PLAN.md | 27 ++++++++++++++-- .../evals/probe-environment/case.yaml | 15 +++++++++ .../evals/probe-explicit-target/case.yaml | 31 +++++++++++++++++++ .../probe-explicit-target/fixture/app.sh.txt | 7 +++++ .../evals/probe-explicit-target/scaffold.sh | 8 +++++ 5 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 plugins/code-tidying/evals/probe-environment/case.yaml create mode 100644 plugins/code-tidying/evals/probe-explicit-target/case.yaml create mode 100644 plugins/code-tidying/evals/probe-explicit-target/fixture/app.sh.txt create mode 100755 plugins/code-tidying/evals/probe-explicit-target/scaffold.sh diff --git a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md index f7108ad56..6d0b3675a 100644 --- a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md +++ b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md @@ -177,7 +177,7 @@ The skill is prose, so its tests are eval cases, written red first. --check`, `allowed-tools-pairing.test.sh`, `check-purged-em-dashes.sh`. The paid suite does not run in CI. -### Phase 1: Eval harness tracer in WSL2 [TODO] +### Phase 1: Eval harness tracer in WSL2 [DONE] Proves the runner mechanics every later phase depends on, against the unchanged skill. All WSL2 commands run in the main checkout at `/mnt/d/repos/github.com/melodic-software/claude-code-plugins` @@ -216,10 +216,33 @@ holds a `D:/` path. `$TEMP` is unset in WSL2; WSL2 commands use `${TMPDIR:-/tmp} - the scaffold cannot resolve its own directory: each scaffold embeds its fixture as a quoted heredoc. +**Phase 1 record (2026-09-14):** + +- Wheels installed into `~/.venvs/code-tidying` with `uv pip install --require-hashes`; imports of + pygments, tree_sitter, tree_sitter_bash, tree_sitter_python, tree_sitter_toml succeed. +- First eval attempt refused every Bash-granting run before turn 1: `~/.docker` in the distro holds + two symlinks into `/mnt/c/Users/.../.docker` (Docker Desktop WSL integration), and the sandbox + refuses a credential store containing a link. `DOCKER_CONFIG` pointing at a plain directory did + not clear it. +- Workaround, no change to the owner's files: run `claude plugin eval` with + `HOME=/var/tmp/dc-evalhome`, a directory holding only symlinks to `~/.claude`, `~/.claude.json`, + and `~/.config`. Every WSL2 eval command in this plan runs that way. WSL2 scratch paths use + `/var/tmp`, because this repository's drive-root temp guard rejects `/tmp` in a command string. +- `probe-environment` passed under that HOME (2 turns, 0.17 USD): the sandboxed Bash resolved a + `python3` that imports the wheels. +- `probe-explicit-target` passed 4 of 4 graders (15 turns, 107 s, 0.54 USD). Confirmed from the + trace and the kept workspace: the slash invocation fired the skill; `change-shape.sh` executed in + the with-arm with tree-sitter and returned `COMMENT-ONLY`; `comment-census.sh` ran; the + `{ source: file }` graders read the post-edit `app.sh` (the class-A comment present at scaffold + time was absent); the scaffold resolved its own directory and copied the fixture, so the heredoc + fallback is not needed and was removed. No stop condition fired. +- Sanity results: `validate-cases.py` exit 0 (WARN lines only, for tools granted by + `--allow-tools`); the grader `jq` check printed `true`. + **Sanity Check:** - `python3 plugins/evals/skills/validate/scripts/validate-cases.py plugins/code-tidying/evals` exits 0. -- `jq '[.cases[].arms.with[0].graders[] | .passed] | all' "${TMPDIR:-/tmp}/dc-probe.json"` prints +- `jq '[.cases[].arms.with[0].graders[] | .passed] | all' /var/tmp/dc-probe.json` prints `true`, or this file records the failing grader and the stop condition taken. - `git log --oneline main..HEAD -- docs/topics/dissolve-comments-aggressive-dial/PLAN.md` lists at least one commit. diff --git a/plugins/code-tidying/evals/probe-environment/case.yaml b/plugins/code-tidying/evals/probe-environment/case.yaml new file mode 100644 index 000000000..a01316d6b --- /dev/null +++ b/plugins/code-tidying/evals/probe-environment/case.yaml @@ -0,0 +1,15 @@ +schema_version: "1.1" +name: probe-environment +description: Confirms the eval run resolves a python3 with the comment-tooling wheels and a make binary +tags: [probe] +runs: 1 +execution: + prompt: "Run this exact command with the Bash tool and report its full output verbatim: command -v python3; python3 -c 'import pygments, tree_sitter, tree_sitter_bash' && echo imports-$((40+2)); command -v make; echo PATH=$PATH" + allowed_tools: [Bash] + max_turns: 5 + timeout_seconds: 180 +graders: + - name: wheels-import + type: regex + pattern: imports-42 + target: trace diff --git a/plugins/code-tidying/evals/probe-explicit-target/case.yaml b/plugins/code-tidying/evals/probe-explicit-target/case.yaml new file mode 100644 index 000000000..ad4ea1ea1 --- /dev/null +++ b/plugins/code-tidying/evals/probe-explicit-target/case.yaml @@ -0,0 +1,31 @@ +schema_version: "1.1" +name: probe-explicit-target +description: Confirms the slash invocation fires dissolve-comments, its proof script runs, and file graders read the edited file +tags: [probe] +runs: 1 +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments app.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: class-a-deleted + type: regex + pattern: "#[^\\n]*increment counter" + match: not_contains + target: { source: file, path: app.sh } + - name: directive-kept + type: regex + pattern: "# shellcheck disable=SC2034" + target: { source: file, path: app.sh } + - name: code-kept + type: regex + pattern: "counter=\\$\\(\\(counter \\+ 1\\)\\)" + target: { source: file, path: app.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both diff --git a/plugins/code-tidying/evals/probe-explicit-target/fixture/app.sh.txt b/plugins/code-tidying/evals/probe-explicit-target/fixture/app.sh.txt new file mode 100644 index 000000000..da0c9e46a --- /dev/null +++ b/plugins/code-tidying/evals/probe-explicit-target/fixture/app.sh.txt @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2034 +unused_flag=1 +counter=0 +# increment counter +counter=$((counter + 1)) +echo "$counter" diff --git a/plugins/code-tidying/evals/probe-explicit-target/scaffold.sh b/plugins/code-tidying/evals/probe-explicit-target/scaffold.sh new file mode 100755 index 000000000..1314a4bd8 --- /dev/null +++ b/plugins/code-tidying/evals/probe-explicit-target/scaffold.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Seeds the eval workspace with app.sh and one commit. +set -euo pipefail + +cp "$(dirname "${BASH_SOURCE[0]}")/fixture/app.sh.txt" app.sh +git init -q +git add app.sh +git -c user.name=eval -c user.email=eval@example.invalid commit -q -m "add app" From d865e889c3a0c38bfcc4b8eb55435c7e98df82bc Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 14 Sep 2026 16:45:46 -0400 Subject: [PATCH 03/17] test(code-tidying): add owner-calibrated dissolve-comments eval cases Twenty-three calibration cases for claude plugin eval cover three frozen real sections (statusline-tee.sh stamp reader, check-silent-revert.sh design block, lib/hook-utils.sh header), invented class A, B, C, exempt-surface and edge fixtures, and the aggressive, strip, safe, --notes, ./aggressive, non-interactive, and UNPROVABLE interactions. Every expected outcome was marked by the owner; kept comments must be at most two succinct lines. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KMAbPZjcSzfppwu1AoCpag --- .../dissolve-comments-aggressive-dial/PLAN.md | 58 +++++++++++++ .../code-tidying/evals/fixtures/Makefile.txt | 3 + .../evals/fixtures/check-markers.sh.txt | 14 ++++ .../evals/fixtures/class-a.sh.txt | 16 ++++ .../evals/fixtures/class-b.sh.txt | 14 ++++ .../evals/fixtures/class-b.test.sh.txt | 14 ++++ .../evals/fixtures/class-c.sh.txt | 38 +++++++++ .../evals/fixtures/dc-notes.md.txt | 1 + .../evals/fixtures/edge-marker.sh.txt | 8 ++ .../evals/fixtures/edge-paired.sh.txt | 7 ++ .../evals/fixtures/edge-paired.test.sh.txt | 10 +++ .../evals/fixtures/edge_docstring.py.txt | 8 ++ .../code-tidying/evals/fixtures/exempt.py.txt | 20 +++++ .../evals/fixtures/hook-utils-header.sh.txt | 59 +++++++++++++ .../fixtures/hook-utils-unprovable.sh.txt | 24 ++++++ .../fixtures/restoration-markers.txt.txt | 1 + plugins/code-tidying/evals/fixtures/seed.sh | 13 +++ .../fixtures/silent-revert-design.sh.txt | 39 +++++++++ .../evals/fixtures/statusline-stamp.sh.txt | 17 ++++ .../interaction-dot-aggressive-path/case.yaml | 43 ++++++++++ .../scaffold.sh | 4 + .../case.yaml | 43 ++++++++++ .../scaffold.sh | 4 + .../case.yaml | 32 +++++++ .../scaffold.sh | 4 + .../interaction-notes-untracked/case.yaml | 37 ++++++++ .../interaction-notes-untracked/scaffold.sh | 5 ++ .../interaction-safe-aggressive/case.yaml | 47 +++++++++++ .../interaction-safe-aggressive/scaffold.sh | 4 + .../evals/interaction-safe-strip/case.yaml | 47 +++++++++++ .../evals/interaction-safe-strip/scaffold.sh | 4 + .../interaction-unprovable-excerpt/case.yaml | 42 ++++++++++ .../scaffold.sh | 2 + .../invented-class-a-aggressive/case.yaml | 37 ++++++++ .../invented-class-a-aggressive/scaffold.sh | 2 + .../evals/invented-class-a-strip/case.yaml | 37 ++++++++ .../evals/invented-class-a-strip/scaffold.sh | 2 + .../invented-class-b-aggressive/case.yaml | 45 ++++++++++ .../invented-class-b-aggressive/scaffold.sh | 2 + .../evals/invented-class-b-strip/case.yaml | 38 +++++++++ .../evals/invented-class-b-strip/scaffold.sh | 2 + .../invented-class-c-aggressive/case.yaml | 62 ++++++++++++++ .../invented-class-c-aggressive/scaffold.sh | 4 + .../evals/invented-class-c-strip/case.yaml | 36 ++++++++ .../evals/invented-class-c-strip/scaffold.sh | 4 + .../evals/invented-edge-aggressive/case.yaml | 51 +++++++++++ .../invented-edge-aggressive/scaffold.sh | 2 + .../evals/invented-edge-strip/case.yaml | 51 +++++++++++ .../evals/invented-edge-strip/scaffold.sh | 2 + .../invented-exempt-aggressive/case.yaml | 49 +++++++++++ .../invented-exempt-aggressive/scaffold.sh | 2 + .../evals/invented-exempt-strip/case.yaml | 49 +++++++++++ .../evals/invented-exempt-strip/scaffold.sh | 2 + .../case.yaml | 84 +++++++++++++++++++ .../scaffold.sh | 2 + .../real-hook-utils-header-strip/case.yaml | 35 ++++++++ .../real-hook-utils-header-strip/scaffold.sh | 2 + .../case.yaml | 42 ++++++++++ .../scaffold.sh | 2 + .../real-silent-revert-design-strip/case.yaml | 31 +++++++ .../scaffold.sh | 2 + .../case.yaml | 51 +++++++++++ .../scaffold.sh | 2 + .../real-statusline-stamp-strip/case.yaml | 31 +++++++ .../real-statusline-stamp-strip/scaffold.sh | 2 + 65 files changed, 1447 insertions(+) create mode 100644 plugins/code-tidying/evals/fixtures/Makefile.txt create mode 100644 plugins/code-tidying/evals/fixtures/check-markers.sh.txt create mode 100644 plugins/code-tidying/evals/fixtures/class-a.sh.txt create mode 100644 plugins/code-tidying/evals/fixtures/class-b.sh.txt create mode 100644 plugins/code-tidying/evals/fixtures/class-b.test.sh.txt create mode 100644 plugins/code-tidying/evals/fixtures/class-c.sh.txt create mode 100644 plugins/code-tidying/evals/fixtures/dc-notes.md.txt create mode 100644 plugins/code-tidying/evals/fixtures/edge-marker.sh.txt create mode 100644 plugins/code-tidying/evals/fixtures/edge-paired.sh.txt create mode 100644 plugins/code-tidying/evals/fixtures/edge-paired.test.sh.txt create mode 100644 plugins/code-tidying/evals/fixtures/edge_docstring.py.txt create mode 100644 plugins/code-tidying/evals/fixtures/exempt.py.txt create mode 100644 plugins/code-tidying/evals/fixtures/hook-utils-header.sh.txt create mode 100644 plugins/code-tidying/evals/fixtures/hook-utils-unprovable.sh.txt create mode 100644 plugins/code-tidying/evals/fixtures/restoration-markers.txt.txt create mode 100755 plugins/code-tidying/evals/fixtures/seed.sh create mode 100644 plugins/code-tidying/evals/fixtures/silent-revert-design.sh.txt create mode 100644 plugins/code-tidying/evals/fixtures/statusline-stamp.sh.txt create mode 100644 plugins/code-tidying/evals/interaction-dot-aggressive-path/case.yaml create mode 100755 plugins/code-tidying/evals/interaction-dot-aggressive-path/scaffold.sh create mode 100644 plugins/code-tidying/evals/interaction-non-interactive-no-target/case.yaml create mode 100755 plugins/code-tidying/evals/interaction-non-interactive-no-target/scaffold.sh create mode 100644 plugins/code-tidying/evals/interaction-notes-tracked-refused/case.yaml create mode 100755 plugins/code-tidying/evals/interaction-notes-tracked-refused/scaffold.sh create mode 100644 plugins/code-tidying/evals/interaction-notes-untracked/case.yaml create mode 100755 plugins/code-tidying/evals/interaction-notes-untracked/scaffold.sh create mode 100644 plugins/code-tidying/evals/interaction-safe-aggressive/case.yaml create mode 100755 plugins/code-tidying/evals/interaction-safe-aggressive/scaffold.sh create mode 100644 plugins/code-tidying/evals/interaction-safe-strip/case.yaml create mode 100755 plugins/code-tidying/evals/interaction-safe-strip/scaffold.sh create mode 100644 plugins/code-tidying/evals/interaction-unprovable-excerpt/case.yaml create mode 100755 plugins/code-tidying/evals/interaction-unprovable-excerpt/scaffold.sh create mode 100644 plugins/code-tidying/evals/invented-class-a-aggressive/case.yaml create mode 100755 plugins/code-tidying/evals/invented-class-a-aggressive/scaffold.sh create mode 100644 plugins/code-tidying/evals/invented-class-a-strip/case.yaml create mode 100755 plugins/code-tidying/evals/invented-class-a-strip/scaffold.sh create mode 100644 plugins/code-tidying/evals/invented-class-b-aggressive/case.yaml create mode 100755 plugins/code-tidying/evals/invented-class-b-aggressive/scaffold.sh create mode 100644 plugins/code-tidying/evals/invented-class-b-strip/case.yaml create mode 100755 plugins/code-tidying/evals/invented-class-b-strip/scaffold.sh create mode 100644 plugins/code-tidying/evals/invented-class-c-aggressive/case.yaml create mode 100755 plugins/code-tidying/evals/invented-class-c-aggressive/scaffold.sh create mode 100644 plugins/code-tidying/evals/invented-class-c-strip/case.yaml create mode 100755 plugins/code-tidying/evals/invented-class-c-strip/scaffold.sh create mode 100644 plugins/code-tidying/evals/invented-edge-aggressive/case.yaml create mode 100755 plugins/code-tidying/evals/invented-edge-aggressive/scaffold.sh create mode 100644 plugins/code-tidying/evals/invented-edge-strip/case.yaml create mode 100755 plugins/code-tidying/evals/invented-edge-strip/scaffold.sh create mode 100644 plugins/code-tidying/evals/invented-exempt-aggressive/case.yaml create mode 100755 plugins/code-tidying/evals/invented-exempt-aggressive/scaffold.sh create mode 100644 plugins/code-tidying/evals/invented-exempt-strip/case.yaml create mode 100755 plugins/code-tidying/evals/invented-exempt-strip/scaffold.sh create mode 100644 plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml create mode 100755 plugins/code-tidying/evals/real-hook-utils-header-aggressive/scaffold.sh create mode 100644 plugins/code-tidying/evals/real-hook-utils-header-strip/case.yaml create mode 100755 plugins/code-tidying/evals/real-hook-utils-header-strip/scaffold.sh create mode 100644 plugins/code-tidying/evals/real-silent-revert-design-aggressive/case.yaml create mode 100755 plugins/code-tidying/evals/real-silent-revert-design-aggressive/scaffold.sh create mode 100644 plugins/code-tidying/evals/real-silent-revert-design-strip/case.yaml create mode 100755 plugins/code-tidying/evals/real-silent-revert-design-strip/scaffold.sh create mode 100644 plugins/code-tidying/evals/real-statusline-stamp-aggressive/case.yaml create mode 100755 plugins/code-tidying/evals/real-statusline-stamp-aggressive/scaffold.sh create mode 100644 plugins/code-tidying/evals/real-statusline-stamp-strip/case.yaml create mode 100755 plugins/code-tidying/evals/real-statusline-stamp-strip/scaffold.sh diff --git a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md index 6d0b3675a..9f5f0b708 100644 --- a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md +++ b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md @@ -296,6 +296,64 @@ comment-hygiene scan do not lint deliberately commented fixtures. 8. Whole-file record (amendment 1): one `--runs 1` run on all of `lib/hook-utils.sh`; record the outcome, turns, and cost here. +**Phase 2 record:** + +- Owner rule (round 1, applies to every mode and every case): a comment that is kept must be + succinct, clear, and justified. Graders enforce it as no kept comment block longer than + `class_c_max_lines` (2) plus a key phrase naming the consequence; Phase 3 writes the rule into + `SKILL.md`. +- Shared fixtures live in `plugins/code-tidying/evals/fixtures/`, seeded by `fixtures/seed.sh`, + which each case's one-line `scaffold.sh` calls. +- Round 1, `statusline-tee.sh` lines 308-324 (`real-statusline-stamp-aggressive`, + `real-statusline-stamp-strip`): + + | Part | `aggressive` | `strip` | + |---|---|---| + | (a) contract: reads a stamp or 0 | delete, staged | delete, staged | + | (b) arithmetic-injection warning | keep, at most 2 lines | delete, staged | + | (c) single-reader rationale | delete, staged | delete, staged | + | (d) builtins-only render-path constraint | delete, staged | delete, staged | + + Expected red on the unchanged skill: `aggressive` graders `contract-deleted`, + `single-reader-rationale-deleted`, `render-path-deleted`; `strip` graders `no-comments-left`, + `narrative-staged`. +- Round 2, `scripts/check-silent-revert.sh` lines 73-106 plus `die()` from lines 368-371 + (`real-silent-revert-design-aggressive`, `real-silent-revert-design-strip`): the heading, the + three rejected designs, and the measured result are deleted and staged in both modes; the + no-threshold warning is kept at most 2 lines under `aggressive` and deleted under `strip`. No + sentence is a paired record. Expected red: `aggressive` `design-history-deleted`; `strip` + `no-comments-left`, `narrative-staged`. +- Owner restatement (round 2): the default answer is delete, or refactor the code until the comment + is unnecessary; every survivor must be justified in the report. +- Rounds 3-9, approved by the owner as one batch (2026-09-14), with the note that a kept line must be + as succinct as the round-3 SSOT line (`# SSOT: edit lib/hook-utils.sh, then run + scripts/sync-hook-utils.sh; CI rejects drifted copies.`): + + | # | Fixture | `aggressive` | `strip` | + |---|---|---|---| + | 3 | `lib/hook-utils.sh` lines 1-59 | keep the one-line SSOT warning and a one-line `_to` calling convention; delete the rest, including the `hook::is_enabled` comment | delete all but the `shellcheck shell=bash` directive | + | 4 | class A: restating comment, commented-out code | delete | delete | + | 5 | class B: `86400` literal, vague `n`, `Makefile` test net | `SECONDS_PER_DAY`, `display_name`, comments deleted | comments deleted, no code change | + | 6 | class C: warning, rationale absent from history, rationale in the commit message, one class-A line | warning kept at most 2 lines; the rest deleted and staged; report names the next commit | delete all | + | 7 | exempt: shebang, license, `# noqa` with reason, `TODO(#12)`, `dissolve-comments-ignore`, one rationale | exempt kept, rationale deleted | same | + | 8 | marker-row identifier, private Python docstring, paired record | comment deleted and identifier never renamed; docstring proposed; paired record kept | same | + | 9 | interactions on the class-C fixture: `safe aggressive`, `safe strip`, `./aggressive`, `--notes` untracked and tracked, non-interactive run with no target; UNPROVABLE `hook-utils.sh` excerpt | `safe` wins; `./aggressive` is a path; untracked notes written, tracked refused; no-target run is safe mode; UNPROVABLE named first, proposals only | same | + +- Cases authored for rounds 3-9: 19 case directories and 15 fixtures, 25 cases in the suite with the + probes. Verified: `validate-cases.py` exit 0; real fixtures byte-identical to their `0a676a578` + slices; every `.sh.txt` fixture passes `bash -n` and self-certifies COMMENT-ONLY, except + `hook-utils-unprovable.sh.txt` (`hook::resolve_read_timeout_to`, lines 1570-1593), which exits 21; + both `.py.txt` fixtures compile and self-certify; the class-B test net passes under `make test` in + WSL2; every comment-deletion `not_contains` grader matches the unedited fixture (red before); + scaffolds pass shellcheck and carry mode 100755. +- Grader decisions: the succinct grader is `(^[ \t]*#(?!!| shellcheck )[^\n]*\n){3}` with `m`, + because JS `\s` spans newlines and the approved hook-utils result is a directive plus two kept + lines; `fork-cost-deleted` anchors on `CreateProcess|copy-on-write|forks a subshell` so a kept + `_to` line may mention forks; `interaction-notes-tracked-refused` has no `proof-ran` grader, + because a refusal before triage is a correct outcome. +- Baseline (`comment-census.sh --json`, before value): `hook-utils-header.sh` 46 of 59 lines are + comment; `silent-revert-design.sh` 34 of 39; `statusline-stamp.sh` 8 of 17; 88 of 115 in total. + **Sanity Check:** - `validate-cases.py plugins/code-tidying/evals` exits 0. diff --git a/plugins/code-tidying/evals/fixtures/Makefile.txt b/plugins/code-tidying/evals/fixtures/Makefile.txt new file mode 100644 index 000000000..9908587ee --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/Makefile.txt @@ -0,0 +1,3 @@ +.PHONY: test +test: + bash class-b.test.sh diff --git a/plugins/code-tidying/evals/fixtures/check-markers.sh.txt b/plugins/code-tidying/evals/fixtures/check-markers.sh.txt new file mode 100644 index 000000000..4a83ac66e --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/check-markers.sh.txt @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Fails when a marker listed in scripts/restoration-markers.txt is missing from src/. +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +status=0 +while IFS= read -r marker; do + [[ -z "$marker" ]] && continue + if ! grep -rqF -- "$marker" "$root/src"; then + echo "missing restoration marker: $marker" >&2 + status=1 + fi +done <"$root/scripts/restoration-markers.txt" +exit "$status" diff --git a/plugins/code-tidying/evals/fixtures/class-a.sh.txt b/plugins/code-tidying/evals/fixtures/class-a.sh.txt new file mode 100644 index 000000000..b98a46de4 --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/class-a.sh.txt @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +counter=0 +total=0 + +for value in "$@"; do + total=$((total + value)) + # increment counter + counter=$((counter + 1)) +done + +# old_total=$((total * 2)) +# echo "$old_total" + +echo "count=$counter total=$total" diff --git a/plugins/code-tidying/evals/fixtures/class-b.sh.txt b/plugins/code-tidying/evals/fixtures/class-b.sh.txt new file mode 100644 index 000000000..ec4aadceb --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/class-b.sh.txt @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +cache_ttl_seconds() { + local days="$1" + # seconds per day + local ttl=$((days * 86400)) + echo "$ttl" +} + +greeting() { + # user's display name + local n="$1" + echo "Hello, $n" +} diff --git a/plugins/code-tidying/evals/fixtures/class-b.test.sh.txt b/plugins/code-tidying/evals/fixtures/class-b.test.sh.txt new file mode 100644 index 000000000..9826988b8 --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/class-b.test.sh.txt @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail +source "$(dirname "${BASH_SOURCE[0]}")/class-b.sh" + +fail=0 +[[ "$(cache_ttl_seconds 2)" == "172800" ]] || { + echo "cache_ttl_seconds 2: expected 172800" >&2 + fail=1 +} +[[ "$(greeting Ada)" == "Hello, Ada" ]] || { + echo "greeting Ada: expected Hello, Ada" >&2 + fail=1 +} +exit "$fail" diff --git a/plugins/code-tidying/evals/fixtures/class-c.sh.txt b/plugins/code-tidying/evals/fixtures/class-c.sh.txt new file mode 100644 index 000000000..5e5575eec --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/class-c.sh.txt @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +lock_file="${LOCK_FILE:-worker.lock}" +api_url="${API_URL:-https://api.example.com}" + +prepare_lock() { + : >"$lock_file" +} + +start_worker() { + echo "worker started with lock $lock_file" +} + +fetch_status() { + local attempt + # Retries three times because the upstream API drops the first connection after idle. + local retries=3 + for ((attempt = 1; attempt <= retries; attempt++)); do + if curl --silent --max-time "$timeout" "$api_url/status"; then + return 0 + fi + done + return 1 +} + +# set the counter to zero +counter=0 + +# Uses a 5 second timeout to stay under the load balancer's 6 second idle cutoff. +timeout=5 + +# Must run before start_worker, or the lock file deadlocks the worker. +prepare_lock +start_worker + +fetch_status || counter=$((counter + 1)) +echo "failures: $counter" diff --git a/plugins/code-tidying/evals/fixtures/dc-notes.md.txt b/plugins/code-tidying/evals/fixtures/dc-notes.md.txt new file mode 100644 index 000000000..395028ae4 --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/dc-notes.md.txt @@ -0,0 +1 @@ +seed notes diff --git a/plugins/code-tidying/evals/fixtures/edge-marker.sh.txt b/plugins/code-tidying/evals/fixtures/edge-marker.sh.txt new file mode 100644 index 000000000..b5cfc8df3 --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/edge-marker.sh.txt @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +next_page_window() { + local offset="$1" page_size="$2" + # end offset of the current page + gql_page_end=$((offset + page_size)) + echo "$offset $gql_page_end" +} diff --git a/plugins/code-tidying/evals/fixtures/edge-paired.sh.txt b/plugins/code-tidying/evals/fixtures/edge-paired.sh.txt new file mode 100644 index 000000000..7ba9124d1 --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/edge-paired.sh.txt @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +has_input() { + # Empty input must return 1, not 0; tests/edge-paired.test.sh pins this. + [[ -n "${1:-}" ]] || return 1 + return 0 +} diff --git a/plugins/code-tidying/evals/fixtures/edge-paired.test.sh.txt b/plugins/code-tidying/evals/fixtures/edge-paired.test.sh.txt new file mode 100644 index 000000000..6bd42749a --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/edge-paired.test.sh.txt @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail +source "$(dirname "${BASH_SOURCE[0]}")/../src/edge-paired.sh" + +if has_input ""; then + echo "has_input returned 0 for empty input; expected 1" >&2 + exit 1 +fi +has_input "x" +echo "ok" diff --git a/plugins/code-tidying/evals/fixtures/edge_docstring.py.txt b/plugins/code-tidying/evals/fixtures/edge_docstring.py.txt new file mode 100644 index 000000000..3dc73c4ba --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/edge_docstring.py.txt @@ -0,0 +1,8 @@ +def _parse(line): + """Split a header line into key and value.""" + key, _, value = line.partition(":") + return key.strip(), value.strip() + + +def parse_headers(lines): + return dict(_parse(line) for line in lines if ":" in line) diff --git a/plugins/code-tidying/evals/fixtures/exempt.py.txt b/plugins/code-tidying/evals/fixtures/exempt.py.txt new file mode 100644 index 000000000..b01cdb2f6 --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/exempt.py.txt @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Example Corp. +# Licensed under the MIT License. See LICENSE in the project root. + +URL = "https://example.com/api/v1/pages?include=title,author,updated_at&sort=updated_at&order=descending&limit=100" # noqa: E501 long canonical URL + +# TODO(#12): replace polling with a webhook +POLL_SECONDS = 30 + + +def fetch_pages(client): + pages = client.get(URL) + # Sorted here because the API returns pages in arbitrary order. + pages.sort(key=lambda page: page["updated_at"]) + return pages + + +def retry_delay(attempt): + # dissolve-comments-ignore + return min(2**attempt, POLL_SECONDS) # capped so a retry never outlasts one poll interval diff --git a/plugins/code-tidying/evals/fixtures/hook-utils-header.sh.txt b/plugins/code-tidying/evals/fixtures/hook-utils-header.sh.txt new file mode 100644 index 000000000..c8fc6af87 --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/hook-utils-header.sh.txt @@ -0,0 +1,59 @@ +# shellcheck shell=bash +# Shared hook utility library for this marketplace's hook plugins. Sourced +# (not executed): kill switch, file_path parsing + path normalization, +# repo-root resolution, additionalContext accumulator, telemetry envelope. +# +# SINGLE SOURCE OF TRUTH: lib/hook-utils.sh at the marketplace repo root. The +# copies at plugins/*/hooks/hook-utils.sh exist because installed plugins are +# cache-isolated and must be self-contained — never edit a copy. Edit the +# source and run scripts/sync-hook-utils.sh; CI rejects drifted copies. +# +# CALLING CONVENTION: a helper that produces a value is spelled +# `hook::_to [args…]` and writes into the caller's variable. That +# is the one convention; call it directly rather than wrapping it in `$( )`. +# GNU Bash forks a subshell for every command substitution even when the body +# is only builtins (Command Substitution, Bash Reference Manual; +# https://mywiki.wooledge.org/CommandSubstitution), and on Windows Git Bash a +# fork is a non-copy-on-write Win32 CreateProcess costing milliseconds, so a +# capture around a `_to` helper is pure loss on hooks that run per edit. +# Five helpers still print instead — hook::json_escape, hook::physical_path, +# hook::repo_root, hook::buffer_stdin and hook::read_file_path (which reads +# fd0 and has no `_to` twin). Each carries, at its definition, the one-line +# reason its capture is still paid for. + +# Guard against double-sourcing. +[[ -n "${_HOOK_UTILS_LOADED:-}" ]] && return 0 +readonly _HOOK_UTILS_LOADED=1 + +# Per-hook kill switch via the plugin's _enabled userConfig boolean, +# read from the hook-process CLAUDE_PLUGIN_OPTION__ENABLED mirror. +# Exits 0 (allow) if disabled. Place after source, before stdin parsing. +# hook::check_enabled "MARKDOWN_FORMAT" # checks CLAUDE_PLUGIN_OPTION_MARKDOWN_FORMAT_ENABLED +# +# Deliberately NOT layered with a marketplace-specific fleet switch. Claude Code +# already ships the coarse controls, and a parallel scheme here would become a +# second source of truth for the same question: +# * `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with every customization +# (CLAUDE.md, plugins, skills, hooks, MCP servers) disabled +# * `disableAllHooks` — disable all hooks and any custom status line +# * `claude plugin disable|enable ` — per-plugin, dependency-aware +# This helper stays scoped to the one thing it owns: the plugin's own +# `_enabled` userConfig boolean, surfaced to hook processes as the native +# `$CLAUDE_PLUGIN_OPTION_` mirror. + +# hook::is_enabled — the same check as a PREDICATE. Returns 0 when the +# plugin should run, 1 when it should not. For callers that must not terminate +# the process on a "disabled" answer. +# +# The statusline tee is exactly that caller: it is a TRANSPARENT WRAPPER around +# the user's real statusline, so exiting 0 on "disabled" would suppress the +# wrapped command's output and blank the status line. It needs to skip its own +# side effect and still pass through. +hook::is_enabled() { + local var_name="CLAUDE_PLUGIN_OPTION_${1}_ENABLED" + [[ "${!var_name:-true}" == "true" ]] +} + +hook::check_enabled() { + hook::is_enabled "$1" || exit 0 +} diff --git a/plugins/code-tidying/evals/fixtures/hook-utils-unprovable.sh.txt b/plugins/code-tidying/evals/fixtures/hook-utils-unprovable.sh.txt new file mode 100644 index 000000000..828f48783 --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/hook-utils-unprovable.sh.txt @@ -0,0 +1,24 @@ +# hook::resolve_read_timeout_to +# Write the resolved timeout into in THIS shell. GNU Bash runs command +# substitution in a subshell even when the body is only builtins (Command +# Execution Environment), so a capture here is a startup fork on every hook; +# this is the only spelling, and the suite pins that buffer_stdin uses it. +hook::resolve_read_timeout_to() { + local __hu_dest="$1" + local __hu_t="${CLAUDE_PLUGIN_OPTION_STDIN_READ_TIMEOUT:-2}" + if [[ "$__hu_t" != "2" ]]; then + if ! [[ "$__hu_t" =~ ^[0-9]+(\.[0-9]+)?$ ]] || [[ "$__hu_t" =~ ^0+(\.0+)?$ ]] || + { [[ "$__hu_t" == *.* ]] && ! hook::read_supports_fractional_timeout; }; then + __hu_t=2 + elif [[ "$__hu_t" =~ ^([0-9]+)(\.([0-9]+))?$ ]]; then + local whole="${BASH_REMATCH[1]}" frac="${BASH_REMATCH[3]:-}" + frac="${frac}000000" + frac="${frac:0:6}" + local micros=$((10#$whole * 1000000 + 10#$frac)) + if ((micros < HOOK_STDIN_READ_TIMEOUT_MIN_MICROS)); then + __hu_t=2 + fi + fi + fi + printf -v "$__hu_dest" '%s' "$__hu_t" +} diff --git a/plugins/code-tidying/evals/fixtures/restoration-markers.txt.txt b/plugins/code-tidying/evals/fixtures/restoration-markers.txt.txt new file mode 100644 index 000000000..0e47ab3a4 --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/restoration-markers.txt.txt @@ -0,0 +1 @@ +gql_page_end= diff --git a/plugins/code-tidying/evals/fixtures/seed.sh b/plugins/code-tidying/evals/fixtures/seed.sh new file mode 100755 index 000000000..0375c6018 --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/seed.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Seeds the eval workspace with fixtures/.txt copied to , committed once. +# Usage: seed.sh ... +set -euo pipefail + +fixtures="$(dirname "${BASH_SOURCE[0]}")" +git init -q +for name in "$@"; do + mkdir -p "$(dirname "$name")" + cp "$fixtures/$(basename "$name").txt" "$name" + git add "$name" +done +git -c user.name=eval -c user.email=eval@example.invalid commit -q -m "add fixtures" diff --git a/plugins/code-tidying/evals/fixtures/silent-revert-design.sh.txt b/plugins/code-tidying/evals/fixtures/silent-revert-design.sh.txt new file mode 100644 index 000000000..f980df2f5 --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/silent-revert-design.sh.txt @@ -0,0 +1,39 @@ +# WHY BLAME-OF-DELETED-LINES, AND NOT THE ALTERNATIVES +# --------------------------------------------------- +# Three designs were measured against the real history before this one was +# chosen. +# +# Curated marker strings (#2691's own suggestion 3). Catches only what +# somebody pre-registered. Nobody had registered #2644, #2642, #2635 or +# #2639 -- registration happens after you already know a fix matters, which +# is exactly the knowledge the incident destroys. It also decays: the list is +# only as fresh as the last person who remembered to append to it. +# +# Merge-base staleness (the PR's branch point vs. what landed since). +# Tested and REJECTED on evidence: it exonerates all three real incidents, +# because all three branches were up to date in history. It is a +# false-negative machine for the exact class it is meant to catch. +# +# PR-creation-time overlap (culprit landed after the PR was opened). +# Tested and rejected as non-discriminating: at the 17-concurrent-PR rate the +# ADR records for this repo, nearly every PR has siblings landing while it is +# open, so it fires on almost everything. +# +# What is left is content: blame the lines a merge deleted and see who had just +# added them. Measured over the 500 first-parent commits of main ending at +# 738791c45, the three known incidents score 853 / 451 / 346 lines against a +# single recent commit -- plus a fourth attribution of 298 lines on the SAME +# #2633 squash, whose deletions trace to two different culprits and are +# reported separately. Unlike the two designs rejected above, this one fires on +# all three rather than exonerating them. +# +# What it does NOT buy is a clean split between incidents and ordinary work. +# Measured below, the two populations OVERLAP on volume -- the smallest true +# finding scores under both verified-legitimate fires. So what makes the +# canary livable is the disposition path and the non-blocking posture, not a +# number that separates the shapes. No number does. + +die() { + echo "check-silent-revert: $*" >&2 + exit 2 +} diff --git a/plugins/code-tidying/evals/fixtures/statusline-stamp.sh.txt b/plugins/code-tidying/evals/fixtures/statusline-stamp.sh.txt new file mode 100644 index 000000000..581039dfc --- /dev/null +++ b/plugins/code-tidying/evals/fixtures/statusline-stamp.sh.txt @@ -0,0 +1,17 @@ +# Read one of this writer's epoch-second stamp files into the NAMED variable, or +# 0 when the file is absent, unreadable, empty, or holds anything that is not a +# plain integer. Five call sites read a stamp exactly this way, and the +# validation is the load-bearing half: bash evaluates the TEXT of an arithmetic +# operand, so a stamp shaped like `a[$(cmd)]` would run cmd on every render. +# Naming it once keeps all five spelled identically, the same reason +# _rlg_bash_at_least exists. Builtins throughout, so no call site pays a +# process — every one of them is on the render path. +_rlg_read_stamp() { + local _var="$1" _val=0 + if [[ -f "$2" ]]; then + IFS= read -r _val <"$2" || _val=0 + [[ "$_val" =~ ^[0-9]+$ ]] || _val=0 + fi + printf -v "$_var" '%s' "$_val" + return 0 +} diff --git a/plugins/code-tidying/evals/interaction-dot-aggressive-path/case.yaml b/plugins/code-tidying/evals/interaction-dot-aggressive-path/case.yaml new file mode 100644 index 000000000..1aefac752 --- /dev/null +++ b/plugins/code-tidying/evals/interaction-dot-aggressive-path/case.yaml @@ -0,0 +1,43 @@ +schema_version: "1.1" +name: interaction-dot-aggressive-path +description: ./aggressive is read as a directory target under the default posture, not as the aggressive mode +tags: [calibration, interaction, invented] +expected_outcome: aggressive/class-c.sh is triaged under default strict; the set-the-counter comment is deleted; the retry rationale, absent from history, is kept; code is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments ./aggressive + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: class-a-deleted + type: regex + pattern: '#[^\n]*set the counter to zero' + match: not_contains + target: { source: file, path: aggressive/class-c.sh } + - name: retry-rationale-kept + type: regex + pattern: '#[^\n]*upstream API' + target: { source: file, path: aggressive/class-c.sh } + - name: kept-comment-succinct + type: regex + pattern: '(^[ \t]*#(?!!| shellcheck )[^\n]*\n){3}' + flags: m + match: not_contains + target: { source: file, path: aggressive/class-c.sh } + - name: code-intact + type: regex + pattern: '^prepare_lock\(\) \{\n : >"\$lock_file"\n\}[\s\S]*^ local retries=3\n for \(\(attempt = 1; attempt <= retries; attempt\+\+\)\); do\n if curl --silent --max-time "\$timeout" "\$api_url/status"; then\n return 0\n fi\n done\n return 1\n\}[\s\S]*^counter=0$[\s\S]*^timeout=5$[\s\S]*^prepare_lock\nstart_worker\n\nfetch_status \|\| counter=\$\(\(counter \+ 1\)\)\necho "failures: \$counter"$' + flags: m + target: { source: file, path: aggressive/class-c.sh } + - name: path-triaged + type: tool_used + tool: Bash + input_match: aggressive/class-c + arm: both + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both diff --git a/plugins/code-tidying/evals/interaction-dot-aggressive-path/scaffold.sh b/plugins/code-tidying/evals/interaction-dot-aggressive-path/scaffold.sh new file mode 100755 index 000000000..d899fda13 --- /dev/null +++ b/plugins/code-tidying/evals/interaction-dot-aggressive-path/scaffold.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" aggressive/class-c.sh +git -c user.name=eval -c user.email=eval@example.invalid commit --amend -q -m "add fixtures" -m "Uses a 5 second timeout to stay under the load balancer's 6 second idle cutoff." diff --git a/plugins/code-tidying/evals/interaction-non-interactive-no-target/case.yaml b/plugins/code-tidying/evals/interaction-non-interactive-no-target/case.yaml new file mode 100644 index 000000000..01142bfa7 --- /dev/null +++ b/plugins/code-tidying/evals/interaction-non-interactive-no-target/case.yaml @@ -0,0 +1,43 @@ +schema_version: "1.1" +name: interaction-non-interactive-no-target +description: aggressive with no target on a clean committed tree widens to the repository rung and runs in safe mode non-interactively +tags: [calibration, interaction, aggressive, invented] +expected_outcome: The scope widens past the empty diffs, the non-interactive run takes safe mode and says so, and no class-C comment is deleted; the warning and the retry rationale stay; code is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments aggressive + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: warning-kept + type: regex + pattern: '#[^\n]*deadlock' + flags: i + target: { source: file, path: class-c.sh } + - name: retry-rationale-kept + type: regex + pattern: '#[^\n]*upstream API' + target: { source: file, path: class-c.sh } + - name: safe-mode-named + type: regex + pattern: 'safe' + flags: i + target: last_message + - name: kept-comment-succinct + type: regex + pattern: '(^[ \t]*#(?!!| shellcheck )[^\n]*\n){3}' + flags: m + match: not_contains + target: { source: file, path: class-c.sh } + - name: code-intact + type: regex + pattern: '^prepare_lock\(\) \{\n : >"\$lock_file"\n\}[\s\S]*^ local retries=3\n for \(\(attempt = 1; attempt <= retries; attempt\+\+\)\); do\n if curl --silent --max-time "\$timeout" "\$api_url/status"; then\n return 0\n fi\n done\n return 1\n\}[\s\S]*^counter=0$[\s\S]*^timeout=5$[\s\S]*^prepare_lock\nstart_worker\n\nfetch_status \|\| counter=\$\(\(counter \+ 1\)\)\necho "failures: \$counter"$' + flags: m + target: { source: file, path: class-c.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both diff --git a/plugins/code-tidying/evals/interaction-non-interactive-no-target/scaffold.sh b/plugins/code-tidying/evals/interaction-non-interactive-no-target/scaffold.sh new file mode 100755 index 000000000..483561a85 --- /dev/null +++ b/plugins/code-tidying/evals/interaction-non-interactive-no-target/scaffold.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" class-c.sh +git -c user.name=eval -c user.email=eval@example.invalid commit --amend -q -m "add fixtures" -m "Uses a 5 second timeout to stay under the load balancer's 6 second idle cutoff." diff --git a/plugins/code-tidying/evals/interaction-notes-tracked-refused/case.yaml b/plugins/code-tidying/evals/interaction-notes-tracked-refused/case.yaml new file mode 100644 index 000000000..9b32c476b --- /dev/null +++ b/plugins/code-tidying/evals/interaction-notes-tracked-refused/case.yaml @@ -0,0 +1,32 @@ +schema_version: "1.1" +name: interaction-notes-tracked-refused +description: --notes pointing at a tracked file is refused and the file is left unchanged +tags: [calibration, interaction, aggressive, invented] +expected_outcome: The run refuses to write notes into the tracked notes/dc-notes.md, says so in the final message, and the file still reads seed notes; code is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments aggressive --notes notes/dc-notes.md class-c.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: notes-not-written + type: regex + pattern: 'upstream API' + match: not_contains + target: { source: file, path: notes/dc-notes.md } + - name: notes-seed-intact + type: regex + pattern: '^seed notes\n$' + target: { source: file, path: notes/dc-notes.md } + - name: refusal-reported + type: regex + pattern: 'refus|tracked' + flags: i + target: last_message + - name: code-intact + type: regex + pattern: '^prepare_lock\(\) \{\n : >"\$lock_file"\n\}[\s\S]*^ local retries=3\n for \(\(attempt = 1; attempt <= retries; attempt\+\+\)\); do\n if curl --silent --max-time "\$timeout" "\$api_url/status"; then\n return 0\n fi\n done\n return 1\n\}[\s\S]*^counter=0$[\s\S]*^timeout=5$[\s\S]*^prepare_lock\nstart_worker\n\nfetch_status \|\| counter=\$\(\(counter \+ 1\)\)\necho "failures: \$counter"$' + flags: m + target: { source: file, path: class-c.sh } diff --git a/plugins/code-tidying/evals/interaction-notes-tracked-refused/scaffold.sh b/plugins/code-tidying/evals/interaction-notes-tracked-refused/scaffold.sh new file mode 100755 index 000000000..5d51a6dd7 --- /dev/null +++ b/plugins/code-tidying/evals/interaction-notes-tracked-refused/scaffold.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" class-c.sh notes/dc-notes.md +git -c user.name=eval -c user.email=eval@example.invalid commit --amend -q -m "add fixtures" -m "Uses a 5 second timeout to stay under the load balancer's 6 second idle cutoff." diff --git a/plugins/code-tidying/evals/interaction-notes-untracked/case.yaml b/plugins/code-tidying/evals/interaction-notes-untracked/case.yaml new file mode 100644 index 000000000..a197dce36 --- /dev/null +++ b/plugins/code-tidying/evals/interaction-notes-untracked/case.yaml @@ -0,0 +1,37 @@ +schema_version: "1.1" +name: interaction-notes-untracked +description: --notes pointing into an untracked directory writes the staged narrative block to that file +tags: [calibration, interaction, aggressive, invented] +expected_outcome: notes/dc-notes.md is created and carries the staged rationale for the removed retry and timeout comments; code is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments aggressive --notes notes/dc-notes.md class-c.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: notes-file-created + type: file_exists + path: notes/dc-notes.md + - name: notes-carry-staged-block + type: regex + pattern: 'upstream API|load balancer' + flags: i + target: { source: file, path: notes/dc-notes.md } + - name: kept-comment-succinct + type: regex + pattern: '(^[ \t]*#(?!!| shellcheck )[^\n]*\n){3}' + flags: m + match: not_contains + target: { source: file, path: class-c.sh } + - name: code-intact + type: regex + pattern: '^prepare_lock\(\) \{\n : >"\$lock_file"\n\}[\s\S]*^ local retries=3\n for \(\(attempt = 1; attempt <= retries; attempt\+\+\)\); do\n if curl --silent --max-time "\$timeout" "\$api_url/status"; then\n return 0\n fi\n done\n return 1\n\}[\s\S]*^counter=0$[\s\S]*^timeout=5$[\s\S]*^prepare_lock\nstart_worker\n\nfetch_status \|\| counter=\$\(\(counter \+ 1\)\)\necho "failures: \$counter"$' + flags: m + target: { source: file, path: class-c.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both diff --git a/plugins/code-tidying/evals/interaction-notes-untracked/scaffold.sh b/plugins/code-tidying/evals/interaction-notes-untracked/scaffold.sh new file mode 100755 index 000000000..58a99ab07 --- /dev/null +++ b/plugins/code-tidying/evals/interaction-notes-untracked/scaffold.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail +bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" class-c.sh +git -c user.name=eval -c user.email=eval@example.invalid commit --amend -q -m "add fixtures" -m "Uses a 5 second timeout to stay under the load balancer's 6 second idle cutoff." +mkdir -p notes diff --git a/plugins/code-tidying/evals/interaction-safe-aggressive/case.yaml b/plugins/code-tidying/evals/interaction-safe-aggressive/case.yaml new file mode 100644 index 000000000..f98e8f115 --- /dev/null +++ b/plugins/code-tidying/evals/interaction-safe-aggressive/case.yaml @@ -0,0 +1,47 @@ +schema_version: "1.1" +name: interaction-safe-aggressive +description: safe combined with aggressive on the class-C script applies only the class-A deletion +tags: [calibration, interaction, aggressive, invented] +expected_outcome: safe wins over aggressive, so only the set-the-counter comment is deleted; the warning and both rationale comments stay; code is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments safe aggressive class-c.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: class-a-deleted + type: regex + pattern: '#[^\n]*set the counter to zero' + match: not_contains + target: { source: file, path: class-c.sh } + - name: warning-kept + type: regex + pattern: '#[^\n]*deadlock' + flags: i + target: { source: file, path: class-c.sh } + - name: retry-rationale-kept + type: regex + pattern: '#[^\n]*upstream API' + target: { source: file, path: class-c.sh } + - name: timeout-rationale-kept + type: regex + pattern: '#[^\n]*load balancer' + target: { source: file, path: class-c.sh } + - name: kept-comment-succinct + type: regex + pattern: '(^[ \t]*#(?!!| shellcheck )[^\n]*\n){3}' + flags: m + match: not_contains + target: { source: file, path: class-c.sh } + - name: code-intact + type: regex + pattern: '^prepare_lock\(\) \{\n : >"\$lock_file"\n\}[\s\S]*^ local retries=3\n for \(\(attempt = 1; attempt <= retries; attempt\+\+\)\); do\n if curl --silent --max-time "\$timeout" "\$api_url/status"; then\n return 0\n fi\n done\n return 1\n\}[\s\S]*^counter=0$[\s\S]*^timeout=5$[\s\S]*^prepare_lock\nstart_worker\n\nfetch_status \|\| counter=\$\(\(counter \+ 1\)\)\necho "failures: \$counter"$' + flags: m + target: { source: file, path: class-c.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both diff --git a/plugins/code-tidying/evals/interaction-safe-aggressive/scaffold.sh b/plugins/code-tidying/evals/interaction-safe-aggressive/scaffold.sh new file mode 100755 index 000000000..483561a85 --- /dev/null +++ b/plugins/code-tidying/evals/interaction-safe-aggressive/scaffold.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" class-c.sh +git -c user.name=eval -c user.email=eval@example.invalid commit --amend -q -m "add fixtures" -m "Uses a 5 second timeout to stay under the load balancer's 6 second idle cutoff." diff --git a/plugins/code-tidying/evals/interaction-safe-strip/case.yaml b/plugins/code-tidying/evals/interaction-safe-strip/case.yaml new file mode 100644 index 000000000..809c21bad --- /dev/null +++ b/plugins/code-tidying/evals/interaction-safe-strip/case.yaml @@ -0,0 +1,47 @@ +schema_version: "1.1" +name: interaction-safe-strip +description: safe combined with strip on the class-C script applies only the class-A deletion +tags: [calibration, interaction, strip, invented] +expected_outcome: safe wins over strip, so only the set-the-counter comment is deleted; the warning and both rationale comments stay; code is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments safe strip class-c.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: class-a-deleted + type: regex + pattern: '#[^\n]*set the counter to zero' + match: not_contains + target: { source: file, path: class-c.sh } + - name: warning-kept + type: regex + pattern: '#[^\n]*deadlock' + flags: i + target: { source: file, path: class-c.sh } + - name: retry-rationale-kept + type: regex + pattern: '#[^\n]*upstream API' + target: { source: file, path: class-c.sh } + - name: timeout-rationale-kept + type: regex + pattern: '#[^\n]*load balancer' + target: { source: file, path: class-c.sh } + - name: kept-comment-succinct + type: regex + pattern: '(^[ \t]*#(?!!| shellcheck )[^\n]*\n){3}' + flags: m + match: not_contains + target: { source: file, path: class-c.sh } + - name: code-intact + type: regex + pattern: '^prepare_lock\(\) \{\n : >"\$lock_file"\n\}[\s\S]*^ local retries=3\n for \(\(attempt = 1; attempt <= retries; attempt\+\+\)\); do\n if curl --silent --max-time "\$timeout" "\$api_url/status"; then\n return 0\n fi\n done\n return 1\n\}[\s\S]*^counter=0$[\s\S]*^timeout=5$[\s\S]*^prepare_lock\nstart_worker\n\nfetch_status \|\| counter=\$\(\(counter \+ 1\)\)\necho "failures: \$counter"$' + flags: m + target: { source: file, path: class-c.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both diff --git a/plugins/code-tidying/evals/interaction-safe-strip/scaffold.sh b/plugins/code-tidying/evals/interaction-safe-strip/scaffold.sh new file mode 100755 index 000000000..483561a85 --- /dev/null +++ b/plugins/code-tidying/evals/interaction-safe-strip/scaffold.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" class-c.sh +git -c user.name=eval -c user.email=eval@example.invalid commit --amend -q -m "add fixtures" -m "Uses a 5 second timeout to stay under the load balancer's 6 second idle cutoff." diff --git a/plugins/code-tidying/evals/interaction-unprovable-excerpt/case.yaml b/plugins/code-tidying/evals/interaction-unprovable-excerpt/case.yaml new file mode 100644 index 000000000..c7f77a79e --- /dev/null +++ b/plugins/code-tidying/evals/interaction-unprovable-excerpt/case.yaml @@ -0,0 +1,42 @@ +schema_version: "1.1" +name: interaction-unprovable-excerpt +description: aggressive on a real lib/hook-utils.sh excerpt that change-shape cannot parse names the file UNPROVABLE up front and applies no deletion +tags: [calibration, interaction, aggressive, real] +expected_outcome: The self-parse check runs before the census and reports the file as UNPROVABLE first; every comment is left in place and any removal is only proposed; code is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments aggressive hook-utils-unprovable.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: unprovable-named + type: regex + pattern: 'UNPROVABLE' + target: last_message + - name: signature-comment-kept + type: regex + pattern: '#[^\n]*hook::resolve_read_timeout_to ' + target: { source: file, path: hook-utils-unprovable.sh } + - name: fork-rationale-kept + type: regex + pattern: '#[^\n]*startup fork on every hook' + target: { source: file, path: hook-utils-unprovable.sh } + - name: spelling-pin-kept + type: regex + pattern: '#[^\n]*buffer_stdin uses it' + target: { source: file, path: hook-utils-unprovable.sh } + - name: code-intact + type: regex + pattern: 'hook::resolve_read_timeout_to\(\) \{\n local __hu_dest="\$1"[\s\S]* local micros=\$\(\(10#\$whole \* 1000000 \+ 10#\$frac\)\)[\s\S]* printf -v "\$__hu_dest" ''%s'' "\$__hu_t"\n\}' + target: { source: file, path: hook-utils-unprovable.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both + - name: self-parse-before-census + type: tool_order + before: { tool: Bash, input_match: change-shape } + after: { tool: Bash, input_match: comment-census } diff --git a/plugins/code-tidying/evals/interaction-unprovable-excerpt/scaffold.sh b/plugins/code-tidying/evals/interaction-unprovable-excerpt/scaffold.sh new file mode 100755 index 000000000..9d6e2e6c4 --- /dev/null +++ b/plugins/code-tidying/evals/interaction-unprovable-excerpt/scaffold.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" hook-utils-unprovable.sh diff --git a/plugins/code-tidying/evals/invented-class-a-aggressive/case.yaml b/plugins/code-tidying/evals/invented-class-a-aggressive/case.yaml new file mode 100644 index 000000000..e377c38bb --- /dev/null +++ b/plugins/code-tidying/evals/invented-class-a-aggressive/case.yaml @@ -0,0 +1,37 @@ +schema_version: "1.1" +name: invented-class-a-aggressive +description: aggressive on an invented script deletes a restating comment and a commented-out code block and keeps the shebang +tags: [calibration, aggressive, invented] +expected_outcome: The increment-counter comment and both commented-out lines are deleted; the shebang stays; every code line is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments aggressive class-a.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: restating-deleted + type: regex + pattern: '#[^\n]*increment counter' + match: not_contains + target: { source: file, path: class-a.sh } + - name: dead-code-deleted + type: regex + pattern: '#[^\n]*old_total' + match: not_contains + target: { source: file, path: class-a.sh } + - name: shebang-kept + type: regex + pattern: '^#!/usr/bin/env bash\n' + target: { source: file, path: class-a.sh } + - name: code-intact + type: regex + pattern: '^counter=0\ntotal=0\n\nfor value in "\$@"; do\n total=\$\(\(total \+ value\)\)$[\s\S]*^ counter=\$\(\(counter \+ 1\)\)\ndone$[\s\S]*^echo "count=\$counter total=\$total"$' + flags: m + target: { source: file, path: class-a.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both diff --git a/plugins/code-tidying/evals/invented-class-a-aggressive/scaffold.sh b/plugins/code-tidying/evals/invented-class-a-aggressive/scaffold.sh new file mode 100755 index 000000000..af69f30f0 --- /dev/null +++ b/plugins/code-tidying/evals/invented-class-a-aggressive/scaffold.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" class-a.sh diff --git a/plugins/code-tidying/evals/invented-class-a-strip/case.yaml b/plugins/code-tidying/evals/invented-class-a-strip/case.yaml new file mode 100644 index 000000000..27e2b558a --- /dev/null +++ b/plugins/code-tidying/evals/invented-class-a-strip/case.yaml @@ -0,0 +1,37 @@ +schema_version: "1.1" +name: invented-class-a-strip +description: strip on an invented script deletes a restating comment and a commented-out code block and keeps the shebang +tags: [calibration, strip, invented] +expected_outcome: The increment-counter comment and both commented-out lines are deleted; the shebang stays; every code line is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments strip class-a.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: restating-deleted + type: regex + pattern: '#[^\n]*increment counter' + match: not_contains + target: { source: file, path: class-a.sh } + - name: dead-code-deleted + type: regex + pattern: '#[^\n]*old_total' + match: not_contains + target: { source: file, path: class-a.sh } + - name: shebang-kept + type: regex + pattern: '^#!/usr/bin/env bash\n' + target: { source: file, path: class-a.sh } + - name: code-intact + type: regex + pattern: '^counter=0\ntotal=0\n\nfor value in "\$@"; do\n total=\$\(\(total \+ value\)\)$[\s\S]*^ counter=\$\(\(counter \+ 1\)\)\ndone$[\s\S]*^echo "count=\$counter total=\$total"$' + flags: m + target: { source: file, path: class-a.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both diff --git a/plugins/code-tidying/evals/invented-class-a-strip/scaffold.sh b/plugins/code-tidying/evals/invented-class-a-strip/scaffold.sh new file mode 100755 index 000000000..af69f30f0 --- /dev/null +++ b/plugins/code-tidying/evals/invented-class-a-strip/scaffold.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" class-a.sh diff --git a/plugins/code-tidying/evals/invented-class-b-aggressive/case.yaml b/plugins/code-tidying/evals/invented-class-b-aggressive/case.yaml new file mode 100644 index 000000000..6624e81f1 --- /dev/null +++ b/plugins/code-tidying/evals/invented-class-b-aggressive/case.yaml @@ -0,0 +1,45 @@ +schema_version: "1.1" +name: invented-class-b-aggressive +description: aggressive on an invented script with a Makefile test net dissolves a magic-literal comment and a vague-local comment into names +tags: [calibration, aggressive, invented] +expected_outcome: 86400 becomes a SECONDS_PER_DAY name and n becomes display_name behind the discovered make test net, then both comments are deleted; the untouched lines are unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments aggressive class-b.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: magic-literal-named + type: regex + pattern: 'SECONDS_PER_DAY' + target: { source: file, path: class-b.sh } + - name: vague-local-renamed + type: regex + pattern: 'display_name' + target: { source: file, path: class-b.sh } + - name: seconds-comment-deleted + type: regex + pattern: '#[^\n]*seconds per day' + match: not_contains + target: { source: file, path: class-b.sh } + - name: display-name-comment-deleted + type: regex + pattern: '#[^\n]*display name' + match: not_contains + target: { source: file, path: class-b.sh } + - name: code-intact + type: regex + pattern: 'cache_ttl_seconds\(\) \{\n local days="\$1"\n' + target: { source: file, path: class-b.sh } + - name: test-net-ran + type: tool_used + tool: Bash + input_match: make test|class-b\.test + arm: both + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both diff --git a/plugins/code-tidying/evals/invented-class-b-aggressive/scaffold.sh b/plugins/code-tidying/evals/invented-class-b-aggressive/scaffold.sh new file mode 100755 index 000000000..91a2a96e7 --- /dev/null +++ b/plugins/code-tidying/evals/invented-class-b-aggressive/scaffold.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" class-b.sh class-b.test.sh Makefile diff --git a/plugins/code-tidying/evals/invented-class-b-strip/case.yaml b/plugins/code-tidying/evals/invented-class-b-strip/case.yaml new file mode 100644 index 000000000..5aff2b4be --- /dev/null +++ b/plugins/code-tidying/evals/invented-class-b-strip/case.yaml @@ -0,0 +1,38 @@ +schema_version: "1.1" +name: invented-class-b-strip +description: strip on an invented script with a Makefile test net deletes the magic-literal and vague-local comments without renaming anything +tags: [calibration, strip, invented] +expected_outcome: Both comments are deleted; the 86400 and n= lines are byte-identical; no SECONDS_PER_DAY name is introduced. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments strip class-b.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: seconds-comment-deleted + type: regex + pattern: '#[^\n]*seconds per day' + match: not_contains + target: { source: file, path: class-b.sh } + - name: display-name-comment-deleted + type: regex + pattern: '#[^\n]*display name' + match: not_contains + target: { source: file, path: class-b.sh } + - name: code-intact + type: regex + pattern: '^ local ttl=\$\(\(days \* 86400\)\)$[\s\S]*^ local n="\$1"\n echo "Hello, \$n"$' + flags: m + target: { source: file, path: class-b.sh } + - name: no-constant-introduced + type: regex + pattern: 'SECONDS_PER_DAY' + match: not_contains + target: { source: file, path: class-b.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both diff --git a/plugins/code-tidying/evals/invented-class-b-strip/scaffold.sh b/plugins/code-tidying/evals/invented-class-b-strip/scaffold.sh new file mode 100755 index 000000000..91a2a96e7 --- /dev/null +++ b/plugins/code-tidying/evals/invented-class-b-strip/scaffold.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" class-b.sh class-b.test.sh Makefile diff --git a/plugins/code-tidying/evals/invented-class-c-aggressive/case.yaml b/plugins/code-tidying/evals/invented-class-c-aggressive/case.yaml new file mode 100644 index 000000000..6243dee2f --- /dev/null +++ b/plugins/code-tidying/evals/invented-class-c-aggressive/case.yaml @@ -0,0 +1,62 @@ +schema_version: "1.1" +name: invented-class-c-aggressive +description: aggressive on an invented script keeps a load-bearing warning and deletes two rationale comments and a restating comment with the narrative staged for the next commit +tags: [calibration, aggressive, invented] +expected_outcome: The lock-deadlock warning survives in at most two lines; the retry rationale (absent from history), the timeout rationale (in the commit message), and the set-the-counter comment are deleted; both rationales are staged and the report names the next commit as their landing place; code is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments aggressive class-c.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: warning-kept + type: regex + pattern: '#[^\n]*deadlock' + flags: i + target: { source: file, path: class-c.sh } + - name: retry-rationale-deleted + type: regex + pattern: '#[^\n]*upstream API' + match: not_contains + target: { source: file, path: class-c.sh } + - name: timeout-rationale-deleted + type: regex + pattern: '#[^\n]*load balancer' + match: not_contains + target: { source: file, path: class-c.sh } + - name: class-a-deleted + type: regex + pattern: '#[^\n]*set the counter to zero' + match: not_contains + target: { source: file, path: class-c.sh } + - name: kept-comment-succinct + type: regex + pattern: '(^[ \t]*#(?!!| shellcheck )[^\n]*\n){3}' + flags: m + match: not_contains + target: { source: file, path: class-c.sh } + - name: code-intact + type: regex + pattern: '^prepare_lock\(\) \{\n : >"\$lock_file"\n\}[\s\S]*^ local retries=3\n for \(\(attempt = 1; attempt <= retries; attempt\+\+\)\); do\n if curl --silent --max-time "\$timeout" "\$api_url/status"; then\n return 0\n fi\n done\n return 1\n\}[\s\S]*^counter=0$[\s\S]*^timeout=5$[\s\S]*^prepare_lock\nstart_worker\n\nfetch_status \|\| counter=\$\(\(counter \+ 1\)\)\necho "failures: \$counter"$' + flags: m + target: { source: file, path: class-c.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both + - name: landing-place-named + type: regex + pattern: 'next commit' + flags: i + target: last_message + - name: no-intentional-removal-trailer + type: regex + pattern: 'Intentional-removal:' + match: not_contains + target: last_message + - name: narrative-staged + type: llm + criteria: PASS when the final report contains a proposed commit-message block that preserves both removed rationales, namely that fetch_status retries three times because the upstream API drops the first connection after idle, and that the 5 second timeout stays under the load balancer's 6 second idle cutoff. FAIL when either rationale appears nowhere in the report. diff --git a/plugins/code-tidying/evals/invented-class-c-aggressive/scaffold.sh b/plugins/code-tidying/evals/invented-class-c-aggressive/scaffold.sh new file mode 100755 index 000000000..483561a85 --- /dev/null +++ b/plugins/code-tidying/evals/invented-class-c-aggressive/scaffold.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" class-c.sh +git -c user.name=eval -c user.email=eval@example.invalid commit --amend -q -m "add fixtures" -m "Uses a 5 second timeout to stay under the load balancer's 6 second idle cutoff." diff --git a/plugins/code-tidying/evals/invented-class-c-strip/case.yaml b/plugins/code-tidying/evals/invented-class-c-strip/case.yaml new file mode 100644 index 000000000..64ffb901c --- /dev/null +++ b/plugins/code-tidying/evals/invented-class-c-strip/case.yaml @@ -0,0 +1,36 @@ +schema_version: "1.1" +name: invented-class-c-strip +description: strip on an invented script deletes the warning, both rationale comments, and the restating comment, and stages the narrative +tags: [calibration, strip, invented] +expected_outcome: Every comment but the shebang is deleted; code is unchanged; the report stages the lock-deadlock warning and both rationales. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments strip class-c.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: no-comments-left + type: regex + pattern: '^[ \t]*#(?!!)' + flags: m + match: not_contains + target: { source: file, path: class-c.sh } + - name: shebang-kept + type: regex + pattern: '^#!/usr/bin/env bash\n' + target: { source: file, path: class-c.sh } + - name: code-intact + type: regex + pattern: '^prepare_lock\(\) \{\n : >"\$lock_file"\n\}[\s\S]*^ local retries=3\n for \(\(attempt = 1; attempt <= retries; attempt\+\+\)\); do\n if curl --silent --max-time "\$timeout" "\$api_url/status"; then\n return 0\n fi\n done\n return 1\n\}[\s\S]*^counter=0$[\s\S]*^timeout=5$[\s\S]*^prepare_lock\nstart_worker\n\nfetch_status \|\| counter=\$\(\(counter \+ 1\)\)\necho "failures: \$counter"$' + flags: m + target: { source: file, path: class-c.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both + - name: narrative-staged + type: llm + criteria: PASS when the final report contains a proposed commit-message block that preserves the removed information, including the warning that prepare_lock must run before start_worker or the lock file deadlocks the worker. FAIL when that warning appears nowhere in the report. diff --git a/plugins/code-tidying/evals/invented-class-c-strip/scaffold.sh b/plugins/code-tidying/evals/invented-class-c-strip/scaffold.sh new file mode 100755 index 000000000..483561a85 --- /dev/null +++ b/plugins/code-tidying/evals/invented-class-c-strip/scaffold.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" class-c.sh +git -c user.name=eval -c user.email=eval@example.invalid commit --amend -q -m "add fixtures" -m "Uses a 5 second timeout to stay under the load balancer's 6 second idle cutoff." diff --git a/plugins/code-tidying/evals/invented-edge-aggressive/case.yaml b/plugins/code-tidying/evals/invented-edge-aggressive/case.yaml new file mode 100644 index 000000000..32f394a0a --- /dev/null +++ b/plugins/code-tidying/evals/invented-edge-aggressive/case.yaml @@ -0,0 +1,51 @@ +schema_version: "1.1" +name: invented-edge-aggressive +description: aggressive on an invented src tree deletes a comment above a marker-listed identifier without renaming it, proposes a private docstring, and keeps a comment paired with a regression test +tags: [calibration, aggressive, invented] +expected_outcome: The end-offset comment is deleted and gql_page_end is never renamed because scripts/restoration-markers.txt lists it; the _parse docstring is unchanged and reported as a proposal; the empty-input comment paired with tests/edge-paired.test.sh stays; code is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments aggressive src + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: marker-comment-deleted + type: regex + pattern: '#[^\n]*end offset of the current page' + match: not_contains + target: { source: file, path: src/edge-marker.sh } + - name: marker-identifier-unchanged + type: regex + pattern: '^ gql_page_end=\$\(\(offset \+ page_size\)\)$' + flags: m + target: { source: file, path: src/edge-marker.sh } + - name: docstring-unchanged + type: regex + pattern: 'def _parse\(line\):\n """Split a header line into key and value\."""\n key, _, value = line\.partition\(":"\)' + target: { source: file, path: src/edge_docstring.py } + - name: docstring-reported + type: regex + pattern: 'docstring' + flags: i + target: last_message + - name: paired-comment-kept + type: regex + pattern: '#[^\n]*Empty input' + target: { source: file, path: src/edge-paired.sh } + - name: kept-comment-succinct + type: regex + pattern: '(^[ \t]*#(?!!| shellcheck )[^\n]*\n){3}' + flags: m + match: not_contains + target: { source: file, path: src/edge-paired.sh } + - name: code-intact + type: regex + pattern: '\[\[ -n "\$\{1:-\}" \]\] \|\| return 1\n return 0\n\}' + target: { source: file, path: src/edge-paired.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both diff --git a/plugins/code-tidying/evals/invented-edge-aggressive/scaffold.sh b/plugins/code-tidying/evals/invented-edge-aggressive/scaffold.sh new file mode 100755 index 000000000..71610d9c2 --- /dev/null +++ b/plugins/code-tidying/evals/invented-edge-aggressive/scaffold.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" scripts/restoration-markers.txt scripts/check-markers.sh src/edge-marker.sh src/edge_docstring.py src/edge-paired.sh tests/edge-paired.test.sh diff --git a/plugins/code-tidying/evals/invented-edge-strip/case.yaml b/plugins/code-tidying/evals/invented-edge-strip/case.yaml new file mode 100644 index 000000000..e85bd43ba --- /dev/null +++ b/plugins/code-tidying/evals/invented-edge-strip/case.yaml @@ -0,0 +1,51 @@ +schema_version: "1.1" +name: invented-edge-strip +description: strip on an invented src tree deletes a comment above a marker-listed identifier without renaming it, proposes a private docstring, and keeps a comment paired with a regression test +tags: [calibration, strip, invented] +expected_outcome: The end-offset comment is deleted and gql_page_end is never renamed because scripts/restoration-markers.txt lists it; the _parse docstring is unchanged and reported as a proposal; the empty-input comment paired with tests/edge-paired.test.sh stays; code is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments strip src + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: marker-comment-deleted + type: regex + pattern: '#[^\n]*end offset of the current page' + match: not_contains + target: { source: file, path: src/edge-marker.sh } + - name: marker-identifier-unchanged + type: regex + pattern: '^ gql_page_end=\$\(\(offset \+ page_size\)\)$' + flags: m + target: { source: file, path: src/edge-marker.sh } + - name: docstring-unchanged + type: regex + pattern: 'def _parse\(line\):\n """Split a header line into key and value\."""\n key, _, value = line\.partition\(":"\)' + target: { source: file, path: src/edge_docstring.py } + - name: docstring-reported + type: regex + pattern: 'docstring' + flags: i + target: last_message + - name: paired-comment-kept + type: regex + pattern: '#[^\n]*Empty input' + target: { source: file, path: src/edge-paired.sh } + - name: kept-comment-succinct + type: regex + pattern: '(^[ \t]*#(?!!| shellcheck )[^\n]*\n){3}' + flags: m + match: not_contains + target: { source: file, path: src/edge-paired.sh } + - name: code-intact + type: regex + pattern: '\[\[ -n "\$\{1:-\}" \]\] \|\| return 1\n return 0\n\}' + target: { source: file, path: src/edge-paired.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both diff --git a/plugins/code-tidying/evals/invented-edge-strip/scaffold.sh b/plugins/code-tidying/evals/invented-edge-strip/scaffold.sh new file mode 100755 index 000000000..71610d9c2 --- /dev/null +++ b/plugins/code-tidying/evals/invented-edge-strip/scaffold.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" scripts/restoration-markers.txt scripts/check-markers.sh src/edge-marker.sh src/edge_docstring.py src/edge-paired.sh tests/edge-paired.test.sh diff --git a/plugins/code-tidying/evals/invented-exempt-aggressive/case.yaml b/plugins/code-tidying/evals/invented-exempt-aggressive/case.yaml new file mode 100644 index 000000000..670ed3ad2 --- /dev/null +++ b/plugins/code-tidying/evals/invented-exempt-aggressive/case.yaml @@ -0,0 +1,49 @@ +schema_version: "1.1" +name: invented-exempt-aggressive +description: aggressive on an invented Python module keeps every exempt surface and deletes the one ordinary rationale comment +tags: [calibration, aggressive, invented] +expected_outcome: The shebang, the two-line license header, the noqa justification, the TODO(#12) marker, and the dissolve-comments-ignore line with the comment it covers all stay; the arbitrary-order rationale is deleted; code is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments aggressive exempt.py + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: shebang-kept + type: regex + pattern: '^#!/usr/bin/env python3\n' + target: { source: file, path: exempt.py } + - name: license-kept + type: regex + pattern: '^# Copyright \(c\) 2026 Example Corp\.\n# Licensed under the MIT License\. See LICENSE in the project root\.$' + flags: m + target: { source: file, path: exempt.py } + - name: noqa-kept + type: regex + pattern: '# noqa: E501 long canonical URL' + target: { source: file, path: exempt.py } + - name: todo-kept + type: regex + pattern: '# TODO\(#12\): replace polling with a webhook' + target: { source: file, path: exempt.py } + - name: ignore-marker-kept + type: regex + pattern: '# dissolve-comments-ignore\n return min\(2\*\*attempt, POLL_SECONDS\) # capped so a retry never outlasts one poll interval' + target: { source: file, path: exempt.py } + - name: rationale-deleted + type: regex + pattern: '#[^\n]*arbitrary order' + match: not_contains + target: { source: file, path: exempt.py } + - name: code-intact + type: regex + pattern: '^ pages = client\.get\(URL\)$[\s\S]*^ pages\.sort\(key=lambda page: page\["updated_at"\]\)\n return pages$' + flags: m + target: { source: file, path: exempt.py } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both diff --git a/plugins/code-tidying/evals/invented-exempt-aggressive/scaffold.sh b/plugins/code-tidying/evals/invented-exempt-aggressive/scaffold.sh new file mode 100755 index 000000000..af2235976 --- /dev/null +++ b/plugins/code-tidying/evals/invented-exempt-aggressive/scaffold.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" exempt.py diff --git a/plugins/code-tidying/evals/invented-exempt-strip/case.yaml b/plugins/code-tidying/evals/invented-exempt-strip/case.yaml new file mode 100644 index 000000000..e9bd050ce --- /dev/null +++ b/plugins/code-tidying/evals/invented-exempt-strip/case.yaml @@ -0,0 +1,49 @@ +schema_version: "1.1" +name: invented-exempt-strip +description: strip on an invented Python module keeps every exempt surface and deletes the one ordinary rationale comment +tags: [calibration, strip, invented] +expected_outcome: The shebang, the two-line license header, the noqa justification, the TODO(#12) marker, and the dissolve-comments-ignore line with the comment it covers all stay; the arbitrary-order rationale is deleted; code is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments strip exempt.py + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: shebang-kept + type: regex + pattern: '^#!/usr/bin/env python3\n' + target: { source: file, path: exempt.py } + - name: license-kept + type: regex + pattern: '^# Copyright \(c\) 2026 Example Corp\.\n# Licensed under the MIT License\. See LICENSE in the project root\.$' + flags: m + target: { source: file, path: exempt.py } + - name: noqa-kept + type: regex + pattern: '# noqa: E501 long canonical URL' + target: { source: file, path: exempt.py } + - name: todo-kept + type: regex + pattern: '# TODO\(#12\): replace polling with a webhook' + target: { source: file, path: exempt.py } + - name: ignore-marker-kept + type: regex + pattern: '# dissolve-comments-ignore\n return min\(2\*\*attempt, POLL_SECONDS\) # capped so a retry never outlasts one poll interval' + target: { source: file, path: exempt.py } + - name: rationale-deleted + type: regex + pattern: '#[^\n]*arbitrary order' + match: not_contains + target: { source: file, path: exempt.py } + - name: code-intact + type: regex + pattern: '^ pages = client\.get\(URL\)$[\s\S]*^ pages\.sort\(key=lambda page: page\["updated_at"\]\)\n return pages$' + flags: m + target: { source: file, path: exempt.py } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both diff --git a/plugins/code-tidying/evals/invented-exempt-strip/scaffold.sh b/plugins/code-tidying/evals/invented-exempt-strip/scaffold.sh new file mode 100755 index 000000000..af2235976 --- /dev/null +++ b/plugins/code-tidying/evals/invented-exempt-strip/scaffold.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" exempt.py diff --git a/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml b/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml new file mode 100644 index 000000000..be49b5d5c --- /dev/null +++ b/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml @@ -0,0 +1,84 @@ +schema_version: "1.1" +name: real-hook-utils-header-aggressive +description: aggressive on the lib/hook-utils.sh header keeps the directive, a one-line SSOT warning, and a one-line _to calling convention, and deletes the rest +tags: [calibration, aggressive, real] +expected_outcome: The shellcheck directive stays on line 1; the SSOT warning and the _to calling convention each survive as one terse line; the library description, fork-cost reasoning, five-printer list, double-sourcing comment, kill-switch comment, no-fleet-switch rationale, and hook::is_enabled comment are deleted with their narrative staged; the guard and both function bodies are unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments aggressive hook-utils-header.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: directive-kept + type: regex + pattern: '^# shellcheck shell=bash\n' + target: { source: file, path: hook-utils-header.sh } + - name: ssot-warning-kept + type: regex + pattern: '#[^\n]*(SSOT|single source)' + flags: i + target: { source: file, path: hook-utils-header.sh } + - name: ssot-sync-kept + type: regex + pattern: '#[^\n]*sync-hook-utils' + target: { source: file, path: hook-utils-header.sh } + - name: calling-convention-kept + type: regex + pattern: '#[^\n]*_to' + target: { source: file, path: hook-utils-header.sh } + - name: description-deleted + type: regex + pattern: '#[^\n]*Shared hook utility library' + match: not_contains + target: { source: file, path: hook-utils-header.sh } + - name: fork-cost-deleted + type: regex + pattern: '#[^\n]*(CreateProcess|copy-on-write|forks a subshell)' + match: not_contains + target: { source: file, path: hook-utils-header.sh } + - name: print-helpers-list-deleted + type: regex + pattern: '#[^\n]*Five helpers still print' + match: not_contains + target: { source: file, path: hook-utils-header.sh } + - name: double-sourcing-deleted + type: regex + pattern: '#[^\n]*double-sourcing' + flags: i + match: not_contains + target: { source: file, path: hook-utils-header.sh } + - name: kill-switch-comment-deleted + type: regex + pattern: '#[^\n]*Per-hook kill switch' + match: not_contains + target: { source: file, path: hook-utils-header.sh } + - name: fleet-switch-rationale-deleted + type: regex + pattern: '#[^\n]*(safe-mode|disableAllHooks|fleet)' + match: not_contains + target: { source: file, path: hook-utils-header.sh } + - name: is-enabled-comment-deleted + type: regex + pattern: '#[^\n]*(TRANSPARENT WRAPPER|statusline tee|PREDICATE)' + match: not_contains + target: { source: file, path: hook-utils-header.sh } + - name: kept-comment-succinct + type: regex + pattern: '(^[ \t]*#(?!!| shellcheck )[^\n]*\n){3}' + flags: m + match: not_contains + target: { source: file, path: hook-utils-header.sh } + - name: code-intact + type: regex + pattern: '\[\[ -n "\$\{_HOOK_UTILS_LOADED:-\}" \]\] && return 0\nreadonly _HOOK_UTILS_LOADED=1[\s\S]*hook::is_enabled\(\) \{\n local var_name="CLAUDE_PLUGIN_OPTION_\$\{1\}_ENABLED"\n \[\[ "\$\{!var_name:-true\}" == "true" \]\]\n\}\n\nhook::check_enabled\(\) \{\n hook::is_enabled "\$1" \|\| exit 0\n\}' + target: { source: file, path: hook-utils-header.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both + - name: narrative-staged + type: llm + criteria: PASS when the final report contains a proposed commit-message block that preserves the removed rationale, including that a command substitution forks a subshell (a Win32 CreateProcess on Git Bash) so a capture around a _to helper is pure loss, and that the statusline tee needs hook::is_enabled because it is a transparent wrapper that must still pass output through when disabled. FAIL when either reason appears nowhere in the report. diff --git a/plugins/code-tidying/evals/real-hook-utils-header-aggressive/scaffold.sh b/plugins/code-tidying/evals/real-hook-utils-header-aggressive/scaffold.sh new file mode 100755 index 000000000..852887121 --- /dev/null +++ b/plugins/code-tidying/evals/real-hook-utils-header-aggressive/scaffold.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" hook-utils-header.sh diff --git a/plugins/code-tidying/evals/real-hook-utils-header-strip/case.yaml b/plugins/code-tidying/evals/real-hook-utils-header-strip/case.yaml new file mode 100644 index 000000000..4ce02372a --- /dev/null +++ b/plugins/code-tidying/evals/real-hook-utils-header-strip/case.yaml @@ -0,0 +1,35 @@ +schema_version: "1.1" +name: real-hook-utils-header-strip +description: strip on the lib/hook-utils.sh header deletes every comment but the shellcheck directive, rewrites no code, and stages the narrative +tags: [calibration, strip, real] +expected_outcome: Only the shellcheck shell=bash directive remains as a comment; the guard and both function bodies are byte-identical; the removed narrative, including the SSOT warning, is staged in the report. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments strip hook-utils-header.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: directive-kept + type: regex + pattern: '^# shellcheck shell=bash\n' + target: { source: file, path: hook-utils-header.sh } + - name: only-directive-left + type: regex + pattern: '^\s*#(?! shellcheck shell=bash)' + flags: m + match: not_contains + target: { source: file, path: hook-utils-header.sh } + - name: code-intact + type: regex + pattern: '\[\[ -n "\$\{_HOOK_UTILS_LOADED:-\}" \]\] && return 0\nreadonly _HOOK_UTILS_LOADED=1[\s\S]*hook::is_enabled\(\) \{\n local var_name="CLAUDE_PLUGIN_OPTION_\$\{1\}_ENABLED"\n \[\[ "\$\{!var_name:-true\}" == "true" \]\]\n\}\n\nhook::check_enabled\(\) \{\n hook::is_enabled "\$1" \|\| exit 0\n\}' + target: { source: file, path: hook-utils-header.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both + - name: narrative-staged + type: llm + criteria: PASS when the final report contains a proposed commit-message block that preserves the removed information, including the SSOT warning (lib/hook-utils.sh is the source, the plugin copies are never edited, scripts/sync-hook-utils.sh propagates it, and CI rejects drifted copies). FAIL when that warning appears nowhere in the report. diff --git a/plugins/code-tidying/evals/real-hook-utils-header-strip/scaffold.sh b/plugins/code-tidying/evals/real-hook-utils-header-strip/scaffold.sh new file mode 100755 index 000000000..852887121 --- /dev/null +++ b/plugins/code-tidying/evals/real-hook-utils-header-strip/scaffold.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" hook-utils-header.sh diff --git a/plugins/code-tidying/evals/real-silent-revert-design-aggressive/case.yaml b/plugins/code-tidying/evals/real-silent-revert-design-aggressive/case.yaml new file mode 100644 index 000000000..82d088257 --- /dev/null +++ b/plugins/code-tidying/evals/real-silent-revert-design-aggressive/case.yaml @@ -0,0 +1,42 @@ +schema_version: "1.1" +name: real-silent-revert-design-aggressive +description: aggressive on the check-silent-revert.sh design block deletes the rejected-alternatives history and keeps only a terse threshold warning +tags: [calibration, aggressive, real] +expected_outcome: The heading, the three rejected designs, and the measured result are deleted with their narrative staged; the warning that no volume threshold separates incidents from routine work survives in at most two comment lines; die() is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments aggressive silent-revert-design.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: design-history-deleted + type: regex + pattern: '#[^\n]*(Curated marker|Merge-base staleness|PR-creation-time|Three designs|blame the lines|NOT THE ALTERNATIVES)' + flags: i + match: not_contains + target: { source: file, path: silent-revert-design.sh } + - name: threshold-warning-kept + type: regex + pattern: '#[^\n]*(threshold|number)' + flags: i + target: { source: file, path: silent-revert-design.sh } + - name: kept-comment-succinct + type: regex + pattern: '(^#[^\n]*\n){3}' + flags: m + match: not_contains + target: { source: file, path: silent-revert-design.sh } + - name: code-intact + type: regex + pattern: 'die\(\) \{\n echo "check-silent-revert: \$\*" >&2\n exit 2\n\}' + target: { source: file, path: silent-revert-design.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both + - name: narrative-staged + type: llm + criteria: PASS when the final report contains a proposed commit-message block that preserves the removed design history, naming the rejected alternatives (curated marker strings, merge-base staleness, PR-creation-time overlap) and that blame of deleted lines was chosen. FAIL when that history appears nowhere in the report. diff --git a/plugins/code-tidying/evals/real-silent-revert-design-aggressive/scaffold.sh b/plugins/code-tidying/evals/real-silent-revert-design-aggressive/scaffold.sh new file mode 100755 index 000000000..49b16c05e --- /dev/null +++ b/plugins/code-tidying/evals/real-silent-revert-design-aggressive/scaffold.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" silent-revert-design.sh diff --git a/plugins/code-tidying/evals/real-silent-revert-design-strip/case.yaml b/plugins/code-tidying/evals/real-silent-revert-design-strip/case.yaml new file mode 100644 index 000000000..9977c0c21 --- /dev/null +++ b/plugins/code-tidying/evals/real-silent-revert-design-strip/case.yaml @@ -0,0 +1,31 @@ +schema_version: "1.1" +name: real-silent-revert-design-strip +description: strip on the check-silent-revert.sh design block deletes every comment, rewrites no code, and stages the narrative +tags: [calibration, strip, real] +expected_outcome: All 34 comment lines are deleted; die() is unchanged; the removed design history and threshold warning are staged in the report. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments strip silent-revert-design.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: no-comments-left + type: regex + pattern: '^\s*#' + flags: m + match: not_contains + target: { source: file, path: silent-revert-design.sh } + - name: code-intact + type: regex + pattern: 'die\(\) \{\n echo "check-silent-revert: \$\*" >&2\n exit 2\n\}' + target: { source: file, path: silent-revert-design.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both + - name: narrative-staged + type: llm + criteria: PASS when the final report contains a proposed commit-message block that preserves the removed design history (the rejected alternatives and the chosen blame-of-deleted-lines design) and the warning that no volume threshold separates incidents from routine deletions. FAIL when either appears nowhere in the report. diff --git a/plugins/code-tidying/evals/real-silent-revert-design-strip/scaffold.sh b/plugins/code-tidying/evals/real-silent-revert-design-strip/scaffold.sh new file mode 100755 index 000000000..49b16c05e --- /dev/null +++ b/plugins/code-tidying/evals/real-silent-revert-design-strip/scaffold.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" silent-revert-design.sh diff --git a/plugins/code-tidying/evals/real-statusline-stamp-aggressive/case.yaml b/plugins/code-tidying/evals/real-statusline-stamp-aggressive/case.yaml new file mode 100644 index 000000000..5c5d2e49c --- /dev/null +++ b/plugins/code-tidying/evals/real-statusline-stamp-aggressive/case.yaml @@ -0,0 +1,51 @@ +schema_version: "1.1" +name: real-statusline-stamp-aggressive +description: aggressive on the statusline-tee.sh stamp reader keeps only a terse security warning and leaves the code intact +tags: [calibration, aggressive, real] +expected_outcome: The contract, single-reader rationale, and render-path comments are deleted with their narrative staged; the arithmetic-injection warning survives in at most two comment lines; the function body is unchanged. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments aggressive statusline-stamp.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: contract-deleted + type: regex + pattern: '#[^\n]*epoch-second stamp' + match: not_contains + target: { source: file, path: statusline-stamp.sh } + - name: injection-warning-kept + type: regex + pattern: '#[^\n]*arithmetic' + flags: i + target: { source: file, path: statusline-stamp.sh } + - name: single-reader-rationale-deleted + type: regex + pattern: '#[^\n]*(spelled identically|_rlg_bash_at_least)' + match: not_contains + target: { source: file, path: statusline-stamp.sh } + - name: render-path-deleted + type: regex + pattern: '#[^\n]*render path' + match: not_contains + target: { source: file, path: statusline-stamp.sh } + - name: kept-comment-succinct + type: regex + pattern: '(^#[^\n]*\n){3}' + flags: m + match: not_contains + target: { source: file, path: statusline-stamp.sh } + - name: code-intact + type: regex + pattern: '_rlg_read_stamp\(\) \{\n local _var="\$1" _val=0\n if \[\[ -f "\$2" \]\]; then\n IFS= read -r _val <"\$2" \|\| _val=0\n \[\[ "\$_val" =~ \^\[0-9\]\+\$ \]\] \|\| _val=0\n fi\n printf -v "\$_var" ''%s'' "\$_val"\n return 0\n\}' + target: { source: file, path: statusline-stamp.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both + - name: narrative-staged + type: llm + criteria: PASS when the final report contains a proposed commit-message block that preserves the removed rationale, namely that one reader serves five call sites and that the reader uses builtins only because it runs on every status-line render. FAIL when the removed rationale appears nowhere in the report. diff --git a/plugins/code-tidying/evals/real-statusline-stamp-aggressive/scaffold.sh b/plugins/code-tidying/evals/real-statusline-stamp-aggressive/scaffold.sh new file mode 100755 index 000000000..46e3ddee8 --- /dev/null +++ b/plugins/code-tidying/evals/real-statusline-stamp-aggressive/scaffold.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" statusline-stamp.sh diff --git a/plugins/code-tidying/evals/real-statusline-stamp-strip/case.yaml b/plugins/code-tidying/evals/real-statusline-stamp-strip/case.yaml new file mode 100644 index 000000000..ceb45c514 --- /dev/null +++ b/plugins/code-tidying/evals/real-statusline-stamp-strip/case.yaml @@ -0,0 +1,31 @@ +schema_version: "1.1" +name: real-statusline-stamp-strip +description: strip on the statusline-tee.sh stamp reader deletes every comment, rewrites no code, and stages the narrative +tags: [calibration, strip, real] +expected_outcome: All eight comment lines are deleted, including the arithmetic-injection warning; the function body is byte-identical; the removed narrative is staged in the report. +context: + scaffold_script: scaffold.sh +execution: + prompt: /code-tidying:dissolve-comments strip statusline-stamp.sh + allowed_tools: [Read, Glob, Grep, Skill, Bash, Edit, Write] + max_turns: 80 + timeout_seconds: 1200 +graders: + - name: no-comments-left + type: regex + pattern: '^\s*#' + flags: m + match: not_contains + target: { source: file, path: statusline-stamp.sh } + - name: code-intact + type: regex + pattern: '_rlg_read_stamp\(\) \{\n local _var="\$1" _val=0\n if \[\[ -f "\$2" \]\]; then\n IFS= read -r _val <"\$2" \|\| _val=0\n \[\[ "\$_val" =~ \^\[0-9\]\+\$ \]\] \|\| _val=0\n fi\n printf -v "\$_var" ''%s'' "\$_val"\n return 0\n\}' + target: { source: file, path: statusline-stamp.sh } + - name: proof-ran + type: tool_used + tool: Bash + input_match: change-shape + arm: both + - name: narrative-staged + type: llm + criteria: PASS when the final report contains a proposed commit-message block that preserves the removed information, including the arithmetic-injection warning (bash evaluates arithmetic operand text, so a stamp like a[$(cmd)] would run cmd). FAIL when that warning appears nowhere in the report. diff --git a/plugins/code-tidying/evals/real-statusline-stamp-strip/scaffold.sh b/plugins/code-tidying/evals/real-statusline-stamp-strip/scaffold.sh new file mode 100755 index 000000000..46e3ddee8 --- /dev/null +++ b/plugins/code-tidying/evals/real-statusline-stamp-strip/scaffold.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec bash "$(dirname "${BASH_SOURCE[0]}")/../fixtures/seed.sh" statusline-stamp.sh From 13e66a353cbe9ae2f8a6728d662d8ec3496e5bbb Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 14 Sep 2026 23:54:10 -0400 Subject: [PATCH 04/17] docs(code-tidying): record the posture-contract sweep for the aggressive dial Phase 3's first work item is the consumer sweep; its hits and their dispositions are recorded before any wording changes. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KcBf2VNNsVTuiqZ1N5RuCu --- docs/topics/dissolve-comments-aggressive-dial/PLAN.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md index 9f5f0b708..a0777079b 100644 --- a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md +++ b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md @@ -423,6 +423,15 @@ comment-hygiene scan do not lint deliberately commented fixtures. (`strip-keeps-exempt-surfaces-and-rewrites-nothing`), 18 (`standing-aggressive-posture-yields-to-safe`), each `narration: true` with `expectations`. +**Phase 3 record:** + +- Item 1 sweep (run before the red run; read-only). Hits to edit: `plugin.json:30` + (`comment_posture`), `README.md:129, 136, 160`, `SKILL.md:39, 88, 107, 152`, + `reference/triage.md:95`, `reference/scope.md:30`, `reference/dissolving-moves.md:57, 61`. Hits + kept as they are: `CHANGELOG.md` history entries; `evals/evals.json:151, 163` (posture narration + cases, still valid); every hit outside `plugins/code-tidying/skills/dissolve-comments`, + `README.md`, and `plugin.json` uses "narrows" or "widened" in an unrelated sense. + **Sanity Check:** - `grep -rn "only descends" plugins/code-tidying` prints nothing, and every hit recorded in item 1 From a25eb39be95428f7e40807ad1b4474352aabc1a8 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 00:13:08 -0400 Subject: [PATCH 05/17] feat(code-tidying): add the aggressive and strip dials to dissolve-comments aggressive keeps only the exempt surfaces, paired comment-plus-test records, and terse warnings of consequence; every other comment is staged and deleted. strip deletes every comment but the exempt surfaces and rewrites no code. --notes writes the staged block to an untracked file, refusing a tracked path. No gate moves: deletions still carry COMMENT-ONLY, function-local renames RENAME-ONLY, tier-2 and tier-3 moves a discovered test net, and an UNPROVABLE file yields proposals only. The retired "posture ladder only descends" wording is replaced by "no knob loosens a gate", and the two tier tables are reconciled to one 16-move set. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KcBf2VNNsVTuiqZ1N5RuCu --- docs/catalog.md | 2 +- .../dissolve-comments-aggressive-dial/PLAN.md | 28 +++- .../code-tidying/.claude-plugin/plugin.json | 6 +- plugins/code-tidying/CHANGELOG.md | 31 +++++ plugins/code-tidying/README.md | 17 ++- .../skills/dissolve-comments/SKILL.md | 123 +++++++++++++----- .../skills/dissolve-comments/evals/evals.json | 36 +++++ .../reference/dissolving-moves.md | 6 +- .../dissolve-comments/reference/safety.md | 28 +++- .../dissolve-comments/reference/scope.md | 3 +- .../dissolve-comments/reference/triage.md | 11 +- 11 files changed, 232 insertions(+), 59 deletions(-) diff --git a/docs/catalog.md b/docs/catalog.md index 7245634f6..61d6c353b 100644 --- a/docs/catalog.md +++ b/docs/catalog.md @@ -71,7 +71,7 @@ plugin manifests and kept in sync by CI. Never hand-edit it; the category vocabu - [`bugs`](../plugins/bugs): Produces from an informal defect description a structured five-field bug report: title, steps to reproduce, expected vs actual, severity with justification, and suggested fix location. Read-only by default: it emits the report and never edits code, opens a PR, or files an issue on its own. - [`debugging`](../plugins/debugging): Debug observed failures via a disciplined six-phase loop: build a fast deterministic reproduction signal, reproduce, rank falsifiable hypotheses, instrument, fix with a regression test, then clean up and post-mortem. - [`docs-hygiene`](../plugins/docs-hygiene): Documentation-hygiene toolkit: compress (flavor-trim markdown with a semantic-diff safety net), audit-noise (classify markdown noise), extract-ssot (deduplicate repeated content into a single source of truth), audit-encapsulation (detect citations into skill-private surfaces), rename-references (sweep stale references after renames), audit-derivability (classify whether a whole document earns its existence: could a fresh agent re-derive it from the code?), audit-progressive-disclosure (grade instruction files against a load-tier model for split opportunities and hub/spoke disclosure defects), write-for-agents (authoring-time doctrine that fires while agent-consumed markdown is being written), write-for-humans (the same moment for the other reader, covering end-user READMEs, RFCs, release notes and guides, and resolving the consuming project's own style guide first), and a file-name set that plans, applies, and enforces a casing rule across a doc tree: setup (the one configuration surface), audit-file-names (read-only inventory plus the reference sweep), realign-file-names (the executor, one human acceptance per file), and generate-file-name-gate (emits the standalone check that keeps the tree from drifting back). -- [`code-tidying`](../plugins/code-tidying): Code tidying and comment hygiene: /code-tidying:tidy proactively hunts a rotated, glob-scoped lane for Beck-style tidyings under a research-backed scope budget and ships one tight PR; /code-tidying:batch-simplify sweeps a time window, a branch, or an entire repository through grouped, dependency-ordered simplification waves with a fix-first deferral contract that resolves deferrals in the same run instead of filing issues; /code-tidying:dissolve-comments enforces self-describing expressive code over a diff or target, widening to the branch diff and then the whole repository when the tree is clean: it deletes zero-information comments, dissolves code-expressible ones into names and structure behind a tests gate (safe mode restricts applied edits to removals), and keeps only terse load-bearing comments code cannot express; /code-tidying:audit-comment-residue is a read-only classifier that flags history, plan, conversational, and ticket/PR residue in code comments for author-applied deletion; /code-tidying:audit-dead-code is a read-only whole-repo dead-code hunter running four labelled lanes of unequal confidence (knip for TS/JS, vulture for Python, gopls for Go, and a portable grep lane for shell and other symbol languages), adjudicating every candidate against dynamic-usage evidence into a dead, uncertain, or alive verdict. Project-specific tidy lanes are scaffolded into a tracked .claude/tidy-lanes/ config folder by a re-runnable setup skill. +- [`code-tidying`](../plugins/code-tidying): Code tidying and comment hygiene: /code-tidying:tidy proactively hunts a rotated, glob-scoped lane for Beck-style tidyings under a research-backed scope budget and ships one tight PR; /code-tidying:batch-simplify sweeps a time window, a branch, or an entire repository through grouped, dependency-ordered simplification waves with a fix-first deferral contract that resolves deferrals in the same run instead of filing issues; /code-tidying:dissolve-comments enforces self-describing expressive code over a diff or target, widening to the branch diff and then the whole repository when the tree is clean: it deletes zero-information comments, dissolves code-expressible ones into names and structure behind a tests gate (safe mode restricts applied edits to removals, the aggressive dial keeps only exempt surfaces and terse warnings, and strip deletes every comment but the exempt surfaces without rewriting code), and keeps only terse load-bearing comments code cannot express; /code-tidying:audit-comment-residue is a read-only classifier that flags history, plan, conversational, and ticket/PR residue in code comments for author-applied deletion; /code-tidying:audit-dead-code is a read-only whole-repo dead-code hunter running four labelled lanes of unequal confidence (knip for TS/JS, vulture for Python, gopls for Go, and a portable grep lane for shell and other symbol languages), adjudicating every candidate against dynamic-usage evidence into a dead, uncertain, or alive verdict. Project-specific tidy lanes are scaffolded into a tracked .claude/tidy-lanes/ config folder by a re-runnable setup skill. - [`coupling`](../plugins/coupling): Iteratively reduces coupling in any repository at any altitude, whether documents, code modules, applications, or repositories: scans for change-transmitting dependencies typed against a coupling model (strength ladder, connascence, volatility weighting), verifies every finding, applies a budgeted batch of safe behavior-preserving reductions, and keeps a durable ledger so structural candidates route to design lanes and repeated runs continue where the last stopped. - [`repo-hygiene`](../plugins/repo-hygiene): Repo hygiene action-router: /repo-hygiene:clean sweeps reclaimable caches, build artifacts, and stale git metadata, and can realign the working tree to a fresh-pull state, dry-run-first, with destructive tiers gated behind explicit confirmation and a session-scoped destructive-command guard. Ecosystem targets are detected at runtime; secrets, runtime dependencies, and skill data are preserved by default. - [`repo-fleet-hygiene`](../plugins/repo-fleet-hygiene): Cross-repository Git/GitHub fleet discovery, evidence rollup, and a gated apply verb that executes a prior fleet action plan behind one confirmation. Audit stays read-only and confidence-tiered; apply mutates only with --apply plus interactive confirmation or --yes. diff --git a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md index a0777079b..437533040 100644 --- a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md +++ b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md @@ -247,7 +247,7 @@ holds a `D:/` path. `$TEMP` is unset in WSL2; WSL2 commands use `${TMPDIR:-/tmp} - `git log --oneline main..HEAD -- docs/topics/dissolve-comments-aggressive-dial/PLAN.md` lists at least one commit. -### Phase 2: Expected outputs with the owner, then the red run [TODO] +### Phase 2: Expected outputs with the owner, then the red run [DONE] Main session, interactive, one fixture per round. Fixtures are committed with a `.txt` suffix (`app.sh.txt`, `mod.py.txt`) and renamed by the scaffold, so CI shellcheck, ruff, and the @@ -353,6 +353,13 @@ comment-hygiene scan do not lint deliberately commented fixtures. because a refusal before triage is a correct outcome. - Baseline (`comment-census.sh --json`, before value): `hook-utils-header.sh` 46 of 59 lines are comment; `silent-revert-design.sh` 34 of 39; `statusline-stamp.sh` 8 of 17; 88 of 115 in total. +- Red run skipped on the owner's decision (2026-09-15): nearly every case reaches its behavior by an + argument token the unchanged skill reads as a path, so the run would have re-measured a known + outcome at 12 to 30 USD. The expected-red lists above stand as the record of what the unchanged + skill does, and Phase 4's run is the first paid pass. +- The branch moved to the worktree `D:/worktrees/ccp-dissolve-dial` after another session switched + the main checkout back to `main`. WSL2 eval commands run from + `/mnt/d/worktrees/ccp-dissolve-dial`. **Sanity Check:** @@ -367,7 +374,7 @@ comment-hygiene scan do not lint deliberately commented fixtures. - `git ls-files plugins/code-tidying/evals | grep -E '\.(sh|py|bash)$'` lists only scaffold scripts. - Every case's expected-red list in this file matches its red-run record. -### Phase 3: Skill change [TODO] +### Phase 3: Skill change [DOING] 1. Consumer and claim sweep first: `grep -rn "comment_posture\|only descends\|narrows\|widened\|loosen nothing\|ceiling\|of 15" plugins/code-tidying docs scripts`; record every hit and its disposition @@ -431,10 +438,25 @@ comment-hygiene scan do not lint deliberately commented fixtures. kept as they are: `CHANGELOG.md` history entries; `evals/evals.json:151, 163` (posture narration cases, still valid); every hit outside `plugins/code-tidying/skills/dissolve-comments`, `README.md`, and `plugin.json` uses "narrows" or "widened" in an unrelated sense. +- Edits landed: `SKILL.md` (dial rows, `--notes`, survivor list, no-knob-loosens-a-gate wording, + doubt rule, paired-record and marker-row rules, docstring ceiling, per-mode branches in steps 1, + 5, 6 and 7, `## Next`), `reference/safety.md` (mode-ladder rows, knob paragraph, staging section, + tier table), `reference/dissolving-moves.md` (tier table, counts), `reference/triage.md`, + `reference/scope.md`, `plugin.json` (posture value, description, 0.20.0), `README.md` (regenerated + options block plus hand-written prose), `CHANGELOG.md`, `evals/evals.json` (entries 16 to 18), + `docs/catalog.md` (regenerated). +- The skill's `description` was over the 1024-codepoint Agent Skills maximum once the dials were + named (1199), and was on `main` at 978 with only 46 codepoints of headroom. It is now 1016 with + every base trigger phrase preserved; `check-skill` warns that the next added clause breaches the + limit. +- Gates: `check-skill` PASS (1 warning, the headroom note), `sync-plugin-options-docs.py --check` + exit 0, `check-evals-quality.sh` PASS, `allowed-tools-pairing.test.sh` exit 0, tier tables agree + tier by tier. **Sanity Check:** -- `grep -rn "only descends" plugins/code-tidying` prints nothing, and every hit recorded in item 1 +- `grep -rn "only descends" plugins/code-tidying --exclude=CHANGELOG.md` prints nothing (the + changelog quotes the retired wording on purpose), and every hit recorded in item 1 has a disposition line in this file. - `grep -n '^## ' plugins/code-tidying/skills/dissolve-comments/SKILL.md` shows `## Next` immediately before `## Gotchas`. diff --git a/plugins/code-tidying/.claude-plugin/plugin.json b/plugins/code-tidying/.claude-plugin/plugin.json index f88036312..7bc83b0f3 100644 --- a/plugins/code-tidying/.claude-plugin/plugin.json +++ b/plugins/code-tidying/.claude-plugin/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "code-tidying", - "version": "0.19.3", - "description": "Code tidying and comment hygiene: /code-tidying:tidy proactively hunts a rotated, glob-scoped lane for Beck-style tidyings under a research-backed scope budget and ships one tight PR; /code-tidying:batch-simplify sweeps a time window, a branch, or an entire repository through grouped, dependency-ordered simplification waves with a fix-first deferral contract that resolves deferrals in the same run instead of filing issues; /code-tidying:dissolve-comments enforces self-describing expressive code over a diff or target, widening to the branch diff and then the whole repository when the tree is clean: it deletes zero-information comments, dissolves code-expressible ones into names and structure behind a tests gate (safe mode restricts applied edits to removals), and keeps only terse load-bearing comments code cannot express; /code-tidying:audit-comment-residue is a read-only classifier that flags history, plan, conversational, and ticket/PR residue in code comments for author-applied deletion; /code-tidying:audit-dead-code is a read-only whole-repo dead-code hunter running four labelled lanes of unequal confidence (knip for TS/JS, vulture for Python, gopls for Go, and a portable grep lane for shell and other symbol languages), adjudicating every candidate against dynamic-usage evidence into a dead, uncertain, or alive verdict. Project-specific tidy lanes are scaffolded into a tracked .claude/tidy-lanes/ config folder by a re-runnable setup skill.", + "version": "0.20.0", + "description": "Code tidying and comment hygiene: /code-tidying:tidy proactively hunts a rotated, glob-scoped lane for Beck-style tidyings under a research-backed scope budget and ships one tight PR; /code-tidying:batch-simplify sweeps a time window, a branch, or an entire repository through grouped, dependency-ordered simplification waves with a fix-first deferral contract that resolves deferrals in the same run instead of filing issues; /code-tidying:dissolve-comments enforces self-describing expressive code over a diff or target, widening to the branch diff and then the whole repository when the tree is clean: it deletes zero-information comments, dissolves code-expressible ones into names and structure behind a tests gate (safe mode restricts applied edits to removals, the aggressive dial keeps only exempt surfaces and terse warnings, and strip deletes every comment but the exempt surfaces without rewriting code), and keeps only terse load-bearing comments code cannot express; /code-tidying:audit-comment-residue is a read-only classifier that flags history, plan, conversational, and ticket/PR residue in code comments for author-applied deletion; /code-tidying:audit-dead-code is a read-only whole-repo dead-code hunter running four labelled lanes of unequal confidence (knip for TS/JS, vulture for Python, gopls for Go, and a portable grep lane for shell and other symbol languages), adjudicating every candidate against dynamic-usage evidence into a dead, uncertain, or alive verdict. Project-specific tidy lanes are scaffolded into a tracked .claude/tidy-lanes/ config folder by a re-runnable setup skill.", "author": { "name": "Melodic Software", "email": "info@melodicsoftware.com" @@ -30,7 +30,7 @@ "comment_posture": { "type": "string", "title": "dissolve-comments posture", - "description": "How dissolve-comments treats a kept comment. strict (default): every kept comment is held to class_c_max_lines and rewritten terser when over it, with the removed narrative staged for the commit message; balanced: the same triage, but an over-budget comment is reported instead of rewritten; conservative: class-A deletions only, every class-B item and class-C rewrite is proposed. Doubt keeps the comment in every posture. Any other value is read as strict.", + "description": "How dissolve-comments treats a kept comment. strict (default): every kept comment is held to class_c_max_lines and rewritten terser when over it, with the removed narrative staged for the commit message; balanced: the same triage, but an over-budget comment is reported instead of rewritten; conservative: class-A deletions only, every class-B item and class-C rewrite is proposed; aggressive: only exempt surfaces, paired records, and terse warnings of consequence survive, and every other comment is staged and deleted. The per-run tokens safe, strip, and aggressive beat this value, safe first. No posture loosens a gate: every applied deletion still carries the token proof and every tier-2 or tier-3 move still needs a test net. Any other value is read as strict.", "default": "strict" }, "class_c_max_lines": { diff --git a/plugins/code-tidying/CHANGELOG.md b/plugins/code-tidying/CHANGELOG.md index 35d53a7b0..1d92f4a40 100644 --- a/plugins/code-tidying/CHANGELOG.md +++ b/plugins/code-tidying/CHANGELOG.md @@ -3,6 +3,37 @@ All notable changes to the `code-tidying` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.20.0] + +### Added + +- **`dissolve-comments` gains an aggressive dial.** `aggressive` (a per-run token and a + `comment_posture` value) keeps only the exempt surfaces, paired comment-plus-test records, and + terse warnings of consequence; every other comment is staged and deleted, rationale included. + `strip` (a per-run token) deletes every comment but the exempt surfaces and paired records and + rewrites no code. Precedence is `safe`, then `strip`, then `aggressive`, and a token beats the + standing posture. +- **`--notes `** appends the staged commit-message block to an untracked or out-of-repo file. + A tracked path is refused and the run continues with the report as the only vehicle. +- **A calibration eval suite** under `plugins/code-tidying/evals/`, run with `claude plugin eval`: + three frozen real sections, invented fixtures per triage class, exempt surfaces, marker rows, + Python docstrings, paired records, and the dial interactions. + +### Changed + +- **"The posture ladder only descends" is replaced by "no knob loosens a gate."** The dials widen + what a run removes; they change no proof. Deletions still carry COMMENT-ONLY, function-local + renames RENAME-ONLY, tier-2 and tier-3 moves a discovered test net, and an UNPROVABLE file still + yields proposals only. +- **Two rules now hold in every mode:** a comment paired with a regression test is never deleted + alone, and an identifier a repo-local marker row pins is never renamed. +- **The tier tables in `safety.md` and `dissolving-moves.md` agree.** The merged set is 16 moves: + tier 2 gains Replace Nested Conditional with Guard Clauses and Introduce Special Case, tier 3 + gains Inline Function in `safety.md` and Extract Class in `dissolving-moves.md`. The apply-capacity + counts read 2 of 16 and 0 of 16. +- **The `Intentional-removal:` trailer is conditional.** The staged block carries it only where the + target repository's own gates read that trailer. + ## [0.19.3] ### Changed diff --git a/plugins/code-tidying/README.md b/plugins/code-tidying/README.md index 5f5c8243a..aa504749b 100644 --- a/plugins/code-tidying/README.md +++ b/plugins/code-tidying/README.md @@ -17,7 +17,11 @@ Six skills, one capability: behind a token-level proof (`change-shape.py`, so they act on a repository with no test suite); additive refactors need a discovered test net; interface-creating ones are proposal-first. `safe` mode restricts applied - edits to removals. Ships a comment census with a token estimate and a + edits to removals; `aggressive` keeps only exempt surfaces, paired records, + and terse warnings of consequence, staging every other comment's narrative + before deleting it; `strip` deletes every comment but the exempt surfaces and + rewrites no code. `--notes ` writes the staged block to an untracked + file as well. No dial loosens a gate. Ships a comment census with a token estimate and a cross-language commented-out-code detector, and probes its reading layers (`scc`, `pygments`, `tree-sitter`, `ruff`, `ast-grep`) at run time, naming what each absent one costs. @@ -126,14 +130,15 @@ personal variation is limited to lane names the team does not track: an uncommit ## Configuration -Four `userConfig` options. Three tune `dissolve-comments` and loosen nothing; -the fourth is the personal-posture channel of the exclusion override above, and -loosening is its whole job: +Four `userConfig` options. Three tune `dissolve-comments`, and none of them +loosens a gate: they set what a run removes, never what it may apply without a +proof. The fourth is the personal-posture channel of the exclusion override +above, and loosening is its whole job: | Option | Default | Effect | |---|---|---| | `hard_exclusions` | `enforce` | `enforce` keeps every GLOBAL HARD **path** entry blocking; `advisory` reports each match and blocks nothing, so runs may reach lint config, agent config, CI workflows, and hook chains. Path entries only: the behavioral guards, the work-tracking entries, and the self-update protections hold at either value. | -| `comment_posture` | `strict` | `strict` rewrites an over-budget kept comment terser and stages the removed narrative; `balanced` reports it instead; `conservative` applies class-A deletions only and proposes everything else. Doubt keeps the comment in every posture. | +| `comment_posture` | `strict` | `strict` rewrites an over-budget kept comment terser and stages the removed narrative; `balanced` reports it instead; `conservative` applies class-A deletions only and proposes everything else; `aggressive` keeps only exempt surfaces, paired records, and terse warnings. The per-run tokens `safe`, `strip`, and `aggressive` beat this value, `safe` first. | | `class_c_max_lines` | `2` | Line budget for a kept (class-C) comment before it is rewritten. | | `apply_local_renames` | `true` | Apply a function-local rename that `change-shape.py` certifies as RENAME-ONLY even with no test net; `false` proposes it. | @@ -157,7 +162,7 @@ reads it from. | Option | Type | Default | Environment variable | Description | | --- | --- | --- | --- | --- | | `hard_exclusions` | string | `"enforce"` | `CLAUDE_PLUGIN_OPTION_HARD_EXCLUSIONS` | How tidy, dissolve-comments, and batch-simplify treat the GLOBAL HARD path list in skills/tidy/reference/exclusions.md. enforce (default): a path on that list is dropped before triage; advisory: the list is reported per path and never blocks, so a run may edit lint config, agent config, CI workflows, and hook chains. advisory is the standing form of the per-run override argument and is lifted for path entries only: the behavioral guards, the work-tracking entries, and the SELF-UPDATE EXTRA HARD list hold under every value. Any other value is read as enforce. | -| `comment_posture` | string | `"strict"` | `CLAUDE_PLUGIN_OPTION_COMMENT_POSTURE` | How dissolve-comments treats a kept comment. strict (default): every kept comment is held to class_c_max_lines and rewritten terser when over it, with the removed narrative staged for the commit message; balanced: the same triage, but an over-budget comment is reported instead of rewritten; conservative: class-A deletions only, every class-B item and class-C rewrite is proposed. Doubt keeps the comment in every posture. Any other value is read as strict. | +| `comment_posture` | string | `"strict"` | `CLAUDE_PLUGIN_OPTION_COMMENT_POSTURE` | How dissolve-comments treats a kept comment. strict (default): every kept comment is held to class_c_max_lines and rewritten terser when over it, with the removed narrative staged for the commit message; balanced: the same triage, but an over-budget comment is reported instead of rewritten; conservative: class-A deletions only, every class-B item and class-C rewrite is proposed; aggressive: only exempt surfaces, paired records, and terse warnings of consequence survive, and every other comment is staged and deleted. The per-run tokens safe, strip, and aggressive beat this value, safe first. No posture loosens a gate: every applied deletion still carries the token proof and every tier-2 or tier-3 move still needs a test net. Any other value is read as strict. | | `class_c_max_lines` | number
*min 1, max 40* | `2` | `CLAUDE_PLUGIN_OPTION_CLASS_C_MAX_LINES` | Lines a kept (class-C) comment may run before dissolve-comments rewrites it terser, staging any removed narrative for the commit message. A genuinely load-bearing multi-line contract may exceed it when the report says why. | | `apply_local_renames` | boolean | `true` | `CLAUDE_PLUGIN_OPTION_APPLY_LOCAL_RENAMES` | When true (default), a function-local Rename Variable whose edit change-shape.py certifies as RENAME-ONLY is applied and reported with its identifier mapping even when no test net is discovered. When false, such renames are proposed. | diff --git a/plugins/code-tidying/skills/dissolve-comments/SKILL.md b/plugins/code-tidying/skills/dissolve-comments/SKILL.md index 4c5413962..edac613d9 100644 --- a/plugins/code-tidying/skills/dissolve-comments/SKILL.md +++ b/plugins/code-tidying/skills/dissolve-comments/SKILL.md @@ -1,7 +1,7 @@ --- -description: "Enforce self-describing code over a diff, branch, or ranked repository: a three-way comment triage that deletes zero-information comments, dissolves code-expressible ones into names and structure by behavior-preserving refactoring, and keeps only terse, load-bearing comments code cannot express. Deletions and local renames apply behind a token-level proof, other refactors behind a test net, else proposed; 'safe' mode restricts applied edits to removals. Use when: 'dissolve comments', 'remove comments', 'strip agent comments', 'too many comments', 'make it self-documenting', 'make the code expressive', 'comments must earn their keep', after an agent wrote over-commented code. Skip when: read-only residue classification (audit-comment-residue), structural tidyings (tidy), simplification waves (batch-simplify), markdown noise (docs-hygiene audit-noise), adding why-comments (tidy #14). Never touches public-API doc comments, license headers, or machine-read directives." -argument-hint: "[safe] [override] [target]" -allowed-tools: ["Bash(${CLAUDE_SKILL_DIR}/scripts/scope-code-files.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/comment-tooling-probe.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/change-shape.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/comment-census.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/commented-out-code.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/rank-comment-targets.sh:*)", "Bash(git branch:*)", "Bash(git log:*)", "Bash(grep:*)", "Bash(echo:*)"] +description: "Enforce self-describing code over a diff, branch, or ranked repo: a three-way triage: delete zero-information comments, dissolve code-expressible ones into names and structure by behavior-preserving refactors, keep only terse load-bearing comments code cannot express. Deletions and local renames need a token proof, other refactors a test net, else proposed; 'safe' removals only, 'aggressive' deletes non-exempt comments with narrative staged, 'strip' deletes all but exempt surfaces, no rewrite. Use when: 'dissolve comments', 'remove comments', 'strip all comments', 'strip agent comments', 'too many comments', 'aggressive comment removal', 'make it self-documenting', 'make the code expressive', 'comments must earn their keep', after an agent wrote over-commented code. Skip when: residue classification (audit-comment-residue), tidyings (tidy), simplification (batch-simplify), markdown noise (audit-noise), why-comments (tidy #14). Never touches public-API docs, license headers, or machine-read directives." +argument-hint: "[safe] [aggressive|strip] [override] [--notes ] [target]" +allowed-tools: ["Bash(${CLAUDE_SKILL_DIR}/scripts/scope-code-files.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/comment-tooling-probe.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/change-shape.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/comment-census.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/commented-out-code.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/rank-comment-targets.sh:*)", "Bash(git branch:*)", "Bash(git log:*)", "Bash(git ls-files:*)", "Bash(grep:*)", "Bash(echo:*)"] disable-model-invocation: false user-invocable: true shell: bash @@ -37,7 +37,7 @@ argument names an explicit target**, and the tooling line is re-derived in step Arguments: `$ARGUMENTS` Posture: `${user_config.comment_posture}` (unexpanded or empty means `strict`; any value outside -`strict`, `balanced`, `conservative` is read as `strict`). +`strict`, `balanced`, `conservative`, `aggressive` is read as `strict`). Kept-comment line budget: `${user_config.class_c_max_lines}` (unexpanded or empty means `2`). Apply proven local renames without a test net: `${user_config.apply_local_renames}` (unexpanded or empty means `true`). @@ -64,8 +64,8 @@ line budget, and worked examples: [reference/triage.md](reference/triage.md). |---|---|---| | **A, zero/negative information** | Restates adjacent code, obsolete, commented-out code | Delete outright, certified by the token proof | | **B, information code could carry** | The comment compensates for a naming/structure deficiency. Empty by construction on a data or config file (TOML, YAML, JSON), which has no naming channel, so the pass there degrades to A plus C | Refactor until the comment is superfluous, then delete, never delete first | -| **C, information code cannot carry** | Why/rationale, constraint, warning, contract, negative or operational information | **Kept** when load-bearing at the point of reading and not recoverable where a reader would look; held to the line budget once the exempt-surface check has cleared it, rewritten terser when over it, narrative staged | -| **C, same test failed** | Inexpressible, but the earn-its-keep test's criterion 2 fails: recoverable from version control, an ADR, or an external source | **Deleted** under `strict`, certified by the same token proof class A uses, narrative staged before the deletion is final; **proposed** under `safe` and `conservative`, which apply class-A deletions only. The negative branch of the class-C test, not a fourth class | +| **C, information code cannot carry** | Why/rationale, constraint, warning, contract, negative or operational information | **Kept** when load-bearing at the point of reading and not recoverable where a reader would look; held to the line budget once the exempt-surface check has cleared it, rewritten terser when over it, narrative staged. Under `aggressive` only a warning of consequence is kept, and under `strip` nothing in this class is | +| **C, same test failed** | Inexpressible, but the earn-its-keep test's criterion 2 fails: recoverable from version control, an ADR, or an external source | **Deleted** under `strict`, certified by the same token proof class A uses, narrative staged before the deletion is final; **proposed** under `safe` and `conservative`, which apply class-A deletions only. Under `aggressive` and `strip` criterion 2 is not run at all on a non-survivor: the comment is staged and deleted whether or not the reasoning is recoverable. The negative branch of the class-C test, not a fourth class | The two class-C rows are one class and one test, whose three criteria must **all** hold, named on each side, so a comment that fails it has somewhere to go. A criterion-1 failure is not this branch: @@ -80,22 +80,48 @@ Class-B moves and their tiers: [reference/dissolving-moves.md](reference/dissolv | *(empty)* | Triage the code files of the narrowest scope that resolves: uncommitted diff → branch diff → whole repository, resolved by `scope-code-files.sh` ([reference/scope.md](reference/scope.md)). On the repository rung, order the files with `rank-comment-targets.py` first. Pass `--allow-path ` for each path the `override` argument or the repository overrides file lifted, so the administrative gate does not re-drop those files and does not ungate every other administrative path. Pass `--override-exclusions` only when `hard_exclusions` is `advisory`, which lifts the whole HARD path list. | | `` | Triage a single file or directory (already-committed code is fine here). The pre-computed scope line above is **void** under an explicit target: that line runs the diff ladder unconditionally, so it names files this run is not triaging. Ignore it and do not run `scope-code-files.sh`. | | `safe [target]` | **Safe mode**: only class-A deletions are applied; every class-B treatment and class-C rewrite is emitted as a proposal. For codebases whose guardrails you do not know. | +| `aggressive [target]` | **Aggressive dial**: the survivor list below is the whole of what stays. Every other comment goes, rationale included, with its narrative staged; a class-B comment is dissolved when its move's gate passes and otherwise kept with a proposal. Gates are unchanged. Combines with `override` and `--notes`; `safe` beats it. | +| `strip [target]` | **Strip**: delete every comment except the survivor list, rewrite no code, certify each deletion COMMENT-ONLY, and stage the narrative. A class-B comment is deleted rather than dissolved, so its information reaches the staged block instead of the code. `safe` beats it, and `strip` beats `aggressive`. | +| `--notes ` | Append the staged block to `` as well as reporting it. The path must be untracked or outside the repository, checked with `git ls-files --error-unmatch `; a tracked path is refused, the run continues, and the block is reported only. | | `override [target]` | **Lift the GLOBAL HARD path list** for this run's target, so `/code-tidying:dissolve-comments override ruff.toml` triages a file the list would otherwise drop. Combines with `safe`. Strip the token before reading the target; match it whole, and treat `./override` as a path. Path entries only, and every lifted path is named in the step 7 report with the channel that lifted it. | Posture `conservative` is safe mode as a standing default; `balanced` keeps the full contract but -reports an over-budget class-C comment instead of rewriting it. - -**The posture ladder only descends.** `strict` is both the default and the ceiling; `balanced`, -`conservative` and `safe` each narrow what gets applied, `class_c_max_lines` bottoms out at 1, and -nothing removes more than `strict` does. That is deliberate: no knob loosens a gate -([reference/safety.md](reference/safety.md)). It is stated here because a user wanting a more -aggressive pass would otherwise hunt for a setting that does not exist. - -In every posture and mode, doubt keeps the comment: "when uncertain, keep or propose" is doctrine, -not timidity. Doubt means an unresolved *classification*, not a resolved one whose verdict is -delete. A criterion-2 failure established by the step-5 evidence check is not doubt, and the tie- -break does not reinstate it; that rule is what stops the earn-its-keep test from collapsing into -"keep everything". +reports an over-budget class-C comment instead of rewriting it; posture `aggressive` is the +`aggressive` row above as a standing default. A per-run token beats the standing posture, and +precedence among tokens is `safe`, then `strip`, then `aggressive`. `./safe`, `./aggressive`, +`./strip` and `./override` are paths, not tokens. + +**No knob loosens a gate.** `aggressive` and `strip` widen *what is triaged away*; they change no +gate and no proof. Every applied deletion still carries the COMMENT-ONLY verdict, every applied +function-local rename still carries RENAME-ONLY, tier-2 and tier-3 moves still need a discovered +test net, and an UNPROVABLE file still yields proposals only. A `strict` run and an `aggressive` run +differ in what they decide to remove, never in what they are allowed to prove. + +**What survives `aggressive` and `strip`** (the whole list; `reference/safety.md` carries the +detail): + +- the exempt surfaces: public-API doc comments, legal headers, machine-read directives (universal + and repo-local), units, sentinels, ownership, thread-safety and ordering contracts, suppression + justifications paired with their waiver, `TODO(#issue)` markers, and lines carrying + `dissolve-comments-ignore`; +- a comment that is one half of a comment-plus-regression-test pair, because deleting half of a + paired record is a correctness bug; +- under `aggressive` only, a load-bearing warning of consequence, held to `class_c_max_lines` and + rewritten terser when over it. Every survivor must be succinct, clear, and justified in the + report: name the consequence, not the history. + +In `strict`, `balanced`, `conservative` and `safe`, doubt keeps the comment: "when uncertain, keep +or propose" is doctrine, not timidity. Doubt means an unresolved *classification*, not a resolved +one whose verdict is delete. A criterion-2 failure established by the step-5 evidence check is not +doubt, and the tie-break does not reinstate it; that rule is what stops the earn-its-keep test from +collapsing into "keep everything". + +Under `aggressive` and `strip` the tie-break is narrower, because everything not on the survivor +list is leaving anyway: doubt whether a comment is an exempt surface, a paired record, or a +load-bearing warning **keeps it**. Doubt between classes does not keep it, and resolves to the +treatment that preserves the information: A-versus-B doubt resolves to B (dissolve when the gate +passes, else keep with a proposal under `aggressive`, delete with the narrative staged under +`strip`), and B-versus-C doubt resolves to B. Default mode applies the full contract: class A applies, each deletion certified by a token-level proof that no code changed; class B applies **per its tier**: a function-local rename behind the @@ -104,7 +130,7 @@ move behind the net and proposal-first. Whatever a tier's gate does not pass is the proof tool, the test-discovery procedure, and the mode ladder: [reference/safety.md](reference/safety.md). **Class B applies less than it looks like it does**, and a run planned around it should know that -first: 2 of 15 moves need no test net, 0 of 15 apply with tree-sitter absent, and no move dissolves +first: 2 of 16 moves need no test net, 0 of 16 apply with tree-sitter absent, and no move dissolves a *why*. Both limits are deliberate. See "Apply capacity" in [reference/dissolving-moves.md](reference/dissolving-moves.md) for the numbers and what follows from them. @@ -112,13 +138,20 @@ from them. ## Hard rules - **Never delete information without a landing place.** A class-B comment's information moves into - code *before* the comment goes. Removed narrative (rationale, justification) is staged in the - output as a proposed commit-message block for `/source-control:commit`. Text is never silently - destroyed. + code *before* the comment goes, except under `strip`, which rewrites nothing and sends that + information to the staged block instead. Removed narrative (rationale, justification) is staged in + the output as a proposed commit-message block for `/source-control:commit`, and `--notes ` + appends it to an untracked or out-of-repo file as well. The block carries an + `Intentional-removal:` line only when the target repository's own scripts or CI read that trailer + (`grep -rl 'Intentional-removal:'` over its gate scripts and workflows); elsewhere the line is + noise. On an explicit target over already-committed code the landing place is the next commit + touching that code, named in the report. Text is never silently destroyed. - **Every applied edit passes the gate its tier names; lint never opens one.** Deletions and function-local renames are certified by `${CLAUDE_SKILL_DIR}/scripts/change-shape.sh` (COMMENT-ONLY, RENAME-ONLY); additive and interface-creating moves need a discovered test net. - Any other verdict reverts the edit and demotes it to a proposal. + Any other verdict reverts the edit and demotes it to a proposal. A Python docstring is a string + token, not a comment, so removing one reads CODE-CHANGED and is always a proposal, private + docstrings included. - **RENAME-ONLY is a shape claim, not a safety claim.** It rejects a rename that misses a reference or lands on a name the file already uses, but cannot see other files, reflection, or string-keyed access. A rename applied on its strength is reported with its mapping, never silently. @@ -128,6 +161,12 @@ from them. `dissolve-comments-ignore`. **Negative and operational information are not on that list.** They are class C with a raised evidence bar, held to the same test and budget as any class-C comment. Exempting the category outright would contradict this skill's own eval 13. +- **A paired record is never half-deleted.** A comment asserting something about code that is not + present, paired with a regression test that pins it, is one artifact in two places; deleting the + comment alone is a correctness bug. Kept in every mode, `strip` included. +- **An identifier named in a repo-local marker row is never renamed.** A gate that pins + `=` as an exactly-once marker (step 2 discovers these) turns that spelling into compiler + input. Dissolve the comment if it earns dissolving, but leave the identifier alone. - **Path exclusions are the plugin's standard tier**, tidy's [exclusions reference](${CLAUDE_PLUGIN_ROOT}/skills/tidy/reference/exclusions.md) GLOBAL HARD list. Agent/enforcement config, CI workflows, hook chains, lint config are not edited unless a @@ -150,7 +189,9 @@ from them. the pre-computed scope line is void, `scope-code-files.sh` is not run, and no file outside the target is triaged or reported. Empty argument: run `scope-code-files.sh` (never the truncated preview), confirm a widening to the repository rung interactively, and take any widened rung in - safe mode when non-interactive. On the repository rung, run `${CLAUDE_SKILL_DIR}/scripts/rank-comment-targets.sh` and triage + safe mode when non-interactive, **whatever the posture or dial token**: `aggressive` and `strip` + reach a widened rung only through an interactive confirmation, and an explicit target is the + other way to mean it. On the repository rung, run `${CLAUDE_SKILL_DIR}/scripts/rank-comment-targets.sh` and triage in its order. When an override channel is active, hand its resolved reach to the ranker so the administrative gate does not re-drop a lifted path: `--allow-path ` per path the `override` argument or the repository overrides file lifted, and `--override-exclusions` only for @@ -210,7 +251,9 @@ from them. content is rationale, run `git log -L ,:` over its own lines and check the repo's ADR or decision-log directory where one is declared; recoverable there **fails** the criterion, absent from both **passes**, unreadable history is recorded as unavailable and keeps - the comment. Full procedure: [reference/triage.md](reference/triage.md). Done when every comment + the comment. Under `aggressive` and `strip` this evidence check is skipped for every comment + outside the survivor list: the verdict is the same either way, and the `git log -L` per comment + is the expensive half of a run. Full procedure: [reference/triage.md](reference/triage.md). Done when every comment carries one class and every class-C candidate a criterion-2 verdict with its evidence. 6. **Apply**, one item at a time, each behind its tier's gate. Class A: delete, run `change-shape.py` on before and after; anything but COMMENT-ONLY (exit 0) restores the comment. @@ -219,7 +262,10 @@ from them. at any length. A non-exempt comment that **failed** criterion 2 is, under `strict`, staged then deleted behind the same COMMENT-ONLY proof class A uses; under `safe` or `conservative` it is proposed instead, since those modes apply class-A deletions only, and a rationale comment is not class - A however its test resolved. A non-exempt comment over budget is rewritten to the budget under + A however its test resolved. Under `aggressive` and `strip` every non-survivor is staged and then + deleted behind that same COMMENT-ONLY proof, class C included, and under `strip` a class-B + comment takes that path rather than its move; under `aggressive` a class-B move still applies + only when its tier's gate passes, and the comment stays with a proposal when it does not. A non-exempt comment over budget is rewritten to the budget under `strict` with the narrative staged, reported instead under `balanced`; its carve-out reason names every kept comment by file and line, written once for a group that enumerates its members. Where most of a file's class-C comments carry contract, negative, or operational information, say so @@ -231,7 +277,11 @@ from them. versus proposed with each applied item's verdict (and the mapping for every RENAME-ONLY), the staged commit-message block, the class-C keeps and rewrites with one-line reasons (grouped where several share one, every member still named) and **each keep naming its criterion-2 evidence** - from step 5, plus any whole-file budget suspension. Under a whole-file verdict, report that + from step 5, plus any whole-file budget suspension. Under `aggressive` and `strip` a keep names + which survivor rule earned it (exempt surface, paired record, warning of consequence) in place of + criterion-2 evidence, and the report names the block's landing place: the pending commit, the + next commit touching already-committed code, or the `--notes` path, with a refused tracked path + said plainly. Under a whole-file verdict, report that file's class-C keeps as a count per reason group rather than a line each; the per-keep evidence line is owed only for keeps the run actually searched. Then the census delta, `comment-census.py --baseline` pointed at the exact `baseline.json` step 4 wrote, in lines, bytes and estimated @@ -242,20 +292,29 @@ from them. ## What this skill is NOT -- **Not "delete all comments."** Class C survives on the earn-its-keep test; exempt surfaces are - never touched. +- **Not "delete all comments."** `strip` comes closest and still keeps the survivor list: exempt + surfaces and paired records are never touched, in any mode. Without a dial token, class C survives + on the earn-its-keep test. - **Not `/code-tidying:audit-comment-residue`**, the read-only residue classifier. Run that for findings without changes. - **Not `/code-tidying:tidy` or `/code-tidying:batch-simplify`.** No lane rotation, no scope budget, no wave machinery: one pass over one resolved scope. - **Not a bug-hunter or general simplifier.** `/code-review` and `/simplify` own those. +## Next + +`/source-control:commit`, which takes the staged commit-message block this run printed and lands the +removed narrative with the diff that removed it. + ## Gotchas - A comment that *looks* like restatement can disambiguate genuinely ambiguous code. Misclassifying - B as A is the information-destroying failure; when uncertain, keep or propose. + B as A is the information-destroying failure; when uncertain, keep or propose. Under `aggressive` + and `strip` the same doubt resolves to B, which is why those modes stage before they delete. - Rationale for a *rejected* approach has no referent in the adjacent code, the same surface as a - stale comment. It is class C by default ([reference/safety.md](reference/safety.md)). + stale comment. It is class C by default ([reference/safety.md](reference/safety.md)), and under + `aggressive` and `strip` it is staged and deleted: rejected alternatives belong in the commit or + PR that removed them. - Extraction has a cost curve: a name that must grow megasyllabic to stay honest signals the information did not fit the name channel. Short name plus terse comment, or Inline Function, beats a dishonest long name ([reference/dissolving-moves.md](reference/dissolving-moves.md)). diff --git a/plugins/code-tidying/skills/dissolve-comments/evals/evals.json b/plugins/code-tidying/skills/dissolve-comments/evals/evals.json index 739a4e491..64bf5dea1 100644 --- a/plugins/code-tidying/skills/dissolve-comments/evals/evals.json +++ b/plugins/code-tidying/skills/dissolve-comments/evals/evals.json @@ -180,6 +180,42 @@ "Deletes the two-line block with a COMMENT-ONLY verdict and keeps the prose comment for ordinary triage", "Notes ruff as absent with the capability lost, rather than failing" ] + }, + { + "id": 16, + "name": "aggressive-deletes-non-exempt-rationale-with-staging", + "prompt": "/code-tidying:dissolve-comments aggressive lib/worker.sh\n\n(lib/worker.sh is committed. It carries a rationale comment explaining why a retry count is three, a warning that one function must run before another or the lock file deadlocks, a license header, and a '# shellcheck disable=SC2034' directive with its justification. The repository has no test suite and tree-sitter is present for bash.)", + "narration": true, + "expectations": [ + "Deletes the retry rationale with its narrative staged in the proposed commit-message block, without running a git log -L recoverability check on it", + "Keeps the deadlock warning, rewritten to at most class_c_max_lines, and names it as a warning of consequence rather than citing criterion-2 evidence", + "Keeps the license header and the shellcheck directive with its justification untouched", + "Certifies each deletion COMMENT-ONLY with change-shape.py and reports the next commit touching the file as the staged block's landing place" + ] + }, + { + "id": 17, + "name": "strip-keeps-exempt-surfaces-and-rewrites-nothing", + "prompt": "/code-tidying:dissolve-comments strip lib/worker.sh\n\n(Same file as the aggressive case: a rationale comment, a deadlock warning, a license header, a shellcheck directive with its justification, and a block comment narrating what a six-line section does. A test suite exists and is discoverable.)", + "narration": true, + "expectations": [ + "Deletes the rationale, the warning, and the narrating block comment, each certified COMMENT-ONLY", + "Does NOT extract a function for the narrating comment even though a test net exists, because strip rewrites no code, and sends that comment's information to the staged block instead", + "Keeps the license header and the shellcheck directive with its justification", + "Leaves every non-comment line byte-identical" + ] + }, + { + "id": 18, + "name": "standing-aggressive-posture-yields-to-safe", + "prompt": "/code-tidying:dissolve-comments safe lib/worker.sh\n\n(comment_posture is aggressive. The file carries one restating comment above the line it restates and one rationale comment whose reasoning is absent from git history.)", + "narration": true, + "expectations": [ + "Applies the class-A deletion of the restating comment", + "Does NOT delete the rationale comment: safe wins over the standing aggressive posture, and only class-A deletions are applied", + "Emits the rationale removal as a proposal with its narrative staged", + "Names safe mode as the effective mode in the report rather than the standing posture" + ] } ] } diff --git a/plugins/code-tidying/skills/dissolve-comments/reference/dissolving-moves.md b/plugins/code-tidying/skills/dissolve-comments/reference/dissolving-moves.md index 141d5a08b..17dfe0701 100644 --- a/plugins/code-tidying/skills/dissolve-comments/reference/dissolving-moves.md +++ b/plugins/code-tidying/skills/dissolve-comments/reference/dissolving-moves.md @@ -12,7 +12,7 @@ net; interface-creating moves need a test net and stay proposals in non-interact |---|---| | 1, token-proven | Rename Variable, Rename Field (function-local identifiers only) | | 2, test-gated | Extract Variable, Replace Magic Literal, Introduce Assertion, Slide Statements, Decompose Conditional, Replace Nested Conditional with Guard Clauses, Introduce Special Case | -| 3, test-gated and proposal-first | Extract Function, Change Function Declaration, Move Statements into Function, Replace Inline Code with Function Call, Introduce Parameter Object, Inline Function | +| 3, test-gated and proposal-first | Extract Function, Change Function Declaration, Extract Class, Move Statements into Function, Replace Inline Code with Function Call, Introduce Parameter Object, Inline Function | | Comment shape being dissolved | Named refactoring | |---|---| @@ -54,11 +54,11 @@ net; interface-creating moves need a test net and stay proposals in non-interact Class B reads like the skill's main engine. On many repositories it turns over nothing, and a run planned around it should know the three limits up front. All three are deliberate. -- **2 of the 15 moves need no test net.** Only Rename Variable and Rename Field are tier 1, and +- **2 of the 16 moves need no test net.** Only Rename Variable and Rename Field are tier 1, and only on a *function-local* identifier. Every other move adds tokens, so the token proof reports CODE-CHANGED by construction ([safety.md](safety.md)) and a discovered test net is required; without one they are proposed, never applied. -- **0 of 15 apply with tree-sitter absent.** The proof is unavailable, so "tier 1 without its proof +- **0 of 16 apply with tree-sitter absent.** The proof is unavailable, so "tier 1 without its proof is tier 2" ([safety.md](safety.md)) demotes the two renames into the test-net tier with everything else. On a repository with neither a runnable test net nor tree-sitter, a class-B pass produces a proposal list and no edits. diff --git a/plugins/code-tidying/skills/dissolve-comments/reference/safety.md b/plugins/code-tidying/skills/dissolve-comments/reference/safety.md index b17001c19..f621a97c3 100644 --- a/plugins/code-tidying/skills/dissolve-comments/reference/safety.md +++ b/plugins/code-tidying/skills/dissolve-comments/reference/safety.md @@ -13,8 +13,12 @@ behavior, while a token comparison is exhaustive over the file. |---|---|---|---| | **Default** | Applied, each deletion certified by the tier-0 proof | Applied per the tier table below; otherwise proposed | Earn-its-keep triage; a criterion-2 failure is deleted behind the tier-0 proof, an over-budget comment rewritten; narrative staged before either | | **`safe`** | Applied, same certification | Always proposed: no code-structure change is applied | Same triage, but **nothing class-C is applied**: a criterion-2 deletion and an over-budget rewrite are both proposed, with the narrative staged. Only class A deletes here | +| **`aggressive`** | Applied, same certification | Dissolved when the tier's gate passes; otherwise the comment stays with a proposal | Earn-its-keep is replaced by the survivor list: exempt surfaces, paired records, and warnings of consequence within `class_c_max_lines` stay, and every other class-C comment is staged and deleted behind the tier-0 proof | +| **`strip`** | Applied, same certification | Deleted as a comment, no move attempted, narrative staged | Same survivor list minus the warnings: only exempt surfaces and paired records stay | -`conservative` is `safe` as a standing default, so it reads the `safe` row. The class-C column is +`conservative` is `safe` as a standing default, so it reads the `safe` row, and posture +`aggressive` reads the `aggressive` row. A per-run token beats the standing posture; precedence is +`safe`, then `strip`, then `aggressive`. The class-C column is the one to get right: the triage still runs in every mode and still returns a verdict, but a verdict is not an application. `safe` narrowing class C to proposals is what makes "only class-A deletions are applied" in the action router true rather than approximately true. @@ -28,8 +32,8 @@ or excluded path, or delete text without a landing place (staging rule below). |---|---|---|---| | **0** | Class-A deletion | `change-shape.py` verdict **COMMENT-ONLY** | The comment-stripped token sequence is identical, so no code token moved. Exhaustive over the file; needs no tests, no build, no config | | **1** | Rename Variable / Rename Field on a function-local identifier | verdict **RENAME-ONLY** under one consistent mapping, and the identifier is neither exported nor public | A shape claim: every differing token is an identifier under one injective old→new mapping, no old name survives at an unchanged position, and no new name was already in use in the file. It cannot see other files, reflection, or string-keyed access, so it earns application plus a flagged review line in the report, never silence | -| **2** | Additive local move: Extract Variable, Replace Magic Literal, Introduce Assertion, Slide Statements, Decompose Conditional | discovered test net, run before and after | These add tokens, so the token proof reports CODE-CHANGED by construction and cannot certify them. Only tests attest behavior preservation here | -| **3** | Interface-creating move: Extract Function, Change Function Declaration, Extract Class, Introduce Parameter Object, Move Statements into Function, Replace Inline Code with Function Call | discovered test net, and **always a proposal in a non-interactive run** | Creates or renames an interface other code depends on. Ousterhout (APOSD §9.8) and Anthropic's own overeagerness guidance both warn against automating exactly this; the test net is necessary, not sufficient | +| **2** | Additive local move: Extract Variable, Replace Magic Literal, Introduce Assertion, Slide Statements, Decompose Conditional, Replace Nested Conditional with Guard Clauses, Introduce Special Case | discovered test net, run before and after | These add tokens, so the token proof reports CODE-CHANGED by construction and cannot certify them. Only tests attest behavior preservation here | +| **3** | Interface-creating move: Extract Function, Change Function Declaration, Extract Class, Introduce Parameter Object, Move Statements into Function, Replace Inline Code with Function Call, Inline Function | discovered test net, and **always a proposal in a non-interactive run** | Creates or renames an interface other code depends on. Ousterhout (APOSD §9.8) and Anthropic's own overeagerness guidance both warn against automating exactly this; the test net is necessary, not sufficient | `change-shape.py` is at `../../../scripts/change-shape.py` (relative to this file; the `${CLAUDE_PLUGIN_ROOT}` token is substituted in `SKILL.md` but **not** in a reference file, which @@ -55,10 +59,12 @@ probe reported: a pygments-level read may still apply deletions; a grep-level re in a language with heredocs or block comments, because it cannot tell a comment from string data. Tier 1 without its proof is tier 2. -Two user-config knobs move tiers without changing any gate: `apply_local_renames=false` makes -tier 1 a proposal even when RENAME-ONLY holds, and posture `conservative` makes every tier above 0 -a proposal (safe mode as a standing default). Nothing loosens a gate: no knob applies an edit its -tier's proof did not pass. +Knobs move tiers without changing any gate: `apply_local_renames=false` makes tier 1 a proposal even +when RENAME-ONLY holds, and posture `conservative` makes every tier above 0 a proposal (safe mode as +a standing default). `aggressive` and `strip` move in the other direction and still change no gate: +they widen which comments are triaged away, while every applied deletion carries COMMENT-ONLY, every +applied rename carries RENAME-ONLY, tiers 2 and 3 keep their test net, and an UNPROVABLE file yields +proposals only. No knob applies an edit its tier's proof did not pass. ## The test net (tiers 2 and 3) @@ -208,6 +214,14 @@ code, note in the report that the narrative belongs with the *next* commit touch or keep the comment if no vehicle exists (staging with no landing place is not a deletion licence). +`--notes ` gives the block a second home: the run appends it to that file as well as +reporting it. The path must be untracked or outside the repository, checked with +`git ls-files --error-unmatch `; a tracked path is refused and the run continues with the +report as the only vehicle. The block carries an `Intentional-removal:` line only where the target +repository's own gate scripts or CI read that trailer, since elsewhere it is a line no tool will +ever match. Under `strip` the block carries more than usual: a class-B comment's information lands +there rather than in a rewrite, so a thin staged block under `strip` is a defect, not a clean run. + ## Gotcha: rejected-alternative rationale reads exactly like residue The highest-cost misclassification this skill can make is deleting a comment that records why an diff --git a/plugins/code-tidying/skills/dissolve-comments/reference/scope.md b/plugins/code-tidying/skills/dissolve-comments/reference/scope.md index a83b762bc..0b5312e2f 100644 --- a/plugins/code-tidying/skills/dissolve-comments/reference/scope.md +++ b/plugins/code-tidying/skills/dissolve-comments/reference/scope.md @@ -27,7 +27,8 @@ The ladder advances on **absence** of a rung, never on emptiness: a rung that ex code files is reported with `files=0`, so a docs-only branch reports its files as out of scope instead of silently escalating to the whole repository. Widening to the `repository` rung is confirmed with the user in an interactive session; a non-interactive run proceeds in **safe** mode -on any widened rung. +on any widened rung, whatever the posture or dial token. `aggressive` and `strip` reach a widened +rung only through that interactive confirmation; an explicit target is the other way to mean it. Granularity is per file: every comment in a listed file is triaged, not only the lines the diff added. That is deliberate. A pull request that touches a file is the moment its existing comments diff --git a/plugins/code-tidying/skills/dissolve-comments/reference/triage.md b/plugins/code-tidying/skills/dissolve-comments/reference/triage.md index c124abdf5..099f936f4 100644 --- a/plugins/code-tidying/skills/dissolve-comments/reference/triage.md +++ b/plugins/code-tidying/skills/dissolve-comments/reference/triage.md @@ -82,7 +82,8 @@ A comment survives only if **all three** hold: reason once, provided the group **enumerates every member by file and line**; the sentence is what may be written once, never the naming. What never survives is length spent on justification narrative. Posture `balanced` reports an over-budget comment instead of rewriting - it; `conservative` proposes the rewrite. + it; `conservative` proposes the rewrite; `aggressive` keeps only a warning of consequence and + rewrites it to the budget, and `strip` keeps nothing here. **When the test fails.** A comment that passes criterion 1 and fails criterion 2 is **deleted** under `strict`, behind the same COMMENT-ONLY token proof class A uses, with its narrative staged @@ -92,8 +93,12 @@ Criterion 3 has its own treatment, the rewrite above; only criterion 2 sends a c Under `safe` mode and posture `conservative` this deletion is **proposed, never applied**. Those modes apply class-A deletions only, and a comment that reached this branch is class C whatever its -test returned. The mode ladder narrows what is applied, and it does not get to be widened by a -verdict reached inside it. +test returned. What the run may apply is set by the mode, never by a verdict reached inside it. + +Under `aggressive` and `strip` the criteria are not what decides. The survivor list in `SKILL.md` +does: an exempt surface, a paired record, and (under `aggressive`) a warning of consequence stay, +and every other comment here is staged and deleted behind the same token proof, recoverable +rationale or not. Criterion 2 is not evaluated for them, since its answer changes nothing. **Whole-file verdict.** Where the majority of a file's class-C comments carry contract, negative, or operational information, the file is contract-heavy rather than over-narrated. The report states From ff399bedb9ae158fce466c978620be728850979e Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 00:23:07 -0400 Subject: [PATCH 06/17] docs(code-tidying): close phase 3 of the aggressive-dial plan Every static gate passed, the em-dash scan included. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KcBf2VNNsVTuiqZ1N5RuCu --- docs/topics/dissolve-comments-aggressive-dial/PLAN.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md index 437533040..7ecc815f3 100644 --- a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md +++ b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md @@ -374,7 +374,7 @@ comment-hygiene scan do not lint deliberately commented fixtures. - `git ls-files plugins/code-tidying/evals | grep -E '\.(sh|py|bash)$'` lists only scaffold scripts. - Every case's expected-red list in this file matches its red-run record. -### Phase 3: Skill change [DOING] +### Phase 3: Skill change [DONE] 1. Consumer and claim sweep first: `grep -rn "comment_posture\|only descends\|narrows\|widened\|loosen nothing\|ceiling\|of 15" plugins/code-tidying docs scripts`; record every hit and its disposition @@ -450,8 +450,8 @@ comment-hygiene scan do not lint deliberately commented fixtures. every base trigger phrase preserved; `check-skill` warns that the next added clause breaches the limit. - Gates: `check-skill` PASS (1 warning, the headroom note), `sync-plugin-options-docs.py --check` - exit 0, `check-evals-quality.sh` PASS, `allowed-tools-pairing.test.sh` exit 0, tier tables agree - tier by tier. + exit 0, `check-evals-quality.sh` PASS, `allowed-tools-pairing.test.sh` exit 0, + `check-purged-em-dashes.sh` exit 0, tier tables agree tier by tier. **Sanity Check:** From 67f48b5e591aa347e5b195cb87a8fa4327b2150c Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 09:57:53 -0400 Subject: [PATCH 07/17] fix(code-tidying): sharpen the aggressive survivor rules after the first eval pass Pass 1 scored 0.92 with four cases under the bar. "Warning of consequence" is now defined: a runtime failure a caller hits by using the code as written, not the reason a value was chosen. The exempt bullet for units, sentinels, ownership, thread-safety and ordering narrows to annotations on the adjacent declaration, so a sentence about a choice is triaged like any other comment. A kept comment now stays where it is and keeps its own words, and a run is non-interactive whenever AskUserQuestion is unavailable. Six grader anchors widened where a correct result was missed or a reworded keep passed a deletion check. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KcBf2VNNsVTuiqZ1N5RuCu --- .../dissolve-comments-aggressive-dial/PLAN.md | 31 +++++++++++++++++- .../invented-class-b-aggressive/case.yaml | 3 +- .../invented-class-c-aggressive/case.yaml | 3 +- .../case.yaml | 9 ++++-- .../skills/dissolve-comments/SKILL.md | 32 +++++++++++++------ .../dissolve-comments/reference/safety.md | 8 +++-- 6 files changed, 68 insertions(+), 18 deletions(-) diff --git a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md index 7ecc815f3..21c314030 100644 --- a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md +++ b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md @@ -471,7 +471,36 @@ comment-hygiene scan do not lint deliberately commented fixtures. each exit 0. - `jq -r .version plugins/code-tidying/.claude-plugin/plugin.json` prints `0.20.0`. -### Phase 4: Green run, calibration loop, PR [TODO] +### Phase 4: Green run, calibration loop, PR [DOING] + +**Pass 1 record (2026-09-15, `--runs 1 --ablation none --max-cost-usd 20`, 20.16 USD):** 21 of 23 +cases ran before the ceiling stopped the run; 17 passed, overall score 0.92. The two +`real-statusline-stamp-*` cases never started. + +Causes, classified from the traces and the kept workspaces: + +| Case | Grader | Cause | Fix | +|---|---|---|---| +| `invented-class-c-aggressive` | `timeout-rationale-deleted` | skill wording: "warning of consequence" was undefined, and the load-balancer rationale read as both a warning and a boundary-semantics exempt surface | `SKILL.md` defines the term and excludes the reason a value was chosen | +| `invented-class-c-strip` | `no-comments-left` | same, plus the exempt bullet covered whole sentences | `safety.md` and `SKILL.md` narrow units, sentinels, ownership, thread-safety and ordering to annotations on the adjacent declaration | +| `real-hook-utils-header-aggressive` | `kept-comment-succinct`, `code-intact` | the run relocated a kept comment and wrote a new one, which no rule forbade | `SKILL.md` step 6 adds: a kept comment stays where it is and keeps its own words | +| `interaction-non-interactive-no-target` | `safe-mode-named`, `proof-ran` | the skill had no way to detect a non-interactive session, and the scaffold's untracked files keep the ladder on the uncommitted rung | `SKILL.md` step 1 ties non-interactive to `AskUserQuestion` being unavailable; the case's own expectation is still open (below) | +| `invented-class-b-aggressive` | `magic-literal-named` | grader anchoring: the run named the constant `seconds_per_day`, correct shell style for a `local` | pattern lowercased with `flags: i` | +| `invented-class-c-aggressive` | `no-intentional-removal-trailer` | grader anchoring: the report said it emitted no such trailer, and the bare token matched that sentence | pattern anchored to line start with `flags: m` | +| `real-silent-revert-design-strip` | `narrative-staged` | not a defect: the judge was skipped when the cost ceiling hit | re-run under a higher ceiling | + +Grader changes and their reasons (anchoring only, per the phase rule): the three above, plus three +deletion anchors in `real-hook-utils-header-aggressive` (`description-deleted`, +`kill-switch-comment-deleted`, `is-enabled-comment-deleted`) widened to catch a reworded keep. They +had passed while the comment was kept in different words, which is a false pass. + +**Open for the owner:** `interaction-non-interactive-no-target` cannot exercise the widened-rung +rule as written, because any untracked file (the harness writes `.claude/`) keeps the scope ladder +on the uncommitted rung with `files=0`, where the doctrine correctly stops. Either the case's +expectation narrows to the new non-interactive statement, or `scope-code-files.sh` changes to +advance on a rung with no code files, which is a script change this plan put out of scope. + +### Phase 4 remaining work [TODO] 1. Run the full suite in WSL2 with `--runs 3 --threshold 0.8 --max-cost-usd `. 2. For each failing case, classify the cause from the `--keep-temp` trace before editing: a denied diff --git a/plugins/code-tidying/evals/invented-class-b-aggressive/case.yaml b/plugins/code-tidying/evals/invented-class-b-aggressive/case.yaml index 6624e81f1..5c2cb493e 100644 --- a/plugins/code-tidying/evals/invented-class-b-aggressive/case.yaml +++ b/plugins/code-tidying/evals/invented-class-b-aggressive/case.yaml @@ -13,7 +13,8 @@ execution: graders: - name: magic-literal-named type: regex - pattern: 'SECONDS_PER_DAY' + pattern: 'seconds_per_day' + flags: i target: { source: file, path: class-b.sh } - name: vague-local-renamed type: regex diff --git a/plugins/code-tidying/evals/invented-class-c-aggressive/case.yaml b/plugins/code-tidying/evals/invented-class-c-aggressive/case.yaml index 6243dee2f..3f39dea3c 100644 --- a/plugins/code-tidying/evals/invented-class-c-aggressive/case.yaml +++ b/plugins/code-tidying/evals/invented-class-c-aggressive/case.yaml @@ -54,7 +54,8 @@ graders: target: last_message - name: no-intentional-removal-trailer type: regex - pattern: 'Intentional-removal:' + pattern: '^[ \t]*Intentional-removal:' + flags: m match: not_contains target: last_message - name: narrative-staged diff --git a/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml b/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml index be49b5d5c..727b65d79 100644 --- a/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml +++ b/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml @@ -30,7 +30,8 @@ graders: target: { source: file, path: hook-utils-header.sh } - name: description-deleted type: regex - pattern: '#[^\n]*Shared hook utility library' + pattern: '#[^\n]*(hook utility library|Sourced|kill switch, file_path parsing)' + flags: i match: not_contains target: { source: file, path: hook-utils-header.sh } - name: fork-cost-deleted @@ -51,7 +52,8 @@ graders: target: { source: file, path: hook-utils-header.sh } - name: kill-switch-comment-deleted type: regex - pattern: '#[^\n]*Per-hook kill switch' + pattern: '#[^\n]*(kill switch|CLAUDE_PLUGIN_OPTION_.*_ENABLED|exits 0)' + flags: i match: not_contains target: { source: file, path: hook-utils-header.sh } - name: fleet-switch-rationale-deleted @@ -61,7 +63,8 @@ graders: target: { source: file, path: hook-utils-header.sh } - name: is-enabled-comment-deleted type: regex - pattern: '#[^\n]*(TRANSPARENT WRAPPER|statusline tee|PREDICATE)' + pattern: '#[^\n]*(transparent wrapper|statusline|predicate)' + flags: i match: not_contains target: { source: file, path: hook-utils-header.sh } - name: kept-comment-succinct diff --git a/plugins/code-tidying/skills/dissolve-comments/SKILL.md b/plugins/code-tidying/skills/dissolve-comments/SKILL.md index edac613d9..fa4f13841 100644 --- a/plugins/code-tidying/skills/dissolve-comments/SKILL.md +++ b/plugins/code-tidying/skills/dissolve-comments/SKILL.md @@ -101,14 +101,19 @@ differ in what they decide to remove, never in what they are allowed to prove. detail): - the exempt surfaces: public-API doc comments, legal headers, machine-read directives (universal - and repo-local), units, sentinels, ownership, thread-safety and ordering contracts, suppression - justifications paired with their waiver, `TODO(#issue)` markers, and lines carrying - `dissolve-comments-ignore`; + and repo-local), units, sentinels, ownership, thread-safety and ordering **annotations on the + adjacent declaration** (`# seconds`, `# -1 means unset`; a sentence explaining a choice is not an + annotation), suppression justifications paired with their waiver, `TODO(#issue)` markers, and + lines carrying `dissolve-comments-ignore`; - a comment that is one half of a comment-plus-regression-test pair, because deleting half of a paired record is a correctness bug; -- under `aggressive` only, a load-bearing warning of consequence, held to `class_c_max_lines` and - rewritten terser when over it. Every survivor must be succinct, clear, and justified in the - report: name the consequence, not the history. +- under `aggressive` only, a **warning of consequence**: a comment naming a runtime failure a caller + hits by using the code as written, such as a required call order, a precondition, or a required + call form a caller would otherwise get wrong. The *reason a + value was chosen*, another system's limit, an upstream's behavior, a past incident, is rationale + rather than a warning: it is staged and deleted. Held to `class_c_max_lines` and rewritten terser + when over it. Every survivor must be succinct, clear, and justified in the report: name the + consequence, not the history. In `strict`, `balanced`, `conservative` and `safe`, doubt keeps the comment: "when uncertain, keep or propose" is doctrine, not timidity. Doubt means an unresolved *classification*, not a resolved @@ -157,8 +162,9 @@ from them. string-keyed access. A rename applied on its strength is reported with its mapping, never silently. - **Exempt surfaces are invisible to this skill** ([reference/safety.md](reference/safety.md)): public-API doc comments; legal headers; machine-read directives, universal and repo-local; - units, sentinels and suppression justifications; `TODO(#issue)` markers; lines carrying - `dissolve-comments-ignore`. **Negative and operational information are not on that list.** They + unit, sentinel, ownership, thread-safety and ordering annotations on the adjacent declaration, + never a sentence explaining a choice; suppression justifications; `TODO(#issue)` markers; lines + carrying `dissolve-comments-ignore`. **Negative and operational information are not on that list.** They are class C with a raised evidence bar, held to the same test and budget as any class-C comment. Exempting the category outright would contradict this skill's own eval 13. - **A paired record is never half-deleted.** A comment asserting something about code that is not @@ -191,7 +197,10 @@ from them. preview), confirm a widening to the repository rung interactively, and take any widened rung in safe mode when non-interactive, **whatever the posture or dial token**: `aggressive` and `strip` reach a widened rung only through an interactive confirmation, and an explicit target is the - other way to mean it. On the repository rung, run `${CLAUDE_SKILL_DIR}/scripts/rank-comment-targets.sh` and triage + other way to mean it. **You are non-interactive whenever the `AskUserQuestion` tool is + unavailable.** There, take the widened rung in safe mode, say "safe mode, non-interactive + widening" in the report, and name the explicit-target re-run that would apply the full contract; + do not end the turn on a question nobody can answer. On the repository rung, run `${CLAUDE_SKILL_DIR}/scripts/rank-comment-targets.sh` and triage in its order. When an override channel is active, hand its resolved reach to the ranker so the administrative gate does not re-drop a lifted path: `--allow-path ` per path the `override` argument or the repository overrides file lifted, and `--override-exclusions` only for @@ -266,7 +275,10 @@ from them. deleted behind that same COMMENT-ONLY proof, class C included, and under `strip` a class-B comment takes that path rather than its move; under `aggressive` a class-B move still applies only when its tier's gate passes, and the comment stays with a proposal when it does not. A non-exempt comment over budget is rewritten to the budget under - `strict` with the narrative staged, reported instead under `balanced`; its carve-out reason names + `strict` with the narrative staged, reported instead under `balanced`. **A kept comment stays + where it is and keeps its own words**: rewriting shortens the comment that is there, and never + relocates it, merges two comments, or writes a new one. A comment whose referent is gone is + deleted, not re-authored. its carve-out reason names every kept comment by file and line, written once for a group that enumerates its members. Where most of a file's class-C comments carry contract, negative, or operational information, say so once as a whole-file verdict with its count and suspend the budget for that file. Criterion 2 diff --git a/plugins/code-tidying/skills/dissolve-comments/reference/safety.md b/plugins/code-tidying/skills/dissolve-comments/reference/safety.md index f621a97c3..c6227a5c4 100644 --- a/plugins/code-tidying/skills/dissolve-comments/reference/safety.md +++ b/plugins/code-tidying/skills/dissolve-comments/reference/safety.md @@ -96,8 +96,12 @@ open the apply path, because they cannot attest behavior preservation. `#pragma warning`), region markers, editor folds, encoding cookies - **Repo-local machine-read markers**, discovered per run. See the section below. The universal pragmas above are the floor, not the list -- Units, ranges, boundary semantics, sentinel values, ownership and lifetime, thread-safety, and - ordering guarantees. A comment naming what `-1` or `nullptr` means is a contract, not narration +- Units, ranges, boundary semantics, sentinel values, ownership and lifetime, thread-safety and + ordering guarantees, **as an annotation on the adjacent declaration**: `# seconds`, `# -1 means + unset`, `# caller owns the handle`. A comment naming what `-1` or `nullptr` means is a contract, + not narration. A *sentence* about why a value was chosen or when a function must be called is not + an annotation: it is class C, held to the earn-its-keep test, the budget, and the dials like any + other comment - Suppression justifications: the reason attached to a lint waiver, a cast-safety claim, or a narrowing assertion (`@SuppressWarnings("unchecked") // safe because …`). The waiver is a directive and the reason is what makes it reviewable. Removing either breaks the pair From 880a5b8f5f4773198fc2226fa49c8e39703c29c0 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 10:35:49 -0400 Subject: [PATCH 08/17] test(code-tidying): scope the non-interactive case to what a sandbox can show Any untracked file keeps the scope ladder on the uncommitted rung with zero code files, where the doctrine correctly stops, so the case cannot reach a widened rung. It now checks the posture statement instead: the run names the rung it resolved, says the session is non-interactive, names the explicit-target re-run, and does not end on a question. The skill states that rule for a zero-file rung as well as a widened one. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KcBf2VNNsVTuiqZ1N5RuCu --- .../case.yaml | 23 ++++++++++++++----- .../skills/dissolve-comments/SKILL.md | 4 +++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/plugins/code-tidying/evals/interaction-non-interactive-no-target/case.yaml b/plugins/code-tidying/evals/interaction-non-interactive-no-target/case.yaml index 01142bfa7..ecc11500e 100644 --- a/plugins/code-tidying/evals/interaction-non-interactive-no-target/case.yaml +++ b/plugins/code-tidying/evals/interaction-non-interactive-no-target/case.yaml @@ -1,8 +1,8 @@ schema_version: "1.1" name: interaction-non-interactive-no-target -description: aggressive with no target on a clean committed tree widens to the repository rung and runs in safe mode non-interactively +description: aggressive with no target in a non-interactive session states its posture, applies no class-C deletion, and does not end on a question tags: [calibration, interaction, aggressive, invented] -expected_outcome: The scope widens past the empty diffs, the non-interactive run takes safe mode and says so, and no class-C comment is deleted; the warning and the retry rationale stay; code is unchanged. +expected_outcome: The run names the rung the ladder resolved and states that it is non-interactive (safe mode, and the explicit-target re-run that would apply the full contract). No class-C comment is deleted, the warning and the retry rationale stay, code is unchanged, and the final message is a report rather than a question to the user. context: scaffold_script: scaffold.sh execution: @@ -20,11 +20,22 @@ graders: type: regex pattern: '#[^\n]*upstream API' target: { source: file, path: class-c.sh } - - name: safe-mode-named + - name: non-interactive-posture-named type: regex - pattern: 'safe' + pattern: 'non-interactive' flags: i target: last_message + - name: rung-reported + type: regex + pattern: 'rung|uncommitted|files=0|branch diff' + flags: i + target: last_message + - name: does-not-end-on-a-question + type: regex + pattern: '(shall I|should I|would you like|do you want|may I) [^\n]*\?' + flags: i + match: not_contains + target: last_message - name: kept-comment-succinct type: regex pattern: '(^[ \t]*#(?!!| shellcheck )[^\n]*\n){3}' @@ -36,8 +47,8 @@ graders: pattern: '^prepare_lock\(\) \{\n : >"\$lock_file"\n\}[\s\S]*^ local retries=3\n for \(\(attempt = 1; attempt <= retries; attempt\+\+\)\); do\n if curl --silent --max-time "\$timeout" "\$api_url/status"; then\n return 0\n fi\n done\n return 1\n\}[\s\S]*^counter=0$[\s\S]*^timeout=5$[\s\S]*^prepare_lock\nstart_worker\n\nfetch_status \|\| counter=\$\(\(counter \+ 1\)\)\necho "failures: \$counter"$' flags: m target: { source: file, path: class-c.sh } - - name: proof-ran + - name: scope-resolved type: tool_used tool: Bash - input_match: change-shape + input_match: scope-code-files arm: both diff --git a/plugins/code-tidying/skills/dissolve-comments/SKILL.md b/plugins/code-tidying/skills/dissolve-comments/SKILL.md index fa4f13841..7b7bc229a 100644 --- a/plugins/code-tidying/skills/dissolve-comments/SKILL.md +++ b/plugins/code-tidying/skills/dissolve-comments/SKILL.md @@ -200,7 +200,9 @@ from them. other way to mean it. **You are non-interactive whenever the `AskUserQuestion` tool is unavailable.** There, take the widened rung in safe mode, say "safe mode, non-interactive widening" in the report, and name the explicit-target re-run that would apply the full contract; - do not end the turn on a question nobody can answer. On the repository rung, run `${CLAUDE_SKILL_DIR}/scripts/rank-comment-targets.sh` and triage + do not end the turn on a question nobody can answer. The same holds when the ladder stops on a + rung with no code files: report the rung and its count, say the session is non-interactive, name + the explicit-target re-run, and stop there rather than asking. On the repository rung, run `${CLAUDE_SKILL_DIR}/scripts/rank-comment-targets.sh` and triage in its order. When an override channel is active, hand its resolved reach to the ranker so the administrative gate does not re-drop a lifted path: `--allow-path ` per path the `override` argument or the repository overrides file lifted, and `--override-exclusions` only for From 84cbda33c563e382b6099256e1318f83f7cd0653 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 11:08:07 -0400 Subject: [PATCH 09/17] fix(code-tidying): a shell header block is not a public-API doc comment The aggressive run kept a library's header, its kill-switch note, and a predicate note, reading each as an exempt public-API doc comment. The exemption now names the language's structured doc-comment form; a language without one has no exempt surface there. A warning also earns its keep only when the failure it names is not already visible in the adjacent code. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KcBf2VNNsVTuiqZ1N5RuCu --- plugins/code-tidying/skills/dissolve-comments/SKILL.md | 9 +++++++-- .../skills/dissolve-comments/reference/safety.md | 5 ++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/code-tidying/skills/dissolve-comments/SKILL.md b/plugins/code-tidying/skills/dissolve-comments/SKILL.md index 7b7bc229a..7c51bdf5a 100644 --- a/plugins/code-tidying/skills/dissolve-comments/SKILL.md +++ b/plugins/code-tidying/skills/dissolve-comments/SKILL.md @@ -100,7 +100,10 @@ differ in what they decide to remove, never in what they are allowed to prove. **What survives `aggressive` and `strip`** (the whole list; `reference/safety.md` carries the detail): -- the exempt surfaces: public-API doc comments, legal headers, machine-read directives (universal +- the exempt surfaces: public-API doc comments **in the language's structured doc-comment form** + (a docstring, an XML doc, JSDoc or TSDoc, a GoDoc sentence) attached to a public declaration; a + free-form comment block in a language with no doc-comment form, shell and make among them, is not + a doc comment and takes the ordinary triage. Also legal headers, machine-read directives (universal and repo-local), units, sentinels, ownership, thread-safety and ordering **annotations on the adjacent declaration** (`# seconds`, `# -1 means unset`; a sentence explaining a choice is not an annotation), suppression justifications paired with their waiver, `TODO(#issue)` markers, and @@ -111,7 +114,9 @@ detail): hits by using the code as written, such as a required call order, a precondition, or a required call form a caller would otherwise get wrong. The *reason a value was chosen*, another system's limit, an upstream's behavior, a past incident, is rationale - rather than a warning: it is staged and deleted. Held to `class_c_max_lines` and rewritten terser + rather than a warning: it is staged and deleted. A warning earns its keep only when the failure it + names is **not visible in the adjacent code**: if the body a reader is already looking at shows + the behavior (an `exit` in the function, a guard, a return), the comment restates code and goes; Held to `class_c_max_lines` and rewritten terser when over it. Every survivor must be succinct, clear, and justified in the report: name the consequence, not the history. diff --git a/plugins/code-tidying/skills/dissolve-comments/reference/safety.md b/plugins/code-tidying/skills/dissolve-comments/reference/safety.md index c6227a5c4..3b46f7cef 100644 --- a/plugins/code-tidying/skills/dissolve-comments/reference/safety.md +++ b/plugins/code-tidying/skills/dissolve-comments/reference/safety.md @@ -86,7 +86,10 @@ open the apply path, because they cannot attest behavior preservation. ## Exempt surfaces (never touched, any mode) -- Public-API doc comments: docstrings, C# XML docs, JSDoc/TSDoc on exported/public surfaces. Python +- Public-API doc comments, in the language's structured doc-comment form: docstrings, C# XML docs, + JSDoc/TSDoc, GoDoc sentences, on exported/public surfaces. A language with no doc-comment form, + shell and make among them, has no exempt surface here: a header block there is an ordinary comment + and takes the ordinary triage. Python has no export keyword, so the rule there is the leading underscore: a module docstring, and the docstring of any module, class, function, method, or attribute whose name does not start with an underscore, is public and exempt. A leading underscore marks it private, and a private docstring From 461fb66a21d26dde0b766e57528180003e405342 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 11:17:32 -0400 Subject: [PATCH 10/17] fix(code-tidying): a warning addresses its own declaration's caller A comment explaining why a sibling function exists, or why an interface is shaped as it is, is design rationale and is staged and deleted; a warning belongs to the declaration it sits on. The succinct-keep grader now counts comment lines with content, so a blank comment line between two two-line keeps no longer reads as one oversized block. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KcBf2VNNsVTuiqZ1N5RuCu --- .../evals/real-hook-utils-header-aggressive/case.yaml | 2 +- plugins/code-tidying/skills/dissolve-comments/SKILL.md | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml b/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml index 727b65d79..976844f74 100644 --- a/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml +++ b/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml @@ -69,7 +69,7 @@ graders: target: { source: file, path: hook-utils-header.sh } - name: kept-comment-succinct type: regex - pattern: '(^[ \t]*#(?!!| shellcheck )[^\n]*\n){3}' + pattern: '(^[ \t]*#(?!!| shellcheck )[^\n]{2,}\n){3}' flags: m match: not_contains target: { source: file, path: hook-utils-header.sh } diff --git a/plugins/code-tidying/skills/dissolve-comments/SKILL.md b/plugins/code-tidying/skills/dissolve-comments/SKILL.md index 7c51bdf5a..4e654154a 100644 --- a/plugins/code-tidying/skills/dissolve-comments/SKILL.md +++ b/plugins/code-tidying/skills/dissolve-comments/SKILL.md @@ -116,7 +116,10 @@ detail): value was chosen*, another system's limit, an upstream's behavior, a past incident, is rationale rather than a warning: it is staged and deleted. A warning earns its keep only when the failure it names is **not visible in the adjacent code**: if the body a reader is already looking at shows - the behavior (an `exit` in the function, a guard, a return), the comment restates code and goes; Held to `class_c_max_lines` and rewritten terser + the behavior (an `exit` in the function, a guard, a return), the comment restates code and goes. + A warning belongs to the declaration it sits on and addresses that declaration's caller; a comment + explaining why a *sibling* exists, or why the interface is shaped as it is, is design rationale + and goes; Held to `class_c_max_lines` and rewritten terser when over it. Every survivor must be succinct, clear, and justified in the report: name the consequence, not the history. From f01266b829c78997cb29efcd1b3b390ec094d646 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 11:46:48 -0400 Subject: [PATCH 11/17] test(code-tidying): accept the kill switch's placement note as a keep A required call order is what the doctrine now defines a warning to be, and the note is one succinct line. The case expects it kept within the budget instead of deleted; the owner agreed after the definition landed. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KcBf2VNNsVTuiqZ1N5RuCu --- .../evals/real-hook-utils-header-aggressive/case.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml b/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml index 976844f74..74f580fa6 100644 --- a/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml +++ b/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml @@ -2,7 +2,7 @@ schema_version: "1.1" name: real-hook-utils-header-aggressive description: aggressive on the lib/hook-utils.sh header keeps the directive, a one-line SSOT warning, and a one-line _to calling convention, and deletes the rest tags: [calibration, aggressive, real] -expected_outcome: The shellcheck directive stays on line 1; the SSOT warning and the _to calling convention each survive as one terse line; the library description, fork-cost reasoning, five-printer list, double-sourcing comment, kill-switch comment, no-fleet-switch rationale, and hook::is_enabled comment are deleted with their narrative staged; the guard and both function bodies are unchanged. +expected_outcome: The shellcheck directive stays on line 1; the SSOT warning, the _to calling convention, and the kill switch's required placement each survive within the line budget; the library description, fork-cost reasoning, five-printer list, double-sourcing comment, no-fleet-switch rationale, and the hook::is_enabled design rationale are deleted with their narrative staged; the guard and both function bodies are unchanged. context: scaffold_script: scaffold.sh execution: @@ -50,11 +50,10 @@ graders: flags: i match: not_contains target: { source: file, path: hook-utils-header.sh } - - name: kill-switch-comment-deleted + - name: kill-switch-placement-kept type: regex - pattern: '#[^\n]*(kill switch|CLAUDE_PLUGIN_OPTION_.*_ENABLED|exits 0)' + pattern: '#[^\n]*(place after source|before stdin parsing)' flags: i - match: not_contains target: { source: file, path: hook-utils-header.sh } - name: fleet-switch-rationale-deleted type: regex From 874a4ff9ec49ff4bd781591c85c310389fa9fdc0 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 11:52:47 -0400 Subject: [PATCH 12/17] test(code-tidying): stop the description check from matching an allowed keep The widened anchor matched the word "Sourced" in a kept line, so a correct run failed a deletion check. It now names phrases only the deleted description carried. The plan records passes 2 to 5. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KcBf2VNNsVTuiqZ1N5RuCu --- .../dissolve-comments-aggressive-dial/PLAN.md | 18 ++++++++++++++++++ .../case.yaml | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md index 21c314030..718bae1cb 100644 --- a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md +++ b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md @@ -500,6 +500,24 @@ on the uncommitted rung with `files=0`, where the doctrine correctly stops. Eith expectation narrows to the new non-interactive statement, or `scope-code-files.sh` changes to advance on a rung with no code files, which is a script change this plan put out of scope. +**Pass 2 and the follow-ups (2026-09-15, ~12.3 USD total across passes 2 to 5):** 7 of 8 re-run +cases passed at `--runs 1`. `real-hook-utils-header-aggressive` took three rounds: + +- the run treated the shell header block as an exempt public-API doc comment, so that exemption now + names the language's structured doc-comment form and says a language without one (shell, make) has + no exempt surface there; +- it kept a note explaining why a sibling helper exists, so a warning now has to address its own + declaration's caller; +- the succinct-keep grader counted a `#` spacer between two two-line keeps as one oversized block, + and now counts comment lines with content; +- the case's own expectation changed with the owner's agreement: the kill switch's required + placement is a keep, because a required call order is what the doctrine defines a warning to be. + The original round-3 call predates that definition. + +Final scores: `real-hook-utils-header-aggressive` 0.93, `real-hook-utils-header-strip` 1.00, +everything else at 1.00 except `invented-class-c-aggressive` 0.90 (its landing-place line). Two +grader anchors were widened after a keep collided with a deletion check; both are logged above. + ### Phase 4 remaining work [TODO] 1. Run the full suite in WSL2 with `--runs 3 --threshold 0.8 --max-cost-usd `. diff --git a/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml b/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml index 74f580fa6..a3e81cc96 100644 --- a/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml +++ b/plugins/code-tidying/evals/real-hook-utils-header-aggressive/case.yaml @@ -30,7 +30,7 @@ graders: target: { source: file, path: hook-utils-header.sh } - name: description-deleted type: regex - pattern: '#[^\n]*(hook utility library|Sourced|kill switch, file_path parsing)' + pattern: '#[^\n]*(hook utility library|additionalContext accumulator|telemetry envelope|kill switch, file_path parsing)' flags: i match: not_contains target: { source: file, path: hook-utils-header.sh } From 2a78f0a0f847792a34e78170e1558cd6272ae55c Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:21:53 -0400 Subject: [PATCH 13/17] docs(code-tidying): close the calibration loop for the aggressive dial Every case passes on the final wording, case by case. The owner declined a whole-suite confirmation pass and sent the work to review. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KcBf2VNNsVTuiqZ1N5RuCu --- docs/topics/dissolve-comments-aggressive-dial/PLAN.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md index 718bae1cb..0f5c77ea9 100644 --- a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md +++ b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md @@ -518,7 +518,13 @@ Final scores: `real-hook-utils-header-aggressive` 0.93, `real-hook-utils-header- everything else at 1.00 except `invented-class-c-aggressive` 0.90 (its landing-place line). Two grader anchors were widened after a keep collided with a deletion check; both are logged above. -### Phase 4 remaining work [TODO] +### Phase 4 remaining work [DONE] + +The owner ended the calibration loop here and sent the work to a pull request: every case passed on +the final wording, case by case, at `--runs 1`. A single whole-suite confirmation pass on that +wording (~20 USD) was offered and declined, so no run has yet scored all 23 cases green in one +invocation. The measured comment-line deltas on the real fixtures under `aggressive`: +`hook-utils-header.sh` 46 to 4, `silent-revert-design.sh` 34 to 1, `statusline-stamp.sh` 8 to 1. 1. Run the full suite in WSL2 with `--runs 3 --threshold 0.8 --max-cost-usd `. 2. For each failing case, classify the cause from the `--keep-temp` trace before editing: a denied From 3617acc1f49dcbbab87453ca203eecf7fb5e1f55 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:36:01 -0400 Subject: [PATCH 14/17] docs(code-tidying): prune the aggressive-dial contract slice The topic slice is branch-lived. Its durable outcomes shipped: the doctrine in the skill body and its references, the expectations in the eval suite, the calibration record in the pull request body, and the release note in the changelog. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KcBf2VNNsVTuiqZ1N5RuCu --- .../dissolve-comments-aggressive-dial/PLAN.md | 634 ------------------ .../design/design-resolution.md | 41 -- 2 files changed, 675 deletions(-) delete mode 100644 docs/topics/dissolve-comments-aggressive-dial/PLAN.md delete mode 100644 docs/topics/dissolve-comments-aggressive-dial/design/design-resolution.md diff --git a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md b/docs/topics/dissolve-comments-aggressive-dial/PLAN.md deleted file mode 100644 index 0f5c77ea9..000000000 --- a/docs/topics/dissolve-comments-aggressive-dial/PLAN.md +++ /dev/null @@ -1,634 +0,0 @@ -# dissolve-comments aggressive dial - -## Brief - -### TLDR - -Add an `aggressive` posture and a `strip` argument to `/code-tidying:dissolve-comments` so a run -removes nearly every comment and rewrites the code to carry what it can, without loosening any -proof gate, then calibrate it against real and invented sections through a `claude plugin eval` -suite run in WSL2. - -### Goal - -Four runs of the current skill on this repository removed 2 of about 2,360 comment lines, because -every posture keeps rationale that passes the class-C test. The owner wants a dial that removes all -removable comments and makes the code self-describing for each one removed, plus a mode that -removes all comments except the ones that must survive, and a repeatable way to check the output -matches what the owner expects. - -### Constraints - -- No knob loosens a gate. Deletions carry the COMMENT-ONLY proof; function-local renames carry the - RENAME-ONLY proof; tier-2 and tier-3 moves need a discovered test net covering the touched file; - Introduce Assertion is test-net-only. A syntax check never substitutes for a gate. -- The exempt surfaces in `plugins/code-tidying/skills/dissolve-comments/reference/safety.md` hold in - every mode, including `strip`, and so does a comment paired with a regression test. -- An identifier that appears in a repo-local marker row (for example - `scripts/silent-revert-incidents.txt`) is never renamed. -- A non-interactive run on a widened rung still runs in safe mode. -- `change-shape.py` reads a Python docstring as a string token, so deleting any docstring, private - ones included, reads CODE-CHANGED and cannot apply; under `aggressive`, Python docstring removals - are proposals. -- The survivor list and gate statement live in `SKILL.md`, because a `claude plugin eval` with-arm - cannot read the skill's reference files. -- `claude plugin eval` cannot set plugin `userConfig`, so every behavior the suite measures is - reachable by an argument token. -- The per-skill `evals/evals.json` stays the CI-gated format; skill bodies state current rules only - (`.claude/rules/skill-bodies-state-current-rules.md`); no em dashes in plugin prose. - -### Acceptance criteria - -Format: `free-text` (default; no convention surface resolved). - -- `comment_posture` accepts `aggressive`, and `aggressive [target]` is accepted as a per-run token - that wins over the standing value. -- Under `aggressive`, a comment passing today's class-C test but not on the exempt-surface list is - deleted with its narrative staged, except a load-bearing warning, which is kept within - `class_c_max_lines`; a class-B comment is dissolved when its move's gate passes or kept with a - proposal when it does not. -- `strip [target]` deletes every comment except exempt surfaces, rewrites no code, and certifies - every deletion COMMENT-ONLY. -- `safe` wins over `aggressive` and `strip`; `./aggressive` and `./strip` resolve as paths. -- The staged block carries an `Intentional-removal:` line when the target repository's own scripts - or CI reference that trailer, and the optional `--notes ` appends - the block to an untracked or out-of-repo file and refuses a tracked path. -- IF an `aggressive` or `strip` run targets already-committed code, THEN removals apply and the - report names the next commit touching that code as the block's landing place, as `strict` does - today. -- IF a scoped file is UNPROVABLE against itself, THEN the run names it before the census and every - edit in it is a proposal. -- WHILE the run is non-interactive on a widened rung, it runs in safe mode whatever the posture. -- The "posture ladder only descends" wording is replaced in `SKILL.md`, `reference/safety.md`, - `README.md`, and the `plugin.json` option description, and the tier lists in `safety.md` and - `dissolving-moves.md` agree. -- A `claude plugin eval` suite under `plugins/code-tidying/evals/` runs in WSL2 with `--scaffold` - and `--allow-tools Edit Bash Write`, covering the three frozen real sections, a minimal - `lib/hook-utils.sh` excerpt that is UNPROVABLE (the whole file run once and recorded), one - invented case per triage class, and the `aggressive`/`strip`/`safe` interactions, each graded on - file contents. -- Expected outputs for every calibration case are written with the owner, one section at a time, - before the suite's scores are read as a verdict. - -### Captured assumptions - -- WSL2 distro `Ubuntu-26.04` stays the eval host; it has `claude` 2.1.270, `bwrap`, and `socat`, and - needs the CI-pinned pygments and tree-sitter wheels installed before the first run. -- `claude plugin eval` graders on `{ source: file, path }` read post-edit contents of a scaffolded - file; this is documented but unprobed, and the first case run confirms it. -- The with-arm can execute the skill's `${CLAUDE_SKILL_DIR}/scripts/*` wrappers; unprobed, confirmed - by the first case run. -- Unwanted-behaviour and state-driven coverage were examined: the IF and WHILE criteria above. - -### Out-of-scope - -- A separate command for destructive comment removal; `strip` keeps the exempt-surface floor. -- New proof tooling for tier-2 moves; they stay behind a test net. -- A tree-sitter-bash grammar fix for base-N arithmetic. -- Running the dial across this repository; calibration comes first. - -### Deferred questions - -- Q7 Which side of the Martin versus Ousterhout split `aggressive` takes on internal-interface - comments that are not on the exempt list. arbiter: /planning:plan -- Q8 Whether `evals.json` validation follows skill-creator's `expectations` or `assertions` field - name. arbiter: /planning:plan -- Q9 Under `aggressive`, whether a tier-2 move with no discovered test net applies as a labelled - unproven edit instead of a proposal. This is a risk-appetite call: the Brief defaults to proposal, - which means that on this repository's shell scripts (no covering net, a net over the 600-second - tool cap, or an UNPROVABLE file) `aggressive` deletes rationale and proposes the rewrites. - arbiter: USER-RESERVED - -## Plan - -### Goal - -Ship `aggressive` and `strip` in `/code-tidying:dissolve-comments` 0.20.0 and a `claude plugin eval` -suite whose expected outputs the owner wrote, with the suite passing in WSL2. Design: -[design/design-resolution.md](design/design-resolution.md) (Tier B early-exit, argument grammar -and mode table). - -### Resolved deferred questions - -- **Q7 (arbiter /planning:plan, owner agreed).** `aggressive` takes Martin's side on - internal-interface comments: a non-exempt comment describing a private function's contract is - deleted with its narrative staged. Public-API doc comments stay exempt under the - leading-underscore rule. Basis: the owner asked for "ALL or most ALL comments" removed, and Q1 - keeps only the exempt surfaces and load-bearing warnings. -- **Q8 (arbiter /planning:plan, owner agreed).** New `evals.json` entries use `expectations`. - Basis: all 15 existing entries use it, and `plugins/skill-quality/scripts/check-evals-quality.sh` - treats `expectations` and `assertions` as equivalent and warns when a case carries both. -- **Q9 (USER-RESERVED, owner agreed to the default).** A tier-2 move with no discovered test net is - a proposal. - -### Brief amendments (owner approved 2026-09-14, applied to the Brief) - -Stress-test findings that changed a Brief line. - -1. **Whole-file case becomes a recorded manual run.** The suite's UNPROVABLE case uses the smallest - `lib/hook-utils.sh` excerpt that still exits 21 from `change-shape.sh` (a function using - `10#$var`); the whole 4,086-line file is run once with `--runs 1` and its result recorded in this - file. Basis: an UNPROVABLE file still gets full triage and a `git log -L` per rationale comment - (`SKILL.md:187-214`), 3 runs times up to 1,800 s each, and the fixture would ship inside the - plugin. -2. **`Intentional-removal:` only where the target repository reads it.** The staged block carries - that line when the target repository's own scripts or CI reference the trailer (a grep over the - repository), and omits it otherwise. Basis: it is this repository's own trailer - (`scripts/check-silent-revert.sh:445`), and `code-tidying` is installed in other repositories. -3. **Paired records survive every mode.** A comment that is one half of a comment-plus-regression- - test pair is kept under `aggressive` and `strip`. Basis: `reference/safety.md:224-227` calls - deleting half a paired record a correctness bug, which no mode may loosen under "no knob loosens a - gate". -4. **The eval grant adds `Write`.** `--allow-tools Edit Bash Write`, because `--notes` creates a - file and `Edit` cannot create one (`case-authoring.md:82`). - -### Standards grounding - -Loaded for the surfaces touched: `.claude/rules/skill-bodies-state-current-rules.md` (verification -records, `## Next` placement before `## Gotchas`), `.claude/rules/pr-body-contract.md`, root -`AGENTS.md` (draft PRs), `plugins/evals/skills/plugin-eval/SKILL.md` and -`reference/case-authoring.md`, `reference/reading-results.md` (case layout, graders, sandbox, -results JSON), `docs/specs/plugin-evals-pilot-measurement.md` (with-arm cannot read spokes). No -consumer standards index beyond these. - -### Test strategy - -The skill is prose, so its tests are eval cases, written red first. - -- **Boundary driven:** the existing public interface, the invocation - `/code-tidying:dissolve-comments [tokens] `, run through `claude plugin eval` against the - working-tree plugin at `plugins/code-tidying`. No new interface is introduced for testability. -- **Red:** Phase 2 runs the suite against the unchanged skill. Each case lists its expected-red - graders (the ones encoding behavior new to `aggressive` or `strip`); graders encoding today's - behavior (class-A deletion, exempt surfaces) are expected to pass red and are recorded as such. -- **Green:** Phase 4 re-runs the suite after the Phase 3 skill change with `--threshold 0.8`. -- **Graders per case:** - - survivors: `regex` `contains` on `{ source: file, path }`, anchored to comment syntax around an - owner-chosen key phrase (`#[^\n]*`), so a terser rewrite of a kept warning still passes; - - deletions: `regex` `not_contains` on the same anchored form; - - code preserved: one `regex` `contains` per non-comment code line, or a `count:N` over them, so - deleted or rewritten code fails the case (for `strip`, "rewrites no code"); - - process: `tool_used` on `Bash` with `input_match: change-shape`, `arm: both`, so a run that skips - the proof scores lower; - - UNPROVABLE case: `tool_order` with `change-shape` before `comment-census`. -- **Delta caveat:** in the without-arm the slash command does not resolve, so the delta partly - measures whether the command exists. Read pass rates in the with-arm as the calibration verdict. -- **Static checks in CI:** `check-evals-quality.sh` over `evals.json`, `sync-plugin-options-docs.py - --check`, `allowed-tools-pairing.test.sh`, `check-purged-em-dashes.sh`. The paid suite does not - run in CI. - -### Phase 1: Eval harness tracer in WSL2 [DONE] - -Proves the runner mechanics every later phase depends on, against the unchanged skill. All WSL2 -commands run in the main checkout at `/mnt/d/repos/github.com/melodic-software/claude-code-plugins` -on the task branch, never in a worktree, because WSL git cannot read a worktree `.git` file that -holds a `D:/` path. `$TEMP` is unset in WSL2; WSL2 commands use `${TMPDIR:-/tmp}`. - -1. Create branch `feat/dissolve-comments-aggressive-dial` from `main`; commit `PLAN.md` and - `design/design-resolution.md`. -2. WSL2 tooling `[EXEC-SHAPE]`: `uv venv ~/.venvs/code-tidying --python 3.14` then `uv pip install - --python ~/.venvs/code-tidying/bin/python --require-hashes -r .github/requirements-ci.txt`, and - launch every `claude plugin eval` with `~/.venvs/code-tidying/bin` first on `PATH`. The skill - wrappers exec `python3` from `PATH` (`skills/dissolve-comments/scripts/change-shape.sh:17`), and - the distro's current `python3` is uv's CPython 3.14 at `~/.local/bin`. -3. Author `plugins/code-tidying/evals/probe-environment/case.yaml`: prompt `Run: command -v python3; - python3 -c 'import pygments, tree_sitter, tree_sitter_bash'; command -v make; echo "$PATH"`, - `execution: { allowed_tools: [Bash], max_turns: 5, timeout_seconds: 120 }`, graders `regex` on - `trace` for `tree_sitter_bash` import success. No skill involved. -4. Author `plugins/code-tidying/evals/probe-explicit-target/`: `case.yaml` with - `context.scaffold_script` that copies `fixture/app.sh.txt` (from `$(dirname "$0")`) to `app.sh`, - runs `git init`, and commits with `-c user.name=eval -c user.email=eval@example.invalid` and a - commit message carrying no rationale. `app.sh` holds one class-A comment `# increment counter` - above `counter=$((counter + 1))` and one `# shellcheck disable=SC2034` directive. Prompt: - `/code-tidying:dissolve-comments app.sh`. `execution: { allowed_tools: [Read, Glob, Grep, Skill, - Bash, Edit, Write], max_turns: 80, timeout_seconds: 1200 }`. Graders per the test strategy. -5. Run both probes in WSL2: `claude plugin eval plugins/code-tidying --case 'probe-*' --scaffold - --allow-tools Edit Bash Write --trust-plugin --runs 1 --ablation none --keep-temp --no-publish - --json "${TMPDIR:-/tmp}/dc-probe.json"`. From the result and the kept traces, record in this file: - the `python3` the run resolved and whether the imports succeeded; whether the slash invocation - fired the skill; whether `change-shape.sh` executed or was denied; whether the file-content - graders read the post-edit file; whether the scaffold resolved its own directory. -6. **Stop conditions** `[FALLBACK — confirm or override]`: - - the run cannot import the wheels, or `change-shape.sh` is denied in the with-arm: stop, record - the evidence here, and return to `/planning:plan review` before Phase 2; - - the slash invocation does not fire the skill: prompts become `Use the Skill tool with skill - code-tidying:dissolve-comments and args " "`, which keeps token parsing; - - the scaffold cannot resolve its own directory: each scaffold embeds its fixture as a quoted - heredoc. - -**Phase 1 record (2026-09-14):** - -- Wheels installed into `~/.venvs/code-tidying` with `uv pip install --require-hashes`; imports of - pygments, tree_sitter, tree_sitter_bash, tree_sitter_python, tree_sitter_toml succeed. -- First eval attempt refused every Bash-granting run before turn 1: `~/.docker` in the distro holds - two symlinks into `/mnt/c/Users/.../.docker` (Docker Desktop WSL integration), and the sandbox - refuses a credential store containing a link. `DOCKER_CONFIG` pointing at a plain directory did - not clear it. -- Workaround, no change to the owner's files: run `claude plugin eval` with - `HOME=/var/tmp/dc-evalhome`, a directory holding only symlinks to `~/.claude`, `~/.claude.json`, - and `~/.config`. Every WSL2 eval command in this plan runs that way. WSL2 scratch paths use - `/var/tmp`, because this repository's drive-root temp guard rejects `/tmp` in a command string. -- `probe-environment` passed under that HOME (2 turns, 0.17 USD): the sandboxed Bash resolved a - `python3` that imports the wheels. -- `probe-explicit-target` passed 4 of 4 graders (15 turns, 107 s, 0.54 USD). Confirmed from the - trace and the kept workspace: the slash invocation fired the skill; `change-shape.sh` executed in - the with-arm with tree-sitter and returned `COMMENT-ONLY`; `comment-census.sh` ran; the - `{ source: file }` graders read the post-edit `app.sh` (the class-A comment present at scaffold - time was absent); the scaffold resolved its own directory and copied the fixture, so the heredoc - fallback is not needed and was removed. No stop condition fired. -- Sanity results: `validate-cases.py` exit 0 (WARN lines only, for tools granted by - `--allow-tools`); the grader `jq` check printed `true`. - -**Sanity Check:** - -- `python3 plugins/evals/skills/validate/scripts/validate-cases.py plugins/code-tidying/evals` exits 0. -- `jq '[.cases[].arms.with[0].graders[] | .passed] | all' /var/tmp/dc-probe.json` prints - `true`, or this file records the failing grader and the stop condition taken. -- `git log --oneline main..HEAD -- docs/topics/dissolve-comments-aggressive-dial/PLAN.md` lists at - least one commit. - -### Phase 2: Expected outputs with the owner, then the red run [DONE] - -Main session, interactive, one fixture per round. Fixtures are committed with a `.txt` suffix -(`app.sh.txt`, `mod.py.txt`) and renamed by the scaffold, so CI shellcheck, ruff, and the -comment-hygiene scan do not lint deliberately commented fixtures. - -1. Freeze the real sections at commit `0a676a578` as fixture copies, each a self-parsing block: - `plugins/rate-limit-guard/scripts/statusline-tee.sh` lines 308-324 (`_rlg_read_stamp` and its - header comment); the `WHY BLAME-OF-DELETED-LINES, AND NOT THE ALTERNATIVES` comment block in - `scripts/check-silent-revert.sh` with the function that follows it; the `lib/hook-utils.sh` - header through its calling-convention block plus one helper, and separately the smallest - `lib/hook-utils.sh` excerpt that exits 21 (amendment 1). -2. Baseline `[EXEC-SHAPE]`: `comment-census.sh --json` over each real fixture; record each file's - comment-line count here as the before value. -3. Write invented fixtures: - - class A: a restating comment and commented-out code; - - class B: a magic literal and a vague local name, with a `Makefile` `test:` target the scaffold - writes, so the skill's test-net discovery finds a net; - - class C: a load-bearing warning, a rationale comment whose text is absent from git history, and - a rationale comment the scaffold's commit message does repeat; - - exempt surfaces: license header, `# noqa` with justification, `TODO(#12)`, - `dissolve-comments-ignore`; - - marker row: a repo-local marker file listing an identifier that a comment would otherwise - rename; - - Python: a private function with a docstring (expected proposal, not applied); - - paired record: a comment paired with a regression test (amendment 3). -4. For each fixture, show it to the owner with the doctrine's prediction under `aggressive` and - under `strip`; the owner marks every comment keep, delete, or dissolve and picks the key phrase - each grader anchors on. Record the marks as graders plus `expected_outcome`, and list the case's - expected-red graders in this file. `/planning:interview` round format. -5. Author the interaction and criterion cases on the class-C fixture unless noted: - - `aggressive`, `strip`, `safe aggressive`, `safe strip` (both expect class-A deletions only); - - `./aggressive` as a target (scaffold creates a directory named `aggressive`; expects it triaged - as a path under the default posture); - - `--notes notes/dc-notes.md` with `notes/` untracked: `file_exists` plus `regex` on `{ source: - file, path: notes/dc-notes.md }` for the staged block; - - `--notes` on a tracked path: expects a refusal in `last_message` and the file unchanged; - - committed-code landing place: `last_message` regex for the next-commit landing statement; - - WHILE criterion: scaffold with a clean tree and history, prompt `/code-tidying:dissolve-comments - aggressive` with no target; expects no class-C deletion applied (safe mode); - - the standing `comment_posture: aggressive` route goes to `evals.json`, because eval runs cannot - set `userConfig`. -6. Cost gate: print the suite estimate per `plugins/evals/skills/plugin-eval/SKILL.md` "cost - estimate"; the owner sets `--max-cost-usd` before the red run (user-approval gate). -7. Red run: full suite, `--runs 1`, against the unchanged skill. Record per case which graders - passed and whether that matches its expected-red list. -8. Whole-file record (amendment 1): one `--runs 1` run on all of `lib/hook-utils.sh`; record the - outcome, turns, and cost here. - -**Phase 2 record:** - -- Owner rule (round 1, applies to every mode and every case): a comment that is kept must be - succinct, clear, and justified. Graders enforce it as no kept comment block longer than - `class_c_max_lines` (2) plus a key phrase naming the consequence; Phase 3 writes the rule into - `SKILL.md`. -- Shared fixtures live in `plugins/code-tidying/evals/fixtures/`, seeded by `fixtures/seed.sh`, - which each case's one-line `scaffold.sh` calls. -- Round 1, `statusline-tee.sh` lines 308-324 (`real-statusline-stamp-aggressive`, - `real-statusline-stamp-strip`): - - | Part | `aggressive` | `strip` | - |---|---|---| - | (a) contract: reads a stamp or 0 | delete, staged | delete, staged | - | (b) arithmetic-injection warning | keep, at most 2 lines | delete, staged | - | (c) single-reader rationale | delete, staged | delete, staged | - | (d) builtins-only render-path constraint | delete, staged | delete, staged | - - Expected red on the unchanged skill: `aggressive` graders `contract-deleted`, - `single-reader-rationale-deleted`, `render-path-deleted`; `strip` graders `no-comments-left`, - `narrative-staged`. -- Round 2, `scripts/check-silent-revert.sh` lines 73-106 plus `die()` from lines 368-371 - (`real-silent-revert-design-aggressive`, `real-silent-revert-design-strip`): the heading, the - three rejected designs, and the measured result are deleted and staged in both modes; the - no-threshold warning is kept at most 2 lines under `aggressive` and deleted under `strip`. No - sentence is a paired record. Expected red: `aggressive` `design-history-deleted`; `strip` - `no-comments-left`, `narrative-staged`. -- Owner restatement (round 2): the default answer is delete, or refactor the code until the comment - is unnecessary; every survivor must be justified in the report. -- Rounds 3-9, approved by the owner as one batch (2026-09-14), with the note that a kept line must be - as succinct as the round-3 SSOT line (`# SSOT: edit lib/hook-utils.sh, then run - scripts/sync-hook-utils.sh; CI rejects drifted copies.`): - - | # | Fixture | `aggressive` | `strip` | - |---|---|---|---| - | 3 | `lib/hook-utils.sh` lines 1-59 | keep the one-line SSOT warning and a one-line `_to` calling convention; delete the rest, including the `hook::is_enabled` comment | delete all but the `shellcheck shell=bash` directive | - | 4 | class A: restating comment, commented-out code | delete | delete | - | 5 | class B: `86400` literal, vague `n`, `Makefile` test net | `SECONDS_PER_DAY`, `display_name`, comments deleted | comments deleted, no code change | - | 6 | class C: warning, rationale absent from history, rationale in the commit message, one class-A line | warning kept at most 2 lines; the rest deleted and staged; report names the next commit | delete all | - | 7 | exempt: shebang, license, `# noqa` with reason, `TODO(#12)`, `dissolve-comments-ignore`, one rationale | exempt kept, rationale deleted | same | - | 8 | marker-row identifier, private Python docstring, paired record | comment deleted and identifier never renamed; docstring proposed; paired record kept | same | - | 9 | interactions on the class-C fixture: `safe aggressive`, `safe strip`, `./aggressive`, `--notes` untracked and tracked, non-interactive run with no target; UNPROVABLE `hook-utils.sh` excerpt | `safe` wins; `./aggressive` is a path; untracked notes written, tracked refused; no-target run is safe mode; UNPROVABLE named first, proposals only | same | - -- Cases authored for rounds 3-9: 19 case directories and 15 fixtures, 25 cases in the suite with the - probes. Verified: `validate-cases.py` exit 0; real fixtures byte-identical to their `0a676a578` - slices; every `.sh.txt` fixture passes `bash -n` and self-certifies COMMENT-ONLY, except - `hook-utils-unprovable.sh.txt` (`hook::resolve_read_timeout_to`, lines 1570-1593), which exits 21; - both `.py.txt` fixtures compile and self-certify; the class-B test net passes under `make test` in - WSL2; every comment-deletion `not_contains` grader matches the unedited fixture (red before); - scaffolds pass shellcheck and carry mode 100755. -- Grader decisions: the succinct grader is `(^[ \t]*#(?!!| shellcheck )[^\n]*\n){3}` with `m`, - because JS `\s` spans newlines and the approved hook-utils result is a directive plus two kept - lines; `fork-cost-deleted` anchors on `CreateProcess|copy-on-write|forks a subshell` so a kept - `_to` line may mention forks; `interaction-notes-tracked-refused` has no `proof-ran` grader, - because a refusal before triage is a correct outcome. -- Baseline (`comment-census.sh --json`, before value): `hook-utils-header.sh` 46 of 59 lines are - comment; `silent-revert-design.sh` 34 of 39; `statusline-stamp.sh` 8 of 17; 88 of 115 in total. -- Red run skipped on the owner's decision (2026-09-15): nearly every case reaches its behavior by an - argument token the unchanged skill reads as a path, so the run would have re-measured a known - outcome at 12 to 30 USD. The expected-red lists above stand as the record of what the unchanged - skill does, and Phase 4's run is the first paid pass. -- The branch moved to the worktree `D:/worktrees/ccp-dissolve-dial` after another session switched - the main checkout back to `main`. WSL2 eval commands run from - `/mnt/d/worktrees/ccp-dissolve-dial`. - -**Sanity Check:** - -- `validate-cases.py plugins/code-tidying/evals` exits 0. -- `for c in plugins/code-tidying/evals/*/; do case "$c" in *probe-*) continue;; esac; grep -rqs - "source: file" "$c" || echo "$c"; done` prints nothing. -- For each shell fixture `f` in `plugins/code-tidying/evals/*/fixture/*.sh.txt` other than - `Makefile`-target test scripts, `bash -n "$f"` exits 0 and - `plugins/code-tidying/skills/dissolve-comments/scripts/change-shape.sh "$f" "$f"` exits 0, except - the UNPROVABLE excerpt, which exits 21. (Check whether `change-shape.py` maps `.txt`; if not, copy - to a `.sh` name under `${TMPDIR:-/tmp}` first and record that in this command.) -- `git ls-files plugins/code-tidying/evals | grep -E '\.(sh|py|bash)$'` lists only scaffold scripts. -- Every case's expected-red list in this file matches its red-run record. - -### Phase 3: Skill change [DONE] - -1. Consumer and claim sweep first: `grep -rn "comment_posture\|only descends\|narrows\|widened\|loosen - nothing\|ceiling\|of 15" plugins/code-tidying docs scripts`; record every hit and its disposition - here before editing. Known hits: `SKILL.md:39-40, 88-92, 107`, `reference/safety.md:58-61`, - `reference/triage.md:84-96`, `reference/scope.md:29-30`, `reference/dissolving-moves.md:57, 61`, - `README.md:129-136`. -2. Per-mode treatment of the hub rules that `aggressive` and `strip` touch, written into - `SKILL.md` so the with-arm sees them: - - | Rule | Where | `aggressive` | `strip` | - |---|---|---|---| - | Information moves into code before a class-B comment goes | `SKILL.md:114-117` | holds | exception: deleted with narrative staged | - | Class B misread as A is the information-destroying failure | `SKILL.md:255-256`, `triage.md:43-44` | holds | narrative staged for every deletion | - | Staging with no landing place is not a deletion licence | `safety.md:206-209` | holds (next commit is the landing place) | holds | - | Paired comment-plus-regression-test record | `safety.md:224-227` | kept | kept | - | Shape used at scale is proposed | `SKILL.md:140-141` | holds | holds | - | SSOT copy never edited; source edit forcing consumer bumps proposed | `SKILL.md:165-169` | holds | holds | - | Marker-row identifier never renamed | new, workflow step 2 | holds | not applicable (no renames) | - | Exempt surfaces | `SKILL.md:125-130` | hold | hold | - -3. `skills/dissolve-comments/SKILL.md`: - - frontmatter `description` names `aggressive` and `strip`; `argument-hint` becomes - `[safe] [aggressive|strip] [override] [--notes ] [target]`; - - `allowed-tools` adds `Bash(git ls-files:*)` `[EXEC-SHAPE]` for the tracked-path refusal; - - Variables: posture accepts `aggressive`; - - action router rows for `aggressive [target]`, `strip [target]`, `--notes `, and the - precedence from the design resolution; - - the "posture ladder only descends" paragraph becomes "no knob loosens a gate", the survivor - list (exempt surfaces, load-bearing warnings within `class_c_max_lines`, paired records), and - the gate statement; - - triage table and workflow steps 1, 2, 6, and 7 carry the `aggressive` and `strip` branches, - the table in item 2, the conditional `Intentional-removal:` line (amendment 2), and the Python - docstring ceiling; - - doubt rule `[EXEC-SHAPE]`: under `aggressive`, doubt between A and B resolves to B, and doubt - between B and C resolves to B (dissolve when the gate passes, else keep with a proposal); under - `strip`, every doubted deletion stages its narrative; under both, doubt whether a comment is an - exempt surface, a load-bearing warning, or a paired record keeps it; - - "What this skill is NOT" first bullet names `strip` and its exempt-surface floor; - - `## Next` naming `/source-control:commit`, placed before `## Gotchas`. -4. `reference/safety.md`: mode ladder rows for `aggressive` and `strip`; the knob paragraph at lines - 58-61; the staging section gains the conditional `Intentional-removal:` line and `--notes`; tier - table reconciled with `reference/dissolving-moves.md` `[EXEC-SHAPE]` to the union of both, tier - by tier (tier 2 adds Replace Nested Conditional with Guard Clauses and Introduce Special Case; - tier 3 adds Inline Function; `dissolving-moves.md` tier 3 adds Extract Class). -5. The "of 15" counts at `SKILL.md:107` and `reference/dissolving-moves.md:57, 61` are recounted - against the merged tables. -6. `reference/triage.md:84-96` and `reference/scope.md:29-30` name the new modes. -7. `plugin.json`: `comment_posture` description names `aggressive`; top-level description names - `strip`; version `0.20.0`. Then `python3 scripts/sync-plugin-options-docs.py` regenerates the - README options block; hand-edit `README.md:129-136`. -8. `CHANGELOG.md` `## [0.20.0]` entry. -9. `evals/evals.json`: entries 16 (`aggressive-deletes-non-exempt-rationale-with-staging`), 17 - (`strip-keeps-exempt-surfaces-and-rewrites-nothing`), 18 - (`standing-aggressive-posture-yields-to-safe`), each `narration: true` with `expectations`. - -**Phase 3 record:** - -- Item 1 sweep (run before the red run; read-only). Hits to edit: `plugin.json:30` - (`comment_posture`), `README.md:129, 136, 160`, `SKILL.md:39, 88, 107, 152`, - `reference/triage.md:95`, `reference/scope.md:30`, `reference/dissolving-moves.md:57, 61`. Hits - kept as they are: `CHANGELOG.md` history entries; `evals/evals.json:151, 163` (posture narration - cases, still valid); every hit outside `plugins/code-tidying/skills/dissolve-comments`, - `README.md`, and `plugin.json` uses "narrows" or "widened" in an unrelated sense. -- Edits landed: `SKILL.md` (dial rows, `--notes`, survivor list, no-knob-loosens-a-gate wording, - doubt rule, paired-record and marker-row rules, docstring ceiling, per-mode branches in steps 1, - 5, 6 and 7, `## Next`), `reference/safety.md` (mode-ladder rows, knob paragraph, staging section, - tier table), `reference/dissolving-moves.md` (tier table, counts), `reference/triage.md`, - `reference/scope.md`, `plugin.json` (posture value, description, 0.20.0), `README.md` (regenerated - options block plus hand-written prose), `CHANGELOG.md`, `evals/evals.json` (entries 16 to 18), - `docs/catalog.md` (regenerated). -- The skill's `description` was over the 1024-codepoint Agent Skills maximum once the dials were - named (1199), and was on `main` at 978 with only 46 codepoints of headroom. It is now 1016 with - every base trigger phrase preserved; `check-skill` warns that the next added clause breaches the - limit. -- Gates: `check-skill` PASS (1 warning, the headroom note), `sync-plugin-options-docs.py --check` - exit 0, `check-evals-quality.sh` PASS, `allowed-tools-pairing.test.sh` exit 0, - `check-purged-em-dashes.sh` exit 0, tier tables agree tier by tier. - -**Sanity Check:** - -- `grep -rn "only descends" plugins/code-tidying --exclude=CHANGELOG.md` prints nothing (the - changelog quotes the retired wording on purpose), and every hit recorded in item 1 - has a disposition line in this file. -- `grep -n '^## ' plugins/code-tidying/skills/dissolve-comments/SKILL.md` shows `## Next` - immediately before `## Gotchas`. -- Tier tables match tier by tier. With `d=plugins/code-tidying/skills/dissolve-comments/reference` - and `p='Rename Variable|Rename Field|Extract Variable|Replace Magic Literal|Introduce Assertion|Slide Statements|Decompose Conditional|Replace Nested Conditional with Guard Clauses|Introduce Special Case|Extract Function|Change Function Declaration|Extract Class|Introduce Parameter Object|Move Statements into Function|Replace Inline Code with Function Call|Inline Function'`, - `for t in 1 2 3; do diff <(grep -E "^\| \*\*$t\*\*" $d/safety.md | cut -d'|' -f3 | grep -oE "$p" | sort -u) <(grep -E "^\| $t," $d/dissolving-moves.md | cut -d'|' -f3 | grep -oE "$p" | sort -u) || echo "tier $t differs"; done` - prints nothing. On `main` today it prints tier 2 and tier 3 differences (run 2026-09-14). -- `python3 scripts/sync-plugin-options-docs.py --check`, `bash - plugins/skill-quality/scripts/check-evals-quality.sh - plugins/code-tidying/skills/dissolve-comments/evals/evals.json`, `bash - plugins/code-tidying/scripts/allowed-tools-pairing.test.sh`, and `scripts/check-purged-em-dashes.sh` - each exit 0. -- `jq -r .version plugins/code-tidying/.claude-plugin/plugin.json` prints `0.20.0`. - -### Phase 4: Green run, calibration loop, PR [DOING] - -**Pass 1 record (2026-09-15, `--runs 1 --ablation none --max-cost-usd 20`, 20.16 USD):** 21 of 23 -cases ran before the ceiling stopped the run; 17 passed, overall score 0.92. The two -`real-statusline-stamp-*` cases never started. - -Causes, classified from the traces and the kept workspaces: - -| Case | Grader | Cause | Fix | -|---|---|---|---| -| `invented-class-c-aggressive` | `timeout-rationale-deleted` | skill wording: "warning of consequence" was undefined, and the load-balancer rationale read as both a warning and a boundary-semantics exempt surface | `SKILL.md` defines the term and excludes the reason a value was chosen | -| `invented-class-c-strip` | `no-comments-left` | same, plus the exempt bullet covered whole sentences | `safety.md` and `SKILL.md` narrow units, sentinels, ownership, thread-safety and ordering to annotations on the adjacent declaration | -| `real-hook-utils-header-aggressive` | `kept-comment-succinct`, `code-intact` | the run relocated a kept comment and wrote a new one, which no rule forbade | `SKILL.md` step 6 adds: a kept comment stays where it is and keeps its own words | -| `interaction-non-interactive-no-target` | `safe-mode-named`, `proof-ran` | the skill had no way to detect a non-interactive session, and the scaffold's untracked files keep the ladder on the uncommitted rung | `SKILL.md` step 1 ties non-interactive to `AskUserQuestion` being unavailable; the case's own expectation is still open (below) | -| `invented-class-b-aggressive` | `magic-literal-named` | grader anchoring: the run named the constant `seconds_per_day`, correct shell style for a `local` | pattern lowercased with `flags: i` | -| `invented-class-c-aggressive` | `no-intentional-removal-trailer` | grader anchoring: the report said it emitted no such trailer, and the bare token matched that sentence | pattern anchored to line start with `flags: m` | -| `real-silent-revert-design-strip` | `narrative-staged` | not a defect: the judge was skipped when the cost ceiling hit | re-run under a higher ceiling | - -Grader changes and their reasons (anchoring only, per the phase rule): the three above, plus three -deletion anchors in `real-hook-utils-header-aggressive` (`description-deleted`, -`kill-switch-comment-deleted`, `is-enabled-comment-deleted`) widened to catch a reworded keep. They -had passed while the comment was kept in different words, which is a false pass. - -**Open for the owner:** `interaction-non-interactive-no-target` cannot exercise the widened-rung -rule as written, because any untracked file (the harness writes `.claude/`) keeps the scope ladder -on the uncommitted rung with `files=0`, where the doctrine correctly stops. Either the case's -expectation narrows to the new non-interactive statement, or `scope-code-files.sh` changes to -advance on a rung with no code files, which is a script change this plan put out of scope. - -**Pass 2 and the follow-ups (2026-09-15, ~12.3 USD total across passes 2 to 5):** 7 of 8 re-run -cases passed at `--runs 1`. `real-hook-utils-header-aggressive` took three rounds: - -- the run treated the shell header block as an exempt public-API doc comment, so that exemption now - names the language's structured doc-comment form and says a language without one (shell, make) has - no exempt surface there; -- it kept a note explaining why a sibling helper exists, so a warning now has to address its own - declaration's caller; -- the succinct-keep grader counted a `#` spacer between two two-line keeps as one oversized block, - and now counts comment lines with content; -- the case's own expectation changed with the owner's agreement: the kill switch's required - placement is a keep, because a required call order is what the doctrine defines a warning to be. - The original round-3 call predates that definition. - -Final scores: `real-hook-utils-header-aggressive` 0.93, `real-hook-utils-header-strip` 1.00, -everything else at 1.00 except `invented-class-c-aggressive` 0.90 (its landing-place line). Two -grader anchors were widened after a keep collided with a deletion check; both are logged above. - -### Phase 4 remaining work [DONE] - -The owner ended the calibration loop here and sent the work to a pull request: every case passed on -the final wording, case by case, at `--runs 1`. A single whole-suite confirmation pass on that -wording (~20 USD) was offered and declined, so no run has yet scored all 23 cases green in one -invocation. The measured comment-line deltas on the real fixtures under `aggressive`: -`hook-utils-header.sh` 46 to 4, `silent-revert-design.sh` 34 to 1, `statusline-stamp.sh` 8 to 1. - -1. Run the full suite in WSL2 with `--runs 3 --threshold 0.8 --max-cost-usd `. -2. For each failing case, classify the cause from the `--keep-temp` trace before editing: a denied - `reference/` read (the rule belongs in `SKILL.md`), doctrine wording, or grader anchoring. Fix the - skill for the first two. A grader change is allowed only for anchoring (the key phrase or its - comment-syntax anchor) or with the owner's agreement in session; log each grader change here with - its reason. -3. Re-run `comment-census.sh --json` on each real fixture's post-run workspace and record comment - lines before and after beside the Phase 2 baseline, distilled. -4. Commit, open a draft PR with the body contract (`No related issue: ` or `Closes #`, - Summary, Fix, Verification, Related), run the Phase 3 static checks plus - `plugins/code-tidying/scripts/*.test.sh`, then mark ready. - -**Sanity Check:** - -- The suite command in item 1 exits 0. -- `gh pr view --json isDraft,body -q '.isDraft, (.body | test("## Verification"))'` prints `false` - and `true` after the flip. - -## Decisions made (gate-passed) - -| Decision | What it changes in the plan | Basis (evidence) | -|---|---|---| -| WSL2 wheels go into a uv venv put first on `PATH` | Phase 1 item 2; no system-wide install, no sudo | WSL2 `python3` is uv CPython 3.14 at `~/.local/bin`; `/usr/bin/python3` has no pip (probed); wrappers exec `python3` from `PATH` | -| Probe failures stop the work and return to planning; a slash-command failure switches prompts to an explicit Skill-tool instruction; an unresolvable scaffold directory switches fixtures to heredocs | Phase 1 item 6 | Pilot record: with-arm reads under the plugin directory were denied (`docs/specs/plugin-evals-pilot-measurement.md`) | -| Doubt under `aggressive` resolves A/B and B/C to B; under `strip` every doubted deletion stages narrative; doubt about exempt, warning, or paired record keeps | Phase 3 item 3, `SKILL.md` doubt paragraph | Brief AC: class B kept with a proposal when its gate fails; `SKILL.md:255-256` B-misread-as-A is the information-destroying failure | -| Tier tables merge to the union, tier by tier | Phase 3 item 4; `safety.md` gains three moves, `dissolving-moves.md` gains Extract Class | Tier comparison command output above (tier 2 and 3 differ today) | -| `Bash(git ls-files:*)` added to the skill's `allowed-tools` | Phase 3 item 3 | `--notes` must refuse a tracked path; `git ls-files --error-unmatch` answers that | -| `## Next` names `/source-control:commit`, before `## Gotchas` | Phase 3 item 3 | `.claude/rules/skill-bodies-state-current-rules.md`; the staged block is that skill's input (`SKILL.md:116`) | -| Fixtures committed with a `.txt` suffix | Phase 2 preamble | CI shellcheck lints changed `*.sh` (`ci.yml:381-386`); comment-hygiene scan excludes only audit-comment-residue | -| Baseline is a census over the real fixtures | Phase 2 item 2 | Brief goal is a measured comment-line count | -| Sequential, main-session execution | Execution shape | Phase 2 and 4 need the owner; Phase 3 files interlock | - -## Blast radius - -MEDIUM. One plugin, about 12 tracked files plus the new eval suite, reversible by revert. It still -triggers a stress-test: the change rewrites agent instructions for a skill that deletes text, and -it reverses a documented invariant. - -## Stress-test summary - -Two fresh-context reviewers ran on the first draft: a plan reviewer (1 critical, 13 important, 4 -suggestions) and `/planning:devils-advocate` (1 critical, 5 high, 6 medium, 2 low). Findings were -checked against the files before applying. Confirmed and applied: - -- Hub rules left contradicting `strip` and `aggressive` now have a per-mode table (Phase 3 item 2). -- The doubt rule names which class wins. -- `$TEMP` is unset in WSL2 (probed); the notes case moved into the workspace with a `Write` grant. -- The sudo fallback was dropped. WSL2 `python3` is uv's CPython at `~/.local/bin` (probed); a uv - venv plus an environment probe case replaces it. -- A denied script in the with-arm is a hard stop (the pilot recorded denied plugin-directory reads). -- Code-preservation graders were added and the proof grader set to `arm: both`. -- The red run lists expected-red graders per case. -- Graders anchor on key phrases. -- The whole-file case became a manual record (amendment 1), and a cost gate precedes the red run. -- Each uncovered acceptance criterion got a case. -- Fixtures carry a `.txt` suffix. -- A slash-command fallback was added. -- `case.yaml` uses `execution:` nesting. -- The results `jq` path was corrected. -- Repo-relative script paths were fixed. -- The baseline moved to Phase 2. -- A Makefile test net was added (`make` present in WSL2, `bats` absent). -- The claim sweep was widened. -- The "of 15" counts were added. -- The `## Next` order check was added. -- `Intentional-removal:` became conditional (amendment 2). -- WSL runs use the main checkout. - -Brief AC line 46 was corrected to match Q1 (load-bearing warnings kept). - -## Execution shape - -Fully sequential, all main session. Phase 1 gates Phase 2 (runner mechanics), Phase 2 gates Phase 3 -(red cases exist first), Phase 3 gates Phase 4. Phase 2 is an owner interview and Phase 4 a -calibration loop, so neither suits parallel workers, and Phase 3's files interlock (hub rules, -references, tier tables). - -| Phase | Surface | Basis | -|---|---|---| -| 1 | main session | WSL2 probing with stop conditions that re-plan | -| 2 | main session | owner marks every expected output | -| 3 | main session | interlocking prose edits to one skill | -| 4 | main session | failure classification and owner-gated grader changes | - -## Open questions - -None. Plan and Brief amendments approved by the owner 2026-09-14. - -## Handoff to implementation - -### User-approval gates - -- Phase 1 item 6: a stop condition returns to `/planning:plan review`. -- Phase 2 item 4: every expected output is the owner's call, one fixture per round. -- Phase 2 item 6 and Phase 4 item 1: the owner sets the cost ceiling before each paid run. -- Phase 4 item 2: any grader change beyond anchoring needs the owner's agreement. - -### Execution shape ([EXEC-SHAPE] tagged) - -Sequential, main session, per the table above. Sanity Checks per phase as written. - -### Mechanical work - -- One commit per phase; `PLAN.md` phase tags and records ride each phase's commit. -- Version bump and CHANGELOG land in Phase 3's commit. -- Commit messages via heredoc with the attribution trailer; PR opened as a draft. diff --git a/docs/topics/dissolve-comments-aggressive-dial/design/design-resolution.md b/docs/topics/dissolve-comments-aggressive-dial/design/design-resolution.md deleted file mode 100644 index 6ac87bec9..000000000 --- a/docs/topics/dissolve-comments-aggressive-dial/design/design-resolution.md +++ /dev/null @@ -1,41 +0,0 @@ -# Design resolution: dissolve-comments aggressive dial - -outcome: early-exit -tier: B (light design) -reason: The change adds one posture value, two per-run argument tokens, and one flag to an existing -prose skill. No script, type, module, or package boundary changes; the skill's scripts are reused -as they are. - -## Argument grammar sketch - -```text -/code-tidying:dissolve-comments [safe] [aggressive | strip] [override] [--notes ] [target] -``` - -- Tokens are matched whole, in any order, and stripped before the target is read. `./aggressive`, - `./strip`, `./safe`, and `./override` are paths. -- `--notes` consumes the next word as its path. -- `aggressive` and `strip` together: `strip` wins, since it is the narrower edit set (no rewrites). - -## Effective mode resolution - -| Inputs | Effective mode | -|---|---| -| `safe` token present | safe (whatever else is set) | -| non-interactive run on a widened rung | safe (whatever else is set) | -| `strip` token | strip | -| `aggressive` token | aggressive | -| no dial token, `comment_posture` = `aggressive` | aggressive | -| no dial token, `comment_posture` = `strict`, `balanced`, `conservative`, empty, or unknown | as today | - -## What each mode applies - -| Mode | Class A | Class B | Class C (non-exempt) | Exempt surfaces | -|---|---|---|---|---| -| strict (today) | delete, COMMENT-ONLY | per tier gate, else proposal | earn-its-keep test; failures deleted, over-budget rewritten | untouched | -| aggressive | delete, COMMENT-ONLY | per tier gate, else proposal and the comment stays | deleted with narrative staged, except a load-bearing warning, which is kept at `class_c_max_lines` | untouched | -| strip | delete, COMMENT-ONLY | deleted as a comment, no rewrite, COMMENT-ONLY, narrative staged | deleted, COMMENT-ONLY, narrative staged | untouched | -| safe | delete, COMMENT-ONLY | proposal | proposal | untouched | - -Every applied deletion in every mode carries the COMMENT-ONLY proof; an UNPROVABLE file yields -proposals only. From 2641b58d565483928df372469073ce0b08940bc3 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:48:30 -0400 Subject: [PATCH 15/17] test(code-tidying): keep the eval fixture corpus honest for CI Three lint gates read the fixtures as ordinary source. A fixture test now names every shared fixture and asserts it still parses and self-certifies, which is also the guard against corpus rot; the shebang-carrying fixtures take the executable bit the repo requires of any shebang file; and an .editorconfig beside the suite exempts frozen corpus from indentation normalization, because a Makefile recipe needs its tab and rewriting whitespace would change what the graders measure. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KcBf2VNNsVTuiqZ1N5RuCu --- plugins/code-tidying/evals/.editorconfig | 9 ++ .../evals/fixtures/check-markers.sh.txt | 0 .../evals/fixtures/class-a.sh.txt | 0 .../evals/fixtures/class-b.sh.txt | 0 .../evals/fixtures/class-b.test.sh.txt | 0 .../evals/fixtures/class-c.sh.txt | 0 .../evals/fixtures/edge-marker.sh.txt | 0 .../evals/fixtures/edge-paired.sh.txt | 0 .../evals/fixtures/edge-paired.test.sh.txt | 0 .../code-tidying/evals/fixtures/exempt.py.txt | 0 .../scripts/evals-fixtures.test.sh | 87 +++++++++++++++++++ 11 files changed, 96 insertions(+) create mode 100644 plugins/code-tidying/evals/.editorconfig mode change 100644 => 100755 plugins/code-tidying/evals/fixtures/check-markers.sh.txt mode change 100644 => 100755 plugins/code-tidying/evals/fixtures/class-a.sh.txt mode change 100644 => 100755 plugins/code-tidying/evals/fixtures/class-b.sh.txt mode change 100644 => 100755 plugins/code-tidying/evals/fixtures/class-b.test.sh.txt mode change 100644 => 100755 plugins/code-tidying/evals/fixtures/class-c.sh.txt mode change 100644 => 100755 plugins/code-tidying/evals/fixtures/edge-marker.sh.txt mode change 100644 => 100755 plugins/code-tidying/evals/fixtures/edge-paired.sh.txt mode change 100644 => 100755 plugins/code-tidying/evals/fixtures/edge-paired.test.sh.txt mode change 100644 => 100755 plugins/code-tidying/evals/fixtures/exempt.py.txt create mode 100644 plugins/code-tidying/scripts/evals-fixtures.test.sh diff --git a/plugins/code-tidying/evals/.editorconfig b/plugins/code-tidying/evals/.editorconfig new file mode 100644 index 000000000..c0e6b9916 --- /dev/null +++ b/plugins/code-tidying/evals/.editorconfig @@ -0,0 +1,9 @@ +# Eval fixtures are frozen corpus, not source: real sections copied byte for byte +# at a pinned commit, plus invented samples whose whitespace is part of what the +# graders measure. A Makefile recipe needs its tab, and normalizing any of it +# would silently change what the suite tests. + +[*] +indent_style = unset +indent_size = unset +trim_trailing_whitespace = false diff --git a/plugins/code-tidying/evals/fixtures/check-markers.sh.txt b/plugins/code-tidying/evals/fixtures/check-markers.sh.txt old mode 100644 new mode 100755 diff --git a/plugins/code-tidying/evals/fixtures/class-a.sh.txt b/plugins/code-tidying/evals/fixtures/class-a.sh.txt old mode 100644 new mode 100755 diff --git a/plugins/code-tidying/evals/fixtures/class-b.sh.txt b/plugins/code-tidying/evals/fixtures/class-b.sh.txt old mode 100644 new mode 100755 diff --git a/plugins/code-tidying/evals/fixtures/class-b.test.sh.txt b/plugins/code-tidying/evals/fixtures/class-b.test.sh.txt old mode 100644 new mode 100755 diff --git a/plugins/code-tidying/evals/fixtures/class-c.sh.txt b/plugins/code-tidying/evals/fixtures/class-c.sh.txt old mode 100644 new mode 100755 diff --git a/plugins/code-tidying/evals/fixtures/edge-marker.sh.txt b/plugins/code-tidying/evals/fixtures/edge-marker.sh.txt old mode 100644 new mode 100755 diff --git a/plugins/code-tidying/evals/fixtures/edge-paired.sh.txt b/plugins/code-tidying/evals/fixtures/edge-paired.sh.txt old mode 100644 new mode 100755 diff --git a/plugins/code-tidying/evals/fixtures/edge-paired.test.sh.txt b/plugins/code-tidying/evals/fixtures/edge-paired.test.sh.txt old mode 100644 new mode 100755 diff --git a/plugins/code-tidying/evals/fixtures/exempt.py.txt b/plugins/code-tidying/evals/fixtures/exempt.py.txt old mode 100644 new mode 100755 diff --git a/plugins/code-tidying/scripts/evals-fixtures.test.sh b/plugins/code-tidying/scripts/evals-fixtures.test.sh new file mode 100644 index 000000000..b8a15b587 --- /dev/null +++ b/plugins/code-tidying/scripts/evals-fixtures.test.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Contract: every fixture the dissolve-comments eval suite seeds still parses in +# its own language and still self-certifies through change-shape.py, so a case +# that scores 0 means the skill regressed rather than the corpus rotting. The +# UNPROVABLE excerpt is named here because its exit 21 is the point of the case +# it feeds, not a defect. +set -euo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +plugin="$(cd "$here/.." && pwd)" +fixtures="$plugin/evals/fixtures" +change_shape="$plugin/scripts/change-shape.py" +rc=0 + +ok() { printf 'ok: %s\n' "$1"; } +fail() { + printf 'FAIL: %s\n' "$1" >&2 + rc=1 +} + +shell_fixtures=( + check-markers.sh.txt + class-a.sh.txt + class-b.sh.txt + class-b.test.sh.txt + class-c.sh.txt + edge-marker.sh.txt + edge-paired.sh.txt + edge-paired.test.sh.txt + hook-utils-header.sh.txt + silent-revert-design.sh.txt + statusline-stamp.sh.txt +) +python_fixtures=(edge_docstring.py.txt exempt.py.txt) +other_fixtures=(Makefile.txt dc-notes.md.txt restoration-markers.txt.txt) +unprovable_fixture=hook-utils-unprovable.sh.txt + +scratch="$(mktemp -d)" +trap 'rm -rf "$scratch"' EXIT + +check_self_certifies() { + local src="$1" name="$2" lang="$3" want="$4" got=0 + cp "$src" "$scratch/$name" + python3 "$change_shape" --lang "$lang" "$scratch/$name" "$scratch/$name" >/dev/null 2>&1 || got=$? + [[ "$got" == "$want" ]] || fail "$name: change-shape exit $got, expected $want" +} + +for file in "${shell_fixtures[@]}"; do + src="$fixtures/$file" + [[ -f "$src" ]] || { + fail "$file is missing" + continue + } + bash -n "$src" || fail "$file does not parse as bash" + check_self_certifies "$src" "${file%.txt}" bash 0 + ok "$file parses and self-certifies COMMENT-ONLY" +done + +for file in "${python_fixtures[@]}"; do + src="$fixtures/$file" + [[ -f "$src" ]] || { + fail "$file is missing" + continue + } + cp "$src" "$scratch/${file%.txt}" + python3 -m py_compile "$scratch/${file%.txt}" || fail "$file does not compile" + check_self_certifies "$src" "${file%.txt}" python 0 + ok "$file compiles and self-certifies COMMENT-ONLY" +done + +check_self_certifies "$fixtures/$unprovable_fixture" "${unprovable_fixture%.txt}" bash 21 +ok "$unprovable_fixture still reads UNPROVABLE (exit 21)" + +for file in "${other_fixtures[@]}"; do + [[ -f "$fixtures/$file" ]] || fail "$file is missing" +done +ok "the non-source fixtures are present" + +# Every fixture ships to feed a case: a scaffold must seed it by name. +for src in "$fixtures"/*.txt; do + base="$(basename "$src" .txt)" + grep -rqF -- "$base" "$plugin"/evals/*/scaffold.sh "$fixtures/seed.sh" || + fail "$base is seeded by no scaffold" +done +ok "every fixture is seeded by a scaffold" + +exit "$rc" From 46c706f0d40a0c1cf057f438f8e27803b7416f1b Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:57:25 -0400 Subject: [PATCH 16/17] fix(code-tidying): set the exec bit on the last two shebang files The probe fixture and the fixture test carry a shebang, so the repo requires mode 100755 on both. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KcBf2VNNsVTuiqZ1N5RuCu --- .../code-tidying/evals/probe-explicit-target/fixture/app.sh.txt | 0 plugins/code-tidying/scripts/evals-fixtures.test.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 plugins/code-tidying/evals/probe-explicit-target/fixture/app.sh.txt mode change 100644 => 100755 plugins/code-tidying/scripts/evals-fixtures.test.sh diff --git a/plugins/code-tidying/evals/probe-explicit-target/fixture/app.sh.txt b/plugins/code-tidying/evals/probe-explicit-target/fixture/app.sh.txt old mode 100644 new mode 100755 diff --git a/plugins/code-tidying/scripts/evals-fixtures.test.sh b/plugins/code-tidying/scripts/evals-fixtures.test.sh old mode 100644 new mode 100755 From 676b621c864d5232d0edf2da0ac1f00e322bc5d7 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 14:10:56 -0400 Subject: [PATCH 17/17] fix(code-tidying): close three review findings on the aggressive dial --notes now refuses a symlink before the index check, because `git ls-files --error-unmatch` reads the entry for the path it is given and an untracked link to a tracked file would otherwise pass while the append lands on the tracked target. The triage step's done condition is mode-aware: under aggressive and strip only survivors owe a criterion-2 verdict, matching the instruction that skips the evidence check for everything leaving anyway. The apply step routes aggressive through the over-budget rewrite path, so a survivor longer than the budget is rewritten with its narrative staged rather than left as it stands. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KcBf2VNNsVTuiqZ1N5RuCu --- plugins/code-tidying/skills/dissolve-comments/SKILL.md | 10 +++++++--- .../skills/dissolve-comments/reference/safety.md | 7 ++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/plugins/code-tidying/skills/dissolve-comments/SKILL.md b/plugins/code-tidying/skills/dissolve-comments/SKILL.md index 4e654154a..fca40b5df 100644 --- a/plugins/code-tidying/skills/dissolve-comments/SKILL.md +++ b/plugins/code-tidying/skills/dissolve-comments/SKILL.md @@ -82,7 +82,7 @@ Class-B moves and their tiers: [reference/dissolving-moves.md](reference/dissolv | `safe [target]` | **Safe mode**: only class-A deletions are applied; every class-B treatment and class-C rewrite is emitted as a proposal. For codebases whose guardrails you do not know. | | `aggressive [target]` | **Aggressive dial**: the survivor list below is the whole of what stays. Every other comment goes, rationale included, with its narrative staged; a class-B comment is dissolved when its move's gate passes and otherwise kept with a proposal. Gates are unchanged. Combines with `override` and `--notes`; `safe` beats it. | | `strip [target]` | **Strip**: delete every comment except the survivor list, rewrite no code, certify each deletion COMMENT-ONLY, and stage the narrative. A class-B comment is deleted rather than dissolved, so its information reaches the staged block instead of the code. `safe` beats it, and `strip` beats `aggressive`. | -| `--notes ` | Append the staged block to `` as well as reporting it. The path must be untracked or outside the repository, checked with `git ls-files --error-unmatch `; a tracked path is refused, the run continues, and the block is reported only. | +| `--notes ` | Append the staged block to `` as well as reporting it. Refuse a symlink outright, then check the path with `git ls-files --error-unmatch `, which reads the index entry and never the destination a link points at. The path must be untracked or outside the repository; a tracked or symlinked path is refused, the run continues, and the block is reported only. | | `override [target]` | **Lift the GLOBAL HARD path list** for this run's target, so `/code-tidying:dissolve-comments override ruff.toml` triages a file the list would otherwise drop. Combines with `safe`. Strip the token before reading the target; match it whole, and treat `./override` as a path. Path entries only, and every lifted path is named in the step 7 report with the channel that lifted it. | Posture `conservative` is safe mode as a standing default; `balanced` keeps the full contract but @@ -273,7 +273,9 @@ from them. the comment. Under `aggressive` and `strip` this evidence check is skipped for every comment outside the survivor list: the verdict is the same either way, and the `git log -L` per comment is the expensive half of a run. Full procedure: [reference/triage.md](reference/triage.md). Done when every comment - carries one class and every class-C candidate a criterion-2 verdict with its evidence. + carries one class, and every class-C candidate the run still tests carries a criterion-2 verdict + with its evidence: under `aggressive` and `strip` that is the survivors only, since the rest are + leaving whatever the evidence says. 6. **Apply**, one item at a time, each behind its tier's gate. Class A: delete, run `change-shape.py` on before and after; anything but COMMENT-ONLY (exit 0) restores the comment. Class B: apply the named move, run the tier's gate, then delete the comment. Class C: check the @@ -285,7 +287,9 @@ from them. deleted behind that same COMMENT-ONLY proof, class C included, and under `strip` a class-B comment takes that path rather than its move; under `aggressive` a class-B move still applies only when its tier's gate passes, and the comment stays with a proposal when it does not. A non-exempt comment over budget is rewritten to the budget under - `strict` with the narrative staged, reported instead under `balanced`. **A kept comment stays + `strict` and under `aggressive`, each rewrite carrying the narrative to the staged block and the + COMMENT-ONLY proof like any other edit; it is reported instead under `balanced`. An over-budget + survivor left as it stands is a failed run, not a conservative one. **A kept comment stays where it is and keeps its own words**: rewriting shortens the comment that is there, and never relocates it, merges two comments, or writes a new one. A comment whose referent is gone is deleted, not re-authored. its carve-out reason names diff --git a/plugins/code-tidying/skills/dissolve-comments/reference/safety.md b/plugins/code-tidying/skills/dissolve-comments/reference/safety.md index 3b46f7cef..aff1c90f9 100644 --- a/plugins/code-tidying/skills/dissolve-comments/reference/safety.md +++ b/plugins/code-tidying/skills/dissolve-comments/reference/safety.md @@ -222,9 +222,10 @@ or keep the comment if no vehicle exists (staging with no landing place is not a licence). `--notes ` gives the block a second home: the run appends it to that file as well as -reporting it. The path must be untracked or outside the repository, checked with -`git ls-files --error-unmatch `; a tracked path is refused and the run continues with the -report as the only vehicle. The block carries an `Intentional-removal:` line only where the target +reporting it. Refuse a symlink first: `git ls-files --error-unmatch ` reads the index entry +for the path it is given, so an untracked link pointing at a tracked file passes that check while +the append lands on the tracked target. Then the path must be untracked or outside the repository; +a tracked or symlinked path is refused and the run continues with the report as the only vehicle. The block carries an `Intentional-removal:` line only where the target repository's own gate scripts or CI read that trailer, since elsewhere it is a line no tool will ever match. Under `strip` the block carries more than usual: a class-B comment's information lands there rather than in a rewrite, so a thin staged block under `strip` is a defect, not a clean run.