Skip to content

Close linter doc-sync gap by bridging registry, prose docs, and spec labels - #47204

Merged
pelikhan merged 2 commits into
mainfrom
copilot/fix-doc-sync-guard-issue
Jul 22, 2026
Merged

Close linter doc-sync gap by bridging registry, prose docs, and spec labels#47204
pelikhan merged 2 commits into
mainfrom
copilot/fix-doc-sync-guard-issue

Conversation

Copilot AI commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Current doc-sync guards enforced two disconnected invariants: linters.All()documentedAnalyzers() and doc.goREADME.md. That allowed code/docs drift (and stale documentedAnalyzers().label values) to pass if each cluster stayed internally consistent.

  • Bridge registry ↔ prose docs (new invariant)

    • Added TestDocSurfacesMatchRegistryAndSpecList in pkg/linters/doc_sync_test.go.
    • Parses analyzer slugs from:
      • doc.go bullets
      • README.md Subpackages table
    • Normalizes doc slugs to analyzer names and asserts set-equality with linters.All().
  • Validate documentedAnalyzers() labels against README

    • New assertion in the same test checks documentedAnalyzers().label set exactly matches parsed README subpackage slugs.
    • This verifies the “derived from README” contract instead of relying on comments.
  • Refactor parser reuse in tests

    • Hoisted regexes to shared test-level vars (docBulletRe, readmeTableRe).
    • Added focused helper parsers to avoid duplicate parsing logic and keep existing doc-sync tests consistent.
docAsAnalyzerNames := normalizedDocSlugSetToAnalyzerNames(parseDocBulletSet(t))
readmeAsAnalyzerNames := normalizedDocSlugSetToAnalyzerNames(parseReadmeSubpackageSet(t))

assert.Equal(t, sortedKeys(registrySet), sortedKeys(docAsAnalyzerNames))
assert.Equal(t, sortedKeys(registrySet), sortedKeys(readmeAsAnalyzerNames))
assert.Equal(t, sortedKeys(readmeSlugs), sortedKeys(documentedLabels))

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix incomplete doc-sync guard for registry and documentation Close linter doc-sync gap by bridging registry, prose docs, and spec labels Jul 22, 2026
Copilot AI requested a review from pelikhan July 22, 2026 06:24
@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Triage

Field Value
Category test
Risk 🟢 Low
Score 40/100 (impact: 18, urgency: 10, quality: 12)
Action auto_merge

Pure test improvement — strengthens doc-sync invariants in pkg/linters. No production code changes. Auto-merge when CI passes.

Generated by 🔧 PR Triage Agent · sonnet46 35.2 AIC · ⌖ 7.92 AIC · ⊞ 5.6K ·

@pelikhan
pelikhan marked this pull request as ready for review July 22, 2026 07:26
Copilot AI review requested due to automatic review settings July 22, 2026 07:26
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #47204 does not have the 'implementation' label and has 66 new lines of code in business logic directories (≤100 threshold).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds test coverage linking the linter registry, prose documentation, and spec labels to prevent drift.

Changes:

  • Adds registry-to-documentation set comparisons.
  • Validates documentedAnalyzers() labels against README entries.
  • Refactors documentation parsing into reusable helpers.
Show a summary per file
File Description
pkg/linters/doc_sync_test.go Adds cross-surface synchronization tests and parsing helpers.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (1)

