You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .claude/knowledge/testing-patterns.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,4 +7,8 @@ Testing strategies, test infrastructure quirks, how to run/debug specific test s
7
7
- All tests live under `tests/` as external (`_test`) packages; there are no white-box tests inside `internal/`/`pkg/`. Internal packages are still importable from `tests/` because they are within the module. This is enforced by `ci.test-file-location` (the repo's own scan fails on `_test.go` files outside `tests/**`), so do not add tests next to the code even for unexported access — import the internal package from `tests/` and test via its exported API instead.
8
8
-**Never commit a contiguous real-format secret as a test fixture.** GitHub push protection rejects the push (it flags GitLab/Stripe/SendGrid/Twilio/etc. tokens even in `_test.go` files), and it is poor practice in a secret-detection repo. Assemble fixtures at runtime from a prefix + body via the `cred(prefix, body)` helper in `tests/checks/test_helpers_test.go` (e.g. `cred("AKIA", "1234567890ABCDEF")`), splitting the recognizable provider prefix from the rest so no full token literal appears in source. The reconstructed value still exercises the scanner identically. `goConst(value)` wraps a value as a minimal Go source fixture.
9
9
- The MCP server smoke tests (`tests/mcp/`) drive the **real binary in a subprocess**, not the in-process handler, to keep tests external. Pattern: a `Test...HelperProcess` func gated by an env var (`GO_WANT_MCP_HELPER_PROCESS` for stdio, `GO_WANT_MCP_HTTP_HELPER_PROCESS` for HTTP) re-execs `os.Args[0]` and calls `cli.Run(...)`. stdio replays NDJSON transcripts over stdin; HTTP reserves a free `127.0.0.1:0` port, launches `serve --mcp --http`, polls `/healthz` for readiness, then issues real HTTP requests. Add new MCP behavior as a transcript + assertion (stdio) or a subtest in `http_test.go` (HTTP).
10
+
-**Detector precision corpus** (`tests/corpus/`): fixtures live under `testdata/<language-group>/<rule>/{vulnerable,clean}/` with ground truth in `expectations.yaml`. `known_gaps` entries assert a documented FP/FN *still exists* — when you improve a detector the corpus test fails on purpose with a "promote it to must_fire / remove the known_gaps entry" message; update the manifest in the same change. Fixture credentials must be synthetic but pattern-shaped (see the secret-fixture rule above).
11
+
-**Every catalog rule must ship a `FixTemplate`** with a valid `Kind` (`deterministic`|`guided`) — `TestSDKRuleMetadataFixTemplatesPopulated` iterates the full catalog and fails on any rule without one. When adding a rule, add its template to the family map in `internal/codeguard/rules/catalog_fix_templates_*.go` (or inline in the catalog entry; inline wins over the registry).
12
+
-`gofmt -l .` at repo root is polluted by `.claude/worktrees/` (live agent worktrees) and `.gomodcache/`; scope it to `gofmt -l cmd internal pkg tests changelog.go` or use `make fmt-check`.
10
13
-**Bidirectional (server→client) MCP tests** live in `tests/mcp/sampling_test.go`: the test acts as the MCP client, advertises `sampling`/`roots` at `initialize`, and answers the server's server-initiated requests. stdio uses interactive `StdinPipe`/`StdoutPipe` (not the replay harness). HTTP opens the `GET /mcp` SSE stream (waits for the `: ready` comment to avoid the attach race), reads the request off the stream, and POSTs the response with the matching `Mcp-Session-Id`. propose_fix verification is expected to fail on the throwaway diff — assert the round trip fired, not a verified patch. The HTTP helper passes `-config` via `CODEGUARD_TEST_HTTP_CONFIG`.
14
+
-**TS tests can be hijacked by the Node semantic engine**: on hosts with a discoverable `typescript.js` (e.g. VS Code installed), TypeScript targets route through the Node runner instead of the per-file Go path. Tests that must exercise the per-file path (tree-sitter differential tests, corpus TS groups) set `CODEGUARD_TYPESCRIPT_LIB_PATH` to an existing-but-invalid lib to force the fallback.
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)
Copy file name to clipboardExpand all lines: docs/checks.md
+85-1Lines changed: 85 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,13 +12,16 @@ This file documents the current check categories in `codeguard` and the config k
12
12
"security": true,
13
13
"prompts": true,
14
14
"ci": true,
15
-
"supply_chain": false
15
+
"supply_chain": false,
16
+
"context": true
16
17
}
17
18
}
18
19
```
19
20
20
21
Each top-level boolean enables or disables an entire check family.
21
22
23
+
`context` covers agent-context legibility: when the key is omitted the family defaults to enabled in full scans and disabled in diff scans; see [Agent Context](#agent-context).
24
+
22
25
`supply_chain` is opt-in and currently covers normalized manifest parsing plus initial policy checks for missing lockfiles, content-based lockfile drift validation, unpinned dependencies, and dependency license policy resolved from local manifest and installed metadata where available.
23
26
24
27
For ecosystems where local metadata is not present, `supply_chain_rules.license_commands` can provide an opt-in per-ecosystem command that prints JSON license mappings for unresolved dependencies.
- baseline fingerprints are filtered before section status is computed
152
+
- each entry stores two fingerprints: the legacy line-based one
153
+
(`fingerprint`) and a context fingerprint (`context_fingerprint`) hashed
154
+
from the rule, path, and the whitespace-normalized source lines around the
155
+
finding (2 lines either side), so unrelated edits that only shift line
156
+
numbers do not break suppression
157
+
- a finding is suppressed when either fingerprint matches; two identical
158
+
findings in the same file (same rule and surrounding source) share a context
159
+
fingerprint, so baselining one also baselines its identical twins
160
+
- baseline files written before context fingerprints existed keep working:
161
+
their legacy fingerprints still match unchanged findings
149
162
- suppressed counts remain visible in the report summary
150
163
151
164
## Policy profiles
@@ -221,6 +234,21 @@ TypeScript semantic runtime:
221
234
- discovery order is `CODEGUARD_TYPESCRIPT_LIB_PATH`, then `node_modules/typescript/lib/typescript.js` from the target path upward, then the bundled VS Code TypeScript runtime
222
235
- if no runtime is available, codeguard falls back to the lightweight parser-based checks for TypeScript and JavaScript
223
236
237
+
Tree-sitter parsing (opt-in):
238
+
-`parsers.treesitter: "auto"` (default `"off"`) routes the lightweight
239
+
TypeScript/TSX/JavaScript checks through embedded tree-sitter grammars
240
+
instead of regexes for `quality.typescript.explicit-any`,
241
+
`quality.typescript.non-null-assertion`,
242
+
`quality.typescript.double-assertion`, and
243
+
`security.typescript.unsafe-html-sink` (plus their `*.javascript.*`
244
+
mirrors where the syntax exists in JavaScript)
245
+
- tree-based findings keep the same rule IDs, levels, and messages and set
246
+
`confidence: high`; they see through template-literal interpolations,
247
+
regex literals, JSX text, formatter-split expressions, and compound
248
+
assignments where the regex path cannot
249
+
- oversized files (> 256 KiB), parse failures, and error-heavy trees fall
250
+
back to the regex path per file
251
+
224
252
## Quality
225
253
226
254
Purpose:
@@ -685,6 +713,62 @@ Rules:
685
713
-`ci.always-true-test-assertion` warns when every assertion in a test only compares constants (`expect(true).toBe(true)`, `assert 1 == 1`, `require.True(t, true)`), so the test can never fail
686
714
-`ci.conditional-assertion` warns when every assertion in a test sits inside a conditional without an else branch, so the assertions may silently never run; idiomatic Go failure checks (`if got != want { t.Errorf(...) }`) are not flagged
- Drift between agent docs / README commands and the actual repository
721
+
- Agent context budget for individual source files
722
+
- Basename ambiguity that defeats filename-based navigation
723
+
- A `repo_legibility` artifact scoring how legible the repository is to AI agents
724
+
725
+
Config keys:
726
+
727
+
```json
728
+
{
729
+
"checks": {
730
+
"context": true,
731
+
"context_rules": {
732
+
"detect_missing_agent_docs": true,
733
+
"detect_agent_docs_drift": true,
734
+
"detect_readme_drift": true,
735
+
"detect_oversized_files": true,
736
+
"detect_ambiguous_symbols": true,
737
+
"max_file_lines": 1500,
738
+
"ambiguous_symbol_threshold": 4
739
+
}
740
+
}
741
+
}
742
+
```
743
+
744
+
When `checks.context` is omitted the family runs in full scans and is skipped in diff scans: its signature findings are repo-level (missing agent docs, duplicated basenames) and would repeat on every PR regardless of the change under review. Set `"context": true` to force it on in diff scans, or `false` to disable it entirely.
745
+
746
+
Current behavior:
747
+
-`context.agent-docs-missing` warns once at repo level when none of the recognized agent instruction files exist at the target root
748
+
-`context.agent-docs-drift` warns when an agent instruction file references a file or directory path, a `make` target, or an npm/pnpm/yarn `run` script that provably does not exist
749
+
-`context.readme-drift` applies the same resolution to fenced `bash`/`sh`/`shell` blocks in the root README.md: `./`-prefixed paths, make targets, and run scripts that resolve nowhere
750
+
-`context.oversized-context-unit` warns when a source file exceeds `context_rules.max_file_lines` (default 1500); the message is framed as agent context cost, distinct from `quality.max-file-lines` maintainability thresholds; generated and vendored files are skipped
751
+
-`context.ambiguous-symbol` warns once per source-file basename shared by at least `context_rules.ambiguous_symbol_threshold` files (default 4), listing up to five locations
752
+
753
+
Drift resolution is deliberately conservative — precision over recall. It only flags references it can positively prove broken, and skips:
754
+
- URLs, module/domain paths (`github.com/...`), absolute paths, and `..` traversals
755
+
- placeholders and expansions (`<name>`, `$VAR`), globs, and template syntax
756
+
- all fenced blocks except shell command fences (code samples and captured output are never treated as paths)
757
+
- shell blocks after a `cd`/`pushd` or a heredoc, and `make -C`/`-f` invocations that select another makefile
758
+
- make targets when no root Makefile exists or the Makefile uses `include` or pattern rules
759
+
- npm scripts when there is no root package.json or it declares workspaces
760
+
761
+
`repo_legibility` artifact:
762
+
763
+
Every context run publishes one `repo_legibility` artifact per target with a 0-100 score (higher is more legible) and an explainable component breakdown:
764
+
-`agent_docs` (25): any agent instruction file present
765
+
-`readme` (10): root README.md present
766
+
-`doc_accuracy` (20): minus 4 points per unresolvable doc/README reference
767
+
-`context_economy` (25): scaled down by the share of source files over the context budget (10% oversized zeroes it)
768
+
-`navigability` (20): scaled down by the share of source files caught in ambiguous basename groups (20% affected zeroes it)
769
+
770
+
The artifact is emitted even when individual rules are toggled off, so the score always reports reality.
|`security.log-secret-exposure`| A09 | secret-named identifiers (password, token, api_key, …) inside the argument list of a Go/Python/TS/JS logging call, secret-named structured-log keys, and secret-labeled string literals concatenated or format-directed into log output |
104
+
|`security.unsanitized-error-response`| A09 | raw error values written directly into HTTP responses: Go `http.Error(w, err.Error(), …)` / `fmt.Fprintf(w, …, err)`, TS/JS `res.send(err)` / `res.json(err)` / `res.status(…).send(err.stack \|\| err.message)`, Python `return str(e)` / `HttpResponse(str(e))` inside `except` blocks |
102
105
|`security.ssrf.go` / `security.ssrf.python`| A10 | untrusted input flowing into an outbound HTTP request URL |
0 commit comments