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
package/src/context/ranker.js
CHANGED
|
@@ -1,339 +1,337 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* ranker.js — Deterministic Evidence-Driven Context Ranking Engine
|
|
5
|
-
* =================================================================
|
|
6
|
-
* System-1 Capability 2:
|
|
7
|
-
* Evaluates repository evidence deterministically to rank candidate files
|
|
8
|
-
* for prompt context selection, ensuring maximum relevant evidence and
|
|
9
|
-
* minimum unnecessary context.
|
|
10
|
-
*
|
|
11
|
-
* Guarantees:
|
|
12
|
-
* - Stable, deterministic ordering (score DESC, path ASC)
|
|
13
|
-
* - Zero LLM dependency for ranking
|
|
14
|
-
* - Critical security boundaries receive mandatory retention
|
|
15
|
-
* - Configurable token and item budgets
|
|
16
|
-
* - Explicit before/after measurement metrics
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const fs = require('fs');
|
|
20
|
-
const path = require('path');
|
|
21
|
-
|
|
22
|
-
const SECURITY_PATTERNS = [
|
|
23
|
-
/[\\/](?:auth|security|permissions?|jwt|crypto|secrets?|tokens?|passwords?|sessions?)[\\/.]/i,
|
|
24
|
-
/[\\/](?:roles?|access-control|rbac|oauth|sso)[\\/.]/i,
|
|
25
|
-
/[\\/]SECURITY\.md$/i,
|
|
26
|
-
/[\\/]\.env/i,
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
const CONFIG_PATTERNS = [
|
|
30
|
-
/[\\/](?:package\.json|tsconfig\.json|Cargo\.toml|Cargo\.lock|go\.mod|pyproject\.toml)$/i,
|
|
31
|
-
/[\\/](?:\.eslintrc|\.prettierrc|jest\.config|vitest\.config|webpack\.config|vite\.config)[^\\/]*$/i,
|
|
32
|
-
/[\\/]Dockerfile/i,
|
|
33
|
-
/[\\/](?:docker-compose|\.github[\\/]workflows)/i,
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const DOC_PATTERNS = [
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
*
|
|
121
|
-
* @param {
|
|
122
|
-
* @
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (
|
|
199
|
-
score = Math.max(score, 0.
|
|
200
|
-
reasons.push('security
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (
|
|
212
|
-
score = Math.max(score, 0.
|
|
213
|
-
reasons.push(`direct
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
estimateTokens,
|
|
339
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ranker.js — Deterministic Evidence-Driven Context Ranking Engine
|
|
5
|
+
* =================================================================
|
|
6
|
+
* System-1 Capability 2:
|
|
7
|
+
* Evaluates repository evidence deterministically to rank candidate files
|
|
8
|
+
* for prompt context selection, ensuring maximum relevant evidence and
|
|
9
|
+
* minimum unnecessary context.
|
|
10
|
+
*
|
|
11
|
+
* Guarantees:
|
|
12
|
+
* - Stable, deterministic ordering (score DESC, path ASC)
|
|
13
|
+
* - Zero LLM dependency for ranking
|
|
14
|
+
* - Critical security boundaries receive mandatory retention
|
|
15
|
+
* - Configurable token and item budgets
|
|
16
|
+
* - Explicit before/after measurement metrics
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
const SECURITY_PATTERNS = [
|
|
23
|
+
/[\\/](?:auth|security|permissions?|jwt|crypto|secrets?|tokens?|passwords?|sessions?)[\\/.]/i,
|
|
24
|
+
/[\\/](?:roles?|access-control|rbac|oauth|sso)[\\/.]/i,
|
|
25
|
+
/[\\/]SECURITY\.md$/i,
|
|
26
|
+
/[\\/]\.env/i,
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const CONFIG_PATTERNS = [
|
|
30
|
+
/[\\/](?:package\.json|tsconfig\.json|Cargo\.toml|Cargo\.lock|go\.mod|pyproject\.toml)$/i,
|
|
31
|
+
/[\\/](?:\.eslintrc|\.prettierrc|jest\.config|vitest\.config|webpack\.config|vite\.config)[^\\/]*$/i,
|
|
32
|
+
/[\\/]Dockerfile/i,
|
|
33
|
+
/[\\/](?:docker-compose|\.github[\\/]workflows)/i,
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const DOC_PATTERNS = [/\.(?:md|markdown|txt|rst|adoc)$/i, /[\\/]docs[\\/]/i];
|
|
37
|
+
|
|
38
|
+
const TEST_PATTERNS = [
|
|
39
|
+
/\.(?:test|spec)\.[a-zA-Z0-9]+$/i,
|
|
40
|
+
/[\\/](?:__tests__|tests?|fixtures?)[\\/]/i,
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const ENTRY_PATTERNS = [/[\\/](?:index|main|cli|app|server)\.[a-zA-Z0-9]+$/i, /[\\/]api[\\/]/i];
|
|
44
|
+
|
|
45
|
+
function normalizePath(filePath) {
|
|
46
|
+
return filePath.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function estimateTokens(textOrBytes) {
|
|
50
|
+
if (typeof textOrBytes === 'number') {
|
|
51
|
+
return Math.max(1, Math.ceil(textOrBytes / 4));
|
|
52
|
+
}
|
|
53
|
+
if (typeof textOrBytes === 'string') {
|
|
54
|
+
return Math.max(1, Math.ceil(textOrBytes.length / 4));
|
|
55
|
+
}
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Extract direct import / require targets from code text
|
|
61
|
+
*/
|
|
62
|
+
function extractImports(code) {
|
|
63
|
+
const imports = new Set();
|
|
64
|
+
const importRegex =
|
|
65
|
+
/(?:import\s+(?:[\w*\s{},]*\s+from\s+)?['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\))/g;
|
|
66
|
+
let match;
|
|
67
|
+
while ((match = importRegex.exec(code)) !== null) {
|
|
68
|
+
const target = match[1] || match[2];
|
|
69
|
+
if (target && (target.startsWith('.') || target.startsWith('/'))) {
|
|
70
|
+
imports.add(target);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return imports;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if candidateFile is imported by targetFile or vice versa
|
|
78
|
+
*/
|
|
79
|
+
function checkImportRelation(candidateRel, targetRel, fileContents) {
|
|
80
|
+
const targetContent = fileContents.get(targetRel) || '';
|
|
81
|
+
const candidateContent = fileContents.get(candidateRel) || '';
|
|
82
|
+
|
|
83
|
+
const targetBasename = path.basename(targetRel, path.extname(targetRel));
|
|
84
|
+
const candidateBasename = path.basename(candidateRel, path.extname(candidateRel));
|
|
85
|
+
|
|
86
|
+
let isDirectDependency = false;
|
|
87
|
+
let isDirectImporter = false;
|
|
88
|
+
|
|
89
|
+
// Check target -> candidate
|
|
90
|
+
if (targetContent) {
|
|
91
|
+
const targetImports = extractImports(targetContent);
|
|
92
|
+
for (const imp of targetImports) {
|
|
93
|
+
if (imp.includes(candidateBasename) || imp.endsWith(candidateBasename)) {
|
|
94
|
+
isDirectDependency = true;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check candidate -> target
|
|
101
|
+
if (candidateContent) {
|
|
102
|
+
const candidateImports = extractImports(candidateContent);
|
|
103
|
+
for (const imp of candidateImports) {
|
|
104
|
+
if (imp.includes(targetBasename) || imp.endsWith(targetBasename)) {
|
|
105
|
+
isDirectImporter = true;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { isDirectDependency, isDirectImporter };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Deterministic Context Ranking Engine
|
|
116
|
+
* @param {Object} options
|
|
117
|
+
* @param {string} [options.repoRoot] - Repository root directory
|
|
118
|
+
* @param {string[]} [options.targetFiles] - Array of directly modified files
|
|
119
|
+
* @param {string[]} [options.candidateFiles] - Array of candidate files to rank
|
|
120
|
+
* @param {number} [options.maxTokens] - Maximum token budget for ranking
|
|
121
|
+
* @param {number} [options.maxItems] - Maximum item count for ranking
|
|
122
|
+
* @returns {Object} Ranking payload with scored items and measurement metrics
|
|
123
|
+
*/
|
|
124
|
+
function rankContext(options = {}) {
|
|
125
|
+
const repoRoot = options.repoRoot ? path.resolve(options.repoRoot) : process.cwd();
|
|
126
|
+
const rawTargetFiles = options.targetFiles || [];
|
|
127
|
+
const targetFiles = (Array.isArray(rawTargetFiles) ? rawTargetFiles : [rawTargetFiles])
|
|
128
|
+
.map(normalizePath)
|
|
129
|
+
.filter(Boolean);
|
|
130
|
+
|
|
131
|
+
let rawCandidates = options.candidateFiles || [];
|
|
132
|
+
if (!Array.isArray(rawCandidates)) rawCandidates = [rawCandidates];
|
|
133
|
+
|
|
134
|
+
// If candidates not provided, union of targetFiles
|
|
135
|
+
const candidateSet = new Set(rawCandidates.map(normalizePath).filter(Boolean));
|
|
136
|
+
for (const tf of targetFiles) {
|
|
137
|
+
candidateSet.add(tf);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const allCandidatePaths = Array.from(candidateSet);
|
|
141
|
+
|
|
142
|
+
// Pre-load content for import analysis if files exist on disk
|
|
143
|
+
const fileContents = new Map();
|
|
144
|
+
const fileStats = new Map();
|
|
145
|
+
|
|
146
|
+
for (const relPath of allCandidatePaths) {
|
|
147
|
+
const absPath = path.resolve(repoRoot, relPath);
|
|
148
|
+
if (fs.existsSync(absPath)) {
|
|
149
|
+
try {
|
|
150
|
+
const stat = fs.statSync(absPath);
|
|
151
|
+
if (stat.isFile()) {
|
|
152
|
+
fileStats.set(relPath, stat);
|
|
153
|
+
// Only read if reasonably sized (< 2MB)
|
|
154
|
+
if (stat.size < 2 * 1024 * 1024) {
|
|
155
|
+
fileContents.set(relPath, fs.readFileSync(absPath, 'utf8'));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch (_) {}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const scoredItems = [];
|
|
163
|
+
let totalTokensBefore = 0;
|
|
164
|
+
|
|
165
|
+
for (const relPath of allCandidatePaths) {
|
|
166
|
+
const norm = normalizePath(relPath);
|
|
167
|
+
const reasons = [];
|
|
168
|
+
let score = 0.05; // Base baseline
|
|
169
|
+
let isMandatory = false;
|
|
170
|
+
|
|
171
|
+
const stat = fileStats.get(norm);
|
|
172
|
+
const content = fileContents.get(norm);
|
|
173
|
+
const sizeBytes = stat ? stat.size : 100;
|
|
174
|
+
const estimatedTokens = content ? estimateTokens(content) : estimateTokens(sizeBytes);
|
|
175
|
+
totalTokensBefore += estimatedTokens;
|
|
176
|
+
|
|
177
|
+
const isDirectlyModified = targetFiles.includes(norm);
|
|
178
|
+
const isRootSecurityBoundary =
|
|
179
|
+
/[\\/]SECURITY\.md$/i.test('/' + norm) || /[\\/]\.env/i.test('/' + norm);
|
|
180
|
+
const isSecurityDomain = SECURITY_PATTERNS.some(pat => pat.test('/' + norm));
|
|
181
|
+
const isConfig = CONFIG_PATTERNS.some(pat => pat.test('/' + norm));
|
|
182
|
+
const isDoc = DOC_PATTERNS.some(pat => pat.test('/' + norm));
|
|
183
|
+
const isTest = TEST_PATTERNS.some(pat => pat.test('/' + norm));
|
|
184
|
+
const isEntrypoint = ENTRY_PATTERNS.some(pat => pat.test('/' + norm));
|
|
185
|
+
|
|
186
|
+
// Signal 1: Directly modified file (1.00, mandatory)
|
|
187
|
+
if (isDirectlyModified) {
|
|
188
|
+
score = Math.max(score, 1.0);
|
|
189
|
+
reasons.push('directly modified');
|
|
190
|
+
isMandatory = true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Signal 2: Security boundary (0.95, mandatory for root policy; 0.90 for security domain)
|
|
194
|
+
if (isRootSecurityBoundary) {
|
|
195
|
+
score = Math.max(score, 0.95);
|
|
196
|
+
reasons.push('security boundary');
|
|
197
|
+
isMandatory = true;
|
|
198
|
+
} else if (isSecurityDomain && !isDirectlyModified) {
|
|
199
|
+
score = Math.max(score, 0.9);
|
|
200
|
+
reasons.push('security domain module');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Signal 3 & 4: Import relationships with target files (0.85 importer, 0.80 dependency)
|
|
204
|
+
for (const tf of targetFiles) {
|
|
205
|
+
if (tf === norm) continue;
|
|
206
|
+
const { isDirectDependency, isDirectImporter } = checkImportRelation(norm, tf, fileContents);
|
|
207
|
+
if (isDirectImporter) {
|
|
208
|
+
score = Math.max(score, 0.85);
|
|
209
|
+
reasons.push(`direct importer of ${path.basename(tf)}`);
|
|
210
|
+
}
|
|
211
|
+
if (isDirectDependency) {
|
|
212
|
+
score = Math.max(score, 0.8);
|
|
213
|
+
reasons.push(`direct dependency of ${path.basename(tf)}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Signal 5: Test covering changed behavior (0.75 covering test; 0.20 unrelated test)
|
|
218
|
+
if (isTest) {
|
|
219
|
+
const targetBaseNames = targetFiles.map(tf => path.basename(tf).replace(/\.[^.]+$/, ''));
|
|
220
|
+
const coversTarget = targetBaseNames.some(tb => norm.includes(tb));
|
|
221
|
+
if (coversTarget) {
|
|
222
|
+
score = Math.max(score, 0.75);
|
|
223
|
+
reasons.push('test covering changed behavior');
|
|
224
|
+
} else {
|
|
225
|
+
score = Math.max(score, 0.2);
|
|
226
|
+
reasons.push('unrelated test suite');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Signal 6: Configuration controlling behavior (0.70)
|
|
231
|
+
if (isConfig) {
|
|
232
|
+
score = Math.max(score, 0.7);
|
|
233
|
+
reasons.push('configuration controlling behavior');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Signal 7: Public API entry point (0.65)
|
|
237
|
+
if (isEntrypoint && !isDirectlyModified) {
|
|
238
|
+
score = Math.max(score, 0.65);
|
|
239
|
+
reasons.push('public API surface');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Signal 8: Architectural parent / sibling directory (0.50)
|
|
243
|
+
if (!isDirectlyModified && targetFiles.some(tf => path.dirname(tf) === path.dirname(norm))) {
|
|
244
|
+
score = Math.max(score, 0.5);
|
|
245
|
+
reasons.push('architectural sibling/parent');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Signal 9: Documentation (0.25)
|
|
249
|
+
if (isDoc && !isRootSecurityBoundary && !isSecurityDomain) {
|
|
250
|
+
score = Math.max(score, 0.25);
|
|
251
|
+
reasons.push('documentation');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Fallback: If no reasons matched, mark as unrelated (0.05 baseline)
|
|
255
|
+
if (reasons.length === 0) {
|
|
256
|
+
reasons.push('unrelated repository content');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const finalScore = parseFloat(Math.min(1.0, Math.max(0.05, score)).toFixed(2));
|
|
260
|
+
|
|
261
|
+
scoredItems.push({
|
|
262
|
+
path: norm,
|
|
263
|
+
score: finalScore,
|
|
264
|
+
reasons,
|
|
265
|
+
is_mandatory: isMandatory,
|
|
266
|
+
size_bytes: sizeBytes,
|
|
267
|
+
estimated_tokens: estimatedTokens,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Deterministic stable sort: score DESCENDING, path ASCENDING
|
|
272
|
+
scoredItems.sort((a, b) => {
|
|
273
|
+
if (b.score !== a.score) {
|
|
274
|
+
return b.score - a.score;
|
|
275
|
+
}
|
|
276
|
+
return a.path.localeCompare(b.path);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Apply budget constraints (maxTokens, maxItems)
|
|
280
|
+
const maxTokens =
|
|
281
|
+
typeof options.maxTokens === 'number' && options.maxTokens > 0 ? options.maxTokens : Infinity;
|
|
282
|
+
const maxItems =
|
|
283
|
+
typeof options.maxItems === 'number' && options.maxItems > 0 ? options.maxItems : Infinity;
|
|
284
|
+
|
|
285
|
+
const rankedItems = [];
|
|
286
|
+
const truncatedItems = [];
|
|
287
|
+
let currentTokens = 0;
|
|
288
|
+
|
|
289
|
+
// Step 1: Retain all mandatory items first
|
|
290
|
+
const mandatoryItems = scoredItems.filter(item => item.is_mandatory);
|
|
291
|
+
const optionalItems = scoredItems.filter(item => !item.is_mandatory);
|
|
292
|
+
|
|
293
|
+
for (const item of mandatoryItems) {
|
|
294
|
+
rankedItems.push(item);
|
|
295
|
+
currentTokens += item.estimated_tokens;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Step 2: Include optional items up to the budget limits
|
|
299
|
+
for (const item of optionalItems) {
|
|
300
|
+
const wouldExceedTokens = currentTokens + item.estimated_tokens > maxTokens;
|
|
301
|
+
const wouldExceedItems = rankedItems.length + 1 > maxItems;
|
|
302
|
+
|
|
303
|
+
if (!wouldExceedTokens && !wouldExceedItems) {
|
|
304
|
+
rankedItems.push(item);
|
|
305
|
+
currentTokens += item.estimated_tokens;
|
|
306
|
+
} else {
|
|
307
|
+
truncatedItems.push(item);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Re-sort rankedItems so display follows deterministic score order
|
|
312
|
+
rankedItems.sort((a, b) => {
|
|
313
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
314
|
+
return a.path.localeCompare(b.path);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const totalTokensAfter = rankedItems.reduce((acc, it) => acc + it.estimated_tokens, 0);
|
|
318
|
+
const criticalRetained = rankedItems.filter(it => it.is_mandatory).length;
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
ranked_items: rankedItems,
|
|
322
|
+
truncated_items: truncatedItems,
|
|
323
|
+
metrics: {
|
|
324
|
+
context_items_before: allCandidatePaths.length,
|
|
325
|
+
context_items_after: rankedItems.length,
|
|
326
|
+
estimated_tokens_before: totalTokensBefore,
|
|
327
|
+
estimated_tokens_after: totalTokensAfter,
|
|
328
|
+
critical_evidence_retained: criticalRetained,
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
module.exports = {
|
|
334
|
+
rankContext,
|
|
335
|
+
normalizePath,
|
|
336
|
+
estimateTokens,
|
|
337
|
+
};
|