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.
@@ -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
+ };