@aiready/core 0.7.9 → 0.7.11

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.d.mts CHANGED
@@ -34,9 +34,12 @@ interface AIReadyConfig {
34
34
  scan?: {
35
35
  include?: string[];
36
36
  exclude?: string[];
37
+ tools?: string[];
37
38
  };
38
39
  tools?: {
39
40
  'pattern-detect'?: {
41
+ enabled?: boolean;
42
+ scoreWeight?: number;
40
43
  minSimilarity?: number;
41
44
  minLines?: number;
42
45
  batchSize?: number;
@@ -47,6 +50,8 @@ interface AIReadyConfig {
47
50
  maxResults?: number;
48
51
  };
49
52
  'context-analyzer'?: {
53
+ enabled?: boolean;
54
+ scoreWeight?: number;
50
55
  maxDepth?: number;
51
56
  maxContextBudget?: number;
52
57
  minCohesion?: number;
@@ -59,10 +64,23 @@ interface AIReadyConfig {
59
64
  pathDomainMap?: Record<string, string>;
60
65
  };
61
66
  'consistency'?: {
67
+ enabled?: boolean;
68
+ scoreWeight?: number;
62
69
  acceptedAbbreviations?: string[];
63
70
  shortWords?: string[];
64
71
  disableChecks?: ('single-letter' | 'abbreviation' | 'convention-mix' | 'unclear' | 'poor-naming')[];
65
72
  };
73
+ [toolName: string]: {
74
+ enabled?: boolean;
75
+ scoreWeight?: number;
76
+ [key: string]: any;
77
+ } | undefined;
78
+ };
79
+ scoring?: {
80
+ threshold?: number;
81
+ showBreakdown?: boolean;
82
+ compareBaseline?: string;
83
+ saveTo?: string;
66
84
  };
67
85
  output?: {
68
86
  format?: 'console' | 'json' | 'html';
@@ -196,4 +214,114 @@ declare function handleCLIError(error: unknown, commandName: string): never;
196
214
  */
197
215
  declare function getElapsedTime(startTime: number): string;
198
216
 
199
- export { type AIReadyConfig, type ASTNode, type AnalysisResult, type CLIOptions, DEFAULT_EXCLUDE, type ExportWithImports, type FileImport, type Issue, type IssueType, type Location, type Metrics, type Report, type ScanOptions, calculateImportSimilarity, estimateTokens, extractFunctions, extractImports, getElapsedTime, getFileExtension, handleCLIError, handleJSONOutput, isSourceFile, loadConfig, loadMergedConfig, mergeConfigWithDefaults, parseCode, parseFileExports, readFileContent, resolveOutputPath, scanFiles };
217
+ /**
218
+ * AI Readiness Scoring System
219
+ *
220
+ * Provides dynamic, composable scoring across multiple analysis tools.
221
+ * Each tool contributes a 0-100 score with configurable weights.
222
+ */
223
+ interface ToolScoringOutput {
224
+ /** Unique tool identifier (e.g., "pattern-detect") */
225
+ toolName: string;
226
+ /** Normalized 0-100 score for this tool */
227
+ score: number;
228
+ /** Raw metrics used to calculate the score */
229
+ rawMetrics: Record<string, any>;
230
+ /** Factors that influenced the score */
231
+ factors: Array<{
232
+ name: string;
233
+ impact: number;
234
+ description: string;
235
+ }>;
236
+ /** Actionable recommendations with estimated impact */
237
+ recommendations: Array<{
238
+ action: string;
239
+ estimatedImpact: number;
240
+ priority: 'high' | 'medium' | 'low';
241
+ }>;
242
+ }
243
+ interface ScoringResult {
244
+ /** Overall AI Readiness Score (0-100) */
245
+ overall: number;
246
+ /** Rating category */
247
+ rating: 'Excellent' | 'Good' | 'Fair' | 'Needs Work' | 'Critical';
248
+ /** Timestamp of score calculation */
249
+ timestamp: string;
250
+ /** Tools that contributed to this score */
251
+ toolsUsed: string[];
252
+ /** Breakdown by tool */
253
+ breakdown: ToolScoringOutput[];
254
+ /** Calculation details */
255
+ calculation: {
256
+ formula: string;
257
+ weights: Record<string, number>;
258
+ normalized: string;
259
+ };
260
+ }
261
+ interface ScoringConfig {
262
+ /** Minimum passing score (exit code 1 if below) */
263
+ threshold?: number;
264
+ /** Show detailed breakdown in output */
265
+ showBreakdown?: boolean;
266
+ /** Path to baseline JSON for comparison */
267
+ compareBaseline?: string;
268
+ /** Auto-save score to this path */
269
+ saveTo?: string;
270
+ }
271
+ /**
272
+ * Default weights for known tools.
273
+ * New tools get weight of 10 if not specified.
274
+ */
275
+ declare const DEFAULT_TOOL_WEIGHTS: Record<string, number>;
276
+ /**
277
+ * Tool name normalization map (shorthand -> full name)
278
+ */
279
+ declare const TOOL_NAME_MAP: Record<string, string>;
280
+ /**
281
+ * Normalize tool name from shorthand to full name
282
+ */
283
+ declare function normalizeToolName(shortName: string): string;
284
+ /**
285
+ * Get tool weight with fallback priority:
286
+ * 1. CLI override
287
+ * 2. Tool config scoreWeight
288
+ * 3. Default weight
289
+ * 4. 10 (for unknown tools)
290
+ */
291
+ declare function getToolWeight(toolName: string, toolConfig?: {
292
+ scoreWeight?: number;
293
+ }, cliOverride?: number): number;
294
+ /**
295
+ * Parse weight string from CLI (e.g., "patterns:50,context:30")
296
+ */
297
+ declare function parseWeightString(weightStr?: string): Map<string, number>;
298
+ /**
299
+ * Calculate overall AI Readiness Score from multiple tool scores.
300
+ *
301
+ * Formula: Σ(tool_score × tool_weight) / Σ(active_tool_weights)
302
+ *
303
+ * This allows dynamic composition - score adjusts automatically
304
+ * based on which tools actually ran.
305
+ */
306
+ declare function calculateOverallScore(toolOutputs: Map<string, ToolScoringOutput>, config?: any, cliWeights?: Map<string, number>): ScoringResult;
307
+ /**
308
+ * Convert numeric score to rating category
309
+ */
310
+ declare function getRating(score: number): ScoringResult['rating'];
311
+ /**
312
+ * Get rating emoji and color for display
313
+ */
314
+ declare function getRatingDisplay(rating: ScoringResult['rating']): {
315
+ emoji: string;
316
+ color: string;
317
+ };
318
+ /**
319
+ * Format score for display with rating
320
+ */
321
+ declare function formatScore(result: ScoringResult): string;
322
+ /**
323
+ * Format individual tool score for display
324
+ */
325
+ declare function formatToolScore(output: ToolScoringOutput): string;
326
+
327
+ export { type AIReadyConfig, type ASTNode, type AnalysisResult, type CLIOptions, DEFAULT_EXCLUDE, DEFAULT_TOOL_WEIGHTS, type ExportWithImports, type FileImport, type Issue, type IssueType, type Location, type Metrics, type Report, type ScanOptions, type ScoringConfig, type ScoringResult, TOOL_NAME_MAP, type ToolScoringOutput, calculateImportSimilarity, calculateOverallScore, estimateTokens, extractFunctions, extractImports, formatScore, formatToolScore, getElapsedTime, getFileExtension, getRating, getRatingDisplay, getToolWeight, handleCLIError, handleJSONOutput, isSourceFile, loadConfig, loadMergedConfig, mergeConfigWithDefaults, normalizeToolName, parseCode, parseFileExports, parseWeightString, readFileContent, resolveOutputPath, scanFiles };
package/dist/index.d.ts CHANGED
@@ -34,9 +34,12 @@ interface AIReadyConfig {
34
34
  scan?: {
35
35
  include?: string[];
36
36
  exclude?: string[];
37
+ tools?: string[];
37
38
  };
38
39
  tools?: {
39
40
  'pattern-detect'?: {
41
+ enabled?: boolean;
42
+ scoreWeight?: number;
40
43
  minSimilarity?: number;
41
44
  minLines?: number;
42
45
  batchSize?: number;
@@ -47,6 +50,8 @@ interface AIReadyConfig {
47
50
  maxResults?: number;
48
51
  };
49
52
  'context-analyzer'?: {
53
+ enabled?: boolean;
54
+ scoreWeight?: number;
50
55
  maxDepth?: number;
51
56
  maxContextBudget?: number;
52
57
  minCohesion?: number;
@@ -59,10 +64,23 @@ interface AIReadyConfig {
59
64
  pathDomainMap?: Record<string, string>;
60
65
  };
61
66
  'consistency'?: {
67
+ enabled?: boolean;
68
+ scoreWeight?: number;
62
69
  acceptedAbbreviations?: string[];
63
70
  shortWords?: string[];
64
71
  disableChecks?: ('single-letter' | 'abbreviation' | 'convention-mix' | 'unclear' | 'poor-naming')[];
65
72
  };
73
+ [toolName: string]: {
74
+ enabled?: boolean;
75
+ scoreWeight?: number;
76
+ [key: string]: any;
77
+ } | undefined;
78
+ };
79
+ scoring?: {
80
+ threshold?: number;
81
+ showBreakdown?: boolean;
82
+ compareBaseline?: string;
83
+ saveTo?: string;
66
84
  };
67
85
  output?: {
68
86
  format?: 'console' | 'json' | 'html';
@@ -196,4 +214,114 @@ declare function handleCLIError(error: unknown, commandName: string): never;
196
214
  */
197
215
  declare function getElapsedTime(startTime: number): string;
198
216
 
199
- export { type AIReadyConfig, type ASTNode, type AnalysisResult, type CLIOptions, DEFAULT_EXCLUDE, type ExportWithImports, type FileImport, type Issue, type IssueType, type Location, type Metrics, type Report, type ScanOptions, calculateImportSimilarity, estimateTokens, extractFunctions, extractImports, getElapsedTime, getFileExtension, handleCLIError, handleJSONOutput, isSourceFile, loadConfig, loadMergedConfig, mergeConfigWithDefaults, parseCode, parseFileExports, readFileContent, resolveOutputPath, scanFiles };
217
+ /**
218
+ * AI Readiness Scoring System
219
+ *
220
+ * Provides dynamic, composable scoring across multiple analysis tools.
221
+ * Each tool contributes a 0-100 score with configurable weights.
222
+ */
223
+ interface ToolScoringOutput {
224
+ /** Unique tool identifier (e.g., "pattern-detect") */
225
+ toolName: string;
226
+ /** Normalized 0-100 score for this tool */
227
+ score: number;
228
+ /** Raw metrics used to calculate the score */
229
+ rawMetrics: Record<string, any>;
230
+ /** Factors that influenced the score */
231
+ factors: Array<{
232
+ name: string;
233
+ impact: number;
234
+ description: string;
235
+ }>;
236
+ /** Actionable recommendations with estimated impact */
237
+ recommendations: Array<{
238
+ action: string;
239
+ estimatedImpact: number;
240
+ priority: 'high' | 'medium' | 'low';
241
+ }>;
242
+ }
243
+ interface ScoringResult {
244
+ /** Overall AI Readiness Score (0-100) */
245
+ overall: number;
246
+ /** Rating category */
247
+ rating: 'Excellent' | 'Good' | 'Fair' | 'Needs Work' | 'Critical';
248
+ /** Timestamp of score calculation */
249
+ timestamp: string;
250
+ /** Tools that contributed to this score */
251
+ toolsUsed: string[];
252
+ /** Breakdown by tool */
253
+ breakdown: ToolScoringOutput[];
254
+ /** Calculation details */
255
+ calculation: {
256
+ formula: string;
257
+ weights: Record<string, number>;
258
+ normalized: string;
259
+ };
260
+ }
261
+ interface ScoringConfig {
262
+ /** Minimum passing score (exit code 1 if below) */
263
+ threshold?: number;
264
+ /** Show detailed breakdown in output */
265
+ showBreakdown?: boolean;
266
+ /** Path to baseline JSON for comparison */
267
+ compareBaseline?: string;
268
+ /** Auto-save score to this path */
269
+ saveTo?: string;
270
+ }
271
+ /**
272
+ * Default weights for known tools.
273
+ * New tools get weight of 10 if not specified.
274
+ */
275
+ declare const DEFAULT_TOOL_WEIGHTS: Record<string, number>;
276
+ /**
277
+ * Tool name normalization map (shorthand -> full name)
278
+ */
279
+ declare const TOOL_NAME_MAP: Record<string, string>;
280
+ /**
281
+ * Normalize tool name from shorthand to full name
282
+ */
283
+ declare function normalizeToolName(shortName: string): string;
284
+ /**
285
+ * Get tool weight with fallback priority:
286
+ * 1. CLI override
287
+ * 2. Tool config scoreWeight
288
+ * 3. Default weight
289
+ * 4. 10 (for unknown tools)
290
+ */
291
+ declare function getToolWeight(toolName: string, toolConfig?: {
292
+ scoreWeight?: number;
293
+ }, cliOverride?: number): number;
294
+ /**
295
+ * Parse weight string from CLI (e.g., "patterns:50,context:30")
296
+ */
297
+ declare function parseWeightString(weightStr?: string): Map<string, number>;
298
+ /**
299
+ * Calculate overall AI Readiness Score from multiple tool scores.
300
+ *
301
+ * Formula: Σ(tool_score × tool_weight) / Σ(active_tool_weights)
302
+ *
303
+ * This allows dynamic composition - score adjusts automatically
304
+ * based on which tools actually ran.
305
+ */
306
+ declare function calculateOverallScore(toolOutputs: Map<string, ToolScoringOutput>, config?: any, cliWeights?: Map<string, number>): ScoringResult;
307
+ /**
308
+ * Convert numeric score to rating category
309
+ */
310
+ declare function getRating(score: number): ScoringResult['rating'];
311
+ /**
312
+ * Get rating emoji and color for display
313
+ */
314
+ declare function getRatingDisplay(rating: ScoringResult['rating']): {
315
+ emoji: string;
316
+ color: string;
317
+ };
318
+ /**
319
+ * Format score for display with rating
320
+ */
321
+ declare function formatScore(result: ScoringResult): string;
322
+ /**
323
+ * Format individual tool score for display
324
+ */
325
+ declare function formatToolScore(output: ToolScoringOutput): string;
326
+
327
+ export { type AIReadyConfig, type ASTNode, type AnalysisResult, type CLIOptions, DEFAULT_EXCLUDE, DEFAULT_TOOL_WEIGHTS, type ExportWithImports, type FileImport, type Issue, type IssueType, type Location, type Metrics, type Report, type ScanOptions, type ScoringConfig, type ScoringResult, TOOL_NAME_MAP, type ToolScoringOutput, calculateImportSimilarity, calculateOverallScore, estimateTokens, extractFunctions, extractImports, formatScore, formatToolScore, getElapsedTime, getFileExtension, getRating, getRatingDisplay, getToolWeight, handleCLIError, handleJSONOutput, isSourceFile, loadConfig, loadMergedConfig, mergeConfigWithDefaults, normalizeToolName, parseCode, parseFileExports, parseWeightString, readFileContent, resolveOutputPath, scanFiles };
package/dist/index.js CHANGED
@@ -21,20 +21,30 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  DEFAULT_EXCLUDE: () => DEFAULT_EXCLUDE,
24
+ DEFAULT_TOOL_WEIGHTS: () => DEFAULT_TOOL_WEIGHTS,
25
+ TOOL_NAME_MAP: () => TOOL_NAME_MAP,
24
26
  calculateImportSimilarity: () => calculateImportSimilarity,
27
+ calculateOverallScore: () => calculateOverallScore,
25
28
  estimateTokens: () => estimateTokens,
26
29
  extractFunctions: () => extractFunctions,
27
30
  extractImports: () => extractImports,
31
+ formatScore: () => formatScore,
32
+ formatToolScore: () => formatToolScore,
28
33
  getElapsedTime: () => getElapsedTime,
29
34
  getFileExtension: () => getFileExtension,
35
+ getRating: () => getRating,
36
+ getRatingDisplay: () => getRatingDisplay,
37
+ getToolWeight: () => getToolWeight,
30
38
  handleCLIError: () => handleCLIError,
31
39
  handleJSONOutput: () => handleJSONOutput,
32
40
  isSourceFile: () => isSourceFile,
33
41
  loadConfig: () => loadConfig,
34
42
  loadMergedConfig: () => loadMergedConfig,
35
43
  mergeConfigWithDefaults: () => mergeConfigWithDefaults,
44
+ normalizeToolName: () => normalizeToolName,
36
45
  parseCode: () => parseCode,
37
46
  parseFileExports: () => parseFileExports,
47
+ parseWeightString: () => parseWeightString,
38
48
  readFileContent: () => readFileContent,
39
49
  resolveOutputPath: () => resolveOutputPath,
40
50
  scanFiles: () => scanFiles
@@ -44,6 +54,8 @@ module.exports = __toCommonJS(index_exports);
44
54
  // src/utils/file-scanner.ts
45
55
  var import_glob = require("glob");
46
56
  var import_promises = require("fs/promises");
57
+ var import_fs = require("fs");
58
+ var import_path = require("path");
47
59
  var DEFAULT_EXCLUDE = [
48
60
  // Dependencies
49
61
  "**/node_modules/**",
@@ -96,7 +108,17 @@ async function scanFiles(options) {
96
108
  // Broad default - tools should filter further
97
109
  exclude
98
110
  } = options;
99
- const finalExclude = exclude ? [.../* @__PURE__ */ new Set([...DEFAULT_EXCLUDE, ...exclude])] : DEFAULT_EXCLUDE;
111
+ const ignoreFilePath = (0, import_path.join)(rootDir || ".", ".aireadyignore");
112
+ let ignoreFromFile = [];
113
+ if ((0, import_fs.existsSync)(ignoreFilePath)) {
114
+ try {
115
+ const txt = await (0, import_promises.readFile)(ignoreFilePath, "utf-8");
116
+ ignoreFromFile = txt.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).filter((l) => !l.startsWith("#")).filter((l) => !l.startsWith("!"));
117
+ } catch (e) {
118
+ ignoreFromFile = [];
119
+ }
120
+ }
121
+ const finalExclude = [.../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...DEFAULT_EXCLUDE])];
100
122
  const files = await (0, import_glob.glob)(include, {
101
123
  cwd: rootDir,
102
124
  ignore: finalExclude,
@@ -295,8 +317,8 @@ function estimateTokens(text) {
295
317
  }
296
318
 
297
319
  // src/utils/config.ts
298
- var import_fs = require("fs");
299
- var import_path = require("path");
320
+ var import_fs2 = require("fs");
321
+ var import_path2 = require("path");
300
322
  var import_url = require("url");
301
323
  var CONFIG_FILES = [
302
324
  "aiready.json",
@@ -307,11 +329,11 @@ var CONFIG_FILES = [
307
329
  ".aireadyrc.js"
308
330
  ];
309
331
  async function loadConfig(rootDir) {
310
- let currentDir = (0, import_path.resolve)(rootDir);
332
+ let currentDir = (0, import_path2.resolve)(rootDir);
311
333
  while (true) {
312
334
  for (const configFile of CONFIG_FILES) {
313
- const configPath = (0, import_path.join)(currentDir, configFile);
314
- if ((0, import_fs.existsSync)(configPath)) {
335
+ const configPath = (0, import_path2.join)(currentDir, configFile);
336
+ if ((0, import_fs2.existsSync)(configPath)) {
315
337
  try {
316
338
  let config;
317
339
  if (configFile.endsWith(".js")) {
@@ -319,7 +341,7 @@ async function loadConfig(rootDir) {
319
341
  const module2 = await import(`${fileUrl}?t=${Date.now()}`);
320
342
  config = module2.default || module2;
321
343
  } else {
322
- const content = (0, import_fs.readFileSync)(configPath, "utf-8");
344
+ const content = (0, import_fs2.readFileSync)(configPath, "utf-8");
323
345
  config = JSON.parse(content);
324
346
  }
325
347
  if (typeof config !== "object" || config === null) {
@@ -332,7 +354,7 @@ async function loadConfig(rootDir) {
332
354
  }
333
355
  }
334
356
  }
335
- const parent = (0, import_path.dirname)(currentDir);
357
+ const parent = (0, import_path2.dirname)(currentDir);
336
358
  if (parent === currentDir) {
337
359
  break;
338
360
  }
@@ -364,19 +386,19 @@ function mergeConfigWithDefaults(userConfig, defaults) {
364
386
  }
365
387
 
366
388
  // src/utils/cli-helpers.ts
367
- var import_fs2 = require("fs");
368
- var import_path2 = require("path");
389
+ var import_fs3 = require("fs");
390
+ var import_path3 = require("path");
369
391
  function resolveOutputPath(userPath, defaultFilename, workingDir = process.cwd()) {
370
392
  let outputPath;
371
393
  if (userPath) {
372
394
  outputPath = userPath;
373
395
  } else {
374
- const aireadyDir = (0, import_path2.join)(workingDir, ".aiready");
375
- outputPath = (0, import_path2.join)(aireadyDir, defaultFilename);
396
+ const aireadyDir = (0, import_path3.join)(workingDir, ".aiready");
397
+ outputPath = (0, import_path3.join)(aireadyDir, defaultFilename);
376
398
  }
377
- const parentDir = (0, import_path2.dirname)(outputPath);
378
- if (!(0, import_fs2.existsSync)(parentDir)) {
379
- (0, import_fs2.mkdirSync)(parentDir, { recursive: true });
399
+ const parentDir = (0, import_path3.dirname)(outputPath);
400
+ if (!(0, import_fs3.existsSync)(parentDir)) {
401
+ (0, import_fs3.mkdirSync)(parentDir, { recursive: true });
380
402
  }
381
403
  return outputPath;
382
404
  }
@@ -392,11 +414,11 @@ async function loadMergedConfig(directory, defaults, cliOptions) {
392
414
  }
393
415
  function handleJSONOutput(data, outputFile, successMessage) {
394
416
  if (outputFile) {
395
- const dir = (0, import_path2.dirname)(outputFile);
396
- if (!(0, import_fs2.existsSync)(dir)) {
397
- (0, import_fs2.mkdirSync)(dir, { recursive: true });
417
+ const dir = (0, import_path3.dirname)(outputFile);
418
+ if (!(0, import_fs3.existsSync)(dir)) {
419
+ (0, import_fs3.mkdirSync)(dir, { recursive: true });
398
420
  }
399
- (0, import_fs2.writeFileSync)(outputFile, JSON.stringify(data, null, 2));
421
+ (0, import_fs3.writeFileSync)(outputFile, JSON.stringify(data, null, 2));
400
422
  console.log(successMessage || `\u2705 Results saved to ${outputFile}`);
401
423
  } else {
402
424
  console.log(JSON.stringify(data, null, 2));
@@ -409,23 +431,177 @@ function handleCLIError(error, commandName) {
409
431
  function getElapsedTime(startTime) {
410
432
  return ((Date.now() - startTime) / 1e3).toFixed(2);
411
433
  }
434
+
435
+ // src/scoring.ts
436
+ var DEFAULT_TOOL_WEIGHTS = {
437
+ "pattern-detect": 40,
438
+ "context-analyzer": 35,
439
+ "consistency": 25,
440
+ "doc-drift": 20,
441
+ "deps": 15
442
+ };
443
+ var TOOL_NAME_MAP = {
444
+ "patterns": "pattern-detect",
445
+ "context": "context-analyzer",
446
+ "consistency": "consistency",
447
+ "doc-drift": "doc-drift",
448
+ "deps": "deps"
449
+ };
450
+ function normalizeToolName(shortName) {
451
+ return TOOL_NAME_MAP[shortName] || shortName;
452
+ }
453
+ function getToolWeight(toolName, toolConfig, cliOverride) {
454
+ if (cliOverride !== void 0) {
455
+ return cliOverride;
456
+ }
457
+ if (toolConfig?.scoreWeight !== void 0) {
458
+ return toolConfig.scoreWeight;
459
+ }
460
+ return DEFAULT_TOOL_WEIGHTS[toolName] || 10;
461
+ }
462
+ function parseWeightString(weightStr) {
463
+ const weights = /* @__PURE__ */ new Map();
464
+ if (!weightStr) {
465
+ return weights;
466
+ }
467
+ const pairs = weightStr.split(",");
468
+ for (const pair of pairs) {
469
+ const [toolShortName, weightStr2] = pair.split(":");
470
+ if (toolShortName && weightStr2) {
471
+ const toolName = normalizeToolName(toolShortName.trim());
472
+ const weight = parseInt(weightStr2.trim(), 10);
473
+ if (!isNaN(weight) && weight > 0) {
474
+ weights.set(toolName, weight);
475
+ }
476
+ }
477
+ }
478
+ return weights;
479
+ }
480
+ function calculateOverallScore(toolOutputs, config, cliWeights) {
481
+ if (toolOutputs.size === 0) {
482
+ throw new Error("No tool outputs provided for scoring");
483
+ }
484
+ const weights = /* @__PURE__ */ new Map();
485
+ for (const [toolName] of toolOutputs.entries()) {
486
+ const cliWeight = cliWeights?.get(toolName);
487
+ const configWeight = config?.tools?.[toolName]?.scoreWeight;
488
+ const weight = cliWeight ?? configWeight ?? DEFAULT_TOOL_WEIGHTS[toolName] ?? 10;
489
+ weights.set(toolName, weight);
490
+ }
491
+ let weightedSum = 0;
492
+ let totalWeight = 0;
493
+ const breakdown = [];
494
+ const toolsUsed = [];
495
+ const calculationWeights = {};
496
+ for (const [toolName, output] of toolOutputs.entries()) {
497
+ const weight = weights.get(toolName) || 10;
498
+ const weightedScore = output.score * weight;
499
+ weightedSum += weightedScore;
500
+ totalWeight += weight;
501
+ toolsUsed.push(toolName);
502
+ calculationWeights[toolName] = weight;
503
+ breakdown.push(output);
504
+ }
505
+ const overall = Math.round(weightedSum / totalWeight);
506
+ const rating = getRating(overall);
507
+ const formulaParts = Array.from(toolOutputs.entries()).map(([name, output]) => {
508
+ const w = weights.get(name) || 10;
509
+ return `(${output.score} \xD7 ${w})`;
510
+ });
511
+ const formulaStr = `[${formulaParts.join(" + ")}] / ${totalWeight} = ${overall}`;
512
+ return {
513
+ overall,
514
+ rating,
515
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
516
+ toolsUsed,
517
+ breakdown,
518
+ calculation: {
519
+ formula: formulaStr,
520
+ weights: calculationWeights,
521
+ normalized: formulaStr
522
+ }
523
+ };
524
+ }
525
+ function getRating(score) {
526
+ if (score >= 90) return "Excellent";
527
+ if (score >= 75) return "Good";
528
+ if (score >= 60) return "Fair";
529
+ if (score >= 40) return "Needs Work";
530
+ return "Critical";
531
+ }
532
+ function getRatingDisplay(rating) {
533
+ switch (rating) {
534
+ case "Excellent":
535
+ return { emoji: "\u2705", color: "green" };
536
+ case "Good":
537
+ return { emoji: "\u{1F44D}", color: "blue" };
538
+ case "Fair":
539
+ return { emoji: "\u26A0\uFE0F", color: "yellow" };
540
+ case "Needs Work":
541
+ return { emoji: "\u{1F528}", color: "orange" };
542
+ case "Critical":
543
+ return { emoji: "\u274C", color: "red" };
544
+ }
545
+ }
546
+ function formatScore(result) {
547
+ const { emoji, color } = getRatingDisplay(result.rating);
548
+ return `${result.overall}/100 (${result.rating}) ${emoji}`;
549
+ }
550
+ function formatToolScore(output) {
551
+ let result = ` Score: ${output.score}/100
552
+
553
+ `;
554
+ if (output.factors && output.factors.length > 0) {
555
+ result += ` Factors:
556
+ `;
557
+ output.factors.forEach((factor) => {
558
+ const impactSign = factor.impact > 0 ? "+" : "";
559
+ result += ` \u2022 ${factor.name}: ${impactSign}${factor.impact} - ${factor.description}
560
+ `;
561
+ });
562
+ result += "\n";
563
+ }
564
+ if (output.recommendations && output.recommendations.length > 0) {
565
+ result += ` Recommendations:
566
+ `;
567
+ output.recommendations.forEach((rec, i) => {
568
+ const priorityIcon = rec.priority === "high" ? "\u{1F534}" : rec.priority === "medium" ? "\u{1F7E1}" : "\u{1F535}";
569
+ result += ` ${i + 1}. ${priorityIcon} ${rec.action}
570
+ `;
571
+ result += ` Impact: +${rec.estimatedImpact} points
572
+
573
+ `;
574
+ });
575
+ }
576
+ return result;
577
+ }
412
578
  // Annotate the CommonJS export names for ESM import in node:
413
579
  0 && (module.exports = {
414
580
  DEFAULT_EXCLUDE,
581
+ DEFAULT_TOOL_WEIGHTS,
582
+ TOOL_NAME_MAP,
415
583
  calculateImportSimilarity,
584
+ calculateOverallScore,
416
585
  estimateTokens,
417
586
  extractFunctions,
418
587
  extractImports,
588
+ formatScore,
589
+ formatToolScore,
419
590
  getElapsedTime,
420
591
  getFileExtension,
592
+ getRating,
593
+ getRatingDisplay,
594
+ getToolWeight,
421
595
  handleCLIError,
422
596
  handleJSONOutput,
423
597
  isSourceFile,
424
598
  loadConfig,
425
599
  loadMergedConfig,
426
600
  mergeConfigWithDefaults,
601
+ normalizeToolName,
427
602
  parseCode,
428
603
  parseFileExports,
604
+ parseWeightString,
429
605
  readFileContent,
430
606
  resolveOutputPath,
431
607
  scanFiles
package/dist/index.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  // src/utils/file-scanner.ts
2
2
  import { glob } from "glob";
3
3
  import { readFile } from "fs/promises";
4
+ import { existsSync } from "fs";
5
+ import { join } from "path";
4
6
  var DEFAULT_EXCLUDE = [
5
7
  // Dependencies
6
8
  "**/node_modules/**",
@@ -53,7 +55,17 @@ async function scanFiles(options) {
53
55
  // Broad default - tools should filter further
54
56
  exclude
55
57
  } = options;
56
- const finalExclude = exclude ? [.../* @__PURE__ */ new Set([...DEFAULT_EXCLUDE, ...exclude])] : DEFAULT_EXCLUDE;
58
+ const ignoreFilePath = join(rootDir || ".", ".aireadyignore");
59
+ let ignoreFromFile = [];
60
+ if (existsSync(ignoreFilePath)) {
61
+ try {
62
+ const txt = await readFile(ignoreFilePath, "utf-8");
63
+ ignoreFromFile = txt.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).filter((l) => !l.startsWith("#")).filter((l) => !l.startsWith("!"));
64
+ } catch (e) {
65
+ ignoreFromFile = [];
66
+ }
67
+ }
68
+ const finalExclude = [.../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...DEFAULT_EXCLUDE])];
57
69
  const files = await glob(include, {
58
70
  cwd: rootDir,
59
71
  ignore: finalExclude,
@@ -252,8 +264,8 @@ function estimateTokens(text) {
252
264
  }
253
265
 
254
266
  // src/utils/config.ts
255
- import { readFileSync, existsSync } from "fs";
256
- import { join, resolve, dirname } from "path";
267
+ import { readFileSync, existsSync as existsSync2 } from "fs";
268
+ import { join as join2, resolve, dirname } from "path";
257
269
  import { pathToFileURL } from "url";
258
270
  var CONFIG_FILES = [
259
271
  "aiready.json",
@@ -267,8 +279,8 @@ async function loadConfig(rootDir) {
267
279
  let currentDir = resolve(rootDir);
268
280
  while (true) {
269
281
  for (const configFile of CONFIG_FILES) {
270
- const configPath = join(currentDir, configFile);
271
- if (existsSync(configPath)) {
282
+ const configPath = join2(currentDir, configFile);
283
+ if (existsSync2(configPath)) {
272
284
  try {
273
285
  let config;
274
286
  if (configFile.endsWith(".js")) {
@@ -321,18 +333,18 @@ function mergeConfigWithDefaults(userConfig, defaults) {
321
333
  }
322
334
 
323
335
  // src/utils/cli-helpers.ts
324
- import { writeFileSync, mkdirSync, existsSync as existsSync2 } from "fs";
325
- import { join as join2, dirname as dirname2 } from "path";
336
+ import { writeFileSync, mkdirSync, existsSync as existsSync3 } from "fs";
337
+ import { join as join3, dirname as dirname2 } from "path";
326
338
  function resolveOutputPath(userPath, defaultFilename, workingDir = process.cwd()) {
327
339
  let outputPath;
328
340
  if (userPath) {
329
341
  outputPath = userPath;
330
342
  } else {
331
- const aireadyDir = join2(workingDir, ".aiready");
332
- outputPath = join2(aireadyDir, defaultFilename);
343
+ const aireadyDir = join3(workingDir, ".aiready");
344
+ outputPath = join3(aireadyDir, defaultFilename);
333
345
  }
334
346
  const parentDir = dirname2(outputPath);
335
- if (!existsSync2(parentDir)) {
347
+ if (!existsSync3(parentDir)) {
336
348
  mkdirSync(parentDir, { recursive: true });
337
349
  }
338
350
  return outputPath;
@@ -350,7 +362,7 @@ async function loadMergedConfig(directory, defaults, cliOptions) {
350
362
  function handleJSONOutput(data, outputFile, successMessage) {
351
363
  if (outputFile) {
352
364
  const dir = dirname2(outputFile);
353
- if (!existsSync2(dir)) {
365
+ if (!existsSync3(dir)) {
354
366
  mkdirSync(dir, { recursive: true });
355
367
  }
356
368
  writeFileSync(outputFile, JSON.stringify(data, null, 2));
@@ -366,22 +378,176 @@ function handleCLIError(error, commandName) {
366
378
  function getElapsedTime(startTime) {
367
379
  return ((Date.now() - startTime) / 1e3).toFixed(2);
368
380
  }
381
+
382
+ // src/scoring.ts
383
+ var DEFAULT_TOOL_WEIGHTS = {
384
+ "pattern-detect": 40,
385
+ "context-analyzer": 35,
386
+ "consistency": 25,
387
+ "doc-drift": 20,
388
+ "deps": 15
389
+ };
390
+ var TOOL_NAME_MAP = {
391
+ "patterns": "pattern-detect",
392
+ "context": "context-analyzer",
393
+ "consistency": "consistency",
394
+ "doc-drift": "doc-drift",
395
+ "deps": "deps"
396
+ };
397
+ function normalizeToolName(shortName) {
398
+ return TOOL_NAME_MAP[shortName] || shortName;
399
+ }
400
+ function getToolWeight(toolName, toolConfig, cliOverride) {
401
+ if (cliOverride !== void 0) {
402
+ return cliOverride;
403
+ }
404
+ if (toolConfig?.scoreWeight !== void 0) {
405
+ return toolConfig.scoreWeight;
406
+ }
407
+ return DEFAULT_TOOL_WEIGHTS[toolName] || 10;
408
+ }
409
+ function parseWeightString(weightStr) {
410
+ const weights = /* @__PURE__ */ new Map();
411
+ if (!weightStr) {
412
+ return weights;
413
+ }
414
+ const pairs = weightStr.split(",");
415
+ for (const pair of pairs) {
416
+ const [toolShortName, weightStr2] = pair.split(":");
417
+ if (toolShortName && weightStr2) {
418
+ const toolName = normalizeToolName(toolShortName.trim());
419
+ const weight = parseInt(weightStr2.trim(), 10);
420
+ if (!isNaN(weight) && weight > 0) {
421
+ weights.set(toolName, weight);
422
+ }
423
+ }
424
+ }
425
+ return weights;
426
+ }
427
+ function calculateOverallScore(toolOutputs, config, cliWeights) {
428
+ if (toolOutputs.size === 0) {
429
+ throw new Error("No tool outputs provided for scoring");
430
+ }
431
+ const weights = /* @__PURE__ */ new Map();
432
+ for (const [toolName] of toolOutputs.entries()) {
433
+ const cliWeight = cliWeights?.get(toolName);
434
+ const configWeight = config?.tools?.[toolName]?.scoreWeight;
435
+ const weight = cliWeight ?? configWeight ?? DEFAULT_TOOL_WEIGHTS[toolName] ?? 10;
436
+ weights.set(toolName, weight);
437
+ }
438
+ let weightedSum = 0;
439
+ let totalWeight = 0;
440
+ const breakdown = [];
441
+ const toolsUsed = [];
442
+ const calculationWeights = {};
443
+ for (const [toolName, output] of toolOutputs.entries()) {
444
+ const weight = weights.get(toolName) || 10;
445
+ const weightedScore = output.score * weight;
446
+ weightedSum += weightedScore;
447
+ totalWeight += weight;
448
+ toolsUsed.push(toolName);
449
+ calculationWeights[toolName] = weight;
450
+ breakdown.push(output);
451
+ }
452
+ const overall = Math.round(weightedSum / totalWeight);
453
+ const rating = getRating(overall);
454
+ const formulaParts = Array.from(toolOutputs.entries()).map(([name, output]) => {
455
+ const w = weights.get(name) || 10;
456
+ return `(${output.score} \xD7 ${w})`;
457
+ });
458
+ const formulaStr = `[${formulaParts.join(" + ")}] / ${totalWeight} = ${overall}`;
459
+ return {
460
+ overall,
461
+ rating,
462
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
463
+ toolsUsed,
464
+ breakdown,
465
+ calculation: {
466
+ formula: formulaStr,
467
+ weights: calculationWeights,
468
+ normalized: formulaStr
469
+ }
470
+ };
471
+ }
472
+ function getRating(score) {
473
+ if (score >= 90) return "Excellent";
474
+ if (score >= 75) return "Good";
475
+ if (score >= 60) return "Fair";
476
+ if (score >= 40) return "Needs Work";
477
+ return "Critical";
478
+ }
479
+ function getRatingDisplay(rating) {
480
+ switch (rating) {
481
+ case "Excellent":
482
+ return { emoji: "\u2705", color: "green" };
483
+ case "Good":
484
+ return { emoji: "\u{1F44D}", color: "blue" };
485
+ case "Fair":
486
+ return { emoji: "\u26A0\uFE0F", color: "yellow" };
487
+ case "Needs Work":
488
+ return { emoji: "\u{1F528}", color: "orange" };
489
+ case "Critical":
490
+ return { emoji: "\u274C", color: "red" };
491
+ }
492
+ }
493
+ function formatScore(result) {
494
+ const { emoji, color } = getRatingDisplay(result.rating);
495
+ return `${result.overall}/100 (${result.rating}) ${emoji}`;
496
+ }
497
+ function formatToolScore(output) {
498
+ let result = ` Score: ${output.score}/100
499
+
500
+ `;
501
+ if (output.factors && output.factors.length > 0) {
502
+ result += ` Factors:
503
+ `;
504
+ output.factors.forEach((factor) => {
505
+ const impactSign = factor.impact > 0 ? "+" : "";
506
+ result += ` \u2022 ${factor.name}: ${impactSign}${factor.impact} - ${factor.description}
507
+ `;
508
+ });
509
+ result += "\n";
510
+ }
511
+ if (output.recommendations && output.recommendations.length > 0) {
512
+ result += ` Recommendations:
513
+ `;
514
+ output.recommendations.forEach((rec, i) => {
515
+ const priorityIcon = rec.priority === "high" ? "\u{1F534}" : rec.priority === "medium" ? "\u{1F7E1}" : "\u{1F535}";
516
+ result += ` ${i + 1}. ${priorityIcon} ${rec.action}
517
+ `;
518
+ result += ` Impact: +${rec.estimatedImpact} points
519
+
520
+ `;
521
+ });
522
+ }
523
+ return result;
524
+ }
369
525
  export {
370
526
  DEFAULT_EXCLUDE,
527
+ DEFAULT_TOOL_WEIGHTS,
528
+ TOOL_NAME_MAP,
371
529
  calculateImportSimilarity,
530
+ calculateOverallScore,
372
531
  estimateTokens,
373
532
  extractFunctions,
374
533
  extractImports,
534
+ formatScore,
535
+ formatToolScore,
375
536
  getElapsedTime,
376
537
  getFileExtension,
538
+ getRating,
539
+ getRatingDisplay,
540
+ getToolWeight,
377
541
  handleCLIError,
378
542
  handleJSONOutput,
379
543
  isSourceFile,
380
544
  loadConfig,
381
545
  loadMergedConfig,
382
546
  mergeConfigWithDefaults,
547
+ normalizeToolName,
383
548
  parseCode,
384
549
  parseFileExports,
550
+ parseWeightString,
385
551
  readFileContent,
386
552
  resolveOutputPath,
387
553
  scanFiles
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/core",
3
- "version": "0.7.9",
3
+ "version": "0.7.11",
4
4
  "description": "Shared utilities for AIReady analysis tools",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",