tlc-claude-code 1.2.27 → 1.2.29
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/README.md +9 -4
- package/dashboard/dist/components/ActivityFeed.d.ts +17 -0
- package/dashboard/dist/components/ActivityFeed.js +42 -0
- package/dashboard/dist/components/ActivityFeed.test.d.ts +1 -0
- package/dashboard/dist/components/ActivityFeed.test.js +162 -0
- package/dashboard/dist/components/BranchSelector.d.ts +16 -0
- package/dashboard/dist/components/BranchSelector.js +49 -0
- package/dashboard/dist/components/BranchSelector.test.d.ts +1 -0
- package/dashboard/dist/components/BranchSelector.test.js +166 -0
- package/dashboard/dist/components/CommandPalette.d.ts +17 -0
- package/dashboard/dist/components/CommandPalette.js +118 -0
- package/dashboard/dist/components/CommandPalette.test.d.ts +1 -0
- package/dashboard/dist/components/CommandPalette.test.js +181 -0
- package/dashboard/dist/components/ConnectionStatus.d.ts +16 -0
- package/dashboard/dist/components/ConnectionStatus.js +27 -0
- package/dashboard/dist/components/ConnectionStatus.test.d.ts +1 -0
- package/dashboard/dist/components/ConnectionStatus.test.js +121 -0
- package/dashboard/dist/components/DeviceFrame.d.ts +19 -0
- package/dashboard/dist/components/DeviceFrame.js +52 -0
- package/dashboard/dist/components/DeviceFrame.test.d.ts +1 -0
- package/dashboard/dist/components/DeviceFrame.test.js +118 -0
- package/dashboard/dist/components/EnvironmentBadge.d.ts +11 -0
- package/dashboard/dist/components/EnvironmentBadge.js +16 -0
- package/dashboard/dist/components/EnvironmentBadge.test.d.ts +1 -0
- package/dashboard/dist/components/EnvironmentBadge.test.js +102 -0
- package/dashboard/dist/components/FocusIndicator.d.ts +19 -0
- package/dashboard/dist/components/FocusIndicator.js +47 -0
- package/dashboard/dist/components/FocusIndicator.test.d.ts +1 -0
- package/dashboard/dist/components/FocusIndicator.test.js +117 -0
- package/dashboard/dist/components/KeyboardHelp.d.ts +15 -0
- package/dashboard/dist/components/KeyboardHelp.js +61 -0
- package/dashboard/dist/components/KeyboardHelp.test.d.ts +1 -0
- package/dashboard/dist/components/KeyboardHelp.test.js +131 -0
- package/dashboard/dist/components/LogSearch.d.ts +13 -0
- package/dashboard/dist/components/LogSearch.js +43 -0
- package/dashboard/dist/components/LogSearch.test.d.ts +1 -0
- package/dashboard/dist/components/LogSearch.test.js +100 -0
- package/dashboard/dist/components/LogStream.d.ts +21 -0
- package/dashboard/dist/components/LogStream.js +123 -0
- package/dashboard/dist/components/LogStream.test.d.ts +1 -0
- package/dashboard/dist/components/LogStream.test.js +159 -0
- package/dashboard/dist/components/PreviewPanel.d.ts +18 -0
- package/dashboard/dist/components/PreviewPanel.js +73 -0
- package/dashboard/dist/components/PreviewPanel.test.d.ts +1 -0
- package/dashboard/dist/components/PreviewPanel.test.js +124 -0
- package/dashboard/dist/components/ProjectCard.d.ts +18 -0
- package/dashboard/dist/components/ProjectCard.js +19 -0
- package/dashboard/dist/components/ProjectCard.test.d.ts +1 -0
- package/dashboard/dist/components/ProjectCard.test.js +53 -0
- package/dashboard/dist/components/ProjectDetail.d.ts +44 -0
- package/dashboard/dist/components/ProjectDetail.js +65 -0
- package/dashboard/dist/components/ProjectDetail.test.d.ts +1 -0
- package/dashboard/dist/components/ProjectDetail.test.js +196 -0
- package/dashboard/dist/components/ProjectList.d.ts +11 -0
- package/dashboard/dist/components/ProjectList.js +62 -0
- package/dashboard/dist/components/ProjectList.test.d.ts +1 -0
- package/dashboard/dist/components/ProjectList.test.js +93 -0
- package/dashboard/dist/components/SettingsPanel.d.ts +32 -0
- package/dashboard/dist/components/SettingsPanel.js +154 -0
- package/dashboard/dist/components/SettingsPanel.test.d.ts +1 -0
- package/dashboard/dist/components/SettingsPanel.test.js +196 -0
- package/dashboard/dist/components/StatusBar.d.ts +16 -0
- package/dashboard/dist/components/StatusBar.js +47 -0
- package/dashboard/dist/components/StatusBar.test.d.ts +1 -0
- package/dashboard/dist/components/StatusBar.test.js +123 -0
- package/dashboard/dist/components/TaskBoard.d.ts +22 -0
- package/dashboard/dist/components/TaskBoard.js +102 -0
- package/dashboard/dist/components/TaskBoard.test.d.ts +1 -0
- package/dashboard/dist/components/TaskBoard.test.js +113 -0
- package/dashboard/dist/components/TaskCard.d.ts +17 -0
- package/dashboard/dist/components/TaskCard.js +29 -0
- package/dashboard/dist/components/TaskCard.test.d.ts +1 -0
- package/dashboard/dist/components/TaskCard.test.js +109 -0
- package/dashboard/dist/components/TaskDetail.d.ts +36 -0
- package/dashboard/dist/components/TaskDetail.js +41 -0
- package/dashboard/dist/components/TaskDetail.test.d.ts +1 -0
- package/dashboard/dist/components/TaskDetail.test.js +164 -0
- package/dashboard/dist/components/TaskFilter.d.ts +12 -0
- package/dashboard/dist/components/TaskFilter.js +138 -0
- package/dashboard/dist/components/TaskFilter.test.d.ts +1 -0
- package/dashboard/dist/components/TaskFilter.test.js +109 -0
- package/dashboard/dist/components/TeamPanel.d.ts +15 -0
- package/dashboard/dist/components/TeamPanel.js +24 -0
- package/dashboard/dist/components/TeamPanel.test.d.ts +1 -0
- package/dashboard/dist/components/TeamPanel.test.js +109 -0
- package/dashboard/dist/components/TeamPresence.d.ts +14 -0
- package/dashboard/dist/components/TeamPresence.js +31 -0
- package/dashboard/dist/components/TeamPresence.test.d.ts +1 -0
- package/dashboard/dist/components/TeamPresence.test.js +144 -0
- package/dashboard/dist/components/layout/Header.d.ts +9 -0
- package/dashboard/dist/components/layout/Header.js +11 -0
- package/dashboard/dist/components/layout/Header.test.d.ts +1 -0
- package/dashboard/dist/components/layout/Header.test.js +35 -0
- package/dashboard/dist/components/layout/Shell.d.ts +10 -0
- package/dashboard/dist/components/layout/Shell.js +5 -0
- package/dashboard/dist/components/layout/Shell.test.d.ts +1 -0
- package/dashboard/dist/components/layout/Shell.test.js +34 -0
- package/dashboard/dist/components/layout/Sidebar.d.ts +14 -0
- package/dashboard/dist/components/layout/Sidebar.js +8 -0
- package/dashboard/dist/components/layout/Sidebar.test.d.ts +1 -0
- package/dashboard/dist/components/layout/Sidebar.test.js +40 -0
- package/dashboard/dist/components/ui/Badge.d.ts +9 -0
- package/dashboard/dist/components/ui/Badge.js +13 -0
- package/dashboard/dist/components/ui/Badge.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Badge.test.js +69 -0
- package/dashboard/dist/components/ui/Button.d.ts +12 -0
- package/dashboard/dist/components/ui/Button.js +14 -0
- package/dashboard/dist/components/ui/Button.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Button.test.js +81 -0
- package/dashboard/dist/components/ui/Card.d.ts +21 -0
- package/dashboard/dist/components/ui/Card.js +20 -0
- package/dashboard/dist/components/ui/Card.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Card.test.js +82 -0
- package/dashboard/dist/components/ui/Input.d.ts +13 -0
- package/dashboard/dist/components/ui/Input.js +8 -0
- package/dashboard/dist/components/ui/Input.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Input.test.js +68 -0
- package/dashboard/dist/styles/tokens.d.ts +150 -0
- package/dashboard/dist/styles/tokens.js +184 -0
- package/dashboard/dist/styles/tokens.test.d.ts +1 -0
- package/dashboard/dist/styles/tokens.test.js +95 -0
- package/dashboard/dist/test/setup.d.ts +1 -0
- package/dashboard/dist/test/setup.js +1 -0
- package/dashboard/package.json +3 -0
- package/package.json +15 -4
- package/scripts/capture-screenshots.js +170 -0
- package/scripts/docs-update.js +253 -0
- package/scripts/generate-screenshots.js +321 -0
- package/scripts/project-docs.js +377 -0
- package/scripts/vps-setup.sh +477 -0
- package/server/lib/adapters/base-adapter.js +114 -0
- package/server/lib/adapters/base-adapter.test.js +90 -0
- package/server/lib/adapters/claude-adapter.js +141 -0
- package/server/lib/adapters/claude-adapter.test.js +180 -0
- package/server/lib/adapters/deepseek-adapter.js +153 -0
- package/server/lib/adapters/deepseek-adapter.test.js +193 -0
- package/server/lib/adapters/openai-adapter.js +190 -0
- package/server/lib/adapters/openai-adapter.test.js +231 -0
- package/server/lib/budget-tracker.js +169 -0
- package/server/lib/budget-tracker.test.js +165 -0
- package/server/lib/claude-injector.js +85 -0
- package/server/lib/claude-injector.test.js +161 -0
- package/server/lib/consensus-engine.js +135 -0
- package/server/lib/consensus-engine.test.js +152 -0
- package/server/lib/context-builder.js +112 -0
- package/server/lib/context-builder.test.js +120 -0
- package/server/lib/file-collector.js +322 -0
- package/server/lib/file-collector.test.js +307 -0
- package/server/lib/memory-classifier.js +175 -0
- package/server/lib/memory-classifier.test.js +169 -0
- package/server/lib/memory-committer.js +138 -0
- package/server/lib/memory-committer.test.js +136 -0
- package/server/lib/memory-hooks.js +127 -0
- package/server/lib/memory-hooks.test.js +136 -0
- package/server/lib/memory-init.js +104 -0
- package/server/lib/memory-init.test.js +119 -0
- package/server/lib/memory-observer.js +149 -0
- package/server/lib/memory-observer.test.js +158 -0
- package/server/lib/memory-reader.js +243 -0
- package/server/lib/memory-reader.test.js +216 -0
- package/server/lib/memory-storage.js +120 -0
- package/server/lib/memory-storage.test.js +136 -0
- package/server/lib/memory-writer.js +176 -0
- package/server/lib/memory-writer.test.js +231 -0
- package/server/lib/overdrive-command.js +30 -6
- package/server/lib/overdrive-command.test.js +8 -1
- package/server/lib/pattern-detector.js +216 -0
- package/server/lib/pattern-detector.test.js +241 -0
- package/server/lib/relevance-scorer.js +175 -0
- package/server/lib/relevance-scorer.test.js +107 -0
- package/server/lib/review-command.js +238 -0
- package/server/lib/review-command.test.js +245 -0
- package/server/lib/review-orchestrator.js +273 -0
- package/server/lib/review-orchestrator.test.js +300 -0
- package/server/lib/review-reporter.js +288 -0
- package/server/lib/review-reporter.test.js +240 -0
- package/server/lib/session-summary.js +90 -0
- package/server/lib/session-summary.test.js +156 -0
- package/templates/docs-sync.yml +91 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Command - /tlc:review implementation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { ReviewOrchestrator } = require('./review-orchestrator.js');
|
|
8
|
+
const { generateReport } = require('./review-reporter.js');
|
|
9
|
+
const { ClaudeAdapter } = require('./adapters/claude-adapter.js');
|
|
10
|
+
const { OpenAIAdapter } = require('./adapters/openai-adapter.js');
|
|
11
|
+
const { DeepSeekAdapter } = require('./adapters/deepseek-adapter.js');
|
|
12
|
+
const { BudgetTracker } = require('./budget-tracker.js');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse command arguments
|
|
16
|
+
* @param {string} args - Command arguments
|
|
17
|
+
* @returns {Object} Parsed options
|
|
18
|
+
*/
|
|
19
|
+
function parseArgs(args) {
|
|
20
|
+
const options = {
|
|
21
|
+
file: null,
|
|
22
|
+
dir: null,
|
|
23
|
+
format: 'md',
|
|
24
|
+
output: null,
|
|
25
|
+
models: ['claude', 'openai', 'deepseek'],
|
|
26
|
+
extensions: [],
|
|
27
|
+
consensusType: 'majority',
|
|
28
|
+
verbose: false,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (!args) return options;
|
|
32
|
+
|
|
33
|
+
const parts = args.split(/\s+/);
|
|
34
|
+
let i = 0;
|
|
35
|
+
|
|
36
|
+
while (i < parts.length) {
|
|
37
|
+
const part = parts[i];
|
|
38
|
+
|
|
39
|
+
if (part === '--file' || part === '-f') {
|
|
40
|
+
options.file = parts[++i];
|
|
41
|
+
} else if (part === '--dir' || part === '-d') {
|
|
42
|
+
options.dir = parts[++i];
|
|
43
|
+
} else if (part === '--format') {
|
|
44
|
+
options.format = parts[++i];
|
|
45
|
+
} else if (part === '--output' || part === '-o') {
|
|
46
|
+
options.output = parts[++i];
|
|
47
|
+
} else if (part === '--models' || part === '-m') {
|
|
48
|
+
options.models = parts[++i].split(',').map(m => m.trim().toLowerCase());
|
|
49
|
+
} else if (part === '--ext' || part === '--extensions') {
|
|
50
|
+
options.extensions = parts[++i].split(',').map(e => e.trim());
|
|
51
|
+
} else if (part === '--consensus') {
|
|
52
|
+
options.consensusType = parts[++i];
|
|
53
|
+
} else if (part === '--verbose' || part === '-v') {
|
|
54
|
+
options.verbose = true;
|
|
55
|
+
} else if (!part.startsWith('-') && !options.file && !options.dir) {
|
|
56
|
+
// Positional argument - treat as file or dir
|
|
57
|
+
if (fs.existsSync(part)) {
|
|
58
|
+
const stat = fs.statSync(part);
|
|
59
|
+
if (stat.isDirectory()) {
|
|
60
|
+
options.dir = part;
|
|
61
|
+
} else {
|
|
62
|
+
options.file = part;
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
options.file = part; // Let it fail later with proper error
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return options;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Create adapters based on model selection
|
|
77
|
+
* @param {string[]} models - Model names to use
|
|
78
|
+
* @param {Object} config - Configuration (budgets, etc.)
|
|
79
|
+
* @returns {Array} Adapter instances
|
|
80
|
+
*/
|
|
81
|
+
function createAdapters(models, config = {}) {
|
|
82
|
+
const budgetTracker = config.budgetTracker || null;
|
|
83
|
+
const adapters = [];
|
|
84
|
+
|
|
85
|
+
for (const model of models) {
|
|
86
|
+
switch (model.toLowerCase()) {
|
|
87
|
+
case 'claude':
|
|
88
|
+
adapters.push(new ClaudeAdapter({
|
|
89
|
+
budgetTracker,
|
|
90
|
+
budget: config.claudeBudget,
|
|
91
|
+
trackCost: config.trackCost !== false,
|
|
92
|
+
}));
|
|
93
|
+
break;
|
|
94
|
+
case 'openai':
|
|
95
|
+
adapters.push(new OpenAIAdapter({
|
|
96
|
+
budgetTracker,
|
|
97
|
+
budget: config.openaiBudget,
|
|
98
|
+
}));
|
|
99
|
+
break;
|
|
100
|
+
case 'deepseek':
|
|
101
|
+
adapters.push(new DeepSeekAdapter({
|
|
102
|
+
budgetTracker,
|
|
103
|
+
budget: config.deepseekBudget,
|
|
104
|
+
}));
|
|
105
|
+
break;
|
|
106
|
+
default:
|
|
107
|
+
console.warn(`Unknown model: ${model}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return adapters;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Execute review command
|
|
116
|
+
* @param {string} args - Command arguments
|
|
117
|
+
* @param {Object} context - Execution context
|
|
118
|
+
* @returns {Promise<Object>} Command result
|
|
119
|
+
*/
|
|
120
|
+
async function executeReview(args, context = {}) {
|
|
121
|
+
const options = parseArgs(args);
|
|
122
|
+
|
|
123
|
+
// Validate target
|
|
124
|
+
if (!options.file && !options.dir) {
|
|
125
|
+
return {
|
|
126
|
+
success: false,
|
|
127
|
+
error: 'No target specified. Use --file <path> or --dir <path>',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Create adapters
|
|
132
|
+
const adapters = createAdapters(options.models, context);
|
|
133
|
+
|
|
134
|
+
if (adapters.length === 0) {
|
|
135
|
+
return {
|
|
136
|
+
success: false,
|
|
137
|
+
error: 'No valid models specified',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Create orchestrator
|
|
142
|
+
const orchestrator = new ReviewOrchestrator(adapters, {
|
|
143
|
+
consensusType: options.consensusType,
|
|
144
|
+
requireMinimum: 1,
|
|
145
|
+
budgetAware: true,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Show available models
|
|
149
|
+
const availableModels = orchestrator.getAvailableModels();
|
|
150
|
+
if (options.verbose) {
|
|
151
|
+
console.log(`Available models: ${availableModels.join(', ')}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Run review
|
|
155
|
+
let results;
|
|
156
|
+
try {
|
|
157
|
+
if (options.file) {
|
|
158
|
+
results = await orchestrator.reviewFile(options.file);
|
|
159
|
+
// Wrap single file result in summary format
|
|
160
|
+
results = orchestrator.summarizeResults(
|
|
161
|
+
[results],
|
|
162
|
+
results.models || [],
|
|
163
|
+
results.costs || { byModel: {}, total: 0 }
|
|
164
|
+
);
|
|
165
|
+
} else {
|
|
166
|
+
results = await orchestrator.reviewDirectory(options.dir, {
|
|
167
|
+
extensions: options.extensions,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
} catch (err) {
|
|
171
|
+
return {
|
|
172
|
+
success: false,
|
|
173
|
+
error: err.message,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Generate report
|
|
178
|
+
const report = generateReport(results, options.format);
|
|
179
|
+
|
|
180
|
+
// Write to file or return
|
|
181
|
+
if (options.output) {
|
|
182
|
+
try {
|
|
183
|
+
fs.writeFileSync(options.output, report, 'utf-8');
|
|
184
|
+
return {
|
|
185
|
+
success: true,
|
|
186
|
+
message: `Report saved to ${options.output}`,
|
|
187
|
+
results,
|
|
188
|
+
outputPath: options.output,
|
|
189
|
+
};
|
|
190
|
+
} catch (err) {
|
|
191
|
+
return {
|
|
192
|
+
success: false,
|
|
193
|
+
error: `Failed to write report: ${err.message}`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
success: true,
|
|
200
|
+
report,
|
|
201
|
+
results,
|
|
202
|
+
summary: {
|
|
203
|
+
files: results.files?.length || 0,
|
|
204
|
+
issues: results.totalIssues || 0,
|
|
205
|
+
cost: results.totalCost || 0,
|
|
206
|
+
models: results.models || [],
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Format summary for display
|
|
213
|
+
* @param {Object} summary - Summary object
|
|
214
|
+
* @returns {string} Formatted summary
|
|
215
|
+
*/
|
|
216
|
+
function formatSummary(summary) {
|
|
217
|
+
const lines = [
|
|
218
|
+
'',
|
|
219
|
+
'═══════════════════════════════════════════════════════════════',
|
|
220
|
+
' Review Summary ',
|
|
221
|
+
'═══════════════════════════════════════════════════════════════',
|
|
222
|
+
'',
|
|
223
|
+
` Files reviewed: ${summary.files}`,
|
|
224
|
+
` Issues found: ${summary.issues}`,
|
|
225
|
+
` Models used: ${summary.models.join(', ')}`,
|
|
226
|
+
` Total cost: $${summary.cost.toFixed(4)}`,
|
|
227
|
+
'',
|
|
228
|
+
'═══════════════════════════════════════════════════════════════',
|
|
229
|
+
];
|
|
230
|
+
return lines.join('\n');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
module.exports = {
|
|
234
|
+
executeReview,
|
|
235
|
+
parseArgs,
|
|
236
|
+
createAdapters,
|
|
237
|
+
formatSummary,
|
|
238
|
+
};
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { executeReview, parseArgs, createAdapters, formatSummary } from './review-command.js';
|
|
6
|
+
|
|
7
|
+
describe('Review Command', () => {
|
|
8
|
+
let testDir;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tlc-review-cmd-'));
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('parseArgs', () => {
|
|
19
|
+
it('parses --file option', () => {
|
|
20
|
+
const options = parseArgs('--file src/index.js');
|
|
21
|
+
expect(options.file).toBe('src/index.js');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('parses -f shorthand', () => {
|
|
25
|
+
const options = parseArgs('-f src/index.js');
|
|
26
|
+
expect(options.file).toBe('src/index.js');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('parses --dir option', () => {
|
|
30
|
+
const options = parseArgs('--dir src');
|
|
31
|
+
expect(options.dir).toBe('src');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('parses -d shorthand', () => {
|
|
35
|
+
const options = parseArgs('-d src');
|
|
36
|
+
expect(options.dir).toBe('src');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('parses --format option', () => {
|
|
40
|
+
const options = parseArgs('--format json');
|
|
41
|
+
expect(options.format).toBe('json');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('parses --output option', () => {
|
|
45
|
+
const options = parseArgs('--output report.md');
|
|
46
|
+
expect(options.output).toBe('report.md');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('parses --models option', () => {
|
|
50
|
+
const options = parseArgs('--models claude,openai');
|
|
51
|
+
expect(options.models).toEqual(['claude', 'openai']);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('parses --ext option', () => {
|
|
55
|
+
const options = parseArgs('--ext .js,.ts');
|
|
56
|
+
expect(options.extensions).toEqual(['.js', '.ts']);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('parses --consensus option', () => {
|
|
60
|
+
const options = parseArgs('--consensus unanimous');
|
|
61
|
+
expect(options.consensusType).toBe('unanimous');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('parses --verbose option', () => {
|
|
65
|
+
const options = parseArgs('--verbose');
|
|
66
|
+
expect(options.verbose).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('parses positional file argument', () => {
|
|
70
|
+
const filePath = path.join(testDir, 'test.js');
|
|
71
|
+
fs.writeFileSync(filePath, 'code');
|
|
72
|
+
|
|
73
|
+
const options = parseArgs(filePath);
|
|
74
|
+
expect(options.file).toBe(filePath);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('parses positional directory argument', () => {
|
|
78
|
+
const options = parseArgs(testDir);
|
|
79
|
+
expect(options.dir).toBe(testDir);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('returns defaults for empty args', () => {
|
|
83
|
+
const options = parseArgs('');
|
|
84
|
+
expect(options.format).toBe('md');
|
|
85
|
+
expect(options.models).toEqual(['claude', 'openai', 'deepseek']);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('handles multiple options', () => {
|
|
89
|
+
const options = parseArgs('--file test.js --format json --models claude -v');
|
|
90
|
+
expect(options.file).toBe('test.js');
|
|
91
|
+
expect(options.format).toBe('json');
|
|
92
|
+
expect(options.models).toEqual(['claude']);
|
|
93
|
+
expect(options.verbose).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('createAdapters', () => {
|
|
98
|
+
it('creates claude adapter', () => {
|
|
99
|
+
const adapters = createAdapters(['claude']);
|
|
100
|
+
expect(adapters).toHaveLength(1);
|
|
101
|
+
expect(adapters[0].name).toBe('claude');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('creates openai adapter', () => {
|
|
105
|
+
const adapters = createAdapters(['openai']);
|
|
106
|
+
expect(adapters).toHaveLength(1);
|
|
107
|
+
expect(adapters[0].name).toBe('openai');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('creates deepseek adapter', () => {
|
|
111
|
+
const adapters = createAdapters(['deepseek']);
|
|
112
|
+
expect(adapters).toHaveLength(1);
|
|
113
|
+
expect(adapters[0].name).toBe('deepseek');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('creates multiple adapters', () => {
|
|
117
|
+
const adapters = createAdapters(['claude', 'openai', 'deepseek']);
|
|
118
|
+
expect(adapters).toHaveLength(3);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('skips unknown models', () => {
|
|
122
|
+
const adapters = createAdapters(['claude', 'unknown', 'openai']);
|
|
123
|
+
expect(adapters).toHaveLength(2);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('is case insensitive', () => {
|
|
127
|
+
const adapters = createAdapters(['CLAUDE', 'OpenAI']);
|
|
128
|
+
expect(adapters).toHaveLength(2);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('executeReview', () => {
|
|
133
|
+
it('returns error when no target specified', async () => {
|
|
134
|
+
const result = await executeReview('');
|
|
135
|
+
expect(result.success).toBe(false);
|
|
136
|
+
expect(result.error).toContain('No target specified');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('reviews single file', async () => {
|
|
140
|
+
const filePath = path.join(testDir, 'test.js');
|
|
141
|
+
fs.writeFileSync(filePath, 'const x = 1;');
|
|
142
|
+
|
|
143
|
+
const result = await executeReview(`--file ${filePath} --models claude`);
|
|
144
|
+
expect(result.success).toBe(true);
|
|
145
|
+
expect(result.summary.files).toBe(1);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('reviews directory', async () => {
|
|
149
|
+
fs.writeFileSync(path.join(testDir, 'a.js'), 'code');
|
|
150
|
+
fs.writeFileSync(path.join(testDir, 'b.js'), 'code');
|
|
151
|
+
|
|
152
|
+
const result = await executeReview(`--dir ${testDir} --models claude`);
|
|
153
|
+
expect(result.success).toBe(true);
|
|
154
|
+
expect(result.summary.files).toBe(2);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('generates markdown report by default', async () => {
|
|
158
|
+
const filePath = path.join(testDir, 'test.js');
|
|
159
|
+
fs.writeFileSync(filePath, 'const x = 1;');
|
|
160
|
+
|
|
161
|
+
const result = await executeReview(`--file ${filePath} --models claude`);
|
|
162
|
+
expect(result.report).toContain('# Code Review Report');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('generates JSON report when requested', async () => {
|
|
166
|
+
const filePath = path.join(testDir, 'test.js');
|
|
167
|
+
fs.writeFileSync(filePath, 'const x = 1;');
|
|
168
|
+
|
|
169
|
+
const result = await executeReview(`--file ${filePath} --format json --models claude`);
|
|
170
|
+
expect(() => JSON.parse(result.report)).not.toThrow();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('generates HTML report when requested', async () => {
|
|
174
|
+
const filePath = path.join(testDir, 'test.js');
|
|
175
|
+
fs.writeFileSync(filePath, 'const x = 1;');
|
|
176
|
+
|
|
177
|
+
const result = await executeReview(`--file ${filePath} --format html --models claude`);
|
|
178
|
+
expect(result.report).toContain('<!DOCTYPE html>');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('saves report to file when --output specified', async () => {
|
|
182
|
+
const filePath = path.join(testDir, 'test.js');
|
|
183
|
+
const outputPath = path.join(testDir, 'report.md');
|
|
184
|
+
fs.writeFileSync(filePath, 'const x = 1;');
|
|
185
|
+
|
|
186
|
+
const result = await executeReview(`--file ${filePath} --output ${outputPath} --models claude`);
|
|
187
|
+
|
|
188
|
+
expect(result.success).toBe(true);
|
|
189
|
+
expect(fs.existsSync(outputPath)).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('returns summary with cost', async () => {
|
|
193
|
+
const filePath = path.join(testDir, 'test.js');
|
|
194
|
+
fs.writeFileSync(filePath, 'const x = 1;');
|
|
195
|
+
|
|
196
|
+
const result = await executeReview(`--file ${filePath} --models claude`);
|
|
197
|
+
expect(result.summary).toHaveProperty('cost');
|
|
198
|
+
expect(result.summary).toHaveProperty('models');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('filters by extension', async () => {
|
|
202
|
+
fs.writeFileSync(path.join(testDir, 'a.js'), 'code');
|
|
203
|
+
fs.writeFileSync(path.join(testDir, 'b.ts'), 'code');
|
|
204
|
+
|
|
205
|
+
const result = await executeReview(`--dir ${testDir} --ext .js --models claude`);
|
|
206
|
+
expect(result.summary.files).toBe(1);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('uses specified models only', async () => {
|
|
210
|
+
const filePath = path.join(testDir, 'test.js');
|
|
211
|
+
fs.writeFileSync(filePath, 'const x = 1;');
|
|
212
|
+
|
|
213
|
+
const result = await executeReview(`--file ${filePath} --models openai`);
|
|
214
|
+
expect(result.summary.models).toContain('openai');
|
|
215
|
+
expect(result.summary.models).not.toContain('claude');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('returns error for invalid models', async () => {
|
|
219
|
+
const filePath = path.join(testDir, 'test.js');
|
|
220
|
+
fs.writeFileSync(filePath, 'code');
|
|
221
|
+
|
|
222
|
+
const result = await executeReview(`--file ${filePath} --models unknown`);
|
|
223
|
+
expect(result.success).toBe(false);
|
|
224
|
+
expect(result.error).toContain('No valid models');
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('formatSummary', () => {
|
|
229
|
+
it('formats summary for display', () => {
|
|
230
|
+
const summary = {
|
|
231
|
+
files: 5,
|
|
232
|
+
issues: 10,
|
|
233
|
+
models: ['claude', 'openai'],
|
|
234
|
+
cost: 0.0234,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const formatted = formatSummary(summary);
|
|
238
|
+
|
|
239
|
+
expect(formatted).toContain('Files reviewed: 5');
|
|
240
|
+
expect(formatted).toContain('Issues found: 10');
|
|
241
|
+
expect(formatted).toContain('claude, openai');
|
|
242
|
+
expect(formatted).toContain('$0.0234');
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|