pkg/linters/doc_sync_test.go:117

  • This scans every table in the README rather than only the Subpackages table. A lowercase backticked key in another table would be treated as an analyzer and could even mask a missing Subpackages row, so the new assertions do not enforce the stated invariant. Scope parsing from the ### Subpackages heading to the next heading.
	for line := range strings.SplitSeq(string(readmeBytes), "\n") {
		if !strings.HasPrefix(line, "| `") {
			continue
		}
		m := readmeTableRe.FindStringSubmatch(line)
  • Files reviewed: 1/1 changed files
  • Comments generated: 0
  • Review effort level: Medium

@github-actions github-actions Bot mentioned this pull request Jul 22, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: Close linter doc-sync gap

The approach is sound — bridging all three surfaces (registry, doc.go, README) in a single test is a real improvement over the previous disconnected checks.

One maintainability concern:

The docSlugToAnalyzerName switch is a fragile hardcoded mapping. The general rule (strip hyphens) already covers all existing cases, including panic-in-library-codepanicinlibrarycode. Replacing the switch with strings.ReplaceAll(slug, "-", "") would eliminate the need to remember to update it whenever a new hyphenated analyzer is added. See inline comment for the suggested fix.

Otherwise the refactor is clean — shared regexes, focused helper parsers, and clear error messages.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 23 AIC · ⌖ 4.65 AIC · ⊞ 5K

return "panicinlibrarycode"
default:
return slug
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The docSlugToAnalyzerName function uses a hardcoded switch with a single special case for "panic-in-library-code". The conversion rule (strip hyphens) is actually a general one: any hyphenated slug maps to an analyzer name by strings.ReplaceAll(slug, "-", ""). This hardcoded switch will silently need updating every time a new hyphenated analyzer is added.

Consider replacing with:

func docSlugToAnalyzerName(slug string) string {
	return strings.ReplaceAll(slug, "-", "")
}

This is simpler, future-proof, and consistent with how all existing analyzer names are formed.

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking observations

Two medium-priority maintainability issues worth addressing:

  1. ** hardcoded switch** (line 108): The single-case mapping has no documented contract, no enforcement that new slugs are explicitly handled, and will produce confusing set-diff failures when a future hyphenated analyzer is added. At minimum, add a comment stating the general rule and consider validating outputs against the registry inside the helper.

  2. Third assertion's coverage gap (line 51): The readmeSlugs ↔ documentedLabels assertion doesn't anchor to the registry, so a linter absent from both README and documentedAnalyzers() simultaneously passes silently. Each assertion should carry a brief comment explaining what it independently guarantees so future maintainers can reason about coverage.

No blocking bugs — the new invariants are a net improvement. Both issues are about future-proofing the test itself.

🔎 Code quality review by PR Code Quality Reviewer · sonnet46 25.4 AIC · ⌖ 4.75 AIC · ⊞ 5.6K
Comment /review to run again

Comments that could not be inline-anchored

pkg/linters/doc_sync_test.go:108

docSlugToAnalyzerName is a hardcoded manual mapping that will silently break for future analyzers. When a new hyphenated analyzer is added, the test will fail with a confusing set-mismatch rather than guiding the developer to add a mapping entry.

<details>
<summary>💡 Suggested fix</summary>

Document the invariant and optionally validate outputs against the registry at test time:

// docSlugToAnalyzerName maps a doc/README slug to its linters.All() Name.
// General rule: slugs equ…

</details>

<details><summary>pkg/linters/doc_sync_test.go:51</summary>

**Third assertion cross-checks README against `documentedAnalyzers()` but not against the registrya linter omitted from both README and spec simultaneously still passes.** This creates a false confidence that all three surfaces are mutually consistent.

&lt;details&gt;
&lt;summary&gt;💡 Details&lt;/summary&gt;

The chain is:
1. `registrySet``docAsAnalyzerNames`2. `registrySet``readmeAsAnalyzerNames`3. `readmeSlugs``documentedLabels`If a linter is removed from `linters.All()` but its slug st</details>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /codebase-design — requesting changes on two maintainability issues.

📋 Key Themes & Highlights

Key Themes

  • Fragile slug→name mapping: docSlugToAnalyzerName hardcodes a single case instead of deriving the rule algorithmically (strings.ReplaceAll(slug, "-", "")). A second hyphenated analyzer will silently break the new test.
  • Unexplained filter: the "internal" skip in parseReadmeSubpackageSet lacks a comment, making it opaque.

Positive Highlights

  • ✅ The new three-way invariant (linters.All()doc.go ↔ README) is exactly the right guard — it closes the gap described in #47190.
  • ✅ Extracting parseDocBulletSet / parseReadmeSubpackageSet as helpers eliminates duplicated parsing logic cleanly.
  • ✅ Hoisting regex vars to package level is the correct Go pattern.
  • ✅ The documentedAnalyzers().label ↔ README assertion enforces the "derived from README" contract that was previously just a comment.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 32 AIC · ⌖ 4.7 AIC · ⊞ 6.7K
Comment /matt to run again

Comments that could not be inline-anchored

pkg/linters/doc_sync_test.go:141

[/codebase-design] The hardcoded switch in docSlugToAnalyzerName will silently break when another hyphenated-package analyzer is added — the mapping rule is implicit rather than derived.

<details>
<summary>💡 Suggested fix</summary>

Replace the switch with the underlying rule:

func docSlugToAnalyzerName(slug string) string {
	return strings.ReplaceAll(slug, &quot;-&quot;, &quot;&quot;)
}

All existing analyzer Name values are the directory name with hyphens stripped (panic-in-library-code

pkg/linters/doc_sync_test.go:121

[/codebase-design] The magic string &quot;internal&quot; filter has no comment explaining why the internal subpackage is excluded — this will confuse the next reader who sees it.

<details>
<summary>💡 Suggested fix</summary>

Add an inline comment:

if m[1] == &quot;internal&quot; {
    continue // the internal helper package is not a public analyzer subpackage
}

</details>

@copilot please address this.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 80/100 — Excellent

Analyzed 3 test(s): 3 design, 0 implementation, 0 violation(s).

📊 Metrics (3 tests)
Metric Value
Analyzed 3 (Go: 3, JS: 0)
✅ Design 3 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 1 (33%)
Duplicate clusters 0
Inflation No (pure refactoring)
🚨 Violations 0

Per-Test Analysis:

Test File Classification Notes
TestDocGo_CountMatchesBullets doc_sync_test.go:28 Behavioral contract Validates doc.go header count matches bullet entries; 4 assertions with error/happy-path coverage
TestDocGo_AnalyzersMatchREADME doc_sync_test.go:60 Behavioral contract Refactored to use helper; verifies doc.go and README list same analyzers
TestDocSurfacesMatchRegistryAndSpecList doc_sync_test.go:67 Behavioral contract (NEW) Closes documented sync-drift bugs (#40436, #45185, #46131, #46527, #46707, #46977); bridges registry, doc.go, README, and spec_test; 3 assertions with descriptive messages

Verdict

APPROVED. 0% implementation tests (threshold: 30%). All tests verify design contracts and user-visible specs. Pure refactoring (no production code changed) extracts helpers to improve maintainability. Build tags present, no mock violations, all assertions have descriptive failure context.

Key Strengths:

  • New test TestDocSurfacesMatchRegistryAndSpecList directly addresses recurring doc-sync gap reported in 6 issues
  • All 3 assertions in new test have clear, actionable failure messages
  • Helper functions (parseDocBulletSet, parseReadmeSubpackageSet, normalizedDocSlugSetToAnalyzerNames) improve clarity and reuse
  • Comprehensive coverage: registry ↔ doc ↔ README ↔ spec alignment

🧪 Test quality analysis by Test Quality Sentinel · haiku45 31.6 AIC · ⌖ 7.81 AIC · ⊞ 7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 80/100 Excellent. 0% implementation tests (threshold: 30%). All 3 tests verify design contracts and user-visible specifications. New test TestDocSurfacesMatchRegistryAndSpecList directly addresses recurring doc-sync gap. Pure refactoring improves maintainability without changing production code.

@pelikhan
pelikhan merged commit 5b14568 into main Jul 22, 2026
71 of 82 checks passed
@pelikhan
pelikhan deleted the copilot/fix-doc-sync-guard-issue branch July 22, 2026 07:38
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.83.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants