Skip to content

fix(format.sh): stop matching a prettier devDependency as a prettier config - #7

Merged
itsvedantkumar merged 3 commits into
mainfrom
fix/prettier-cfg-devdep-false-match
Sep 2, 2026
Merged

fix(format.sh): stop matching a prettier devDependency as a prettier config#7
itsvedantkumar merged 3 commits into
mainfrom
fix/prettier-cfg-devdep-false-match

Conversation

@itsvedantkumar

Copy link
Copy Markdown
Owner

find_prettier_cfg accepted package.json as a prettier config whenever this matched:

grep -q '"prettier"[[:space:]]*:' "$p"

That regex matches "prettier": "^3.4.2" sitting in devDependencies, which is a version range, not config. package.json is first in the filename list, so the hook returned it, ran prettier --config package.json --write, and prettier found no top-level prettier key. It then fell back to its built-in defaults and reformatted the file. The repo's real .prettierrc, sitting in the same directory and next in the loop, was never reached.

Caught in a live repo whose .prettierrc sets singleQuote: true and printWidth: 90. Every agent edit was silently rewritten to double quotes at width 80, fighting the committed config on every save.

Fix

Parse instead of grep. pkg_has_top_level_prettier uses node to JSON.parse the file and check hasOwnProperty("prettier") at the top level. If node is missing or the file does not parse, it returns non-zero so the caller's continue falls through to .prettierrc in the same directory. Degrading to the dedicated config file is the safe direction; formatting with the wrong defaults is not.

The filename order and the --config "$cfg" argument are unchanged. The cfg_has_plugins + is_project_trusted gate depends on knowing exactly which file prettier will load, and tests/repro/formatter-config.sh exists to prove that gate. Node parsing a package.json executes no repo code, so this does not widen the plugin-execution hole that test guards.

Verification

  • tests/repro/formatter-config-devdep.sh (new) exits 0.
  • tests/repro/formatter-config.sh unchanged, exits 0. The plugin-trust gate did not regress.
  • No-op proof, derived from history the same way the sibling test does rather than pinned to a SHA that rots: format.sh at 2792f2a does mistake the devDependency line for a config -- this test is not vacuous. The pre-fix blob leaves the file double-quoted; the fixed hook produces 'hello'.

🤖 Generated with Claude Code

itsvedantkumar and others added 3 commits September 1, 2026 19:59
… census fix missed

The v1.61.0 tag was destroyed a fourth time, and this time every part of the machinery was right.
install-alpine runs the gate inside an alpine job container whose apk line predates check 63.
The check skipped on "python3 not on PATH"; the lane's skip audit refused the undeclared skip,
correctly; the job concluded failure for the exact tagged commit; require-checks-green read a live
conclusion, correctly; cleanup deleted the tag, correctly. The defect was mine: the D2 fix
provisioned python3 in tests/container-matrix.sh's three bootstraps and missed the one alpine
container that gates releases.

The lane's own header states the rule this follows: install the ONE package a needed behaviour
requires and name the behaviour, rather than widening the approved-skip list. The named behaviour
is check 63's census self-test, stdlib Python.

Not added to the skip allowlist because the skip is not an environment fact of this lane -- the
lane exists to prove the gate runs on Alpine, and a gate check that never runs on Alpine is not
proven there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1bfb133's commit -a in this shared checkout picked up another session's staged
hook/test changes (skill-mandate.sh serial-tail mandate, inject-session-context.sh
digest changes, three test files) alongside the alpine python3 fix it describes.
That payload drift is what turned 'declared version matches what installs' and the
inventory digest red against the in-flight v1.61.0. This restores those five paths
to their 59c99f4 state; the mandate work continues on the auto-enforcement branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
find_prettier_cfg() gated package.json on `grep -q '"prettier"[[:space:]]*:'`,
which matches `"prettier": "^3.4.2"` inside devDependencies/dependencies just
as happily as a real top-level "prettier" key. package.json is tried before
.prettierrc in the same directory (matching prettier's own cosmiconfig search
order), so a repo with prettier merely listed as a devDependency had its
package.json win that slot, get handed to `prettier --config package.json`,
find no real "prettier" field, and silently fall back to built-in defaults --
the sibling .prettierrc, with the project's real settings, was never reached.

Replace the grep with pkg_has_top_level_prettier(), which parses the JSON via
node (found through find_bin, falling back to `command -v node`) and checks
Object.prototype.hasOwnProperty on the top-level key. No node, or a
package.json that fails to parse, falls through via `continue` so the walk
keeps going toward .prettierrc -- degrading to the dedicated config file is
the safe direction, silently formatting with wrong defaults is not.

Config filename order and the --config argument are unchanged; the
cfg_has_plugins/is_project_trusted gate downstream still sees exactly which
file will be loaded, and tests/repro/formatter-config.sh still passes.

Adds tests/repro/formatter-config-devdep.sh: builds a repo with a
devDependency-only "prettier" mention plus a sibling .prettierrc, proves the
fixed hook picks .prettierrc, and re-runs the same case against the newest
committed format.sh predating the pkg_has_top_level_prettier guard (derived
from git history, not a pinned SHA) to prove the case is not vacuous.
@itsvedantkumar
itsvedantkumar merged commit afaa81a into main Sep 2, 2026
3 of 15 checks passed
@itsvedantkumar
itsvedantkumar deleted the fix/prettier-cfg-devdep-false-match branch September 2, 2026 05:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant