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.
Files changed (63) hide show
  1. package/.agent/ARCHITECTURE.md +2 -2
  2. package/.agent/history/architecture-intelligence.html +1487 -401
  3. package/.agent/history/architecture-model.json +9365 -5697
  4. package/.agent/history/architecture-verification.json +37 -9
  5. package/.agent/routing_index.json +2069 -3340
  6. package/.agent/rules/GEMINI.md +1 -1
  7. package/.agent/scripts/adaptive_execution.js +511 -0
  8. package/.agent/scripts/architecture_diff.js +303 -260
  9. package/.agent/scripts/architecture_extractor.js +792 -753
  10. package/.agent/scripts/architecture_health.js +293 -256
  11. package/.agent/scripts/architecture_intelligence.js +3100 -0
  12. package/.agent/scripts/architecture_verifier.js +303 -270
  13. package/.agent/scripts/architecture_visualizer.js +880 -873
  14. package/.agent/scripts/blast_radius_engine.js +274 -234
  15. package/.agent/scripts/capability_evolution.js +453 -0
  16. package/.agent/scripts/concept_extractor.js +240 -0
  17. package/.agent/scripts/consideration_engine.js +284 -0
  18. package/.agent/scripts/coverage_engine.js +102 -0
  19. package/.agent/scripts/cross_domain_engine.js +1883 -0
  20. package/.agent/scripts/engineering_memory.js +1313 -0
  21. package/.agent/scripts/integrity_manifest.js +1 -5
  22. package/.agent/scripts/judgment_engine.js +790 -0
  23. package/.agent/scripts/materiality_engine.js +251 -0
  24. package/.agent/scripts/outcome_engine.js +624 -0
  25. package/.agent/scripts/outcome_optimization_engine.js +1632 -0
  26. package/.agent/scripts/simulation_engine.js +1478 -0
  27. package/.agent/scripts/skill_intelligence.js +1371 -0
  28. package/.agent/scripts/validation_engine.js +236 -236
  29. package/.agent/scripts/verification_auditor.js +266 -0
  30. package/.agent/skill-intelligence/taxonomy/engineering-concepts.json +555 -0
  31. package/.agent/skill_topic_map.json +301 -341
  32. package/.agent/skills_inventory.json +4272 -6922
  33. package/.agent/workflows/arch.md +96 -80
  34. package/.agent/workflows/skill-intelligence.md +216 -0
  35. package/CONTRIBUTING.md +2 -2
  36. package/README.md +24 -24
  37. package/bin/adapter-install.js +1 -1
  38. package/package.json +8 -9
  39. package/plugins/core-plugin.json +1 -1
  40. package/plugins/gemini/extension.json +1 -1
  41. package/src/browser/index.js +2 -1
  42. package/src/browser/launcher.js +41 -1
  43. package/src/cli.js +3 -1
  44. package/src/commands/arch.js +261 -172
  45. package/src/commands/context.js +27 -8
  46. package/src/commands/learn.js +21 -6
  47. package/src/commands/memory.js +10 -1
  48. package/src/commands/native.js +12 -3
  49. package/src/commands/skill-intelligence.js +1165 -0
  50. package/src/commands/system1.js +105 -4
  51. package/src/context/ranker.js +337 -339
  52. package/src/evolution/engine.js +296 -295
  53. package/src/synthesis/claim_check_separator.js +294 -271
  54. package/src/system1/browser_intelligence.js +300 -0
  55. package/src/system1/constants.js +49 -0
  56. package/src/system1/decision_engine.js +498 -0
  57. package/src/system1/evidence_engine.js +602 -0
  58. package/src/system1/evolution_pipeline.js +581 -0
  59. package/src/system1/index.d.ts +114 -0
  60. package/src/system1/index.js +102 -0
  61. package/src/system1/reviewer_orchestrator.js +477 -0
  62. package/src/tui/banner.js +1 -1
  63. package/src/utils/fs.js +27 -0
