Rule Analysis Demo¶
Example output from the rule-based analysis engine. This report was generated by running
scripts/generate_rule_analysis_report.pyagainst intentionally flawed fixture files in 5 languages (Python, Go, TypeScript, Rust, Java).Each finding includes code context, explanations, fix suggestions, and bad-vs-good examples β everything the connected coding agent needs to triage and fix issues without additional tool calls.
To generate your own:
python scripts/generate_rule_analysis_report.py --path /your/project
Summary¶
| Metric | Value |
|---|---|
| Rules loaded | 150 (5 packs) |
| Findings | 19 across 5 files |
| π High | 1 |
| π‘ Medium | 12 |
| π΅ Low | 5 |
| βͺ Info | 1 |
By Category¶
| π¨ style | βββββββ 7 | | π correctness | ββββββ 6 | | β‘ performance | ββββββ 6 |
Severity Distribution¶
critical ββββββββββββββββββββββββββββββββββββββββ 0 (0%)
high ββββββββββββββββββββββββββββββββββββββββ 1 (5%)
medium ββββββββββββββββββββββββββββββββββββββββ 12 (63%)
low ββββββββββββββββββββββββββββββββββββββββ 5 (26%)
info ββββββββββββββββββββββββββββββββββββββββ 1 (5%)
Findings by File¶
go/bad_patterns.go go¶
π Line 18: go-no-panic¶
15 | func HandleRequest(name string) string {
16 | // Team preference: no panic
17 | if name == "" {
> 18 | panic("name cannot be empty") // expect: go-no-panic
19 | }
20 |
21 | // Builtin: string comparison without EqualFold
HIGH | π correctness | Confidence: 90%
What: Don't panic β return errors instead
Why this matters
In our services, panics crash the entire process. Go's error return convention exists specifically to avoid this. Only panic for truly unrecoverable programmer errors in init().
Fix: Return error instead: return fmt.Errorf(...)
π‘ Line 11: go-no-init-function¶
8 | "strings"
9 | )
10 |
> 11 | func init() { // expect: go-no-init-function
12 | fmt.Println("initializing")
13 | }
14 |
MEDIUM | π¨ style | Confidence: 85%
What: Avoid init() β use explicit initialization for testability
Why this matters
init() functions run implicitly at import time, making it impossible to control initialization order in tests. Use an explicit Setup() or NewService() pattern instead.
Fix: Move initialization to an explicit constructor or Setup function
π‘ Line 22: go/go-string-tolower-compare¶
19 | }
20 |
21 | // Builtin: string comparison without EqualFold
> 22 | if strings.ToLower(name) == "admin" { // expect: go-string-tolower-compare
23 | return "admin dashboard"
24 | }
25 |
MEDIUM | β‘ performance | Confidence: 85% | Pack: go
What: strings.ToLower() allocates a new string for comparison β use EqualFold
Why this matters
strings.ToLower() creates a new lowercase copy of the string just to compare it. strings.EqualFold() compares case-insensitively without any allocation.
Fix: Use strings.EqualFold(a, b) for case-insensitive comparison
π΅ Line 27: go/go-sprintf-allocation¶
24 | }
25 |
26 | // Builtin: error string comparison
> 27 | return fmt.Sprintf("hello %s", name) // expect: go-sprintf-allocation
28 | }
29 |
30 | func CleanFunction(x int) int {
LOW | β‘ performance | Confidence: 50% | Pack: go
What: fmt.Sprintf allocates a new string per call β consider alternatives in hot paths
Why this matters
fmt.Sprintf parses the format string and allocates a new string on every invocation. In hot loops, this creates N allocations that pressure the GC. Not every Sprintf is a problem β only flag this in performance-sensitive paths.
Fix: For hot paths, use strings.Builder, strconv.Itoa + concatenation, or pre-allocate
java/BadPatterns.java java¶
π‘ Line 14: java/java-string-concat-loop¶
11 | // Builtin: string concat in loop
12 | String result = "";
13 | for (String item : items) {
> 14 | result += "item: " + item; // expect: java-string-concat-loop
15 | }
16 | System.out.println("Done: " + result); // expect: java-no-system-out
17 | return result;
MEDIUM | β‘ performance | Confidence: 60% | Pack: java
What: String concatenation in loop β O(N^2) due to immutable strings
Fix: Use StringBuilder for loop concatenation
for (String s : items) sb.append(s); ```
π‘ Line 16: java-no-system-out¶
13 | for (String item : items) {
14 | result += "item: " + item; // expect: java-string-concat-loop
15 | }
> 16 | System.out.println("Done: " + result); // expect: java-no-system-out
17 | return result;
18 | }
19 |
MEDIUM | π¨ style | Confidence: 90%
What: Use SLF4J logger instead of System.out
Why this matters
System.out bypasses the logging framework. Log output won't appear in structured logs, can't be filtered by level, and won't include context (thread, timestamp, class).
Fix: Use log.info(), log.debug(), etc.
π‘ Line 23: java/java-catch-exception¶
20 | public void riskyMethod() {
21 | try {
22 | loadData();
> 23 | } catch (Exception e) { // expect: java-catch-exception
24 | System.out.println(e.getMessage()); // expect: java-no-system-out
25 | }
26 | }
MEDIUM | π correctness | Confidence: 70% | Pack: java
What: Catching generic Exception β too broad, may mask bugs
Fix: Catch specific exception types
π‘ Line 24: java-no-system-out¶
21 | try {
22 | loadData();
23 | } catch (Exception e) { // expect: java-catch-exception
> 24 | System.out.println(e.getMessage()); // expect: java-no-system-out
25 | }
26 | }
27 |
MEDIUM | π¨ style | Confidence: 90%
What: Use SLF4J logger instead of System.out
Why this matters
System.out bypasses the logging framework. Log output won't appear in structured logs, can't be filtered by level, and won't include context (thread, timestamp, class).
Fix: Use log.info(), log.debug(), etc.
python/bad_patterns.py python¶
π‘ Line 8: no-star-import¶
5 | and no unexpected findings appear.
6 | """
7 | import logging
> 8 | from utils import * # expect: no-star-import
9 |
10 | logger = logging.getLogger(__name__)
11 |
MEDIUM | π¨ style | Confidence: 95%
What: Star imports pollute namespace and break static analysis
Why this matters
Star imports make it impossible to determine where a name came from without running the code. They also prevent dead-code detection and auto-completion from working correctly.
Fix: Import specific names: from module import Class, function
π‘ Line 16: no-print-debugging¶
13 | def process_items(items: list, config: dict) -> None:
14 | """Process items with several bad patterns."""
15 | # Team preference: no print debugging
> 16 | print(f"Starting with {len(items)} items") # expect: no-print-debugging
17 |
18 | # Team preference: no bare dict access
19 | api_key = config['api_key'] # expect: no-bare-dict-access
MEDIUM | π¨ style | Confidence: 85%
What: Use logger instead of print() β prints are stripped in CI
Why this matters
Our CI pipeline redirects stdout to /dev/null. print() calls are invisible in production. Use the project logger which writes to both stdout and the structured log file.
Fix: Replace with logger.info(), logger.debug(), etc.
π‘ Line 25: no-print-debugging¶
22 | logger.info(f"Using key {api_key[:4]}...") # expect: no-string-format-logging
23 |
24 | for item in items:
> 25 | print(item.name) # expect: no-print-debugging
26 | status = config['default_status'] # expect: no-bare-dict-access
27 |
28 |
MEDIUM | π¨ style | Confidence: 85%
What: Use logger instead of print() β prints are stripped in CI
Why this matters
Our CI pipeline redirects stdout to /dev/null. print() calls are invisible in production. Use the project logger which writes to both stdout and the structured log file.
Fix: Replace with logger.info(), logger.debug(), etc.
π΅ Line 19: no-bare-dict-access¶
16 | print(f"Starting with {len(items)} items") # expect: no-print-debugging
17 |
18 | # Team preference: no bare dict access
> 19 | api_key = config['api_key'] # expect: no-bare-dict-access
20 |
21 | # Team preference: no f-string in logger
22 | logger.info(f"Using key {api_key[:4]}...") # expect: no-string-format-logging
LOW | π correctness | Confidence: 60%
What: Use .get() for dict access β bare [] throws KeyError on missing keys
Why this matters
In our data pipeline, configs and API responses frequently have optional keys. Bare dict[key] access crashes on missing keys. Use .get(key, default) for resilient access.
Fix: Use config.get('key', default) instead of config['key']
π΅ Line 22: no-string-format-logging¶
19 | api_key = config['api_key'] # expect: no-bare-dict-access
20 |
21 | # Team preference: no f-string in logger
> 22 | logger.info(f"Using key {api_key[:4]}...") # expect: no-string-format-logging
23 |
24 | for item in items:
25 | print(item.name) # expect: no-print-debugging
LOW | β‘ performance | Confidence: 80%
What: Use lazy % formatting in logger calls, not f-strings
Why this matters
f-strings are evaluated immediately even if the log level is disabled. Logger % formatting (logger.info("x=%s", x)) is lazy β the string is only formatted if the message will actually be emitted.
Fix: Use logger.info("Processing %s", item) not logger.info(f"Processing {item}")
π΅ Line 26: no-bare-dict-access¶
23 |
24 | for item in items:
25 | print(item.name) # expect: no-print-debugging
> 26 | status = config['default_status'] # expect: no-bare-dict-access
27 |
28 |
29 | def calculate_total(prices: list[float]) -> float:
LOW | π correctness | Confidence: 60%
What: Use .get() for dict access β bare [] throws KeyError on missing keys
Why this matters
In our data pipeline, configs and API responses frequently have optional keys. Bare dict[key] access crashes on missing keys. Use .get(key, default) for resilient access.
Fix: Use config.get('key', default) instead of config['key']
rust/bad_patterns.rs rust¶
π‘ Line 9: rs-no-expect-in-lib¶
6 |
7 | pub fn load_config(path: &str) -> String {
8 | // Team preference: library code should return Result, not expect
> 9 | let content = fs::read_to_string(path).expect("failed to read config"); // expect: rs-no-expect-in-lib
10 | content
11 | }
12 |
MEDIUM | π correctness | Confidence: 70%
What: Library code should return Result, not expect/panic
Why this matters
.expect() panics when the Result is Err, crashing the caller. Library code should propagate errors with ? so callers decide how to handle failures.
Fix: Use the ? operator: let value = result?;
π΅ Line 15: rust/rs-clone-in-loop¶
12 |
13 | pub fn process(data: &[u8]) -> Vec<u8> {
14 | // Builtin: clone in potential hot path
> 15 | let copy = data.to_vec().clone(); // expect: rs-clone-in-loop
16 | copy
17 | }
18 |
LOW | β‘ performance | Confidence: 40% | Pack: rust
What: clone() in potential hot path β deep copy may be expensive
Fix: Use references or Cow
typescript/bad_patterns.ts typescript¶
π‘ Line 10: typescript/ts-await-in-loop¶
7 | async function loadAll(ids: string[]) {
8 | // Builtin: sequential await in loop
9 | for (const id of ids) {
> 10 | const data = await fetchUser(id); // expect: ts-await-in-loop
11 | console.log(data); // expect: ts-console-log
12 | }
13 | }
MEDIUM | β‘ performance | Confidence: 55% | Pack: typescript
What: Possible sequential await in loop β requests may execute one at a time
Why this matters
Using await inside a for loop makes each iteration wait for the previous one to complete. If the operations are independent, they can run in parallel with Promise.all(), potentially reducing total time by NΓ.
Fix: Collect promises and use Promise.all() for independent operations
```typescript for (const id of ids) {
const data = await fetch(id); } ```
π‘ Line 17: ts-no-any-cast¶
14 |
15 | function getStatus(response: unknown): string {
16 | // Team preference: no cast to any
> 17 | const data = response as any; // expect: ts-no-any-cast
18 | return data.status;
19 | }
20 |
MEDIUM | π correctness | Confidence: 90%
What: Don't cast to any β use proper type narrowing
Why this matters
Casting to any silently disables all type checking for that value. Bugs that TypeScript would catch at compile time become runtime errors. Use type guards, unknown, or proper interface narrowing.
Fix: Use type guards (if 'key' in obj), unknown, or proper interfaces
βͺ Line 11: typescript/ts-console-log¶
8 | // Builtin: sequential await in loop
9 | for (const id of ids) {
10 | const data = await fetchUser(id); // expect: ts-await-in-loop
> 11 | console.log(data); // expect: ts-console-log
12 | }
13 | }
14 |
INFO | π¨ style | Confidence: 60% | Pack: typescript
What: console.log left in code β use proper logging or remove
Fix: Use a structured logger or remove debug statements
Available Language Packs¶
- go β 12 rules (5x performance, 4x security, 2x correctness, 1x suspicious)
- java β 5 rules (2x performance, 1x security, 1x correctness, 1x style)
- python β 6 rules (2x correctness, 2x performance, 1x complexity, 1x style)
- rust β 4 rules (2x correctness, 1x security, 1x performance)
- typescript β 5 rules (3x style, 1x correctness, 1x performance)
Custom Team Rules¶
- π‘ no-print-debugging β Use logger instead of print() β prints are stripped in CI
- π΅ no-bare-dict-access β Use .get() for dict access β bare [] throws KeyError on missing keys
- π‘ no-star-import β Star imports pollute namespace and break static analysis
- π΅ no-string-format-logging β Use lazy % formatting in logger calls, not f-strings
- π go-no-panic β Don't panic β return errors instead
- π‘ go-no-init-function β Avoid init() β use explicit initialization for testability
- π‘ ts-no-any-cast β Don't cast to any β use proper type narrowing
- π‘ rs-no-expect-in-lib β Library code should return Result, not expect/panic
- π‘ java-no-system-out β Use SLF4J logger instead of System.out