Skip to content

feat(rules): config + ESLint-style rules engine with check command#42

Open
bntvllnt wants to merge 2 commits into
mainfrom
feat/config-rules-engine
Open

feat(rules): config + ESLint-style rules engine with check command#42
bntvllnt wants to merge 2 commits into
mainfrom
feat/config-rules-engine

Conversation

@bntvllnt
Copy link
Copy Markdown
Owner

@bntvllnt bntvllnt commented Jun 3, 2026

Implements specs/backlog/2026-06-02-config-rules-engine.md end-to-end.

What

  • Config (src/config) — discovery (codebase-intelligence.json + dotfile variants + package.json#codebaseIntelligence, walk-up), zod validation against the committed schema.json, CLI overrides, ConfigError → exit 2.
  • Rules engine (src/rules/engine.ts) — pluggable Rule contract, ESLint-style severities (off|warn|error or 0|1|2, [severity, options]), // ci-ignore-file / // ci-ignore-next-line <rules> suppressions, stable fingerprints, deterministic ordering.
  • Rulesno-comments (the requested one; configurable — keeps JSDoc, tool/compiler directives, and a file-leading license header by default; style: line|block|all), no-circular-deps, no-dead-exports.
  • Formatterstext, json, sarif.
  • CLI check — exit codes 0 clean / 1 findings ≥ failOn / 2 config or usage error; flags --format/--json/--config/--fail-on/--gate/--base/--quiet/--summary.
  • MCP check tool — returns {verdict, summary, findings}. Read-only — actions[] are advisory hints, never applied (per roadmap §3).
  • Fix: getGitChurn/getHeadHash no longer leak git's stderr on non-git dirs.

Tests (38 new, no internal mocks)

  • config-loader — discovery, package.json key, walk-up, invalid JSON / unknown-key → ConfigError, overrides.
  • rules-engine — real parser→graph→analyzer→engine pipeline: no-comments option matrix, suppressions, circular/dead-export rules, formatters, verdict gating.
  • cli-check.e2e — spawns the real built binary, asserts exit codes + stdout (json, sarif, suppression, config error, --config).
  • mcp-check — real in-memory MCP server + client.

Validation

  • pnpm lint clean · pnpm typecheck clean · pnpm build clean.
  • pnpm vitest run (parallel): 380 passed, 0 errors.
  • Coverage 95.71% lines / 86.8% branches (thresholds 80/70/80/80); src/rules 97.5%.

Note: the local --coverage --no-file-parallelism run hit vitest's known onTaskUpdate worker-RPC timeout under heavy serial load on a loaded dev box (suite 460s vs 35s parallel). All tests pass and coverage thresholds are met; CI's dedicated runner is the authoritative gate for the serial coverage step.

Not in scope (follow-ups)

Boundary rules engine, audit new-only diffing, more formatters (codeclimate/pr-comment), init/hooks/watch — speced, not built here.

Implements specs/backlog/2026-06-02-config-rules-engine.md.

- config: discovery (codebase-intelligence.json + variants + package.json key,
  walk-up) + zod validation + CLI overrides + ConfigError (exit 2)
- engine: pluggable Rule contract, severity resolution (off/warn/error|0/1/2),
  ci-ignore-file / ci-ignore-next-line suppressions, stable fingerprints
- rules: no-comments (configurable: keeps JSDoc/directives/header by default,
  style line|block|all), no-circular-deps, no-dead-exports
- formatters: text, json, sarif
- CLI 'check' command: exit 0/1/2, --format/--json/--config/--fail-on/--gate/--base
- MCP 'check' tool + setRoot/getRoot (read-only, advisory actions only)
- silence git stderr leak in getGitChurn/getHeadHash (non-git dirs)
- tests: config loader, rules engine (real pipeline), CLI e2e (spawned binary),
  MCP tool — 38 tests, no internal mocks
- docs: mcp-tools (16th tool), architecture + CLAUDE module maps
@bntvllnt bntvllnt self-assigned this Jun 4, 2026
Blocker:
- F1: implement file-level --gate new-only --base (git diff filtering) instead of
  shipping inert flags

Correctness/reliability:
- surface rule-throws to stderr (no silent CI false-negative)
- confine sourceOf reads to project root via realpath (blocks ../ and symlink-out)
- failOn:'never' now disables the maxWarnings gate too
- block-comment ci-ignore (/* ci-ignore-file */) suppressions now honored
- no-comments allow[] = comment-body prefix match (no 'a' over-matching 'bad')

Config:
- ConfigError messages use basename (no full-path leak via MCP)
- stop config walk-up at repo root (no ambient-config hijack)
- distinct read vs parse errors (EACCES != invalid JSON)

SARIF/output:
- emit shortDescription, partialFingerprints, endLine/endColumn
- MCP check uses canonical formatJson; extract shared formatSummaryLine
- quiet-first output ordering

Types/MCP:
- FindingAction.kind union; RuleSetting tuple rejects [off, opts]
- register check in codebase://setup; generic tool description; getRoot TSDoc
- example config gate: all (safe default)

Tests: new-only gate (real git repo), block suppression, allow precision, failOn:never+maxWarnings
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