@@ -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
- /\.(?:md|markdown|txt|rst|adoc)$/i,
38
- /[\\/]docs[\\/]/i,
39
- ];
40
-
41
- const TEST_PATTERNS = [
42
- /\.(?:test|spec)\.[a-zA-Z0-9]+$/i,
43
- /[\\/](?:__tests__|tests?|fixtures?)[\\/]/i,
44
- ];
45
-
46
- const ENTRY_PATTERNS = [
47
- /[\\/](?:index|main|cli|app|server)\.[a-zA-Z0-9]+$/i,
48
- /[\\/]api[\\/]/i,
49
- ];
50
-
51
- function normalizePath(filePath) {
52
- return filePath.replace(/\\/g, '/').replace(/^\.\//, '');
53
- }
54
-
55
- function estimateTokens(textOrBytes) {
56
- if (typeof textOrBytes === 'number') {
57
- return Math.max(1, Math.ceil(textOrBytes / 4));
58
- }
59
- if (typeof textOrBytes === 'string') {
60
- return Math.max(1, Math.ceil(textOrBytes.length / 4));
61
- }
62
- return 1;
63
- }
64
-
65
- /**
66
- * Extract direct import / require targets from code text
67
- */
68
- function extractImports(code) {
69
- const imports = new Set();
70
- const importRegex = /(?:import\s+(?:[\w*\s{},]*\s+from\s+)?['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\))/g;
71
- let match;
72
- while ((match = importRegex.exec(code)) !== null) {
73
- const target = match[1] || match[2];
74
- if (target && (target.startsWith('.') || target.startsWith('/'))) {
75
- imports.add(target);
76
- }
77
- }
78
- return imports;
79
- }
80
-
81
- /**
82
- * Check if candidateFile is imported by targetFile or vice versa
83
- */
84
- function checkImportRelation(candidateRel, targetRel, fileContents) {
85
- const targetContent = fileContents.get(targetRel) || '';
86
- const candidateContent = fileContents.get(candidateRel) || '';
87
-
88
- const targetBasename = path.basename(targetRel, path.extname(targetRel));
89
- const candidateBasename = path.basename(candidateRel, path.extname(candidateRel));
90
-
91
- let isDirectDependency = false;
92
- let isDirectImporter = false;
93
-
94
- // Check target -> candidate
95
- if (targetContent) {
96
- const targetImports = extractImports(targetContent);
97
- for (const imp of targetImports) {
98
- if (imp.includes(candidateBasename) || imp.endsWith(candidateBasename)) {
99
- isDirectDependency = true;
100
- break;
101
- }
102
- }
103
- }
104
-
105
- // Check candidate -> target
106
- if (candidateContent) {
107
- const candidateImports = extractImports(candidateContent);
108
- for (const imp of candidateImports) {
109
- if (imp.includes(targetBasename) || imp.endsWith(targetBasename)) {
110
- isDirectImporter = true;
111
- break;
112
- }
113
- }
114
- }
115
-
116
- return { isDirectDependency, isDirectImporter };
117
- }
118
-
119
- /**
120
- * Deterministic Context Ranking Engine
121
- * @param {Object} options
122
- * @param {string} [options.repoRoot] - Repository root directory
123
- * @param {string[]} [options.targetFiles] - Array of directly modified files
124
- * @param {string[]} [options.candidateFiles] - Array of candidate files to rank
125
- * @param {number} [options.maxTokens] - Maximum token budget for ranking
126
- * @param {number} [options.maxItems] - Maximum item count for ranking
127
- * @returns {Object} Ranking payload with scored items and measurement metrics
128
- */
129
- function rankContext(options = {}) {
130
- const repoRoot = options.repoRoot ? path.resolve(options.repoRoot) : process.cwd();
131
- const rawTargetFiles = options.targetFiles || [];
132
- const targetFiles = (Array.isArray(rawTargetFiles) ? rawTargetFiles : [rawTargetFiles])
133
- .map(normalizePath)
134
- .filter(Boolean);
135
-
136
- let rawCandidates = options.candidateFiles || [];
137
- if (!Array.isArray(rawCandidates)) rawCandidates = [rawCandidates];
138
-
139
- // If candidates not provided, union of targetFiles
140
- const candidateSet = new Set(rawCandidates.map(normalizePath).filter(Boolean));
141
- for (const tf of targetFiles) {
142
- candidateSet.add(tf);
143
- }
144
-
145
- const allCandidatePaths = Array.from(candidateSet);
146
-
147
- // Pre-load content for import analysis if files exist on disk
148
- const fileContents = new Map();
149
- const fileStats = new Map();
150
-
151
- for (const relPath of allCandidatePaths) {
152
- const absPath = path.resolve(repoRoot, relPath);
153
- if (fs.existsSync(absPath)) {
154
- try {
155
- const stat = fs.statSync(absPath);
156
- if (stat.isFile()) {
157
- fileStats.set(relPath, stat);
158
- // Only read if reasonably sized (< 2MB)
159
- if (stat.size < 2 * 1024 * 1024) {
160
- fileContents.set(relPath, fs.readFileSync(absPath, 'utf8'));
161
- }
162
- }
163
- } catch (_) {}
164
- }
165
- }
166
-
167
- const scoredItems = [];
168
- let totalTokensBefore = 0;
169
-
170
- for (const relPath of allCandidatePaths) {
171
- const norm = normalizePath(relPath);
172
- const reasons = [];
173
- let score = 0.05; // Base baseline
174
- let isMandatory = false;
175
-
176
- const stat = fileStats.get(norm);
177
- const content = fileContents.get(norm);
178
- const sizeBytes = stat ? stat.size : 100;
179
- const estimatedTokens = content ? estimateTokens(content) : estimateTokens(sizeBytes);
180
- totalTokensBefore += estimatedTokens;
181
-
182
- const isDirectlyModified = targetFiles.includes(norm);
183
- const isRootSecurityBoundary = /[\\/]SECURITY\.md$/i.test('/' + norm) || /[\\/]\.env/i.test('/' + norm);
184
- const isSecurityDomain = SECURITY_PATTERNS.some(pat => pat.test('/' + norm));
185
- const isConfig = CONFIG_PATTERNS.some(pat => pat.test('/' + norm));
186
- const isDoc = DOC_PATTERNS.some(pat => pat.test('/' + norm));
187
- const isTest = TEST_PATTERNS.some(pat => pat.test('/' + norm));
188
- const isEntrypoint = ENTRY_PATTERNS.some(pat => pat.test('/' + norm));
189
-
190
- // Signal 1: Directly modified file (1.00, mandatory)
191
- if (isDirectlyModified) {
192
- score = Math.max(score, 1.00);
193
- reasons.push('directly modified');
194
- isMandatory = true;
195
- }
196
-
197
- // Signal 2: Security boundary (0.95, mandatory for root policy; 0.90 for security domain)
198
- if (isRootSecurityBoundary) {
199
- score = Math.max(score, 0.95);
200
- reasons.push('security boundary');
201
- isMandatory = true;
202
- } else if (isSecurityDomain && !isDirectlyModified) {
203
- score = Math.max(score, 0.90);
204
- reasons.push('security domain module');
205
- }
206
-
207
- // Signal 3 & 4: Import relationships with target files (0.85 importer, 0.80 dependency)
208
- for (const tf of targetFiles) {
209
- if (tf === norm) continue;
210
- const { isDirectDependency, isDirectImporter } = checkImportRelation(norm, tf, fileContents);
211
- if (isDirectImporter) {
212
- score = Math.max(score, 0.85);
213
- reasons.push(`direct importer of ${path.basename(tf)}`);
214
- }
215
- if (isDirectDependency) {
216
- score = Math.max(score, 0.80);
217
- reasons.push(`direct dependency of ${path.basename(tf)}`);
218
- }
219
- }
220
-
221
- // Signal 5: Test covering changed behavior (0.75 covering test; 0.20 unrelated test)
222
- if (isTest) {
223
- const targetBaseNames = targetFiles.map(tf => path.basename(tf).replace(/\.[^.]+$/, ''));
224
- const coversTarget = targetBaseNames.some(tb => norm.includes(tb));
225
- if (coversTarget) {
226
- score = Math.max(score, 0.75);
227
- reasons.push('test covering changed behavior');
228
- } else {
229
- score = Math.max(score, 0.20);
230
- reasons.push('unrelated test suite');
231
- }
232
- }
233
-
234
- // Signal 6: Configuration controlling behavior (0.70)
235
- if (isConfig) {
236
- score = Math.max(score, 0.70);
237
- reasons.push('configuration controlling behavior');
238
- }
239
-
240
- // Signal 7: Public API entry point (0.65)
241
- if (isEntrypoint && !isDirectlyModified) {
242
- score = Math.max(score, 0.65);
243
- reasons.push('public API surface');
244
- }
245
-
246
- // Signal 8: Architectural parent / sibling directory (0.50)
247
- if (!isDirectlyModified && targetFiles.some(tf => path.dirname(tf) === path.dirname(norm))) {
248
- score = Math.max(score, 0.50);
249
- reasons.push('architectural sibling/parent');
250
- }
251
-
252
- // Signal 9: Documentation (0.25)
253
- if (isDoc && !isRootSecurityBoundary && !isSecurityDomain) {
254
- score = Math.max(score, 0.25);
255
- reasons.push('documentation');
256
- }
257
-
258
- // Fallback: If no reasons matched, mark as unrelated (0.05 baseline)
259
- if (reasons.length === 0) {
260
- reasons.push('unrelated repository content');
261
- }
262
-
263
- const finalScore = parseFloat(Math.min(1.0, Math.max(0.05, score)).toFixed(2));
264
-
265
- scoredItems.push({
266
- path: norm,
267
- score: finalScore,
268
- reasons,
269
- is_mandatory: isMandatory,
270
- size_bytes: sizeBytes,
271
- estimated_tokens: estimatedTokens,
272
- });
273
- }
274
-
275
- // Deterministic stable sort: score DESCENDING, path ASCENDING
276
- scoredItems.sort((a, b) => {
277
- if (b.score !== a.score) {
278
- return b.score - a.score;
279
- }
280
- return a.path.localeCompare(b.path);
281
- });
282
-
283
- // Apply budget constraints (maxTokens, maxItems)
284
- const maxTokens = typeof options.maxTokens === 'number' && options.maxTokens > 0 ? options.maxTokens : Infinity;
285
- const maxItems = typeof options.maxItems === 'number' && options.maxItems > 0 ? options.maxItems : Infinity;
286
-
287
- const rankedItems = [];
288
- const truncatedItems = [];
289
- let currentTokens = 0;
290
-
291
- // Step 1: Retain all mandatory items first
292
- const mandatoryItems = scoredItems.filter(item => item.is_mandatory);
293
- const optionalItems = scoredItems.filter(item => !item.is_mandatory);
294
-
295
- for (const item of mandatoryItems) {
296
- rankedItems.push(item);
297
- currentTokens += item.estimated_tokens;
298
- }
299
-
300
- // Step 2: Include optional items up to the budget limits
301
- for (const item of optionalItems) {
302
- const wouldExceedTokens = (currentTokens + item.estimated_tokens) > maxTokens;
303
- const wouldExceedItems = (rankedItems.length + 1) > maxItems;
304
-
305
- if (!wouldExceedTokens && !wouldExceedItems) {
306
- rankedItems.push(item);
307
- currentTokens += item.estimated_tokens;
308
- } else {
309
- truncatedItems.push(item);
310
- }
311
- }
312
-
313
- // Re-sort rankedItems so display follows deterministic score order
314
- rankedItems.sort((a, b) => {
315
- if (b.score !== a.score) return b.score - a.score;
316
- return a.path.localeCompare(b.path);
317
- });
318
-
319
- const totalTokensAfter = rankedItems.reduce((acc, it) => acc + it.estimated_tokens, 0);
320
- const criticalRetained = rankedItems.filter(it => it.is_mandatory).length;
321
-
322
- return {
323
- ranked_items: rankedItems,
324
- truncated_items: truncatedItems,
325
- metrics: {
326
- context_items_before: allCandidatePaths.length,
327
- context_items_after: rankedItems.length,
328
- estimated_tokens_before: totalTokensBefore,
329
- estimated_tokens_after: totalTokensAfter,
330
- critical_evidence_retained: criticalRetained,
331
- },
332
- };
333
- }
334
-
335
- module.exports = {
336
- rankContext,
337
- normalizePath,
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
+ };