composto-ai 0.1.2 → 0.2.1

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/index.js CHANGED
@@ -1,1079 +1,25 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ benchmarkFile,
4
+ computeHealthFromTrends,
5
+ detectDecay,
6
+ detectHotspots,
7
+ detectInconsistencies,
8
+ estimateTokens,
9
+ generateLayer,
10
+ getGitLog,
11
+ loadConfig,
12
+ packContext,
13
+ runDetector,
14
+ summarize
15
+ } from "./chunk-T2SFR7QS.js";
2
16
 
3
17
  // src/cli/commands.ts
4
- import { readFileSync as readFileSync2, readdirSync } from "fs";
5
- import { join as join2, relative } from "path";
6
-
7
- // src/config/loader.ts
8
- import { readFileSync, existsSync } from "fs";
9
- import { join } from "path";
10
- import { parse } from "yaml";
11
- var DEFAULT_CONFIG = {
12
- watchers: {
13
- security: {
14
- enabled: true,
15
- severity: { "src/**": "warning", "tests/**": "info" }
16
- },
17
- deadCode: { enabled: true, trigger: "on-commit" },
18
- consoleLog: { enabled: true, severity: { "src/**": "warning", "tests/**": "info" } }
19
- },
20
- agents: {
21
- fixer: { enabled: true, model: "haiku" },
22
- reviewer: { enabled: false, model: "sonnet" }
23
- },
24
- ir: {
25
- deltaContextLines: 3,
26
- confidenceThreshold: 0.6,
27
- genericPatterns: "default"
28
- },
29
- trends: {
30
- enabled: true,
31
- hotspotThreshold: 10,
32
- bugFixRatioThreshold: 0.5,
33
- decayCheckTrigger: "on-commit",
34
- fullReportSchedule: "weekly"
35
- }
36
- };
37
- function deepMerge(target, source) {
38
- const result = { ...target };
39
- for (const key of Object.keys(source)) {
40
- if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
41
- result[key] = deepMerge(target[key] ?? {}, source[key]);
42
- } else {
43
- result[key] = source[key];
44
- }
45
- }
46
- return result;
47
- }
48
- function parseConfig(yamlContent) {
49
- if (!yamlContent.trim()) return { ...DEFAULT_CONFIG };
50
- const parsed = parse(yamlContent) ?? {};
51
- return deepMerge(DEFAULT_CONFIG, parsed);
52
- }
53
- function loadConfig(projectPath) {
54
- const configPath = join(projectPath, ".composto", "config.yaml");
55
- if (!existsSync(configPath)) return { ...DEFAULT_CONFIG };
56
- const content = readFileSync(configPath, "utf-8");
57
- return parseConfig(content);
58
- }
59
-
60
- // src/watcher/detector.ts
61
- import picomatch from "picomatch";
62
- function getSeverity(filePath, severityMap) {
63
- for (const [glob, severity] of Object.entries(severityMap)) {
64
- if (picomatch.isMatch(filePath, glob)) return severity;
65
- }
66
- return "info";
67
- }
68
- var SECRET_PATTERNS = [
69
- /["'](sk-[a-zA-Z0-9-]{20,})["']/,
70
- /["'](AKIA[0-9A-Z]{16})["']/,
71
- /["'](ghp_[a-zA-Z0-9]{36})["']/,
72
- /(?:password|secret|token|api_?key)\s*[:=]\s*["']([^"']{8,})["']/i
73
- ];
74
- function securityRule(code, filePath, severityMap) {
75
- const findings = [];
76
- const severity = getSeverity(filePath, severityMap);
77
- const lines = code.split("\n");
78
- for (let i = 0; i < lines.length; i++) {
79
- const line = lines[i];
80
- if (line.trim().startsWith("//") || line.trim().startsWith("/*")) continue;
81
- for (const pattern of SECRET_PATTERNS) {
82
- if (pattern.test(line)) {
83
- findings.push({
84
- watcherId: "security",
85
- severity,
86
- file: filePath,
87
- line: i + 1,
88
- message: "Potential hardcoded secret detected",
89
- action: {
90
- type: "agent-required",
91
- agentHint: { role: "fixer", model: "haiku", contextFiles: [filePath] }
92
- }
93
- });
94
- break;
95
- }
96
- }
97
- }
98
- return findings;
99
- }
100
- function consoleLogRule(code, filePath, severityMap) {
101
- const findings = [];
102
- const severity = getSeverity(filePath, severityMap);
103
- const lines = code.split("\n");
104
- for (let i = 0; i < lines.length; i++) {
105
- if (/\bconsole\.(log|debug|info|warn)\b/.test(lines[i])) {
106
- findings.push({
107
- watcherId: "consoleLog",
108
- severity,
109
- file: filePath,
110
- line: i + 1,
111
- message: "console.log detected \u2014 likely debug artifact",
112
- action: { type: "auto-fix", autoFix: "remove-line" }
113
- });
114
- }
115
- }
116
- return findings;
117
- }
118
- var RULES = {
119
- security: securityRule,
120
- consoleLog: consoleLogRule
121
- };
122
- function runDetector(code, filePath, watcherConfigs) {
123
- const findings = [];
124
- for (const [name, config] of Object.entries(watcherConfigs)) {
125
- if (!config.enabled) continue;
126
- const rule = RULES[name];
127
- if (rule && config.severity) {
128
- findings.push(...rule(code, filePath, config.severity));
129
- }
130
- }
131
- return findings;
132
- }
133
-
134
- // src/ir/structure.ts
135
- var CLASSIFIERS = [
136
- [/^(function|def|fn|func)\b/, "function-start"],
137
- [/^(class|struct|interface)\b/, "type-start"],
138
- [/^(if|else|elif|switch|match|case)\b/, "branch"],
139
- [/^(for|while|loop|do)\b/, "loop"],
140
- [/^(return|yield)\b/, "exit"],
141
- [/^(import|require|use|from)\b/, "import"],
142
- [/^(export|pub|public)\b/, "export"],
143
- [/^(const|let|var|val|mut)\b/, "assignment"],
144
- [/^(try|catch|except|finally)\b/, "error-handling"],
145
- [/^(async|await)\b/, "async"],
146
- [/^(\/\/|\/\*|#)/, "comment"]
147
- ];
148
- function classifyLine(firstToken) {
149
- if (firstToken === "") return "blank";
150
- for (const [pattern, type] of CLASSIFIERS) {
151
- if (pattern.test(firstToken)) return type;
152
- }
153
- return "unknown";
154
- }
155
- function extractStructure(code) {
156
- const lines = code.split("\n");
157
- return lines.map((raw, i) => {
158
- const indent = raw.search(/\S/);
159
- const trimmed = raw.trim();
160
- const firstToken = trimmed.split(/[\s({<]/)[0];
161
- const type = classifyLine(firstToken);
162
- return {
163
- line: i + 1,
164
- indent: indent === -1 ? -1 : indent,
165
- type,
166
- raw
167
- };
168
- });
169
- }
170
-
171
- // src/ir/fingerprint.ts
172
- var PATTERNS = [
173
- // import type { x, y } from "module"
174
- {
175
- match: /^import\s+type\s+\{([^}]+)\}\s+from\s+["']([^"']+)["'];?\s*$/,
176
- transform: (m) => `USE:${m[2]}{${m[1].replace(/\s/g, "")}}`,
177
- confidence: 0.95
178
- },
179
- // import { x, y } from "module"
180
- {
181
- match: /^import\s+\{([^}]+)\}\s+from\s+["']([^"']+)["'];?\s*$/,
182
- transform: (m) => `USE:${m[2]}{${m[1].replace(/\s/g, "")}}`,
183
- confidence: 0.95
184
- },
185
- // import type x from "module"
186
- {
187
- match: /^import\s+type\s+(\w+)\s+from\s+["']([^"']+)["'];?\s*$/,
188
- transform: (m) => `USE:${m[2]}{${m[1]}}`,
189
- confidence: 0.95
190
- },
191
- // import x from "module"
192
- {
193
- match: /^import\s+(\w+)\s+from\s+["']([^"']+)["'];?\s*$/,
194
- transform: (m) => `USE:${m[2]}{${m[1]}}`,
195
- confidence: 0.95
196
- },
197
- // const x = require("module")
198
- {
199
- match: /^(?:const|let|var)\s+(\w+)\s*=\s*require\(["']([^"']+)["']\);?\s*$/,
200
- transform: (m) => `USE:${m[2]}{${m[1]}}`,
201
- confidence: 0.95
202
- },
203
- // export function name(params) {
204
- {
205
- match: /^export\s+(?:default\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)\s*(?::\s*\S+\s*)?\{?\s*$/,
206
- transform: (m) => `OUT FN:${m[1]}(${m[2].replace(/\s/g, "")})`,
207
- confidence: 0.95
208
- },
209
- // function name(params) {
210
- {
211
- match: /^(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)\s*(?::\s*\S+\s*)?\{?\s*$/,
212
- transform: (m) => `FN:${m[1]}(${m[2].replace(/\s/g, "")})`,
213
- confidence: 0.95
214
- },
215
- // export class Name extends Base {
216
- {
217
- match: /^export\s+class\s+(\w+)(?:\s+extends\s+(\w+))?\s*(?:implements\s+\S+\s*)?\{?\s*$/,
218
- transform: (m) => `OUT CLASS:${m[1]}${m[2] ? ` < ${m[2]}` : ""}`,
219
- confidence: 0.95
220
- },
221
- // class Name extends Base {
222
- {
223
- match: /^class\s+(\w+)(?:\s+extends\s+(\w+))?\s*(?:implements\s+\S+\s*)?\{?\s*$/,
224
- transform: (m) => `CLASS:${m[1]}${m[2] ? ` < ${m[2]}` : ""}`,
225
- confidence: 0.95
226
- },
227
- // if (cond) return expr;
228
- {
229
- match: /^if\s*\(([^)]+)\)\s*return\s+(.+);?\s*$/,
230
- transform: (m) => `IF:${m[1].trim()} -> RET ${m[2].trim().replace(/;$/, "")}`,
231
- confidence: 0.95
232
- },
233
- // if (cond) {
234
- {
235
- match: /^if\s*\(([^)]+)\)\s*\{?\s*$/,
236
- transform: (m) => `IF:${m[1].trim()}`,
237
- confidence: 0.9
238
- },
239
- // for (... of/in ...) {
240
- {
241
- match: /^for\s*\((?:const|let|var)\s+(\w+)\s+(?:of|in)\s+(\w+)\)\s*\{?\s*$/,
242
- transform: (m) => `LOOP:${m[2]} -> ${m[1]}`,
243
- confidence: 0.9
244
- },
245
- // return expr
246
- {
247
- match: /^return\s+(.+);?\s*$/,
248
- transform: (m) => `RET ${m[1].trim().replace(/;$/, "")}`,
249
- confidence: 0.95
250
- },
251
- // return;
252
- {
253
- match: /^return;?\s*$/,
254
- transform: () => "RET",
255
- confidence: 0.95
256
- },
257
- // try {
258
- {
259
- match: /^try\s*\{\s*$/,
260
- transform: () => "TRY:",
261
- confidence: 0.9
262
- },
263
- // catch (e) {
264
- {
265
- match: /^(?:\}\s*)?catch\s*\((\w+)\)\s*\{?\s*$/,
266
- transform: (m) => `CATCH:${m[1]}`,
267
- confidence: 0.9
268
- },
269
- // switch (expr) {
270
- {
271
- match: /^switch\s*\(([^)]+)\)\s*\{?\s*$/,
272
- transform: (m) => `SWITCH:${m[1].trim()}`,
273
- confidence: 0.9
274
- },
275
- // case "value": / case value:
276
- {
277
- match: /^case\s+(.+)\s*:\s*$/,
278
- transform: (m) => `CASE:${m[1].trim()}`,
279
- confidence: 0.9
280
- },
281
- // default:
282
- {
283
- match: /^default\s*:\s*$/,
284
- transform: () => "DEFAULT:",
285
- confidence: 0.9
286
- },
287
- // export type Name = ...
288
- {
289
- match: /^export\s+type\s+(\w+)(?:<[^>]+>)?\s*=\s*(.+);?\s*$/,
290
- transform: (m) => `OUT TYPE:${m[1]}`,
291
- confidence: 0.9
292
- },
293
- // if (cond) expr; (inline if with method call)
294
- {
295
- match: /^if\s*\(([^)]+)\)\s+(\w+.+);?\s*$/,
296
- transform: (m) => `IF:${m[1].trim()} -> ${m[2].replace(/;$/, "").trim().slice(0, 50)}`,
297
- confidence: 0.9
298
- },
299
- // export async function name( (multiline signature)
300
- {
301
- match: /^export\s+(?:default\s+)?(?:async\s+)?function\s+(\w+)\s*\(\s*$/,
302
- transform: (m) => `OUT FN:${m[1]}(`,
303
- confidence: 0.95
304
- },
305
- // interface/type property — name: type;
306
- {
307
- match: /^\s*(\w+)\??\s*:\s*(.+);?\s*$/,
308
- transform: (m) => `PROP:${m[1]}: ${m[2].replace(/;$/, "").trim()}`,
309
- confidence: 0.75
310
- },
311
- // const x = await expr;
312
- {
313
- match: /^(?:export\s+)?(?:const|let|var)\s+(\w+)(?:\s*:\s*[^=]+)?\s*=\s*await\s+(.+);?\s*$/,
314
- transform: (m) => {
315
- const prefix = m[0].startsWith("export") ? "OUT " : "";
316
- return `${prefix}AWAIT:VAR:${m[1]} = ${m[2].replace(/;$/, "").trim()}`;
317
- },
318
- confidence: 0.85
319
- },
320
- // export const name = async (params) => { OR export const name = (params) => expr;
321
- {
322
- match: /^export\s+(?:const|let|var)\s+(\w+)\s*=\s*(async\s+)?\(([^)]*)\)\s*=>\s*(.*)$/,
323
- transform: (m) => {
324
- const asyncPrefix = m[2] ? "ASYNC " : "";
325
- const body = m[4].replace(/[{;]\s*$/, "").trim();
326
- return `OUT ${asyncPrefix}FN:${m[1]} = (${m[3].trim()}) => ${body || "{"}`;
327
- },
328
- confidence: 0.9
329
- },
330
- // const name = async (params) => { OR const name = (params) => expr;
331
- {
332
- match: /^(?:const|let|var)\s+(\w+)\s*=\s*(async\s+)?\(([^)]*)\)\s*=>\s*(.*)$/,
333
- transform: (m) => {
334
- const asyncPrefix = m[2] ? "ASYNC " : "";
335
- const body = m[4].replace(/[{;]\s*$/, "").trim();
336
- return `${asyncPrefix}FN:${m[1]} = (${m[3].trim()}) => ${body || "{"}`;
337
- },
338
- confidence: 0.9
339
- },
340
- // get name() {
341
- {
342
- match: /^\s*get\s+(\w+)\s*\(\)\s*(?::\s*\S+\s*)?\{?\s*$/,
343
- transform: (m) => `GET:${m[1]}()`,
344
- confidence: 0.9
345
- },
346
- // set name(value) {
347
- {
348
- match: /^\s*set\s+(\w+)\s*\(([^)]*)\)\s*\{?\s*$/,
349
- transform: (m) => `SET:${m[1]}(${m[2].replace(/\s/g, "")})`,
350
- confidence: 0.9
351
- },
352
- // methodName(params) { (inside class body, indented)
353
- {
354
- match: /^\s*(?:async\s+)?(\w+)\s*\(([^)]*)\)\s*(?::\s*\S+\s*)?\{\s*$/,
355
- transform: (m) => {
356
- const name = m[1];
357
- if (["if", "for", "while", "switch", "catch", "function"].includes(name)) return `${name}`;
358
- return `METHOD:${name}(${m[2].replace(/\s/g, "")})`;
359
- },
360
- confidence: 0.9
361
- },
362
- // const { a, b } = expr (object destructuring — before regular assignment)
363
- {
364
- match: /^(?:const|let|var)\s+\{([^}]+)\}\s*=\s*(.+);?\s*$/,
365
- transform: (m) => `VAR:{${m[1].replace(/\s/g, "")}} = ${m[2].replace(/;$/, "").trim()}`,
366
- confidence: 0.9
367
- },
368
- // const [a, b] = expr (destructuring — before regular assignment)
369
- {
370
- match: /^(?:const|let|var)\s+\[([^\]]+)\]\s*=\s*(.+);?\s*$/,
371
- transform: (m) => `VAR:[${m[1].replace(/\s/g, "")}] = ${m[2].replace(/;$/, "").trim()}`,
372
- confidence: 0.9
373
- },
374
- // const name = value;
375
- {
376
- match: /^(?:export\s+)?(?:const|let|var)\s+(\w+)(?:\s*:\s*[^=]+)?\s*=\s*(.+);?\s*$/,
377
- transform: (m) => {
378
- const prefix = m[0].startsWith("export") ? "OUT " : "";
379
- return `${prefix}VAR:${m[1]} = ${m[2].replace(/;$/, "").trim()}`;
380
- },
381
- confidence: 0.85
382
- }
383
- ];
384
- function fingerprintLine(line) {
385
- const trimmed = line.trim();
386
- if (trimmed === "" || trimmed === "{" || trimmed === "}" || trimmed === "});" || trimmed === ");") {
387
- return { type: "fingerprint", ir: "", confidence: 1 };
388
- }
389
- if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
390
- return { type: "fingerprint", ir: "", confidence: 1 };
391
- }
392
- for (const pattern of PATTERNS) {
393
- const match = trimmed.match(pattern.match);
394
- if (match) {
395
- const ir = pattern.transform(match);
396
- if (pattern.confidence > 0.9) {
397
- return { type: "fingerprint", ir, confidence: pattern.confidence };
398
- }
399
- return {
400
- type: "fingerprint+hint",
401
- ir,
402
- hint: trimmed,
403
- confidence: pattern.confidence
404
- };
405
- }
406
- }
407
- return { type: "raw", ir: trimmed, confidence: 0.1 };
408
- }
409
- function fingerprintFile(code, confidenceThreshold = 0.6) {
410
- const lines = code.split("\n");
411
- const irLines = [];
412
- for (const line of lines) {
413
- const indent = line.search(/\S/);
414
- const indentStr = indent > 0 ? " ".repeat(Math.floor(indent / 2)) : "";
415
- const result = fingerprintLine(line);
416
- if (result.ir === "") continue;
417
- if (result.confidence >= confidenceThreshold) {
418
- irLines.push(`${indentStr}${result.ir}`);
419
- }
420
- }
421
- return irLines.join("\n");
422
- }
423
-
424
- // src/ir/health.ts
425
- var CHURN_THRESHOLD = 10;
426
- var FIX_RATIO_THRESHOLD = 0.5;
427
- function buildHealthTag(health) {
428
- const parts = [];
429
- if (health.churn > CHURN_THRESHOLD) parts.push(`HOT:${health.churn}/30`);
430
- if (health.fixRatio > FIX_RATIO_THRESHOLD) parts.push(`FIX:${Math.round(health.fixRatio * 100)}%`);
431
- if (health.coverageTrend === "down") parts.push("COV:\u2193");
432
- if (health.consistency === "low") parts.push("INCON");
433
- return parts.length > 0 ? `[${parts.join(" ")}]` : "";
434
- }
435
- function annotateIR(ir, health) {
436
- const tag = buildHealthTag(health);
437
- if (!tag) return ir;
438
- const lines = ir.split("\n");
439
- lines[0] = `${lines[0]} ${tag}`;
440
- return lines.join("\n");
441
- }
442
- function computeHealthFromTrends(file, trends) {
443
- const hotspot = trends.hotspots.find((h) => h.file === file);
444
- const decay = trends.decaySignals.find((d) => d.file === file);
445
- const inconsistency = trends.inconsistencies.find((i) => i.file === file);
446
- return {
447
- churn: hotspot?.changesInLast30Commits ?? 0,
448
- fixRatio: hotspot?.bugFixRatio ?? 0,
449
- coverageTrend: decay?.trend === "declining" ? "down" : decay?.trend === "improving" ? "up" : "stable",
450
- staleness: "",
451
- authorCount: hotspot?.authorCount ?? 0,
452
- consistency: inconsistency ? "low" : "high"
453
- };
454
- }
455
-
456
- // src/parser/init.ts
457
- import { Parser, Language } from "web-tree-sitter";
458
- import { resolve, dirname } from "path";
459
- import { existsSync as existsSync2 } from "fs";
460
- import { fileURLToPath } from "url";
461
- var __dirname = dirname(fileURLToPath(import.meta.url));
462
- var initialized = false;
463
- var cache = /* @__PURE__ */ new Map();
464
- function grammarPath(lang) {
465
- const distPath = resolve(__dirname, "grammars", `tree-sitter-${lang}.wasm`);
466
- if (existsSync2(distPath)) return distPath;
467
- const devPath = resolve(__dirname, "../../grammars", `tree-sitter-${lang}.wasm`);
468
- if (existsSync2(devPath)) return devPath;
469
- throw new Error(`Grammar not found for ${lang}`);
470
- }
471
- async function getParser(lang) {
472
- if (!initialized) {
473
- await Parser.init();
474
- initialized = true;
475
- }
476
- const cached = cache.get(lang);
477
- if (cached) return cached;
478
- const parser = new Parser();
479
- const language = await Language.load(grammarPath(lang));
480
- parser.setLanguage(language);
481
- const result = { parser, language };
482
- cache.set(lang, result);
483
- return result;
484
- }
485
-
486
- // src/parser/languages.ts
487
- import { extname } from "path";
488
- var EXT_MAP = {
489
- ".ts": "typescript",
490
- ".tsx": "typescript",
491
- ".js": "javascript",
492
- ".jsx": "javascript",
493
- ".mjs": "javascript",
494
- ".py": "python",
495
- ".go": "go",
496
- ".rs": "rust"
497
- };
498
- var SUPPORTED_EXTENSIONS = Object.keys(EXT_MAP);
499
- function detectLanguage(filePath) {
500
- const ext = extname(filePath);
501
- return EXT_MAP[ext] ?? null;
502
- }
503
-
504
- // src/ir/ast-walker.ts
505
- var TIER_MAP = {
506
- // Tier 1 — structural declarations
507
- import_statement: "T1_KEEP",
508
- function_declaration: "T1_KEEP",
509
- class_declaration: "T1_KEEP",
510
- interface_declaration: "T1_KEEP",
511
- type_alias_declaration: "T1_KEEP",
512
- enum_declaration: "T1_KEEP",
513
- // Tier 2 — control flow
514
- if_statement: "T2_CONTROL",
515
- else_clause: "WALK_ONLY",
516
- for_statement: "T2_CONTROL",
517
- for_in_statement: "T2_CONTROL",
518
- while_statement: "T2_CONTROL",
519
- do_statement: "T2_CONTROL",
520
- switch_statement: "T2_CONTROL",
521
- switch_case: "T2_CONTROL",
522
- switch_default: "T2_CONTROL",
523
- return_statement: "T2_CONTROL",
524
- throw_statement: "T2_CONTROL",
525
- try_statement: "T2_CONTROL",
526
- catch_clause: "T2_CONTROL",
527
- // Tier 3 — compressible expressions
528
- lexical_declaration: "T3_COMPRESS",
529
- expression_statement: "T3_COMPRESS",
530
- // Walk-only — containers that need traversal but no emission
531
- program: "WALK_ONLY",
532
- statement_block: "WALK_ONLY",
533
- class_body: "WALK_ONLY",
534
- switch_body: "WALK_ONLY",
535
- export_statement: "WALK_ONLY"
536
- };
537
- function tierOf(nodeType) {
538
- return TIER_MAP[nodeType] ?? "T4_DROP";
539
- }
540
- function collapseText(text, maxLen) {
541
- const collapsed = text.replace(/\s*\n\s*/g, " ").replace(/\s{2,}/g, " ").trim();
542
- if (collapsed.length <= maxLen) return collapsed;
543
- return collapsed.slice(0, maxLen - 3) + "...";
544
- }
545
- function getTypeParams(node) {
546
- for (let i = 0; i < node.childCount; i++) {
547
- const child = node.child(i);
548
- if (child.type === "type_parameters") {
549
- return child.text;
550
- }
551
- }
552
- return "";
553
- }
554
- function isExported(node) {
555
- return node.parent?.type === "export_statement";
556
- }
557
- function isAsync(node) {
558
- return node.text.trimStart().startsWith("async");
559
- }
560
- function extractCondition(node) {
561
- const condNode = node.childForFieldName("condition") ?? (() => {
562
- for (let i = 0; i < node.childCount; i++) {
563
- const c = node.child(i);
564
- if (c.type === "parenthesized_expression") return c;
565
- }
566
- return null;
567
- })();
568
- if (!condNode) return "...";
569
- const text = condNode.text.replace(/^\(/, "").replace(/\)$/, "").trim();
570
- return text.length > 60 ? text.slice(0, 57) + "..." : text;
571
- }
572
- function emitTier2(node) {
573
- switch (node.type) {
574
- case "if_statement": {
575
- const cond = extractCondition(node);
576
- return `IF:${cond}`;
577
- }
578
- case "else_clause":
579
- return "ELSE:";
580
- case "for_statement":
581
- case "for_in_statement":
582
- return "LOOP";
583
- case "while_statement": {
584
- const cond = extractCondition(node);
585
- return `WHILE:${cond}`;
586
- }
587
- case "do_statement": {
588
- const cond = extractCondition(node);
589
- return `WHILE:${cond}`;
590
- }
591
- case "switch_statement": {
592
- const expr = node.childForFieldName("value") ?? node.childForFieldName("condition") ?? (() => {
593
- for (let i = 0; i < node.childCount; i++) {
594
- const c = node.child(i);
595
- if (c.type === "parenthesized_expression") return c;
596
- }
597
- return null;
598
- })();
599
- const text = expr ? expr.text.replace(/^\(/, "").replace(/\)$/, "").trim() : "...";
600
- return `SWITCH:${text.length > 60 ? text.slice(0, 57) + "..." : text}`;
601
- }
602
- case "switch_case": {
603
- let value = null;
604
- const valNode = node.childForFieldName("value");
605
- if (valNode) {
606
- value = valNode.text;
607
- } else {
608
- for (let i = 0; i < node.childCount; i++) {
609
- const c = node.child(i);
610
- if (c.type !== "case" && c.type !== ":" && c.childCount === 0 && c.text === "case") continue;
611
- if (c.type !== "case" && c.text !== "case" && c.text !== ":") {
612
- value = c.text;
613
- break;
614
- }
615
- }
616
- }
617
- return `CASE:${value ?? "..."}`;
618
- }
619
- case "switch_default":
620
- return "DEFAULT:";
621
- case "return_statement": {
622
- let retText = "";
623
- for (let i = 0; i < node.childCount; i++) {
624
- const c = node.child(i);
625
- if (c.text !== "return" && c.text !== ";") {
626
- retText += (retText ? " " : "") + c.text;
627
- }
628
- }
629
- retText = retText.replace(/\s*\n\s*/g, " ").replace(/\s{2,}/g, " ").trim();
630
- if (!retText) return "RET";
631
- return `RET ${retText.length > 60 ? retText.slice(0, 57) + "..." : retText}`;
632
- }
633
- case "throw_statement": {
634
- let throwText = "";
635
- for (let i = 0; i < node.childCount; i++) {
636
- const c = node.child(i);
637
- if (c.text !== "throw" && c.text !== ";") {
638
- throwText += (throwText ? " " : "") + c.text;
639
- }
640
- }
641
- throwText = throwText.trim();
642
- return `THROW:${throwText.length > 60 ? throwText.slice(0, 57) + "..." : throwText}`;
643
- }
644
- case "try_statement":
645
- return "TRY";
646
- case "catch_clause": {
647
- const param = node.childForFieldName("parameter");
648
- const paramText = param ? param.text : "...";
649
- return `CATCH:${paramText}`;
650
- }
651
- default:
652
- return null;
653
- }
654
- }
655
- function emitTier1(node) {
656
- const exported = isExported(node);
657
- const outPrefix = exported ? "OUT " : "";
658
- switch (node.type) {
659
- case "import_statement": {
660
- const text = collapseText(node.text, 80);
661
- return `USE:${text}`;
662
- }
663
- case "function_declaration": {
664
- const name = node.childForFieldName("name")?.text ?? "anonymous";
665
- const rawParams = node.childForFieldName("parameters")?.text ?? "()";
666
- const params = collapseText(rawParams, 60);
667
- const asyncPrefix = isAsync(node) ? "ASYNC " : "";
668
- return `${outPrefix}${asyncPrefix}FN:${name}${params}`;
669
- }
670
- case "class_declaration": {
671
- const name = node.childForFieldName("name")?.text ?? "Anonymous";
672
- const typeParams = getTypeParams(node);
673
- return `${outPrefix}CLASS:${name}${typeParams}`;
674
- }
675
- case "interface_declaration": {
676
- const name = node.childForFieldName("name")?.text ?? "Anonymous";
677
- const typeParams = getTypeParams(node);
678
- return `${outPrefix}INTERFACE:${name}${typeParams}`;
679
- }
680
- case "type_alias_declaration": {
681
- const name = node.childForFieldName("name")?.text ?? "Anonymous";
682
- return `${outPrefix}TYPE:${name}`;
683
- }
684
- case "enum_declaration": {
685
- const name = node.childForFieldName("name")?.text ?? "Anonymous";
686
- return `${outPrefix}ENUM:${name}`;
687
- }
688
- default:
689
- return null;
690
- }
691
- }
692
- function emitTier3(node) {
693
- switch (node.type) {
694
- case "lexical_declaration": {
695
- let declarator = null;
696
- for (let i = 0; i < node.childCount; i++) {
697
- const c = node.child(i);
698
- if (c.type === "variable_declarator") {
699
- declarator = c;
700
- break;
701
- }
702
- }
703
- if (!declarator) return null;
704
- const name = declarator.childForFieldName("name")?.text ?? "?";
705
- const value = declarator.childForFieldName("value");
706
- if (value) {
707
- if (value.type === "arrow_function") {
708
- const asyncPrefix = isAsync(value) ? "ASYNC " : "";
709
- const params = value.childForFieldName("parameters")?.text ?? "()";
710
- return `${asyncPrefix}FN:${name}${collapseText(params, 60)} => ...`;
711
- }
712
- if (value.type === "await_expression") {
713
- const callee = value.childCount > 1 ? value.child(1).text : "...";
714
- return `AWAIT:${name}=${collapseText(callee, 40)}`;
715
- }
716
- if (node.parent?.type === "statement_block") return null;
717
- const vt = value.type;
718
- if (vt === "number" || vt === "true" || vt === "false") return null;
719
- if (vt === "object" || vt === "array") return null;
720
- if (vt === "new_expression" || vt === "call_expression") return null;
721
- const valText = value.text.replace(/"[^"]*"/g, '""').replace(/'[^']*'/g, "''").replace(/`[^`]*`/g, "``");
722
- return `VAR:${name} = ${collapseText(valText, 50)}`;
723
- }
724
- return null;
725
- }
726
- case "expression_statement": {
727
- const expr = node.child(0);
728
- if (!expr) return null;
729
- if (expr.type === "await_expression") {
730
- return null;
731
- }
732
- if (expr.type === "call_expression") {
733
- return null;
734
- }
735
- return null;
736
- }
737
- default:
738
- return null;
739
- }
740
- }
741
- function walkNode(node, depth, lines) {
742
- const tier = tierOf(node.type);
743
- switch (tier) {
744
- case "T1_KEEP": {
745
- const ir = emitTier1(node);
746
- if (ir) lines.push(ir);
747
- for (let i = 0; i < node.childCount; i++) {
748
- const child = node.child(i);
749
- const childType = child.type;
750
- if (childType === "statement_block" || childType === "class_body") {
751
- walkNode(child, depth + 1, lines);
752
- }
753
- }
754
- break;
755
- }
756
- case "T2_CONTROL": {
757
- if (depth > 4 && node.type !== "return_statement" && node.type !== "throw_statement" && node.type !== "switch_case" && node.type !== "switch_default") break;
758
- if (node.type === "if_statement") {
759
- let hasElse = false;
760
- for (let i = 0; i < node.childCount; i++) {
761
- if (node.child(i).type === "else_clause") {
762
- hasElse = true;
763
- break;
764
- }
765
- }
766
- if (!hasElse) {
767
- const body = node.childForFieldName("consequence") ?? (() => {
768
- for (let i = 0; i < node.childCount; i++) {
769
- const c = node.child(i);
770
- if (c.type === "statement_block") return c;
771
- }
772
- return null;
773
- })();
774
- if (body) {
775
- let singleStmt = null;
776
- if (body.type === "statement_block") {
777
- const stmts = [];
778
- for (let i = 0; i < body.childCount; i++) {
779
- const c = body.child(i);
780
- if (c.type !== "{" && c.type !== "}") stmts.push(c);
781
- }
782
- if (stmts.length === 1) singleStmt = stmts[0];
783
- } else if (body.type === "return_statement" || body.type === "throw_statement") {
784
- singleStmt = body;
785
- }
786
- if (singleStmt && (singleStmt.type === "return_statement" || singleStmt.type === "throw_statement")) {
787
- const cond = extractCondition(node);
788
- const retLine = emitTier2(singleStmt);
789
- if (retLine) {
790
- const indent2 = " ".repeat(depth);
791
- lines.push(`${indent2}IF:${cond} \u2192 ${retLine}`);
792
- break;
793
- }
794
- }
795
- }
796
- }
797
- }
798
- const line = emitTier2(node);
799
- const indent = " ".repeat(depth);
800
- if (line) lines.push(indent + line);
801
- for (let i = 0; i < node.childCount; i++) {
802
- walkNode(node.child(i), depth + 1, lines);
803
- }
804
- break;
805
- }
806
- case "T3_COMPRESS": {
807
- if (depth > 4) break;
808
- const line = emitTier3(node);
809
- const indent = " ".repeat(depth);
810
- if (line) lines.push(indent + line);
811
- break;
812
- }
813
- case "WALK_ONLY": {
814
- for (let i = 0; i < node.childCount; i++) {
815
- const child = node.child(i);
816
- if (node.type === "export_statement") {
817
- if (child.type === "export" || child.type === "default" || child.text === "export" || child.text === "default") {
818
- if (child.childCount === 0 && (child.text === "export" || child.text === "default")) {
819
- continue;
820
- }
821
- }
822
- }
823
- walkNode(child, depth + 1, lines);
824
- }
825
- break;
826
- }
827
- case "T4_DROP":
828
- default:
829
- break;
830
- }
831
- }
832
- async function astWalkIR(code, filePath) {
833
- const lang = detectLanguage(filePath);
834
- if (!lang) return null;
835
- const { parser } = await getParser(lang);
836
- const tree = parser.parse(code);
837
- const root = tree.rootNode;
838
- const lines = [];
839
- walkNode(root, 0, lines);
840
- if (lines.length === 0) return null;
841
- const merged = [];
842
- let useBlock = [];
843
- for (const line of lines) {
844
- if (line.startsWith("USE:")) {
845
- const m = line.match(/from\s+["']([^"']+)["']/);
846
- useBlock.push(m ? m[1] : line.slice(4));
847
- } else {
848
- if (useBlock.length > 0) {
849
- if (useBlock.length <= 3) {
850
- for (const mod of useBlock) merged.push(`USE:${mod}`);
851
- } else {
852
- merged.push(`USE:[${useBlock.join(", ")}]`);
853
- }
854
- useBlock = [];
855
- }
856
- merged.push(line);
857
- }
858
- }
859
- if (useBlock.length > 0) {
860
- if (useBlock.length <= 3) {
861
- for (const mod of useBlock) merged.push(`USE:${mod}`);
862
- } else {
863
- merged.push(`USE:[${useBlock.join(", ")}]`);
864
- }
865
- }
866
- return merged.join("\n");
867
- }
868
-
869
- // src/ir/layers.ts
870
- function generateL0(code, filePath) {
871
- const structure = extractStructure(code);
872
- const topLevel = structure.filter(
873
- (s) => s.indent === 0 && ["function-start", "type-start", "export"].includes(s.type)
874
- );
875
- const declarations = topLevel.map((s) => {
876
- const name = s.raw.match(
877
- /(?:function|class|interface|const|let|var|export\s+(?:default\s+)?(?:function|class|async\s+function))\s+(\w+)/
878
- )?.[1] ?? "unknown";
879
- return ` ${s.type === "type-start" ? "CLASS" : "FN"}:${name} L${s.line}`;
880
- });
881
- return `${filePath}
882
- ${declarations.join("\n")}`;
883
- }
884
- async function generateL1(code, filePath, health) {
885
- const ir = await astWalkIR(code, filePath) ?? fingerprintFile(code, 0.75);
886
- if (health) {
887
- return annotateIR(ir, health);
888
- }
889
- return ir;
890
- }
891
- function generateL2(delta, health) {
892
- const parts = [`FILE: ${delta.file}`];
893
- for (const hunk of delta.hunks) {
894
- if (hunk.functionScope) parts.push(`SCOPE: ${hunk.functionScope}`);
895
- parts.push(`CHANGED: ${hunk.changed.join("\n ")}`);
896
- if (hunk.surroundingIR) parts.push(`CONTEXT: ${hunk.surroundingIR}`);
897
- if (hunk.blame) {
898
- parts.push(`BLAME: ${hunk.blame.author}, ${hunk.blame.date}, commit:"${hunk.blame.commitMessage}"`);
899
- }
900
- }
901
- const ir = parts.join("\n");
902
- if (health) return annotateIR(ir, health);
903
- return ir;
904
- }
905
- function generateL3(code, startLine, endLine) {
906
- const lines = code.split("\n");
907
- return lines.slice(startLine - 1, endLine).join("\n");
908
- }
909
- async function generateLayer(layer, options) {
910
- switch (layer) {
911
- case "L0":
912
- return generateL0(options.code, options.filePath);
913
- case "L1":
914
- return generateL1(options.code, options.filePath, options.health);
915
- case "L2":
916
- if (!options.delta) return generateL1(options.code, options.filePath, options.health);
917
- return generateL2(options.delta, options.health);
918
- case "L3":
919
- if (options.lineRange) {
920
- return generateL3(options.code, options.lineRange.start, options.lineRange.end);
921
- }
922
- return options.code;
923
- }
924
- }
925
-
926
- // src/trends/git-log-parser.ts
927
- import { execSync } from "child_process";
928
- var BUG_FIX_PATTERNS = [
929
- /\bfix\b/i,
930
- /\bbugfix\b/i,
931
- /\bhotfix\b/i,
932
- /\bpatch\b/i,
933
- /\bresolve\b/i,
934
- /\bbug\b/i
935
- ];
936
- function isBugFixCommit(message) {
937
- return BUG_FIX_PATTERNS.some((p) => p.test(message));
938
- }
939
- function parseGitLogOutput(output) {
940
- const entries = [];
941
- const lines = output.split("\n");
942
- let i = 0;
943
- while (i < lines.length) {
944
- const line = lines[i].trim();
945
- if (!line || !line.includes("|")) {
946
- i++;
947
- continue;
948
- }
949
- const [hash, author, date, ...messageParts] = line.split("|");
950
- const message = messageParts.join("|");
951
- const files = [];
952
- i++;
953
- while (i < lines.length && lines[i].trim() !== "" && !lines[i].includes("|")) {
954
- const fileLine = lines[i].trim();
955
- if (fileLine) files.push(fileLine);
956
- i++;
957
- }
958
- entries.push({ hash, author, date, message, files });
959
- }
960
- return entries;
961
- }
962
- function getGitLog(repoPath, count = 100) {
963
- try {
964
- const output = execSync(
965
- `git log --format="%h|%an|%as|%s" --name-only -n ${count}`,
966
- { cwd: repoPath, encoding: "utf-8", timeout: 1e4 }
967
- );
968
- return parseGitLogOutput(output);
969
- } catch {
970
- return [];
971
- }
972
- }
973
-
974
- // src/trends/hotspot.ts
975
- function detectHotspots(entries, options) {
976
- const fileStats = /* @__PURE__ */ new Map();
977
- for (const entry of entries) {
978
- const isFix = isBugFixCommit(entry.message);
979
- for (const file of entry.files) {
980
- const stats = fileStats.get(file) ?? { changes: 0, fixes: 0, authors: /* @__PURE__ */ new Set() };
981
- stats.changes++;
982
- if (isFix) stats.fixes++;
983
- stats.authors.add(entry.author);
984
- fileStats.set(file, stats);
985
- }
986
- }
987
- const hotspots = [];
988
- for (const [file, stats] of fileStats) {
989
- const fixRatio = stats.changes > 0 ? stats.fixes / stats.changes : 0;
990
- if (stats.changes >= options.threshold && fixRatio >= options.fixRatioThreshold) {
991
- hotspots.push({
992
- file,
993
- changesInLast30Commits: stats.changes,
994
- bugFixRatio: fixRatio,
995
- authorCount: stats.authors.size
996
- });
997
- }
998
- }
999
- return hotspots.sort((a, b) => b.changesInLast30Commits - a.changesInLast30Commits);
1000
- }
1001
-
1002
- // src/trends/decay.ts
1003
- function detectDecay(entries) {
1004
- const fileChanges = /* @__PURE__ */ new Map();
1005
- for (const entry of entries) {
1006
- for (const file of entry.files) {
1007
- const changes = fileChanges.get(file) ?? [];
1008
- changes.push({ date: entry.date });
1009
- fileChanges.set(file, changes);
1010
- }
1011
- }
1012
- const signals = [];
1013
- for (const [file, changes] of fileChanges) {
1014
- if (changes.length < 4) continue;
1015
- const sorted = [...changes].sort((a, b) => a.date.localeCompare(b.date));
1016
- const firstDate = new Date(sorted[0].date).getTime();
1017
- const lastDate = new Date(sorted[sorted.length - 1].date).getTime();
1018
- const midDate = firstDate + (lastDate - firstDate) / 2;
1019
- const firstHalfCount = sorted.filter((c) => new Date(c.date).getTime() <= midDate).length;
1020
- const secondHalfCount = sorted.length - firstHalfCount;
1021
- if (secondHalfCount > firstHalfCount) {
1022
- signals.push({
1023
- file,
1024
- metric: "churn",
1025
- trend: "declining",
1026
- dataPoints: sorted.map((c, i) => ({ date: c.date, value: i + 1 }))
1027
- });
1028
- }
1029
- }
1030
- return signals;
1031
- }
1032
-
1033
- // src/trends/inconsistency.ts
1034
- function detectInconsistencies(entries, minAuthors = 3) {
1035
- const fileAuthors = /* @__PURE__ */ new Map();
1036
- for (const entry of entries) {
1037
- for (const file of entry.files) {
1038
- const authors = fileAuthors.get(file) ?? /* @__PURE__ */ new Map();
1039
- const commits = authors.get(entry.author) ?? [];
1040
- commits.push(entry.message);
1041
- authors.set(entry.author, commits);
1042
- fileAuthors.set(file, authors);
1043
- }
1044
- }
1045
- const inconsistencies = [];
1046
- for (const [file, authors] of fileAuthors) {
1047
- if (authors.size >= minAuthors) {
1048
- const patterns = Array.from(authors.entries()).map(([author, commits]) => ({
1049
- author,
1050
- style: categorizeStyle(commits)
1051
- }));
1052
- inconsistencies.push({ file, patterns });
1053
- }
1054
- }
1055
- return inconsistencies;
1056
- }
1057
- function categorizeStyle(commits) {
1058
- const types = commits.map((m) => {
1059
- if (m.match(/^fix/i)) return "fix";
1060
- if (m.match(/^feat/i)) return "feature";
1061
- if (m.match(/^refactor/i)) return "refactor";
1062
- return "other";
1063
- });
1064
- const primary = mode(types);
1065
- return `primarily ${primary} (${commits.length} commits)`;
1066
- }
1067
- function mode(arr) {
1068
- const counts = /* @__PURE__ */ new Map();
1069
- for (const item of arr) {
1070
- counts.set(item, (counts.get(item) ?? 0) + 1);
1071
- }
1072
- return Array.from(counts.entries()).sort((a, b) => b[1] - a[1])[0]?.[0] ?? "unknown";
1073
- }
18
+ import { readFileSync, readdirSync } from "fs";
19
+ import { join, relative } from "path";
1074
20
 
1075
21
  // src/router/router.ts
1076
- import picomatch2 from "picomatch";
22
+ import picomatch from "picomatch";
1077
23
  var DEFAULT_ROUTES = [
1078
24
  { pattern: "**/auth/**", agents: ["reviewer"], irLayer: "L1" },
1079
25
  { pattern: "**/*.test.*", agents: ["reviewer"], irLayer: "L0" },
@@ -1090,7 +36,7 @@ var FALLBACK = {
1090
36
  };
1091
37
  function route(finding, rules) {
1092
38
  for (const rule of rules) {
1093
- if (!picomatch2.isMatch(finding.file, rule.pattern)) continue;
39
+ if (!picomatch.isMatch(finding.file, rule.pattern)) continue;
1094
40
  if (rule.contentSignal) {
1095
41
  const content = finding.message + (finding.file ?? "");
1096
42
  if (!rule.contentSignal.test(content)) continue;
@@ -1168,35 +114,6 @@ var CLIAdapter = class {
1168
114
  }
1169
115
  };
1170
116
 
1171
- // src/benchmark/tokenizer.ts
1172
- function estimateTokens(text) {
1173
- if (!text) return 0;
1174
- const tokens = text.split(/[\s]+|(?<=[{}()[\];,.:=<>!&|?+\-*/^~@#$%\\])|(?=[{}()[\];,.:=<>!&|?+\-*/^~@#$%\\])/).filter(Boolean);
1175
- return tokens.length;
1176
- }
1177
-
1178
- // src/benchmark/runner.ts
1179
- async function benchmarkFile(code, filePath) {
1180
- const rawTokens = estimateTokens(code);
1181
- const irL0 = await generateLayer("L0", { code, filePath, health: null });
1182
- const irL1 = await generateLayer("L1", { code, filePath, health: null });
1183
- const irL0Tokens = estimateTokens(irL0);
1184
- const irL1Tokens = estimateTokens(irL1);
1185
- const astResult = await astWalkIR(code, filePath);
1186
- const engine = astResult !== null ? "AST" : "FP";
1187
- const savedPercent = rawTokens > 0 ? (rawTokens - irL1Tokens) / rawTokens * 100 : 0;
1188
- return { file: filePath, rawTokens, irL0Tokens, irL1Tokens, savedPercent, engine };
1189
- }
1190
- function summarize(results) {
1191
- const totalRaw = results.reduce((s, r) => s + r.rawTokens, 0);
1192
- const totalIRL0 = results.reduce((s, r) => s + r.irL0Tokens, 0);
1193
- const totalIRL1 = results.reduce((s, r) => s + r.irL1Tokens, 0);
1194
- const totalSavedPercent = totalRaw > 0 ? (totalRaw - totalIRL1) / totalRaw * 100 : 0;
1195
- const astCount = results.filter((r) => r.engine === "AST").length;
1196
- const fpCount = results.filter((r) => r.engine === "FP").length;
1197
- return { fileCount: results.length, totalRaw, totalIRL0, totalIRL1, totalSavedPercent, astCount, fpCount };
1198
- }
1199
-
1200
117
  // src/benchmark/quality.ts
1201
118
  var BENCHMARK_PROMPTS = [
1202
119
  {
@@ -1263,63 +180,13 @@ async function runQualityBenchmark(code, filePath, apiKey, promptId = "understan
1263
180
  return { file: filePath, raw: rawResult, ir: irResult, savedPercent };
1264
181
  }
1265
182
 
1266
- // src/context/packer.ts
1267
- async function packContext(files, options) {
1268
- const { budget, hotspots } = options;
1269
- const hotspotSet = new Set(hotspots.map((h) => h.file));
1270
- const entries = [];
1271
- let totalTokens = 0;
1272
- for (const file of files) {
1273
- const l0 = await generateLayer("L0", { code: file.code, filePath: file.path, health: null });
1274
- const l0Tokens = estimateTokens(l0);
1275
- entries.push({ path: file.path, layer: "L0", ir: l0, tokens: l0Tokens });
1276
- totalTokens += l0Tokens;
1277
- }
1278
- if (totalTokens > budget) {
1279
- const truncated = [];
1280
- let used = 0;
1281
- for (const entry of entries) {
1282
- if (used + entry.tokens <= budget) {
1283
- truncated.push(entry);
1284
- used += entry.tokens;
1285
- }
1286
- }
1287
- return { entries: truncated, totalTokens: used, budget, filesAtL0: truncated.length, filesAtL1: 0 };
1288
- }
1289
- const upgradeOrder = entries.map((e, i) => ({ index: i, path: e.path, rawTokens: files[i].rawTokens, isHotspot: hotspotSet.has(e.path) })).sort((a, b) => {
1290
- if (a.isHotspot && !b.isHotspot) return -1;
1291
- if (!a.isHotspot && b.isHotspot) return 1;
1292
- return b.rawTokens - a.rawTokens;
1293
- });
1294
- let filesAtL1 = 0;
1295
- for (const item of upgradeOrder) {
1296
- const file = files[item.index];
1297
- const l1 = await generateLayer("L1", { code: file.code, filePath: file.path, health: null });
1298
- const l1Tokens = estimateTokens(l1);
1299
- const currentL0Tokens = entries[item.index].tokens;
1300
- const additionalTokens = l1Tokens - currentL0Tokens;
1301
- if (totalTokens + additionalTokens <= budget) {
1302
- entries[item.index] = { path: item.path, layer: "L1", ir: l1, tokens: l1Tokens };
1303
- totalTokens += additionalTokens;
1304
- filesAtL1++;
1305
- }
1306
- }
1307
- return {
1308
- entries,
1309
- totalTokens,
1310
- budget,
1311
- filesAtL0: entries.length - filesAtL1,
1312
- filesAtL1
1313
- };
1314
- }
1315
-
1316
183
  // src/cli/commands.ts
1317
184
  function collectFiles(dir, extensions) {
1318
185
  const files = [];
1319
186
  try {
1320
187
  const entries = readdirSync(dir, { withFileTypes: true });
1321
188
  for (const entry of entries) {
1322
- const fullPath = join2(dir, entry.name);
189
+ const fullPath = join(dir, entry.name);
1323
190
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") continue;
1324
191
  if (entry.isDirectory()) {
1325
192
  files.push(...collectFiles(fullPath, extensions));
@@ -1340,7 +207,7 @@ function runScan(projectPath) {
1340
207
  `);
1341
208
  const allFindings = [];
1342
209
  for (const file of files) {
1343
- const code = readFileSync2(file, "utf-8");
210
+ const code = readFileSync(file, "utf-8");
1344
211
  const relPath = relative(projectPath, file);
1345
212
  const findings = runDetector(code, relPath, config.watchers);
1346
213
  allFindings.push(...findings);
@@ -1381,7 +248,7 @@ function runTrends(projectPath) {
1381
248
  }
1382
249
  async function runIR(projectPath, filePath, layer) {
1383
250
  const config = loadConfig(projectPath);
1384
- const code = readFileSync2(filePath, "utf-8");
251
+ const code = readFileSync(filePath, "utf-8");
1385
252
  const relPath = relative(projectPath, filePath);
1386
253
  const entries = getGitLog(projectPath, 100);
1387
254
  const trends = {
@@ -1409,7 +276,7 @@ async function runBenchmark(projectPath) {
1409
276
  `);
1410
277
  const results = [];
1411
278
  for (const file of files) {
1412
- const code = readFileSync2(file, "utf-8");
279
+ const code = readFileSync(file, "utf-8");
1413
280
  const relPath = relative(projectPath, file);
1414
281
  results.push(await benchmarkFile(code, relPath));
1415
282
  }
@@ -1448,7 +315,7 @@ async function runBenchmarkQuality(projectPath, filePath) {
1448
315
  console.error(" Error: ANTHROPIC_API_KEY environment variable is required.");
1449
316
  process.exit(1);
1450
317
  }
1451
- const code = readFileSync2(filePath, "utf-8");
318
+ const code = readFileSync(filePath, "utf-8");
1452
319
  const relPath = relative(projectPath, filePath);
1453
320
  console.log("composto v0.1.0 \u2014 quality benchmark\n");
1454
321
  console.log(` File: ${relPath}
@@ -1492,7 +359,7 @@ async function runContext(projectPath, budget) {
1492
359
  fixRatioThreshold: config.trends.bugFixRatioThreshold
1493
360
  });
1494
361
  const fileInputs = files.map((file) => {
1495
- const code = readFileSync2(file, "utf-8");
362
+ const code = readFileSync(file, "utf-8");
1496
363
  const relPath = relative(projectPath, file);
1497
364
  return { path: relPath, code, rawTokens: estimateTokens(code) };
1498
365
  });
@@ -1524,17 +391,17 @@ async function runContext(projectPath, budget) {
1524
391
  }
1525
392
 
1526
393
  // src/index.ts
1527
- import { resolve as resolve2 } from "path";
394
+ import { resolve } from "path";
1528
395
  var args = process.argv.slice(2);
1529
396
  var command = args[0];
1530
397
  switch (command) {
1531
398
  case "scan": {
1532
- const projectPath = resolve2(args[1] ?? ".");
399
+ const projectPath = resolve(args[1] ?? ".");
1533
400
  runScan(projectPath);
1534
401
  break;
1535
402
  }
1536
403
  case "trends": {
1537
- const projectPath = resolve2(args[1] ?? ".");
404
+ const projectPath = resolve(args[1] ?? ".");
1538
405
  runTrends(projectPath);
1539
406
  break;
1540
407
  }
@@ -1545,11 +412,11 @@ switch (command) {
1545
412
  console.error("Usage: composto ir <file> [L0|L1|L2|L3]");
1546
413
  process.exit(1);
1547
414
  }
1548
- await runIR(resolve2("."), resolve2(filePath), layer);
415
+ await runIR(resolve("."), resolve(filePath), layer);
1549
416
  break;
1550
417
  }
1551
418
  case "benchmark": {
1552
- const projectPath = resolve2(args[1] ?? ".");
419
+ const projectPath = resolve(args[1] ?? ".");
1553
420
  await runBenchmark(projectPath);
1554
421
  break;
1555
422
  }
@@ -1559,11 +426,11 @@ switch (command) {
1559
426
  console.error("Usage: composto benchmark-quality <file>");
1560
427
  process.exit(1);
1561
428
  }
1562
- await runBenchmarkQuality(resolve2("."), resolve2(filePath));
429
+ await runBenchmarkQuality(resolve("."), resolve(filePath));
1563
430
  break;
1564
431
  }
1565
432
  case "context": {
1566
- const projectPath = resolve2(args[1] ?? ".");
433
+ const projectPath = resolve(args[1] ?? ".");
1567
434
  const budgetFlag = args.indexOf("--budget");
1568
435
  const budget = budgetFlag !== -1 && args[budgetFlag + 1] ? parseInt(args[budgetFlag + 1], 10) : 4e3;
1569
436
  await runContext(projectPath, budget);