LearnNewsExamplesServices
Frontmatter
id17201
titleBlock alignment rejects what it can already fix — and --fix is whole-file, which is why
stateClosed
labels
enhancementaibuild
assigneesneo-kimi-iris
createdAtAug 15, 2026, 8:32 PM
updatedAtAug 24, 2026, 11:06 PM
githubUrlhttps://github.com/neomjs/neo/issues/17201
authorneo-opus-grace
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 16, 2026, 1:45 AM

Block alignment rejects what it can already fix — and --fix is whole-file, which is why

Closed Backlog/active-chunk-16 enhancementaibuild
neo-opus-grace
neo-opus-grace commented on Aug 15, 2026, 8:32 PM

Refs D#17085

Operator-framed: "write code any way you like → pre-commit hook reformats, done. Unless we need to iron out parts inside the auto-formatter." We do need to iron out one part, and it is the reason this was never wired up.

Problem

check-block-alignment.mjs already knows how to fix what it rejects--fix exists and rewrites files. The pre-commit hook runs it in check mode (--staged) and blocks instead, so the author is told to run a fixer the tool could have run itself.

Measured cost in one session: it blocked commits repeatedly, and each time the resolution was mechanically identical — run --fix on the same files, re-stage, retry. No judgment was exercised at any point. That is a check standing where a fix belongs.

Why it was not simply wired in — the real gap

The two modes disagree about scope, and only one of them is safe:

// check mode: only drift the author actually introduced
const added    = gitRoot ? getStagedAddedLines(file, gitRoot) : null;
const reported = added ? allViolations.filter(v => added.has(v.lineIndex + 1)) : allViolations;
// and the file says so plainly:
// (gitRoot is set only in --staged check mode; --fix always rewrites whole-file.)

Check mode is line-scoped: a grandfathered misalignment on an untouched line never blocks an unrelated commit. --fix ignores that scoping entirely. Wiring today's --fix into the hook would reformat lines the author never touched, spraying unrelated changes into every commit — worse than the friction it removes, and it would make every diff a review hazard.

So this is not an oversight. It is a formatter that is correct for a deliberate whole-file pass and unsafe for an automatic one.

The Architectural Reality

  • The scoping function the fixer needs already exists and is already correct: getStagedAddedLines(file, gitRoot). Check mode applies it to the report set; fix mode needs it applied to the rewrite set.
  • lint-staged already re-stages files modified by tasks — every commit this session logged Staging changes from tasks…. No new plumbing.
  • The guarantee is unchanged either way: the same violations are detected by the same code. Only the disposition changes, from reject to repair.
  • Fail-closed behaviour must survive the change. Check mode reports the whole file when the git read fails, deliberately; a scoped fixer must refuse to rewrite in that case rather than fall back to whole-file, or a git hiccup becomes an unrelated reformat.

Why this is its own disposition verb, and worth naming

D#17085 has been sorting gates into keep / retire. This one is neither: it is a convert. The enforcement is right, the incident class is live, the cost is pure friction — the correct move is to keep the guarantee and delete the interaction.

A gate that can compute the correct output should never ask the author to produce it. Where a fix is deterministic, rejecting is strictly worse than repairing: identical guarantee, one round-trip more, and it spends the author's attention on something no judgment was required for.

The Fix

  1. Apply the existing staged-line scoping to --fix, so it rewrites only lines the author touched.
  2. Make the scoped fixer fail closed: no reliable staged-line set ⇒ report, never rewrite.
  3. Run --fix --staged from the pre-commit hook and let lint-staged re-stage; keep pure --fix as today's deliberate whole-file pass.

Out of Scope

  • The alignment rules themselves. This changes disposition, not house style.
  • Other check-only guards that could become fixers (check-whitespace is the obvious sibling). Worth the same treatment, and worth doing after this one proves the shape.

Acceptance Criteria

  • --fix --staged rewrites only violations on staged-added lines; a grandfathered misalignment on an untouched line in the same file is left exactly as it was, proven by a fixture containing both.
  • With no reliable staged-line set (git read failure), the scoped fixer reports and does not write — the fail-closed direction, since the alternative silently reformats a whole file on a transient error.
  • The pre-commit hook applies the fix and the commit proceeds without author action; a fixture commit that would previously have been blocked now completes with the corrected formatting staged.
  • Pure --fix still performs the whole-file pass, unchanged, for deliberate invocation.
  • The same violations are still detected: the detector is untouched and a spec pins that check-mode output is identical before and after.

Evidence class

Read at source on dev 2026-08-15: check-block-alignment.mjs--fix write path, the getStagedAddedLines scoping in check mode, and the in-file comment stating fix mode is whole-file. Friction observed live across one session: the hook blocked commits repeatedly, resolved every time by the identical manual --fix + re-stage cycle, with no judgment exercised. lint-staged's re-stage step observed in every commit run this session.

Live latest-open sweep at 2026-08-15T18:31:40Z; nothing equivalent open. A2A sweep: no competing claim.

Decision Record impact

none.

🖖 Authored by Grace (Claude Opus 5, Claude Code). Session b17338dd-b474-494f-b08c-683044de2ddb. Operator-framed during the D#17085 re-pricing conversation; filed as the first instance of the convert verb rather than keep-or-retire.

tobiu referenced in commit e2af34e - "feat(build): scoped block-alignment repair replaces rejection in the pre-commit hook (#17201) (#17223)" on Aug 16, 2026, 1:45 AM
tobiu closed this issue on Aug 16, 2026, 1:45 AM