The config-protection hook blocks Write/Edit on any basename in the
PROTECTED_FILES set, regardless of whether the file already exists. The
hook's stated purpose is to prevent agents from softening rules in an
existing config — but the same code path also blocks the legitimate
bootstrap case of scaffolding a linter config into a project that has
none.
Add an fs.existsSync check inside run(): when the basename matches a
protected entry and the file does not yet exist on disk, exit 0 and
let the Write proceed. Keep the exit-2 block for all modifications to
existing files. Stat errors (EACCES, etc.) fail closed — we treat the
path as existing so the guard is never silently weakened.
Update the existing "blocks protected config file edits" test to use a
real temp file so the BLOCK path is still exercised, and add two new
tests covering:
- first-time creation of eslint.config.mjs is allowed (exit 0, raw
passthrough, no stderr)
- Edit against an existing .eslintrc.js is still blocked (exit 2, no
stdout, BLOCKED message in stderr)
Fixes#1873
* feat(hooks): add config protection hook to block linter config manipulation
Agents frequently modify linter/formatter configs (.eslintrc, biome.json,
.prettierrc, .ruff.toml, etc.) to make checks pass instead of fixing
the actual code.
This PreToolUse hook intercepts Write/Edit/MultiEdit calls targeting
known config files and blocks them with a steering message that directs
the agent to fix the source code instead.
Covers: ESLint, Prettier, Biome, Ruff, ShellCheck, Stylelint, and
Markdownlint configs.
Fixes#733
* Address review: fix dead code, add missing configs, export run()
- Removed pyproject.toml from PROTECTED_FILES (was dead code since
it was also in PARTIAL_CONFIG_FILES). Added comment explaining why
it's intentionally excluded.
- Removed PARTIAL_CONFIG_FILES entirely (no longer needed).
- Added missing ESLint v9 TypeScript flat configs: eslint.config.ts,
eslint.config.mts, eslint.config.cts
- Added missing Prettier ESM config: prettier.config.mjs
- Exported run() function for in-process execution via run-with-flags,
avoiding the spawnSync overhead (~50-100ms per call).
* Handle stdin truncation gracefully, log warning instead of fail-open
If stdin exceeds 1MB, the JSON would be malformed and the catch
block would silently pass through. Now we detect truncation and
log a warning. The in-process run() path is not affected.