codereview-aia 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/.cr-aia.yml +23 -0
  2. package/.crignore +0 -0
  3. package/dist/index.js +27 -0
  4. package/package.json +85 -0
  5. package/src/analysis/FindingsExtractor.ts +431 -0
  6. package/src/analysis/ai-detection/analyzers/BaseAnalyzer.ts +267 -0
  7. package/src/analysis/ai-detection/analyzers/DocumentationAnalyzer.ts +622 -0
  8. package/src/analysis/ai-detection/analyzers/GitHistoryAnalyzer.ts +430 -0
  9. package/src/analysis/ai-detection/core/AIDetectionEngine.ts +467 -0
  10. package/src/analysis/ai-detection/types/DetectionTypes.ts +406 -0
  11. package/src/analysis/ai-detection/utils/SubmissionConverter.ts +390 -0
  12. package/src/analysis/context/ReviewContext.ts +378 -0
  13. package/src/analysis/context/index.ts +7 -0
  14. package/src/analysis/index.ts +8 -0
  15. package/src/analysis/tokens/TokenAnalysisFormatter.ts +154 -0
  16. package/src/analysis/tokens/TokenAnalyzer.ts +747 -0
  17. package/src/analysis/tokens/index.ts +8 -0
  18. package/src/clients/base/abstractClient.ts +190 -0
  19. package/src/clients/base/httpClient.ts +160 -0
  20. package/src/clients/base/index.ts +12 -0
  21. package/src/clients/base/modelDetection.ts +107 -0
  22. package/src/clients/base/responseProcessor.ts +586 -0
  23. package/src/clients/factory/clientFactory.ts +55 -0
  24. package/src/clients/factory/index.ts +8 -0
  25. package/src/clients/implementations/index.ts +8 -0
  26. package/src/clients/implementations/openRouterClient.ts +411 -0
  27. package/src/clients/openRouterClient.ts +863 -0
  28. package/src/clients/openRouterClientWrapper.ts +44 -0
  29. package/src/clients/utils/directoryStructure.ts +52 -0
  30. package/src/clients/utils/index.ts +11 -0
  31. package/src/clients/utils/languageDetection.ts +44 -0
  32. package/src/clients/utils/promptFormatter.ts +105 -0
  33. package/src/clients/utils/promptLoader.ts +53 -0
  34. package/src/clients/utils/tokenCounter.ts +297 -0
  35. package/src/core/ApiClientSelector.ts +37 -0
  36. package/src/core/ConfigurationService.ts +591 -0
  37. package/src/core/ConsolidationService.ts +423 -0
  38. package/src/core/InteractiveDisplayManager.ts +81 -0
  39. package/src/core/OutputManager.ts +275 -0
  40. package/src/core/ReviewGenerator.ts +140 -0
  41. package/src/core/fileDiscovery.ts +237 -0
  42. package/src/core/handlers/EstimationHandler.ts +104 -0
  43. package/src/core/handlers/FileProcessingHandler.ts +204 -0
  44. package/src/core/handlers/OutputHandler.ts +125 -0
  45. package/src/core/handlers/ReviewExecutor.ts +104 -0
  46. package/src/core/reviewOrchestrator.ts +333 -0
  47. package/src/core/utils/ModelInfoUtils.ts +56 -0
  48. package/src/formatters/outputFormatter.ts +62 -0
  49. package/src/formatters/utils/IssueFormatters.ts +83 -0
  50. package/src/formatters/utils/JsonFormatter.ts +77 -0
  51. package/src/formatters/utils/MarkdownFormatters.ts +609 -0
  52. package/src/formatters/utils/MetadataFormatter.ts +269 -0
  53. package/src/formatters/utils/ModelInfoExtractor.ts +115 -0
  54. package/src/index.ts +27 -0
  55. package/src/plugins/PluginInterface.ts +50 -0
  56. package/src/plugins/PluginManager.ts +126 -0
  57. package/src/prompts/PromptManager.ts +69 -0
  58. package/src/prompts/cache/PromptCache.ts +50 -0
  59. package/src/prompts/promptText/common/variables/css-frameworks.json +33 -0
  60. package/src/prompts/promptText/common/variables/framework-versions.json +45 -0
  61. package/src/prompts/promptText/frameworks/react/comprehensive.hbs +19 -0
  62. package/src/prompts/promptText/languages/css/comprehensive.hbs +18 -0
  63. package/src/prompts/promptText/languages/generic/comprehensive.hbs +20 -0
  64. package/src/prompts/promptText/languages/html/comprehensive.hbs +18 -0
  65. package/src/prompts/promptText/languages/javascript/comprehensive.hbs +18 -0
  66. package/src/prompts/promptText/languages/python/comprehensive.hbs +18 -0
  67. package/src/prompts/promptText/languages/typescript/comprehensive.hbs +18 -0
  68. package/src/runtime/auth/service.ts +58 -0
  69. package/src/runtime/auth/session.ts +103 -0
  70. package/src/runtime/auth/types.ts +11 -0
  71. package/src/runtime/cliEntry.ts +196 -0
  72. package/src/runtime/errors.ts +13 -0
  73. package/src/runtime/fileCollector.ts +188 -0
  74. package/src/runtime/manifest.ts +64 -0
  75. package/src/runtime/openrouterProxy.ts +45 -0
  76. package/src/runtime/proxyConfig.ts +94 -0
  77. package/src/runtime/proxyEnvironment.ts +71 -0
  78. package/src/runtime/reportMerge.ts +102 -0
  79. package/src/runtime/reporting/markdownReportBuilder.ts +138 -0
  80. package/src/runtime/reporting/reportDataCollector.ts +234 -0
  81. package/src/runtime/reporting/summaryGenerator.ts +86 -0
  82. package/src/runtime/reviewPipeline.ts +155 -0
  83. package/src/runtime/runAiCodeReview.ts +153 -0
  84. package/src/runtime/runtimeConfig.ts +5 -0
  85. package/src/runtime/ui/Layout.tsx +57 -0
  86. package/src/runtime/ui/RuntimeApp.tsx +150 -0
  87. package/src/runtime/ui/inkModules.ts +73 -0
  88. package/src/runtime/ui/screens/AuthScreen.tsx +128 -0
  89. package/src/runtime/ui/screens/ModeSelection.tsx +52 -0
  90. package/src/runtime/ui/screens/ProgressScreen.tsx +55 -0
  91. package/src/runtime/ui/screens/ResultsScreen.tsx +76 -0
  92. package/src/strategies/ArchitecturalReviewStrategy.ts +54 -0
  93. package/src/strategies/CodingTestReviewStrategy.ts +920 -0
  94. package/src/strategies/ConsolidatedReviewStrategy.ts +59 -0
  95. package/src/strategies/ExtractPatternsReviewStrategy.ts +64 -0
  96. package/src/strategies/MultiPassReviewStrategy.ts +785 -0
  97. package/src/strategies/ReviewStrategy.ts +64 -0
  98. package/src/strategies/StrategyFactory.ts +79 -0
  99. package/src/strategies/index.ts +14 -0
  100. package/src/tokenizers/baseTokenizer.ts +61 -0
  101. package/src/tokenizers/gptTokenizer.ts +27 -0
  102. package/src/tokenizers/index.ts +8 -0
  103. package/src/types/apiResponses.ts +40 -0
  104. package/src/types/cli.ts +24 -0
  105. package/src/types/common.ts +39 -0
  106. package/src/types/configuration.ts +201 -0
  107. package/src/types/handlebars.d.ts +5 -0
  108. package/src/types/patch.d.ts +25 -0
  109. package/src/types/review.ts +294 -0
  110. package/src/types/reviewContext.d.ts +65 -0
  111. package/src/types/reviewSchema.ts +181 -0
  112. package/src/types/structuredReview.ts +167 -0
  113. package/src/types/tokenAnalysis.ts +56 -0
  114. package/src/utils/FileReader.ts +93 -0
  115. package/src/utils/FileWriter.ts +76 -0
  116. package/src/utils/PathGenerator.ts +97 -0
  117. package/src/utils/api/apiUtils.ts +14 -0
  118. package/src/utils/api/index.ts +1 -0
  119. package/src/utils/apiErrorHandler.ts +287 -0
  120. package/src/utils/ciDataCollector.ts +252 -0
  121. package/src/utils/codingTestConfigLoader.ts +466 -0
  122. package/src/utils/dependencies/aiDependencyAnalyzer.ts +454 -0
  123. package/src/utils/detection/frameworkDetector.ts +879 -0
  124. package/src/utils/detection/index.ts +10 -0
  125. package/src/utils/detection/projectTypeDetector.ts +518 -0
  126. package/src/utils/diagramGenerator.ts +206 -0
  127. package/src/utils/errorLogger.ts +60 -0
  128. package/src/utils/estimationUtils.ts +407 -0
  129. package/src/utils/fileFilters.ts +373 -0
  130. package/src/utils/fileSystem.ts +57 -0
  131. package/src/utils/index.ts +36 -0
  132. package/src/utils/logger.ts +240 -0
  133. package/src/utils/pathValidator.ts +98 -0
  134. package/src/utils/priorityFilter.ts +59 -0
  135. package/src/utils/projectDocs.ts +189 -0
  136. package/src/utils/promptPaths.ts +29 -0
  137. package/src/utils/promptTemplateManager.ts +157 -0
  138. package/src/utils/review/consolidateReview.ts +553 -0
  139. package/src/utils/review/fixDisplay.ts +100 -0
  140. package/src/utils/review/fixImplementation.ts +61 -0
  141. package/src/utils/review/index.ts +36 -0
  142. package/src/utils/review/interactiveProcessing.ts +294 -0
  143. package/src/utils/review/progressTracker.ts +296 -0
  144. package/src/utils/review/reviewExtraction.ts +382 -0
  145. package/src/utils/review/types.ts +46 -0
  146. package/src/utils/reviewActionHandler.ts +18 -0
  147. package/src/utils/reviewParser.ts +253 -0
  148. package/src/utils/sanitizer.ts +238 -0
  149. package/src/utils/smartFileSelector.ts +255 -0
  150. package/src/utils/templateLoader.ts +514 -0
  151. package/src/utils/treeGenerator.ts +153 -0
  152. package/tsconfig.build.json +14 -0
  153. package/tsconfig.json +59 -0
