@aiready/core 0.9.32 → 0.9.35

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.mjs CHANGED
@@ -18,13 +18,13 @@ import {
18
18
  getToolWeight,
19
19
  normalizeToolName,
20
20
  parseWeightString
21
- } from "./chunk-CWRCDSKZ.mjs";
21
+ } from "./chunk-HFLFBA6F.mjs";
22
22
 
23
23
  // src/utils/file-scanner.ts
24
24
  import { glob } from "glob";
25
25
  import { readFile } from "fs/promises";
26
26
  import { existsSync } from "fs";
27
- import { join, relative } from "path";
27
+ import { join, relative, dirname } from "path";
28
28
  import ignorePkg from "ignore";
29
29
  var DEFAULT_EXCLUDE = [
30
30
  // Dependencies
@@ -40,6 +40,8 @@ var DEFAULT_EXCLUDE = [
40
40
  "**/cdk.out/**",
41
41
  // Framework-specific build dirs
42
42
  "**/.next/**",
43
+ "**/.sst/**",
44
+ "**/.open-next/**",
43
45
  "**/.nuxt/**",
44
46
  "**/.vuepress/**",
45
47
  "**/.cache/**",
@@ -71,6 +73,28 @@ var DEFAULT_EXCLUDE = [
71
73
  "**/*.log",
72
74
  "**/.DS_Store"
73
75
  ];
76
+ var VAGUE_FILE_NAMES = /* @__PURE__ */ new Set([
77
+ "utils",
78
+ "helpers",
79
+ "helper",
80
+ "misc",
81
+ "common",
82
+ "shared",
83
+ "tools",
84
+ "util",
85
+ "lib",
86
+ "libs",
87
+ "stuff",
88
+ "functions",
89
+ "methods",
90
+ "handlers",
91
+ "data",
92
+ "temp",
93
+ "tmp",
94
+ "test-utils",
95
+ "test-helpers",
96
+ "mocks"
97
+ ]);
74
98
  async function scanFiles(options) {
75
99
  const {
76
100
  rootDir,
@@ -88,18 +112,35 @@ async function scanFiles(options) {
88
112
  ignoreFromFile = [];
89
113
  }
90
114
  }
91
- const finalExclude = [.../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...DEFAULT_EXCLUDE])];
115
+ const TEST_PATTERNS = ["**/*.test.*", "**/*.spec.*", "**/__tests__/**", "**/test/**", "**/tests/**"];
116
+ const baseExclude = options.includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS.includes(p)) : DEFAULT_EXCLUDE;
117
+ const finalExclude = [
118
+ .../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])
119
+ ];
92
120
  const files = await glob(include, {
93
121
  cwd: rootDir,
94
122
  ignore: finalExclude,
95
123
  absolute: true
96
124
  });
97
- const gitignorePath = join(rootDir || ".", ".gitignore");
98
- if (existsSync(gitignorePath)) {
125
+ const gitignoreFiles = await glob("**/.gitignore", {
126
+ cwd: rootDir,
127
+ ignore: finalExclude,
128
+ absolute: true
129
+ });
130
+ if (gitignoreFiles.length > 0) {
99
131
  try {
100
- const gitTxt = await readFile(gitignorePath, "utf-8");
101
132
  const ig = ignorePkg();
102
- ig.add(gitTxt);
133
+ for (const gitignorePath of gitignoreFiles) {
134
+ const gitTxt = await readFile(gitignorePath, "utf-8");
135
+ const gitignoreDir = dirname(gitignorePath);
136
+ const relativePrefix = relative(rootDir || ".", gitignoreDir).replace(/\\/g, "/");
137
+ const patterns = gitTxt.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).filter((l) => !l.startsWith("#"));
138
+ if (relativePrefix === "." || relativePrefix === "") {
139
+ ig.add(patterns);
140
+ } else {
141
+ ig.add(patterns.map((p) => p.startsWith("/") ? `${relativePrefix}${p}` : `${relativePrefix}/**/${p}`));
142
+ }
143
+ }
103
144
  const filtered = files.filter((f) => {
104
145
  let rel = relative(rootDir || ".", f).replace(/\\/g, "/");
105
146
  if (rel === "") rel = f;
@@ -112,6 +153,54 @@ async function scanFiles(options) {
112
153
  }
113
154
  return files;
114
155
  }
156
+ async function scanEntries(options) {
157
+ const files = await scanFiles(options);
158
+ const { rootDir, include = ["**/*"], exclude, includeTests } = options;
159
+ const ignoreFilePath = join(rootDir || ".", ".aireadyignore");
160
+ let ignoreFromFile = [];
161
+ if (existsSync(ignoreFilePath)) {
162
+ try {
163
+ const txt = await readFile(ignoreFilePath, "utf-8");
164
+ ignoreFromFile = txt.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).filter((l) => !l.startsWith("#")).filter((l) => !l.startsWith("!"));
165
+ } catch (e) {
166
+ ignoreFromFile = [];
167
+ }
168
+ }
169
+ const TEST_PATTERNS = ["**/*.test.*", "**/*.spec.*", "**/__tests__/**", "**/test/**", "**/tests/**"];
170
+ const baseExclude = includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS.includes(p)) : DEFAULT_EXCLUDE;
171
+ const finalExclude = [.../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])];
172
+ const dirs = await glob("**/", {
173
+ cwd: rootDir,
174
+ ignore: finalExclude,
175
+ absolute: true
176
+ });
177
+ const gitignoreFiles = await glob("**/.gitignore", {
178
+ cwd: rootDir,
179
+ ignore: finalExclude,
180
+ absolute: true
181
+ });
182
+ if (gitignoreFiles.length > 0) {
183
+ const ig = ignorePkg();
184
+ for (const gitignorePath of gitignoreFiles) {
185
+ const gitTxt = await readFile(gitignorePath, "utf-8");
186
+ const gitignoreDir = dirname(gitignorePath);
187
+ const relativePrefix = relative(rootDir || ".", gitignoreDir).replace(/\\/g, "/");
188
+ const patterns = gitTxt.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).filter((l) => !l.startsWith("#"));
189
+ if (relativePrefix === "." || relativePrefix === "") {
190
+ ig.add(patterns);
191
+ } else {
192
+ ig.add(patterns.map((p) => p.startsWith("/") ? `${relativePrefix}${p}` : `${relativePrefix}/**/${p}`));
193
+ }
194
+ }
195
+ const filteredDirs = dirs.filter((d) => {
196
+ let rel = relative(rootDir || ".", d).replace(/\\/g, "/").replace(/\/$/, "");
197
+ if (rel === "") return true;
198
+ return !ig.ignores(rel);
199
+ });
200
+ return { files, dirs: filteredDirs };
201
+ }
202
+ return { files, dirs };
203
+ }
115
204
  async function readFileContent(filePath) {
116
205
  return readFile(filePath, "utf-8");
117
206
  }
