Skip to content

feat(core): report how many places each declaration examined - #380

Merged
oekazuma merged 15 commits into
mainfrom
feat/examined-counts
Aug 7, 2026
Merged

oekazuma merged 15 commits into
mainfrom
feat/examined-counts

Conversation

@oekazuma

@oekazuma oekazuma commented Aug 7, 2026 •

Copy link
Copy Markdown
Owner

Why

Verifying a real project meant planting a deliberate violation to see whether anything fired.

architecture/reserved-name-placement is glob-configured and emits no pass results, so a compliant project produces zero routes, zero findings and zero diagnostics. Confirming that 0 meant "the tree complies" rather than "nothing was checked" required adding a violating directory, observing the finding, and deleting it again. Nothing in the output could answer the question.

The charter's inverse-precision gate names this exactly —

zero findings reads identically as "the project complies" and "the declaration matched nothing"

— and records the fix as its own unbuilt spec. This is that spec, one level finer: per declaration, not per rule. One configuration of this rule carries eight declarations, and "the rule examined 137 places" does not answer "did parts see 28 or 0?".

The gap got wider in 0.42.0, not narrower. Before it, one of the rule's diagnostics fired on a correct declaration — and that false note was the only evidence the rule had run. Fixing it left a compliant project with a completely empty result. The observability was accidental, and correcting the diagnostic removed it.

More diagnostics cannot answer this. Reporting a declaration that examined nothing is precisely the case 0.42.0 deliberately made silent: a convention document declaring every permitted position, including ones nothing occupies yet, is correct, and telling its author to "remove the declaration" is advice to delete a check they will want. What a reader needs is information, not a verdict.

What

A top-level examined map in the JSON report — rule id, then declaration label, then how many places that declaration judged.

"examined": {
  "architecture/reserved-name-placement": {
    "capitalisedUnitPlacements.parts → src/**": 28,
    "anyCaseUnitPlacements.types → src/**": 0
  }
}

The engine owns the sink. runRules builds it and returns { results, examined }, rather than each of the three call sites threading one. A caller that forgot would drop the counts silently — the exact failure this feature removes, and a shape this repository hit twice recently in option forwarding.

Not on rules[id], and the reporters guide already said why: "The counts describe the report, not the tree." findings/passed describe what survived reporting; this describes what the analysis examined and is deliberately unfiltered by --diff, --baseline and suppressions. Two scopes behind sibling keys with nothing marking the difference is one field carrying two meanings — the shape behind two defects fixed earlier this week. A top-level map makes the difference structural, next to inventories, which is there for the same reason. The guide's sentence is now scoped to rules.

The labels are the diagnostic's own strings, verbatim. The count is what makes a silent declaration legible beside the diagnostic that describes a broken one; different names for the same declaration would defeat that.

