@aiready/cli 0.14.8 → 0.14.10

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.
@@ -57,7 +57,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
57
57
  console.log(chalk.blue('🚀 Starting AIReady unified analysis...\n'));
58
58
 
59
59
  const startTime = Date.now();
60
- const resolvedDir = resolvePath(process.cwd(), directory || '.');
60
+ const resolvedDir = resolvePath(process.cwd(), directory ?? '.');
61
61
  const repoMetadata = getRepoMetadata(resolvedDir);
62
62
 
63
63
  try {
@@ -159,7 +159,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
159
159
  const { getSmartDefaults } = await import('@aiready/pattern-detect');
160
160
  const patternSmartDefaults = await getSmartDefaults(
161
161
  resolvedDir,
162
- finalOptions.toolConfigs?.[ToolName.PatternDetect] || {}
162
+ finalOptions.toolConfigs?.[ToolName.PatternDetect] ?? {}
163
163
  );
164
164
 
165
165
  // Merge smart defaults into toolConfigs instead of root level
@@ -173,7 +173,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
173
173
  console.log(chalk.cyan('\n=== AIReady Run Preview ==='));
174
174
  console.log(
175
175
  chalk.white('Tools to run:'),
176
- (finalOptions.tools || []).join(', ')
176
+ (finalOptions.tools ?? []).join(', ')
177
177
  );
178
178
 
179
179
  // Dynamic progress callback
@@ -202,7 +202,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
202
202
 
203
203
  // Determine scoring profile for project-type-aware weighting
204
204
  const scoringProfile =
205
- options.profile || baseOptions.scoring?.profile || 'default';
205
+ options.profile ?? baseOptions.scoring?.profile ?? 'default';
206
206
 
207
207
  const results = await analyzeUnified({
208
208
  ...finalOptions,
@@ -233,7 +233,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
233
233
  readFileSync(resolvePath(process.cwd(), options.compareTo), 'utf8')
234
234
  );
235
235
  const prevScore =
236
- prevReport.scoring?.overall || prevReport.scoring?.score;
236
+ prevReport.scoring?.overall ?? prevReport.scoring?.score;
237
237
  if (typeof prevScore === 'number') {
238
238
  const diff = scoringResult.overall - prevScore;
239
239
  const diffStr = diff > 0 ? `+${diff}` : String(diff);
@@ -262,19 +262,19 @@ export async function scanAction(directory: string, options: ScanOptions) {
262
262
  }
263
263
 
264
264
  // Token Budget & Cost Logic
265
- const totalWastedDuplication = (scoringResult.breakdown || []).reduce(
266
- (sum, s) =>
267
- sum + (s.tokenBudget?.wastedTokens.bySource.duplication || 0),
265
+ const totalWastedDuplication = (scoringResult.breakdown ?? []).reduce(
266
+ (sum: number, s: any) =>
267
+ sum + (s.tokenBudget?.wastedTokens?.bySource?.duplication ?? 0),
268
268
  0
269
269
  );
270
- const totalWastedFragmentation = (scoringResult.breakdown || []).reduce(
271
- (sum, s) =>
272
- sum + (s.tokenBudget?.wastedTokens.bySource.fragmentation || 0),
270
+ const totalWastedFragmentation = (scoringResult.breakdown ?? []).reduce(
271
+ (sum: number, s: any) =>
272
+ sum + (s.tokenBudget?.wastedTokens?.bySource?.fragmentation ?? 0),
273
273
  0
274
274
  );
275
275
  const totalContext = Math.max(
276
- ...(scoringResult.breakdown || []).map(
277
- (s) => s.tokenBudget?.totalContextTokens || 0
276
+ ...(scoringResult.breakdown ?? []).map(
277
+ (s: any) => s.tokenBudget?.totalContextTokens ?? 0
278
278
  ),
279
279
  0
280
280
  );
@@ -300,7 +300,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
300
300
  }
301
301
  }
302
302
 
303
- const modelId = options.model || 'claude-3-5-sonnet';
303
+ const modelId = options.model ?? 'gpt-5.4-mini';
304
304
  const roi = (await import('@aiready/core')).calculateBusinessROI({
305
305
  tokenWaste: unifiedBudget.wastedTokens.total,
306
306
  issues: allIssues,
@@ -345,9 +345,9 @@ export async function scanAction(directory: string, options: ScanOptions) {
345
345
 
346
346
  // Output persistence
347
347
  const outputFormat =
348
- options.output || finalOptions.output?.format || 'console';
348
+ options.output ?? finalOptions.output?.format ?? 'console';
349
349
  const outputPath = resolveOutputPath(
350
- options.outputFile || finalOptions.output?.file,
350
+ options.outputFile ?? finalOptions.output?.file,
351
351
  `aiready-report-${getReportTimestamp()}.json`,
352
352
  resolvedDir
353
353
  );
@@ -380,8 +380,8 @@ export async function scanAction(directory: string, options: ScanOptions) {
380
380
  const threshold = options.threshold
381
381
  ? parseInt(options.threshold)
382
382
  : undefined;
383
- const failOnLevel = options.failOn || 'critical';
384
- const isCI = options.ci || process.env.CI === 'true';
383
+ const failOnLevel = options.failOn ?? 'critical';
384
+ const isCI = options.ci ?? process.env.CI === 'true';
385
385
 
386
386
  let shouldFail = false;
387
387
  let failReason = '';
@@ -13,10 +13,10 @@ export async function uploadAction(file: string, options: UploadOptions) {
13
13
  const startTime = Date.now();
14
14
  const filePath = resolvePath(process.cwd(), file);
15
15
  const serverUrl =
16
- options.server ||
17
- process.env.AIREADY_SERVER ||
16
+ options.server ??
17
+ process.env.AIREADY_SERVER ??
18
18
  'https://dev.platform.getaiready.dev';
19
- const apiKey = options.apiKey || process.env.AIREADY_API_KEY;
19
+ const apiKey = options.apiKey ?? process.env.AIREADY_API_KEY;
20
20
 
21
21
  if (!apiKey) {
22
22
  console.error(chalk.red('❌ API Key is required for upload.'));
@@ -49,7 +49,7 @@ export async function uploadAction(file: string, options: UploadOptions) {
49
49
 
50
50
  // Prepare upload payload
51
51
  // Note: repoId is optional if the metadata contains it, but for now we'll require it or infer from metadata
52
- const repoId = options.repoId || reportData.repository?.repoId;
52
+ const repoId = options.repoId ?? reportData.repository?.repoId;
53
53
 
54
54
  const response = await fetch(`${serverUrl}/api/analysis/upload`, {
55
55
  method: 'POST',
@@ -70,13 +70,13 @@ export async function uploadAction(file: string, options: UploadOptions) {
70
70
  uploadResult = await response.json();
71
71
  } else {
72
72
  const text = await response.text();
73
- uploadResult = { error: text || response.statusText };
73
+ uploadResult = { error: text ?? response.statusText };
74
74
  }
75
75
 
76
76
  if (!response.ok) {
77
77
  console.error(
78
78
  chalk.red(
79
- `❌ Upload failed: ${uploadResult.error || response.statusText}`
79
+ `❌ Upload failed: ${uploadResult.error ?? response.statusText}`
80
80
  )
81
81
  );
82
82
 
@@ -30,7 +30,7 @@ export async function visualizeAction(
30
30
  options: VisualizeOptions
31
31
  ) {
32
32
  try {
33
- const dirPath = resolvePath(process.cwd(), directory || '.');
33
+ const dirPath = resolvePath(process.cwd(), directory ?? '.');
34
34
  let reportPath = options.report
35
35
  ? resolvePath(dirPath, options.report)
36
36
  : null;
@@ -87,7 +87,7 @@ export async function visualizeAction(
87
87
  const graph = GraphBuilder.buildFromReport(report, dirPath);
88
88
 
89
89
  // Check if --dev mode is requested and available
90
- let useDevMode = options.dev || false;
90
+ let useDevMode = options.dev ?? false;
91
91
  let devServerStarted = false;
92
92
 
93
93
  if (useDevMode) {
@@ -230,7 +230,7 @@ export async function visualizeAction(
230
230
  console.log('Generating HTML...');
231
231
  const html = generateHTML(graph as any);
232
232
  const defaultOutput = 'visualization.html';
233
- const outPath = resolvePath(dirPath, options.output || defaultOutput);
233
+ const outPath = resolvePath(dirPath, options.output ?? defaultOutput);
234
234
  writeFileSync(outPath, html, 'utf8');
235
235
  console.log(chalk.green(`✅ Visualization written to: ${outPath}`));
236
236
 
@@ -250,7 +250,7 @@ export async function visualizeAction(
250
250
 
251
251
  const server = http.createServer(async (req, res) => {
252
252
  try {
253
- const urlPath = req.url || '/';
253
+ const urlPath = req.url ?? '/';
254
254
  if (urlPath === '/' || urlPath === '/index.html') {
255
255
  const content = await fsp.readFile(outPath, 'utf8');
256
256
  res.writeHead(200, {
package/src/index.ts CHANGED
@@ -57,6 +57,12 @@ export interface UnifiedAnalysisOptions extends ScanOptions {
57
57
  total?: number;
58
58
  message?: string;
59
59
  }) => void;
60
+ /** Files or directories to include in scan */
61
+ include?: string[];
62
+ /** Files or directories to exclude from scan */
63
+ exclude?: string[];
64
+ /** Batch size for comparisons */
65
+ batchSize?: number;
60
66
  }
61
67
 
62
68
  /**
@@ -147,8 +153,12 @@ function sanitizeToolConfig(config: any): any {
147
153
  }
148
154
 
149
155
  /**
150
- * AIReady Unified Analysis
156
+ * AIReady Unified Analysis.
151
157
  * Orchestrates all registered tools via the ToolRegistry.
158
+ *
159
+ * @param options - Unified analysis configuration including tools and rootDir.
160
+ * @returns Promise resolving to the consolidated analysis result.
161
+ * @lastUpdated 2026-03-18
152
162
  */
153
163
  export async function analyzeUnified(
154
164
  options: UnifiedAnalysisOptions
@@ -157,7 +167,7 @@ export async function analyzeUnified(
157
167
  await initializeParsers();
158
168
 
159
169
  const startTime = Date.now();
160
- const requestedTools = options.tools || [
170
+ const requestedTools = options.tools ?? [
161
171
  'patterns',
162
172
  'context',
163
173
  'consistency',
@@ -182,7 +192,7 @@ export async function analyzeUnified(
182
192
  // Dynamic Loading: If provider not found, attempt to import the package
183
193
  if (!provider) {
184
194
  const packageName =
185
- TOOL_PACKAGE_MAP[toolName] ||
195
+ TOOL_PACKAGE_MAP[toolName] ??
186
196
  (toolName.startsWith('@aiready/') ? toolName : `@aiready/${toolName}`);
187
197
  try {
188
198
  await import(packageName);
@@ -297,34 +307,16 @@ export async function analyzeUnified(
297
307
 
298
308
  // Track total files analyzed across all tools
299
309
  const toolFiles =
300
- output.summary?.totalFiles || output.summary?.filesAnalyzed || 0;
310
+ output.summary?.totalFiles ?? output.summary?.filesAnalyzed ?? 0;
301
311
  if (toolFiles > result.summary.totalFiles) {
302
312
  result.summary.totalFiles = toolFiles;
303
313
  }
304
314
 
305
315
  const issueCount = output.results.reduce(
306
- (sum: number, file: any) => sum + (file.issues?.length || 0),
316
+ (sum: number, file: any) => sum + (file.issues?.length ?? 0),
307
317
  0
308
318
  );
309
319
  result.summary.totalIssues += issueCount;
310
-
311
- // Robust backward compatibility fallbacks
312
- // 1. Add all aliases as keys (e.g., 'patterns', 'context', 'consistency')
313
- if (provider.alias && Array.isArray(provider.alias)) {
314
- for (const alias of provider.alias) {
315
- if (!result[alias]) {
316
- (result as any)[alias] = output;
317
- }
318
- }
319
- }
320
-
321
- // 2. Add camelCase version of canonical ID (e.g., 'patternDetect', 'contextAnalyzer')
322
- const camelCaseId = provider.id.replace(/-([a-z])/g, (g) =>
323
- g[1].toUpperCase()
324
- );
325
- if (camelCaseId !== provider.id && !result[camelCaseId]) {
326
- (result as any)[camelCaseId] = output;
327
- }
328
320
  } catch (err) {
329
321
  console.error(`❌ Error running tool '${provider.id}':`, err);
330
322
  }
@@ -343,12 +335,38 @@ export async function analyzeUnified(
343
335
  });
344
336
 
345
337
  result.summary.executionTime = Date.now() - startTime;
338
+
339
+ // Add backward compatibility key mappings (kebab-case -> camelCase and aliases)
340
+ const keyMappings: Record<string, string[]> = {
341
+ 'pattern-detect': ['patternDetect', 'patterns'],
342
+ 'context-analyzer': ['contextAnalyzer', 'context'],
343
+ 'naming-consistency': ['namingConsistency', 'consistency'],
344
+ 'ai-signal-clarity': ['aiSignalClarity'],
345
+ 'agent-grounding': ['agentGrounding'],
346
+ 'testability-index': ['testabilityIndex', 'testability'],
347
+ 'doc-drift': ['docDrift'],
348
+ 'dependency-health': ['dependencyHealth', 'deps'],
349
+ 'change-amplification': ['changeAmplification'],
350
+ };
351
+
352
+ for (const [kebabKey, aliases] of Object.entries(keyMappings)) {
353
+ if (result[kebabKey]) {
354
+ for (const alias of aliases) {
355
+ result[alias] = result[kebabKey];
356
+ }
357
+ }
358
+ }
359
+
346
360
  return result;
347
361
  }
348
362
 
349
363
  /**
350
- * AIReady Unified Scoring
364
+ * AIReady Unified Scoring.
351
365
  * Calculates scores for all analyzed tools.
366
+ *
367
+ * @param results - The consolidated results from a unified analysis.
368
+ * @param options - Analysis options for weighting and budget calculation.
369
+ * @returns Promise resolving to the final scoring result.
352
370
  */
353
371
  export async function scoreUnified(
354
372
  results: UnifiedAnalysisResult,
@@ -370,7 +388,7 @@ export async function scoreUnified(
370
388
  if (!toolScore.tokenBudget) {
371
389
  if (toolId === ToolName.PatternDetect && (output as any).duplicates) {
372
390
  const wastedTokens = (output as any).duplicates.reduce(
373
- (sum: number, d: any) => sum + (d.tokenCost || 0),
391
+ (sum: number, d: any) => sum + (d.tokenCost ?? 0),
374
392
  0
375
393
  );
376
394
  toolScore.tokenBudget = calculateTokenBudget({
@@ -386,7 +404,7 @@ export async function scoreUnified(
386
404
  totalContextTokens: output.summary.totalTokens,
387
405
  wastedTokens: {
388
406
  duplication: 0,
389
- fragmentation: output.summary.totalPotentialSavings || 0,
407
+ fragmentation: output.summary.totalPotentialSavings ?? 0,
390
408
  chattiness: 0,
391
409
  },
392
410
  });
@@ -419,7 +437,10 @@ export async function scoreUnified(
419
437
  }
420
438
 
421
439
  /**
422
- * Generate human-readable summary of unified results
440
+ * Generate human-readable summary of unified results.
441
+ *
442
+ * @param result - The consolidated analysis result object.
443
+ * @returns Formatted summary string.
423
444
  */
424
445
  export function generateUnifiedSummary(result: UnifiedAnalysisResult): string {
425
446
  const { summary } = result;
@@ -433,7 +454,7 @@ export function generateUnifiedSummary(result: UnifiedAnalysisResult): string {
433
454
  const toolResult = result[provider.id];
434
455
  if (toolResult) {
435
456
  const issueCount = toolResult.results.reduce(
436
- (sum: number, r: any) => sum + (r.issues?.length || 0),
457
+ (sum: number, r: any) => sum + (r.issues?.length ?? 0),
437
458
  0
438
459
  );
439
460
  output += `• ${provider.id}: ${issueCount} issues\n`;
@@ -22,7 +22,10 @@ export function getReportTimestamp(): string {
22
22
  }
23
23
 
24
24
  /**
25
- * Warn if graph caps may be exceeded
25
+ * Warn if graph caps may be exceeded for visualization.
26
+ *
27
+ * @param report - The combined analysis report.
28
+ * @param dirPath - Root directory to look for configuration.
26
29
  */
27
30
  export async function warnIfGraphCapExceeded(report: any, dirPath: string) {
28
31
  try {
@@ -90,7 +93,11 @@ export async function warnIfGraphCapExceeded(report: any, dirPath: string) {
90
93
  }
91
94
 
92
95
  /**
93
- * Generate markdown report for consistency command
96
+ * Generate markdown report for consistency command.
97
+ *
98
+ * @param report - The consistency report object.
99
+ * @param elapsedTime - Time taken for analysis in seconds.
100
+ * @returns Formatted markdown string.
94
101
  */
95
102
  export function generateMarkdownReport(
96
103
  report: any,
@@ -117,7 +124,11 @@ export function generateMarkdownReport(
117
124
  }
118
125
 
119
126
  /**
120
- * Truncate array for display (show first N items with "... +N more")
127
+ * Truncate array for display (show first N items with "... +N more").
128
+ *
129
+ * @param arr - The array to truncate.
130
+ * @param cap - Maximum number of items to show before truncating.
131
+ * @returns Formatted string for display.
121
132
  */
122
133
  export function truncateArray(arr: any[] | undefined, cap = 8): string {
123
134
  if (!Array.isArray(arr)) return '';
@@ -128,6 +139,10 @@ export function truncateArray(arr: any[] | undefined, cap = 8): string {
128
139
 
129
140
  /**
130
141
  * Build a common ToolScoringOutput payload from a tool report.
142
+ *
143
+ * @param toolName - Identifier for the tool.
144
+ * @param report - Minimal report structure containing score and recommendations.
145
+ * @returns Standardized scoring output.
131
146
  */
132
147
  export function buildToolScoringOutput(
133
148
  toolName: string,
@@ -152,6 +167,10 @@ export function buildToolScoringOutput(
152
167
 
153
168
  /**
154
169
  * Load config and apply tool-level defaults.
170
+ *
171
+ * @param directory - Directory to search for config.
172
+ * @param defaults - Tool-specific default values.
173
+ * @returns Merged configuration with tool defaults.
155
174
  */
156
175
  export async function loadMergedToolConfig<T extends Record<string, unknown>>(
157
176
  directory: string,
@@ -164,6 +183,11 @@ export async function loadMergedToolConfig<T extends Record<string, unknown>>(
164
183
 
165
184
  /**
166
185
  * Shared base scan options used by CLI tool commands.
186
+ *
187
+ * @param directory - Root directory for scanning.
188
+ * @param options - CLI commander options object.
189
+ * @param extras - Additional tool-specific options.
190
+ * @returns Combined scan options for the analyzer.
167
191
  */
168
192
  export function buildCommonScanOptions(
169
193
  directory: string,
@@ -180,6 +204,9 @@ export function buildCommonScanOptions(
180
204
 
181
205
  /**
182
206
  * Execute a config-driven tool command with shared CLI plumbing.
207
+ *
208
+ * @param params - Execution parameters including analyze and score callbacks.
209
+ * @returns Promise resolving to the tool report and its scoring output.
183
210
  */
184
211
  export async function runConfiguredToolCommand<TReport, TScoring>(params: {
185
212
  directory: string;