Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/knowledge/architecture-boundaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ Key architectural decisions, service boundaries, data flow, integration points,
- **Git subprocess hardening contract (threat model: untrusted PR).** `base_ref` from MCP/CLI is validated by `runnersupport.ValidateBaseRef` (rejects leading `-` to block git option injection; restricts charset; preserves the `stdin` sentinel) BEFORE reaching git, and `--end-of-options` is passed to git diff/show/worktree. All git/govulncheck subprocesses use `exec.CommandContext` with a contained timeout and read output through `io.LimitReader` (`maxGitOutputBytes`) to prevent hangs/OOM. The `//nolint:contextcheck` markers in the git layer (`runner/support`, `ai/fix`, `ai/semantic`, `checks/quality/quality.go`, `runner/runner.go`) are DELIBERATE — the contained timeout is the chosen design; threading caller `ctx` end-to-end is a tracked follow-up. Don't "fix" them by removing the nolint without actually threading ctx. `runner/support/MatchPattern` (glob→regex) uses `regexp.Compile` + a `sync.Map` cache and returns false (never panics) on a malformed config glob.
- **The usage menu makes one outbound call (opt-out).** Running `codeguard` with no args or `help` renders a "What's New" banner (`internal/whatsnew` + `internal/cli/whatsnew.go`) that queries the GitHub releases API for the latest version. It is disabled when `CODEGUARD_NO_UPDATE_CHECK` is truthy OR `$CI` is non-empty (so CI/automated runs make no surprise call), caches the result for 24h under `os.UserCacheDir()/codeguard/update-check.json`, uses the SSRF-guarded `ai/safehttp.Client` with a 1.5s timeout, and fails silent (falls back to stale cache, then to version-only). Changelog bullets come from `CHANGELOG.md` embedded via a **module-root package** (`/changelog.go`, `package codeguard`, imported as `root "github.com/devr-tools/codeguard"`) — `go:embed` cannot reference `../CHANGELOG.md`, so the embed directive must live at the repo root, not in `internal/`.
- **Lint is enforced in CI.** `make lint` is `go vet`; `make lint-strict` / the CI `golangci-lint-action` step (now blocking — no `continue-on-error`) runs the 16-linter `.golangci.yml`. `golangci-lint run` must report 0 before pushing. The config excludes revive's documentation/naming style rules (`exported`, `package-comments`, `unused-parameter`) and excludes `tests/` from gosec/errcheck/prealloc/bodyclose (intentional fixtures). Cache/identity keys use sha256 (not sha1). Documenting the public `pkg/codeguard` API with doc comments is a known follow-up (revive `exported` is currently excluded rather than satisfied).
- **Adding a check section touches a fixed wiring list; two are silent-failure traps.** Beyond the obvious (check package with `Run(ctx, env) core.SectionResult`, a `sectionDef` in `runner/checks/registry.go`, a `CheckConfig` bool + rules struct in `core/config_types.go`/`config_rule_types.go`, catalog + fix-template maps merged in `rules/catalog.go`/`catalog_fix_templates.go`, defaults in `config/defaults*.go` + `example*.go`, docs): (1) `runner/support/cache_helpers.go` — add the section to `SectionConfigHashes` AND `sectionConfigFamily`, else the section's per-file cache keys fall back to the all-checks fingerprint (correct but invalidates on any config change); (2) top-level `CheckConfig` bools get NO defaulting in `applyCheckDefaults` — an omitted `checks.<section>` key means **off**, which is how the opt-in `performance` and `supply_chain` sections work. The performance section (v0.9.0) is the reference move: rules migrated from quality with renamed ids (`quality.*` → `performance.*`), no runtime aliasing, `rules.RetiredPerformanceRuleIDs()` powers a `codeguard doctor` waiver migration hint.
6 changes: 6 additions & 0 deletions .claude/knowledge/local-dev-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Local Development Setup

How to set up, run, and work with this project locally. Non-obvious dependencies, environment config, common setup issues.

