docs: add lint/static-analysis gaps brief#829
Conversation
Interview contract for the lint/static-analysis gap epic: typos hook plugin, Go coverage (batch + hook), lychee-offline and pyright batch additions, .NET runtime guard, hook-observability fleet convention, bespoke-guard routing. Related epics routed to their owning repos. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019KVQkSXBKY8pcCSNKUnP3g
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Claude finished @kyle-sexton's task in 2m 0s —— View job PR Review: docs/lint-static-analysis-gaps-brief
SummaryThis PR adds a single planning document: Overall: LGTM with minor observations. What's goodFormat is consistent with established patterns. The Lane assignments are correctly applied. Sub-items correctly map tools to lanes per the two-lane rule:
Plugin philosophy constraints are correctly restated. The Constraints section (lines 49–55) correctly enumerates repo/user/machine/org-agnostic, cross-platform, setup contract, and graceful degradation — all aligned with Time-stamped assertion (line 39). PR title follows Conventional Commits ( Minor observations1. "rung-4" is undefined in this document (line 62).
The consumer override ladder taxonomy is internal shorthand. This is fine for a Brief that will be read by people already in context, but worth adding a pointer to wherever "rung-4" is defined (the ecosystem override ladder doc or the plugin philosophy) so the acceptance criteria are unambiguous when the sub-items are executed. 2. Setup-lifecycle convention dependency (line 55).
This creates a blocking dependency on a future epic. The Brief correctly captures it as a constraint, but the sub-issues should explicitly track this dependency so the typos and Go hook plugins don't ship without the setup-lifecycle convention resolved. A note in the epic tracking issue (when filed) linking to the setup-lifecycle epic would close the loop. 3. Sub-item 7 cross-repo routing (lines 43–45). 4. golangci-lint FAQ citation (line 31). None of these are blocking. Observations 1 and 2 are worth addressing in the epic issue rather than in this doc. Observation 3 is a tracking hygiene note for sub-issue filing. Observation 4 is a style preference only. |
## Summary New hook plugin `plugins/typos-format/`: runs `typos --write-changes` on every `Write`/`Edit`, gated on a consumer typos config (`typos.toml`/`_typos.toml`/`.typos.toml`/`Cargo.toml`/`pyproject.toml`) found by an ancestor walk-up, mirroring the shipped `ruff-format`/`markdown-format` plugin pattern. Residual (unfixable) findings surface via `additionalContext` with remediation guidance pointing at `extend-words`/`extend-identifiers`/`extend-ignore-re` allowlist entries. Advisory only — never blocks the edit. - Hook-precision + hook-telemetry convention conformance (schema + registry row added). - `/typos-format:setup check|apply` skill. - 44 black-box contract-test assertions (`typos-format.test.sh`), including a `--force-exclude` regression case added after independent review caught the flag was missing (verified empirically: without it, the hook silently overrode a consumer's own typos excludes). ## Test plan - [x] `typos-format.test.sh` — 44/44 pass against real `typos-cli 1.44.0`. - [x] Full fleet `scripts/run-plugin-tests.sh` — all plugin contract tests pass. - [x] `scripts/validate-plugins.sh` (manifest schema, catalog sync, no-npx contract). - [x] `scripts/sync-hook-utils.sh --check`, `check-silent-skips.sh`, `check-changelog-parity.sh --check`, `check-cross-plugin-source-drift.sh --check`, `check-skill-leaf-names.sh --check`, `check-changed-skills.sh origin/main`. - [x] `typos`/`shellcheck`/`markdownlint-cli2` self-check on all new files. - [x] Two independent fresh-context reviews (code review + security review via CI): one CRITICAL finding (missing `--force-exclude`) fixed + regression-tested; two SUGGESTION-level security notes triaged (one deferred as a fleet-wide pre-existing test-harness pattern, one out-of-scope for the shared `hook-utils.sh` library); two minor code-review notes fixed (test assertion strength, marketplace `relevance` block). ## Review follow-ups filed - #874 — `ruff-format` is missing its hook-telemetry data schema + registry row (pre-existing docs-drift gap, surfaced while using it as a reference plugin). - #875 — fleet-wide: concurrent `PostToolUse` hook writes have no locking/ordering, so overlapping-scope hooks (e.g. this plugin + `ruff-format`, or `eol-normalizer` + any formatter) can silently clobber each other's fix. Documented as a known limitation in this plugin's header comment and README; not fixable at the single-plugin level. ## Related - Closes #831 (epic #830, sub-item 1 of `docs/topics/lint-static-analysis-gaps/PLAN.md`, contract landed via #829). <details> <summary>Implementation plan (docs/topics/typos-format-hook/PLAN.md, pruned from the tree after merge)</summary> # typos-format hook plugin ## Brief Issue #831 (epic #830, sub-item 1 of docs/topics/lint-static-analysis-gaps/PLAN.md, merged via PR #829). New hook plugin `plugins/typos-format/`: per-file `typos -w` autofix on Write/Edit, mirroring the existing `plugins/markdown-format/` and `plugins/ruff-format/` patterns. Opt-in via consumer typos config ancestor walk-up (no config = no-op, never impose typos' defaults on a repo that hasn't adopted it — same posture as ruff-format). Advisory only, hook-precision + hook-telemetry conventions, setup skill (check/apply). Scope: Tier C (no design) — pure pattern replication of two already-shipped, gate-passing plugins; no new types, no new architecture. ## Plan ### Phase 1: Hook plugin skeleton Files (new): `.claude-plugin/plugin.json`, `hooks/hooks.json`, `hooks/hook-utils.sh` (synced copy of `lib/hook-utils.sh`), `hooks/typos-format.sh`, `.claude-plugin/marketplace.json` entry. Hook control flow (modeled on `ruff-format.sh`): kill-switch check, buffered stdin read, jq gate, file-path extraction, repo-root resolution, ancestor config walk-up (`typos.toml` > `_typos.toml` > `.typos.toml` > `Cargo.toml` metadata > `pyproject.toml [tool.typos]`, precedence per crate-ci/typos' own docs and empirical verification), typos binary resolution from PATH, `typos --write-changes --force-exclude --format json` fix pass, exit-code branching (0 = clean/fixed, 2 = residual findings, other = tool break), residual-finding parsing with remediation guidance pointing at `extend-words`/`extend-identifiers`/`extend-ignore-re`, and hook-telemetry envelope emission. Hook-precision conformance: the convention is fleet-wide by intent (its owner doc states "every plugin hook follows it") though CI-audited enforcement is currently guardrails-only; this plugin opts in voluntarily. Known, documented, out-of-scope risk: concurrent same-file `PostToolUse` hook writes (no extension filter + no locking) — tracked in #875. ### Phase 2: Setup skill + docs `skills/setup/SKILL.md` (`check`/`apply` contract; `apply` is guidance-only since typos has no per-repo dependency-manager install path, unlike Ruff or markdownlint-cli2), `README.md`, `CHANGELOG.md` (starts at 0.1.0). ### Phase 3: Contract test `hooks/typos-format.test.sh` — black-box Bash contract test mirroring `ruff-format.test.sh`'s fixture-corpus shape: opt-in gate (present/absent config, pyproject with/without `[tool.typos]`, `typos.toml`/`_typos.toml` precedence), fix + residual-finding reporting, config-exclude respect (`--force-exclude`), kill switch, telemetry envelope shape, missing-binary and missing-`jq` visibility notices. ### Phase 4: Local CI-equivalent verification Ran every gate the new directory trips in `plugin-gate`/`hygiene` locally before opening the PR (hook-utils sync, plugin manifest validation, catalog sync, changelog parity, silent-skip gate, cross-plugin source drift, skill leaf names, skill quality gate, full fleet test suite, and a `typos`/ `shellcheck`/`markdownlint-cli2` self-check on the new files). ## Blast radius LOW — new, additive plugin directory; zero changes to existing plugin behavior beyond the synced `hook-utils.sh` copy (mechanical, CI-gated) and two purely-additive doc/registry rows. ## Open questions (resolved during implementation) 1. Binary-file handling — empirically not an issue; typos handles arbitrary file content without a hard error. 2. Setup `apply` scope — resolved to guidance-only (no auto-install), confirmed against crate-ci/typos' own install docs (no universal per-repo dependency-manager path exists for a standalone Rust binary). </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…atus (#890) ## Summary Adds an `opt-in` key to the `dotnet` ecosystem entry — the one lint-bearing ecosystem (of 8: dotnet, python, typescript, bash, markdown, powershell, yaml, cross-cutting) that lacked one. `dotnet format`'s own docs state preferences are read from `.editorconfig` "if present, otherwise a default set of preferences will be used"; verified empirically (dotnet SDK 10.0.302) that a project with **no** `.editorconfig` still fails `dotnet format --verify-no-changes` against Roslyn's built-in conventions. Without a gate, this imposes formatting opinions on a repo that never configured any — the same class of violation `ruff-format`/`typos-format` already avoid at the hook layer. - `check/SKILL.md` now honors `opt-in` for the lint phase — it never did before, so dotnet's `check-cmd` ran unconditionally whenever `.cs`/`.csproj`/etc. changed. Build and test are unaffected; only the lint phase is gated. - Both `check/SKILL.md` and `lint/SKILL.md` make an opt-in-unmet skip **visible** (`skip (opt-in unmet: ...)`) instead of `lint/SKILL.md`'s prior behavior of silently dropping the ecosystem from output entirely — this applied to every opt-in-bearing ecosystem, not just dotnet. - Multi-tool ecosystems (bash's shellcheck-always/shfmt-conditional split; cross-cutting's per-tool config list) get explicit handling so an unconditionally-applicable sub-tool is never suppressed just because a sibling sub-tool's condition is unmet. - `plugins/toolchain` version 0.5.2 → 0.6.0 (minor — new capability, not a fix to previously-intended behavior). ## Review history An independent fresh-context review caught two IMPORTANT findings before this PR, both fixed and empirically re-verified: 1. The opt-in condition originally required a C#-specific `.editorconfig` section (`[*.cs]`), but a universal `[*]` section demonstrably governs `dotnet format`'s output on `.cs` files too (empirically confirmed: `[*] indent_style=tab` alone triggers real WHITESPACE violations). Widened the condition to `[*]` or a C#-glob section. 2. The initial "opt-in-unmet skip suppresses the whole ecosystem row" rule was correct for single-condition ecosystems (dotnet, python) but wrong for multi-tool ones (bash, cross-cutting) where one sub-tool's condition can hold unconditionally — fixed both skill files to gate only the actually-unmet sub-tool(s). `plugins/toolchain` has no automated test harness (documentation-driven: skills read YAML config, the executing agent runs the resolved shell commands — there's no script to unit-test), so verification here is empirical testing of the underlying `dotnet format` behavior plus careful prose review, not a CI-gated test suite. ## Test plan - [x] `check-jsonschema` against `ecosystem.schema.json` — both `dotnet.yaml` files (bundled default + example fixture) validate. - [x] `dotnet format --verify-no-changes` behavior empirically verified (dotnet SDK 10.0.302): no `.editorconfig` → built-in defaults apply; `.editorconfig` with only unrelated globs (`[*.md]`) → zero effect; `.editorconfig` with `[*]` → real effect (matches a `[*.cs]` section). - [x] `scripts/check-changed-skills.sh origin/main` — both edited skills PASS, 0 errors. - [x] `scripts/generate-catalog.mjs --check`, `scripts/check-changelog-parity.sh --check` — pass. - [x] `typos` (full repo, matches CI), `markdownlint-cli2` on all touched files — clean. - [x] Independent fresh-context code review — 2 IMPORTANT findings, both fixed and re-verified above. ## Related - Closes #835 (epic #830, sub-item 5 of `docs/topics/lint-static-analysis-gaps/PLAN.md`, contract landed via #829). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…#910) ## Summary Two deliverables per issue #832's "Go coverage, both lanes": 1. **`plugins/toolchain/reference/ecosystems/go.yaml`** batch ecosystem default (+ matching `docs/conventions/ecosystem-commands/examples/go.yaml` fixture) — `go build ./...`/`go test ./...` unconditional; `golangci-lint run [--fix] ./...` gated behind an `opt-in` key (`.golangci.yml`/`.golangci.yaml`/`.golangci.toml`/`.golangci.json` presence); `project-discovery: ["go.mod"]` for nested-module coverage; a `go-mod-tidy-drift` gate via `go mod tidy -diff`. 2. **New hook plugin `plugins/go-format/`** — runs `goimports -w` on every `.go` `Write`/`Edit`, **unconditionally** (no consumer-config opt-in gate — the one deliberate shape difference from sibling `ruff-format`/`typos-format`), skipping files carrying Go's `// Code generated ... DO NOT EDIT.` marker. ## Design decisions Issue #832 calls for an "implementation-time field survey" to pick the per-file formatter (gofmt/goimports/gofumpt/golangci-lint fmt) against three criteria: official/authoritative, maintained, feature-fit. - **`goimports`** picked: its own docs state it "formats your code in the same style as gofmt so it can be used as a replacement for your editor's gofmt-on-save hook" — a direct official statement of intent for this exact per-file-hook scenario. Actively maintained (golang/tools, v0.48.0). Handles import add/remove, which bare `gofmt` never does — necessary since an LLM edit changing symbol usage can leave a broken import list. - **`gofumpt`** rejected as the unconditional default: third-party opinionated superset ("a stricter gofmt"), same risk class as why `ruff-format`/`dotnet format` gate behind consumer config. No toggle added in v1 (no consumer has asked; can be added later if requested). - **`golangci-lint fmt`** rejected for the per-file hook: with no config present it has zero formatters enabled by default (does nothing) — corroborates the issue's own "golangci-lint is batch-only by design" framing, for the correct reason (verified via the tool's own behavior, not just restated from the brief). - **`go-format` runs unconditionally** — `goimports` has no meaningful config-divergence axis when left unconfigured (unlike ruff/dotnet-format, whose underlying tools genuinely diverge by config), so gating it would be inert ceremony. - **`golangci-lint`'s lint step IS gated** — empirically verified golangci-lint v2 with no config file still applies its own fixed "standard" linter preset unconditionally, the same imposed-unconfigured-opinion risk PR #890 (issue #835) fixed for `dotnet format`. Mirrors the existing `python.yaml`/`dotnet.yaml` `opt-in` pattern. Full rationale, stress-test findings, and empirical verification notes: `docs/topics/832-go-ecosystem/PLAN.md` (pasted below). ## Review history Two independent fresh-context passes ran before this PR, findings folded in: **Plan stress-test** (before implementation) — one HIGH finding: the "goimports has no config-divergence axis" premise missed that `goimports` has zero awareness of Go's generated-file convention, and would silently rewrite generated files. Fixed by adding a marker-skip guard to the hook before implementation began. Also found a MEDIUM multi-module gap (`./...` doesn't cross a nested `go.mod` boundary — fixed by adding `project-discovery: ["go.mod"]`) and corrected two citation errors (a misattributed `golangci-lint fmt` quote; an inaccurate "every ecosystem has a matching example fixture" claim). **Independent code review** (after implementation, before PR) — one CRITICAL finding: the generated-file guard checked only the file's *first non-blank line*, missing the common real-world shape where a copyright/license header (`addlicense`/`goheader`-style tooling) precedes the marker by a few `//` comment lines — reviewer empirically reproduced the miss against a real generated-file shape and confirmed the hook silently rewrote it. Fixed: the guard now scans the file's full leading comment/blank-line run per Go's own stated convention ("before the first non-comment, non-blank text in the file"), and two related defeat vectors caught by the same review (trailing CRLF, leading UTF-8 BOM) are also fixed. Five new regression cases added. Two SUGGESTION-level findings (an unnecessary `mktemp` for stderr capture; an inaccurate PLAN.md claim about a nonexistent schema-check script) were also fixed. ## Test plan - [x] `plugins/go-format/hooks/go-format.test.sh` — 41/41 passing (FAKEBIN-pattern contract test, real `goimports` v0.48.0 binary), including 5 generated-file-guard regression cases (bare marker, license-header preamble, CRLF, BOM, marker-after-leading-block negative case). - [x] `check-jsonschema` against `ecosystem.schema.json` — both `go.yaml` files (bundled default + example fixture) validate; against the plugin manifest schema for `plugins/go-format/.claude-plugin/plugin.json`. - [x] `scripts/check-changed-skills.sh` / direct `check-skill.sh` invocation — `go-format:setup`, `toolchain:check`, `toolchain:lint` all PASS. - [x] `scripts/sync-hook-utils.sh --check`, `scripts/check-cross-plugin-source-drift.sh`, `scripts/check-silent-skips.sh`, `scripts/check-changelog-parity.sh --check`, `node scripts/validate-plugin-contracts.mjs`, `bash scripts/validate-plugins.sh` (strict catalog validation) — all pass. - [x] `typos`, `markdownlint-cli2`, `shellcheck` on all new/changed files — clean. - [x] `node scripts/generate-catalog.mjs --check` — in sync. - [x] Manually verified `go mod tidy -diff` and golangci-lint's unconfigured-defaults behavior against real installed toolchains (Go 1.26.5, golangci-lint v2) — see PLAN.md. - [x] Plan stress-test + independent code review — findings above, both fixed and re-verified. ## Related - Closes #832 (epic #830, sub-item 2 of `docs/topics/lint-static-analysis-gaps/PLAN.md`, contract landed via PR #829). <details> <summary>PLAN.md (implementation plan, decisions, and review history)</summary> ## Brief Issue #832 (epic #830, sub-item 2 of `docs/topics/lint-static-analysis-gaps/PLAN.md` lines 26-30). Two deliverables, one PR: 1. New `plugins/toolchain/reference/ecosystems/go.yaml` batch ecosystem entry + matching `docs/conventions/ecosystem-commands/examples/go.yaml` fixture. 2. New hook plugin `plugins/go-format/` — per-file `goimports` autofix on Write/Edit. Scope boundaries: batch additions are rung-4 defaults only (consumer `.claude/ecosystems/*.yaml` override ladder unchanged). `govulncheck` is explicitly "optional" per the brief — NOT shipped as a default; documented as a consumer-addable local gate only. No `gofumpt` toggle in v1 (YAGNI — no consumer has asked). Success criteria: both new surfaces pass the plugin contract gate + fleet conformance audit (acceptance criteria line 61 of the epic Brief); CI/local parity restored for the Go toolchain gap identified 2026-07-21 (line 66-67). ## Open Decisions (resolved this session, recorded here for the approval gate) 1. **Formatter pick for the per-file hook: `goimports`.** Field survey against the Brief's own criteria (official/authoritative, maintained, feature-fit), researched fresh this session: - `gofmt` — non-configurable by design (go.dev/blog/gofmt, go.dev/doc/effective_go), but doesn't manage imports; an LLM edit that adds/removes symbol usage leaves a broken file. - `goimports` (golang.org/x/tools, v0.48.0, 2026-07-09; ~7,983 commits, last updated 2026-07-20) — its own docs state it "formats your code in the same style as gofmt so it can be used as a replacement for your editor's gofmt-on-save hook." Direct official statement of intent for exactly this scenario. **Picked.** - `gofumpt` (mvdan/gofumpt v0.10.0, 2026-05-04) — self-branded "a stricter gofmt," third-party opinionated superset. Rejected as the unconditional default (same class of risk as why ruff-format/dotnet-format gate behind consumer config); not adding a toggle in v1. - `golangci-lint fmt` — rejected for the per-file hook. **Stress-test correction:** the plan's original citation was imprecise — `golangci-lint fmt --stdin` IS a genuine single-file, non-package-scoped invocation (empirically confirmed: no `go/packages` loading, formats piped stdin instantly). The "directories are NOT analyzed recursively... files must come from the same package" quote governs `golangci-lint run` (linters), not `fmt` (formatters) — don't misattribute it. The correct, empirically-confirmed rejection reason: with no config file present, `golangci-lint fmt` has **zero formatters enabled by default** (unlike `run`'s fixed "standard" 5-linter preset) — i.e. it silently does nothing, the opposite of a usable default. Conclusion (rejected for the hook) is unchanged; only the stated reason is corrected. Independently corroborates the Brief's own line 30 ("golangci-lint is batch-only by design") for the `run`/lint half of the tool. - **Consequence: `go-format` runs `goimports` unconditionally — no consumer-config opt-in gate.** This is the one deliberate shape difference from ruff-format/typos-format/dotnet-format's opt-in-gated pattern. Rationale: goimports has no meaningful config-divergence axis when left unconfigured, so gating it would be inert ceremony. Qualifies for `docs/PLUGIN-PHILOSOPHY.md` lines 143-147 lane-1 treatment (non-conflicting good-practice default) where gofumpt/golangci-lint-fmt would not. - **Stress-test correction (HIGH finding, folded in):** the "no config-divergence axis" claim was incomplete — empirically confirmed goimports rewrites files carrying the canonical `// Code generated ... DO NOT EDIT.` marker with zero awareness of that convention, while golangci-lint's own linters/formatters default to `issues.exclude-generated: strict` in v2. Generated Go files (protobuf, mockgen, sqlc, stringer, wire output) are common; an unconditional hook would silently rewrite them on any edit. **Fix, not a re-open of the unconditional-default decision:** `go-format.sh` adds a generated-file marker guard — skip (emit_skipped) when the marker appears anywhere in the file's leading comment/blank-line run (scanning stops at the first line that is neither blank nor a `//` comment), matching Go's own stated convention ("before the first non-comment, non-blank text in the file"), not just the first non-blank line. **Independent-review correction (CRITICAL, folded in post-stress-test):** the original implementation checked ONLY the first non-blank line, on the premise that a preamble before the marker is "uncommon" — that premise was false; a license/copyright header (common `addlicense`/`goheader` tooling output) routinely precedes the marker by several `//` lines, and this shape is empirically present in real Go stdlib-adjacent generated files. Fixed by scanning the full leading comment/blank block instead of only line one; also fixed two related defeat vectors caught by the same review (trailing CRLF `\r` not stripped before the `$` anchor; a leading UTF-8 BOM defeating the `^` anchor) and added five regression test cases (license-header preamble, CRLF, BOM, and a negative case confirming a marker appearing AFTER the leading block does NOT suppress a real edit). This is precision-scoping (same category as `--force-exclude` giving Ruff per-file skip precision), not a consumer-config walk-up, so it does not undermine Open Decision 1's "unconditional" framing. 2. **`golangci-lint` lint step gated behind an `opt-in` key** requiring `.golangci.yml`/`.golangci.yaml`/`.golangci.toml`/`.golangci.json` present (ancestor walk to repo root, mirroring `dotnet.yaml`'s `root = true` boundary if an analogous concept exists for golangci-lint — verified during Phase 1 to NOT exist, so ceiling at repo root only). Verified via WebFetch/WebSearch this session (golangci-lint.run/docs/configuration, github.com/golangci/golangci-lint discussions, 2026): golangci-lint v2 with **no config file present still runs its own fixed "standard" linter preset** (`linters.default: standard`) rather than erroring or running nothing — the same imposed-unconfigured-opinion risk class PR #890 (issue #835) just fixed for `dotnet format`'s unconfigured Roslyn defaults, and consistent with why `python.yaml` already gates ruff behind `[tool.ruff]`/`ruff.toml` presence. Mirrors `python.yaml:10` and `dotnet.yaml:34`'s `opt-in` key shape exactly. 3. **Single PR, closes #832.** Both deliverables are one Brief line ("Go coverage, both lanes"), share the same formatter research, and match the one-issue-one-PR precedent from #831/#835. ## Plan ### Phase 1: `go.yaml` ecosystem batch entry [DONE] Files: - `plugins/toolchain/reference/ecosystems/go.yaml` (new) — flat top-level keys, bundled-fallback disclaimer header (mirrors `dotnet.yaml:1-27` style): - `globs: ["*.go", "go.mod", "go.sum"]` - `project-discovery: ["go.mod"]` — **added per stress-test MEDIUM finding**: empirically confirmed `go build ./...`/`go test ./...`/`go list ./...` run from a repo root silently skip a nested module's packages (a nested `go.mod` bounds `./...` expansion; a root `go.work` file does NOT cross module boundaries for this either). Without `project-discovery`, a monorepo with a nested Go module gets silent incomplete build/test/lint, directly undercutting this issue's own "CI/local parity" success criterion. Mirrors `python.yaml`/`typescript.yaml`'s existing `project-discovery` handling for the same class of problem. - `build-cmd: "go build ./..."` - `test-cmd: "go test ./..."` - `check-cmd: "golangci-lint run ./..."` - `fix-cmd: "golangci-lint run --fix ./..."` - `opt-in`: text per Open Decision 2 above. - `install-hint`: **resolved per stress-test finding** — do NOT bake in a pinned `curl|sh -s -- -b ... vX.Y.Z` one-liner (upstream's own install docs explicitly state `go install`/`go get` "aren't guaranteed to work" for golangci-lint, and a version-pinned curl\|sh command drifts immediately). Use a durable pointer instead: `"Install golangci-lint: https://golangci-lint.run/docs/welcome/install/ | Go toolchain: https://go.dev/dl/"` — same durable-pointer style as `dotnet.yaml`'s `"Install .NET SDK from https://dot.net"`. - `gates`: one entry, `go-mod-tidy-drift`, `trigger-globs: ["go.mod", "go.sum"]`, `cmd: "go mod tidy -diff"` — **confirmed live** via `go help mod tidy` (Go 1.26.5 installed this session): "-diff causes tidy not to modify go.mod or go.sum but instead print the necessary changes as a unified diff. It exits with a non-zero code if the diff is not empty." Introduced Go 1.23 (2024) — note this as an implicit minimum-Go-version prerequisite in `context/go.md`. `remediation: "Run go mod tidy and commit the updated go.mod/go.sum."` - `notes`: mention `govulncheck` as an available consumer-addable local gate (not shipped by default per Open Decision brief-wording), pointing at `.claude/ecosystems/go.local.yaml`. - `docs/conventions/ecosystem-commands/examples/go.yaml` (new) — richer worked-example fixture mirroring `examples/dotnet.yaml`'s structure (same keys as above plus the `gates` block spelled out). **Correction (stress-test finding):** only 3 of the 8 existing bundled ecosystem yamls (`bash`, `python`, `dotnet`) actually have a matching example fixture — `markdown`, `powershell`, `typescript`, `cross-cutting`, `yaml` do not, and no CI gate enforces 1:1 coverage. Add `examples/go.yaml` anyway (it's good practice and dotnet/python both have one), but don't claim in the PR body that this closes a universal-coverage gap — it doesn't exist as a gap. - `plugins/toolchain/skills/check/context/go.md` (new) — Go-specific gotchas: `go test ./...` module-root requirement (must run from the module root or a path containing `go.mod`; `project-discovery` above handles nested-module walking), GOFLAGS interactions, golangci-lint's default-"standard"-preset caveat tied to the opt-in gate, AND (stress-test MEDIUM finding) golangci-lint's own config discovery falls back to the user's **home directory** with no `root = true`-equivalent stop marker when no repo-level config is found — a stray `~/.golangci.yml` on a developer's machine makes local runs diverge from a clean CI container; document this as a known local/CI divergence source. Mirrors the existing `context/dotnet.md`/`context/python.md` shape. - `plugins/toolchain/skills/check/SKILL.md` — add `go` to the covered-ecosystems list (currently: dotnet, python, typescript, bash, powershell, markdown) and its alias table if one applies (no common alias needed — "go" is already short). - `plugins/toolchain/skills/lint/SKILL.md` — same covered-ecosystems list addition if `lint` also enumerates them explicitly (verify during implementation; python's opt-in-gated lint precedent from #835 touched both `check/SKILL.md` and `lint/SKILL.md`, so `go` likely needs the same two-file touch). - `plugins/toolchain/.claude-plugin/plugin.json` — version bump (minor: new ecosystem capability). **Verify current version live** (`git show origin/main:plugins/toolchain/.claude-plugin/plugin.json` at rebase time — do not assume 0.6.0 is still current, #833/#834 may have already bumped it). - `plugins/toolchain/CHANGELOG.md` — `[Unreleased]`/new version entry under Added. **Sanity Check:** `check-jsonschema --schemafile docs/conventions/ecosystem-commands/ecosystem.schema.json <file>` passes for both `plugins/toolchain/reference/ecosystems/go.yaml` and `docs/conventions/ecosystem-commands/examples/go.yaml`. **Independent-review correction:** no CI job or repo script actually validates `reference/ecosystems/*.yaml`/`examples/*.yaml` against this schema today (confirmed by grepping `.github/workflows/ci.yml` — its four `check-jsonschema` steps cover only marketplace/plugin manifests, dependabot, and workflow files); the check above is a manual `check-jsonschema` CLI run, not an existing repo script being reused. Both files validated clean this way. This is a pre-existing gap in the repo's own CI coverage, out of scope for this issue — noted here rather than silently left as an inaccurate claim. ### Phase 2: `plugins/go-format/` hook plugin [DONE] Files (full new plugin directory, mirroring `plugins/typos-format/` structure): - `.claude-plugin/plugin.json` — `userConfig.go_format_enabled` (boolean, default `true`), version `0.1.0`, keywords `["go","golang","goimports","formatter","hook"]`. - `hooks/hooks.json` — `PostToolUse`, matcher `Write|Edit`, command `"${CLAUDE_PLUGIN_ROOT}"/hooks/go-format.sh`, timeout 15. - `hooks/hook-utils.sh` — initial copy via `scripts/sync-hook-utils.sh` (never hand-copy). - `hooks/go-format.sh` — control flow mirrors `ruff-format.sh`'s shape: - Extension pre-filter on `*.go` (jq-free, before requiring jq) — like ruff-format, unlike typos-format's no-filter shape. - `hook::check_enabled "GO_FORMAT"`, `hook::buffer_stdin`, `hook::require_jq`, `hook::read_file_path`, `hook::repo_root`. - **No ancestor consumer-config walk-up** (goimports is unconditional per Open Decision 1) — document this simplification explicitly in a short header comment referencing this PLAN's rationale, not just silently omitting the walk-up ruff-format/typos-format both have. - Binary resolution: PATH-only (`command -v goimports`) — no `.venv`-style walk (Go has no per-project virtualenv concept; mirrors typos-format's PATH-only resolution). - Invocation: **resolved via empirical stress-test verification (real goimports v0.48.0 binary):** `goimports -w -l "$FILE"` in one pass — `-w` writes the fix, `-l` (list-only) combined with `-w` still lists the filename if changes were needed, giving a single-pass fix+detect (simpler than ruff-format's two-pass shape). Exit-code semantics confirmed: **`-l` ALWAYS exits 0**, even when it lists a file needing changes — there is no exit-1-style "findings" signal like ruff/typos have. Detect "changes were made" from **non-empty stdout** (the listed filename), not from exit code. Non-zero exit (confirmed: exit 2, parseable message on **stderr**) occurs only on a genuine parse/syntax error — surface that as a finding-text message (mirrors how ruff-format surfaces a mid-edit syntax error as a finding, not a tool-break), matching the doctrine even though the underlying signal shape differs from ruff. Skip entirely (before invoking goimports) when the file matches the generated-file marker guard from Open Decision 1's stress-test correction above. - Telemetry: `hook::emit_telemetry "go-format" "PostToolUse" <status> "$start" "$data_json" "$REPO_ROOT"`; `status` semantics per the shared doctrine (`ok` = ran to judgment, `skipped` = tool broke/missing prerequisite). - Always exits 0 (advisory only). - `hooks/go-format.test.sh` — FAKEBIN-pattern contract test mirroring `ruff-format.test.sh`/`typos-format.test.sh`: gate-off, non-`.go` extension skip, clean file, import-added-by-edit auto-added, import-removed-by-edit auto-removed, **generated-file marker skip** (new case per the Open Decision 1 stress-test correction), missing-binary dim-9 visibility (once-per-session), missing-jq dim-9 visibility, kill-switch, telemetry envelope shape assertions (`schema_version`/`timestamp`/`hook`/`hook_event`/`status`/`duration_ms`/`data`), a syntax-error-mid-edit case surfaced as a finding (per the confirmed `-l` exit-2/stderr behavior above, not a tool-break). - `skills/setup/SKILL.md` — `check`/`apply`, `disable-model-invocation: true`. `check` probes Bash, jq, `goimports` on PATH, the `go_format_enabled` toggle, hook registration. `apply`: **resolved per stress-test finding — guidance-only, no write path**, matching `typos-format`'s pattern (`plugins/typos-format/skills/setup/SKILL.md:57-64`), not `ruff-format`'s `.venv`-install path. `go install golang.org/x/tools/cmd/goimports@latest` writes to the machine-global `$GOBIN`/`$GOPATH/bin` (not project-scoped) and `@latest` is not idempotent-pinned (silently drifts over time) — structurally the same "no per-repo dependency-manager, machine-level binary" case as typos-format, not ruff-format's project-`.venv` case. Document the goimports install pointer (`go install golang.org/x/tools/cmd/goimports@latest` — a plain, uncaveated `go install`, unlike golangci-lint's own install docs which explicitly warn `go install`/`go get` "aren't guaranteed to work" for that tool specifically — don't let that caveat bleed across into this SKILL.md's goimports guidance). - `README.md` — mirror typos-format's structure; explicitly document the "no config-gate, unconditional default" design choice (the one plugin in the family without an opt-in section) — say so plainly, don't silently omit the section. - `CHANGELOG.md` — `[0.1.0]` initial release entry, telemetry-conformant from day one (matches typos-format's precedent, not ruff-format's later-follow-up pattern). - `docs/conventions/hook-telemetry/data/go-format.schema.json` (new) — findings shape decided from Phase 2's live verification of goimports' actual diagnostic output (flat-string per ruff-format's shape unless goimports emits something structured — verify, don't assume). - `docs/conventions/hook-telemetry/README.md` — add Implementers table row for `go-format`. - `.claude-plugin/marketplace.json` — new entry: `category: "development"`, `tags: ["go","golang","goimports","formatter","hook"]`, `relevance: {topic: "Go", signals: {filesRead: ["**/*.go"], cli: ["goimports"]}}` (mirrors ruff-format/typos-format's relevance-block shape exactly). - `README.md` (repo root) — regenerate catalog block via `node scripts/generate-catalog.mjs`. **Sanity Check:** `plugins/go-format/hooks/go-format.test.sh` passes 100% when run directly (`bash plugins/go-format/hooks/go-format.test.sh`), and `scripts/sync-hook-utils.sh --check` reports no drift for the new copy. ### Phase 3: Cross-cutting verification + PR [DOING] - Rebase onto latest `origin/main` (expect a benign conflict on `plugins/toolchain/.claude-plugin/plugin.json` + `plugins/toolchain/CHANGELOG.md` against #833/#834's already-merged or still-in-flight changes — resolve by reapplying this lane's version bump on top of theirs, not by discarding either). - Run the full local CI-equivalent gate set: hygiene (schema/markdownlint/typos/shellcheck/exec-bit), hook-utils-sync, cross-plugin-source-drift, silent-skip-gate, changelog-parity-gate, skill-quality-gate + portability-lint, plugin-gate (`scripts/validate-plugin-contracts.mjs` + both new/changed test suites), `scripts/generate-catalog.mjs --check`. - Mandatory fresh-context independent code review (per this session's established discipline). - `gh pr create` — closes #832, body includes the two locked Open Decisions above (formatter pick + opt-in-gate rationale) so reviewers see the reasoning, not just the diff. - Monitor CI, process every review thread to resolution (GraphQL `resolveReviewThread` for bot-authored addressed threads — this repo's ruleset requires `required_review_thread_resolution`), merge, remove worktree, delete branch, confirm issue auto-closed. - `/planning:plan close-out`: paste this PLAN.md into the PR body `<details>` block, prune `docs/topics/832-go-ecosystem/` before merge. **Sanity Check:** `gh pr view <N> --json state -q .state` returns `MERGED`; `gh issue view 832 --json state -q .state` returns `CLOSED`. ## Review history An independent fresh-context code review ran before PR creation and found one CRITICAL and two lower-severity items, all addressed and re-verified before opening the PR: 1. **CRITICAL — generated-file guard only checked the first non-blank line, missing the common license-header-then-marker layout** (and two related defeat vectors: CRLF, UTF-8 BOM). Reviewer empirically reproduced the miss against a real generated-file shape (a copyright header plus a `stringer`-style marker) and confirmed the hook silently rewrote it. Fixed: the guard now scans the file's full leading comment/blank-line run (stopping at the first non-comment, non-blank line) per Go's own stated convention, strips a trailing CRLF and leading BOM per line before matching. Five new regression cases added (license-header preamble, CRLF, BOM, and a negative case proving a marker appearing after the leading block does NOT suppress a real edit) — see Open Decision 1 above for the full before/after. 2. **SUGGESTION — stderr capture used an unnecessary `mktemp` file**, inconsistent with every other hook in the repo's simpler command-substitution idiom. Simplified to match. 3. **SUGGESTION — this PLAN's own Phase 1 Sanity Check claimed an existing repo script validates ecosystem yaml against `ecosystem.schema.json`; no such CI job or script exists.** Corrected the claim (see Phase 1's Sanity Check above) — the schema validation itself was and remains correct (verified manually via `check-jsonschema`), only the "reuse an existing script" framing was wrong. This is a pre-existing repo-wide CI-coverage gap, out of scope here. **On-PR review round** (`claude-review`, `security-review`, and Codex, both pushes) surfaced eight more findings. Six confirmed and fixed, two evaluated and consciously left as-is: 1. **CRITICAL-equivalent (Codex P2, independently confirmed via `go help generate` live) — the generated-file guard still missed a `/* ... */` block-comment preamble.** The authoritative convention text ("This line must appear before the first non-comment, non-blank text in the file") does not restrict "comment" to `//` style; the guard only treated `//` lines as comment-continuation, so a block-comment license header (e.g. `/* Copyright ... */` before `// Code generated`) caused premature loop termination — the same failure class as the pre-PR CRITICAL finding, just a different comment syntax. **Not accepted as "zero practical risk"** (one review pass argued this) — fixed properly: the guard now tracks open `/* */` blocks and continues scanning through them. New regression case (5f) added. 2. **Real correctness gap (Codex P2) — `goimports` ran with no `-local` grouping prefix,** so a repo already formatting with `-local` (a common Go convention wired into CI/editor config) would have every edit re-collapse its local-import grouping back into the third-party group — empirically confirmed this materially changes output (verified with a real third-party import present). This directly undercut Open Decision 1's "no config-divergence axis" premise a second time. Fixed: the hook now derives `-local` from the edited file's own module path (`go list -m`, walks to the nearest `go.mod` via Go's own resolution) when a `go` toolchain is present — zero new consumer-config surface, covers the single most common `-local` use case (self-grouping), gracefully degrades to goimports' plain default when `go` is absent or the file isn't in a resolvable module. New regression case (4b) added. 3. **Security SUGGESTION (both security-review passes) — missing `--` end-of-flags separator before `$FILE`.** Low-confidence defense-in-depth; fixed alongside the `-local` change. 4. **Codex P2 — `go-mod-tidy-drift` gate only triggered on `go.mod`/`go.sum`, missing source-only tidy drift** (e.g. removing the last usage of a dependency leaves `go.mod` over-declared while `go build`/`go test` still pass). One review pass called this an acceptable tradeoff citing the `python.yaml` precedent; **not accepted** — the epic's own stated success criterion is "CI/local parity," and leaving this gap directly contradicts it. Fixed: `trigger-globs` widened to include `*.go`. Remediation text also now notes the Go 1.23+ floor for `go mod tidy -diff` (a separate LOW finding from the first review pass). 5. **Codex P2 — `/toolchain:lint`'s "Per-project walking" list enumerated python/typescript only, omitting `go`** despite `go.yaml` declaring `project-discovery: ["go.mod"]` — a monorepo with a nested Go module would have `/toolchain:lint go` run `golangci-lint run ./...` from the wrong root. Fixed: added a `go` bullet. 6. **LOW — README described the generated-file guard as checking only the "first non-blank line,"** stale after the pre-PR CRITICAL fix. Corrected to describe the actual leading-block scan. 7. **COSMETIC, evaluated and left as-is — BOM-stripping runs on every loop line, not just the first.** A UTF-8 BOM can only appear at byte 0, so this is a harmless no-op after line one, not a bug. Left unchanged (the fix would add branching complexity for zero behavioral gain). 8. **COSMETIC — key ordering differed between the bundled `go.yaml` (`gates` before `install-hint`) and the example fixture (`install-hint` before `gates`).** Aligned the bundled file to the example's ordering (which matches the `dotnet.yaml` example precedent). ## Blast radius **MEDIUM.** New plugin + new ecosystem entry, but both are close pattern-replications of two already-merged, already-reviewed precedents (typos-format PR #872, dotnet opt-in gate PR #890) in this same epic. The two genuine judgment calls (goimports-unconditional, golangci-lint-opt-in) are each backed by fresh primary-source research and a direct analogy to an already-accepted precedent in this repo — not novel territory. No cross-module architectural change, no data-model change, no multi-tenant concern. Several implementation-time facts (exact goimports flags, exact golangci-lint install command, `go mod tidy` dry-run flag syntax) are flagged as **verify-live, don't assume** — this is where a stress-test/implementation-time slip is most likely, not in the two locked design decisions. ## Stress-test summary Blast radius MEDIUM — no CRITICAL/HIGH triggers (no cross-module integration, no data-model change, no multi-tenant posture) per `context/stress-test-triggers.md`'s criteria, but the two load-bearing judgment calls (Open Decisions 1 and 2) warrant the mandatory Step 3 fresh-context plan-reviewer pass before implementation, specifically pressure-testing: (a) is "goimports unconditional, no opt-in" actually safe, or does goimports have a config-divergence axis this session's research missed; (b) is the golangci-lint opt-in gate correctly scoped (does it match how `check-cmd` composes with `fix-cmd` the same way python/dotnet's gates do, and is the ancestor-walk boundary choice — repo-root-only, no `root = true`-equivalent — actually correct for golangci-lint's own config discovery, which may differ from EditorConfig's semantics). *(Dispatched separately as the mandatory Step 3 fresh-context plan-reviewer sub-agent — findings folded in before implementation begins.)* ## Execution shape Sequential — Phase 1 (ecosystem yaml) and Phase 2 (hook plugin) touch disjoint files and share no data dependency (Phase 2 doesn't consume Phase 1's output), so they are parallel-safe by the file-overlap test, but the combined scope is small enough (~14 files total) that the coordination overhead of a two-agent split isn't worth it for a MEDIUM-blast-radius, largely-mechanical replication lane. Single main-session sequential execution, Phase 1 then Phase 2, then Phase 3 cross-cutting verification gates both. Phase 3 is fully sequential-dependent on both. ## Open questions None blocking — all flagged "verify live during implementation" items are execution-time fact lookups (exact CLI flags/install commands), not design decisions requiring further user input. ## Handoff to implementation ### User-approval gates None beyond initial plan approval — no `[FALLBACK]` tags, no scope-expansion proposals anticipated. If live verification during Phase 1/2 surfaces that `goimports` or `golangci-lint` behave materially differently than researched (e.g., goimports turns out to have a real config-divergence axis), STOP and re-open Open Decision 1 rather than silently proceeding. ### Execution shape (`[EXEC-SHAPE]` tagged) - `[EXEC-SHAPE]` Single PR bundling both deliverables (Open Decision 3). - `[EXEC-SHAPE]` Sequential single-main-session execution (Execution shape section above). - `[EXEC-SHAPE]` `go-format` hook plugin structural simplification (no ancestor config walk-up) — Open Decision 1's consequence. ### Mechanical work Commit boundaries: one commit per phase is reasonable (Phase 1, Phase 2, then fixup commits for review findings) but not mandated — squash-merge means the final PR history is one commit anyway. Verification checkpoints: run the full local gate set after each phase, not just once at the end, to catch cross-phase interactions (e.g., `plugin.json`/`CHANGELOG.md` version-bump conflicts between the toolchain-plugin edit in Phase 1 and any repo-root catalog regen in Phase 2) early. Sequential fallback: N/A (already sequential). </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…opic is fully landed (#1004) ## Summary `docs/topics/lint-static-analysis-gaps/PLAN.md` is a contract-tier topic doc per `docs/conventions/topic-docs/README.md`: committed on the task branch only, pruned before merge once the topic's outcome lands (§"Contract-slice lifecycle"). It was added by PR #829 and never pruned. Epic #830 and all 6 sub-issues (#831-#836) plus the related setup-lifecycle epic (#837) are now merged and closed, so the topic's outcome has fully landed — pruning now, mirroring the same fix already applied to #836's leaked plan doc in PR #966. Content preserved for the record (from commit `4a289ab389`, PR #829): <details> <summary>Epic Brief (pruned)</summary> # Lint / static-analysis gaps ## Brief ### TLDR Close the fleet's lint/static-analysis gaps with one epic in this repo (typos hook plugin, Go coverage in both lanes, lychee-offline + pyright batch additions, .NET runtime guard, hook-observability convention, bespoke-guard routing), plus two epics owned elsewhere (setup-lifecycle convention here as its own design effort; standards adoption sweep + ecosystem-declaration in `melodic-software/standards`). ### Goal Local lanes catch what CI gates, so agents fix findings at edit time instead of burning commit–push–CI round-trips. Every addition follows the plugin philosophy: consumer-config-driven, zero shipped opinions, runtime detection over assumptions, advisory hooks that auto-fix silently and surface only residual unfixables. Epic sub-items (discuss-first honored — file only on explicit request; epic + sub-issues shape): 1. **typos hook plugin** — per-file autofix (`typos -w`), consumer `_typos.toml` ancestor walk-up, false-positive remediation via consumer allowlist entries (`extend-words` / `extend-identifiers` / `extend-ignore-re`), advisory residual-only context, hook-precision + hook-telemetry conventions. 2. **Go coverage, both lanes** — new `go` batch ecosystem default (golangci-lint v2, format, `go mod tidy`; govulncheck optional) and a Go format hook plugin. Formatter selection (gofmt / goimports / gofumpt / `golangci-lint fmt`) is an implementation-time field survey per the pick-for-the-problem discipline; criteria: official/authoritative, maintained, feature-fit. golangci-lint is batch-only by design (package-scope analysis, per its FAQ). 3. **lychee-offline** added to `cross-cutting.yaml` (on-disk link/anchor integrity; gating in CI, no network). 4. **pyright** added to `python.yaml` check-cmd (CI gates it; local batch was ruff-only). 5. **.NET batch guard** — `dotnet` ecosystem entry detects analyzer/`.editorconfig` configuration presence at runtime each run; skips with a visible notice when absent. No assumptions about consumer state; .NET stays batch-lane only. 6. **Hook-observability fleet convention** — every fleet hook emits `statusMessage` (during run), `systemMessage` (failure/notable action), and the hook-telemetry OTel envelope. Grounded in current official hooks docs at authoring time (no native user-visible hook UI exists as of 2026-07-21; OTel events + author-emitted messages are the sanctioned surfaces). Optional sub-item: upstream feature request for a native verbose-hooks UI toggle. 7. **Bespoke CI guards** (comment-hygiene, exec-bit, machine-specific-paths, reference-integrity) — sub-issue routed to `ci-workflows`/`standards`: local lane must invoke the same owned source (pointer-not-copy), which needs a small distribution decision those repos own. ### Constraints - Plugin philosophy governs: repo/user/machine/org-agnostic, two-lane convention posture, native-first, cross-platform (Windows/macOS/Linux), setup contract, graceful degradation. - Lane rule (locked): fast (<~2s), per-file, auto-fixing, consumer-config-discovering tool = hook plugin; slow / repo-wide / package-scope tool = toolchain batch entry; both when both fit. - Hooks auto-fix silently, never block, surface only residual unfixables (markdown-format pattern); hook-precision convention bounds false-positive noise. - New plugins conform to the setup-lifecycle convention once that epic lands. ### Acceptance criteria - Each epic sub-item lands as its own planned change with the normal pipeline (explore/research → plan → implement → review). - typos + Go hook plugins pass the plugin contract gate and fleet conformance audit. - Batch additions (go, lychee-offline, pyright, dotnet guard) are rung-4 defaults only — consumer `.claude/ecosystems/*.yaml` override ladder unchanged. - Hook-observability convention documented as an owner doc (convention registry row) and adopted by every fleet hook; conformance audited. - CI/local parity: gaps identified 2026-07-21 (Go toolchain, lychee-offline, pyright) have local coverage. ### Captured assumptions - No work-machine tool-install restriction (winget/brew acceptable). User to correct if wrong. - Single Go repo today (`ci-runner`); Go hook plugin justified by completeness preference (user choice) despite one consumer. ### Out of scope - Standards distribution-model redesign — model is settled (ADR-0001, accepted 2026-07-10); dissatisfaction, if it persists after reading the ADR rationale, is an ADR-supersede discussion in `standards`. - Consumer-config adoption gaps (ruff/pyright targets, dotnet-analysis to itinerary-planner / medley, TS/JS component admission, medley lychee) — standards epic below. - Setup-lifecycle convention design — own epic below. ### Deferred questions - YAML lint/format plugin — arbiter: USER-RESERVED. Trigger: a CI YAML gate lands in the fleet, or Biome ships YAML support (unshipped as of the 2026 roadmap). Facts: yamllint validate-only; yamlfmt autofixes but imposes defaults without consumer config. - gitleaks per-edit hook — arbiter: USER-RESERVED. Trigger: a local leaked-secret incident. Facts: `dir` mode (detect/protect deprecated v8.19), entropy false-positive noise, no autofix. - `dotnet format whitespace --folder` fast path — arbiter: USER-RESERVED. Whitespace-only today (bypasses MSBuild/restore; the only sub-2s path — full/style/analyzer modes pay project-load, ~1.3s minimum single file). Links: <https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-format>, <dotnet/format#757>. Trigger: an MSBuild-free style/analyzer path appears upstream. - Upstream feature request for native verbose-hooks UI — arbiter: /planning:plan (optional sub-item of the observability convention). ### Related epics (owned elsewhere) - **Setup-lifecycle convention** (this repo, own design session) — DONE (#837, PR #990). - **Standards adoption sweep + ecosystem-declaration enhancement** (`melodic-software/standards`) — still open, separate repo (`standards#230`). ## Plan (Empty — `/planning:plan` fills this per epic sub-item.) </details> ## Verification - [x] `git rm -r` only — no other changes; no build/test impact. - [x] Content fully preserved in git history (commit `4a289ab389`) and in this PR body. ## Related No linked issue — epic #830 is already closed (manually, since all 6 sub-issues plus the related setup-lifecycle epic #837 were done); this is a follow-up hygiene fix, not new epic work. Related PRs: #829 (added the brief), #966 (same fix applied to #836's leaked plan doc), #990 (#837, the last related item to land before this cleanup). --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Summary
Interview contract (Brief) for the lint/static-analysis gap epic: typos hook plugin, Go coverage (batch + hook), lychee-offline + pyright batch additions, .NET runtime guard, hook-observability fleet convention, bespoke-guard routing. Plan section intentionally empty — filled per sub-item by /planning:plan.
Epic + sub-issues follow; will be linked once filed.
No linked issue: this PR lands the Brief only; execution issues are tracked separately.
Related
🤖 Generated with Claude Code
https://claude.ai/code/session_019KVQkSXBKY8pcCSNKUnP3g