@@ -0,0 +1,378 @@
1
+ /**
2
+ * @fileoverview Review context for maintaining state between review passes.
3
+ *
4
+ * This module provides a class for maintaining context between multiple review passes.
5
+ * It stores information about the review, including file metadata, important code elements,
6
+ * and findings from previous passes.
7
+ */
8
+
9
+ import type { FileInfo } from '../../types/review';
10
+
11
+ /**
12
+ * Type of code element tracked in the context
13
+ */
14
+ export enum CodeElementType {
15
+ Function = 'function',
16
+ Class = 'class',
17
+ Interface = 'interface',
18
+ Variable = 'variable',
19
+ Import = 'import',
20
+ ExportedItem = 'exported',
21
+ Component = 'component',
22
+ EntryPoint = 'entryPoint',
23
+ }
24
+
25
+ /**
26
+ * A code element tracked in the context
27
+ */
28
+ export interface CodeElement {
29
+ /** Type of code element */
30
+ type: CodeElementType;
31
+ /** Name of the code element */
32
+ name: string;
33
+ /** File where the element is defined */
34
+ file: string;
35
+ /** Short description or signature of the element */
36
+ signature?: string;
37
+ /** Importance score (higher = more important) */
38
+ importance: number;
39
+ }
40
+
41
+ /**
42
+ * A review finding from a previous pass
43
+ */
44
+ export interface ReviewFinding {
45
+ /** Type of finding (e.g., 'bug', 'security', 'performance') */
46
+ type: string;
47
+ /** Short description of the finding */
48
+ description: string;
49
+ /** File where the finding was located */
50
+ file?: string;
51
+ /** Severity of the finding (higher = more severe) */
52
+ severity: number;
53
+ /** Pass number where this finding was identified */
54
+ passNumber: number;
55
+ }
56
+
57
+ /**
58
+ * Summary of a file from previous review passes
59
+ */
60
+ export interface FileSummary {
61
+ /** Path to the file */
62
+ path: string;
63
+ /** File type or extension */
64
+ type: string;
65
+ /** Short description of the file purpose */
66
+ description: string;
67
+ /** Key elements in this file (e.g., classes, functions) */
68
+ keyElements: string[];
69
+ /** Pass number when this summary was created */
70
+ passNumber: number;
71
+ }
72
+
73
+ /**
74
+ * Context for multi-pass reviews
75
+ */
76
+ export class ReviewContext {
77
+ /** Project name */
78
+ private projectName: string;
79
+ /** Review type */
80
+ private reviewType: string;
81
+ /** All files involved in the review */
82
+ private allFiles: string[];
83
+ /** Current pass number */
84
+ private currentPass: number;
85
+ /** Important code elements tracked across passes */
86
+ private codeElements: Map<string, CodeElement>;
87
+ /** Findings from previous passes */
88
+ private findings: ReviewFinding[];
89
+ /** File summaries from previous passes */
90
+ private fileSummaries: Map<string, FileSummary>;
91
+ /** General notes about the codebase */
92
+ private generalNotes: string[];
93
+ /** Timestamp when context was created */
94
+ private createdAt: Date;
95
+ /** Timestamp of last update */
96
+ private updatedAt: Date;
97
+
98
+ /**
99
+ * Create a new review context
100
+ * @param projectName Name of the project
101
+ * @param reviewType Type of review
102
+ * @param files Files involved in the review
103
+ */
104
+ constructor(projectName: string, reviewType: string, files: FileInfo[]) {
105
+ this.projectName = projectName;
106
+ this.reviewType = reviewType;
107
+ this.allFiles = files.map((f) => f.path);
108
+ this.currentPass = 0;
109
+ this.codeElements = new Map();
110
+ this.findings = [];
111
+ this.fileSummaries = new Map();
112
+ this.generalNotes = [];
113
+ this.createdAt = new Date();
114
+ this.updatedAt = new Date();
115
+ }
116
+
117
+ /**
118
+ * Start a new review pass
119
+ * @returns Updated pass number
120
+ */
121
+ public startPass(): number {
122
+ this.currentPass++;
123
+ this.updatedAt = new Date();
124
+ return this.currentPass;
125
+ }
126
+
127
+ /**
128
+ * Get the current pass number
129
+ * @returns Current pass number
130
+ */
131
+ public getCurrentPass(): number {
132
+ return this.currentPass;
133
+ }
134
+
135
+ /**
136
+ * Add a code element to the context
137
+ * @param element Code element to add
138
+ */
139
+ public addCodeElement(element: CodeElement): void {
140
+ const key = `${element.type}:${element.file}:${element.name}`;
141
+ this.codeElements.set(key, element);
142
+ this.updatedAt = new Date();
143
+ }
144
+
145
+ /**
146
+ * Get all tracked code elements
147
+ * @returns Array of code elements
148
+ */
149
+ public getCodeElements(): CodeElement[] {
150
+ return Array.from(this.codeElements.values());
151
+ }
152
+
153
+ /**
154
+ * Get code elements of a specific type
155
+ * @param type Type of code elements to get
156
+ * @returns Array of code elements of the specified type
157
+ */
158
+ public getCodeElementsByType(type: CodeElementType): CodeElement[] {
159
+ return this.getCodeElements().filter((el) => el.type === type);
160
+ }
161
+
162
+ /**
163
+ * Get code elements in a specific file
164
+ * @param filePath Path of the file
165
+ * @returns Array of code elements in the file
166
+ */
167
+ public getCodeElementsInFile(filePath: string): CodeElement[] {
168
+ return this.getCodeElements().filter((el) => el.file === filePath);
169
+ }
170
+
171
+ /**
172
+ * Add a review finding
173
+ * @param finding Review finding to add
174
+ */
175
+ public addFinding(finding: ReviewFinding): void {
176
+ this.findings.push({
177
+ ...finding,
178
+ passNumber: this.currentPass,
179
+ });
180
+ this.updatedAt = new Date();
181
+ }
182
+
183
+ /**
184
+ * Get all findings
185
+ * @returns Array of all findings
186
+ */
187
+ public getFindings(): ReviewFinding[] {
188
+ return [...this.findings];
189
+ }
190
+
191
+ /**
192
+ * Add or update a file summary
193
+ * @param summary File summary to add
194
+ */
195
+ public addFileSummary(summary: FileSummary): void {
196
+ this.fileSummaries.set(summary.path, {
197
+ ...summary,
198
+ passNumber: this.currentPass,
199
+ });
200
+ this.updatedAt = new Date();
201
+ }
202
+
203
+ /**
204
+ * Get summary for a specific file
205
+ * @param filePath Path of the file
206
+ * @returns File summary or undefined if not found
207
+ */
208
+ public getFileSummary(filePath: string): FileSummary | undefined {
209
+ return this.fileSummaries.get(filePath);
210
+ }
211
+
212
+ /**
213
+ * Get summaries for all files
214
+ * @returns Array of file summaries
215
+ */
216
+ public getAllFileSummaries(): FileSummary[] {
217
+ return Array.from(this.fileSummaries.values());
218
+ }
219
+
220
+ /**
221
+ * Add a general note about the codebase
222
+ * @param note Note to add
223
+ */
224
+ public addGeneralNote(note: string): void {
225
+ this.generalNotes.push(note);
226
+ this.updatedAt = new Date();
227
+ }
228
+
229
+ /**
230
+ * Get all general notes
231
+ * @returns Array of general notes
232
+ */
233
+ public getGeneralNotes(): string[] {
234
+ return [...this.generalNotes];
235
+ }
236
+
237
+ /**
238
+ * Generate a contextual prompt for the next pass
239
+ * @param files Files to include in the next pass
240
+ * @param maxContextLength Maximum length of context in characters
241
+ * @returns Formatted context string for inclusion in the next prompt
242
+ */
243
+ public generateNextPassContext(files: string[], maxContextLength = 2000): string {
244
+ let context = `
245
+ ### Review Context (Pass ${this.currentPass})
246
+
247
+ Project: ${this.projectName}
248
+ Review Type: ${this.reviewType}
249
+ Files in this pass: ${files.length} / ${this.allFiles.length}
250
+
251
+ `;
252
+
253
+ // Add important findings from previous passes
254
+ const importantFindings = this.findings.sort((a, b) => b.severity - a.severity).slice(0, 5);
255
+
256
+ if (importantFindings.length > 0) {
257
+ context += '#### Key Findings from Previous Passes\n\n';
258
+ importantFindings.forEach((finding) => {
259
+ context += `- [${finding.type.toUpperCase()}] ${finding.description}${finding.file ? ` (in ${finding.file})` : ''}\n`;
260
+ });
261
+ context += '\n';
262
+ }
263
+
264
+ // Add summaries of files that are related but not in this pass
265
+ const relatedFiles = this.getAllFileSummaries()
266
+ .filter((summary) => !files.includes(summary.path))
267
+ .slice(0, 5);
268
+
269
+ if (relatedFiles.length > 0) {
270
+ context += '#### Related Files (Not in This Pass)\n\n';
271
+ relatedFiles.forEach((file) => {
272
+ context += `- ${file.path}: ${file.description}\n`;
273
+ if (file.keyElements.length > 0) {
274
+ context += ` Key elements: ${file.keyElements.join(', ')}\n`;
275
+ }
276
+ });
277
+ context += '\n';
278
+ }
279
+
280
+ // Add important code elements relevant to this pass
281
+ const relevantElements = this.getCodeElements()
282
+ .filter((el) => files.includes(el.file) || el.importance > 7)
283
+ .sort((a, b) => b.importance - a.importance)
284
+ .slice(0, 10);
285
+
286
+ if (relevantElements.length > 0) {
287
+ context += '#### Important Code Elements\n\n';
288
+ relevantElements.forEach((element) => {
289
+ context += `- ${element.type} \`${element.name}\`${element.signature ? `: ${element.signature}` : ''} (in ${element.file})\n`;
290
+ });
291
+ context += '\n';
292
+ }
293
+
294
+ // Add general notes
295
+ if (this.generalNotes.length > 0) {
296
+ context += '#### General Notes\n\n';
297
+ this.generalNotes.slice(0, 3).forEach((note) => {
298
+ context += `- ${note}\n`;
299
+ });
300
+ context += '\n';
301
+ }
302
+
303
+ // Truncate if too long
304
+ if (context.length > maxContextLength) {
305
+ context = `${context.substring(0, maxContextLength - 3)}...`;
306
+ }
307
+
308
+ return context;
309
+ }
310
+
311
+ /**
312
+ * Serialize the context to JSON
313
+ * @returns JSON representation of the context
314
+ */
315
+ public toJSON(): object {
316
+ return {
317
+ projectName: this.projectName,
318
+ reviewType: this.reviewType,
319
+ currentPass: this.currentPass,
320
+ codeElements: Array.from(this.codeElements.values()),
321
+ findings: this.findings,
322
+ fileSummaries: Array.from(this.fileSummaries.values()),
323
+ generalNotes: this.generalNotes,
324
+ createdAt: this.createdAt.toISOString(),
325
+ updatedAt: this.updatedAt.toISOString(),
326
+ };
327
+ }
328
+
329
+ /**
330
+ * Create a review context from JSON
331
+ * @param json JSON object
332
+ * @returns New ReviewContext instance
333
+ */
334
+ public static fromJSON(json: Record<string, unknown>): ReviewContext {
335
+ const context = new ReviewContext(
336
+ json.projectName as string,
337
+ json.reviewType as string,
338
+ (json.allFiles as any[]) || [],
339
+ );
340
+
341
+ context.currentPass = (json.currentPass as number) || 0;
342
+
343
+ // Restore code elements
344
+ if (Array.isArray(json.codeElements)) {
345
+ json.codeElements.forEach((element: CodeElement) => {
346
+ context.addCodeElement(element);
347
+ });
348
+ }
349
+
350
+ // Restore findings
351
+ if (Array.isArray(json.findings)) {
352
+ context.findings = json.findings;
353
+ }
354
+
355
+ // Restore file summaries
356
+ if (Array.isArray(json.fileSummaries)) {
357
+ json.fileSummaries.forEach((summary: FileSummary) => {
358
+ context.fileSummaries.set(summary.path, summary);
359
+ });
360
+ }
361
+
362
+ // Restore general notes
363
+ if (Array.isArray(json.generalNotes)) {
364
+ context.generalNotes = json.generalNotes;
365
+ }
366
+
367
+ // Restore timestamps
368
+ if (json.createdAt) {
369
+ context.createdAt = new Date(json.createdAt as string);
370
+ }
371
+
372
+ if (json.updatedAt) {
373
+ context.updatedAt = new Date(json.updatedAt as string);
374
+ }
375
+
376
+ return context;
377
+ }
378
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @fileoverview Context management exports.
3
+ *
4
+ * This module exports all context management related functions and types.
5
+ */
6
+
7
+ export * from './ReviewContext';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @fileoverview Analysis module exports.
3
+ *
4
+ * This module exports all analysis related functions and types.
5
+ */
6
+
7
+ export * from './context';
8
+ export * from './tokens';
@@ -0,0 +1,154 @@
1
+ /**
2
+ * @fileoverview Formatter for token analysis results.
3
+ *
4
+ * This module provides utilities for formatting token analysis results into
5
+ * human-readable strings and other formats for display or logging.
6
+ */
7
+
8
+ import type { FileTokenAnalysis, TokenAnalysisResult } from './TokenAnalyzer';
9
+
10
+ /**
11
+ * Format a number with commas as thousands separators
12
+ * @param num Number to format
13
+ * @returns Formatted number string
14
+ */
15
+ function formatNumber(num: number): string {
16
+ return num.toLocaleString();
17
+ }
18
+
19
+ /**
20
+ * Format a file size in bytes to a human-readable string
21
+ * @param bytes Size in bytes
22
+ * @returns Formatted size string (e.g., "1.23 KB")
23
+ */
24
+ function formatFileSize(bytes: number): string {
25
+ if (bytes < 1024) {
26
+ return `${bytes} B`;
27
+ }
28
+ if (bytes < 1024 * 1024) {
29
+ return `${(bytes / 1024).toFixed(2)} KB`;
30
+ }
31
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
32
+ }
33
+
34
+ /**
35
+ * Format token analysis result as a human-readable string
36
+ * @param analysis Token analysis result
37
+ * @param modelName Name of the model used
38
+ * @param includedFiles Whether to include detailed file listing
39
+ * @returns Formatted string representation of the analysis
40
+ */
41
+ export function formatTokenAnalysis(
42
+ analysis: TokenAnalysisResult,
43
+ modelName: string,
44
+ includeFiles = false,
45
+ ): string {
46
+ // Extract provider and model from both colon and slash formats
47
+ // Handle formats: "provider:model", "provider/model", or just "model"
48
+ let provider: string | undefined;
49
+ let model: string;
50
+
51
+ if (modelName.includes(':')) {
52
+ // Traditional format: "provider:model"
53
+ [provider, model] = modelName.split(':', 2);
54
+ } else if (modelName.includes('/')) {
55
+ // OpenRouter format: "provider/model"
56
+ [provider, model] = modelName.split('/', 2);
57
+ } else {
58
+ // Just model name
59
+ provider = undefined;
60
+ model = modelName;
61
+ }
62
+
63
+ const displayModel = model || modelName;
64
+ const displayProvider = provider
65
+ ? `${provider.charAt(0).toUpperCase() + provider.slice(1)}`
66
+ : 'Unknown';
67
+
68
+ let output = `
69
+ === Token Analysis Report ===
70
+
71
+ Provider: ${displayProvider}
72
+ Model: ${displayModel}
73
+ Files: ${formatNumber(analysis.fileCount)} (${formatFileSize(analysis.totalSizeInBytes)})
74
+
75
+ Token Information:
76
+ Content Tokens: ${formatNumber(analysis.totalTokens)}
77
+ Prompt Overhead: ${formatNumber(analysis.promptOverheadTokens)}
78
+ Total Estimated Tokens: ${formatNumber(analysis.estimatedTotalTokens)}
79
+ Context Window Size: ${formatNumber(analysis.contextWindowSize)}
80
+
81
+ Context Utilization:
82
+ ${((analysis.estimatedTotalTokens / analysis.contextWindowSize) * 100).toFixed(2)}% of context window used
83
+
84
+ `;
85
+
86
+ // Add chunking information if recommended
87
+ if (analysis.chunkingRecommendation.chunkingRecommended) {
88
+ output += `
89
+ Multi-Pass Analysis:
90
+ Chunking Required: Yes
91
+ Reason: ${analysis.chunkingRecommendation.reason}
92
+ Estimated Passes: ${formatNumber(analysis.estimatedPassesNeeded)}
93
+ `;
94
+
95
+ // Add chunk details
96
+ analysis.chunkingRecommendation.recommendedChunks.forEach((chunk, index) => {
97
+ output += `
98
+ Chunk ${index + 1}:
99
+ Files: ${formatNumber(chunk.files.length)}
100
+ Estimated Tokens: ${formatNumber(chunk.estimatedTokenCount)}
101
+ Priority: ${chunk.priority}
102
+ `;
103
+ });
104
+ } else {
105
+ output += `
106
+ Multi-Pass Analysis:
107
+ Chunking Required: No
108
+ Reason: ${analysis.chunkingRecommendation.reason}
109
+ `;
110
+ }
111
+
112
+ // Add file details if requested
113
+ if (includeFiles) {
114
+ output += `
115
+ File Details:
116
+ `;
117
+
118
+ // Sort files by token count (largest first)
119
+ const sortedFiles = [...analysis.files].sort((a, b) => b.tokenCount - a.tokenCount);
120
+
121
+ sortedFiles.forEach((file) => {
122
+ output += ` ${file.relativePath}:
123
+ Tokens: ${formatNumber(file.tokenCount)}
124
+ Size: ${formatFileSize(file.sizeInBytes)}
125
+ Tokens/Byte: ${file.tokensPerByte.toFixed(2)}
126
+ `;
127
+ });
128
+ }
129
+
130
+ return output;
131
+ }
132
+
133
+ /**
134
+ * Format token analysis result as JSON
135
+ * @param analysis Token analysis result
136
+ * @returns JSON string representation of the analysis
137
+ */
138
+ export function formatTokenAnalysisAsJson(analysis: TokenAnalysisResult): string {
139
+ return JSON.stringify(analysis, null, 2);
140
+ }
141
+
142
+ /**
143
+ * Format a single file token analysis as a string
144
+ * @param fileAnalysis File token analysis
145
+ * @returns Formatted string representation of the file analysis
146
+ */
147
+ export function formatFileTokenAnalysis(fileAnalysis: FileTokenAnalysis): string {
148
+ return `
149
+ File: ${fileAnalysis.relativePath}
150
+ Tokens: ${formatNumber(fileAnalysis.tokenCount)}
151
+ Size: ${formatFileSize(fileAnalysis.sizeInBytes)}
152
+ Tokens/Byte: ${fileAnalysis.tokensPerByte.toFixed(2)}
153
+ `;
154
+ }