Three states, deliberately: no entry (the rule doesn't count, or returned early), an empty entry (it counts and nothing is declared), and an entry containing 0 (a declaration judged nothing).

Zero means the declaration judged nothing, and nothing more

An earlier draft of the design claimed zero meant "live, reachable and currently unoccupied". Review falsified that by execution with two supported configurations in which zero appears while occupying directories exist: a sibling map's empty value ungoverns the name everywhere, and an overrides-supplied exclude skips the directories while the diagnostic classifier consults only the global exclude — producing a zero with no diagnostic at all.

The rule reaches its judging phase past five early exits, each a separate reason a count can be zero. The design enumerates them; the report states a number and stops. The useful direction is sound and is the one documented: a non-zero count is what distinguishes "complies" from "nothing was checked".

Verification

core 1295, cli 838, vite 207 pass; tsc --noEmit clean in all three; lint and format clean across 969 files; docs gates 27; floor-smoke 8/8.

The whole-branch review ran 13 mutations, seven behavioural probes and an end-to-end run of the built CLI; the fix-wave re-review added four more mutations proving the new tests load-bearing, and re-ran all five zero-causes and four counting scenarios.

The interesting part: the feature's only visible surface was pinned by nothing

The final review deleted the single line that puts examined into the report and ran everything: 2333 tests passed with the field gone from every report. Deleting the argument at either report-producing caller left those suites green too. Both trailing parameters of buildJsonReport are optional, so the type system did not catch it either — "the engine owns it, so omission is a type error" held at the runRules boundary but not at the reporter boundary, which is the one a user's report actually crosses.

The design's testing list required exactly this ("the two report-producing callers carry the counts, asserted by enumerating the call sites, not by sampling"). The plan's own self-review downgraded it to a grep — a one-off manual command, not an assertion. A feature built to stop a silent absence shipped its own silent absence, one review away from merging.

Two counting defects were found the same way. A duplicated glob inside one value multiplied the count — parts: 'src/lib/** | src/routes/** | src/lib/**' would report 56 where 28 were judged, with no diagnostic, because the duplicate deduped into the alternatives map and counted as used. And an overrides layer that replaces a global declaration's value inflated that global declaration's count, so a single report claimed the same label had both judged a place and matched no directory — breaking the label join the design calls its whole justification. The increment now fires only where the resolved value judges.

Earlier in execution, two of the five early exits turned out to be pinned by nothing: the increment could move above the exclusion check or the root check with the suite green — and the exclusion exit is the one the design singles out in Deliberately not solved as a deliberate, documented contract.

The recurring shape is worth naming. Four of the plan's own instructions were wrong and each was caught by executing them: a caller grep scoped to src/ that missed a test file (in a plan whose point is enumerating callers), a fixture asserting 0 for a directory the spec says is judged, a mutation row its own named test could not catch, and the self-review above. Every one surfaced because the process runs each new test against the unchanged code first and works a mutation table where each row names the single test that must fail.

Design: docs/superpowers/specs/2026-08-07-examined-counts-design.md. Plan: docs/superpowers/plans/2026-08-07-examined-counts.md, corrected in place each time execution proved a step wrong.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • JSON reports now include per-rule, per-declaration examination counts, including zero-count entries.
    • Analysis results expose unfiltered counts, helping distinguish inspected locations from reported findings.
  • Documentation
    • Added guidance explaining examination counts and how filtering, suppression, baselines, diffs, and overrides affect reported results.
  • Tests
    • Added coverage for count accuracy, zero-count scenarios, duplicate patterns, and JSON serialization across CLI and build analyses.

oekazuma added 14 commits August 7, 2026 14:23
runRules now returns { results, examined } instead of a bare array: the
engine hands each rule a recordExamined sink via RuleContext and keys
whatever a rule writes to it by rule id, so a caller can never silently
drop the counts by forgetting to build one itself. buildJsonReport gains
a trailing examined argument and a top-level `examined` field, included
only when non-empty so existing reports stay byte-identical.

The CLI and the Vite build-mode analyzer thread the counts through to
their JSON report; the dev-server hooks build no JSON report and drop
them, noted inline. Updated the one existing direct runRules() caller in
core's own test suite to the new return shape.
architecture/reserved-name-placement now calls ctx.recordExamined with a
per-declaration count of directories judged (permitted or rejected), keyed
by the same label the aggregated diagnostic already uses. Empty-value
declarations and override-only declarations stay uncounted, matching what
the diagnostic classifies.

Also documents recordExamined's silent last-write-wins contract, and adds
a CLI-level fixture pinning that --diff scoping narrows results but not
the examined count.
…e-placement

Only the name-in-no-map and empty-value exits actually key on the directory's
name; inert, root-level and excluded key on the directory itself. The prior
comment claimed all five did.
…or examined counts

Two of the five early exits that gate the reserved-name-placement examined
count (excluded, root-level) had no test that would fail if the increment
were moved above them, and the isMentionedAnywhere guard had no test
distinguishing it from the already-pinned sourceFiles===undefined guard.
Verified each new test against a hoisted-increment/hoisted-call mutation
before restoring the correct code.

Also drops a redundant assertion in the CLI --diff test that re-checked the
same examined value applyScope never receives, and corrects the comment
that oversold what it was proving.
The increment was keyed on the globally resolved labels while every early
exit and the placement check used the per-directory resolved maps, so an
overrides layer that replaces a name's value credited the global
declaration with directories its own glob never reached — one report
saying a declaration judged a place and matched no directory at once. A
value repeating a glob multiplied the count for the same reason: the
label array was pushed once per glob and splitNames does not dedupe.
Both trailing parameters of buildJsonReport are optional, so deleting the
field from the report or the argument from either report-producing caller
compiled and left every suite green. Verified by mutation: each deletion
now fails a test.
A rule that counts while declaring nothing reports an empty entry, which
five places said could not happen. The changeset named three changed
exported shapes where AnalyzeResult makes four. The design's zero table
listed an exit that cannot produce a zero, and the plan carried two
instructions execution proved wrong.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a9cb6bd-dbba-482a-8f6b-47a1cbea0404

📥 Commits

Reviewing files that changed from the base of the PR and between 8bd36a7 and 47f1cf1.

📒 Files selected for processing (2)
  • docs/src/content/docs/guides/(reporting)/reporters.md
  • docs/src/content/docs/ja/guides/(reporting)/reporters.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/src/content/docs/guides/(reporting)/reporters.md

📝 Walkthrough

Walkthrough

The change adds per-rule, per-declaration examined counts to rule execution and JSON reports. It updates the reserved-name-placement rule, CLI and Vite pipelines, public contracts, tests, reporter documentation, and release metadata.

Changes

Examined counts reporting

Layer / File(s) Summary
Execution and report contracts
packages/core/src/engine.ts, packages/core/src/rule.ts, packages/core/src/reporter/json.ts, packages/core/test/*
runRules returns results and examined counts. RuleContext supports recordExamined. JSON reports optionally serialize the examined map.
Reserved-name-placement counting
packages/core/src/rules/architecture/reserved-name-placement.ts, packages/core/test/reserved-name-placement.test.ts
The rule initializes zero counts and increments each matching global declaration once per judged directory. Tests cover overrides, exclusions, empty values, duplicate globs, inventories, and zero-count cases.
CLI and Vite propagation
packages/cli/src/index.ts, packages/cli/test/*, packages/vite/src/*, packages/vite/test/*
CLI and Vite analysis paths pass examined counts to JSON reporting. Tests validate filtering independence and report output with fixture projects.
Documentation and release metadata
.changeset/examined-counts.md, docs/src/content/docs/**/reporters.md, docs/superpowers/**
The changeset, design, plan, and English and Japanese reporter guides document the examined map and its count states.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant analyzeProject
  participant runRules
  participant reserved-name-placement
  participant buildJsonReport
  analyzeProject->>runRules: execute rules
  runRules->>reserved-name-placement: provide recordExamined callback
  reserved-name-placement->>runRules: return declaration counts
  runRules->>analyzeProject: return results and examined
  analyzeProject->>buildJsonReport: pass results and examined
  buildJsonReport->>analyzeProject: serialize JSON report
Loading

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: reporting per-declaration examined counts.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/src/content/docs/guides/`(reporting)/reporters.md:
- Around line 139-149: Update the English reporter guide’s description of the
top-level examined field to state that it is omitted when no rule reports
counts, while preserving the existing distinction between absent rule entries
and empty entries. Apply the equivalent Japanese statement in
docs/src/content/docs/ja/guides/(reporting)/reporters.md at lines 111-111 to
keep both reporter guides synchronized; both sites require direct changes.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 509b2d14-2927-4fa9-8bb2-db4077103fbc

📥 Commits

Reviewing files that changed from the base of the PR and between 98170d1 and 8bd36a7.

📒 Files selected for processing (27)
  • .changeset/examined-counts.md
  • docs/src/content/docs/guides/(reporting)/reporters.md
  • docs/src/content/docs/ja/guides/(reporting)/reporters.md
  • docs/superpowers/plans/2026-08-07-examined-counts.md
  • docs/superpowers/specs/2026-08-07-examined-counts-design.md
  • packages/cli/src/index.ts
  • packages/cli/test/analyze-project.test.ts
  • packages/cli/test/fixtures/reserved-name-placement-project/package.json
  • packages/cli/test/fixtures/reserved-name-placement-project/src/app.html
  • packages/cli/test/fixtures/reserved-name-placement-project/src/lib/Card/Card.svelte
  • packages/cli/test/fixtures/reserved-name-placement-project/src/lib/Card/parts/a.svelte
  • packages/cli/test/fixtures/reserved-name-placement-project/src/lib/legacy/parts/c.svelte
  • packages/cli/test/fixtures/reserved-name-placement-project/src/lib/other/parts/b.svelte
  • packages/cli/test/fixtures/reserved-name-placement-project/src/routes/+page.svelte
  • packages/cli/test/fixtures/reserved-name-placement-project/svelte-vitals.config.mjs
  • packages/cli/test/run.test.ts
  • packages/core/src/engine.ts
  • packages/core/src/reporter/json.ts
  • packages/core/src/rule.ts
  • packages/core/src/rules/architecture/reserved-name-placement.ts
  • packages/core/test/engine.test.ts
  • packages/core/test/json-report.test.ts
  • packages/core/test/reserved-name-placement.test.ts
  • packages/core/test/seo001.test.ts
  • packages/vite/src/analyze.ts
  • packages/vite/src/hooks/handle.ts
  • packages/vite/test/analyze.test.ts

Comment thread docs/src/content/docs/guides/(reporting)/reporters.md
@oekazuma
oekazuma merged commit cd79b62 into main Aug 7, 2026
8 checks passed
@oekazuma
oekazuma deleted the feat/examined-counts branch August 7, 2026 15:14
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