@@ -304,7 +393,7 @@ function estimateTokens(text) {
304
393
 
305
394
  // src/utils/config.ts
306
395
  import { readFileSync, existsSync as existsSync2 } from "fs";
307
- import { join as join2, resolve, dirname } from "path";
396
+ import { join as join2, resolve, dirname as dirname2 } from "path";
308
397
  import { pathToFileURL } from "url";
309
398
  var CONFIG_FILES = [
310
399
  "aiready.json",
@@ -336,11 +425,18 @@ async function loadConfig(rootDir) {
336
425
  return config;
337
426
  } catch (error) {
338
427
  const errorMessage = error instanceof Error ? error.message : String(error);
339
- throw new Error(`Failed to load config from ${configPath}: ${errorMessage}`);
428
+ const e = new Error(
429
+ `Failed to load config from ${configPath}: ${errorMessage}`
430
+ );
431
+ try {
432
+ e.cause = error instanceof Error ? error : void 0;
433
+ } catch {
434
+ }
435
+ throw e;
340
436
  }
341
437
  }
342
438
  }
343
- const parent = dirname(currentDir);
439
+ const parent = dirname2(currentDir);
344
440
  if (parent === currentDir) {
345
441
  break;
346
442
  }
@@ -373,7 +469,7 @@ function mergeConfigWithDefaults(userConfig, defaults) {
373
469
 
374
470
  // src/utils/cli-helpers.ts
375
471
  import { writeFileSync, mkdirSync, existsSync as existsSync3 } from "fs";
376
- import { join as join3, dirname as dirname2 } from "path";
472
+ import { join as join3, dirname as dirname3 } from "path";
377
473
  function resolveOutputPath(userPath, defaultFilename, workingDir = process.cwd()) {
378
474
  let outputPath;
379
475
  if (userPath) {
@@ -382,7 +478,7 @@ function resolveOutputPath(userPath, defaultFilename, workingDir = process.cwd()
382
478
  const aireadyDir = join3(workingDir, ".aiready");
383
479
  outputPath = join3(aireadyDir, defaultFilename);
384
480
  }
385
- const parentDir = dirname2(outputPath);
481
+ const parentDir = dirname3(outputPath);
386
482
  if (!existsSync3(parentDir)) {
387
483
  mkdirSync(parentDir, { recursive: true });
388
484
  }
@@ -400,7 +496,7 @@ async function loadMergedConfig(directory, defaults, cliOptions) {
400
496
  }
401
497
  function handleJSONOutput(data, outputFile, successMessage) {
402
498
  if (outputFile) {
403
- const dir = dirname2(outputFile);
499
+ const dir = dirname3(outputFile);
404
500
  if (!existsSync3(dir)) {
405
501
  mkdirSync(dir, { recursive: true });
406
502
  }
@@ -476,7 +572,7 @@ var MODEL_PRICING_PRESETS = {
476
572
  contextTier: "frontier",
477
573
  typicalQueriesPerDevPerDay: 150
478
574
  },
479
- "copilot": {
575
+ copilot: {
480
576
  name: "GitHub Copilot (subscription)",
481
577
  // Amortized per-request cost for a $19/month plan at 80 queries/day
482
578
  pricePer1KInputTokens: 1e-4,
@@ -542,9 +638,18 @@ function calculateProductivityImpact(issues, hourlyRate = DEFAULT_HOURLY_RATE) {
542
638
  hourlyRate,
543
639
  totalCost: Math.round(totalCost),
544
640
  bySeverity: {
545
- critical: { hours: Math.round(hours.critical * 10) / 10, cost: Math.round(hours.critical * hourlyRate) },
546
- major: { hours: Math.round(hours.major * 10) / 10, cost: Math.round(hours.major * hourlyRate) },
547
- minor: { hours: Math.round(hours.minor * 10) / 10, cost: Math.round(hours.minor * hourlyRate) }
641
+ critical: {
642
+ hours: Math.round(hours.critical * 10) / 10,
643
+ cost: Math.round(hours.critical * hourlyRate)
644
+ },
645
+ major: {
646
+ hours: Math.round(hours.major * 10) / 10,
647
+ cost: Math.round(hours.major * hourlyRate)
648
+ },
649
+ minor: {
650
+ hours: Math.round(hours.minor * 10) / 10,
651
+ cost: Math.round(hours.minor * hourlyRate)
652
+ }
548
653
  }
549
654
  };
550
655
  }
@@ -602,12 +707,18 @@ function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentat
602
707
  const criticalBudget = tierThresholds.criticalTokens;
603
708
  const idealDepth = tierThresholds.idealDepth;
604
709
  const budgetRange = criticalBudget - idealBudget;
605
- const budgetFactor = Math.min(100, Math.max(
606
- 0,
607
- (contextBudget - idealBudget) / budgetRange * 100
608
- ));
609
- const depthFactor = Math.min(100, Math.max(0, (importDepth - idealDepth) * 10));
610
- const fragmentationFactor = Math.min(100, Math.max(0, (fragmentation - 0.3) * 250));
710
+ const budgetFactor = Math.min(
711
+ 100,
712
+ Math.max(0, (contextBudget - idealBudget) / budgetRange * 100)
713
+ );
714
+ const depthFactor = Math.min(
715
+ 100,
716
+ Math.max(0, (importDepth - idealDepth) * 10)
717
+ );
718
+ const fragmentationFactor = Math.min(
719
+ 100,
720
+ Math.max(0, (fragmentation - 0.3) * 250)
721
+ );
611
722
  const consistencyFactor = Math.min(100, Math.max(0, 100 - consistencyScore));
612
723
  const fileFactor = Math.min(100, Math.max(0, (totalFiles - 50) / 5));
613
724
  const score = Math.round(
@@ -687,8 +798,12 @@ function calculateScoreTrend(history) {
687
798
  const now = /* @__PURE__ */ new Date();
688
799
  const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
689
800
  const ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1e3);
690
- const last30Days = history.filter((e) => new Date(e.timestamp) >= thirtyDaysAgo);
691
- const last90Days = history.filter((e) => new Date(e.timestamp) >= ninetyDaysAgo);
801
+ const last30Days = history.filter(
802
+ (e) => new Date(e.timestamp) >= thirtyDaysAgo
803
+ );
804
+ const last90Days = history.filter(
805
+ (e) => new Date(e.timestamp) >= ninetyDaysAgo
806
+ );
692
807
  const currentScore = history[history.length - 1].overallScore;
693
808
  const thirtyDaysAgoScore = last30Days[0]?.overallScore || currentScore;
694
809
  const ninetyDaysAgoScore = last90Days[0]?.overallScore || thirtyDaysAgoScore;
@@ -701,7 +816,10 @@ function calculateScoreTrend(history) {
701
816
  if (change30Days > 3) direction = "improving";
702
817
  else if (change30Days < -3) direction = "degrading";
703
818
  else direction = "stable";
704
- const projectedScore = Math.max(0, Math.min(100, currentScore + velocity * 4));
819
+ const projectedScore = Math.max(
820
+ 0,
821
+ Math.min(100, currentScore + velocity * 4)
822
+ );
705
823
  return {
706
824
  direction,
707
825
  change30Days,
@@ -762,9 +880,13 @@ function calculateKnowledgeConcentration(files, authorData) {
762
880
  recommendations: ["No files to analyze"]
763
881
  };
764
882
  }
765
- const orphanFiles = files.filter((f) => f.exports < 2 && f.imports < 2).length;
883
+ const orphanFiles = files.filter(
884
+ (f) => f.exports < 2 && f.imports < 2
885
+ ).length;
766
886
  const avgExports = files.reduce((sum, f) => sum + f.exports, 0) / files.length;
767
- const uniqueConceptFiles = files.filter((f) => f.exports > avgExports * 2).length;
887
+ const uniqueConceptFiles = files.filter(
888
+ (f) => f.exports > avgExports * 2
889
+ ).length;
768
890
  const totalExports = files.reduce((sum, f) => sum + f.exports, 0);
769
891
  const concentrationRatio = totalExports > 0 ? uniqueConceptFiles / files.length : 0;
770
892
  let singleAuthorFiles = 0;
@@ -776,7 +898,10 @@ function calculateKnowledgeConcentration(files, authorData) {
776
898
  const orphanRisk = orphanFiles / files.length * 30;
777
899
  const uniqueRisk = concentrationRatio * 40;
778
900
  const singleAuthorRisk = authorData ? singleAuthorFiles / files.length * 30 : 0;
779
- const score = Math.min(100, Math.round(orphanRisk + uniqueRisk + singleAuthorRisk));
901
+ const score = Math.min(
902
+ 100,
903
+ Math.round(orphanRisk + uniqueRisk + singleAuthorRisk)
904
+ );
780
905
  let rating;
781
906
  if (score < 20) rating = "low";
782
907
  else if (score < 40) rating = "moderate";
@@ -784,13 +909,19 @@ function calculateKnowledgeConcentration(files, authorData) {
784
909
  else rating = "critical";
785
910
  const recommendations = [];
786
911
  if (orphanFiles > files.length * 0.2) {
787
- recommendations.push(`Reduce ${orphanFiles} orphan files by connecting them to main modules`);
912
+ recommendations.push(
913
+ `Reduce ${orphanFiles} orphan files by connecting them to main modules`
914
+ );
788
915
  }
789
916
  if (uniqueConceptFiles > files.length * 0.1) {
790
- recommendations.push("Distribute high-export files into more focused modules");
917
+ recommendations.push(
918
+ "Distribute high-export files into more focused modules"
919
+ );
791
920
  }
792
921
  if (authorData && singleAuthorFiles > files.length * 0.3) {
793
- recommendations.push("Increase knowledge sharing to reduce single-author dependencies");
922
+ recommendations.push(
923
+ "Increase knowledge sharing to reduce single-author dependencies"
924
+ );
794
925
  }
795
926
  return {
796
927
  score,
@@ -944,7 +1075,10 @@ var TypeScriptParser = class {
944
1075
  specifiers,
945
1076
  isTypeOnly,
946
1077
  loc: node.loc ? {
947
- start: { line: node.loc.start.line, column: node.loc.start.column },
1078
+ start: {
1079
+ line: node.loc.start.line,
1080
+ column: node.loc.start.column
1081
+ },
948
1082
  end: { line: node.loc.end.line, column: node.loc.end.column }
949
1083
  } : void 0
950
1084
  });
@@ -955,11 +1089,16 @@ var TypeScriptParser = class {
955
1089
  extractExports(ast, imports) {
956
1090
  const exports = [];
957
1091
  const importedNames = new Set(
958
- imports.flatMap((imp) => imp.specifiers.filter((s) => s !== "*" && s !== "default"))
1092
+ imports.flatMap(
1093
+ (imp) => imp.specifiers.filter((s) => s !== "*" && s !== "default")
1094
+ )
959
1095
  );
960
1096
  for (const node of ast.body) {
961
1097
  if (node.type === "ExportNamedDeclaration" && node.declaration) {
962
- const extracted = this.extractFromDeclaration(node.declaration, importedNames);
1098
+ const extracted = this.extractFromDeclaration(
1099
+ node.declaration,
1100
+ importedNames
1101
+ );
963
1102
  exports.push(...extracted);
964
1103
  } else if (node.type === "ExportDefaultDeclaration") {
965
1104
  let name = "default";
@@ -975,7 +1114,10 @@ var TypeScriptParser = class {
975
1114
  name,
976
1115
  type,
977
1116
  loc: node.loc ? {
978
- start: { line: node.loc.start.line, column: node.loc.start.column },
1117
+ start: {
1118
+ line: node.loc.start.line,
1119
+ column: node.loc.start.column
1120
+ },
979
1121
  end: { line: node.loc.end.line, column: node.loc.end.column }
980
1122
  } : void 0
981
1123
  });
@@ -997,7 +1139,10 @@ var TypeScriptParser = class {
997
1139
  line: declaration.loc.start.line,
998
1140
  column: declaration.loc.start.column
999
1141
  },
1000
- end: { line: declaration.loc.end.line, column: declaration.loc.end.column }
1142
+ end: {
1143
+ line: declaration.loc.end.line,
1144
+ column: declaration.loc.end.column
1145
+ }
1001
1146
  } : void 0
1002
1147
  });
1003
1148
  } else if (declaration.type === "ClassDeclaration" && declaration.id) {
@@ -1009,7 +1154,10 @@ var TypeScriptParser = class {
1009
1154
  line: declaration.loc.start.line,
1010
1155
  column: declaration.loc.start.column
1011
1156
  },
1012
- end: { line: declaration.loc.end.line, column: declaration.loc.end.column }
1157
+ end: {
1158
+ line: declaration.loc.end.line,
1159
+ column: declaration.loc.end.column
1160
+ }
1013
1161
  } : void 0
1014
1162
  });
1015
1163
  } else if (declaration.type === "VariableDeclaration") {
@@ -1040,7 +1188,10 @@ var TypeScriptParser = class {
1040
1188
  line: declaration.loc.start.line,
1041
1189
  column: declaration.loc.start.column
1042
1190
  },
1043
- end: { line: declaration.loc.end.line, column: declaration.loc.end.column }
1191
+ end: {
1192
+ line: declaration.loc.end.line,
1193
+ column: declaration.loc.end.column
1194
+ }
1044
1195
  } : void 0
1045
1196
  });
1046
1197
  } else if (declaration.type === "TSInterfaceDeclaration") {
@@ -1052,7 +1203,10 @@ var TypeScriptParser = class {
1052
1203
  line: declaration.loc.start.line,
1053
1204
  column: declaration.loc.start.column
1054
1205
  },
1055
- end: { line: declaration.loc.end.line, column: declaration.loc.end.column }
1206
+ end: {
1207
+ line: declaration.loc.end.line,
1208
+ column: declaration.loc.end.column
1209
+ }
1056
1210
  } : void 0
1057
1211
  });
1058
1212
  }
