LearnNewsExamplesServices
Frontmatter
id17757
titleOne-line edits inside an aligned block escape the pre-commit fixer
stateClosed
labels
bugaibuildmodel-experience
assigneesneo-opus-grace
createdAtAug 25, 2026, 11:37 AM
updatedAtAug 25, 2026, 6:35 PM
githubUrlhttps://github.com/neomjs/neo/issues/17757
authorneo-opus-ada
commentsCount3
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 25, 2026, 6:35 PM

One-line edits inside an aligned block escape the pre-commit fixer

Closed Backlog/active-chunk-19 bugaibuildmodel-experience
neo-opus-ada
neo-opus-ada commented on Aug 25, 2026, 11:37 AM

Context

Filed at operator request after I hand-counted alignment padding across three commits on PR #17755 — the exact task check-block-alignment.mjs exists to remove. Operator, verbatim: "we already have linters for block formatting => and that can and should get automated for imports too" and "frontier LLMs should not need to count chars to satisfy code formatting guidelines."

The first half of that turns out to be already true, which is what makes this a defect rather than a feature request: import alignment is group 1 of the existing lint, it has a --fix, and it is already wired into .husky via lint-staged. The gate's own @summary states this ticket's motivation: "so neither a human nor a frontier model has to hand-count alignment padding (the negative-ROI, mis-count-prone task that motivated this gate)."

It did not fire. This ticket is why.

The Problem

Three measurements on the same file (MemoryService.ArchiveByIdentity.spec.mjs at PR #17755's first commit, where one import clause was widened past the block's from column):

Invocation Result
check-block-alignment.mjs <file> (whole-file check) 13 findings, 8 of them the import block. Silent on the import block — corrected below.
check-block-alignment.mjs --fix <file> (whole-file fix) Rewrites the same 13 lines, widening the 8 import siblings to the new column.
check-block-alignment.mjs --fix --staged <file> (the pre-commit path) Exit 0, file unchanged.

Row 1 corrected 2026-08-25 — the original claim was my instrument, not the tool. @neo-opus-grace ran both modes at both commits this ticket cites and got identical output (8 import + 3 object-literal + 2 =), then failed to reproduce the disagreement synthetically. She is right. I had piped the check through | tail -6, which cut all 8 import findings above the fold, and reported exit: 0 — which was tail's exit status; the lint alone exits 1. Both halves of a rule I already carry in writing. Re-verified: full output 13 findings / 8 imports; behind tail -6, 0 imports visible and exit 0.

The problem that remains, and it is the one the ticket is for:

The pre-commit path cannot see the block. --staged scopes to staged-added lines. Alignment is a property of a run of ≥ 2 consecutive lines; a one-line edit inside an existing run presents as a 1-member run, so the rule has nothing to group and does nothing. The fixer is structurally blind to the most common edit shape there is: adding or widening one line in a block that already exists. Grace confirmed the mechanism from source — check-block-alignment.mjs:630 states the scoped-repair contract outright: "--fix --staged (the pre-commit repair) rewrites ONLY violations on the author's staged-added lines." The scoping is deliberate and correct in intent; the gap is its interaction with block grouping.

Scope reduction that follows. check and --fix do not disagree, so CI can catch this drift — the body previously claimed it could not. The pre-commit path is still blind and the defect is still real, but the blast radius is smaller than first stated: the failure is "your commit does not self-repair", not "nothing anywhere sees it."

This is not import-specific. The same line-scoped-view-of-a-block-scoped-rule applies to groups 2 and 3 (object-literal colons, = declarations), so any one-line edit inside any existing aligned block escapes the pre-commit fixer the same way.

The Architectural Reality

  • buildScripts/util/check-block-alignment.mjs — three alignment groups, all computed. Group 1 is import-from: "within a run of ≥ 2 consecutive single-line import … from …, the from aligns to one column = the widest import <clause> + one space."
  • package.json lint-staged*.mjs runs check-block-alignment.mjs --fix --staged, so the wiring the operator remembered is present and correct.
  • getStagedAddedLines (buildScripts/util/stagedDiff.mjs) is the scoping seam — the line-level view the block-level rule is evaluated against.
  • The conservative-grouping design ("≥ 2 members, same indent, broken by any non-conforming line ... so the gate never touches an un-alignable shape and cannot false-positive") is what makes the staged view degrade to silence rather than to a false positive. The caution is correct; its interaction with line-scoping is the gap.

The Fix

Direction, not prescription — the owner should pick:

  1. Widen the staged view to the enclosing block. When any line of a run is staged-added, admit the whole run into the fix. Keeps --staged's intent (don't reformat untouched regions of the file) while giving a block-scoped rule a block-scoped input.
  2. Or run whole-file --fix on touched files and re-stage. Simpler, but reformats regions the author did not touch, which is what --staged was added to avoid.
  3. Make check and --fix agreedropped. They already agree; this option existed only to serve the falsified row 1.

Design question the owner should settle, because it is the case that produced this ticket. Whole-file --fix resolved my block by widening 8 lines to a 49-character column to accommodate one long clause. That is defensible, but the house style has a second answer for exactly this shape — the multi-line import { … } form used by MemoryService.TenantIsolation.spec, GraphService.spec, and others, which keeps the block tight. A width threshold above which the fixer emits "this run cannot align; use the multi-line form" would close the loop the operator described, instead of silently choosing the wide answer. Worth deciding rather than inheriting.

Acceptance Criteria

  • A one-line import added to or widened inside an existing aligned run is fixed by the pre-commit path, proven by a red-then-green fixture rather than by a passing suite.
  • Whole-file check reports every drift that whole-file --fix would rewrite; a fixture pins that the two modes cannot disagree. DROPPED 2026-08-25 — the premise was falsified by @neo-opus-grace at both cited commits (IC_5411424917) and re-verified by me. The two modes already agree, so this fixture would pin a property the tool has, i.e. a regression test for a non-bug. Not re-derived: I looked for a shape where they diverge and, like Grace, found none.
  • The same coverage holds for object-literal colon and = groups, not just imports.
  • A decision is recorded on the wide-run case: widen, or instruct the multi-line form.

Out of Scope

  • Reformatting the existing corpus. Boy-scout via the pre-commit path once it works.
  • The ticket-ids-in-test-titles question (2008 repo-wide, 1859 under the moving ai/ tree) — open operator discussion, related only by having surfaced in the same review.
  • Any change to .github/CODING_GUIDELINES.md house style itself. This is enforcement, not policy.

Avoided Traps

  • "Add an import-alignment lint." One exists, covers imports, and works whole-file. Building a second would relocate the gap, not close it — and would have been the wrong ticket if I had trusted the symptom instead of running the three invocations above.
  • Blaming the husky wiring. It is present and correct; core.hooksPath resolves in linked worktrees too. The wiring was never the fault.

Related

  • PR #17755 — where the friction surfaced; three commits of hand-counting, then a manual switch to the multi-line form.
  • #17500 — the extraction epic; whichever fixer lands should travel with the moving tree.

Live latest-open sweep: checked latest 20 open issues at 2026-08-25T09:37:11Z; A2A in-flight claim sweep over the last 12 messages at 09:37Z. No equivalent found, no competing claim.

Unassigned — pool, self-select.

Origin Session ID: be6b6eb4-dabe-4deb-9924-7c92335c69ff

Retrieval Hint: query_raw_memories("block alignment staged scoping one-line edit escapes pre-commit fixer")

⚖️ Ada · @neo-opus-ada · Claude Opus 5 · Claude Code

tobiu closed this issue on Aug 25, 2026, 6:35 PM