- **The self-scan cache lives at `.codeguard/.codeguard/cache.json`, not `.codeguard/cache.json`.** `cache.path` resolves relative to the config directory (`config.containConfigArtifactPaths`), and `make codeguard-ci` uses `-config .codeguard/codeguard.yaml`, so the default `.codeguard/cache.json` nests. When iterating on **rule logic**, delete that file before re-running `make codeguard-ci`: cache keys cover config + file content + the release-bumped scanner-version constant, so a code-only change to a check serves stale findings from the cache and looks like your fix didn't work.
- The Makefile runs Go via `env -u GOROOT go` (Makefile:6), so `make` targets work even with the shell's stale GOROOT; direct `go` commands need `export GOROOT=/opt/homebrew/opt/go/libexec && export PATH=$GOROOT/bin:$PATH` first.
30 changes: 29 additions & 1 deletion .codeguard/codeguard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ exclude:
- .claude/**
- .codeguard/cache.json
- .codeguard/cache.slop-history.json
- .codeguard/cache.perf-history.json
- .gomodcache/**
- tests/**/.codeguard/cache.json
# Detection-precision fixtures: intentionally vulnerable-looking sample
Expand All @@ -19,9 +20,21 @@ waivers:
- rule: ci.test-without-assertion
path: tests/**/trust_main_test.go
reason: Go TestMain functions bootstrap package-wide trust policy and are not assertion-bearing tests
- rule: quality.unbounded-goroutines-in-loop
- rule: performance.unbounded-goroutines-in-loop
path: internal/codeguard/runner/checks/checks.go
reason: section workers are bounded by a NumCPU-sized semaphore before each goroutine is launched
- rule: performance.unbounded-goroutines-in-loop
path: internal/codeguard/runner/support/findings.go
reason: the per-file scan launches a fixed worker-count pool joined by a WaitGroup, which is the bounding the rule asks for
- rule: performance.unbounded-goroutines-in-loop
path: tests/support/rule_stats_collector_test.go
reason: the concurrency test launches a fixed 16 workers joined by a WaitGroup to prove race-freedom
- rule: performance.go.defer-in-loop
path: internal/codeguard/checks/support/treesitter/bench_cgo_test.go
reason: the design-spike benchmark defers parser cleanup per iteration deliberately; the spike module is excluded from the production build
- rule: performance.go.sleep-in-loop
path: tests/mcp/http_test.go
reason: the HTTP helper-process test polls /healthz for server readiness; a short sleep between probes is the intended pattern
- rule: ci.test-file-location
path: internal/codeguard/checks/support/treesitter/*_test.go
reason: the tree-sitter design spike is an isolated Go module whose differential tests must live beside the prototype they validate (docs/treesitter-spike.md)
Expand All @@ -39,6 +52,7 @@ targets:
- cmd/codeguard
checks:
quality: true
performance: true
design: true
security: false
prompts: false
Expand All @@ -49,6 +63,20 @@ checks:
max_function_lines: 80
max_parameters: 5
max_cyclomatic_complexity: 10
performance_rules:
# Dogfood the measured budget gate on artifacts that always exist in the
# repository (dist/ is not built in CI, so a binary budget would only ever
# report "not found"): the user-facing rule reference must stay readable
# in one sitting.
budgets:
- name: checks-doc
kind: file-size
path: docs/checks.md
max_bytes: 204800
- name: readme
kind: file-size
path: README.md
max_bytes: 65536
ci_rules:
require_workflow_dir: true
required_workflow_files:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ go.work.sum
.idea/
.vscode/
**/.codeguard/cache.slop-history.json
**/.codeguard/cache.perf-history.json
**/.codeguard/cache.rule-stats-history.json
2 changes: 1 addition & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ builds:
# full ~206-grammar registry: ~+4 MB binary delta instead of ~+22 MB.
# See docs/treesitter-spike.md §5.3.
flags:
- -tags=grammar_subset,grammar_subset_typescript,grammar_subset_tsx,grammar_subset_javascript
- -tags=grammar_subset,grammar_subset_typescript,grammar_subset_tsx,grammar_subset_javascript,grammar_subset_python
ldflags:
- -s -w -X github.com/devr-tools/codeguard/internal/version.Number=v{{ .Version }}

Expand Down
29 changes: 29 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# codeguard

Static-analysis CLI that scans repositories (full or PR-diff mode) across check
sections — quality, performance, design, security, prompts, CI, supply chain,
agent context — and reports pass/warn/fail findings per rule.

## Build, test, verify

- `make build` → `dist/codeguard`; `make test`; `make lint` (vet) and `make lint-strict` (golangci-lint, CI-blocking, must be 0 issues)
- `make codeguard-ci` — validate + self-scan with `.codeguard/codeguard.yaml`
- `make check` — the full CI gate (fmt-check, lint, test, codeguard-ci)
- Scope gofmt to `gofmt -l cmd internal pkg tests changelog.go` (repo root picks up worktrees/module caches)
- Tests live under `tests/` as external `_test` packages only — never next to the code (enforced by the repo's own `ci.test-file-location` rule)

## Layout

- `internal/codeguard/checks/<section>/` — check implementations (one package per section)
- `internal/codeguard/runner/` — orchestration, registry, caching; `internal/codeguard/rules/` — rule metadata catalog
- `internal/codeguard/config/` — config load/defaults/validation; `internal/cli/` — command handlers; `pkg/codeguard/` — public SDK facade
- `docs/checks.md` — authoritative user-facing rule/config reference; update it when adding or changing rules

## Available Context

Additional context is available in the files below. Consult the relevant file when working in a related area — see each description for scope.

- `.claude/knowledge/local-dev-setup.md` — Local Development Setup: toolchain quirks, self-scan cache location, common setup issues.
- `.claude/knowledge/testing-patterns.md` — Testing Patterns: trust-policy TestMains, external-test-package rule, secret-fixture assembly, MCP test harnesses, catalog rule test requirements.
- `.claude/knowledge/architecture-boundaries.md` — Architecture & System Boundaries: trust gating for config-supplied commands, safehttp, scanner hardening invariants, section wiring checklist.
- `.claude/knowledge/deployment-release.md` — Deployment & Release: release-please/GoReleaser flow, cosign pinning, npm/PyPI trusted publishing.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ MENU_VERSION ?= $(if $(MANIFEST_VERSION),$(MANIFEST_VERSION),$(VERSION))
MENU_LDFLAGS := -X github.com/devr-tools/codeguard/internal/version.Number=v$(MENU_VERSION)
# GRAMMAR_TAGS restricts the gotreesitter grammar registry to the languages
# codeguard parses (docs/treesitter-spike.md §5.3): the subset build embeds
# only the TypeScript/TSX/JavaScript grammar blobs (~+4 MB) instead of all
# only the TypeScript/TSX/JavaScript/Python grammar blobs instead of all
# ~206 (~+22 MB). Builds without these tags (plain `go build`, `go install`)
# still work; they just embed every grammar.
GRAMMAR_TAGS := grammar_subset,grammar_subset_typescript,grammar_subset_tsx,grammar_subset_javascript
GRAMMAR_TAGS := grammar_subset,grammar_subset_typescript,grammar_subset_tsx,grammar_subset_javascript,grammar_subset_python

export GOCACHE
export GOMODCACHE
Expand Down
Loading
Loading