@runhalo/engine 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ast-engine.d.ts +60 -0
- package/dist/ast-engine.js +653 -0
- package/dist/ast-engine.js.map +1 -0
- package/dist/context-analyzer.d.ts +209 -0
- package/dist/context-analyzer.js +408 -0
- package/dist/context-analyzer.js.map +1 -0
- package/dist/data-flow-tracer.d.ts +106 -0
- package/dist/data-flow-tracer.js +506 -0
- package/dist/data-flow-tracer.js.map +1 -0
- package/dist/fp-patterns.d.ts +36 -0
- package/dist/fp-patterns.js +426 -0
- package/dist/fp-patterns.js.map +1 -0
- package/dist/frameworks/angular.d.ts +11 -0
- package/dist/frameworks/angular.js +41 -0
- package/dist/frameworks/angular.js.map +1 -0
- package/dist/frameworks/django.d.ts +11 -0
- package/dist/frameworks/django.js +57 -0
- package/dist/frameworks/django.js.map +1 -0
- package/dist/frameworks/index.d.ts +59 -0
- package/dist/frameworks/index.js +99 -0
- package/dist/frameworks/index.js.map +1 -0
- package/dist/frameworks/nextjs.d.ts +11 -0
- package/dist/frameworks/nextjs.js +59 -0
- package/dist/frameworks/nextjs.js.map +1 -0
- package/dist/frameworks/rails.d.ts +11 -0
- package/dist/frameworks/rails.js +58 -0
- package/dist/frameworks/rails.js.map +1 -0
- package/dist/frameworks/react.d.ts +13 -0
- package/dist/frameworks/react.js +36 -0
- package/dist/frameworks/react.js.map +1 -0
- package/dist/frameworks/types.d.ts +29 -0
- package/dist/frameworks/types.js +11 -0
- package/dist/frameworks/types.js.map +1 -0
- package/dist/frameworks/vue.d.ts +9 -0
- package/dist/frameworks/vue.js +39 -0
- package/dist/frameworks/vue.js.map +1 -0
- package/dist/graduation/fp-verdict-logger.d.ts +81 -0
- package/dist/graduation/fp-verdict-logger.js +130 -0
- package/dist/graduation/fp-verdict-logger.js.map +1 -0
- package/dist/graduation/graduation-codifier.d.ts +37 -0
- package/dist/graduation/graduation-codifier.js +205 -0
- package/dist/graduation/graduation-codifier.js.map +1 -0
- package/dist/graduation/graduation-validator.d.ts +73 -0
- package/dist/graduation/graduation-validator.js +204 -0
- package/dist/graduation/graduation-validator.js.map +1 -0
- package/dist/graduation/index.d.ts +71 -0
- package/dist/graduation/index.js +105 -0
- package/dist/graduation/index.js.map +1 -0
- package/dist/graduation/pattern-aggregator.d.ts +77 -0
- package/dist/graduation/pattern-aggregator.js +154 -0
- package/dist/graduation/pattern-aggregator.js.map +1 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +718 -61
- package/dist/index.js.map +1 -1
- package/dist/review-board/two-agent-review.d.ts +152 -0
- package/dist/review-board/two-agent-review.js +463 -0
- package/dist/review-board/two-agent-review.js.map +1 -0
- package/dist/scope-analyzer.d.ts +91 -0
- package/dist/scope-analyzer.js +300 -0
- package/dist/scope-analyzer.js.map +1 -0
- package/package.json +9 -2
- package/rules/coppa-tier-1.yaml +17 -10
- package/rules/rules.json +2094 -99
- package/rules/validation-report.json +58 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Sprint 13a Week 2: Two-Agent Review Board Prototype
|
|
4
|
+
*
|
|
5
|
+
* Two parallel AI agents review each violation from different perspectives:
|
|
6
|
+
* - Agent 1 (Regulatory): Legal/compliance risk lens, errs toward confirming
|
|
7
|
+
* - Agent 2 (Code Context): Code quality lens, errs toward precision
|
|
8
|
+
*
|
|
9
|
+
* Consensus logic: Both must agree to dismiss (consensus-to-dismiss).
|
|
10
|
+
* Cost: 2x single-agent (two parallel API calls), not 3x.
|
|
11
|
+
*
|
|
12
|
+
* Guardrail: False dismiss rate must be below 20% on ground truth dataset.
|
|
13
|
+
* If not achieved in 1.5 days, ship single-agent with calibration protocol.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.resolveConsensus = resolveConsensus;
|
|
17
|
+
exports.buildRegulatorySystemPrompt = buildRegulatorySystemPrompt;
|
|
18
|
+
exports.buildCodeContextSystemPrompt = buildCodeContextSystemPrompt;
|
|
19
|
+
exports.buildViolationPrompt = buildViolationPrompt;
|
|
20
|
+
exports.runTwoAgentReview = runTwoAgentReview;
|
|
21
|
+
exports.scoreConsensusVerdicts = scoreConsensusVerdicts;
|
|
22
|
+
exports.formatAccuracyReport = formatAccuracyReport;
|
|
23
|
+
// ─── Verdict Priority ───────────────────────────────────────────────────────
|
|
24
|
+
// Higher number = higher severity. Used for consensus resolution.
|
|
25
|
+
const VERDICT_PRIORITY = {
|
|
26
|
+
dismissed: 0,
|
|
27
|
+
downgraded: 1,
|
|
28
|
+
confirmed: 2,
|
|
29
|
+
escalated: 3,
|
|
30
|
+
};
|
|
31
|
+
// ─── Consensus Logic ────────────────────────────────────────────────────────
|
|
32
|
+
/**
|
|
33
|
+
* Resolve two agent verdicts into a consensus result.
|
|
34
|
+
*
|
|
35
|
+
* Rules (consensus-to-dismiss):
|
|
36
|
+
* 1. Both dismiss → dismissed (high confidence both agree it's FP)
|
|
37
|
+
* 2. Both confirm → confirmed (high agreement)
|
|
38
|
+
* 3. Both escalate → escalated
|
|
39
|
+
* 4. Both downgrade → downgraded
|
|
40
|
+
* 5. One confirms/escalates, one dismisses → confirmed (safety bias)
|
|
41
|
+
* 6. One downgrades, one confirms → confirmed at lower confidence
|
|
42
|
+
* 7. One downgrades, one dismisses → downgraded (middle ground)
|
|
43
|
+
*/
|
|
44
|
+
function resolveConsensus(regulatory, codeContext) {
|
|
45
|
+
const regPriority = VERDICT_PRIORITY[regulatory.verdict] ?? 2;
|
|
46
|
+
const codePriority = VERDICT_PRIORITY[codeContext.verdict] ?? 2;
|
|
47
|
+
let finalVerdict;
|
|
48
|
+
let finalConfidence;
|
|
49
|
+
let agreementLevel;
|
|
50
|
+
let consensusReason;
|
|
51
|
+
let fpReason;
|
|
52
|
+
let fpPatternId;
|
|
53
|
+
if (regulatory.verdict === codeContext.verdict) {
|
|
54
|
+
// Full agreement
|
|
55
|
+
finalVerdict = regulatory.verdict;
|
|
56
|
+
finalConfidence = Math.round((regulatory.confidence + codeContext.confidence) / 2);
|
|
57
|
+
agreementLevel = 'full';
|
|
58
|
+
consensusReason = `Both agents agree: ${finalVerdict}`;
|
|
59
|
+
if (finalVerdict === 'dismissed') {
|
|
60
|
+
fpReason = regulatory.fpReason || codeContext.fpReason;
|
|
61
|
+
fpPatternId = regulatory.fpPatternId || codeContext.fpPatternId;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (Math.abs(regPriority - codePriority) === 1) {
|
|
65
|
+
// Partial agreement (adjacent verdicts)
|
|
66
|
+
agreementLevel = 'partial';
|
|
67
|
+
if (regPriority > codePriority) {
|
|
68
|
+
// Regulatory is stricter → use regulatory verdict (safety bias)
|
|
69
|
+
finalVerdict = regulatory.verdict;
|
|
70
|
+
finalConfidence = Math.max(Math.round((regulatory.confidence + codeContext.confidence) / 2) - 1, 1);
|
|
71
|
+
consensusReason = `Regulatory agent stricter (${regulatory.verdict}) vs code context (${codeContext.verdict}). Safety bias applied.`;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Code context is stricter → use code context verdict
|
|
75
|
+
finalVerdict = codeContext.verdict;
|
|
76
|
+
finalConfidence = Math.max(Math.round((regulatory.confidence + codeContext.confidence) / 2) - 1, 1);
|
|
77
|
+
consensusReason = `Code context agent stricter (${codeContext.verdict}) vs regulatory (${regulatory.verdict}).`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// Split verdict (non-adjacent, e.g., confirm vs dismiss)
|
|
82
|
+
agreementLevel = 'split';
|
|
83
|
+
// Consensus-to-dismiss: if EITHER agent flags concern, confirm
|
|
84
|
+
if (regPriority >= VERDICT_PRIORITY.confirmed || codePriority >= VERDICT_PRIORITY.confirmed) {
|
|
85
|
+
finalVerdict = 'confirmed';
|
|
86
|
+
const confirmer = regPriority >= codePriority ? regulatory : codeContext;
|
|
87
|
+
finalConfidence = Math.max(confirmer.confidence - 1, 1);
|
|
88
|
+
consensusReason = `Split verdict: ${regulatory.verdict} (regulatory) vs ${codeContext.verdict} (code). Confirmed per consensus-to-dismiss policy.`;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Both below confirmed (e.g., both downgraded or one dismissed + one downgraded)
|
|
92
|
+
finalVerdict = regPriority > codePriority ? regulatory.verdict : codeContext.verdict;
|
|
93
|
+
finalConfidence = Math.round((regulatory.confidence + codeContext.confidence) / 2);
|
|
94
|
+
consensusReason = `Split verdict resolved to ${finalVerdict}.`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
ruleId: regulatory.ruleId || codeContext.ruleId,
|
|
99
|
+
finalVerdict,
|
|
100
|
+
finalConfidence: Math.max(1, Math.min(10, finalConfidence)),
|
|
101
|
+
regulatoryVerdict: regulatory,
|
|
102
|
+
codeContextVerdict: codeContext,
|
|
103
|
+
agreementLevel,
|
|
104
|
+
consensusReason,
|
|
105
|
+
fpReason,
|
|
106
|
+
fpPatternId,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// ─── System Prompts ─────────────────────────────────────────────────────────
|
|
110
|
+
/**
|
|
111
|
+
* Regulatory Compliance Agent system prompt.
|
|
112
|
+
* Focuses on legal risk, enforcement precedent, statutory requirements.
|
|
113
|
+
* Bias: Err toward confirming (safety first — missing a real violation is worse).
|
|
114
|
+
*/
|
|
115
|
+
function buildRegulatorySystemPrompt(fpChecklists, repoMetadata) {
|
|
116
|
+
const ageContext = repoMetadata?.target_age_range
|
|
117
|
+
? `The target audience is children aged ${repoMetadata.target_age_range}.`
|
|
118
|
+
: 'Assume the product may serve children of any age under 18.';
|
|
119
|
+
return `You are Agent 1 of the Halo Two-Agent Review Board: the REGULATORY COMPLIANCE SPECIALIST.
|
|
120
|
+
|
|
121
|
+
YOUR ROLE: Evaluate each violation through a legal and regulatory lens. Your priority is CHILD SAFETY. You represent the perspective of a regulator at the FTC Children's Privacy Bureau or the UK ICO.
|
|
122
|
+
|
|
123
|
+
${ageContext}
|
|
124
|
+
|
|
125
|
+
== YOUR EVALUATION CRITERIA ==
|
|
126
|
+
|
|
127
|
+
1. STATUTORY COMPLIANCE: Does this code violate COPPA, AADCA, UK AADC, or EU AI Act requirements as written in the statute?
|
|
128
|
+
2. ENFORCEMENT PRECEDENT: Would the FTC, ICO, or a state AG pursue this pattern based on recent enforcement actions?
|
|
129
|
+
3. CHILD HARM POTENTIAL: Even if technically legal, does this create risk of developmental harm to children?
|
|
130
|
+
4. DATA LIFECYCLE: Does this code handle children's data in a way that creates retention, exposure, or consent gaps?
|
|
131
|
+
|
|
132
|
+
== DECISION RULES ==
|
|
133
|
+
|
|
134
|
+
1. Check the FP checklist FIRST. If a pattern clearly matches (vendor code, test file, doc generator) → DISMISS.
|
|
135
|
+
2. If NO FP pattern matches, CONFIRM. A regulator would flag this.
|
|
136
|
+
3. When the violation is technically present but with mitigating factors (framework escaping, admin-only) → DOWNGRADE.
|
|
137
|
+
4. When in doubt, CONFIRM. A missed real violation in a children's product is worse than a false alarm.
|
|
138
|
+
5. You may ONLY dismiss if you can cite a specific FP pattern from the checklist below.
|
|
139
|
+
|
|
140
|
+
== CRITICAL CONSTRAINTS (Sprint 13b) ==
|
|
141
|
+
- You may ONLY use fpPatternId values that appear in the FP CHECKLISTS below. Do NOT invent new pattern IDs.
|
|
142
|
+
- "admin-internal" is NOT a valid dismiss reason when child/student data (email, name, PII) is being processed.
|
|
143
|
+
- "vendor-library" is NOT a valid dismiss reason when vendor code is bundled in a child-facing production app.
|
|
144
|
+
- "generic-code-pattern" or "generic-boolean-assignment" are NEVER valid dismiss reasons. Evaluate what the code DOES.
|
|
145
|
+
- If a code snippet handles student_email, child data, account management, or personal data → CONFIRM regardless of context.
|
|
146
|
+
- ~60% of violations should be confirmed. If you dismiss >50%, you are being too aggressive.
|
|
147
|
+
|
|
148
|
+
== CONFIDENCE SCALE (1-10) ==
|
|
149
|
+
|
|
150
|
+
1-2: Clear false positive (vendor, test, doc generator) → dismiss
|
|
151
|
+
3-4: Likely false positive with specific evidence → dismiss with explanation
|
|
152
|
+
5-6: Ambiguous but with mitigating factors → downgrade
|
|
153
|
+
7-8: Genuine compliance concern → confirm
|
|
154
|
+
9-10: Critical child safety risk, potential enforcement action → confirm/escalate
|
|
155
|
+
|
|
156
|
+
== FP CHECKLISTS ==
|
|
157
|
+
${fpChecklists}
|
|
158
|
+
|
|
159
|
+
Respond with ONLY a JSON array. Each element:
|
|
160
|
+
{
|
|
161
|
+
"ruleId": "string",
|
|
162
|
+
"verdict": "confirmed" | "downgraded" | "escalated" | "dismissed",
|
|
163
|
+
"confidence": <1-10>,
|
|
164
|
+
"reasoning": "2-3 sentences explaining your regulatory assessment",
|
|
165
|
+
"fpReason": "pattern name if dismissed, null otherwise",
|
|
166
|
+
"fpPatternId": "pattern ID if dismissed, null otherwise"
|
|
167
|
+
}`;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Code Context Agent system prompt.
|
|
171
|
+
* Focuses on code semantics, framework behavior, false positive detection.
|
|
172
|
+
* Bias: Err toward precision (fewer false positives, more accurate analysis).
|
|
173
|
+
*/
|
|
174
|
+
function buildCodeContextSystemPrompt(fpChecklists, repoMetadata) {
|
|
175
|
+
const frameworkContext = repoMetadata?.framework
|
|
176
|
+
? `The codebase uses ${repoMetadata.framework}. Consider framework-specific behaviors (auto-escaping, middleware, built-in protections).`
|
|
177
|
+
: '';
|
|
178
|
+
return `You are Agent 2 of the Halo Two-Agent Review Board: the CODE CONTEXT SPECIALIST.
|
|
179
|
+
|
|
180
|
+
YOUR ROLE: Evaluate each violation through a software engineering lens. Your priority is ACCURACY. You understand frameworks, libraries, and code patterns deeply. The regex scanner produces ~25% false positives — your job is to distinguish real violations from scanner noise.
|
|
181
|
+
|
|
182
|
+
${frameworkContext}
|
|
183
|
+
|
|
184
|
+
== YOUR EVALUATION CRITERIA ==
|
|
185
|
+
|
|
186
|
+
1. CODE SEMANTICS: Does the flagged code ACTUALLY do what the rule claims? Or did regex match something benign?
|
|
187
|
+
2. FRAMEWORK BEHAVIOR: Does the framework handle this automatically? (e.g., React auto-escaping, Django CSRF)
|
|
188
|
+
3. EXECUTION CONTEXT: Is this code reachable? Dead code, comments, examples, and config should be dismissed.
|
|
189
|
+
4. PATTERN RECOGNITION: Match against known FP patterns. If the pattern clearly matches a checklist item → DISMISS.
|
|
190
|
+
|
|
191
|
+
== DECISION RULES ==
|
|
192
|
+
|
|
193
|
+
1. Check the FP checklist FIRST. If a specific pattern matches → DISMISS with the pattern ID.
|
|
194
|
+
2. Analyze the surrounding code context (5 lines before + after). Does the code ACTUALLY implement what the rule flags?
|
|
195
|
+
3. Consider the framework: React JSX auto-escapes, Django templates auto-escape, etc. → DOWNGRADE if framework handles it.
|
|
196
|
+
4. If the code genuinely does what the rule says with no mitigation → CONFIRM.
|
|
197
|
+
5. You may ONLY dismiss if you can cite a specific FP pattern from the checklist below.
|
|
198
|
+
|
|
199
|
+
== CRITICAL CONSTRAINTS (Sprint 13b) ==
|
|
200
|
+
- You may ONLY use fpPatternId values that appear in the FP CHECKLISTS below. Do NOT invent new pattern IDs like "vendor-library", "generic-boolean-assignment", "auth-token-pattern", or "non-social-media-context".
|
|
201
|
+
- VENDOR CODE RULE: Bundled third-party code (TinyMCE, Chart.js, jQuery plugins) that executes in a child-facing production app IS in scope. Only dismiss vendor code if it is in a separate, non-bundled package.
|
|
202
|
+
- ADMIN CODE RULE: Admin/staff code that processes child data (student emails, account management, enrollment) is compliance-relevant. Do NOT dismiss as "admin-internal".
|
|
203
|
+
- GENERIC CODE RULE: Simple-looking code (boolean assignments, data assignments, function calls) can still be compliance violations. Evaluate what the code DOES in context, not how simple it looks.
|
|
204
|
+
- If no checklist item matches, you MUST confirm or downgrade. Never dismiss without a checklist match.
|
|
205
|
+
- ~60% of violations should be confirmed. If you dismiss >50%, you are being too aggressive.
|
|
206
|
+
|
|
207
|
+
== PRECISION FOCUS ==
|
|
208
|
+
|
|
209
|
+
Your value is catching FALSE POSITIVES the regex scanner produces. Key FP categories:
|
|
210
|
+
- Vendor/third-party code that is NOT bundled in the child-facing app
|
|
211
|
+
- Test/fixture/mock code that doesn't run in production
|
|
212
|
+
- Framework auto-mitigations (auto-escaping, CSRF protection)
|
|
213
|
+
- Sanitized inputs (DOMPurify, bleach, htmlspecialchars)
|
|
214
|
+
- Comments, documentation strings, TODO notes
|
|
215
|
+
- Cookie consent implementations (privacy-protective code flagged as violation)
|
|
216
|
+
- Media playback flagged as media capture
|
|
217
|
+
|
|
218
|
+
IMPORTANT: Do NOT dismiss admin routes or bundled vendor code automatically. These are common false-dismiss triggers.
|
|
219
|
+
|
|
220
|
+
== CONFIDENCE SCALE (1-10) ==
|
|
221
|
+
|
|
222
|
+
1-2: Obvious FP (standalone test, comment, doc generator) → dismiss
|
|
223
|
+
3-4: Clear FP pattern match → dismiss
|
|
224
|
+
5-6: Framework mitigates but technically present → downgrade
|
|
225
|
+
7-8: Real issue in production code → confirm
|
|
226
|
+
9-10: Serious vulnerability with no mitigation → confirm/escalate
|
|
227
|
+
|
|
228
|
+
== FP CHECKLISTS ==
|
|
229
|
+
${fpChecklists}
|
|
230
|
+
|
|
231
|
+
Respond with ONLY a JSON array. Each element:
|
|
232
|
+
{
|
|
233
|
+
"ruleId": "string",
|
|
234
|
+
"verdict": "confirmed" | "downgraded" | "escalated" | "dismissed",
|
|
235
|
+
"confidence": <1-10>,
|
|
236
|
+
"reasoning": "2-3 sentences explaining your code analysis",
|
|
237
|
+
"fpReason": "pattern name if dismissed, null otherwise",
|
|
238
|
+
"fpPatternId": "pattern ID if dismissed, null otherwise"
|
|
239
|
+
}`;
|
|
240
|
+
}
|
|
241
|
+
// ─── Violation Prompt Builder ───────────────────────────────────────────────
|
|
242
|
+
/**
|
|
243
|
+
* Build the violation prompt (shared by both agents — same data, different lens).
|
|
244
|
+
*/
|
|
245
|
+
function buildViolationPrompt(violations) {
|
|
246
|
+
const items = violations.map((v, i) => {
|
|
247
|
+
const metaFlags = v.fileMetadata
|
|
248
|
+
? `Context Flags: isVendor=${v.fileMetadata.isVendor}, isTest=${v.fileMetadata.isTest}, isAdmin=${v.fileMetadata.isAdmin}, isConsent=${v.fileMetadata.isConsent}, isDocGenerator=${v.fileMetadata.isDocGenerator}`
|
|
249
|
+
: 'Context Flags: not available';
|
|
250
|
+
const langInfo = v.fileMetadata?.language || v.language || 'unknown';
|
|
251
|
+
const framework = v.fileMetadata?.detectedFramework || 'unknown';
|
|
252
|
+
const codeBlock = v.surroundingCode || v.codeSnippet || 'No code available';
|
|
253
|
+
return `--- Violation ${i + 1} ---
|
|
254
|
+
Rule: ${v.ruleId}${v.ruleName ? ` (${v.ruleName})` : ''}
|
|
255
|
+
Severity: ${v.severity}
|
|
256
|
+
File: ${v.filePath}
|
|
257
|
+
Line: ${v.line}
|
|
258
|
+
Language: ${langInfo}
|
|
259
|
+
Framework: ${framework}
|
|
260
|
+
${metaFlags}
|
|
261
|
+
AST Verdict: ${v.astVerdict || 'regex_only'}
|
|
262
|
+
Context Confidence: ${v.contextConfidence ?? 'N/A'}
|
|
263
|
+
|
|
264
|
+
Code:
|
|
265
|
+
\`\`\`
|
|
266
|
+
${codeBlock}
|
|
267
|
+
\`\`\`
|
|
268
|
+
|
|
269
|
+
Message: ${v.message}`;
|
|
270
|
+
});
|
|
271
|
+
return `Review these ${violations.length} violation(s).
|
|
272
|
+
|
|
273
|
+
YOUR PROCESS for each violation:
|
|
274
|
+
1. Check file path and metadata flags FIRST.
|
|
275
|
+
2. Read the code context. Does the code ACTUALLY do what the rule says?
|
|
276
|
+
3. Check the rule-specific FP checklist. Does any pattern match?
|
|
277
|
+
4. Only if no dismissal reason found → CONFIRM.
|
|
278
|
+
|
|
279
|
+
${items.join('\n\n')}
|
|
280
|
+
|
|
281
|
+
Respond with ONLY a JSON array. No other text.`;
|
|
282
|
+
}
|
|
283
|
+
async function callAnthropicAPI(systemPrompt, userPrompt, config) {
|
|
284
|
+
const model = config.model || 'claude-sonnet-4-20250514';
|
|
285
|
+
const maxTokens = config.maxTokens || 4096;
|
|
286
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
287
|
+
method: 'POST',
|
|
288
|
+
headers: {
|
|
289
|
+
'Content-Type': 'application/json',
|
|
290
|
+
'x-api-key': config.anthropicApiKey,
|
|
291
|
+
'anthropic-version': '2023-06-01',
|
|
292
|
+
},
|
|
293
|
+
body: JSON.stringify({
|
|
294
|
+
model,
|
|
295
|
+
max_tokens: maxTokens,
|
|
296
|
+
system: systemPrompt,
|
|
297
|
+
messages: [{ role: 'user', content: userPrompt }],
|
|
298
|
+
}),
|
|
299
|
+
});
|
|
300
|
+
if (!response.ok) {
|
|
301
|
+
const errText = await response.text();
|
|
302
|
+
throw new Error(`Anthropic API error ${response.status}: ${errText}`);
|
|
303
|
+
}
|
|
304
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
305
|
+
const result = await response.json();
|
|
306
|
+
const responseText = result.content?.[0]?.text || '[]';
|
|
307
|
+
let verdicts;
|
|
308
|
+
try {
|
|
309
|
+
const cleaned = responseText.replace(/^```json?\n?/i, '').replace(/\n?```$/i, '').trim();
|
|
310
|
+
verdicts = JSON.parse(cleaned);
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
throw new Error(`Failed to parse agent response: ${responseText.substring(0, 200)}`);
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
verdicts,
|
|
317
|
+
inputTokens: result.usage?.input_tokens || 0,
|
|
318
|
+
outputTokens: result.usage?.output_tokens || 0,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Run the two-agent review on a batch of violations.
|
|
323
|
+
* Both agents run in parallel, then consensus is resolved.
|
|
324
|
+
*/
|
|
325
|
+
async function runTwoAgentReview(violations, fpChecklists, config, repoMetadata) {
|
|
326
|
+
const startTime = Date.now();
|
|
327
|
+
const regulatoryPrompt = buildRegulatorySystemPrompt(fpChecklists, repoMetadata);
|
|
328
|
+
const codeContextPrompt = buildCodeContextSystemPrompt(fpChecklists, repoMetadata);
|
|
329
|
+
const violationPrompt = buildViolationPrompt(violations);
|
|
330
|
+
// Run both agents in parallel
|
|
331
|
+
const [regulatoryResult, codeContextResult] = await Promise.all([
|
|
332
|
+
callAnthropicAPI(regulatoryPrompt, violationPrompt, config),
|
|
333
|
+
callAnthropicAPI(codeContextPrompt, violationPrompt, config),
|
|
334
|
+
]);
|
|
335
|
+
// Resolve consensus for each violation
|
|
336
|
+
const consensusResults = [];
|
|
337
|
+
for (let i = 0; i < violations.length; i++) {
|
|
338
|
+
const regVerdict = regulatoryResult.verdicts[i] || {
|
|
339
|
+
ruleId: violations[i].ruleId,
|
|
340
|
+
verdict: 'confirmed',
|
|
341
|
+
confidence: 7,
|
|
342
|
+
reasoning: 'Regulatory agent could not assess. Defaulting to confirmed.',
|
|
343
|
+
};
|
|
344
|
+
const codeVerdict = codeContextResult.verdicts[i] || {
|
|
345
|
+
ruleId: violations[i].ruleId,
|
|
346
|
+
verdict: 'confirmed',
|
|
347
|
+
confidence: 7,
|
|
348
|
+
reasoning: 'Code context agent could not assess. Defaulting to confirmed.',
|
|
349
|
+
};
|
|
350
|
+
consensusResults.push(resolveConsensus(regVerdict, codeVerdict));
|
|
351
|
+
}
|
|
352
|
+
// Compute stats
|
|
353
|
+
const totalInputTokens = regulatoryResult.inputTokens + codeContextResult.inputTokens;
|
|
354
|
+
const totalOutputTokens = regulatoryResult.outputTokens + codeContextResult.outputTokens;
|
|
355
|
+
const costUsd = (totalInputTokens * 3.0 / 1000000) + (totalOutputTokens * 15.0 / 1000000);
|
|
356
|
+
const agreementStats = {
|
|
357
|
+
full_agreement: consensusResults.filter(r => r.agreementLevel === 'full').length,
|
|
358
|
+
partial_agreement: consensusResults.filter(r => r.agreementLevel === 'partial').length,
|
|
359
|
+
split_decisions: consensusResults.filter(r => r.agreementLevel === 'split').length,
|
|
360
|
+
total: consensusResults.length,
|
|
361
|
+
};
|
|
362
|
+
return {
|
|
363
|
+
consensusResults,
|
|
364
|
+
regulatoryVerdicts: regulatoryResult.verdicts,
|
|
365
|
+
codeContextVerdicts: codeContextResult.verdicts,
|
|
366
|
+
cost: {
|
|
367
|
+
input_tokens: totalInputTokens,
|
|
368
|
+
output_tokens: totalOutputTokens,
|
|
369
|
+
estimated_usd: costUsd,
|
|
370
|
+
},
|
|
371
|
+
latency_ms: Date.now() - startTime,
|
|
372
|
+
agreement_stats: agreementStats,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Score two-agent consensus verdicts against ground truth.
|
|
377
|
+
*/
|
|
378
|
+
function scoreConsensusVerdicts(groundTruth, verdicts) {
|
|
379
|
+
let fpTotal = 0, fpDismissed = 0;
|
|
380
|
+
let tpTotal = 0, tpFalseDismissed = 0;
|
|
381
|
+
let confirmedTp = 0, confirmedTotal = 0;
|
|
382
|
+
let fullAgreement = 0, totalWithVerdicts = 0;
|
|
383
|
+
const perCategory = {};
|
|
384
|
+
for (const entry of groundTruth) {
|
|
385
|
+
const verdict = verdicts.get(entry.id);
|
|
386
|
+
if (!verdict)
|
|
387
|
+
continue;
|
|
388
|
+
totalWithVerdicts++;
|
|
389
|
+
if (verdict.agreementLevel === 'full')
|
|
390
|
+
fullAgreement++;
|
|
391
|
+
const isDismissed = verdict.finalVerdict === 'dismissed';
|
|
392
|
+
const isConfirmed = verdict.finalVerdict === 'confirmed' || verdict.finalVerdict === 'escalated';
|
|
393
|
+
if (entry.label === 'fp') {
|
|
394
|
+
fpTotal++;
|
|
395
|
+
if (isDismissed)
|
|
396
|
+
fpDismissed++;
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
tpTotal++;
|
|
400
|
+
if (isDismissed)
|
|
401
|
+
tpFalseDismissed++;
|
|
402
|
+
}
|
|
403
|
+
if (isConfirmed) {
|
|
404
|
+
confirmedTotal++;
|
|
405
|
+
if (entry.label === 'tp')
|
|
406
|
+
confirmedTp++;
|
|
407
|
+
}
|
|
408
|
+
// Per-category tracking
|
|
409
|
+
const cat = entry.ruleCategory || entry.ruleId;
|
|
410
|
+
if (!perCategory[cat]) {
|
|
411
|
+
perCategory[cat] = { total: 0, correct: 0, accuracy: 0 };
|
|
412
|
+
}
|
|
413
|
+
perCategory[cat].total++;
|
|
414
|
+
const correct = (entry.label === 'fp' && isDismissed) || (entry.label === 'tp' && !isDismissed);
|
|
415
|
+
if (correct)
|
|
416
|
+
perCategory[cat].correct++;
|
|
417
|
+
}
|
|
418
|
+
// Compute per-category accuracy
|
|
419
|
+
for (const cat of Object.keys(perCategory)) {
|
|
420
|
+
perCategory[cat].accuracy = perCategory[cat].total > 0
|
|
421
|
+
? Math.round((perCategory[cat].correct / perCategory[cat].total) * 1000) / 10
|
|
422
|
+
: 0;
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
totalEntries: totalWithVerdicts,
|
|
426
|
+
fpDismissRate: fpTotal > 0 ? Math.round((fpDismissed / fpTotal) * 1000) / 10 : 0,
|
|
427
|
+
tpFalseDismissRate: tpTotal > 0 ? Math.round((tpFalseDismissed / tpTotal) * 1000) / 10 : 0,
|
|
428
|
+
precision: confirmedTotal > 0 ? Math.round((confirmedTp / confirmedTotal) * 1000) / 10 : 0,
|
|
429
|
+
agreementRate: totalWithVerdicts > 0 ? Math.round((fullAgreement / totalWithVerdicts) * 1000) / 10 : 0,
|
|
430
|
+
perCategory,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Format accuracy metrics as a readable report.
|
|
435
|
+
*/
|
|
436
|
+
function formatAccuracyReport(metrics) {
|
|
437
|
+
const lines = [
|
|
438
|
+
'═══ Two-Agent Review Board — Accuracy Report ═══',
|
|
439
|
+
'',
|
|
440
|
+
`Total entries scored: ${metrics.totalEntries}`,
|
|
441
|
+
`FP dismiss rate: ${metrics.fpDismissRate}% (target: >80%)`,
|
|
442
|
+
`TP false dismiss: ${metrics.tpFalseDismissRate}% (target: <20%, HARD GATE)`,
|
|
443
|
+
`Precision: ${metrics.precision}%`,
|
|
444
|
+
`Agent agreement: ${metrics.agreementRate}%`,
|
|
445
|
+
'',
|
|
446
|
+
'── Per-Category Breakdown ──',
|
|
447
|
+
];
|
|
448
|
+
const sorted = Object.entries(metrics.perCategory)
|
|
449
|
+
.sort(([, a], [, b]) => b.total - a.total);
|
|
450
|
+
for (const [cat, data] of sorted) {
|
|
451
|
+
lines.push(` ${cat.padEnd(25)} ${data.correct}/${data.total} (${data.accuracy}%)`);
|
|
452
|
+
}
|
|
453
|
+
lines.push('');
|
|
454
|
+
if (metrics.tpFalseDismissRate >= 20) {
|
|
455
|
+
lines.push('FAIL: TP false dismiss rate exceeds 20% guardrail.');
|
|
456
|
+
lines.push('ACTION: Revert to single-agent. Revisit multi-agent in Sprint 13b.');
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
lines.push('PASS: TP false dismiss rate within guardrail.');
|
|
460
|
+
}
|
|
461
|
+
return lines.join('\n');
|
|
462
|
+
}
|
|
463
|
+
//# sourceMappingURL=two-agent-review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"two-agent-review.js","sourceRoot":"","sources":["../../src/review-board/two-agent-review.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Halo Scope Analyzer
|
|
3
|
+
* Determines the context of code being scanned to reduce false positives.
|
|
4
|
+
*
|
|
5
|
+
* Analyzes WHERE code lives (test file? admin route? type definition?)
|
|
6
|
+
* so that rule violations can be weighted or suppressed based on context.
|
|
7
|
+
*/
|
|
8
|
+
import Parser from 'tree-sitter';
|
|
9
|
+
/**
|
|
10
|
+
* File-level scope context derived from path heuristics and content analysis.
|
|
11
|
+
*/
|
|
12
|
+
export interface ScopeContext {
|
|
13
|
+
/** File is a test: _test.go, .spec.ts, .test.js, __tests__/, test/, tests/ */
|
|
14
|
+
isTestFile: boolean;
|
|
15
|
+
/** File is an admin route: /admin/, admin.py, AdminPanel, AdminDashboard */
|
|
16
|
+
isAdminRoute: boolean;
|
|
17
|
+
/** File is user-facing: /pages/, /components/, /views/, /screens/ */
|
|
18
|
+
isUserFacing: boolean;
|
|
19
|
+
/** File is a type definition: .d.ts, interface{}, type alias files */
|
|
20
|
+
isTypeDefinition: boolean;
|
|
21
|
+
/** File is configuration: .config.ts, config/, settings.py, .env */
|
|
22
|
+
isConfigFile: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Line-level context derived from AST analysis.
|
|
26
|
+
* Describes what construct the line is enclosed within.
|
|
27
|
+
*/
|
|
28
|
+
export interface LineContext {
|
|
29
|
+
/** Line is inside an interface declaration */
|
|
30
|
+
inInterfaceDecl: boolean;
|
|
31
|
+
/** Line is inside a function body */
|
|
32
|
+
inFunctionBody: boolean;
|
|
33
|
+
/** Line is inside a class method */
|
|
34
|
+
inClassMethod: boolean;
|
|
35
|
+
/** Line is inside a JSX/TSX element */
|
|
36
|
+
inJSXElement: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Analyzes the scope/context of source files and individual lines
|
|
40
|
+
* to help the engine make smarter decisions about rule applicability.
|
|
41
|
+
*/
|
|
42
|
+
export declare class ScopeAnalyzer {
|
|
43
|
+
/**
|
|
44
|
+
* Analyze file-level scope from path heuristics and optionally content/AST.
|
|
45
|
+
*
|
|
46
|
+
* @param filePath - Relative or absolute path to the source file
|
|
47
|
+
* @param content - Full text content of the file
|
|
48
|
+
* @param tree - Optional tree-sitter AST (used for deeper analysis)
|
|
49
|
+
* @returns ScopeContext describing the file's role
|
|
50
|
+
*/
|
|
51
|
+
analyzeFile(filePath: string, content: string, tree?: Parser.Tree): ScopeContext;
|
|
52
|
+
/**
|
|
53
|
+
* Analyze what AST construct encloses a given line.
|
|
54
|
+
*
|
|
55
|
+
* Walks the tree to find the deepest node that contains the target line,
|
|
56
|
+
* then walks upward through ancestors to determine enclosing constructs.
|
|
57
|
+
*
|
|
58
|
+
* @param line - 1-based line number
|
|
59
|
+
* @param tree - tree-sitter AST
|
|
60
|
+
* @returns LineContext describing the enclosing constructs
|
|
61
|
+
*/
|
|
62
|
+
analyzeLineContext(line: number, tree: Parser.Tree): LineContext;
|
|
63
|
+
private detectTestFile;
|
|
64
|
+
private detectAdminRoute;
|
|
65
|
+
private detectUserFacing;
|
|
66
|
+
private detectTypeDefinition;
|
|
67
|
+
private detectConfigFile;
|
|
68
|
+
/**
|
|
69
|
+
* Check via AST whether the file consists primarily of type declarations.
|
|
70
|
+
* A file is considered primarily types if >= 80% of its top-level
|
|
71
|
+
* statements are interface/type declarations.
|
|
72
|
+
*/
|
|
73
|
+
private isAstPrimarilyTypes;
|
|
74
|
+
private isTypeNode;
|
|
75
|
+
/**
|
|
76
|
+
* Content-based fallback: count top-level interface/type declaration blocks
|
|
77
|
+
* vs total top-level statements (excluding imports and blanks).
|
|
78
|
+
*
|
|
79
|
+
* Uses a simple state machine: lines starting with `interface` or `type`
|
|
80
|
+
* keywords begin a type block. Lines that are clearly function/class/const
|
|
81
|
+
* declarations begin a non-type block. Lines inside a block (e.g., interface
|
|
82
|
+
* members) are attributed to whatever block they belong to.
|
|
83
|
+
*/
|
|
84
|
+
private isContentPrimarilyTypes;
|
|
85
|
+
/**
|
|
86
|
+
* Find the deepest (most specific) AST node whose range contains the
|
|
87
|
+
* target row. This gives us a starting point for ancestor walking.
|
|
88
|
+
*/
|
|
89
|
+
private findDeepestNodeAtLine;
|
|
90
|
+
}
|
|
91
|
+
export default ScopeAnalyzer;
|