tribunal-kit 9.2.6 → 9.2.7
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/.agent/ARCHITECTURE.md +2 -2
- package/.agent/history/architecture-intelligence.html +1487 -401
- package/.agent/history/architecture-model.json +9365 -5697
- package/.agent/history/architecture-verification.json +37 -9
- package/.agent/routing_index.json +2069 -3340
- package/.agent/rules/GEMINI.md +1 -1
- package/.agent/scripts/adaptive_execution.js +511 -0
- package/.agent/scripts/architecture_diff.js +303 -260
- package/.agent/scripts/architecture_extractor.js +792 -753
- package/.agent/scripts/architecture_health.js +293 -256
- package/.agent/scripts/architecture_intelligence.js +3100 -0
- package/.agent/scripts/architecture_verifier.js +303 -270
- package/.agent/scripts/architecture_visualizer.js +880 -873
- package/.agent/scripts/blast_radius_engine.js +274 -234
- package/.agent/scripts/capability_evolution.js +453 -0
- package/.agent/scripts/concept_extractor.js +240 -0
- package/.agent/scripts/consideration_engine.js +284 -0
- package/.agent/scripts/coverage_engine.js +102 -0
- package/.agent/scripts/cross_domain_engine.js +1883 -0
- package/.agent/scripts/engineering_memory.js +1313 -0
- package/.agent/scripts/integrity_manifest.js +1 -5
- package/.agent/scripts/judgment_engine.js +790 -0
- package/.agent/scripts/materiality_engine.js +251 -0
- package/.agent/scripts/outcome_engine.js +624 -0
- package/.agent/scripts/outcome_optimization_engine.js +1632 -0
- package/.agent/scripts/simulation_engine.js +1478 -0
- package/.agent/scripts/skill_intelligence.js +1371 -0
- package/.agent/scripts/validation_engine.js +236 -236
- package/.agent/scripts/verification_auditor.js +266 -0
- package/.agent/skill-intelligence/taxonomy/engineering-concepts.json +555 -0
- package/.agent/skill_topic_map.json +301 -341
- package/.agent/skills_inventory.json +4272 -6922
- package/.agent/workflows/arch.md +96 -80
- package/.agent/workflows/skill-intelligence.md +216 -0
- package/CONTRIBUTING.md +2 -2
- package/README.md +24 -24
- package/bin/adapter-install.js +1 -1
- package/package.json +8 -9
- package/plugins/core-plugin.json +1 -1
- package/plugins/gemini/extension.json +1 -1
- package/src/browser/index.js +2 -1
- package/src/browser/launcher.js +41 -1
- package/src/cli.js +3 -1
- package/src/commands/arch.js +261 -172
- package/src/commands/context.js +27 -8
- package/src/commands/learn.js +21 -6
- package/src/commands/memory.js +10 -1
- package/src/commands/native.js +12 -3
- package/src/commands/skill-intelligence.js +1165 -0
- package/src/commands/system1.js +105 -4
- package/src/context/ranker.js +337 -339
- package/src/evolution/engine.js +296 -295
- package/src/synthesis/claim_check_separator.js +294 -271
- package/src/system1/browser_intelligence.js +300 -0
- package/src/system1/constants.js +49 -0
- package/src/system1/decision_engine.js +498 -0
- package/src/system1/evidence_engine.js +602 -0
- package/src/system1/evolution_pipeline.js +581 -0
- package/src/system1/index.d.ts +114 -0
- package/src/system1/index.js +102 -0
- package/src/system1/reviewer_orchestrator.js +477 -0
- package/src/tui/banner.js +1 -1
- package/src/utils/fs.js +27 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* evidence_engine.js — System-1 Capability 1: Evidence Intelligence
|
|
5
|
+
* =================================================================
|
|
6
|
+
* Deterministic, explainable, bounded evidence ranking layer.
|
|
7
|
+
* Determines what information is actually relevant to a decision.
|
|
8
|
+
*
|
|
9
|
+
* Evidence Hierarchy:
|
|
10
|
+
* 1. Changed files (directly modified)
|
|
11
|
+
* 2. Changed symbols/functions (extracted from diff/AST)
|
|
12
|
+
* 3. Direct imports/dependencies
|
|
13
|
+
* 4. Relevant tests (covering changed behavior)
|
|
14
|
+
* 5. Relevant configuration (controlling behavior)
|
|
15
|
+
* 6. Related security boundaries (auth, crypto, permissions)
|
|
16
|
+
* 7. Relevant historical memory (procedural & semantic)
|
|
17
|
+
* 8. Broader repository context (siblings, public entrypoints)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const { normalizePath, estimateTokens } = require('../context/ranker');
|
|
23
|
+
const { HIGH_RISK_PATTERNS, HIGH_RISK_EXTENSIONS } = require('./constants');
|
|
24
|
+
|
|
25
|
+
// Security boundary patterns
|
|
26
|
+
const SECURITY_BOUNDARIES = [
|
|
27
|
+
/[\\/](?:auth|security|jwt|crypto|secrets?|tokens?|passwords?|sessions?)[\\/.]/i,
|
|
28
|
+
/[\\/](?:roles?|access-control|rbac|oauth|sso|permissions?)[\\/.]/i,
|
|
29
|
+
/[\\/]SECURITY\.md$/i,
|
|
30
|
+
/[\\/]\.env/i,
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// Type hierarchy priority for deterministic ordering
|
|
34
|
+
const TYPE_PRIORITY = {
|
|
35
|
+
changed_file: 1,
|
|
36
|
+
changed_symbol: 2,
|
|
37
|
+
direct_dependency: 3,
|
|
38
|
+
direct_importer: 3,
|
|
39
|
+
relevant_test: 4,
|
|
40
|
+
configuration: 5,
|
|
41
|
+
security_boundary: 6,
|
|
42
|
+
historical_memory: 7,
|
|
43
|
+
previous_finding: 8,
|
|
44
|
+
repository_context: 9,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Redact sensitive credentials and tokens from evidence snippets
|
|
49
|
+
*/
|
|
50
|
+
function redactSecrets(text) {
|
|
51
|
+
if (!text) return '';
|
|
52
|
+
return text
|
|
53
|
+
.replace(
|
|
54
|
+
/(?:-----BEGIN [A-Z ]+KEY-----[\s\S]*?-----END [A-Z ]+KEY-----)/g,
|
|
55
|
+
'[REDACTED_PRIVATE_KEY]',
|
|
56
|
+
)
|
|
57
|
+
.replace(/(eyJ[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+)/g, '[REDACTED_JWT_TOKEN]')
|
|
58
|
+
.replace(/(Bearer\s+)[A-Za-z0-9-_.~+/]+=*/gi, '$1[REDACTED_BEARER_TOKEN]')
|
|
59
|
+
.replace(
|
|
60
|
+
/((?:api[_-]?key|secret|password|passwd|token|auth[_-]?token|access[_-]?token|credentials?|private[_-]?key)\s*[:=]\s*['"]?)[^'"\s\n,;]+/gi,
|
|
61
|
+
'$1[REDACTED_SECRET]',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Extract symbols/functions modified in diff or file
|
|
67
|
+
* @param {string} diff
|
|
68
|
+
* @param {string} content
|
|
69
|
+
* @returns {Array<{ name: string, type: string, line?: number }>}
|
|
70
|
+
*/
|
|
71
|
+
function extractChangedSymbols(diff = '', content = '') {
|
|
72
|
+
const symbols = [];
|
|
73
|
+
const symbolNames = new Set();
|
|
74
|
+
const MAX_DIFF_LINES_TO_SCAN = 1000;
|
|
75
|
+
const MAX_SYMBOLS_PER_FILE = 16;
|
|
76
|
+
|
|
77
|
+
const functionRegex =
|
|
78
|
+
/(?:function\s+([a-zA-Z0-9_$]+)|(?:const|let|var)\s+([a-zA-Z0-9_$]+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>|(?:async\s+)?([a-zA-Z0-9_$]+)\s*\([^)]*\)\s*\{|class\s+([a-zA-Z0-9_$]+))/g;
|
|
79
|
+
|
|
80
|
+
// 1. Extract from added diff lines (bounded scan)
|
|
81
|
+
if (diff) {
|
|
82
|
+
const lines = diff.split('\n');
|
|
83
|
+
const scanLimit = Math.min(lines.length, MAX_DIFF_LINES_TO_SCAN);
|
|
84
|
+
for (let idx = 0; idx < scanLimit; idx++) {
|
|
85
|
+
if (symbols.length >= MAX_SYMBOLS_PER_FILE) break;
|
|
86
|
+
const line = lines[idx];
|
|
87
|
+
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
88
|
+
let match;
|
|
89
|
+
while ((match = functionRegex.exec(line)) !== null) {
|
|
90
|
+
const sym = match[1] || match[2] || match[3] || match[4];
|
|
91
|
+
if (sym && !symbolNames.has(sym) && sym.length > 1) {
|
|
92
|
+
symbolNames.add(sym);
|
|
93
|
+
symbols.push({
|
|
94
|
+
name: sym,
|
|
95
|
+
type: match[0].includes('class') ? 'class' : 'function',
|
|
96
|
+
diff_line: idx + 1,
|
|
97
|
+
});
|
|
98
|
+
if (symbols.length >= MAX_SYMBOLS_PER_FILE) break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 2. If content is available and symbols are scarce, extract top definitions
|
|
106
|
+
if (symbols.length === 0 && content) {
|
|
107
|
+
const scanContent = content.length > 50000 ? content.slice(0, 50000) : content;
|
|
108
|
+
let match;
|
|
109
|
+
while ((match = functionRegex.exec(scanContent)) !== null) {
|
|
110
|
+
const sym = match[1] || match[2] || match[3] || match[4];
|
|
111
|
+
if (sym && !symbolNames.has(sym) && sym.length > 1) {
|
|
112
|
+
symbolNames.add(sym);
|
|
113
|
+
symbols.push({ name: sym, type: match[0].includes('class') ? 'class' : 'function' });
|
|
114
|
+
if (symbols.length >= 8) break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return symbols;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Extract bounded snippet from file content around specific line or symbol
|
|
124
|
+
*/
|
|
125
|
+
function extractBoundedSnippet(content, targetLine = null, targetSymbol = null, maxLines = 10) {
|
|
126
|
+
if (!content) return '';
|
|
127
|
+
const MAX_SNIPPET_CHARS = 2000;
|
|
128
|
+
const lines = content.split('\n');
|
|
129
|
+
let snippet = content;
|
|
130
|
+
if (lines.length > maxLines) {
|
|
131
|
+
let center = 0;
|
|
132
|
+
if (targetLine !== null && targetLine > 0 && targetLine <= lines.length) {
|
|
133
|
+
center = targetLine - 1;
|
|
134
|
+
} else if (targetSymbol) {
|
|
135
|
+
const foundIdx = lines.findIndex(l => l.includes(targetSymbol));
|
|
136
|
+
if (foundIdx !== -1) center = foundIdx;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const start = Math.max(0, center - Math.floor(maxLines / 2));
|
|
140
|
+
const end = Math.min(lines.length, start + maxLines);
|
|
141
|
+
snippet = lines.slice(start, end).join('\n');
|
|
142
|
+
}
|
|
143
|
+
if (snippet.length > MAX_SNIPPET_CHARS) {
|
|
144
|
+
snippet = snippet.slice(0, MAX_SNIPPET_CHARS) + '\n... [TRUNCATED]';
|
|
145
|
+
}
|
|
146
|
+
return redactSecrets(snippet);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Score and collect evidence items
|
|
151
|
+
* @param {Object} options
|
|
152
|
+
* @param {string} [options.task]
|
|
153
|
+
* @param {string} [options.diff]
|
|
154
|
+
* @param {string[]|string} [options.files]
|
|
155
|
+
* @param {string} [options.repoRoot]
|
|
156
|
+
* @param {Array} [options.memory]
|
|
157
|
+
* @param {Array} [options.previousFindings]
|
|
158
|
+
* @param {number} [options.maxItems]
|
|
159
|
+
* @param {number} [options.maxTokens]
|
|
160
|
+
* @returns {Object} Evidence Intelligence Result
|
|
161
|
+
*/
|
|
162
|
+
function collectAndRankEvidence(options = {}) {
|
|
163
|
+
const repoRoot = options.repoRoot ? path.resolve(options.repoRoot) : process.cwd();
|
|
164
|
+
const rawFiles = options.files;
|
|
165
|
+
const changedFiles = (
|
|
166
|
+
Array.isArray(rawFiles) ? rawFiles : typeof rawFiles === 'string' ? rawFiles.split(',') : []
|
|
167
|
+
)
|
|
168
|
+
.map(f => (typeof f === 'string' ? f.trim() : ''))
|
|
169
|
+
.filter(Boolean)
|
|
170
|
+
.map(normalizePath);
|
|
171
|
+
|
|
172
|
+
const task = typeof options.task === 'string' ? options.task : '';
|
|
173
|
+
const diff = typeof options.diff === 'string' ? options.diff : '';
|
|
174
|
+
const memoryItems = Array.isArray(options.memory) ? options.memory : [];
|
|
175
|
+
const prevFindings = Array.isArray(options.previousFindings) ? options.previousFindings : [];
|
|
176
|
+
const maxItems =
|
|
177
|
+
typeof options.maxItems === 'number' && options.maxItems > 0 ? options.maxItems : 20;
|
|
178
|
+
const maxTokens =
|
|
179
|
+
typeof options.maxTokens === 'number' && options.maxTokens > 0 ? options.maxTokens : 4000;
|
|
180
|
+
|
|
181
|
+
const rawEvidence = [];
|
|
182
|
+
const visitedPaths = new Set();
|
|
183
|
+
|
|
184
|
+
const MAX_FILE_SIZE = 1024 * 1024; // 1MB limit for bounded evidence extraction
|
|
185
|
+
|
|
186
|
+
function isPathInsideRepo(absPath, root) {
|
|
187
|
+
const rel = path.relative(root, absPath);
|
|
188
|
+
return !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Helper to load file safely
|
|
192
|
+
const evidenceTimestamp = Date.now();
|
|
193
|
+
const fileCache = new Map();
|
|
194
|
+
function readFileSafe(relPath) {
|
|
195
|
+
let norm = normalizePath(relPath);
|
|
196
|
+
try {
|
|
197
|
+
norm = decodeURIComponent(norm);
|
|
198
|
+
} catch (_) {}
|
|
199
|
+
if (fileCache.has(norm)) return fileCache.get(norm);
|
|
200
|
+
const abs = path.resolve(repoRoot, norm);
|
|
201
|
+
try {
|
|
202
|
+
if (!isPathInsideRepo(abs, repoRoot)) {
|
|
203
|
+
fileCache.set(norm, null);
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
if (fs.existsSync(abs)) {
|
|
207
|
+
const stat = fs.statSync(abs);
|
|
208
|
+
if (stat.isFile() && stat.size <= MAX_FILE_SIZE) {
|
|
209
|
+
const text = fs.readFileSync(abs, 'utf8');
|
|
210
|
+
fileCache.set(norm, text);
|
|
211
|
+
return text;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} catch (_) {}
|
|
215
|
+
fileCache.set(norm, null);
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Helper to check if file exists
|
|
220
|
+
function fileExists(relPath) {
|
|
221
|
+
let norm = normalizePath(relPath);
|
|
222
|
+
try {
|
|
223
|
+
norm = decodeURIComponent(norm);
|
|
224
|
+
} catch (_) {}
|
|
225
|
+
const abs = path.resolve(repoRoot, norm);
|
|
226
|
+
try {
|
|
227
|
+
if (!isPathInsideRepo(abs, repoRoot)) return false;
|
|
228
|
+
return fs.existsSync(abs) && fs.statSync(abs).isFile();
|
|
229
|
+
} catch (_) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 1. Process Directly Changed Files with Priority & Budgeting
|
|
235
|
+
const MAX_DEEP_INSPECT_FILES = 50;
|
|
236
|
+
|
|
237
|
+
// Prioritize security boundaries and critical schema files first
|
|
238
|
+
changedFiles.sort((a, b) => {
|
|
239
|
+
const aRisk =
|
|
240
|
+
SECURITY_BOUNDARIES.some(p => p.test('/' + a)) || HIGH_RISK_EXTENSIONS.test(a) ? 1 : 0;
|
|
241
|
+
const bRisk =
|
|
242
|
+
SECURITY_BOUNDARIES.some(p => p.test('/' + b)) || HIGH_RISK_EXTENSIONS.test(b) ? 1 : 0;
|
|
243
|
+
return bRisk - aRisk;
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
for (let fIdx = 0; fIdx < changedFiles.length; fIdx++) {
|
|
247
|
+
const file = changedFiles[fIdx];
|
|
248
|
+
visitedPaths.add(file);
|
|
249
|
+
const isDeep = fIdx < MAX_DEEP_INSPECT_FILES;
|
|
250
|
+
const content = isDeep ? readFileSafe(file) : null;
|
|
251
|
+
const isSecurity =
|
|
252
|
+
SECURITY_BOUNDARIES.some(p => p.test('/' + file)) || HIGH_RISK_PATTERNS.test(file);
|
|
253
|
+
const isHighRiskExt = HIGH_RISK_EXTENSIONS.test(file);
|
|
254
|
+
const exists = isDeep ? fileExists(file) : true;
|
|
255
|
+
|
|
256
|
+
const reasons = ['directly modified file in change set'];
|
|
257
|
+
let score = 0.95;
|
|
258
|
+
const confidence = exists ? 'L1' : 'L2';
|
|
259
|
+
|
|
260
|
+
if (isSecurity || isHighRiskExt) {
|
|
261
|
+
score = 1.0;
|
|
262
|
+
reasons.push('touches critical security / schema boundary');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const snippet = content
|
|
266
|
+
? extractBoundedSnippet(content, 1, null, 12)
|
|
267
|
+
: redactSecrets(diff.slice(0, 500) || '');
|
|
268
|
+
|
|
269
|
+
rawEvidence.push({
|
|
270
|
+
id: `file:${file}`,
|
|
271
|
+
type: 'changed_file',
|
|
272
|
+
path: file,
|
|
273
|
+
score,
|
|
274
|
+
reasons,
|
|
275
|
+
confidence,
|
|
276
|
+
provenance: 'OBSERVED',
|
|
277
|
+
timestamp: evidenceTimestamp,
|
|
278
|
+
is_mandatory: true,
|
|
279
|
+
estimated_tokens: estimateTokens(snippet || 100),
|
|
280
|
+
snippet,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (!isDeep) continue; // For overflow files beyond capacity, skip deep AST symbol & dependency checks
|
|
284
|
+
|
|
285
|
+
// 2. Extract changed symbols/functions for this file
|
|
286
|
+
const symbols = extractChangedSymbols(diff, content);
|
|
287
|
+
for (const sym of symbols) {
|
|
288
|
+
const symSnippet = content ? extractBoundedSnippet(content, sym.diff_line, sym.name, 8) : '';
|
|
289
|
+
const symIsSecurity = HIGH_RISK_PATTERNS.test(sym.name);
|
|
290
|
+
const symReasons = [`modified ${sym.type} "${sym.name}" in ${file}`];
|
|
291
|
+
let symScore = 0.88;
|
|
292
|
+
if (symIsSecurity) {
|
|
293
|
+
symScore = 0.96;
|
|
294
|
+
symReasons.push('symbol name matches high-risk pattern');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
rawEvidence.push({
|
|
298
|
+
id: `symbol:${file}:${sym.name}`,
|
|
299
|
+
type: 'changed_symbol',
|
|
300
|
+
path: file,
|
|
301
|
+
symbol: sym.name,
|
|
302
|
+
score: symScore,
|
|
303
|
+
reasons: symReasons,
|
|
304
|
+
confidence: exists ? 'L1' : 'L2',
|
|
305
|
+
provenance: 'INFERRED',
|
|
306
|
+
timestamp: evidenceTimestamp,
|
|
307
|
+
is_mandatory: false,
|
|
308
|
+
estimated_tokens: estimateTokens(symSnippet || 60),
|
|
309
|
+
snippet: symSnippet,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// 3. Direct imports & dependencies
|
|
314
|
+
if (content) {
|
|
315
|
+
const scanContent = content.length > 50000 ? content.slice(0, 50000) : content;
|
|
316
|
+
const importRegex =
|
|
317
|
+
/(?:import\s+(?:[^'"]*?\s+from\s+)?['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\))/g;
|
|
318
|
+
let match;
|
|
319
|
+
while ((match = importRegex.exec(scanContent)) !== null) {
|
|
320
|
+
const importTarget = match[1] || match[2];
|
|
321
|
+
if (importTarget && (importTarget.startsWith('.') || importTarget.startsWith('/'))) {
|
|
322
|
+
// Resolve relative to file directory
|
|
323
|
+
const fileDir = path.dirname(file);
|
|
324
|
+
const resolvedRel = normalizePath(path.join(fileDir, importTarget));
|
|
325
|
+
// Check extensions (.js, .ts, /index.js, etc.)
|
|
326
|
+
const candidates = [
|
|
327
|
+
resolvedRel,
|
|
328
|
+
`${resolvedRel}.js`,
|
|
329
|
+
`${resolvedRel}.ts`,
|
|
330
|
+
`${resolvedRel}.json`,
|
|
331
|
+
`${resolvedRel}/index.js`,
|
|
332
|
+
`${resolvedRel}/index.ts`,
|
|
333
|
+
];
|
|
334
|
+
const actualTarget = candidates.find(c => fileExists(c)) || resolvedRel;
|
|
335
|
+
|
|
336
|
+
if (!visitedPaths.has(actualTarget)) {
|
|
337
|
+
visitedPaths.add(actualTarget);
|
|
338
|
+
const depContent = readFileSafe(actualTarget);
|
|
339
|
+
const depIsSecurity = SECURITY_BOUNDARIES.some(p => p.test('/' + actualTarget));
|
|
340
|
+
const depReasons = [`direct dependency imported by ${file}`];
|
|
341
|
+
const depScore = depIsSecurity ? 0.9 : 0.8;
|
|
342
|
+
if (depIsSecurity) depReasons.push('dependency operates in security boundary');
|
|
343
|
+
|
|
344
|
+
rawEvidence.push({
|
|
345
|
+
id: `dep:${actualTarget}`,
|
|
346
|
+
type: 'direct_dependency',
|
|
347
|
+
path: actualTarget,
|
|
348
|
+
score: depScore,
|
|
349
|
+
reasons: depReasons,
|
|
350
|
+
confidence: fileExists(actualTarget) ? 'L2' : 'L3',
|
|
351
|
+
provenance: 'INFERRED',
|
|
352
|
+
timestamp: evidenceTimestamp,
|
|
353
|
+
is_mandatory: depIsSecurity,
|
|
354
|
+
estimated_tokens: estimateTokens(
|
|
355
|
+
depContent ? extractBoundedSnippet(depContent, 1, null, 8) : 80,
|
|
356
|
+
),
|
|
357
|
+
snippet: depContent ? extractBoundedSnippet(depContent, 1, null, 8) : '',
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 4. Relevant tests for this changed file
|
|
365
|
+
const baseName = path.basename(file).replace(/\.[^.]+$/, '');
|
|
366
|
+
const possibleTestPaths = [
|
|
367
|
+
`test/unit/${baseName}.test.js`,
|
|
368
|
+
`test/unit/${baseName}.spec.js`,
|
|
369
|
+
`test/integration/${baseName}.test.js`,
|
|
370
|
+
`test/${baseName}.test.js`,
|
|
371
|
+
`tests/${baseName}.test.js`,
|
|
372
|
+
`${path.dirname(file)}/__tests__/${baseName}.test.js`,
|
|
373
|
+
`${path.dirname(file)}/${baseName}.test.js`,
|
|
374
|
+
];
|
|
375
|
+
|
|
376
|
+
for (const testPath of possibleTestPaths) {
|
|
377
|
+
const normTest = normalizePath(testPath);
|
|
378
|
+
if (fileExists(normTest) && !visitedPaths.has(normTest)) {
|
|
379
|
+
visitedPaths.add(normTest);
|
|
380
|
+
const testContent = readFileSafe(normTest);
|
|
381
|
+
rawEvidence.push({
|
|
382
|
+
id: `test:${normTest}`,
|
|
383
|
+
type: 'relevant_test',
|
|
384
|
+
path: normTest,
|
|
385
|
+
score: 0.78,
|
|
386
|
+
reasons: [`test suite covering modified behavior in ${file}`],
|
|
387
|
+
confidence: 'L1',
|
|
388
|
+
provenance: 'OBSERVED',
|
|
389
|
+
timestamp: evidenceTimestamp,
|
|
390
|
+
is_mandatory: false,
|
|
391
|
+
estimated_tokens: estimateTokens(
|
|
392
|
+
testContent ? extractBoundedSnippet(testContent, 1, null, 10) : 100,
|
|
393
|
+
),
|
|
394
|
+
snippet: testContent ? extractBoundedSnippet(testContent, 1, null, 10) : '',
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// 5. Relevant configuration files (package.json, tsconfig.json, docker, etc.)
|
|
401
|
+
const commonConfigs = [
|
|
402
|
+
'package.json',
|
|
403
|
+
'tsconfig.json',
|
|
404
|
+
'.eslintrc.js',
|
|
405
|
+
'eslint.config.js',
|
|
406
|
+
'Dockerfile',
|
|
407
|
+
'.github/workflows/ci.yml',
|
|
408
|
+
];
|
|
409
|
+
for (const cfg of commonConfigs) {
|
|
410
|
+
if (fileExists(cfg) && !visitedPaths.has(cfg)) {
|
|
411
|
+
const cfgContent = readFileSafe(cfg);
|
|
412
|
+
const isConfigRelevant =
|
|
413
|
+
changedFiles.some(cf => cf.endsWith('.json') || cf.endsWith('.js') || cf.endsWith('.ts')) ||
|
|
414
|
+
task.toLowerCase().includes('dependency') ||
|
|
415
|
+
task.toLowerCase().includes('package');
|
|
416
|
+
|
|
417
|
+
if (isConfigRelevant || cfg === 'package.json') {
|
|
418
|
+
visitedPaths.add(cfg);
|
|
419
|
+
rawEvidence.push({
|
|
420
|
+
id: `config:${cfg}`,
|
|
421
|
+
type: 'configuration',
|
|
422
|
+
path: cfg,
|
|
423
|
+
score: cfg === 'package.json' ? 0.72 : 0.65,
|
|
424
|
+
reasons: [`project configuration controlling runtime behavior (${cfg})`],
|
|
425
|
+
confidence: 'L1',
|
|
426
|
+
provenance: 'OBSERVED',
|
|
427
|
+
timestamp: evidenceTimestamp,
|
|
428
|
+
is_mandatory: false,
|
|
429
|
+
estimated_tokens: estimateTokens(
|
|
430
|
+
cfgContent ? extractBoundedSnippet(cfgContent, 1, null, 10) : 100,
|
|
431
|
+
),
|
|
432
|
+
snippet: cfgContent ? extractBoundedSnippet(cfgContent, 1, null, 10) : '',
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// 6. Security Boundaries (Root SECURITY.md, auth policies)
|
|
439
|
+
const rootSecurityFiles = ['SECURITY.md', '.env.example'];
|
|
440
|
+
for (const secFile of rootSecurityFiles) {
|
|
441
|
+
if (fileExists(secFile) && !visitedPaths.has(secFile)) {
|
|
442
|
+
visitedPaths.add(secFile);
|
|
443
|
+
const secContent = readFileSafe(secFile);
|
|
444
|
+
rawEvidence.push({
|
|
445
|
+
id: `security:${secFile}`,
|
|
446
|
+
type: 'security_boundary',
|
|
447
|
+
path: secFile,
|
|
448
|
+
score: 0.85,
|
|
449
|
+
reasons: [`repository root security and credential policy (${secFile})`],
|
|
450
|
+
confidence: 'L1',
|
|
451
|
+
provenance: 'OBSERVED',
|
|
452
|
+
timestamp: evidenceTimestamp,
|
|
453
|
+
is_mandatory: true,
|
|
454
|
+
estimated_tokens: estimateTokens(
|
|
455
|
+
secContent ? extractBoundedSnippet(secContent, 1, null, 10) : 120,
|
|
456
|
+
),
|
|
457
|
+
snippet: secContent ? extractBoundedSnippet(secContent, 1, null, 10) : '',
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// 7. Historical Memory from Memory Engine
|
|
463
|
+
for (const mem of memoryItems) {
|
|
464
|
+
const memContent = typeof mem === 'string' ? mem : mem.content || mem.summary || '';
|
|
465
|
+
if (!memContent) continue;
|
|
466
|
+
const memTags = Array.isArray(mem.tags) ? mem.tags : [];
|
|
467
|
+
const memIsRelevant =
|
|
468
|
+
changedFiles.some(f => {
|
|
469
|
+
const baseNoExt = path.basename(f, path.extname(f)).toLowerCase();
|
|
470
|
+
const baseWithExt = path.basename(f).toLowerCase();
|
|
471
|
+
return (
|
|
472
|
+
memContent.toLowerCase().includes(baseNoExt) ||
|
|
473
|
+
memContent.toLowerCase().includes(baseWithExt) ||
|
|
474
|
+
memTags.some(
|
|
475
|
+
t => baseNoExt.includes(t.toLowerCase()) || f.toLowerCase().includes(t.toLowerCase()),
|
|
476
|
+
)
|
|
477
|
+
);
|
|
478
|
+
}) ||
|
|
479
|
+
(task &&
|
|
480
|
+
(memContent.toLowerCase().includes(task.toLowerCase().slice(0, 15)) ||
|
|
481
|
+
memTags.some(t => task.toLowerCase().includes(t.toLowerCase()))));
|
|
482
|
+
|
|
483
|
+
if (memIsRelevant) {
|
|
484
|
+
rawEvidence.push({
|
|
485
|
+
id: `memory:${mem.id || Math.abs(memContent.length)}`,
|
|
486
|
+
type: 'historical_memory',
|
|
487
|
+
path: mem.tags ? mem.tags.join(',') : 'memory-store',
|
|
488
|
+
score: 0.68,
|
|
489
|
+
reasons: ['relevant historical memory from past sessions/decisions'],
|
|
490
|
+
confidence: 'L2',
|
|
491
|
+
provenance: 'REPORTED',
|
|
492
|
+
timestamp: evidenceTimestamp,
|
|
493
|
+
is_mandatory: false,
|
|
494
|
+
estimated_tokens: estimateTokens(memContent),
|
|
495
|
+
snippet: memContent.slice(0, 200),
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// 8. Previous Findings
|
|
501
|
+
for (const f of prevFindings) {
|
|
502
|
+
const fDesc = f.desc || f.description || f.message || '';
|
|
503
|
+
if (!fDesc) continue;
|
|
504
|
+
rawEvidence.push({
|
|
505
|
+
id: `finding:${f.id || 'prev'}`,
|
|
506
|
+
type: 'previous_finding',
|
|
507
|
+
path: f.location || f.file || 'audit-result',
|
|
508
|
+
score: f.severity === 'Critical' ? 0.92 : f.severity === 'Important' ? 0.82 : 0.65,
|
|
509
|
+
reasons: [`previous finding: [${f.severity || 'Finding'}] ${fDesc}`],
|
|
510
|
+
confidence: 'L2',
|
|
511
|
+
provenance: 'REPORTED',
|
|
512
|
+
timestamp: evidenceTimestamp,
|
|
513
|
+
is_mandatory: f.severity === 'Critical',
|
|
514
|
+
estimated_tokens: estimateTokens(fDesc),
|
|
515
|
+
snippet: fDesc,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Deterministic stable sorting:
|
|
520
|
+
// 1. score DESCENDING
|
|
521
|
+
// 2. TYPE_PRIORITY ASCENDING (1 to 9)
|
|
522
|
+
// 3. id / path ASCENDING (alphabetic)
|
|
523
|
+
rawEvidence.sort((a, b) => {
|
|
524
|
+
if (Math.abs(b.score - a.score) > 0.001) return b.score - a.score;
|
|
525
|
+
const prioA = TYPE_PRIORITY[a.type] || 99;
|
|
526
|
+
const prioB = TYPE_PRIORITY[b.type] || 99;
|
|
527
|
+
if (prioA !== prioB) return prioA - prioB;
|
|
528
|
+
return a.id.localeCompare(b.id);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// Apply budget constraints (maxItems & maxTokens)
|
|
532
|
+
const selectedEvidence = [];
|
|
533
|
+
const truncatedEvidence = [];
|
|
534
|
+
let tokenCount = 0;
|
|
535
|
+
|
|
536
|
+
// Step 1: Retain all mandatory items first
|
|
537
|
+
for (const item of rawEvidence) {
|
|
538
|
+
if (item.is_mandatory) {
|
|
539
|
+
selectedEvidence.push(item);
|
|
540
|
+
tokenCount += item.estimated_tokens;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Step 2: Include non-mandatory items up to budget
|
|
545
|
+
for (const item of rawEvidence) {
|
|
546
|
+
if (item.is_mandatory) continue;
|
|
547
|
+
if (selectedEvidence.length < maxItems && tokenCount + item.estimated_tokens <= maxTokens) {
|
|
548
|
+
selectedEvidence.push(item);
|
|
549
|
+
tokenCount += item.estimated_tokens;
|
|
550
|
+
} else {
|
|
551
|
+
truncatedEvidence.push(item);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Deterministic re-sort of selected items
|
|
556
|
+
selectedEvidence.sort((a, b) => {
|
|
557
|
+
if (Math.abs(b.score - a.score) > 0.001) return b.score - a.score;
|
|
558
|
+
const prioA = TYPE_PRIORITY[a.type] || 99;
|
|
559
|
+
const prioB = TYPE_PRIORITY[b.type] || 99;
|
|
560
|
+
if (prioA !== prioB) return prioA - prioB;
|
|
561
|
+
return a.id.localeCompare(b.id);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
// Aggregate overall confidence and risk indicators
|
|
565
|
+
const touchesSecurityBoundary =
|
|
566
|
+
changedFiles.some(
|
|
567
|
+
f => SECURITY_BOUNDARIES.some(p => p.test('/' + f)) || HIGH_RISK_PATTERNS.test(f),
|
|
568
|
+
) ||
|
|
569
|
+
(diff && HIGH_RISK_PATTERNS.test(diff));
|
|
570
|
+
const criticalCount = selectedEvidence.filter(e => e.is_mandatory).length;
|
|
571
|
+
|
|
572
|
+
let overallConfidence = 'L1';
|
|
573
|
+
if (selectedEvidence.length === 0) {
|
|
574
|
+
overallConfidence = 'L4';
|
|
575
|
+
} else if (selectedEvidence.some(e => e.confidence === 'L4')) {
|
|
576
|
+
overallConfidence = 'L3';
|
|
577
|
+
} else if (selectedEvidence.some(e => e.confidence === 'L3')) {
|
|
578
|
+
overallConfidence = 'L2';
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return {
|
|
582
|
+
evidence: selectedEvidence,
|
|
583
|
+
truncated: truncatedEvidence,
|
|
584
|
+
summary: {
|
|
585
|
+
total_items_evaluated: rawEvidence.length,
|
|
586
|
+
total_items_selected: selectedEvidence.length,
|
|
587
|
+
total_tokens: tokenCount,
|
|
588
|
+
critical_evidence_count: criticalCount,
|
|
589
|
+
has_security_boundary: touchesSecurityBoundary,
|
|
590
|
+
confidence: overallConfidence,
|
|
591
|
+
},
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
module.exports = {
|
|
596
|
+
collectAndRankEvidence,
|
|
597
|
+
extractChangedSymbols,
|
|
598
|
+
extractBoundedSnippet,
|
|
599
|
+
redactSecrets,
|
|
600
|
+
TYPE_PRIORITY,
|
|
601
|
+
SECURITY_BOUNDARIES,
|
|
602
|
+
};
|