architect-ai 1.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.
- package/README.md +182 -0
- package/dist/index.js +560 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +186 -0
- package/dist/lib.js +443 -0
- package/dist/lib.js.map +1 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
import { promisify, parseArgs } from 'util';
|
|
4
|
+
import { readFile, stat } from 'fs/promises';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
6
|
+
import { resolve } from 'path';
|
|
7
|
+
import { exec } from 'child_process';
|
|
8
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
9
|
+
|
|
10
|
+
// Architect AI - CLI
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// src/types/index.ts
|
|
14
|
+
var DEFAULT_CONFIG = {
|
|
15
|
+
techStack: "",
|
|
16
|
+
rules: [],
|
|
17
|
+
bypassKeyword: "skip:",
|
|
18
|
+
commitFormat: "",
|
|
19
|
+
maxConcurrency: 5,
|
|
20
|
+
cacheEnabled: true
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/utils/config.ts
|
|
24
|
+
var cachedConfig = null;
|
|
25
|
+
var loadProjectConfig = async (cwd = process.cwd()) => {
|
|
26
|
+
if (cachedConfig) {
|
|
27
|
+
return cachedConfig;
|
|
28
|
+
}
|
|
29
|
+
const configPath = resolve(cwd, ".architectrc.json");
|
|
30
|
+
if (!existsSync(configPath)) {
|
|
31
|
+
cachedConfig = { ...DEFAULT_CONFIG };
|
|
32
|
+
return cachedConfig;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const content = await readFile(configPath, "utf-8");
|
|
36
|
+
const userConfig = JSON.parse(content);
|
|
37
|
+
cachedConfig = {
|
|
38
|
+
...DEFAULT_CONFIG,
|
|
39
|
+
...userConfig
|
|
40
|
+
};
|
|
41
|
+
console.log("\u2699\uFE0F Loaded project-specific rules from .architectrc.json");
|
|
42
|
+
return cachedConfig;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.warn("\u26A0\uFE0F Found .architectrc.json but failed to parse it.");
|
|
45
|
+
cachedConfig = { ...DEFAULT_CONFIG };
|
|
46
|
+
return cachedConfig;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var execAsync = promisify(exec);
|
|
50
|
+
var SUPPORTED_EXTENSIONS = /\.(ts|tsx|js|jsx|mjs|cjs|py|cs|go|java|rs|kt|swift)$/;
|
|
51
|
+
var getLastCommitMessage = async () => {
|
|
52
|
+
try {
|
|
53
|
+
const { stdout } = await execAsync("git log -1 --pretty=%B");
|
|
54
|
+
return stdout.trim();
|
|
55
|
+
} catch {
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var getChangedFiles = async (options = {}) => {
|
|
60
|
+
const { targetBranch = "origin/main", extensions = SUPPORTED_EXTENSIONS } = options;
|
|
61
|
+
try {
|
|
62
|
+
const command = `git diff --name-only --diff-filter=ACMR ${targetBranch}...HEAD`;
|
|
63
|
+
const { stdout } = await execAsync(command);
|
|
64
|
+
return parseAndFilterFiles(stdout, extensions);
|
|
65
|
+
} catch {
|
|
66
|
+
try {
|
|
67
|
+
const command = `git diff --name-only --diff-filter=ACMR ${targetBranch}`;
|
|
68
|
+
const { stdout } = await execAsync(command);
|
|
69
|
+
return parseAndFilterFiles(stdout, extensions);
|
|
70
|
+
} catch {
|
|
71
|
+
console.warn("\u26A0\uFE0F Git diff failed. No files to audit.");
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var isGitRepository = async () => {
|
|
77
|
+
try {
|
|
78
|
+
await execAsync("git rev-parse --is-inside-work-tree");
|
|
79
|
+
return true;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var getCurrentBranch = async () => {
|
|
85
|
+
try {
|
|
86
|
+
const { stdout } = await execAsync("git branch --show-current");
|
|
87
|
+
return stdout.trim();
|
|
88
|
+
} catch {
|
|
89
|
+
return "unknown";
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var parseAndFilterFiles = (output, extensions) => {
|
|
93
|
+
return output.split("\n").map((file) => file.trim()).filter((file) => file.length > 0).filter((file) => extensions.test(file));
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/utils/logger.ts
|
|
97
|
+
var colors = {
|
|
98
|
+
reset: "\x1B[0m",
|
|
99
|
+
bold: "\x1B[1m",
|
|
100
|
+
dim: "\x1B[2m",
|
|
101
|
+
red: "\x1B[31m",
|
|
102
|
+
green: "\x1B[32m",
|
|
103
|
+
yellow: "\x1B[33m",
|
|
104
|
+
blue: "\x1B[34m",
|
|
105
|
+
magenta: "\x1B[35m",
|
|
106
|
+
cyan: "\x1B[36m"};
|
|
107
|
+
var log = {
|
|
108
|
+
info: (msg) => console.log(`${colors.blue}\u2139${colors.reset} ${msg}`),
|
|
109
|
+
success: (msg) => console.log(`${colors.green}\u2705${colors.reset} ${msg}`),
|
|
110
|
+
warning: (msg) => console.warn(`${colors.yellow}\u26A0\uFE0F${colors.reset} ${msg}`),
|
|
111
|
+
error: (msg) => console.error(`${colors.red}\u274C${colors.reset} ${msg}`),
|
|
112
|
+
critical: (msg) => console.error(`${colors.red}${colors.bold}\u{1F6A8}${colors.reset} ${msg}`),
|
|
113
|
+
audit: (msg) => console.log(`${colors.cyan}\u{1F50D}${colors.reset} ${msg}`),
|
|
114
|
+
skip: (msg) => console.log(`${colors.magenta}\u23E9${colors.reset} ${msg}`),
|
|
115
|
+
file: (msg) => console.log(`${colors.dim} ${msg}${colors.reset}`),
|
|
116
|
+
// Issue formatting
|
|
117
|
+
issue: (severity, line, message) => {
|
|
118
|
+
const color = severity === "CRITICAL" ? colors.red : severity === "WARNING" ? colors.yellow : colors.blue;
|
|
119
|
+
console.log(` ${color}[${severity}] Line ${line}: ${message}${colors.reset}`);
|
|
120
|
+
},
|
|
121
|
+
// Progress bar
|
|
122
|
+
progress: (current, total, label) => {
|
|
123
|
+
const percent = Math.round(current / total * 100);
|
|
124
|
+
const barLength = 20;
|
|
125
|
+
const filled = Math.round(current / total * barLength);
|
|
126
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(barLength - filled);
|
|
127
|
+
process.stdout.write(`\r${colors.cyan}${bar}${colors.reset} ${percent}% | ${label}`);
|
|
128
|
+
},
|
|
129
|
+
progressEnd: () => {
|
|
130
|
+
console.log();
|
|
131
|
+
},
|
|
132
|
+
// Divider
|
|
133
|
+
divider: () => console.log(`${colors.dim}${"\u2500".repeat(50)}${colors.reset}`),
|
|
134
|
+
// Header
|
|
135
|
+
header: (title) => {
|
|
136
|
+
console.log();
|
|
137
|
+
console.log(`${colors.bold}${colors.cyan}\u{1F3D7}\uFE0F ${title}${colors.reset}`);
|
|
138
|
+
console.log(`${colors.dim}${"\u2500".repeat(50)}${colors.reset}`);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var formatDuration = (ms) => {
|
|
142
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
143
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
144
|
+
return `${(ms / 6e4).toFixed(1)}m`;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// src/utils/parser.ts
|
|
148
|
+
var cleanJSON = (text) => {
|
|
149
|
+
return text.replace(/```json\s*/gi, "").replace(/```\s*/g, "").trim();
|
|
150
|
+
};
|
|
151
|
+
var parseAuditResponse = (responseText) => {
|
|
152
|
+
const cleaned = cleanJSON(responseText);
|
|
153
|
+
try {
|
|
154
|
+
const parsed = JSON.parse(cleaned);
|
|
155
|
+
if (!parsed.status || !["PASS", "FAIL"].includes(parsed.status)) {
|
|
156
|
+
return {
|
|
157
|
+
status: "FAIL",
|
|
158
|
+
message: "Invalid AI response format",
|
|
159
|
+
issues: []
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return parsed;
|
|
163
|
+
} catch {
|
|
164
|
+
const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
|
|
165
|
+
if (jsonMatch) {
|
|
166
|
+
try {
|
|
167
|
+
return JSON.parse(jsonMatch[0]);
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
status: "FAIL",
|
|
173
|
+
message: "Failed to parse AI response",
|
|
174
|
+
issues: []
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
var formatBytes = (bytes) => {
|
|
179
|
+
if (bytes === 0) return "0 B";
|
|
180
|
+
const k = 1024;
|
|
181
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
182
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
183
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// src/services/file.ts
|
|
187
|
+
var MAX_FILE_SIZE = 500 * 1024;
|
|
188
|
+
var readFilesForAudit = async (filePaths) => {
|
|
189
|
+
const result = {
|
|
190
|
+
success: [],
|
|
191
|
+
skipped: []
|
|
192
|
+
};
|
|
193
|
+
const readPromises = filePaths.map(async (filePath) => {
|
|
194
|
+
const absolutePath = resolve(filePath);
|
|
195
|
+
if (!existsSync(absolutePath)) {
|
|
196
|
+
return { path: filePath, skipped: true, reason: "File not found" };
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
const stats = await stat(absolutePath);
|
|
200
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
201
|
+
return {
|
|
202
|
+
path: filePath,
|
|
203
|
+
skipped: true,
|
|
204
|
+
reason: `File too large (${formatBytes(stats.size)})`
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const content = await readFile(absolutePath, "utf-8");
|
|
208
|
+
return {
|
|
209
|
+
path: filePath,
|
|
210
|
+
content,
|
|
211
|
+
size: stats.size,
|
|
212
|
+
skipped: false
|
|
213
|
+
};
|
|
214
|
+
} catch (error) {
|
|
215
|
+
return {
|
|
216
|
+
path: filePath,
|
|
217
|
+
skipped: true,
|
|
218
|
+
reason: error instanceof Error ? error.message : "Read error"
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
const results = await Promise.all(readPromises);
|
|
223
|
+
for (const item of results) {
|
|
224
|
+
if (item.skipped) {
|
|
225
|
+
result.skipped.push({ path: item.path, reason: item.reason });
|
|
226
|
+
} else {
|
|
227
|
+
result.success.push({
|
|
228
|
+
path: item.path,
|
|
229
|
+
content: item.content,
|
|
230
|
+
size: item.size
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (result.skipped.length > 0) {
|
|
235
|
+
log.warning(`Skipped ${result.skipped.length} file(s):`);
|
|
236
|
+
for (const { path, reason } of result.skipped) {
|
|
237
|
+
log.file(`${path}: ${reason}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// src/config/prompts.ts
|
|
244
|
+
var DEFAULT_COMMIT_PROMPT = `
|
|
245
|
+
### ROLE
|
|
246
|
+
You are a Strict Release Manager. You enforce "Conventional Commits" standards.
|
|
247
|
+
### RULES
|
|
248
|
+
1. Format MUST be: \`<type>(<scope>): <subject>\`
|
|
249
|
+
- Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert.
|
|
250
|
+
- Example: "feat(auth): add google login support"
|
|
251
|
+
2. Subject must be lowercase and imperative (e.g., "add" not "added").
|
|
252
|
+
3. Message must be descriptive enough to understand the change context.
|
|
253
|
+
`;
|
|
254
|
+
var BASE_AUDIT_PROMPT = `
|
|
255
|
+
### ROLE & OBJECTIVE
|
|
256
|
+
You are an Elite Software Architect. Your goal is to enforce "Clean Code", "SOLID Principles", and "Maintainability".
|
|
257
|
+
### GLOBAL STANDARDS
|
|
258
|
+
1. **Clean Code:** Variable/Function names must be semantic. No magic numbers.
|
|
259
|
+
2. **Split Code:** Suggest splitting complex functions/components.
|
|
260
|
+
3. **Performance:** Identify obvious bottlenecks.
|
|
261
|
+
4. **Error Handling:** Ensure proper boundary checks.
|
|
262
|
+
`;
|
|
263
|
+
var buildSystemPrompt = (config2) => {
|
|
264
|
+
const parts = [BASE_AUDIT_PROMPT];
|
|
265
|
+
if (config2.techStack) {
|
|
266
|
+
parts.push(`
|
|
267
|
+
### TECH STACK CONTEXT
|
|
268
|
+
The code is written in: ${config2.techStack}
|
|
269
|
+
`);
|
|
270
|
+
}
|
|
271
|
+
if (config2.rules && config2.rules.length > 0) {
|
|
272
|
+
parts.push(`
|
|
273
|
+
### PROJECT SPECIFIC RULES (HIGHEST PRIORITY)
|
|
274
|
+
`);
|
|
275
|
+
config2.rules.forEach((rule, index) => {
|
|
276
|
+
parts.push(`${index + 1}. ${rule}
|
|
277
|
+
`);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
parts.push(`
|
|
281
|
+
### OUTPUT FORMAT (JSON ONLY)
|
|
282
|
+
{ "status": "PASS" | "FAIL", "issues": [{ "line": number, "severity": "CRITICAL" | "WARNING", "message": "string", "suggestion": "string" }] }`);
|
|
283
|
+
return parts.join("");
|
|
284
|
+
};
|
|
285
|
+
var buildCommitPrompt = (customFormat) => {
|
|
286
|
+
const parts = [];
|
|
287
|
+
if (customFormat) {
|
|
288
|
+
parts.push(`
|
|
289
|
+
### ROLE
|
|
290
|
+
You are a Strict Release Manager.
|
|
291
|
+
### RULES (CUSTOM COMPANY POLICY)
|
|
292
|
+
You must enforce the following strict commit message format:
|
|
293
|
+
"${customFormat}"
|
|
294
|
+
|
|
295
|
+
Any commit message NOT following this pattern must be REJECTED.
|
|
296
|
+
`);
|
|
297
|
+
} else {
|
|
298
|
+
parts.push(DEFAULT_COMMIT_PROMPT);
|
|
299
|
+
}
|
|
300
|
+
parts.push(`
|
|
301
|
+
### OUTPUT (JSON ONLY)
|
|
302
|
+
{ "status": "PASS" | "FAIL", "message": "Reason for failure", "suggestion": "Corrected example" }`);
|
|
303
|
+
return parts.join("");
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// src/services/ai.ts
|
|
307
|
+
var modelInstance = null;
|
|
308
|
+
var getModel = () => {
|
|
309
|
+
if (modelInstance) {
|
|
310
|
+
return modelInstance;
|
|
311
|
+
}
|
|
312
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
313
|
+
if (!apiKey) {
|
|
314
|
+
throw new Error("GEMINI_API_KEY environment variable is required");
|
|
315
|
+
}
|
|
316
|
+
const genAI = new GoogleGenerativeAI(apiKey);
|
|
317
|
+
modelInstance = genAI.getGenerativeModel({
|
|
318
|
+
model: "gemini-1.5-pro",
|
|
319
|
+
generationConfig: {
|
|
320
|
+
temperature: 0.2,
|
|
321
|
+
maxOutputTokens: 8192
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
return modelInstance;
|
|
325
|
+
};
|
|
326
|
+
var auditCommit = async (message, config2) => {
|
|
327
|
+
log.audit(`Auditing Commit Message: "${message}"...`);
|
|
328
|
+
const systemPrompt = buildCommitPrompt(config2.commitFormat);
|
|
329
|
+
const model = getModel();
|
|
330
|
+
try {
|
|
331
|
+
const result = await model.generateContent([
|
|
332
|
+
systemPrompt,
|
|
333
|
+
`Commit Message: "${message}"`
|
|
334
|
+
]);
|
|
335
|
+
return parseAuditResponse(result.response.text());
|
|
336
|
+
} catch (error) {
|
|
337
|
+
log.warning("AI check failed. Skipping commit check.");
|
|
338
|
+
return { status: "PASS", message: "AI unavailable - skipped" };
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
var auditFile = async (filePath, content, systemPrompt) => {
|
|
342
|
+
const model = getModel();
|
|
343
|
+
try {
|
|
344
|
+
const result = await model.generateContent([
|
|
345
|
+
systemPrompt,
|
|
346
|
+
`Code to review:
|
|
347
|
+
${content}`
|
|
348
|
+
]);
|
|
349
|
+
return parseAuditResponse(result.response.text());
|
|
350
|
+
} catch (error) {
|
|
351
|
+
return {
|
|
352
|
+
status: "FAIL",
|
|
353
|
+
message: `Error auditing file: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
354
|
+
issues: []
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
var auditFilesWithConcurrency = async (files, config2, maxConcurrency = 5) => {
|
|
359
|
+
const systemPrompt = buildSystemPrompt(config2);
|
|
360
|
+
const results = [];
|
|
361
|
+
for (let i = 0; i < files.length; i += maxConcurrency) {
|
|
362
|
+
const batch = files.slice(i, i + maxConcurrency);
|
|
363
|
+
const batchPromises = batch.map(async (file) => {
|
|
364
|
+
const startTime = performance.now();
|
|
365
|
+
log.audit(`Auditing: ${file.path}`);
|
|
366
|
+
const result = await auditFile(file.path, file.content, systemPrompt);
|
|
367
|
+
const duration = performance.now() - startTime;
|
|
368
|
+
return {
|
|
369
|
+
filePath: file.path,
|
|
370
|
+
result,
|
|
371
|
+
duration
|
|
372
|
+
};
|
|
373
|
+
});
|
|
374
|
+
const batchResults = await Promise.all(batchPromises);
|
|
375
|
+
results.push(...batchResults);
|
|
376
|
+
log.progress(
|
|
377
|
+
Math.min(i + maxConcurrency, files.length),
|
|
378
|
+
files.length,
|
|
379
|
+
`${results.length}/${files.length} files audited`
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
log.progressEnd();
|
|
383
|
+
return results;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
// src/index.ts
|
|
387
|
+
dotenv.config();
|
|
388
|
+
var parseCliArgs = () => {
|
|
389
|
+
try {
|
|
390
|
+
const { values, positionals } = parseArgs({
|
|
391
|
+
allowPositionals: true,
|
|
392
|
+
options: {
|
|
393
|
+
help: { type: "boolean", short: "h", default: false },
|
|
394
|
+
version: { type: "boolean", short: "v", default: false },
|
|
395
|
+
"skip-commit": { type: "boolean", default: false },
|
|
396
|
+
"skip-files": { type: "boolean", default: false },
|
|
397
|
+
"target-branch": { type: "string", short: "b" },
|
|
398
|
+
concurrency: { type: "string", short: "c", default: "5" },
|
|
399
|
+
verbose: { type: "boolean", default: false }
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
return { values, positionals };
|
|
403
|
+
} catch {
|
|
404
|
+
return {
|
|
405
|
+
values: {
|
|
406
|
+
help: false,
|
|
407
|
+
version: false,
|
|
408
|
+
"skip-commit": false,
|
|
409
|
+
"skip-files": false,
|
|
410
|
+
concurrency: "5",
|
|
411
|
+
verbose: false
|
|
412
|
+
},
|
|
413
|
+
positionals: []
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
var showHelp = () => {
|
|
418
|
+
console.log(`
|
|
419
|
+
\u{1F3D7}\uFE0F Architect AI - AI-powered Code Guardian
|
|
420
|
+
|
|
421
|
+
Usage: architect-ai [options] [files...]
|
|
422
|
+
|
|
423
|
+
Options:
|
|
424
|
+
-h, --help Show this help message
|
|
425
|
+
-v, --version Show version number
|
|
426
|
+
--skip-commit Skip commit message validation
|
|
427
|
+
--skip-files Skip file auditing
|
|
428
|
+
-b, --target-branch Target branch for diff (default: origin/main)
|
|
429
|
+
-c, --concurrency Max concurrent file audits (default: 5)
|
|
430
|
+
--verbose Enable verbose output
|
|
431
|
+
|
|
432
|
+
Examples:
|
|
433
|
+
architect-ai # Audit commit + changed files
|
|
434
|
+
architect-ai --skip-commit # Audit only changed files
|
|
435
|
+
architect-ai src/file.ts # Audit specific file(s)
|
|
436
|
+
architect-ai -b develop # Diff against 'develop' branch
|
|
437
|
+
`);
|
|
438
|
+
};
|
|
439
|
+
var showVersion = () => {
|
|
440
|
+
console.log("1.1.0");
|
|
441
|
+
};
|
|
442
|
+
var printResultsSummary = (results, totalDuration) => {
|
|
443
|
+
const passed = results.filter((r) => r.result.status === "PASS");
|
|
444
|
+
const failed = results.filter((r) => r.result.status === "FAIL");
|
|
445
|
+
const criticalFiles = results.filter(
|
|
446
|
+
(r) => r.result.issues?.some((i) => i.severity === "CRITICAL")
|
|
447
|
+
);
|
|
448
|
+
log.divider();
|
|
449
|
+
console.log();
|
|
450
|
+
console.log(`\u{1F4CA} Audit Summary`);
|
|
451
|
+
console.log(` Total files: ${results.length}`);
|
|
452
|
+
console.log(` \u2705 Passed: ${passed.length}`);
|
|
453
|
+
console.log(` \u274C Failed: ${failed.length}`);
|
|
454
|
+
console.log(` \u{1F6A8} Critical: ${criticalFiles.length}`);
|
|
455
|
+
console.log(` \u23F1\uFE0F Duration: ${formatDuration(totalDuration)}`);
|
|
456
|
+
console.log();
|
|
457
|
+
for (const result of failed) {
|
|
458
|
+
console.log(`\u274C ${result.filePath}:`);
|
|
459
|
+
if (result.result.issues) {
|
|
460
|
+
for (const issue of result.result.issues) {
|
|
461
|
+
log.issue(issue.severity, issue.line, issue.message);
|
|
462
|
+
if (issue.suggestion) {
|
|
463
|
+
log.file(`\u{1F4A1} ${issue.suggestion}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
console.log();
|
|
468
|
+
}
|
|
469
|
+
return criticalFiles.length === 0;
|
|
470
|
+
};
|
|
471
|
+
var run = async () => {
|
|
472
|
+
const startTime = performance.now();
|
|
473
|
+
const { values, positionals } = parseCliArgs();
|
|
474
|
+
if (values.help) {
|
|
475
|
+
showHelp();
|
|
476
|
+
process.exit(0);
|
|
477
|
+
}
|
|
478
|
+
if (values.version) {
|
|
479
|
+
showVersion();
|
|
480
|
+
process.exit(0);
|
|
481
|
+
}
|
|
482
|
+
if (!await isGitRepository()) {
|
|
483
|
+
log.error("Not a git repository. Please run from a git project root.");
|
|
484
|
+
process.exit(1);
|
|
485
|
+
}
|
|
486
|
+
log.header("Architect AI - Code Audit");
|
|
487
|
+
const config2 = await loadProjectConfig();
|
|
488
|
+
const maxConcurrency = parseInt(values.concurrency, 10) || 5;
|
|
489
|
+
const targetBranch = values["target-branch"] ?? process.env.TARGET_BRANCH ?? "origin/main";
|
|
490
|
+
const commitMsg = await getLastCommitMessage();
|
|
491
|
+
const currentBranch = await getCurrentBranch();
|
|
492
|
+
if (values.verbose) {
|
|
493
|
+
log.info(`Current branch: ${currentBranch}`);
|
|
494
|
+
log.info(`Target branch: ${targetBranch}`);
|
|
495
|
+
log.info(`Max concurrency: ${maxConcurrency}`);
|
|
496
|
+
}
|
|
497
|
+
const bypassKey = config2.bypassKeyword ?? "skip:";
|
|
498
|
+
if (commitMsg.toLowerCase().includes(bypassKey.toLowerCase())) {
|
|
499
|
+
log.skip(`BYPASS DETECTED in commit message: "${commitMsg}"`);
|
|
500
|
+
log.skip("Skipping Audit Checks as requested.");
|
|
501
|
+
process.exit(0);
|
|
502
|
+
}
|
|
503
|
+
let hasErrors = false;
|
|
504
|
+
if (!values["skip-commit"] && commitMsg) {
|
|
505
|
+
const commitResult = await auditCommit(commitMsg, config2);
|
|
506
|
+
if (commitResult.status === "PASS") {
|
|
507
|
+
log.success("Commit Message: OK");
|
|
508
|
+
} else {
|
|
509
|
+
log.error(`Commit Message Invalid: ${commitResult.message ?? "Unknown error"}`);
|
|
510
|
+
if (commitResult.suggestion) {
|
|
511
|
+
log.file(`\u{1F4A1} Suggestion: ${commitResult.suggestion}`);
|
|
512
|
+
}
|
|
513
|
+
hasErrors = true;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
let filesToAudit;
|
|
517
|
+
if (positionals.length > 0) {
|
|
518
|
+
filesToAudit = positionals;
|
|
519
|
+
log.info(`Using ${filesToAudit.length} specified file(s)`);
|
|
520
|
+
} else if (!values["skip-files"]) {
|
|
521
|
+
filesToAudit = await getChangedFiles({ targetBranch });
|
|
522
|
+
if (filesToAudit.length === 0) {
|
|
523
|
+
log.success("No relevant code changes detected.");
|
|
524
|
+
process.exit(hasErrors ? 1 : 0);
|
|
525
|
+
}
|
|
526
|
+
log.info(`Found ${filesToAudit.length} changed file(s) to audit`);
|
|
527
|
+
} else {
|
|
528
|
+
filesToAudit = [];
|
|
529
|
+
}
|
|
530
|
+
if (filesToAudit.length > 0) {
|
|
531
|
+
const fileReadResult = await readFilesForAudit(filesToAudit);
|
|
532
|
+
if (fileReadResult.success.length === 0) {
|
|
533
|
+
log.warning("No files could be read for auditing.");
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
const auditResults = await auditFilesWithConcurrency(
|
|
537
|
+
fileReadResult.success.map((f) => ({ path: f.path, content: f.content })),
|
|
538
|
+
config2,
|
|
539
|
+
maxConcurrency
|
|
540
|
+
);
|
|
541
|
+
const auditDuration = performance.now() - startTime;
|
|
542
|
+
const allPassed = printResultsSummary(auditResults, auditDuration);
|
|
543
|
+
if (!allPassed) {
|
|
544
|
+
hasErrors = true;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (hasErrors) {
|
|
548
|
+
log.critical("Audit Failed: Critical issues found.");
|
|
549
|
+
process.exit(1);
|
|
550
|
+
} else {
|
|
551
|
+
log.success("All checks passed! \u2728");
|
|
552
|
+
process.exit(0);
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
run().catch((error) => {
|
|
556
|
+
log.critical(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`);
|
|
557
|
+
process.exit(1);
|
|
558
|
+
});
|
|
559
|
+
//# sourceMappingURL=index.js.map
|
|
560
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types/index.ts","../src/utils/config.ts","../src/utils/git.ts","../src/utils/logger.ts","../src/utils/parser.ts","../src/services/file.ts","../src/config/prompts.ts","../src/services/ai.ts","../src/index.ts"],"names":["resolve","existsSync","readFile","config"],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * Type definitions for architect-ai CLI\r\n */\r\n\r\nexport interface ProjectConfig {\r\n techStack?: string;\r\n rules?: string[];\r\n bypassKeyword?: string;\r\n commitFormat?: string;\r\n maxConcurrency?: number;\r\n cacheEnabled?: boolean;\r\n}\r\n\r\nexport interface AuditIssue {\r\n line: number;\r\n severity: 'CRITICAL' | 'WARNING' | 'INFO';\r\n message: string;\r\n suggestion: string;\r\n}\r\n\r\nexport interface AuditResult {\r\n status: 'PASS' | 'FAIL';\r\n issues?: AuditIssue[];\r\n message?: string;\r\n suggestion?: string;\r\n}\r\n\r\nexport interface FileAuditResult {\r\n filePath: string;\r\n result: AuditResult;\r\n duration: number;\r\n}\r\n\r\nexport interface CLIOptions {\r\n files?: string[];\r\n commit?: boolean;\r\n bypass?: boolean;\r\n verbose?: boolean;\r\n maxConcurrency?: number;\r\n targetBranch?: string;\r\n}\r\n\r\nexport const DEFAULT_CONFIG: Required<ProjectConfig> = {\r\n techStack: '',\r\n rules: [],\r\n bypassKeyword: 'skip:',\r\n commitFormat: '',\r\n maxConcurrency: 5,\r\n cacheEnabled: true,\r\n};\r\n","/**\r\n * Configuration loader with caching and validation\r\n */\r\n\r\nimport { readFile } from 'node:fs/promises';\r\nimport { existsSync } from 'node:fs';\r\nimport { resolve } from 'node:path';\r\nimport type { ProjectConfig } from '../types/index.js';\r\nimport { DEFAULT_CONFIG } from '../types/index.js';\r\n\r\nlet cachedConfig: ProjectConfig | null = null;\r\n\r\n/**\r\n * Load and validate project configuration\r\n */\r\nexport const loadProjectConfig = async (cwd: string = process.cwd()): Promise<ProjectConfig> => {\r\n if (cachedConfig) {\r\n return cachedConfig;\r\n }\r\n\r\n const configPath = resolve(cwd, '.architectrc.json');\r\n \r\n if (!existsSync(configPath)) {\r\n cachedConfig = { ...DEFAULT_CONFIG };\r\n return cachedConfig;\r\n }\r\n\r\n try {\r\n const content = await readFile(configPath, 'utf-8');\r\n const userConfig = JSON.parse(content) as Partial<ProjectConfig>;\r\n \r\n // Merge with defaults\r\n cachedConfig = {\r\n ...DEFAULT_CONFIG,\r\n ...userConfig,\r\n };\r\n \r\n console.log('⚙️ Loaded project-specific rules from .architectrc.json');\r\n return cachedConfig;\r\n } catch (error) {\r\n console.warn('⚠️ Found .architectrc.json but failed to parse it.');\r\n cachedConfig = { ...DEFAULT_CONFIG };\r\n return cachedConfig;\r\n }\r\n};\r\n\r\n/**\r\n * Clear config cache (useful for testing)\r\n */\r\nexport const clearConfigCache = (): void => {\r\n cachedConfig = null;\r\n};\r\n\r\n/**\r\n * Validate configuration schema\r\n */\r\nexport const validateConfig = (config: unknown): config is ProjectConfig => {\r\n if (typeof config !== 'object' || config === null) {\r\n return false;\r\n }\r\n\r\n const c = config as Record<string, unknown>;\r\n\r\n if (c.techStack !== undefined && typeof c.techStack !== 'string') {\r\n return false;\r\n }\r\n\r\n if (c.rules !== undefined && !Array.isArray(c.rules)) {\r\n return false;\r\n }\r\n\r\n if (c.bypassKeyword !== undefined && typeof c.bypassKeyword !== 'string') {\r\n return false;\r\n }\r\n\r\n if (c.commitFormat !== undefined && typeof c.commitFormat !== 'string') {\r\n return false;\r\n }\r\n\r\n if (c.maxConcurrency !== undefined && typeof c.maxConcurrency !== 'number') {\r\n return false;\r\n }\r\n\r\n return true;\r\n};\r\n","/**\r\n * Git utilities with async support and better error handling\r\n */\r\n\r\nimport { exec } from 'node:child_process';\r\nimport { promisify } from 'node:util';\r\n\r\nconst execAsync = promisify(exec);\r\n\r\nconst SUPPORTED_EXTENSIONS = /\\.(ts|tsx|js|jsx|mjs|cjs|py|cs|go|java|rs|kt|swift)$/;\r\n\r\nexport interface GitDiffOptions {\r\n targetBranch?: string;\r\n extensions?: RegExp;\r\n}\r\n\r\n/**\r\n * Get last commit message asynchronously\r\n */\r\nexport const getLastCommitMessage = async (): Promise<string> => {\r\n try {\r\n const { stdout } = await execAsync('git log -1 --pretty=%B');\r\n return stdout.trim();\r\n } catch {\r\n return '';\r\n }\r\n};\r\n\r\n/**\r\n * Get changed files against target branch with parallel execution\r\n */\r\nexport const getChangedFiles = async (options: GitDiffOptions = {}): Promise<string[]> => {\r\n const { targetBranch = 'origin/main', extensions = SUPPORTED_EXTENSIONS } = options;\r\n\r\n try {\r\n // Try three-dot diff first (for PR/MR scenarios)\r\n const command = `git diff --name-only --diff-filter=ACMR ${targetBranch}...HEAD`;\r\n const { stdout } = await execAsync(command);\r\n \r\n return parseAndFilterFiles(stdout, extensions);\r\n } catch {\r\n try {\r\n // Fallback to two-dot diff\r\n const command = `git diff --name-only --diff-filter=ACMR ${targetBranch}`;\r\n const { stdout } = await execAsync(command);\r\n return parseAndFilterFiles(stdout, extensions);\r\n } catch {\r\n console.warn('⚠️ Git diff failed. No files to audit.');\r\n return [];\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * Check if current directory is a git repository\r\n */\r\nexport const isGitRepository = async (): Promise<boolean> => {\r\n try {\r\n await execAsync('git rev-parse --is-inside-work-tree');\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n};\r\n\r\n/**\r\n * Get current branch name\r\n */\r\nexport const getCurrentBranch = async (): Promise<string> => {\r\n try {\r\n const { stdout } = await execAsync('git branch --show-current');\r\n return stdout.trim();\r\n } catch {\r\n return 'unknown';\r\n }\r\n};\r\n\r\nconst parseAndFilterFiles = (output: string, extensions: RegExp): string[] => {\r\n return output\r\n .split('\\n')\r\n .map(file => file.trim())\r\n .filter(file => file.length > 0)\r\n .filter(file => extensions.test(file));\r\n};\r\n","/**\r\n * Console output utilities with colors\r\n */\r\n\r\n// ANSI color codes\r\nconst colors = {\r\n reset: '\\x1b[0m',\r\n bold: '\\x1b[1m',\r\n dim: '\\x1b[2m',\r\n red: '\\x1b[31m',\r\n green: '\\x1b[32m',\r\n yellow: '\\x1b[33m',\r\n blue: '\\x1b[34m',\r\n magenta: '\\x1b[35m',\r\n cyan: '\\x1b[36m',\r\n white: '\\x1b[37m',\r\n} as const;\r\n\r\nexport const log = {\r\n info: (msg: string) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`),\r\n success: (msg: string) => console.log(`${colors.green}✅${colors.reset} ${msg}`),\r\n warning: (msg: string) => console.warn(`${colors.yellow}⚠️${colors.reset} ${msg}`),\r\n error: (msg: string) => console.error(`${colors.red}❌${colors.reset} ${msg}`),\r\n critical: (msg: string) => console.error(`${colors.red}${colors.bold}🚨${colors.reset} ${msg}`),\r\n audit: (msg: string) => console.log(`${colors.cyan}🔍${colors.reset} ${msg}`),\r\n skip: (msg: string) => console.log(`${colors.magenta}⏩${colors.reset} ${msg}`),\r\n file: (msg: string) => console.log(`${colors.dim} ${msg}${colors.reset}`),\r\n \r\n // Issue formatting\r\n issue: (severity: string, line: number, message: string) => {\r\n const color = severity === 'CRITICAL' ? colors.red : \r\n severity === 'WARNING' ? colors.yellow : \r\n colors.blue;\r\n console.log(` ${color}[${severity}] Line ${line}: ${message}${colors.reset}`);\r\n },\r\n \r\n // Progress bar\r\n progress: (current: number, total: number, label: string) => {\r\n const percent = Math.round((current / total) * 100);\r\n const barLength = 20;\r\n const filled = Math.round((current / total) * barLength);\r\n const bar = '█'.repeat(filled) + '░'.repeat(barLength - filled);\r\n process.stdout.write(`\\r${colors.cyan}${bar}${colors.reset} ${percent}% | ${label}`);\r\n },\r\n \r\n progressEnd: () => {\r\n console.log(); // New line after progress\r\n },\r\n\r\n // Divider\r\n divider: () => console.log(`${colors.dim}${'─'.repeat(50)}${colors.reset}`),\r\n \r\n // Header\r\n header: (title: string) => {\r\n console.log();\r\n console.log(`${colors.bold}${colors.cyan}🏗️ ${title}${colors.reset}`);\r\n console.log(`${colors.dim}${'─'.repeat(50)}${colors.reset}`);\r\n },\r\n};\r\n\r\n/**\r\n * Format duration for display\r\n */\r\nexport const formatDuration = (ms: number): string => {\r\n if (ms < 1000) return `${Math.round(ms)}ms`;\r\n if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;\r\n return `${(ms / 60000).toFixed(1)}m`;\r\n};\r\n","/**\r\n * Parsing utilities for AI responses\r\n */\r\n\r\nimport type { AuditResult } from '../types/index.js';\r\n\r\n/**\r\n * Clean JSON from markdown code blocks\r\n */\r\nexport const cleanJSON = (text: string): string => {\r\n return text\r\n .replace(/```json\\s*/gi, '')\r\n .replace(/```\\s*/g, '')\r\n .trim();\r\n};\r\n\r\n/**\r\n * Parse AI response to AuditResult with error handling\r\n */\r\nexport const parseAuditResponse = (responseText: string): AuditResult => {\r\n const cleaned = cleanJSON(responseText);\r\n \r\n try {\r\n const parsed = JSON.parse(cleaned) as AuditResult;\r\n \r\n // Validate required fields\r\n if (!parsed.status || !['PASS', 'FAIL'].includes(parsed.status)) {\r\n return {\r\n status: 'FAIL',\r\n message: 'Invalid AI response format',\r\n issues: [],\r\n };\r\n }\r\n \r\n return parsed;\r\n } catch {\r\n // Try to extract JSON from the response\r\n const jsonMatch = cleaned.match(/\\{[\\s\\S]*\\}/);\r\n if (jsonMatch) {\r\n try {\r\n return JSON.parse(jsonMatch[0]) as AuditResult;\r\n } catch {\r\n // Fall through to error response\r\n }\r\n }\r\n \r\n return {\r\n status: 'FAIL',\r\n message: 'Failed to parse AI response',\r\n issues: [],\r\n };\r\n }\r\n};\r\n\r\n/**\r\n * Format file size for display\r\n */\r\nexport const formatBytes = (bytes: number): string => {\r\n if (bytes === 0) return '0 B';\r\n const k = 1024;\r\n const sizes = ['B', 'KB', 'MB', 'GB'];\r\n const i = Math.floor(Math.log(bytes) / Math.log(k));\r\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;\r\n};\r\n\r\n/**\r\n * Format duration for display\r\n */\r\nexport const formatDuration = (ms: number): string => {\r\n if (ms < 1000) return `${ms}ms`;\r\n if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;\r\n return `${(ms / 60000).toFixed(1)}m`;\r\n};\r\n","/**\r\n * File service with streaming support and memory optimization\r\n */\r\n\r\nimport { readFile, stat } from 'node:fs/promises';\r\nimport { existsSync } from 'node:fs';\r\nimport { resolve } from 'node:path';\r\nimport { log } from '../utils/logger.js';\r\nimport { formatBytes } from '../utils/parser.js';\r\n\r\nconst MAX_FILE_SIZE = 500 * 1024; // 500KB limit per file\r\n\r\nexport interface FileContent {\r\n path: string;\r\n content: string;\r\n size: number;\r\n}\r\n\r\ninterface SkippedFile {\r\n path: string;\r\n skipped: true;\r\n reason: string;\r\n}\r\n\r\ninterface ReadFile {\r\n path: string;\r\n content: string;\r\n size: number;\r\n skipped: false;\r\n}\r\n\r\ntype FileReadItem = SkippedFile | ReadFile;\r\n\r\nexport interface FileReadResult {\r\n success: FileContent[];\r\n skipped: Array<{ path: string; reason: string }>;\r\n}\r\n\r\n/**\r\n * Read multiple files efficiently with size limits\r\n */\r\nexport const readFilesForAudit = async (filePaths: string[]): Promise<FileReadResult> => {\r\n const result: FileReadResult = {\r\n success: [],\r\n skipped: [],\r\n };\r\n\r\n const readPromises = filePaths.map(async (filePath): Promise<FileReadItem> => {\r\n const absolutePath = resolve(filePath);\r\n \r\n // Check existence\r\n if (!existsSync(absolutePath)) {\r\n return { path: filePath, skipped: true, reason: 'File not found' };\r\n }\r\n\r\n try {\r\n // Check file size first\r\n const stats = await stat(absolutePath);\r\n \r\n if (stats.size > MAX_FILE_SIZE) {\r\n return { \r\n path: filePath, \r\n skipped: true, \r\n reason: `File too large (${formatBytes(stats.size)})` \r\n };\r\n }\r\n\r\n // Read file content\r\n const content = await readFile(absolutePath, 'utf-8');\r\n \r\n return {\r\n path: filePath,\r\n content,\r\n size: stats.size,\r\n skipped: false,\r\n };\r\n } catch (error) {\r\n return { \r\n path: filePath, \r\n skipped: true, \r\n reason: error instanceof Error ? error.message : 'Read error' \r\n };\r\n }\r\n });\r\n\r\n const results = await Promise.all(readPromises);\r\n\r\n for (const item of results) {\r\n if (item.skipped) {\r\n result.skipped.push({ path: item.path, reason: item.reason });\r\n } else {\r\n result.success.push({\r\n path: item.path,\r\n content: item.content,\r\n size: item.size,\r\n });\r\n }\r\n }\r\n\r\n // Log skipped files\r\n if (result.skipped.length > 0) {\r\n log.warning(`Skipped ${result.skipped.length} file(s):`);\r\n for (const { path, reason } of result.skipped) {\r\n log.file(`${path}: ${reason}`);\r\n }\r\n }\r\n\r\n return result;\r\n};\r\n\r\n/**\r\n * Get file extension without dot\r\n */\r\nexport const getFileExtension = (filePath: string): string => {\r\n const match = filePath.match(/\\.([^.]+)$/);\r\n return match?.[1] ?? '';\r\n};\r\n\r\n/**\r\n * Check if file is a code file\r\n */\r\nexport const isCodeFile = (filePath: string): boolean => {\r\n const codeExtensions = new Set([\r\n 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs',\r\n 'py', 'cs', 'go', 'java', 'rs', 'kt', 'swift',\r\n 'cpp', 'c', 'h', 'hpp', 'rb', 'php'\r\n ]);\r\n return codeExtensions.has(getFileExtension(filePath));\r\n};\r\n\r\n// Re-export formatBytes for convenience\r\nexport { formatBytes };\r\n","/**\r\n * Prompt templates for AI-powered code auditing\r\n */\r\n\r\nimport type { ProjectConfig } from '../types/index.js';\r\n\r\nexport const DEFAULT_COMMIT_PROMPT = `\r\n### ROLE\r\nYou are a Strict Release Manager. You enforce \"Conventional Commits\" standards.\r\n### RULES\r\n1. Format MUST be: \\`<type>(<scope>): <subject>\\`\r\n - Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert.\r\n - Example: \"feat(auth): add google login support\"\r\n2. Subject must be lowercase and imperative (e.g., \"add\" not \"added\").\r\n3. Message must be descriptive enough to understand the change context.\r\n`;\r\n\r\nexport const BASE_AUDIT_PROMPT = `\r\n### ROLE & OBJECTIVE\r\nYou are an Elite Software Architect. Your goal is to enforce \"Clean Code\", \"SOLID Principles\", and \"Maintainability\".\r\n### GLOBAL STANDARDS\r\n1. **Clean Code:** Variable/Function names must be semantic. No magic numbers.\r\n2. **Split Code:** Suggest splitting complex functions/components.\r\n3. **Performance:** Identify obvious bottlenecks.\r\n4. **Error Handling:** Ensure proper boundary checks.\r\n`;\r\n\r\nexport const buildSystemPrompt = (config: ProjectConfig): string => {\r\n const parts: string[] = [BASE_AUDIT_PROMPT];\r\n\r\n if (config.techStack) {\r\n parts.push(`\\n### TECH STACK CONTEXT\\nThe code is written in: ${config.techStack}\\n`);\r\n }\r\n\r\n if (config.rules && config.rules.length > 0) {\r\n parts.push(`\\n### PROJECT SPECIFIC RULES (HIGHEST PRIORITY)\\n`);\r\n config.rules.forEach((rule, index) => {\r\n parts.push(`${index + 1}. ${rule}\\n`);\r\n });\r\n }\r\n\r\n parts.push(`\\n### OUTPUT FORMAT (JSON ONLY)\\n{ \"status\": \"PASS\" | \"FAIL\", \"issues\": [{ \"line\": number, \"severity\": \"CRITICAL\" | \"WARNING\", \"message\": \"string\", \"suggestion\": \"string\" }] }`);\r\n \r\n return parts.join('');\r\n};\r\n\r\nexport const buildCommitPrompt = (customFormat?: string): string => {\r\n const parts: string[] = [];\r\n \r\n if (customFormat) {\r\n parts.push(`\r\n### ROLE\r\nYou are a Strict Release Manager. \r\n### RULES (CUSTOM COMPANY POLICY)\r\nYou must enforce the following strict commit message format:\r\n\"${customFormat}\"\r\n\r\nAny commit message NOT following this pattern must be REJECTED.\r\n `);\r\n } else {\r\n parts.push(DEFAULT_COMMIT_PROMPT);\r\n }\r\n \r\n parts.push(`\\n### OUTPUT (JSON ONLY)\\n{ \"status\": \"PASS\" | \"FAIL\", \"message\": \"Reason for failure\", \"suggestion\": \"Corrected example\" }`);\r\n \r\n return parts.join('');\r\n};\r\n","/**\r\n * Core AI service with singleton pattern and request pooling\r\n */\r\n\r\nimport { GoogleGenerativeAI, type GenerativeModel } from '@google/generative-ai';\r\nimport type { AuditResult, ProjectConfig, FileAuditResult } from '../types/index.js';\r\nimport { buildSystemPrompt, buildCommitPrompt } from '../config/prompts.js';\r\nimport { parseAuditResponse } from '../utils/parser.js';\r\nimport { log } from '../utils/logger.js';\r\n\r\nlet modelInstance: GenerativeModel | null = null;\r\n\r\n/**\r\n * Initialize AI model (singleton pattern)\r\n */\r\nconst getModel = (): GenerativeModel => {\r\n if (modelInstance) {\r\n return modelInstance;\r\n }\r\n\r\n const apiKey = process.env.GEMINI_API_KEY;\r\n if (!apiKey) {\r\n throw new Error('GEMINI_API_KEY environment variable is required');\r\n }\r\n\r\n const genAI = new GoogleGenerativeAI(apiKey);\r\n modelInstance = genAI.getGenerativeModel({\r\n model: 'gemini-1.5-pro',\r\n generationConfig: {\r\n temperature: 0.2,\r\n maxOutputTokens: 8192,\r\n },\r\n });\r\n\r\n return modelInstance;\r\n};\r\n\r\n/**\r\n * Audit commit message\r\n */\r\nexport const auditCommit = async (\r\n message: string,\r\n config: ProjectConfig\r\n): Promise<AuditResult> => {\r\n log.audit(`Auditing Commit Message: \"${message}\"...`);\r\n \r\n const systemPrompt = buildCommitPrompt(config.commitFormat);\r\n const model = getModel();\r\n\r\n try {\r\n const result = await model.generateContent([\r\n systemPrompt,\r\n `Commit Message: \"${message}\"`,\r\n ]);\r\n \r\n return parseAuditResponse(result.response.text());\r\n } catch (error) {\r\n log.warning('AI check failed. Skipping commit check.');\r\n return { status: 'PASS', message: 'AI unavailable - skipped' };\r\n }\r\n};\r\n\r\n/**\r\n * Audit single file\r\n */\r\nexport const auditFile = async (\r\n filePath: string,\r\n content: string,\r\n systemPrompt: string\r\n): Promise<AuditResult> => {\r\n const model = getModel();\r\n\r\n try {\r\n const result = await model.generateContent([\r\n systemPrompt,\r\n `Code to review:\\n${content}`,\r\n ]);\r\n \r\n return parseAuditResponse(result.response.text());\r\n } catch (error) {\r\n return {\r\n status: 'FAIL',\r\n message: `Error auditing file: ${error instanceof Error ? error.message : 'Unknown error'}`,\r\n issues: [],\r\n };\r\n }\r\n};\r\n\r\n/**\r\n * Audit multiple files with concurrency control\r\n */\r\nexport const auditFilesWithConcurrency = async (\r\n files: Array<{ path: string; content: string }>,\r\n config: ProjectConfig,\r\n maxConcurrency: number = 5\r\n): Promise<FileAuditResult[]> => {\r\n const systemPrompt = buildSystemPrompt(config);\r\n const results: FileAuditResult[] = [];\r\n \r\n // Process files in batches\r\n for (let i = 0; i < files.length; i += maxConcurrency) {\r\n const batch = files.slice(i, i + maxConcurrency);\r\n \r\n const batchPromises = batch.map(async (file) => {\r\n const startTime = performance.now();\r\n log.audit(`Auditing: ${file.path}`);\r\n \r\n const result = await auditFile(file.path, file.content, systemPrompt);\r\n const duration = performance.now() - startTime;\r\n \r\n return {\r\n filePath: file.path,\r\n result,\r\n duration,\r\n };\r\n });\r\n\r\n const batchResults = await Promise.all(batchPromises);\r\n results.push(...batchResults);\r\n \r\n // Progress feedback\r\n log.progress(Math.min(i + maxConcurrency, files.length), files.length, \r\n `${results.length}/${files.length} files audited`);\r\n }\r\n \r\n log.progressEnd();\r\n return results;\r\n};\r\n\r\n/**\r\n * Clear model cache (useful for testing)\r\n */\r\nexport const clearModelCache = (): void => {\r\n modelInstance = null;\r\n};\r\n","#!/usr/bin/env node\r\n/**\r\n * Architect AI - CLI Entry Point\r\n * High-performance CLI for AI-powered code auditing\r\n */\r\n\r\nimport * as dotenv from 'dotenv';\r\nimport { parseArgs } from 'node:util';\r\n\r\nimport type { ProjectConfig, FileAuditResult } from './types/index.js';\r\nimport { loadProjectConfig } from './utils/config.js';\r\nimport { getLastCommitMessage, getChangedFiles, isGitRepository, getCurrentBranch } from './utils/git.js';\r\nimport { readFilesForAudit } from './services/file.js';\r\nimport { auditCommit, auditFilesWithConcurrency } from './services/ai.js';\r\nimport { log, formatDuration } from './utils/logger.js';\r\n\r\n// Load environment variables\r\ndotenv.config();\r\n\r\n// CLI Options interface\r\ninterface CLIValues {\r\n help: boolean;\r\n version: boolean;\r\n 'skip-commit': boolean;\r\n 'skip-files': boolean;\r\n 'target-branch'?: string;\r\n concurrency: string;\r\n verbose: boolean;\r\n}\r\n\r\n// CLI argument parsing\r\nconst parseCliArgs = (): { values: CLIValues; positionals: string[] } => {\r\n try {\r\n const { values, positionals } = parseArgs({\r\n allowPositionals: true,\r\n options: {\r\n help: { type: 'boolean', short: 'h', default: false },\r\n version: { type: 'boolean', short: 'v', default: false },\r\n 'skip-commit': { type: 'boolean', default: false },\r\n 'skip-files': { type: 'boolean', default: false },\r\n 'target-branch': { type: 'string', short: 'b' },\r\n concurrency: { type: 'string', short: 'c', default: '5' },\r\n verbose: { type: 'boolean', default: false },\r\n },\r\n });\r\n return { values: values as CLIValues, positionals };\r\n } catch {\r\n return { \r\n values: {\r\n help: false,\r\n version: false,\r\n 'skip-commit': false,\r\n 'skip-files': false,\r\n concurrency: '5',\r\n verbose: false,\r\n }, \r\n positionals: [] \r\n };\r\n }\r\n};\r\n\r\nconst showHelp = () => {\r\n console.log(`\r\n🏗️ Architect AI - AI-powered Code Guardian\r\n\r\nUsage: architect-ai [options] [files...]\r\n\r\nOptions:\r\n -h, --help Show this help message\r\n -v, --version Show version number\r\n --skip-commit Skip commit message validation\r\n --skip-files Skip file auditing\r\n -b, --target-branch Target branch for diff (default: origin/main)\r\n -c, --concurrency Max concurrent file audits (default: 5)\r\n --verbose Enable verbose output\r\n\r\nExamples:\r\n architect-ai # Audit commit + changed files\r\n architect-ai --skip-commit # Audit only changed files\r\n architect-ai src/file.ts # Audit specific file(s)\r\n architect-ai -b develop # Diff against 'develop' branch\r\n`);\r\n};\r\n\r\nconst showVersion = () => {\r\n console.log('1.1.0');\r\n};\r\n\r\n/**\r\n * Print audit results summary\r\n */\r\nconst printResultsSummary = (results: FileAuditResult[], totalDuration: number): boolean => {\r\n const passed = results.filter(r => r.result.status === 'PASS');\r\n const failed = results.filter(r => r.result.status === 'FAIL');\r\n const criticalFiles = results.filter(r => \r\n r.result.issues?.some(i => i.severity === 'CRITICAL')\r\n );\r\n\r\n log.divider();\r\n console.log();\r\n console.log(`📊 Audit Summary`);\r\n console.log(` Total files: ${results.length}`);\r\n console.log(` ✅ Passed: ${passed.length}`);\r\n console.log(` ❌ Failed: ${failed.length}`);\r\n console.log(` 🚨 Critical: ${criticalFiles.length}`);\r\n console.log(` ⏱️ Duration: ${formatDuration(totalDuration)}`);\r\n console.log();\r\n\r\n // Print detailed issues\r\n for (const result of failed) {\r\n console.log(`❌ ${result.filePath}:`);\r\n if (result.result.issues) {\r\n for (const issue of result.result.issues) {\r\n log.issue(issue.severity, issue.line, issue.message);\r\n if (issue.suggestion) {\r\n log.file(`💡 ${issue.suggestion}`);\r\n }\r\n }\r\n }\r\n console.log();\r\n }\r\n\r\n return criticalFiles.length === 0;\r\n};\r\n\r\n/**\r\n * Main CLI execution\r\n */\r\nconst run = async () => {\r\n const startTime = performance.now();\r\n const { values, positionals } = parseCliArgs();\r\n\r\n // Handle --help and --version\r\n if (values.help) {\r\n showHelp();\r\n process.exit(0);\r\n }\r\n\r\n if (values.version) {\r\n showVersion();\r\n process.exit(0);\r\n }\r\n\r\n // Check if in git repository\r\n if (!await isGitRepository()) {\r\n log.error('Not a git repository. Please run from a git project root.');\r\n process.exit(1);\r\n }\r\n\r\n log.header('Architect AI - Code Audit');\r\n\r\n // Load configuration\r\n const config: ProjectConfig = await loadProjectConfig();\r\n const maxConcurrency = parseInt(values.concurrency, 10) || 5;\r\n const targetBranch = values['target-branch'] ?? process.env.TARGET_BRANCH ?? 'origin/main';\r\n\r\n // Get commit message\r\n const commitMsg = await getLastCommitMessage();\r\n const currentBranch = await getCurrentBranch();\r\n \r\n if (values.verbose) {\r\n log.info(`Current branch: ${currentBranch}`);\r\n log.info(`Target branch: ${targetBranch}`);\r\n log.info(`Max concurrency: ${maxConcurrency}`);\r\n }\r\n\r\n // Check bypass\r\n const bypassKey = config.bypassKeyword ?? 'skip:';\r\n if (commitMsg.toLowerCase().includes(bypassKey.toLowerCase())) {\r\n log.skip(`BYPASS DETECTED in commit message: \"${commitMsg}\"`);\r\n log.skip('Skipping Audit Checks as requested.');\r\n process.exit(0);\r\n }\r\n\r\n let hasErrors = false;\r\n\r\n // Audit commit message\r\n if (!values['skip-commit'] && commitMsg) {\r\n const commitResult = await auditCommit(commitMsg, config);\r\n \r\n if (commitResult.status === 'PASS') {\r\n log.success('Commit Message: OK');\r\n } else {\r\n log.error(`Commit Message Invalid: ${commitResult.message ?? 'Unknown error'}`);\r\n if (commitResult.suggestion) {\r\n log.file(`💡 Suggestion: ${commitResult.suggestion}`);\r\n }\r\n hasErrors = true;\r\n }\r\n }\r\n\r\n // Get files to audit\r\n let filesToAudit: string[];\r\n \r\n if (positionals.length > 0) {\r\n // Use specified files\r\n filesToAudit = positionals;\r\n log.info(`Using ${filesToAudit.length} specified file(s)`);\r\n } else if (!values['skip-files']) {\r\n // Get changed files from git\r\n filesToAudit = await getChangedFiles({ targetBranch });\r\n \r\n if (filesToAudit.length === 0) {\r\n log.success('No relevant code changes detected.');\r\n process.exit(hasErrors ? 1 : 0);\r\n }\r\n \r\n log.info(`Found ${filesToAudit.length} changed file(s) to audit`);\r\n } else {\r\n filesToAudit = [];\r\n }\r\n\r\n // Audit files\r\n if (filesToAudit.length > 0) {\r\n // Read files efficiently\r\n const fileReadResult = await readFilesForAudit(filesToAudit);\r\n \r\n if (fileReadResult.success.length === 0) {\r\n log.warning('No files could be read for auditing.');\r\n process.exit(1);\r\n }\r\n\r\n // Audit with concurrency\r\n const auditResults = await auditFilesWithConcurrency(\r\n fileReadResult.success.map(f => ({ path: f.path, content: f.content })),\r\n config,\r\n maxConcurrency\r\n );\r\n\r\n // Print summary\r\n const auditDuration = performance.now() - startTime;\r\n const allPassed = printResultsSummary(auditResults, auditDuration);\r\n \r\n if (!allPassed) {\r\n hasErrors = true;\r\n }\r\n }\r\n\r\n // Final exit\r\n if (hasErrors) {\r\n log.critical('Audit Failed: Critical issues found.');\r\n process.exit(1);\r\n } else {\r\n log.success('All checks passed! ✨');\r\n process.exit(0);\r\n }\r\n};\r\n\r\n// Execute\r\nrun().catch((error: unknown) => {\r\n log.critical(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`);\r\n process.exit(1);\r\n});\r\n"]}
|