@@ -1077,7 +1231,9 @@ var PythonParser = class {
1077
1231
  try {
1078
1232
  this.initialized = true;
1079
1233
  } catch (error) {
1080
- throw new Error(`Failed to initialize Python parser: ${error.message}`);
1234
+ throw new Error(
1235
+ `Failed to initialize Python parser: ${error.message}`
1236
+ );
1081
1237
  }
1082
1238
  }
1083
1239
  parse(code, filePath) {
@@ -1088,7 +1244,9 @@ var PythonParser = class {
1088
1244
  exports,
1089
1245
  imports,
1090
1246
  language: "python" /* Python */,
1091
- warnings: ["Python parsing is currently using regex-based extraction. Tree-sitter support coming soon."]
1247
+ warnings: [
1248
+ "Python parsing is currently using regex-based extraction. Tree-sitter support coming soon."
1249
+ ]
1092
1250
  };
1093
1251
  } catch (error) {
1094
1252
  throw new ParseError(
@@ -1183,7 +1341,7 @@ var PythonParser = class {
1183
1341
  }
1184
1342
  /**
1185
1343
  * Regex-based export extraction (temporary implementation)
1186
- *
1344
+ *
1187
1345
  * Python doesn't have explicit exports like JavaScript.
1188
1346
  * We extract:
1189
1347
  * - Functions defined at module level (def)
@@ -1351,7 +1509,13 @@ function getSupportedLanguages() {
1351
1509
 
1352
1510
  // src/future-proof-metrics.ts
1353
1511
  function calculateCognitiveLoad(params) {
1354
- const { linesOfCode, exportCount, importCount, uniqueConcepts, cyclomaticComplexity = 1 } = params;
1512
+ const {
1513
+ linesOfCode,
1514
+ exportCount,
1515
+ importCount,
1516
+ uniqueConcepts,
1517
+ cyclomaticComplexity = 1
1518
+ } = params;
1355
1519
  const sizeFactor = {
1356
1520
  name: "Size Complexity",
1357
1521
  score: Math.min(100, Math.max(0, (linesOfCode - 50) / 10)),
@@ -1377,7 +1541,12 @@ function calculateCognitiveLoad(params) {
1377
1541
  weight: 0.2,
1378
1542
  description: `${uniqueConcepts} unique concepts`
1379
1543
  };
1380
- const factors = [sizeFactor, interfaceFactor, dependencyFactor, conceptFactor];
1544
+ const factors = [
1545
+ sizeFactor,
1546
+ interfaceFactor,
1547
+ dependencyFactor,
1548
+ conceptFactor
1549
+ ];
1381
1550
  const score = factors.reduce((sum, f) => sum + f.score * f.weight, 0);
1382
1551
  let rating;
1383
1552
  if (score < 20) rating = "trivial";
@@ -1400,7 +1569,10 @@ function calculateCognitiveLoad(params) {
1400
1569
  function calculateSemanticDistance(params) {
1401
1570
  const { file1, file2, file1Domain, file2Domain, sharedDependencies } = params;
1402
1571
  const domainDistance = file1Domain === file2Domain ? 0 : file1Domain && file2Domain ? 0.5 : 0.8;
1403
- const importOverlap = sharedDependencies.length / Math.max(1, Math.min(params.file1Imports.length, params.file2Imports.length));
1572
+ const importOverlap = sharedDependencies.length / Math.max(
1573
+ 1,
1574
+ Math.min(params.file1Imports.length, params.file2Imports.length)
1575
+ );
1404
1576
  const importDistance = 1 - importOverlap;
1405
1577
  const distance = domainDistance * 0.4 + importDistance * 0.3 + (sharedDependencies.length > 0 ? 0 : 0.3);
1406
1578
  let relationship;
@@ -1408,7 +1580,9 @@ function calculateSemanticDistance(params) {
1408
1580
  else if (file1Domain === file2Domain) relationship = "same-domain";
1409
1581
  else if (sharedDependencies.length > 0) relationship = "cross-domain";
1410
1582
  else relationship = "unrelated";
1411
- const pathItems = [file1Domain, ...sharedDependencies, file2Domain].filter((s) => typeof s === "string" && s.length > 0);
1583
+ const pathItems = [file1Domain, ...sharedDependencies, file2Domain].filter(
1584
+ (s) => typeof s === "string" && s.length > 0
1585
+ );
1412
1586
  return {
1413
1587
  between: [file1, file2],
1414
1588
  distance: Math.round(distance * 100) / 100,
@@ -1423,7 +1597,11 @@ function calculatePatternEntropy(files) {
1423
1597
  domain: "unknown",
1424
1598
  entropy: 0,
1425
1599
  rating: "crystalline",
1426
- distribution: { locationCount: 0, dominantLocation: "", giniCoefficient: 0 },
1600
+ distribution: {
1601
+ locationCount: 0,
1602
+ dominantLocation: "",
1603
+ giniCoefficient: 0
1604
+ },
1427
1605
  recommendations: ["No files to analyze"]
1428
1606
  };
1429
1607
  }
@@ -1463,10 +1641,14 @@ function calculatePatternEntropy(files) {
1463
1641
  else rating = "chaotic";
1464
1642
  const recommendations = [];
1465
1643
  if (normalizedEntropy > 0.5) {
1466
- recommendations.push(`Consolidate ${files.length} files into fewer directories by domain`);
1644
+ recommendations.push(
1645
+ `Consolidate ${files.length} files into fewer directories by domain`
1646
+ );
1467
1647
  }
1468
1648
  if (dirGroups.size > 5) {
1469
- recommendations.push("Consider barrel exports to reduce directory navigation");
1649
+ recommendations.push(
1650
+ "Consider barrel exports to reduce directory navigation"
1651
+ );
1470
1652
  }
1471
1653
  if (gini > 0.5) {
1472
1654
  recommendations.push("Redistribute files more evenly across directories");
@@ -1491,7 +1673,11 @@ function calculateConceptCohesion(params) {
1491
1673
  return {
1492
1674
  score: 1,
1493
1675
  rating: "excellent",
1494
- analysis: { uniqueDomains: 0, domainConcentration: 0, exportPurposeClarity: 1 }
1676
+ analysis: {
1677
+ uniqueDomains: 0,
1678
+ domainConcentration: 0,
1679
+ exportPurposeClarity: 1
1680
+ }
1495
1681
  };
1496
1682
  }
1497
1683
  const allDomains = [];
@@ -1597,7 +1783,10 @@ function calculateAiSignalClarity(params) {
1597
1783
  recommendations: []
1598
1784
  };
1599
1785
  }
1600
- const overloadRatio = Math.min(1, overloadedSymbols / Math.max(1, totalSymbols));
1786
+ const overloadRatio = Math.min(
1787
+ 1,
1788
+ overloadedSymbols / Math.max(1, totalSymbols)
1789
+ );
1601
1790
  const overloadSignal = {
1602
1791
  name: "Symbol Overloading",
1603
1792
  count: overloadedSymbols,
@@ -1621,7 +1810,10 @@ function calculateAiSignalClarity(params) {
1621
1810
  // 20% weight
1622
1811
  description: `${booleanTraps} boolean trap parameters \u2014 AI inverts intent`
1623
1812
  };
1624
- const sideEffectRatio = Math.min(1, implicitSideEffects / Math.max(1, totalExports));
1813
+ const sideEffectRatio = Math.min(
1814
+ 1,
1815
+ implicitSideEffects / Math.max(1, totalExports)
1816
+ );
1625
1817
  const sideEffectSignal = {
1626
1818
  name: "Implicit Side Effects",
1627
1819
  count: implicitSideEffects,
@@ -1629,7 +1821,10 @@ function calculateAiSignalClarity(params) {
1629
1821
  // 15% weight
1630
1822
  description: `${implicitSideEffects} functions with implicit side effects \u2014 AI misses contracts`
1631
1823
  };
1632
- const callbackRatio = Math.min(1, deepCallbacks / Math.max(1, totalSymbols * 0.1));
1824
+ const callbackRatio = Math.min(
1825
+ 1,
1826
+ deepCallbacks / Math.max(1, totalSymbols * 0.1)
1827
+ );
1633
1828
  const callbackSignal = {
1634
1829
  name: "Callback Nesting",
1635
1830
  count: deepCallbacks,
@@ -1637,7 +1832,10 @@ function calculateAiSignalClarity(params) {
1637
1832
  // 10% weight
1638
1833
  description: `${deepCallbacks} deep callback chains \u2014 AI loses control flow context`
1639
1834
  };
1640
- const ambiguousRatio = Math.min(1, ambiguousNames / Math.max(1, totalSymbols));
1835
+ const ambiguousRatio = Math.min(
1836
+ 1,
1837
+ ambiguousNames / Math.max(1, totalSymbols)
1838
+ );
1641
1839
  const ambiguousSignal = {
1642
1840
  name: "Ambiguous Names",
1643
1841
  count: ambiguousNames,
@@ -1645,7 +1843,10 @@ function calculateAiSignalClarity(params) {
1645
1843
  // 10% weight
1646
1844
  description: `${ambiguousNames} non-descriptive identifiers \u2014 AI guesses wrong intent`
1647
1845
  };
1648
- const undocRatio = Math.min(1, undocumentedExports / Math.max(1, totalExports));
1846
+ const undocRatio = Math.min(
1847
+ 1,
1848
+ undocumentedExports / Math.max(1, totalExports)
1849
+ );
1649
1850
  const undocSignal = {
1650
1851
  name: "Undocumented Exports",
1651
1852
  count: undocumentedExports,
@@ -1662,30 +1863,45 @@ function calculateAiSignalClarity(params) {
1662
1863
  ambiguousSignal,
1663
1864
  undocSignal
1664
1865
  ];
1665
- const score = Math.min(100, signals.reduce((sum, s) => sum + s.riskContribution, 0));
1866
+ const score = Math.min(
1867
+ 100,
1868
+ signals.reduce((sum, s) => sum + s.riskContribution, 0)
1869
+ );
1666
1870
  let rating;
1667
1871
  if (score < 10) rating = "minimal";
1668
1872
  else if (score < 25) rating = "low";
1669
1873
  else if (score < 50) rating = "moderate";
1670
1874
  else if (score < 75) rating = "high";
1671
1875
  else rating = "severe";
1672
- const topSignal = signals.reduce((a, b) => a.riskContribution > b.riskContribution ? a : b);
1876
+ const topSignal = signals.reduce(
1877
+ (a, b) => a.riskContribution > b.riskContribution ? a : b
1878
+ );
1673
1879
  const topRisk = topSignal.riskContribution > 0 ? topSignal.description : "No significant AI signal claritys detected";
1674
1880
  const recommendations = [];
1675
1881
  if (overloadSignal.riskContribution > 5) {
1676
- recommendations.push(`Rename ${overloadedSymbols} overloaded symbols to unique, intent-revealing names`);
1882
+ recommendations.push(
1883
+ `Rename ${overloadedSymbols} overloaded symbols to unique, intent-revealing names`
1884
+ );
1677
1885
  }
1678
1886
  if (magicSignal.riskContribution > 5) {
1679
- recommendations.push(`Extract ${magicLiterals} magic literals into named constants`);
1887
+ recommendations.push(
1888
+ `Extract ${magicLiterals} magic literals into named constants`
1889
+ );
1680
1890
  }
1681
1891
  if (trapSignal.riskContribution > 5) {
1682
- recommendations.push(`Replace ${booleanTraps} boolean traps with named options objects`);
1892
+ recommendations.push(
1893
+ `Replace ${booleanTraps} boolean traps with named options objects`
1894
+ );
1683
1895
  }
1684
1896
  if (undocSignal.riskContribution > 5) {
1685
- recommendations.push(`Add JSDoc/docstrings to ${undocumentedExports} undocumented public functions`);
1897
+ recommendations.push(
1898
+ `Add JSDoc/docstrings to ${undocumentedExports} undocumented public functions`
1899
+ );
1686
1900
  }
1687
1901
  if (sideEffectSignal.riskContribution > 5) {
1688
- recommendations.push("Mark functions with side effects explicitly in their names or docs");
1902
+ recommendations.push(
1903
+ "Mark functions with side effects explicitly in their names or docs"
1904
+ );
1689
1905
  }
1690
1906
  return {
1691
1907
  score: Math.round(score),
@@ -1710,7 +1926,10 @@ function calculateAgentGrounding(params) {
1710
1926
  domainVocabularySize
1711
1927
  } = params;
1712
1928
  const deepDirRatio = totalDirectories > 0 ? deepDirectories / totalDirectories : 0;
1713
- const structureClarityScore = Math.max(0, Math.round(100 - deepDirRatio * 80));
1929
+ const structureClarityScore = Math.max(
1930
+ 0,
1931
+ Math.round(100 - deepDirRatio * 80)
1932
+ );
1714
1933
  const vagueRatio = totalFiles > 0 ? vagueFileNames / totalFiles : 0;
1715
1934
  const selfDocumentationScore = Math.max(0, Math.round(100 - vagueRatio * 90));
1716
1935
  let entryPointScore = 60;
@@ -1722,7 +1941,10 @@ function calculateAgentGrounding(params) {
1722
1941
  const untypedRatio = totalExports > 0 ? untypedExports / totalExports : 0;
1723
1942
  const apiClarityScore = Math.max(0, Math.round(100 - untypedRatio * 70));
1724
1943
  const inconsistencyRatio = domainVocabularySize > 0 ? inconsistentDomainTerms / domainVocabularySize : 0;
1725
- const domainConsistencyScore = Math.max(0, Math.round(100 - inconsistencyRatio * 80));
1944
+ const domainConsistencyScore = Math.max(
1945
+ 0,
1946
+ Math.round(100 - inconsistencyRatio * 80)
1947
+ );
1726
1948
  const score = Math.round(
1727
1949
  structureClarityScore * 0.2 + selfDocumentationScore * 0.25 + entryPointScore * 0.2 + apiClarityScore * 0.15 + domainConsistencyScore * 0.2
1728
1950
  );
@@ -1734,21 +1956,33 @@ function calculateAgentGrounding(params) {
1734
1956
  else rating = "disorienting";
1735
1957
  const recommendations = [];
1736
1958
  if (structureClarityScore < 70) {
1737
- recommendations.push(`Flatten ${deepDirectories} overly-deep directories to improve agent navigation`);
1959
+ recommendations.push(
1960
+ `Flatten ${deepDirectories} overly-deep directories to improve agent navigation`
1961
+ );
1738
1962
  }
1739
1963
  if (selfDocumentationScore < 70) {
1740
- recommendations.push(`Rename ${vagueFileNames} vague files (utils, helpers, misc) to domain-specific names`);
1964
+ recommendations.push(
1965
+ `Rename ${vagueFileNames} vague files (utils, helpers, misc) to domain-specific names`
1966
+ );
1741
1967
  }
1742
1968
  if (!hasRootReadme) {
1743
- recommendations.push("Add a root README.md so agents understand the project context immediately");
1969
+ recommendations.push(
1970
+ "Add a root README.md so agents understand the project context immediately"
1971
+ );
1744
1972
  } else if (!readmeIsFresh) {
1745
- recommendations.push("Update README.md \u2014 stale entry-point documentation disorients agents");
1973
+ recommendations.push(
1974
+ "Update README.md \u2014 stale entry-point documentation disorients agents"
1975
+ );
1746
1976
  }
1747
1977
  if (apiClarityScore < 70) {
1748
- recommendations.push(`Add TypeScript types to ${untypedExports} untyped exports to improve API discoverability`);
1978
+ recommendations.push(
1979
+ `Add TypeScript types to ${untypedExports} untyped exports to improve API discoverability`
1980
+ );
1749
1981
  }
1750
1982
  if (domainConsistencyScore < 70) {
1751
- recommendations.push(`Unify ${inconsistentDomainTerms} inconsistent domain terms \u2014 agents need one word per concept`);
1983
+ recommendations.push(
1984
+ `Unify ${inconsistentDomainTerms} inconsistent domain terms \u2014 agents need one word per concept`
1985
+ );
1752
1986
  }
1753
1987
  return {
1754
1988
  score,
@@ -1781,7 +2015,9 @@ function calculateTestabilityIndex(params) {
1781
2015
  const purityRatio = totalFunctions > 0 ? pureFunctions / totalFunctions : 0.5;
1782
2016
  const purityScore = Math.round(purityRatio * 100);
1783
2017
  const injectionRatio = totalClasses > 0 ? injectionPatterns / totalClasses : 0.5;
1784
- const dependencyInjectionScore = Math.round(Math.min(100, injectionRatio * 100));
2018
+ const dependencyInjectionScore = Math.round(
2019
+ Math.min(100, injectionRatio * 100)
2020
+ );
1785
2021
  const bloatedRatio = totalInterfaces > 0 ? bloatedInterfaces / totalInterfaces : 0;
1786
2022
  const interfaceFocusScore = Math.max(0, Math.round(100 - bloatedRatio * 80));
1787
2023
  const mutationRatio = totalFunctions > 0 ? externalStateMutations / totalFunctions : 0;
@@ -1797,25 +2033,36 @@ function calculateTestabilityIndex(params) {
1797
2033
  else rating = "unverifiable";
1798
2034
  let aiChangeSafetyRating;
1799
2035
  if (rawCoverageRatio >= 0.5 && score >= 70) aiChangeSafetyRating = "safe";
1800
- else if (rawCoverageRatio >= 0.2 && score >= 50) aiChangeSafetyRating = "moderate-risk";
2036
+ else if (rawCoverageRatio >= 0.2 && score >= 50)
2037
+ aiChangeSafetyRating = "moderate-risk";
1801
2038
  else if (rawCoverageRatio > 0) aiChangeSafetyRating = "high-risk";
1802
2039
  else aiChangeSafetyRating = "blind-risk";
1803
2040
  const recommendations = [];
1804
2041
  if (!hasTestFramework) {
1805
- recommendations.push("Add a testing framework (Jest, Vitest, pytest) \u2014 AI changes cannot be verified without tests");
2042
+ recommendations.push(
2043
+ "Add a testing framework (Jest, Vitest, pytest) \u2014 AI changes cannot be verified without tests"
2044
+ );
1806
2045
  }
1807
2046
  if (rawCoverageRatio < 0.3) {
1808
2047
  const neededTests = Math.round(sourceFiles * 0.3 - testFiles);
1809
- recommendations.push(`Add ~${neededTests} test files to reach 30% coverage ratio \u2014 minimum for safe AI assistance`);
2048
+ recommendations.push(
2049
+ `Add ~${neededTests} test files to reach 30% coverage ratio \u2014 minimum for safe AI assistance`
2050
+ );
1810
2051
  }
1811
2052
  if (purityScore < 50) {
1812
- recommendations.push("Extract pure functions from side-effectful code \u2014 pure functions are trivially AI-testable");
2053
+ recommendations.push(
2054
+ "Extract pure functions from side-effectful code \u2014 pure functions are trivially AI-testable"
2055
+ );
1813
2056
  }
1814
2057
  if (dependencyInjectionScore < 50 && totalClasses > 0) {
1815
- recommendations.push("Adopt dependency injection \u2014 makes classes mockable and AI-generated code verifiable");
2058
+ recommendations.push(
2059
+ "Adopt dependency injection \u2014 makes classes mockable and AI-generated code verifiable"
2060
+ );
1816
2061
  }
1817
2062
  if (externalStateMutations > totalFunctions * 0.3) {
1818
- recommendations.push("Reduce direct state mutations \u2014 return values instead to improve observability");
2063
+ recommendations.push(
2064
+ "Reduce direct state mutations \u2014 return values instead to improve observability"
2065
+ );
1819
2066
  }
1820
2067
  return {
1821
2068
  score,
@@ -1832,7 +2079,12 @@ function calculateTestabilityIndex(params) {
1832
2079
  };
1833
2080
  }
1834
2081
  function calculateDocDrift(params) {
1835
- const { uncommentedExports, totalExports, outdatedComments, undocumentedComplexity } = params;
2082
+ const {
2083
+ uncommentedExports,
2084
+ totalExports,
2085
+ outdatedComments,
2086
+ undocumentedComplexity
2087
+ } = params;
1836
2088
  const uncommentedRatio = totalExports > 0 ? uncommentedExports / totalExports : 0;
1837
2089
  const outdatedScore = Math.min(100, outdatedComments * 15);
1838
2090
  const uncommentedScore = Math.min(100, uncommentedRatio * 100);
@@ -1849,13 +2101,19 @@ function calculateDocDrift(params) {
1849
2101
  else rating = "severe";
1850
2102
  const recommendations = [];
1851
2103
  if (outdatedComments > 0) {
1852
- recommendations.push(`Update or remove ${outdatedComments} outdated comments that contradict the code.`);
2104
+ recommendations.push(
2105
+ `Update or remove ${outdatedComments} outdated comments that contradict the code.`
2106
+ );
1853
2107
  }
1854
2108
  if (uncommentedRatio > 0.3) {
1855
- recommendations.push(`Add JSDoc to ${uncommentedExports} uncommented exports.`);
2109
+ recommendations.push(
2110
+ `Add JSDoc to ${uncommentedExports} uncommented exports.`
2111
+ );
1856
2112
  }
1857
2113
  if (undocumentedComplexity > 0) {
1858
- recommendations.push(`Explain the business logic for ${undocumentedComplexity} highly complex functions.`);
2114
+ recommendations.push(
2115
+ `Explain the business logic for ${undocumentedComplexity} highly complex functions.`
2116
+ );
1859
2117
  }
1860
2118
  return {
1861
2119
  score: finalScore,
@@ -1869,7 +2127,12 @@ function calculateDocDrift(params) {
1869
2127
  };
1870
2128
  }
1871
2129
  function calculateDependencyHealth(params) {
1872
- const { totalPackages, outdatedPackages, deprecatedPackages, trainingCutoffSkew } = params;
2130
+ const {
2131
+ totalPackages,
2132
+ outdatedPackages,
2133
+ deprecatedPackages,
2134
+ trainingCutoffSkew
2135
+ } = params;
1873
2136
  const outdatedRatio = totalPackages > 0 ? outdatedPackages / totalPackages : 0;
1874
2137
  const deprecatedRatio = totalPackages > 0 ? deprecatedPackages / totalPackages : 0;
1875
2138
  const outdatedScore = Math.max(0, 100 - outdatedRatio * 200);
@@ -1884,19 +2147,27 @@ function calculateDependencyHealth(params) {
1884
2147
  else if (score >= 30) rating = "poor";
1885
2148
  else rating = "hazardous";
1886
2149
  let aiKnowledgeConfidence;
1887
- if (trainingCutoffSkew < 0.2 && deprecatedPackages === 0) aiKnowledgeConfidence = "high";
1888
- else if (trainingCutoffSkew < 0.5 && deprecatedPackages <= 2) aiKnowledgeConfidence = "moderate";
2150
+ if (trainingCutoffSkew < 0.2 && deprecatedPackages === 0)
2151
+ aiKnowledgeConfidence = "high";
2152
+ else if (trainingCutoffSkew < 0.5 && deprecatedPackages <= 2)
2153
+ aiKnowledgeConfidence = "moderate";
1889
2154
  else if (trainingCutoffSkew < 0.8) aiKnowledgeConfidence = "low";
1890
2155
  else aiKnowledgeConfidence = "blind";
1891
2156
  const recommendations = [];
1892
2157
  if (deprecatedPackages > 0) {
1893
- recommendations.push(`Replace ${deprecatedPackages} deprecated packages, as AI will struggle to find modern solutions.`);
2158
+ recommendations.push(
2159
+ `Replace ${deprecatedPackages} deprecated packages, as AI will struggle to find modern solutions.`
2160
+ );
1894
2161
  }
1895
2162
  if (outdatedRatio > 0.2) {
1896
- recommendations.push(`Update ${outdatedPackages} outdated packages to keep APIs aligned with AI training data.`);
2163
+ recommendations.push(
2164
+ `Update ${outdatedPackages} outdated packages to keep APIs aligned with AI training data.`
2165
+ );
1897
2166
  }
1898
2167
  if (trainingCutoffSkew > 0.5) {
1899
- recommendations.push("High training cutoff skew detected. AI may hallucinate APIs that were introduced recently.");
2168
+ recommendations.push(
2169
+ "High training cutoff skew detected. AI may hallucinate APIs that were introduced recently."
2170
+ );
1900
2171
  }
1901
2172
  return {
1902
2173
  score,
@@ -1937,10 +2208,14 @@ function calculateChangeAmplification(params) {
1937
2208
  else if (score < 90) rating = "contained";
1938
2209
  const recommendations = [];
1939
2210
  if (score < 70 && hotspots.length > 0) {
1940
- recommendations.push(`Refactor top hotspot '${hotspots[0].file}' to reduce coupling (fan-out: ${hotspots[0].fanOut}, fan-in: ${hotspots[0].fanIn}).`);
2211
+ recommendations.push(
2212
+ `Refactor top hotspot '${hotspots[0].file}' to reduce coupling (fan-out: ${hotspots[0].fanOut}, fan-in: ${hotspots[0].fanIn}).`
2213
+ );
1941
2214
  }
1942
2215
  if (maxAmplification > 30) {
1943
- recommendations.push(`Break down key bottlenecks with amplification factor > 30.`);
2216
+ recommendations.push(
2217
+ `Break down key bottlenecks with amplification factor > 30.`
2218
+ );
1944
2219
  }
1945
2220
  return {
1946
2221
  score: Math.round(score),
@@ -2022,7 +2297,11 @@ function calculateExtendedFutureProofScore(params) {
2022
2297
  recommendations.push({ action: rec, estimatedImpact: 8, priority: "high" });
2023
2298
  }
2024
2299
  for (const rec of params.agentGrounding.recommendations) {
2025
- recommendations.push({ action: rec, estimatedImpact: 6, priority: "medium" });
2300
+ recommendations.push({
2301
+ action: rec,
2302
+ estimatedImpact: 6,
2303
+ priority: "medium"
2304
+ });
2026
2305
  }
2027
2306
  for (const rec of params.testability.recommendations) {
2028
2307
  const priority = params.testability.aiChangeSafetyRating === "blind-risk" ? "high" : "medium";
@@ -2033,12 +2312,20 @@ function calculateExtendedFutureProofScore(params) {
2033
2312
  }
2034
2313
  if (params.docDrift) {
2035
2314
  for (const rec of params.docDrift.recommendations) {
2036
- recommendations.push({ action: rec, estimatedImpact: 8, priority: "high" });
2315
+ recommendations.push({
2316
+ action: rec,
2317
+ estimatedImpact: 8,
2318
+ priority: "high"
2319
+ });
2037
2320
  }
2038
2321
  }
2039
2322
  if (params.dependencyHealth) {
2040
2323
  for (const rec of params.dependencyHealth.recommendations) {
2041
- recommendations.push({ action: rec, estimatedImpact: 7, priority: "medium" });
2324
+ recommendations.push({
2325
+ action: rec,
2326
+ estimatedImpact: 7,
2327
+ priority: "medium"
2328
+ });
2042
2329
  }
2043
2330
  }
2044
2331
  const semanticDistanceAvg = params.semanticDistances && params.semanticDistances.length > 0 ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
@@ -2063,7 +2350,7 @@ function calculateExtendedFutureProofScore(params) {
2063
2350
 
2064
2351
  // src/utils/history.ts
2065
2352
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
2066
- import { join as join4, dirname as dirname3 } from "path";
2353
+ import { join as join4, dirname as dirname4 } from "path";
2067
2354
  function getHistoryPath(rootDir) {
2068
2355
  return join4(rootDir, ".aiready", "history.json");
2069
2356
  }
@@ -2082,7 +2369,7 @@ function loadScoreHistory(rootDir) {
2082
2369
  }
2083
2370
  function saveScoreEntry(rootDir, entry) {
2084
2371
  const historyPath = getHistoryPath(rootDir);
2085
- const historyDir = dirname3(historyPath);
2372
+ const historyDir = dirname4(historyPath);
2086
2373
  if (!existsSync4(historyDir)) {
2087
2374
  mkdirSync2(historyDir, { recursive: true });
2088
2375
  }
@@ -2134,6 +2421,39 @@ function clearHistory(rootDir) {
2134
2421
  writeFileSync2(historyPath, JSON.stringify([]));
2135
2422
  }
2136
2423
  }
2424
+
2425
+ // src/utils/history-git.ts
2426
+ import { execSync } from "child_process";
2427
+ function getFileCommitTimestamps(file) {
2428
+ const lineStamps = {};
2429
+ try {
2430
+ const output = execSync(`git blame -t "${file}"`, {
2431
+ encoding: "utf-8",
2432
+ stdio: ["ignore", "pipe", "ignore"]
2433
+ });
2434
+ const lines = output.split("\n");
2435
+ for (const line of lines) {
2436
+ if (!line) continue;
2437
+ const match = line.match(/^\S+\s+\(.*?(\d{10,})\s+[-+]\d+\s+(\d+)\)/);
2438
+ if (match) {
2439
+ const ts = parseInt(match[1], 10);
2440
+ const ln = parseInt(match[2], 10);
2441
+ lineStamps[ln] = ts;
2442
+ }
2443
+ }
2444
+ } catch {
2445
+ }
2446
+ return lineStamps;
2447
+ }
2448
+ function getLineRangeLastModifiedCached(lineStamps, startLine, endLine) {
2449
+ let latest = 0;
2450
+ for (let i = startLine; i <= endLine; i++) {
2451
+ if (lineStamps[i] && lineStamps[i] > latest) {
2452
+ latest = lineStamps[i];
2453
+ }
2454
+ }
2455
+ return latest;
2456
+ }
2137
2457
  export {
2138
2458
  CONTEXT_TIER_THRESHOLDS,
2139
2459
  DEFAULT_COST_CONFIG,
@@ -2148,6 +2468,7 @@ export {
2148
2468
  SIZE_ADJUSTED_THRESHOLDS,
2149
2469
  TOOL_NAME_MAP,
2150
2470
  TypeScriptParser,
2471
+ VAGUE_FILE_NAMES,
2151
2472
  calculateAgentGrounding,
2152
2473
  calculateAiSignalClarity,
2153
2474
  calculateChangeAmplification,
@@ -2182,8 +2503,10 @@ export {
2182
2503
  generateHTML,
2183
2504
  getDebtBreakdown,
2184
2505
  getElapsedTime,
2506
+ getFileCommitTimestamps,
2185
2507
  getFileExtension,
2186
2508
  getHistorySummary,
2509
+ getLineRangeLastModifiedCached,
2187
2510
  getModelPreset,
2188
2511
  getParser,
2189
2512
  getProjectSizeTier,
@@ -2209,5 +2532,6 @@ export {
2209
2532
  readFileContent,
2210
2533
  resolveOutputPath,
2211
2534
  saveScoreEntry,
2535
+ scanEntries,
2212
2536
  scanFiles
2213
2537
  };