All Mermaid validated in Mermaid Live Editor. n8n JSON is structurally valid and importable.
Type: n8n workflow (importable JSON) Purpose: The primary visual — the full layered pipeline from PR input to structured findings. Shows the deterministic layers (AST, Semgrep, CodeQL), the LLM triage layer, and synthesis, with the semantic codebase memory feeding context.
{
"name": "S06 — Code Review Harness Pipeline",
"nodes": [
{ "name": "PR Diff / File / Snapshot", "type": "n8n-nodes-base.manualTrigger", "position": [200, 300], "notes": "Pull request diff, full file, repo snapshot, or commit hook payload" },
{ "name": "AST Parsing", "type": "n8n-nodes-base.executeCommand", "position": [420, 300], "notes": "Syntax tree, symbol resolution, cheap structural checks" },
{ "name": "Semgrep", "type": "n8n-nodes-base.executeCommand", "position": [640, 200], "notes": "Fast pattern-based rules (custom + registry). High recall, low precision at scale." },
{ "name": "CodeQL", "type": "n8n-nodes-base.executeCommand", "position": [640, 400], "notes": "Deep data-flow queries, taint tracking. Higher precision, slower." },
{ "name": "Raw Findings (normalized)", "type": "n8n-nodes-base.set", "position": [860, 300], "notes": "file, line, CWE, severity, source. Dedup key assigned." },
{ "name": "Semantic Codebase Memory", "type": "n8n-nodes-base.set", "position": [1080, 450], "notes": "Function-level vector index. For each finding: retrieve enclosing fn, callers, callees, sanitizers." },
{ "name": "LLM Triage", "type": "n8n-nodes-base.set", "position": [1080, 200], "notes": "Per finding: reachable? sanitized? verdict + confidence. Few-shot from feedback store." },
{ "name": "Synthesis", "type": "n8n-nodes-base.set", "position": [1300, 300], "notes": "Dedup across scanners, normalize severity, assign confidence, route." },
{ "name": "Block / Queue / Comment", "type": "n8n-nodes-base.set", "position": [1520, 300], "notes": "High+critical → block PR. Med → queue for human. Low → file silently." },
{ "name": "Feedback Store", "type": "n8n-nodes-base.set", "position": [1080, 100], "notes": "Human verdicts on past findings → few-shot examples for future triage" }
],
"connections": {
"PR Diff / File / Snapshot": { "main": [[{ "node": "AST Parsing", "type": "main", "index": 0 }]] },
"AST Parsing": { "main": [[{ "node": "Semgrep", "type": "main", "index": 0 }], [{ "node": "CodeQL", "type": "main", "index": 0 }]] },
"Semgrep": { "main": [[{ "node": "Raw Findings (normalized)", "type": "main", "index": 0 }]] },
"CodeQL": { "main": [[{ "node": "Raw Findings (normalized)", "type": "main", "index": 0 }]] },
"Raw Findings (normalized)": { "main": [[{ "node": "LLM Triage", "type": "main", "index": 0 }]] },
"Semantic Codebase Memory": { "main": [[{ "node": "LLM Triage", "type": "main", "index": 0 }]] },
"Feedback Store": { "main": [[{ "node": "LLM Triage", "type": "main", "index": 0 }]] },
"LLM Triage": { "main": [[{ "node": "Synthesis", "type": "main", "index": 0 }]] },
"Synthesis": { "main": [[{ "node": "Block / Queue / Comment", "type": "main", "index": 0 }]] }
}
}
Reading the diagram: Left to right. A PR diff enters and is parsed to an AST. Two deterministic scanners — Semgrep (fast, pattern-based) and CodeQL (deep, data-flow) — run on the AST output. Their raw findings are normalized to one schema. Each finding then passes to the LLM triage layer, which is fed by two stores: the semantic codebase memory (cross-file context for the finding) and the feedback store (past human verdicts as few-shot examples). The triage layer assigns a verdict and confidence. Synthesis deduplicates across scanners, normalizes severity, and routes: block the PR, queue for human review, or file silently. Deterministic first, LLM last — the order is load-bearing.
Type: Mermaid (flowchart) Purpose: Shows why raw scanner output is unusable at scale and how LLM triage shifts the precision/recall curve.
flowchart TD
RAW["Raw scanner output<br/>Semgrep + CodeQL on large codebase<br/>~95% recall, ~40% precision"]
NOTE1["Hundreds of findings<br/>majority are false positives<br/>(framework sanitizes, test-only code, unreachable sink)"]
TRIAGE["LLM triage per finding<br/>+ cross-file context<br/>+ feedback few-shot"]
RESULT["High-confidence findings only<br/>~85% recall, ~92% precision"]
RAW --> NOTE1
NOTE1 --> TRIAGE
TRIAGE --> RESULT
BAD["Surface raw findings directly"]
BAD --> MUTE["Developer mutes the bot within a week"]
style RAW fill:#2a1810,stroke:#a04000,color:#f0a868
style TRIAGE fill:#14141f,stroke:#5eead4,color:#5eead4
style RESULT fill:#0d2818,stroke:#1e8449,color:#82e0aa
style BAD fill:#2a0d0d,stroke:#a00000,color:#f08080
style MUTE fill:#2a0d0d,stroke:#a00000,color:#f08080
Reading the diagram: Raw scanner output has high recall (catches most weaknesses) but low precision (many false positives at scale). Surfacing it directly is the anti-pattern — the developer mutes the bot. The LLM triage layer, fed with cross-file context and few-shot examples from past human verdicts, trades a small amount of recall for a large gain in precision. The result: ~85% recall, ~92% precision — a pipeline developers trust rather than mute.
Type: Mermaid (flowchart) Purpose: Shows the three context sources that feed the LLM triage prompt for a single finding.
flowchart LR
FINDING["Raw finding<br/>file, line, CWE, snippet"]
ENCLOSING["Enclosing function<br/>(from AST)"]
SEMANTIC["Semantic memory<br/>callers, callees, sanitizers<br/>(within context budget)"]
FEEDBACK["Feedback store<br/>past human verdicts<br/>as few-shot examples"]
PROMPT["Triage prompt"]
MODEL["LLM triage"]
OUTPUT["verdict: tp/fp<br/>confidence: high/med/low<br/>reasoning"]
FINDING --> PROMPT
ENCLOSING --> PROMPT
SEMANTIC --> PROMPT
FEEDBACK --> PROMPT
PROMPT --> MODEL --> OUTPUT
style FINDING fill:#0d1b2a,stroke:#1b4f72,color:#e4e4e8
style SEMANTIC fill:#14141f,stroke:#5eead4,color:#5eead4
style FEEDBACK fill:#0d2818,stroke:#1e8449,color:#82e0aa
style MODEL fill:#14141f,stroke:#5eead4,color:#5eead4
Reading the diagram: The triage prompt for a single finding assembles four inputs: the raw finding itself (from the deterministic scanner), the enclosing function (from the AST), cross-file context from the semantic codebase memory (callers, callees, sanitizers — within a token budget), and few-shot examples from the feedback store (past human verdicts on similar findings). The model judges reachability and returns a verdict with confidence. The model does not find the finding — the scanners did. The model judges it.
Type: Mermaid (flowchart) Purpose: Shows how a finding in one file pulls cross-file context through the call graph to resolve taint flows.
flowchart TD
FIND["Finding: SQL query in<br/>repository.py:42"]
ENC["Retrieve: enclosing fn<br/>get_user()"]
CALLERS["Retrieve: callers of get_user()<br/>handle_login() in api/handler.py"]
CALLEES["Retrieve: callees<br/>db.execute() — the sink"]
SANI["Retrieve: sanitizer-like fns<br/>validate_input() in validators.py"]
BUDGET{"Within context<br/>budget (4-8k tokens)?"}
PROMPT["Assemble triage prompt<br/>source → sanitizer → sink"]
VERDICT["Verdict: FP<br/>(validate_input escapes<br/>before reaching sink)"]
FIND --> ENC
ENC --> CALLERS
ENC --> CALLEES
ENC --> SANI
CALLERS --> BUDGET
CALLEES --> BUDGET
SANI --> BUDGET
BUDGET -->|"yes, prioritized"| PROMPT --> VERDICT
BUDGET -->|"no, truncate lowest-priority"| PROMPT
style FIND fill:#2a1810,stroke:#a04000,color:#f0a868
style SANI fill:#0d2818,stroke:#1e8449,color:#82e0aa
style BUDGET fill:#14141f,stroke:#5eead4,color:#5eead4
style VERDICT fill:#0d2818,stroke:#1e8449,color:#82e0aa
Reading the diagram: A finding in repository.py cannot be judged in isolation. The semantic memory retrieves the enclosing function, its callers (where the data comes from — handle_login), its callees (the sink — db.execute), and sanitizer-like functions in the call path (validate_input). Retrieval is bounded by a token budget; within the budget, direct callers/callees and sanitizers are prioritized over transitive context. With this cross-file view, the triage model can determine that validate_input escapes the data before it reaches the sink — a false positive that single-file analysis could never resolve.
Type: Mermaid (flowchart) Purpose: The end-to-end autofix loop. Shows every gate the patch passes before merge, and why the human approval gate is non-negotiable.
flowchart TD
CONF["Confirmed finding<br/>(high confidence, post-triage)"]
GEN["LLM generates patch<br/>minimal, targeted diff"]
LINT["Gate 1: linter clean?"]
SEM["Gate 2: Semgrep re-scan<br/>finding resolved? no NEW findings?"]
TEST["Gate 3: test suite passes<br/>coverage preserved?"]
QUALITY["Gate 4: patch quality score<br/>above threshold?"]
DRAFT["Open DRAFT PR<br/>patch + finding + verification evidence"]
APPROVE["GATE 5: Human approval<br/>(non-negotiable)"]
MERGE["Merge"]
HOLD["Hold back<br/>file as 'fix suggested,<br/>needs human authorship'"]
CONF --> GEN --> LINT
LINT -->|"fail"| DISCARD["Discard / revise patch"]
LINT -->|"pass"| SEM
SEM -->|"fail"| DISCARD
SEM -->|"pass"| TEST
TEST -->|"fail"| DISCARD
TEST -->|"pass"| QUALITY
QUALITY -->|"below threshold"| HOLD
QUALITY -->|"above"| DRAFT --> APPROVE
APPROVE -->|"approve"| MERGE
APPROVE -->|"request changes"| GEN
style CONF fill:#0d1b2a,stroke:#1b4f72,color:#e4e4e8
style GEN fill:#14141f,stroke:#5eead4,color:#5eead4
style APPROVE fill:#2a0d0d,stroke:#a00000,color:#f08080
style MERGE fill:#0d2818,stroke:#1e8449,color:#82e0aa
style DISCARD fill:#2a0d0d,stroke:#a00000,color:#f08080
style HOLD fill:#2a1810,stroke:#a04000,color:#f0a868
Reading the diagram: A confirmed finding triggers patch generation. The patch passes four automated gates: linter clean, Semgrep re-scan clean (the finding resolved, no new findings introduced), test suite passes with coverage preserved, and a quality score above threshold. Only patches passing all four gates open a draft PR. Then the non-negotiable gate: human approval. The developer reviews the patch against the finding and verification evidence, requests changes, or merges. Auto-merging is an anti-pattern — the human is the last line of defense against patches that fix one vulnerability while introducing another. The loop is patch, verify, approve, merge — in that order, every time.
# Diagrams — Module S06: Secure Code Review Harnesses
> All Mermaid validated in Mermaid Live Editor. n8n JSON is structurally valid and importable.
---
## Diagram 1 — The Code Review Harness Pipeline (n8n)
**Type**: n8n workflow (importable JSON)
**Purpose**: The primary visual — the full layered pipeline from PR input to structured findings. Shows the deterministic layers (AST, Semgrep, CodeQL), the LLM triage layer, and synthesis, with the semantic codebase memory feeding context.
```json
{
"name": "S06 — Code Review Harness Pipeline",
"nodes": [
{ "name": "PR Diff / File / Snapshot", "type": "n8n-nodes-base.manualTrigger", "position": [200, 300], "notes": "Pull request diff, full file, repo snapshot, or commit hook payload" },
{ "name": "AST Parsing", "type": "n8n-nodes-base.executeCommand", "position": [420, 300], "notes": "Syntax tree, symbol resolution, cheap structural checks" },
{ "name": "Semgrep", "type": "n8n-nodes-base.executeCommand", "position": [640, 200], "notes": "Fast pattern-based rules (custom + registry). High recall, low precision at scale." },
{ "name": "CodeQL", "type": "n8n-nodes-base.executeCommand", "position": [640, 400], "notes": "Deep data-flow queries, taint tracking. Higher precision, slower." },
{ "name": "Raw Findings (normalized)", "type": "n8n-nodes-base.set", "position": [860, 300], "notes": "file, line, CWE, severity, source. Dedup key assigned." },
{ "name": "Semantic Codebase Memory", "type": "n8n-nodes-base.set", "position": [1080, 450], "notes": "Function-level vector index. For each finding: retrieve enclosing fn, callers, callees, sanitizers." },
{ "name": "LLM Triage", "type": "n8n-nodes-base.set", "position": [1080, 200], "notes": "Per finding: reachable? sanitized? verdict + confidence. Few-shot from feedback store." },
{ "name": "Synthesis", "type": "n8n-nodes-base.set", "position": [1300, 300], "notes": "Dedup across scanners, normalize severity, assign confidence, route." },
{ "name": "Block / Queue / Comment", "type": "n8n-nodes-base.set", "position": [1520, 300], "notes": "High+critical → block PR. Med → queue for human. Low → file silently." },
{ "name": "Feedback Store", "type": "n8n-nodes-base.set", "position": [1080, 100], "notes": "Human verdicts on past findings → few-shot examples for future triage" }
],
"connections": {
"PR Diff / File / Snapshot": { "main": [[{ "node": "AST Parsing", "type": "main", "index": 0 }]] },
"AST Parsing": { "main": [[{ "node": "Semgrep", "type": "main", "index": 0 }], [{ "node": "CodeQL", "type": "main", "index": 0 }]] },
"Semgrep": { "main": [[{ "node": "Raw Findings (normalized)", "type": "main", "index": 0 }]] },
"CodeQL": { "main": [[{ "node": "Raw Findings (normalized)", "type": "main", "index": 0 }]] },
"Raw Findings (normalized)": { "main": [[{ "node": "LLM Triage", "type": "main", "index": 0 }]] },
"Semantic Codebase Memory": { "main": [[{ "node": "LLM Triage", "type": "main", "index": 0 }]] },
"Feedback Store": { "main": [[{ "node": "LLM Triage", "type": "main", "index": 0 }]] },
"LLM Triage": { "main": [[{ "node": "Synthesis", "type": "main", "index": 0 }]] },
"Synthesis": { "main": [[{ "node": "Block / Queue / Comment", "type": "main", "index": 0 }]] }
}
}
```
**Reading the diagram**: Left to right. A PR diff enters and is parsed to an AST. Two deterministic scanners — Semgrep (fast, pattern-based) and CodeQL (deep, data-flow) — run on the AST output. Their raw findings are normalized to one schema. Each finding then passes to the LLM triage layer, which is fed by two stores: the semantic codebase memory (cross-file context for the finding) and the feedback store (past human verdicts as few-shot examples). The triage layer assigns a verdict and confidence. Synthesis deduplicates across scanners, normalizes severity, and routes: block the PR, queue for human review, or file silently. Deterministic first, LLM last — the order is load-bearing.
---
## Diagram 2 — The False Positive Problem (precision/recall trade)
**Type**: Mermaid (flowchart)
**Purpose**: Shows why raw scanner output is unusable at scale and how LLM triage shifts the precision/recall curve.
```mermaid
flowchart TD
RAW["Raw scanner output<br/>Semgrep + CodeQL on large codebase<br/>~95% recall, ~40% precision"]
NOTE1["Hundreds of findings<br/>majority are false positives<br/>(framework sanitizes, test-only code, unreachable sink)"]
TRIAGE["LLM triage per finding<br/>+ cross-file context<br/>+ feedback few-shot"]
RESULT["High-confidence findings only<br/>~85% recall, ~92% precision"]
RAW --> NOTE1
NOTE1 --> TRIAGE
TRIAGE --> RESULT
BAD["Surface raw findings directly"]
BAD --> MUTE["Developer mutes the bot within a week"]
style RAW fill:#2a1810,stroke:#a04000,color:#f0a868
style TRIAGE fill:#14141f,stroke:#5eead4,color:#5eead4
style RESULT fill:#0d2818,stroke:#1e8449,color:#82e0aa
style BAD fill:#2a0d0d,stroke:#a00000,color:#f08080
style MUTE fill:#2a0d0d,stroke:#a00000,color:#f08080
```
**Reading the diagram**: Raw scanner output has high recall (catches most weaknesses) but low precision (many false positives at scale). Surfacing it directly is the anti-pattern — the developer mutes the bot. The LLM triage layer, fed with cross-file context and few-shot examples from past human verdicts, trades a small amount of recall for a large gain in precision. The result: ~85% recall, ~92% precision — a pipeline developers trust rather than mute.
---
## Diagram 3 — The Triage Prompt Assembly (context sources)
**Type**: Mermaid (flowchart)
**Purpose**: Shows the three context sources that feed the LLM triage prompt for a single finding.
```mermaid
flowchart LR
FINDING["Raw finding<br/>file, line, CWE, snippet"]
ENCLOSING["Enclosing function<br/>(from AST)"]
SEMANTIC["Semantic memory<br/>callers, callees, sanitizers<br/>(within context budget)"]
FEEDBACK["Feedback store<br/>past human verdicts<br/>as few-shot examples"]
PROMPT["Triage prompt"]
MODEL["LLM triage"]
OUTPUT["verdict: tp/fp<br/>confidence: high/med/low<br/>reasoning"]
FINDING --> PROMPT
ENCLOSING --> PROMPT
SEMANTIC --> PROMPT
FEEDBACK --> PROMPT
PROMPT --> MODEL --> OUTPUT
style FINDING fill:#0d1b2a,stroke:#1b4f72,color:#e4e4e8
style SEMANTIC fill:#14141f,stroke:#5eead4,color:#5eead4
style FEEDBACK fill:#0d2818,stroke:#1e8449,color:#82e0aa
style MODEL fill:#14141f,stroke:#5eead4,color:#5eead4
```
**Reading the diagram**: The triage prompt for a single finding assembles four inputs: the raw finding itself (from the deterministic scanner), the enclosing function (from the AST), cross-file context from the semantic codebase memory (callers, callees, sanitizers — within a token budget), and few-shot examples from the feedback store (past human verdicts on similar findings). The model judges reachability and returns a verdict with confidence. The model does not find the finding — the scanners did. The model judges it.
---
## Diagram 4 — Semantic Codebase Memory (cross-file taint retrieval)
**Type**: Mermaid (flowchart)
**Purpose**: Shows how a finding in one file pulls cross-file context through the call graph to resolve taint flows.
```mermaid
flowchart TD
FIND["Finding: SQL query in<br/>repository.py:42"]
ENC["Retrieve: enclosing fn<br/>get_user()"]
CALLERS["Retrieve: callers of get_user()<br/>handle_login() in api/handler.py"]
CALLEES["Retrieve: callees<br/>db.execute() — the sink"]
SANI["Retrieve: sanitizer-like fns<br/>validate_input() in validators.py"]
BUDGET{"Within context<br/>budget (4-8k tokens)?"}
PROMPT["Assemble triage prompt<br/>source → sanitizer → sink"]
VERDICT["Verdict: FP<br/>(validate_input escapes<br/>before reaching sink)"]
FIND --> ENC
ENC --> CALLERS
ENC --> CALLEES
ENC --> SANI
CALLERS --> BUDGET
CALLEES --> BUDGET
SANI --> BUDGET
BUDGET -->|"yes, prioritized"| PROMPT --> VERDICT
BUDGET -->|"no, truncate lowest-priority"| PROMPT
style FIND fill:#2a1810,stroke:#a04000,color:#f0a868
style SANI fill:#0d2818,stroke:#1e8449,color:#82e0aa
style BUDGET fill:#14141f,stroke:#5eead4,color:#5eead4
style VERDICT fill:#0d2818,stroke:#1e8449,color:#82e0aa
```
**Reading the diagram**: A finding in `repository.py` cannot be judged in isolation. The semantic memory retrieves the enclosing function, its callers (where the data comes from — `handle_login`), its callees (the sink — `db.execute`), and sanitizer-like functions in the call path (`validate_input`). Retrieval is bounded by a token budget; within the budget, direct callers/callees and sanitizers are prioritized over transitive context. With this cross-file view, the triage model can determine that `validate_input` escapes the data before it reaches the sink — a false positive that single-file analysis could never resolve.
---
## Diagram 5 — The Autofix Loop with Approval Gate
**Type**: Mermaid (flowchart)
**Purpose**: The end-to-end autofix loop. Shows every gate the patch passes before merge, and why the human approval gate is non-negotiable.
```mermaid
flowchart TD
CONF["Confirmed finding<br/>(high confidence, post-triage)"]
GEN["LLM generates patch<br/>minimal, targeted diff"]
LINT["Gate 1: linter clean?"]
SEM["Gate 2: Semgrep re-scan<br/>finding resolved? no NEW findings?"]
TEST["Gate 3: test suite passes<br/>coverage preserved?"]
QUALITY["Gate 4: patch quality score<br/>above threshold?"]
DRAFT["Open DRAFT PR<br/>patch + finding + verification evidence"]
APPROVE["GATE 5: Human approval<br/>(non-negotiable)"]
MERGE["Merge"]
HOLD["Hold back<br/>file as 'fix suggested,<br/>needs human authorship'"]
CONF --> GEN --> LINT
LINT -->|"fail"| DISCARD["Discard / revise patch"]
LINT -->|"pass"| SEM
SEM -->|"fail"| DISCARD
SEM -->|"pass"| TEST
TEST -->|"fail"| DISCARD
TEST -->|"pass"| QUALITY
QUALITY -->|"below threshold"| HOLD
QUALITY -->|"above"| DRAFT --> APPROVE
APPROVE -->|"approve"| MERGE
APPROVE -->|"request changes"| GEN
style CONF fill:#0d1b2a,stroke:#1b4f72,color:#e4e4e8
style GEN fill:#14141f,stroke:#5eead4,color:#5eead4
style APPROVE fill:#2a0d0d,stroke:#a00000,color:#f08080
style MERGE fill:#0d2818,stroke:#1e8449,color:#82e0aa
style DISCARD fill:#2a0d0d,stroke:#a00000,color:#f08080
style HOLD fill:#2a1810,stroke:#a04000,color:#f0a868
```
**Reading the diagram**: A confirmed finding triggers patch generation. The patch passes four automated gates: linter clean, Semgrep re-scan clean (the finding resolved, no new findings introduced), test suite passes with coverage preserved, and a quality score above threshold. Only patches passing all four gates open a draft PR. Then the non-negotiable gate: human approval. The developer reviews the patch against the finding and verification evidence, requests changes, or merges. Auto-merging is an anti-pattern — the human is the last line of defense against patches that fix one vulnerability while introducing another. The loop is patch, verify, approve, merge — in that order, every time.