archondev 0.1.0 → 1.2.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.
- package/README.md +84 -51
- package/dist/auth-2QIFQZTL.js +12 -0
- package/dist/bug-DXLBBW3U.js +10 -0
- package/dist/{chunk-R6IMTNKV.js → chunk-2CFO5GVH.js} +0 -35
- package/dist/chunk-A7QU6JC6.js +119 -0
- package/dist/chunk-CAYCSBNX.js +202 -0
- package/dist/chunk-EDP55FCI.js +485 -0
- package/dist/chunk-I4ZVNLNO.js +4648 -0
- package/dist/chunk-IMZN36GC.js +159 -0
- package/dist/chunk-JBKFAD4M.js +650 -0
- package/dist/chunk-MOZHC2GX.js +351 -0
- package/dist/chunk-PK3OQVBG.js +91 -0
- package/dist/chunk-QGM4M3NI.js +37 -0
- package/dist/chunk-SMR7JQK6.js +399 -0
- package/dist/chunk-UDBFDXJI.js +696 -0
- package/dist/chunk-UG2ZZ7CM.js +737 -0
- package/dist/chunk-VKM3HAHW.js +832 -0
- package/dist/chunk-WCCBJSNI.js +62 -0
- package/dist/code-review-FSTYDHNG.js +16 -0
- package/dist/execute-LYID2ODD.js +13 -0
- package/dist/index.js +1250 -7206
- package/dist/keys-EL3FUM5O.js +15 -0
- package/dist/list-VXMVEIL5.js +13 -0
- package/dist/{parser-D6PBQUJH.js → parser-M4DI7A24.js} +2 -1
- package/dist/plan-7VSFESVD.js +16 -0
- package/dist/preferences-PL2ON5VY.js +17 -0
- package/dist/review-3R6QXAXQ.js +27 -0
- package/package.json +21 -1
|
@@ -0,0 +1,832 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnthropicClient,
|
|
3
|
+
getDefaultModel
|
|
4
|
+
} from "./chunk-A7QU6JC6.js";
|
|
5
|
+
|
|
6
|
+
// src/core/code-review/database.ts
|
|
7
|
+
import Database from "better-sqlite3";
|
|
8
|
+
import { join, dirname } from "path";
|
|
9
|
+
import { mkdirSync, existsSync } from "fs";
|
|
10
|
+
var REVIEW_DB_PATH = "docs/code-review/review-tasks.db";
|
|
11
|
+
var CREATE_TABLE_SQL = `
|
|
12
|
+
CREATE TABLE IF NOT EXISTS review_tasks (
|
|
13
|
+
id INTEGER PRIMARY KEY,
|
|
14
|
+
|
|
15
|
+
-- Task Definition
|
|
16
|
+
category TEXT NOT NULL,
|
|
17
|
+
subcategory TEXT,
|
|
18
|
+
feature_name TEXT NOT NULL,
|
|
19
|
+
user_story TEXT,
|
|
20
|
+
acceptance_criteria TEXT,
|
|
21
|
+
technical_requirements TEXT,
|
|
22
|
+
expected_behavior TEXT,
|
|
23
|
+
edge_cases TEXT,
|
|
24
|
+
integration_points TEXT,
|
|
25
|
+
verification_steps TEXT,
|
|
26
|
+
files_to_check TEXT,
|
|
27
|
+
dependencies TEXT,
|
|
28
|
+
data_contracts TEXT,
|
|
29
|
+
|
|
30
|
+
-- Architecture Context
|
|
31
|
+
architecture_context TEXT,
|
|
32
|
+
|
|
33
|
+
-- Review Tracking
|
|
34
|
+
status TEXT DEFAULT 'pending',
|
|
35
|
+
reviewed_by TEXT,
|
|
36
|
+
reviewed_at TEXT,
|
|
37
|
+
|
|
38
|
+
-- Findings
|
|
39
|
+
passes_review INTEGER,
|
|
40
|
+
issues_found TEXT,
|
|
41
|
+
fix_priority TEXT,
|
|
42
|
+
fix_plan TEXT,
|
|
43
|
+
architecture_impact TEXT,
|
|
44
|
+
related_bugs TEXT,
|
|
45
|
+
|
|
46
|
+
-- Metadata
|
|
47
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
48
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_review_tasks_status ON review_tasks(status);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_review_tasks_category ON review_tasks(category);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_review_tasks_priority ON review_tasks(fix_priority);
|
|
54
|
+
`;
|
|
55
|
+
var ReviewDatabase = class {
|
|
56
|
+
db = null;
|
|
57
|
+
projectPath;
|
|
58
|
+
constructor(projectPath) {
|
|
59
|
+
this.projectPath = projectPath;
|
|
60
|
+
}
|
|
61
|
+
getDbPath() {
|
|
62
|
+
return join(this.projectPath, REVIEW_DB_PATH);
|
|
63
|
+
}
|
|
64
|
+
isInitialized() {
|
|
65
|
+
return existsSync(this.getDbPath());
|
|
66
|
+
}
|
|
67
|
+
open() {
|
|
68
|
+
if (this.db) return;
|
|
69
|
+
const dbPath = this.getDbPath();
|
|
70
|
+
const dbDir = dirname(dbPath);
|
|
71
|
+
if (!existsSync(dbDir)) {
|
|
72
|
+
mkdirSync(dbDir, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
this.db = new Database(dbPath);
|
|
75
|
+
this.db.pragma("journal_mode = WAL");
|
|
76
|
+
this.db.exec(CREATE_TABLE_SQL);
|
|
77
|
+
}
|
|
78
|
+
close() {
|
|
79
|
+
if (this.db) {
|
|
80
|
+
this.db.close();
|
|
81
|
+
this.db = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
ensureOpen() {
|
|
85
|
+
if (!this.db) {
|
|
86
|
+
throw new Error("Database not open. Call open() first.");
|
|
87
|
+
}
|
|
88
|
+
return this.db;
|
|
89
|
+
}
|
|
90
|
+
rowToTask(row) {
|
|
91
|
+
return {
|
|
92
|
+
id: row["id"],
|
|
93
|
+
category: row["category"],
|
|
94
|
+
subcategory: row["subcategory"],
|
|
95
|
+
featureName: row["feature_name"],
|
|
96
|
+
userStory: row["user_story"],
|
|
97
|
+
acceptanceCriteria: row["acceptance_criteria"] ? JSON.parse(row["acceptance_criteria"]) : null,
|
|
98
|
+
technicalRequirements: row["technical_requirements"],
|
|
99
|
+
expectedBehavior: row["expected_behavior"],
|
|
100
|
+
edgeCases: row["edge_cases"],
|
|
101
|
+
integrationPoints: row["integration_points"] ? JSON.parse(row["integration_points"]) : null,
|
|
102
|
+
verificationSteps: row["verification_steps"],
|
|
103
|
+
filesToCheck: row["files_to_check"] ? JSON.parse(row["files_to_check"]) : null,
|
|
104
|
+
dependencies: row["dependencies"] ? JSON.parse(row["dependencies"]) : null,
|
|
105
|
+
dataContracts: row["data_contracts"] ? JSON.parse(row["data_contracts"]) : null,
|
|
106
|
+
architectureContext: row["architecture_context"],
|
|
107
|
+
status: row["status"],
|
|
108
|
+
reviewedBy: row["reviewed_by"],
|
|
109
|
+
reviewedAt: row["reviewed_at"],
|
|
110
|
+
passesReview: row["passes_review"] === null ? null : row["passes_review"] === 1,
|
|
111
|
+
issuesFound: row["issues_found"] ? JSON.parse(row["issues_found"]) : null,
|
|
112
|
+
fixPriority: row["fix_priority"],
|
|
113
|
+
fixPlan: row["fix_plan"],
|
|
114
|
+
architectureImpact: row["architecture_impact"],
|
|
115
|
+
relatedBugs: row["related_bugs"],
|
|
116
|
+
createdAt: row["created_at"],
|
|
117
|
+
updatedAt: row["updated_at"]
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
insertTask(task) {
|
|
121
|
+
const db = this.ensureOpen();
|
|
122
|
+
const stmt = db.prepare(`
|
|
123
|
+
INSERT INTO review_tasks (
|
|
124
|
+
category, subcategory, feature_name, user_story, acceptance_criteria,
|
|
125
|
+
technical_requirements, expected_behavior, edge_cases, integration_points,
|
|
126
|
+
verification_steps, files_to_check, dependencies, data_contracts,
|
|
127
|
+
architecture_context
|
|
128
|
+
) VALUES (
|
|
129
|
+
?, ?, ?, ?, ?,
|
|
130
|
+
?, ?, ?, ?,
|
|
131
|
+
?, ?, ?, ?,
|
|
132
|
+
?
|
|
133
|
+
)
|
|
134
|
+
`);
|
|
135
|
+
const result = stmt.run(
|
|
136
|
+
task.category,
|
|
137
|
+
task.subcategory ?? null,
|
|
138
|
+
task.featureName,
|
|
139
|
+
task.userStory ?? null,
|
|
140
|
+
task.acceptanceCriteria ? JSON.stringify(task.acceptanceCriteria) : null,
|
|
141
|
+
task.technicalRequirements ?? null,
|
|
142
|
+
task.expectedBehavior ?? null,
|
|
143
|
+
task.edgeCases ?? null,
|
|
144
|
+
task.integrationPoints ? JSON.stringify(task.integrationPoints) : null,
|
|
145
|
+
task.verificationSteps ?? null,
|
|
146
|
+
task.filesToCheck ? JSON.stringify(task.filesToCheck) : null,
|
|
147
|
+
task.dependencies ? JSON.stringify(task.dependencies) : null,
|
|
148
|
+
task.dataContracts ? JSON.stringify(task.dataContracts) : null,
|
|
149
|
+
task.architectureContext ?? null
|
|
150
|
+
);
|
|
151
|
+
return Number(result.lastInsertRowid);
|
|
152
|
+
}
|
|
153
|
+
updateTask(id, update) {
|
|
154
|
+
const db = this.ensureOpen();
|
|
155
|
+
const fields = [];
|
|
156
|
+
const values = [];
|
|
157
|
+
if (update.status !== void 0) {
|
|
158
|
+
fields.push("status = ?");
|
|
159
|
+
values.push(update.status);
|
|
160
|
+
}
|
|
161
|
+
if (update.reviewedBy !== void 0) {
|
|
162
|
+
fields.push("reviewed_by = ?");
|
|
163
|
+
values.push(update.reviewedBy);
|
|
164
|
+
}
|
|
165
|
+
if (update.reviewedAt !== void 0) {
|
|
166
|
+
fields.push("reviewed_at = ?");
|
|
167
|
+
values.push(update.reviewedAt);
|
|
168
|
+
}
|
|
169
|
+
if (update.passesReview !== void 0) {
|
|
170
|
+
fields.push("passes_review = ?");
|
|
171
|
+
values.push(update.passesReview ? 1 : 0);
|
|
172
|
+
}
|
|
173
|
+
if (update.issuesFound !== void 0) {
|
|
174
|
+
fields.push("issues_found = ?");
|
|
175
|
+
values.push(JSON.stringify(update.issuesFound));
|
|
176
|
+
}
|
|
177
|
+
if (update.fixPriority !== void 0) {
|
|
178
|
+
fields.push("fix_priority = ?");
|
|
179
|
+
values.push(update.fixPriority);
|
|
180
|
+
}
|
|
181
|
+
if (update.fixPlan !== void 0) {
|
|
182
|
+
fields.push("fix_plan = ?");
|
|
183
|
+
values.push(update.fixPlan);
|
|
184
|
+
}
|
|
185
|
+
if (update.architectureImpact !== void 0) {
|
|
186
|
+
fields.push("architecture_impact = ?");
|
|
187
|
+
values.push(update.architectureImpact);
|
|
188
|
+
}
|
|
189
|
+
if (update.relatedBugs !== void 0) {
|
|
190
|
+
fields.push("related_bugs = ?");
|
|
191
|
+
values.push(update.relatedBugs);
|
|
192
|
+
}
|
|
193
|
+
if (fields.length === 0) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
fields.push("updated_at = datetime('now')");
|
|
197
|
+
values.push(id);
|
|
198
|
+
const stmt = db.prepare(`UPDATE review_tasks SET ${fields.join(", ")} WHERE id = ?`);
|
|
199
|
+
const result = stmt.run(...values);
|
|
200
|
+
return result.changes > 0;
|
|
201
|
+
}
|
|
202
|
+
getTask(id) {
|
|
203
|
+
const db = this.ensureOpen();
|
|
204
|
+
const row = db.prepare("SELECT * FROM review_tasks WHERE id = ?").get(id);
|
|
205
|
+
return row ? this.rowToTask(row) : null;
|
|
206
|
+
}
|
|
207
|
+
getAllTasks() {
|
|
208
|
+
const db = this.ensureOpen();
|
|
209
|
+
const rows = db.prepare("SELECT * FROM review_tasks ORDER BY id").all();
|
|
210
|
+
return rows.map((row) => this.rowToTask(row));
|
|
211
|
+
}
|
|
212
|
+
getTasksByStatus(status) {
|
|
213
|
+
const db = this.ensureOpen();
|
|
214
|
+
const rows = db.prepare("SELECT * FROM review_tasks WHERE status = ? ORDER BY id").all(status);
|
|
215
|
+
return rows.map((row) => this.rowToTask(row));
|
|
216
|
+
}
|
|
217
|
+
getNextPendingTask() {
|
|
218
|
+
const db = this.ensureOpen();
|
|
219
|
+
const row = db.prepare("SELECT * FROM review_tasks WHERE status = ? ORDER BY id LIMIT 1").get("pending");
|
|
220
|
+
return row ? this.rowToTask(row) : null;
|
|
221
|
+
}
|
|
222
|
+
getTasksNeedingFix() {
|
|
223
|
+
const db = this.ensureOpen();
|
|
224
|
+
const rows = db.prepare(`
|
|
225
|
+
SELECT * FROM review_tasks
|
|
226
|
+
WHERE status = 'needs_fix'
|
|
227
|
+
ORDER BY
|
|
228
|
+
CASE fix_priority
|
|
229
|
+
WHEN 'critical' THEN 1
|
|
230
|
+
WHEN 'high' THEN 2
|
|
231
|
+
WHEN 'medium' THEN 3
|
|
232
|
+
WHEN 'low' THEN 4
|
|
233
|
+
ELSE 5
|
|
234
|
+
END,
|
|
235
|
+
id
|
|
236
|
+
`).all();
|
|
237
|
+
return rows.map((row) => this.rowToTask(row));
|
|
238
|
+
}
|
|
239
|
+
getStats() {
|
|
240
|
+
const db = this.ensureOpen();
|
|
241
|
+
const total = db.prepare("SELECT COUNT(*) as count FROM review_tasks").get().count;
|
|
242
|
+
const pending = db.prepare("SELECT COUNT(*) as count FROM review_tasks WHERE status = 'pending'").get().count;
|
|
243
|
+
const inReview = db.prepare("SELECT COUNT(*) as count FROM review_tasks WHERE status = 'in_review'").get().count;
|
|
244
|
+
const completed = db.prepare("SELECT COUNT(*) as count FROM review_tasks WHERE status = 'completed'").get().count;
|
|
245
|
+
const needsFix = db.prepare("SELECT COUNT(*) as count FROM review_tasks WHERE status = 'needs_fix'").get().count;
|
|
246
|
+
const passing = db.prepare("SELECT COUNT(*) as count FROM review_tasks WHERE passes_review = 1").get().count;
|
|
247
|
+
const failing = db.prepare("SELECT COUNT(*) as count FROM review_tasks WHERE passes_review = 0").get().count;
|
|
248
|
+
return { total, pending, inReview, completed, needsFix, passing, failing };
|
|
249
|
+
}
|
|
250
|
+
clearAllTasks() {
|
|
251
|
+
const db = this.ensureOpen();
|
|
252
|
+
db.prepare("DELETE FROM review_tasks").run();
|
|
253
|
+
}
|
|
254
|
+
deleteTask(id) {
|
|
255
|
+
const db = this.ensureOpen();
|
|
256
|
+
const result = db.prepare("DELETE FROM review_tasks WHERE id = ?").run(id);
|
|
257
|
+
return result.changes > 0;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// src/core/code-review/analyzer.ts
|
|
262
|
+
import { readdir, readFile } from "fs/promises";
|
|
263
|
+
import { join as join2, extname, basename, relative } from "path";
|
|
264
|
+
import { existsSync as existsSync2 } from "fs";
|
|
265
|
+
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
266
|
+
"node_modules",
|
|
267
|
+
".git",
|
|
268
|
+
"dist",
|
|
269
|
+
"build",
|
|
270
|
+
".next",
|
|
271
|
+
".nuxt",
|
|
272
|
+
"coverage",
|
|
273
|
+
"__pycache__",
|
|
274
|
+
".pytest_cache",
|
|
275
|
+
"venv",
|
|
276
|
+
".venv",
|
|
277
|
+
"target",
|
|
278
|
+
".idea",
|
|
279
|
+
".vscode"
|
|
280
|
+
]);
|
|
281
|
+
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
282
|
+
".ts",
|
|
283
|
+
".tsx",
|
|
284
|
+
".js",
|
|
285
|
+
".jsx",
|
|
286
|
+
".mjs",
|
|
287
|
+
".cjs",
|
|
288
|
+
".py",
|
|
289
|
+
".rb",
|
|
290
|
+
".go",
|
|
291
|
+
".rs",
|
|
292
|
+
".java",
|
|
293
|
+
".kt",
|
|
294
|
+
".swift",
|
|
295
|
+
".c",
|
|
296
|
+
".cpp",
|
|
297
|
+
".h",
|
|
298
|
+
".hpp",
|
|
299
|
+
".cs",
|
|
300
|
+
".php",
|
|
301
|
+
".vue",
|
|
302
|
+
".svelte"
|
|
303
|
+
]);
|
|
304
|
+
var CONFIG_FILES = /* @__PURE__ */ new Set([
|
|
305
|
+
"package.json",
|
|
306
|
+
"tsconfig.json",
|
|
307
|
+
"vite.config.ts",
|
|
308
|
+
"vitest.config.ts",
|
|
309
|
+
"webpack.config.js",
|
|
310
|
+
"rollup.config.js",
|
|
311
|
+
"babel.config.js",
|
|
312
|
+
"Cargo.toml",
|
|
313
|
+
"go.mod",
|
|
314
|
+
"requirements.txt",
|
|
315
|
+
"Gemfile",
|
|
316
|
+
"pom.xml",
|
|
317
|
+
"build.gradle",
|
|
318
|
+
"Makefile",
|
|
319
|
+
"CMakeLists.txt",
|
|
320
|
+
".eslintrc",
|
|
321
|
+
".prettierrc",
|
|
322
|
+
"jest.config.js"
|
|
323
|
+
]);
|
|
324
|
+
function getFileType(filepath) {
|
|
325
|
+
const name = basename(filepath);
|
|
326
|
+
const ext = extname(filepath);
|
|
327
|
+
if (name.includes(".test.") || name.includes(".spec.") || filepath.includes("__tests__")) {
|
|
328
|
+
return "test";
|
|
329
|
+
}
|
|
330
|
+
if (CONFIG_FILES.has(name) || name.startsWith(".") || ext === ".json" || ext === ".yaml" || ext === ".yml") {
|
|
331
|
+
return "config";
|
|
332
|
+
}
|
|
333
|
+
if (ext === ".md" || ext === ".txt" || ext === ".rst" || filepath.includes("/docs/")) {
|
|
334
|
+
return "docs";
|
|
335
|
+
}
|
|
336
|
+
if (SOURCE_EXTENSIONS.has(ext)) {
|
|
337
|
+
return "source";
|
|
338
|
+
}
|
|
339
|
+
return "other";
|
|
340
|
+
}
|
|
341
|
+
function getLanguage(filepath) {
|
|
342
|
+
const ext = extname(filepath);
|
|
343
|
+
const langMap = {
|
|
344
|
+
".ts": "typescript",
|
|
345
|
+
".tsx": "typescript",
|
|
346
|
+
".js": "javascript",
|
|
347
|
+
".jsx": "javascript",
|
|
348
|
+
".py": "python",
|
|
349
|
+
".rb": "ruby",
|
|
350
|
+
".go": "go",
|
|
351
|
+
".rs": "rust",
|
|
352
|
+
".java": "java",
|
|
353
|
+
".kt": "kotlin",
|
|
354
|
+
".swift": "swift",
|
|
355
|
+
".c": "c",
|
|
356
|
+
".cpp": "cpp",
|
|
357
|
+
".cs": "csharp",
|
|
358
|
+
".php": "php",
|
|
359
|
+
".vue": "vue",
|
|
360
|
+
".svelte": "svelte"
|
|
361
|
+
};
|
|
362
|
+
return langMap[ext];
|
|
363
|
+
}
|
|
364
|
+
async function countLines(filepath) {
|
|
365
|
+
try {
|
|
366
|
+
const content = await readFile(filepath, "utf-8");
|
|
367
|
+
return content.split("\n").length;
|
|
368
|
+
} catch {
|
|
369
|
+
return 0;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
async function scanDirectory(dir, baseDir, files, depth = 0) {
|
|
373
|
+
if (depth > 10) return;
|
|
374
|
+
try {
|
|
375
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
376
|
+
for (const entry of entries) {
|
|
377
|
+
const fullPath = join2(dir, entry.name);
|
|
378
|
+
const relativePath = relative(baseDir, fullPath);
|
|
379
|
+
if (entry.isDirectory()) {
|
|
380
|
+
if (!IGNORE_DIRS.has(entry.name)) {
|
|
381
|
+
await scanDirectory(fullPath, baseDir, files, depth + 1);
|
|
382
|
+
}
|
|
383
|
+
} else if (entry.isFile()) {
|
|
384
|
+
const fileType = getFileType(relativePath);
|
|
385
|
+
if (fileType !== "other" || SOURCE_EXTENSIONS.has(extname(entry.name))) {
|
|
386
|
+
const language = getLanguage(relativePath);
|
|
387
|
+
const lines = fileType === "source" ? await countLines(fullPath) : void 0;
|
|
388
|
+
files.push({
|
|
389
|
+
path: relativePath,
|
|
390
|
+
type: fileType,
|
|
391
|
+
language,
|
|
392
|
+
lines
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
} catch {
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function inferFeatures(files, projectPath) {
|
|
401
|
+
const features = [];
|
|
402
|
+
const featureMap = /* @__PURE__ */ new Map();
|
|
403
|
+
for (const file of files) {
|
|
404
|
+
if (file.type !== "source") continue;
|
|
405
|
+
const parts = file.path.split("/");
|
|
406
|
+
if (parts.length < 2) continue;
|
|
407
|
+
const category = parts[0] ?? "root";
|
|
408
|
+
const feature = parts[1] ?? "main";
|
|
409
|
+
const key = `${category}/${feature}`;
|
|
410
|
+
if (!featureMap.has(key)) {
|
|
411
|
+
featureMap.set(key, /* @__PURE__ */ new Set());
|
|
412
|
+
}
|
|
413
|
+
featureMap.get(key)?.add(file.path);
|
|
414
|
+
}
|
|
415
|
+
for (const [key, filePaths] of featureMap) {
|
|
416
|
+
const [category, feature] = key.split("/");
|
|
417
|
+
if (!category || !feature) continue;
|
|
418
|
+
features.push({
|
|
419
|
+
name: feature,
|
|
420
|
+
category,
|
|
421
|
+
files: Array.from(filePaths)
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
features.sort((a, b) => {
|
|
425
|
+
if (a.category !== b.category) return a.category.localeCompare(b.category);
|
|
426
|
+
return a.name.localeCompare(b.name);
|
|
427
|
+
});
|
|
428
|
+
return features;
|
|
429
|
+
}
|
|
430
|
+
async function analyzeProject(projectPath) {
|
|
431
|
+
const name = basename(projectPath);
|
|
432
|
+
const files = [];
|
|
433
|
+
await scanDirectory(projectPath, projectPath, files);
|
|
434
|
+
const hasArchitecture = existsSync2(join2(projectPath, "ARCHITECTURE.md"));
|
|
435
|
+
const hasDataDictionary = existsSync2(join2(projectPath, "DATA-DICTIONARY.md"));
|
|
436
|
+
const hasDependencies = existsSync2(join2(projectPath, "DEPENDENCIES.md"));
|
|
437
|
+
const hasReviewDb = existsSync2(join2(projectPath, "docs/code-review/review-tasks.db"));
|
|
438
|
+
const features = inferFeatures(files, projectPath);
|
|
439
|
+
return {
|
|
440
|
+
name,
|
|
441
|
+
path: projectPath,
|
|
442
|
+
hasArchitecture,
|
|
443
|
+
hasDataDictionary,
|
|
444
|
+
hasDependencies,
|
|
445
|
+
hasReviewDb,
|
|
446
|
+
features,
|
|
447
|
+
files
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function featuresToTasks(features, architectureContext) {
|
|
451
|
+
return features.map((feature) => ({
|
|
452
|
+
category: feature.category,
|
|
453
|
+
featureName: feature.name,
|
|
454
|
+
filesToCheck: feature.files,
|
|
455
|
+
expectedBehavior: feature.description,
|
|
456
|
+
architectureContext
|
|
457
|
+
}));
|
|
458
|
+
}
|
|
459
|
+
async function readArchitectureContext(projectPath) {
|
|
460
|
+
const archPath = join2(projectPath, "ARCHITECTURE.md");
|
|
461
|
+
if (!existsSync2(archPath)) {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
const content = await readFile(archPath, "utf-8");
|
|
466
|
+
return content.slice(0, 2e3);
|
|
467
|
+
} catch {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/agents/reviewer.ts
|
|
473
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
474
|
+
import { existsSync as existsSync3 } from "fs";
|
|
475
|
+
import { join as join3 } from "path";
|
|
476
|
+
var SYSTEM_PROMPT = `You are a meticulous Code Reviewer, responsible for analyzing code quality, correctness, and adherence to specifications.
|
|
477
|
+
|
|
478
|
+
Your role:
|
|
479
|
+
- Review code files against provided specifications and acceptance criteria
|
|
480
|
+
- Identify bugs, security issues, performance problems, and architectural violations
|
|
481
|
+
- Document issues with specific file locations and actionable suggestions
|
|
482
|
+
- Be thorough but fair - only flag genuine issues
|
|
483
|
+
- Do NOT suggest modifications - only document findings
|
|
484
|
+
|
|
485
|
+
Output your review in the following JSON format:
|
|
486
|
+
{
|
|
487
|
+
"passes_review": boolean,
|
|
488
|
+
"issues": [
|
|
489
|
+
{
|
|
490
|
+
"type": "bug" | "security" | "performance" | "style" | "architecture" | "documentation",
|
|
491
|
+
"severity": "critical" | "high" | "medium" | "low",
|
|
492
|
+
"description": "Clear description of the issue",
|
|
493
|
+
"location": "path/to/file.ts:L42" (optional),
|
|
494
|
+
"suggestion": "How to fix it" (optional)
|
|
495
|
+
}
|
|
496
|
+
],
|
|
497
|
+
"fix_priority": "critical" | "high" | "medium" | "low" | "none",
|
|
498
|
+
"fix_plan": "Overall approach to fix the issues" (or null if passes),
|
|
499
|
+
"architecture_impact": "What else might be affected" (or null if none)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
Guidelines:
|
|
503
|
+
- passes_review is true ONLY if there are no issues of severity high or critical
|
|
504
|
+
- fix_priority should reflect the highest severity issue found
|
|
505
|
+
- Be specific about locations (file:line when possible)
|
|
506
|
+
- Focus on correctness, not style preferences
|
|
507
|
+
- Consider edge cases and error handling
|
|
508
|
+
- Check for security vulnerabilities (injection, auth, data exposure)
|
|
509
|
+
- Verify the code actually implements what's specified`;
|
|
510
|
+
var ReviewerAgent = class {
|
|
511
|
+
client;
|
|
512
|
+
config;
|
|
513
|
+
constructor(config, apiKey) {
|
|
514
|
+
this.config = {
|
|
515
|
+
provider: "anthropic",
|
|
516
|
+
model: config?.model ?? getDefaultModel("reviewer"),
|
|
517
|
+
maxTokens: config?.maxTokens ?? 4096,
|
|
518
|
+
temperature: config?.temperature ?? 0.3
|
|
519
|
+
};
|
|
520
|
+
this.client = new AnthropicClient(this.config, apiKey);
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Review a task by analyzing its associated files
|
|
524
|
+
*/
|
|
525
|
+
async reviewTask(task, context) {
|
|
526
|
+
const fileContents = await this.loadFilesForReview(task, context);
|
|
527
|
+
if (fileContents.length === 0) {
|
|
528
|
+
return {
|
|
529
|
+
passesReview: false,
|
|
530
|
+
issues: [{
|
|
531
|
+
type: "documentation",
|
|
532
|
+
severity: "medium",
|
|
533
|
+
description: "No files found to review for this task"
|
|
534
|
+
}],
|
|
535
|
+
fixPriority: "medium",
|
|
536
|
+
fixPlan: "Ensure filesToCheck is populated with valid file paths",
|
|
537
|
+
architectureImpact: null,
|
|
538
|
+
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0, baseCost: 0, markedUpCost: 0 }
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
const userMessage = this.buildPrompt(task, fileContents, context);
|
|
542
|
+
const response = await this.client.chat(SYSTEM_PROMPT, userMessage, {
|
|
543
|
+
temperature: 0.3,
|
|
544
|
+
maxTokens: 4096
|
|
545
|
+
});
|
|
546
|
+
return this.parseReviewResponse(response.content, response.usage);
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Load file contents for review
|
|
550
|
+
*/
|
|
551
|
+
async loadFilesForReview(task, context) {
|
|
552
|
+
const files = [];
|
|
553
|
+
const filesToCheck = task.filesToCheck ?? [];
|
|
554
|
+
const maxFiles = context.maxFilesToRead ?? 10;
|
|
555
|
+
for (const relativePath of filesToCheck.slice(0, maxFiles)) {
|
|
556
|
+
const fullPath = join3(context.projectPath, relativePath);
|
|
557
|
+
if (!existsSync3(fullPath)) {
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
try {
|
|
561
|
+
const content = await readFile2(fullPath, "utf-8");
|
|
562
|
+
const truncated = content.length > 1e4 ? content.slice(0, 1e4) + "\n\n... [truncated, file too large]" : content;
|
|
563
|
+
files.push({ path: relativePath, content: truncated });
|
|
564
|
+
} catch {
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return files;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Build the review prompt
|
|
571
|
+
*/
|
|
572
|
+
buildPrompt(task, files, context) {
|
|
573
|
+
const parts = [];
|
|
574
|
+
parts.push("# Review Task");
|
|
575
|
+
parts.push(`**Feature:** ${task.featureName}`);
|
|
576
|
+
parts.push(`**Category:** ${task.category}${task.subcategory ? ` / ${task.subcategory}` : ""}`);
|
|
577
|
+
parts.push("");
|
|
578
|
+
if (task.userStory) {
|
|
579
|
+
parts.push("**User Story:**");
|
|
580
|
+
parts.push(task.userStory);
|
|
581
|
+
parts.push("");
|
|
582
|
+
}
|
|
583
|
+
if (task.acceptanceCriteria && task.acceptanceCriteria.length > 0) {
|
|
584
|
+
parts.push("**Acceptance Criteria:**");
|
|
585
|
+
task.acceptanceCriteria.forEach((ac, i) => parts.push(`${i + 1}. ${ac}`));
|
|
586
|
+
parts.push("");
|
|
587
|
+
}
|
|
588
|
+
if (task.technicalRequirements) {
|
|
589
|
+
parts.push("**Technical Requirements:**");
|
|
590
|
+
parts.push(task.technicalRequirements);
|
|
591
|
+
parts.push("");
|
|
592
|
+
}
|
|
593
|
+
if (task.expectedBehavior) {
|
|
594
|
+
parts.push("**Expected Behavior:**");
|
|
595
|
+
parts.push(task.expectedBehavior);
|
|
596
|
+
parts.push("");
|
|
597
|
+
}
|
|
598
|
+
if (task.edgeCases) {
|
|
599
|
+
parts.push("**Edge Cases to Consider:**");
|
|
600
|
+
parts.push(task.edgeCases);
|
|
601
|
+
parts.push("");
|
|
602
|
+
}
|
|
603
|
+
if (task.verificationSteps) {
|
|
604
|
+
parts.push("**Verification Steps:**");
|
|
605
|
+
parts.push(task.verificationSteps);
|
|
606
|
+
parts.push("");
|
|
607
|
+
}
|
|
608
|
+
if (context.architectureContent) {
|
|
609
|
+
parts.push("# Architecture Context");
|
|
610
|
+
parts.push(context.architectureContent);
|
|
611
|
+
parts.push("");
|
|
612
|
+
} else if (task.architectureContext) {
|
|
613
|
+
parts.push("# Architecture Context");
|
|
614
|
+
parts.push(task.architectureContext);
|
|
615
|
+
parts.push("");
|
|
616
|
+
}
|
|
617
|
+
parts.push("# Files to Review");
|
|
618
|
+
parts.push("");
|
|
619
|
+
for (const file of files) {
|
|
620
|
+
parts.push(`## ${file.path}`);
|
|
621
|
+
parts.push("```");
|
|
622
|
+
parts.push(file.content);
|
|
623
|
+
parts.push("```");
|
|
624
|
+
parts.push("");
|
|
625
|
+
}
|
|
626
|
+
parts.push("# Instructions");
|
|
627
|
+
parts.push("");
|
|
628
|
+
parts.push("Review these files against the specifications above.");
|
|
629
|
+
parts.push("Identify any issues, bugs, security vulnerabilities, or deviations from requirements.");
|
|
630
|
+
parts.push("Output your review as valid JSON.");
|
|
631
|
+
return parts.join("\n");
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Parse the review response from AI
|
|
635
|
+
*/
|
|
636
|
+
parseReviewResponse(content, usage) {
|
|
637
|
+
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
|
638
|
+
if (!jsonMatch) {
|
|
639
|
+
throw new Error("Failed to parse review: no JSON found in response");
|
|
640
|
+
}
|
|
641
|
+
try {
|
|
642
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
643
|
+
const issues = (parsed.issues ?? []).filter((i) => i.description).map((i) => ({
|
|
644
|
+
type: this.normalizeIssueType(i.type),
|
|
645
|
+
severity: this.normalizeSeverity(i.severity),
|
|
646
|
+
description: i.description ?? "",
|
|
647
|
+
location: i.location,
|
|
648
|
+
suggestion: i.suggestion
|
|
649
|
+
}));
|
|
650
|
+
const hasCriticalOrHigh = issues.some((i) => i.severity === "critical" || i.severity === "high");
|
|
651
|
+
return {
|
|
652
|
+
passesReview: hasCriticalOrHigh ? false : parsed.passes_review ?? true,
|
|
653
|
+
issues,
|
|
654
|
+
fixPriority: this.normalizeFixPriority(parsed.fix_priority),
|
|
655
|
+
fixPlan: parsed.fix_plan ?? null,
|
|
656
|
+
architectureImpact: parsed.architecture_impact ?? null,
|
|
657
|
+
usage
|
|
658
|
+
};
|
|
659
|
+
} catch (error) {
|
|
660
|
+
throw new Error(`Failed to parse review JSON: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
normalizeIssueType(value) {
|
|
664
|
+
const normalized = value?.toLowerCase();
|
|
665
|
+
if (normalized === "bug" || normalized === "security" || normalized === "performance" || normalized === "style" || normalized === "architecture" || normalized === "documentation") {
|
|
666
|
+
return normalized;
|
|
667
|
+
}
|
|
668
|
+
return "bug";
|
|
669
|
+
}
|
|
670
|
+
normalizeSeverity(value) {
|
|
671
|
+
const normalized = value?.toLowerCase();
|
|
672
|
+
if (normalized === "critical" || normalized === "high" || normalized === "medium" || normalized === "low") {
|
|
673
|
+
return normalized;
|
|
674
|
+
}
|
|
675
|
+
return "medium";
|
|
676
|
+
}
|
|
677
|
+
normalizeFixPriority(value) {
|
|
678
|
+
const normalized = value?.toLowerCase();
|
|
679
|
+
if (normalized === "critical" || normalized === "high" || normalized === "medium" || normalized === "low" || normalized === "none") {
|
|
680
|
+
return normalized;
|
|
681
|
+
}
|
|
682
|
+
return "medium";
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
// src/core/code-review/service.ts
|
|
687
|
+
var ReviewService = class {
|
|
688
|
+
db;
|
|
689
|
+
agent;
|
|
690
|
+
config;
|
|
691
|
+
architectureContent = null;
|
|
692
|
+
constructor(config) {
|
|
693
|
+
this.config = config;
|
|
694
|
+
this.db = new ReviewDatabase(config.projectPath);
|
|
695
|
+
this.agent = new ReviewerAgent(void 0, config.apiKey);
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Initialize the service (load architecture context)
|
|
699
|
+
*/
|
|
700
|
+
async initialize() {
|
|
701
|
+
this.db.open();
|
|
702
|
+
this.architectureContent = await readArchitectureContext(this.config.projectPath);
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Close the database connection
|
|
706
|
+
*/
|
|
707
|
+
close() {
|
|
708
|
+
this.db.close();
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Get current review statistics
|
|
712
|
+
*/
|
|
713
|
+
getStats() {
|
|
714
|
+
return this.db.getStats();
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Review a single task by ID
|
|
718
|
+
*/
|
|
719
|
+
async reviewTask(taskId) {
|
|
720
|
+
const task = this.db.getTask(taskId);
|
|
721
|
+
if (!task) {
|
|
722
|
+
throw new Error(`Task ${taskId} not found`);
|
|
723
|
+
}
|
|
724
|
+
return this.executeReview(task);
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Review the next pending task
|
|
728
|
+
*/
|
|
729
|
+
async reviewNext() {
|
|
730
|
+
const task = this.db.getNextPendingTask();
|
|
731
|
+
if (!task) {
|
|
732
|
+
return null;
|
|
733
|
+
}
|
|
734
|
+
const result = await this.executeReview(task);
|
|
735
|
+
return { task, result };
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Review all pending tasks
|
|
739
|
+
*/
|
|
740
|
+
async reviewAllPending(maxTasks) {
|
|
741
|
+
const stats = {
|
|
742
|
+
tasksReviewed: 0,
|
|
743
|
+
tasksPassed: 0,
|
|
744
|
+
tasksFailed: 0,
|
|
745
|
+
tasksErrored: 0,
|
|
746
|
+
totalUsage: {
|
|
747
|
+
inputTokens: 0,
|
|
748
|
+
outputTokens: 0,
|
|
749
|
+
totalTokens: 0,
|
|
750
|
+
baseCost: 0,
|
|
751
|
+
markedUpCost: 0
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
const limit = maxTasks ?? Infinity;
|
|
755
|
+
while (stats.tasksReviewed < limit) {
|
|
756
|
+
const task = this.db.getNextPendingTask();
|
|
757
|
+
if (!task) {
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
try {
|
|
761
|
+
const result = await this.executeReview(task);
|
|
762
|
+
stats.tasksReviewed++;
|
|
763
|
+
if (result.passesReview) {
|
|
764
|
+
stats.tasksPassed++;
|
|
765
|
+
} else {
|
|
766
|
+
stats.tasksFailed++;
|
|
767
|
+
}
|
|
768
|
+
stats.totalUsage.inputTokens += result.usage.inputTokens;
|
|
769
|
+
stats.totalUsage.outputTokens += result.usage.outputTokens;
|
|
770
|
+
stats.totalUsage.totalTokens += result.usage.totalTokens;
|
|
771
|
+
stats.totalUsage.baseCost += result.usage.baseCost;
|
|
772
|
+
stats.totalUsage.markedUpCost += result.usage.markedUpCost;
|
|
773
|
+
} catch (error) {
|
|
774
|
+
stats.tasksErrored++;
|
|
775
|
+
this.db.updateTask(task.id, {
|
|
776
|
+
status: "pending",
|
|
777
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
778
|
+
reviewedBy: "ai-reviewer-error"
|
|
779
|
+
});
|
|
780
|
+
if (this.config.onError) {
|
|
781
|
+
this.config.onError(task, error instanceof Error ? error : new Error(String(error)));
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return stats;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Execute a review on a single task
|
|
789
|
+
*/
|
|
790
|
+
async executeReview(task) {
|
|
791
|
+
this.db.updateTask(task.id, {
|
|
792
|
+
status: "in_review",
|
|
793
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
794
|
+
reviewedBy: "ai-reviewer"
|
|
795
|
+
});
|
|
796
|
+
if (this.config.onTaskStart) {
|
|
797
|
+
this.config.onTaskStart(task);
|
|
798
|
+
}
|
|
799
|
+
const context = {
|
|
800
|
+
projectPath: this.config.projectPath,
|
|
801
|
+
architectureContent: this.architectureContent ?? void 0,
|
|
802
|
+
maxFilesToRead: this.config.maxFilesPerTask ?? 10
|
|
803
|
+
};
|
|
804
|
+
const result = await this.agent.reviewTask(task, context);
|
|
805
|
+
const update = {
|
|
806
|
+
status: result.passesReview ? "completed" : "needs_fix",
|
|
807
|
+
passesReview: result.passesReview,
|
|
808
|
+
issuesFound: result.issues,
|
|
809
|
+
fixPriority: result.fixPriority,
|
|
810
|
+
fixPlan: result.fixPlan ?? void 0,
|
|
811
|
+
architectureImpact: result.architectureImpact ?? void 0,
|
|
812
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
813
|
+
reviewedBy: "ai-reviewer"
|
|
814
|
+
};
|
|
815
|
+
this.db.updateTask(task.id, update);
|
|
816
|
+
if (this.config.onTaskComplete) {
|
|
817
|
+
const updatedTask = this.db.getTask(task.id);
|
|
818
|
+
if (updatedTask) {
|
|
819
|
+
this.config.onTaskComplete(updatedTask, result);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return result;
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
export {
|
|
827
|
+
ReviewDatabase,
|
|
828
|
+
analyzeProject,
|
|
829
|
+
featuresToTasks,
|
|
830
|
+
readArchitectureContext,
|
|
831
|
+
ReviewService
|
|
832
|
+
};
|