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,609 @@
1
+ /**
2
+ * @fileoverview Formatters for Markdown output of code reviews.
3
+ *
4
+ * This module provides functions to format code review results as Markdown,
5
+ * including different formats for structured and unstructured reviews.
6
+ */
7
+
8
+ import type { ReviewResult } from '../../types/review';
9
+ import type { StructuredReview } from '../../types/structuredReview';
10
+ import logger from '../../utils/logger';
11
+ import { sanitizeContent } from '../../utils/sanitizer';
12
+ import { formatIssue, formatSchemaIssue } from './IssueFormatters';
13
+ import { formatCostInfo, formatMetadataSection, parseCostInfo } from './MetadataFormatter';
14
+ import { extractModelInfo, extractModelInfoFromString } from './ModelInfoExtractor';
15
+
16
+ /**
17
+ * Format the review as Markdown
18
+ * @param review Review result to format
19
+ * @returns Markdown string
20
+ */
21
+ export function formatAsMarkdown(review: ReviewResult): string {
22
+ const { filePath, reviewType, content, timestamp, structuredData } = review;
23
+ // Use costInfo if available, fallback to cost
24
+ const cost = review.costInfo || review.cost;
25
+
26
+ // Extract model information
27
+ const { modelInfo } = extractModelInfo(review.modelUsed);
28
+
29
+ // Format cost information if available
30
+ const costInfo = formatCostInfo(cost);
31
+
32
+ // Check if the content is JSON that should be formatted as structured data
33
+ let actualStructuredData = structuredData;
34
+
35
+ // For architectural reviews with diagrams, always prefer Markdown format
36
+ const forceMarkdown = reviewType === 'architectural' && review.metadata?.diagramRequested;
37
+
38
+ if (!actualStructuredData && content && typeof content === 'string' && !forceMarkdown) {
39
+ // Improved JSON detection - check for both raw JSON and code blocks
40
+ const trimmedContent = content.trim();
41
+
42
+ // First, check if this looks like truncated/incomplete JSON
43
+ if (trimmedContent.startsWith('{') && !trimmedContent.includes('}')) {
44
+ logger.warn('Content appears to be truncated JSON - missing closing brace');
45
+
46
+ // Don't try to parse incomplete JSON, just return it as plain text with a warning
47
+ const warningMessage =
48
+ '⚠️ **Warning**: The AI response appears to be incomplete or truncated. ' +
49
+ 'This may be due to token limits or API issues. Please try again or use a different model.\n\n' +
50
+ '**Partial response received:**\n\n';
51
+ return formatSimpleMarkdown(
52
+ `${warningMessage}\`\`\`json\n${content}\n\`\`\``,
53
+ filePath || '',
54
+ reviewType,
55
+ timestamp,
56
+ costInfo,
57
+ modelInfo,
58
+ );
59
+ }
60
+
61
+ // Try to extract JSON from code blocks with improved regex
62
+ // This regex matches code blocks with or without the json language specifier
63
+ const jsonBlockRegex = /```(?:json)?\s*([\s\S]*?)\s*```/g;
64
+ const jsonBlocks = [...trimmedContent.matchAll(jsonBlockRegex)];
65
+
66
+ if (jsonBlocks.length > 0) {
67
+ // Try each code block until we find valid JSON
68
+ for (const match of jsonBlocks) {
69
+ try {
70
+ const jsonContent = match[1].trim();
71
+ if (jsonContent) {
72
+ actualStructuredData = JSON.parse(jsonContent);
73
+ logger.debug('Successfully parsed JSON from code block');
74
+ break;
75
+ }
76
+ } catch (e) {
77
+ logger.debug(
78
+ `Failed to parse JSON from code block: ${e instanceof Error ? e.message : String(e)}`,
79
+ );
80
+ // Continue to next block
81
+ }
82
+ }
83
+ }
84
+
85
+ // If no valid JSON found in code blocks, try the entire content if it looks like JSON
86
+ if (!actualStructuredData && trimmedContent.startsWith('{') && trimmedContent.endsWith('}')) {
87
+ try {
88
+ actualStructuredData = JSON.parse(trimmedContent);
89
+ logger.debug('Successfully parsed JSON from full content');
90
+ } catch (e) {
91
+ logger.debug(
92
+ `Failed to parse content as JSON: ${e instanceof Error ? e.message : String(e)}`,
93
+ );
94
+ // Not valid JSON, continue with regular formatting
95
+ }
96
+ }
97
+ }
98
+
99
+ // If we have structured data, format it as Markdown
100
+ // But skip JSON parsing for architectural reviews with diagrams - keep original Markdown
101
+ if (actualStructuredData && !forceMarkdown) {
102
+ try {
103
+ let structuredReview: any;
104
+
105
+ if (typeof actualStructuredData === 'string') {
106
+ try {
107
+ structuredReview = JSON.parse(actualStructuredData);
108
+ logger.debug('Successfully parsed structured data string as JSON');
109
+ } catch (parseError) {
110
+ logger.warn(
111
+ `Failed to parse structured data as JSON: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
112
+ );
113
+ // If it's not valid JSON, treat it as plain text
114
+ return formatSimpleMarkdown(
115
+ content,
116
+ filePath || '',
117
+ reviewType,
118
+ timestamp,
119
+ costInfo,
120
+ modelInfo,
121
+ );
122
+ }
123
+ } else {
124
+ structuredReview = actualStructuredData;
125
+ }
126
+
127
+ // Check if the data has a 'review' property (our JSON structure)
128
+ if (structuredReview?.review) {
129
+ return formatSchemaBasedReviewAsMarkdown(
130
+ structuredReview,
131
+ filePath || '',
132
+ reviewType,
133
+ timestamp,
134
+ costInfo,
135
+ modelInfo,
136
+ );
137
+ }
138
+
139
+ // Validate the parsed data has expected structure
140
+ if (typeof structuredReview === 'object' && structuredReview !== null) {
141
+ return formatStructuredReviewAsMarkdown(
142
+ structuredReview,
143
+ filePath || '',
144
+ reviewType,
145
+ timestamp,
146
+ costInfo,
147
+ modelInfo,
148
+ );
149
+ }
150
+ logger.warn('Structured data is not an object:', typeof structuredReview);
151
+ // If the data doesn't have the right structure, fall back to plain text
152
+ return formatSimpleMarkdown(
153
+ content,
154
+ filePath || '',
155
+ reviewType,
156
+ timestamp,
157
+ costInfo,
158
+ modelInfo,
159
+ );
160
+ } catch (error) {
161
+ logger.error(
162
+ `Error processing structured review data: ${error instanceof Error ? error.message : String(error)}`,
163
+ );
164
+ // Fall back to unstructured format
165
+ return formatSimpleMarkdown(
166
+ content,
167
+ filePath || '',
168
+ reviewType,
169
+ timestamp,
170
+ costInfo,
171
+ modelInfo,
172
+ );
173
+ }
174
+ }
175
+
176
+ // Sanitize the content to prevent XSS attacks
177
+ const sanitizedContent = sanitizeContent(content);
178
+
179
+ // Use the actual file path for the review title and the reviewed field
180
+ // If filePath is the same as reviewType, is 'consolidated', or is undefined/empty, show the current directory path
181
+ let displayPath = filePath || '';
182
+
183
+ if (!displayPath || displayPath === reviewType || displayPath === 'consolidated') {
184
+ // For consolidated reviews, show the full target directory path
185
+ displayPath = `${process.cwd()} (Current Directory)`;
186
+ }
187
+
188
+ // Extract model vendor and name from modelInfo (currently unused but may be needed for future features)
189
+ // const { modelVendor, modelName } = extractModelInfoFromString(modelInfo);
190
+
191
+ // Create metadata section
192
+ const metadataSection = formatMetadataSection(
193
+ reviewType,
194
+ timestamp,
195
+ modelInfo,
196
+ cost,
197
+ review.toolVersion,
198
+ review.commandOptions,
199
+ review.detectedLanguage,
200
+ review.detectedFramework,
201
+ review.frameworkVersion,
202
+ review.cssFrameworks,
203
+ );
204
+
205
+ return `# Code Review: ${displayPath}
206
+
207
+ > **Review Type**: ${reviewType}
208
+ > **Model**: ${modelInfo}
209
+ > **Generated**: ${new Date(timestamp).toLocaleString()}
210
+
211
+ ---
212
+
213
+ ${metadataSection}
214
+
215
+ ${sanitizedContent}
216
+
217
+ ---${costInfo}
218
+
219
+ *Generated by [cr-aia](https://github.com/filipkustec/cr-aia) using ${modelInfo}*`;
220
+ }
221
+
222
+ /**
223
+ * Format a structured review as Markdown
224
+ * @param structuredReview Structured review data
225
+ * @param filePath Path to the reviewed file
226
+ * @param reviewType Type of review performed
227
+ * @param timestamp Timestamp of when the review was generated
228
+ * @param costInfo Cost information formatted as Markdown
229
+ * @param modelInfo Model information
230
+ * @param metadataSection Optional metadata section to include
231
+ * @returns Markdown string
232
+ */
233
+ export function formatStructuredReviewAsMarkdown(
234
+ structuredReview: StructuredReview,
235
+ filePath: string,
236
+ reviewType: string,
237
+ timestamp: string,
238
+ costInfo: string,
239
+ modelInfo: string,
240
+ metadataSection?: string,
241
+ ): string {
242
+ // Check if the structuredReview has required properties
243
+ if (!structuredReview || typeof structuredReview !== 'object') {
244
+ console.warn('Invalid structured review data, falling back to simple format');
245
+ return formatSimpleMarkdown(
246
+ 'No structured data available. The review may be in an unsupported format.',
247
+ filePath,
248
+ reviewType,
249
+ timestamp,
250
+ costInfo,
251
+ modelInfo,
252
+ metadataSection,
253
+ );
254
+ }
255
+
256
+ // Extract properties with fallbacks for missing properties
257
+ const summary = structuredReview.summary || 'No summary provided';
258
+ const issues = Array.isArray(structuredReview.issues) ? structuredReview.issues : [];
259
+ const recommendations = Array.isArray(structuredReview.recommendations)
260
+ ? structuredReview.recommendations
261
+ : [];
262
+ const positiveAspects = Array.isArray(structuredReview.positiveAspects)
263
+ ? structuredReview.positiveAspects
264
+ : [];
265
+
266
+ // Extract grade information if available
267
+ const grade = structuredReview.grade;
268
+ const gradeCategories = structuredReview.gradeCategories;
269
+
270
+ // Group issues by priority
271
+ const highPriorityIssues = issues.filter((issue) => issue && issue.priority === 'high');
272
+ const mediumPriorityIssues = issues.filter((issue) => issue && issue.priority === 'medium');
273
+ const lowPriorityIssues = issues.filter((issue) => issue && issue.priority === 'low');
274
+
275
+ // Format issues by priority
276
+ let issuesMarkdown = '';
277
+
278
+ if (highPriorityIssues.length > 0) {
279
+ issuesMarkdown += '### High Priority\n\n';
280
+ issuesMarkdown += highPriorityIssues.map((issue) => formatIssue(issue)).join('\n\n');
281
+ issuesMarkdown += '\n\n';
282
+ }
283
+
284
+ if (mediumPriorityIssues.length > 0) {
285
+ issuesMarkdown += '### Medium Priority\n\n';
286
+ issuesMarkdown += mediumPriorityIssues.map((issue) => formatIssue(issue)).join('\n\n');
287
+ issuesMarkdown += '\n\n';
288
+ }
289
+
290
+ if (lowPriorityIssues.length > 0) {
291
+ issuesMarkdown += '### Low Priority\n\n';
292
+ issuesMarkdown += lowPriorityIssues.map((issue) => formatIssue(issue)).join('\n\n');
293
+ issuesMarkdown += '\n\n';
294
+ }
295
+
296
+ // Format recommendations
297
+ let recommendationsMarkdown = '';
298
+ if (recommendations && recommendations.length > 0) {
299
+ recommendationsMarkdown = '## General Recommendations\n\n';
300
+ recommendationsMarkdown += recommendations.map((rec) => `- ${rec}`).join('\n');
301
+ recommendationsMarkdown += '\n\n';
302
+ }
303
+
304
+ // Format positive aspects
305
+ let positiveAspectsMarkdown = '';
306
+ if (positiveAspects && positiveAspects.length > 0) {
307
+ positiveAspectsMarkdown = '## Positive Aspects\n\n';
308
+ positiveAspectsMarkdown += positiveAspects.map((aspect) => `- ${aspect}`).join('\n');
309
+ positiveAspectsMarkdown += '\n\n';
310
+ }
311
+
312
+ // Use the actual file path for the review title and the reviewed field
313
+ // If filePath is the same as reviewType, is 'consolidated', or is undefined/empty, show the current directory path
314
+ let displayPath = filePath || '';
315
+
316
+ if (!displayPath || displayPath === reviewType || displayPath === 'consolidated') {
317
+ // For consolidated reviews, show the full target directory path
318
+ displayPath = `${process.cwd()} (Current Directory)`;
319
+ }
320
+
321
+ // Include metadata section if available
322
+ const metadataContent = metadataSection ? `${metadataSection}\n` : '';
323
+
324
+ // Format grade section if available
325
+ let gradeMarkdown = '';
326
+ if (grade) {
327
+ gradeMarkdown = `## Grade: ${grade}\n\n`;
328
+
329
+ // Add grade categories if available
330
+ if (gradeCategories) {
331
+ if (gradeCategories.functionality)
332
+ gradeMarkdown += `- **Functionality**: ${gradeCategories.functionality}\n`;
333
+ if (gradeCategories.codeQuality)
334
+ gradeMarkdown += `- **Code Quality**: ${gradeCategories.codeQuality}\n`;
335
+ if (gradeCategories.documentation)
336
+ gradeMarkdown += `- **Documentation**: ${gradeCategories.documentation}\n`;
337
+ if (gradeCategories.testing) gradeMarkdown += `- **Testing**: ${gradeCategories.testing}\n`;
338
+ if (gradeCategories.maintainability)
339
+ gradeMarkdown += `- **Maintainability**: ${gradeCategories.maintainability}\n`;
340
+ if (gradeCategories.security)
341
+ gradeMarkdown += `- **Security**: ${gradeCategories.security}\n`;
342
+ if (gradeCategories.performance)
343
+ gradeMarkdown += `- **Performance**: ${gradeCategories.performance}\n`;
344
+ gradeMarkdown += '\n';
345
+ }
346
+ }
347
+
348
+ return `# Code Review: ${displayPath}
349
+
350
+ > **Review Type**: ${reviewType}
351
+ > **Model**: ${modelInfo}
352
+ > **Generated**: ${new Date(timestamp).toLocaleString()}
353
+
354
+ ---
355
+
356
+ ${metadataContent}${gradeMarkdown}## Summary
357
+
358
+ ${summary}
359
+
360
+ ## Issues
361
+
362
+ ${issuesMarkdown}
363
+ ${recommendationsMarkdown}${positiveAspectsMarkdown}---${costInfo}
364
+
365
+ *Generated by [cr-aia](https://github.com/filipkustec/cr-aia) using ${modelInfo}*`;
366
+ }
367
+
368
+ /**
369
+ * Format a simple markdown document with just the content
370
+ * Used as fallback when structured data isn't available
371
+ * @param content Content to include in the document
372
+ * @param filePath Path to the reviewed file
373
+ * @param reviewType Type of review performed
374
+ * @param timestamp Timestamp of when the review was generated
375
+ * @param costInfo Cost information formatted as Markdown
376
+ * @param modelInfo Model information
377
+ * @param metadataSection Optional metadata section to include
378
+ * @returns Markdown string
379
+ */
380
+ export function formatSimpleMarkdown(
381
+ content: string,
382
+ filePath: string,
383
+ reviewType: string,
384
+ timestamp: string,
385
+ costInfo: string,
386
+ modelInfo: string,
387
+ metadataSection?: string,
388
+ ): string {
389
+ // Sanitize the content
390
+ const sanitizedContent = sanitizeContent(content);
391
+
392
+ // Use the actual file path for the review title and the reviewed field
393
+ let displayPath = filePath || '';
394
+
395
+ if (!displayPath || displayPath === reviewType || displayPath === 'consolidated') {
396
+ // For consolidated reviews, show the full target directory path
397
+ displayPath = `${process.cwd()} (Current Directory)`;
398
+ }
399
+
400
+ // Extract model vendor and name from modelInfo
401
+ const { modelVendor, modelName } = extractModelInfoFromString(modelInfo);
402
+
403
+ // Parse cost information if it's available in string form
404
+ const cost = parseCostInfo(costInfo);
405
+
406
+ // Include metadata section if available
407
+ const metadataContent = metadataSection ? `${metadataSection}\n` : '';
408
+
409
+ // Generate a metadata section with model information if not provided
410
+ const modelMetadata = !metadataSection
411
+ ? `## Metadata
412
+ | Property | Value |
413
+ |----------|-------|
414
+ | Review Type | ${reviewType} |
415
+ | Generated At | ${new Date(timestamp).toLocaleString(undefined, {
416
+ year: 'numeric',
417
+ month: 'long',
418
+ day: 'numeric',
419
+ hour: '2-digit',
420
+ minute: '2-digit',
421
+ second: '2-digit',
422
+ timeZoneName: 'short',
423
+ })} |
424
+ | Model Provider | ${modelVendor} |
425
+ | Model Name | ${modelName} |${
426
+ cost
427
+ ? `
428
+ | Input Tokens | ${cost.inputTokens.toLocaleString()} |
429
+ | Output Tokens | ${cost.outputTokens.toLocaleString()} |
430
+ | Total Tokens | ${cost.totalTokens.toLocaleString()} |
431
+ | Estimated Cost | ${cost.formattedCost} |`
432
+ : ''
433
+ }${
434
+ cost?.passCount
435
+ ? `
436
+ | Multi-pass Review | ${cost.passCount} passes |`
437
+ : ''
438
+ }
439
+ `
440
+ : '';
441
+
442
+ // Include this metadata section in all formats for consistency
443
+ const fullMetadataContent = metadataContent || modelMetadata;
444
+
445
+ return `# Code Review: ${displayPath}
446
+
447
+ > **Review Type**: ${reviewType}
448
+ > **Model**: ${modelInfo}
449
+ > **Generated**: ${new Date(timestamp).toLocaleString()}
450
+
451
+ ---
452
+
453
+ ${fullMetadataContent}
454
+
455
+ ${sanitizedContent}
456
+
457
+ ---${costInfo}
458
+
459
+ *Generated by [cr-aia](https://github.com/filipkustec/cr-aia) using ${modelInfo}*`;
460
+ }
461
+
462
+ /**
463
+ * Format a schema-based review (with 'review' property) as Markdown
464
+ * @param schemaReview Schema-based review object
465
+ * @param filePath Path to the reviewed file
466
+ * @param reviewType Type of review
467
+ * @param timestamp Timestamp of the review
468
+ * @param costInfo Cost information string
469
+ * @param modelInfo Model information string
470
+ * @param metadataSection Optional metadata section
471
+ * @returns Formatted markdown string
472
+ */
473
+ export function formatSchemaBasedReviewAsMarkdown(
474
+ schemaReview: any,
475
+ filePath: string,
476
+ reviewType: string,
477
+ timestamp: string,
478
+ costInfo: string,
479
+ modelInfo: string,
480
+ metadataSection?: string,
481
+ ): string {
482
+ // Extract the review object
483
+ const review = schemaReview.review;
484
+ if (!review || typeof review !== 'object') {
485
+ return formatSimpleMarkdown(
486
+ JSON.stringify(schemaReview, null, 2),
487
+ filePath,
488
+ reviewType,
489
+ timestamp,
490
+ costInfo,
491
+ modelInfo,
492
+ metadataSection,
493
+ );
494
+ }
495
+
496
+ // Extract files and issues
497
+ const files = review.files || [];
498
+ const summary = review.summary || {};
499
+
500
+ // Create issues sections by priority
501
+ const highPriorityIssues: any[] = [];
502
+ const mediumPriorityIssues: any[] = [];
503
+ const lowPriorityIssues: any[] = [];
504
+
505
+ // Collect all issues from all files
506
+ files.forEach((file: any) => {
507
+ const issues = file.issues || [];
508
+ issues.forEach((issue: any) => {
509
+ // Add file path to issue for context
510
+ const issueWithFile = { ...issue, filePath: file.filePath };
511
+
512
+ if (issue.priority === 'HIGH') {
513
+ highPriorityIssues.push(issueWithFile);
514
+ } else if (issue.priority === 'MEDIUM') {
515
+ mediumPriorityIssues.push(issueWithFile);
516
+ } else if (issue.priority === 'LOW') {
517
+ lowPriorityIssues.push(issueWithFile);
518
+ }
519
+ });
520
+ });
521
+
522
+ // Format the metadata section
523
+ let displayPath = filePath || '';
524
+ if (!displayPath || displayPath === reviewType || displayPath === 'consolidated') {
525
+ displayPath = `${process.cwd()} (Current Directory)`;
526
+ }
527
+
528
+ // Extract model vendor and name from modelInfo
529
+ const { modelVendor, modelName } = extractModelInfoFromString(modelInfo);
530
+
531
+ // Build the metadata section if not provided
532
+ if (!metadataSection) {
533
+ const formattedDate = new Date(timestamp).toLocaleString(undefined, {
534
+ year: 'numeric',
535
+ month: 'long',
536
+ day: 'numeric',
537
+ hour: '2-digit',
538
+ minute: '2-digit',
539
+ second: '2-digit',
540
+ timeZoneName: 'short',
541
+ });
542
+
543
+ metadataSection = `## Metadata
544
+ | Property | Value |
545
+ |----------|-------|
546
+ | Review Type | ${reviewType} |
547
+ | Generated At | ${formattedDate} |
548
+ | Model Provider | ${modelVendor} |
549
+ | Model Name | ${modelName} |`;
550
+ }
551
+
552
+ let output = `# Code Review: ${displayPath}
553
+
554
+ > **Review Type**: ${reviewType}
555
+ > **Model**: ${modelInfo}
556
+ > **Generated**: ${new Date(timestamp).toLocaleString()}
557
+
558
+ ---
559
+
560
+ ${metadataSection}
561
+
562
+ ## Review Summary
563
+
564
+ `;
565
+
566
+ // Add summary counts
567
+ if (summary.totalIssues > 0) {
568
+ output += `Total issues found: **${summary.totalIssues}**
569
+ - High Priority: ${summary.highPriorityIssues || 0}
570
+ - Medium Priority: ${summary.mediumPriorityIssues || 0}
571
+ - Low Priority: ${summary.lowPriorityIssues || 0}
572
+
573
+ `;
574
+ } else {
575
+ output += `No issues found. The code looks good!\n\n`;
576
+ }
577
+
578
+ // Add issues by priority
579
+ if (highPriorityIssues.length > 0) {
580
+ output += `## High Priority Issues\n\n`;
581
+ highPriorityIssues.forEach((issue, index) => {
582
+ output += formatSchemaIssue(issue, index + 1);
583
+ });
584
+ }
585
+
586
+ if (mediumPriorityIssues.length > 0) {
587
+ output += `## Medium Priority Issues\n\n`;
588
+ mediumPriorityIssues.forEach((issue, index) => {
589
+ output += formatSchemaIssue(issue, index + 1);
590
+ });
591
+ }
592
+
593
+ if (lowPriorityIssues.length > 0) {
594
+ output += `## Low Priority Issues\n\n`;
595
+ lowPriorityIssues.forEach((issue, index) => {
596
+ output += formatSchemaIssue(issue, index + 1);
597
+ });
598
+ }
599
+
600
+ // Add cost information at the end
601
+ if (costInfo) {
602
+ output += `\n${costInfo}\n`;
603
+ }
604
+
605
+ // Add footer with tool information
606
+ output += `\n*Generated by [cr-aia](https://github.com/filipkustec/cr-aia) using ${modelInfo}*`;
607
+
608
+ return output;
609
+ }