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/lib.d.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for architect-ai CLI
|
|
3
|
+
*/
|
|
4
|
+
interface ProjectConfig {
|
|
5
|
+
techStack?: string;
|
|
6
|
+
rules?: string[];
|
|
7
|
+
bypassKeyword?: string;
|
|
8
|
+
commitFormat?: string;
|
|
9
|
+
maxConcurrency?: number;
|
|
10
|
+
cacheEnabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface AuditIssue {
|
|
13
|
+
line: number;
|
|
14
|
+
severity: 'CRITICAL' | 'WARNING' | 'INFO';
|
|
15
|
+
message: string;
|
|
16
|
+
suggestion: string;
|
|
17
|
+
}
|
|
18
|
+
interface AuditResult {
|
|
19
|
+
status: 'PASS' | 'FAIL';
|
|
20
|
+
issues?: AuditIssue[];
|
|
21
|
+
message?: string;
|
|
22
|
+
suggestion?: string;
|
|
23
|
+
}
|
|
24
|
+
interface FileAuditResult {
|
|
25
|
+
filePath: string;
|
|
26
|
+
result: AuditResult;
|
|
27
|
+
duration: number;
|
|
28
|
+
}
|
|
29
|
+
interface CLIOptions {
|
|
30
|
+
files?: string[];
|
|
31
|
+
commit?: boolean;
|
|
32
|
+
bypass?: boolean;
|
|
33
|
+
verbose?: boolean;
|
|
34
|
+
maxConcurrency?: number;
|
|
35
|
+
targetBranch?: string;
|
|
36
|
+
}
|
|
37
|
+
declare const DEFAULT_CONFIG: Required<ProjectConfig>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Configuration loader with caching and validation
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load and validate project configuration
|
|
45
|
+
*/
|
|
46
|
+
declare const loadProjectConfig: (cwd?: string) => Promise<ProjectConfig>;
|
|
47
|
+
/**
|
|
48
|
+
* Clear config cache (useful for testing)
|
|
49
|
+
*/
|
|
50
|
+
declare const clearConfigCache: () => void;
|
|
51
|
+
/**
|
|
52
|
+
* Validate configuration schema
|
|
53
|
+
*/
|
|
54
|
+
declare const validateConfig: (config: unknown) => config is ProjectConfig;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Git utilities with async support and better error handling
|
|
58
|
+
*/
|
|
59
|
+
interface GitDiffOptions {
|
|
60
|
+
targetBranch?: string;
|
|
61
|
+
extensions?: RegExp;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get last commit message asynchronously
|
|
65
|
+
*/
|
|
66
|
+
declare const getLastCommitMessage: () => Promise<string>;
|
|
67
|
+
/**
|
|
68
|
+
* Get changed files against target branch with parallel execution
|
|
69
|
+
*/
|
|
70
|
+
declare const getChangedFiles: (options?: GitDiffOptions) => Promise<string[]>;
|
|
71
|
+
/**
|
|
72
|
+
* Check if current directory is a git repository
|
|
73
|
+
*/
|
|
74
|
+
declare const isGitRepository: () => Promise<boolean>;
|
|
75
|
+
/**
|
|
76
|
+
* Get current branch name
|
|
77
|
+
*/
|
|
78
|
+
declare const getCurrentBranch: () => Promise<string>;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parsing utilities for AI responses
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Clean JSON from markdown code blocks
|
|
86
|
+
*/
|
|
87
|
+
declare const cleanJSON: (text: string) => string;
|
|
88
|
+
/**
|
|
89
|
+
* Parse AI response to AuditResult with error handling
|
|
90
|
+
*/
|
|
91
|
+
declare const parseAuditResponse: (responseText: string) => AuditResult;
|
|
92
|
+
/**
|
|
93
|
+
* Format file size for display
|
|
94
|
+
*/
|
|
95
|
+
declare const formatBytes: (bytes: number) => string;
|
|
96
|
+
/**
|
|
97
|
+
* Format duration for display
|
|
98
|
+
*/
|
|
99
|
+
declare const formatDuration$1: (ms: number) => string;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Console output utilities with colors
|
|
103
|
+
*/
|
|
104
|
+
declare const log: {
|
|
105
|
+
info: (msg: string) => void;
|
|
106
|
+
success: (msg: string) => void;
|
|
107
|
+
warning: (msg: string) => void;
|
|
108
|
+
error: (msg: string) => void;
|
|
109
|
+
critical: (msg: string) => void;
|
|
110
|
+
audit: (msg: string) => void;
|
|
111
|
+
skip: (msg: string) => void;
|
|
112
|
+
file: (msg: string) => void;
|
|
113
|
+
issue: (severity: string, line: number, message: string) => void;
|
|
114
|
+
progress: (current: number, total: number, label: string) => void;
|
|
115
|
+
progressEnd: () => void;
|
|
116
|
+
divider: () => void;
|
|
117
|
+
header: (title: string) => void;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Format duration for display
|
|
121
|
+
*/
|
|
122
|
+
declare const formatDuration: (ms: number) => string;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Core AI service with singleton pattern and request pooling
|
|
126
|
+
*/
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Audit commit message
|
|
130
|
+
*/
|
|
131
|
+
declare const auditCommit: (message: string, config: ProjectConfig) => Promise<AuditResult>;
|
|
132
|
+
/**
|
|
133
|
+
* Audit single file
|
|
134
|
+
*/
|
|
135
|
+
declare const auditFile: (filePath: string, content: string, systemPrompt: string) => Promise<AuditResult>;
|
|
136
|
+
/**
|
|
137
|
+
* Audit multiple files with concurrency control
|
|
138
|
+
*/
|
|
139
|
+
declare const auditFilesWithConcurrency: (files: Array<{
|
|
140
|
+
path: string;
|
|
141
|
+
content: string;
|
|
142
|
+
}>, config: ProjectConfig, maxConcurrency?: number) => Promise<FileAuditResult[]>;
|
|
143
|
+
/**
|
|
144
|
+
* Clear model cache (useful for testing)
|
|
145
|
+
*/
|
|
146
|
+
declare const clearModelCache: () => void;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* File service with streaming support and memory optimization
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
interface FileContent {
|
|
153
|
+
path: string;
|
|
154
|
+
content: string;
|
|
155
|
+
size: number;
|
|
156
|
+
}
|
|
157
|
+
interface FileReadResult {
|
|
158
|
+
success: FileContent[];
|
|
159
|
+
skipped: Array<{
|
|
160
|
+
path: string;
|
|
161
|
+
reason: string;
|
|
162
|
+
}>;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Read multiple files efficiently with size limits
|
|
166
|
+
*/
|
|
167
|
+
declare const readFilesForAudit: (filePaths: string[]) => Promise<FileReadResult>;
|
|
168
|
+
/**
|
|
169
|
+
* Get file extension without dot
|
|
170
|
+
*/
|
|
171
|
+
declare const getFileExtension: (filePath: string) => string;
|
|
172
|
+
/**
|
|
173
|
+
* Check if file is a code file
|
|
174
|
+
*/
|
|
175
|
+
declare const isCodeFile: (filePath: string) => boolean;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Prompt templates for AI-powered code auditing
|
|
179
|
+
*/
|
|
180
|
+
|
|
181
|
+
declare const DEFAULT_COMMIT_PROMPT = "\n### ROLE\nYou are a Strict Release Manager. You enforce \"Conventional Commits\" standards.\n### RULES\n1. Format MUST be: `<type>(<scope>): <subject>`\n - Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert.\n - Example: \"feat(auth): add google login support\"\n2. Subject must be lowercase and imperative (e.g., \"add\" not \"added\").\n3. Message must be descriptive enough to understand the change context.\n";
|
|
182
|
+
declare const BASE_AUDIT_PROMPT = "\n### ROLE & OBJECTIVE\nYou are an Elite Software Architect. Your goal is to enforce \"Clean Code\", \"SOLID Principles\", and \"Maintainability\".\n### GLOBAL STANDARDS\n1. **Clean Code:** Variable/Function names must be semantic. No magic numbers.\n2. **Split Code:** Suggest splitting complex functions/components.\n3. **Performance:** Identify obvious bottlenecks.\n4. **Error Handling:** Ensure proper boundary checks.\n";
|
|
183
|
+
declare const buildSystemPrompt: (config: ProjectConfig) => string;
|
|
184
|
+
declare const buildCommitPrompt: (customFormat?: string) => string;
|
|
185
|
+
|
|
186
|
+
export { type AuditIssue, type AuditResult, BASE_AUDIT_PROMPT, type CLIOptions, DEFAULT_COMMIT_PROMPT, DEFAULT_CONFIG, type FileAuditResult, type FileContent, type FileReadResult, type ProjectConfig, auditCommit, auditFile, auditFilesWithConcurrency, buildCommitPrompt, buildSystemPrompt, cleanJSON, clearConfigCache, clearModelCache, formatBytes, formatDuration, formatDuration$1 as formatDurationParser, getChangedFiles, getCurrentBranch, getFileExtension, getLastCommitMessage, isCodeFile, isGitRepository, loadProjectConfig, log, parseAuditResponse, readFilesForAudit, validateConfig };
|
package/dist/lib.js
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import { readFile, stat } from 'fs/promises';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import { exec } from 'child_process';
|
|
5
|
+
import { promisify } from 'util';
|
|
6
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
7
|
+
|
|
8
|
+
// Architect AI - CLI
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
// src/types/index.ts
|
|
12
|
+
var DEFAULT_CONFIG = {
|
|
13
|
+
techStack: "",
|
|
14
|
+
rules: [],
|
|
15
|
+
bypassKeyword: "skip:",
|
|
16
|
+
commitFormat: "",
|
|
17
|
+
maxConcurrency: 5,
|
|
18
|
+
cacheEnabled: true
|
|
19
|
+
};
|
|
20
|
+
var cachedConfig = null;
|
|
21
|
+
var loadProjectConfig = async (cwd = process.cwd()) => {
|
|
22
|
+
if (cachedConfig) {
|
|
23
|
+
return cachedConfig;
|
|
24
|
+
}
|
|
25
|
+
const configPath = resolve(cwd, ".architectrc.json");
|
|
26
|
+
if (!existsSync(configPath)) {
|
|
27
|
+
cachedConfig = { ...DEFAULT_CONFIG };
|
|
28
|
+
return cachedConfig;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const content = await readFile(configPath, "utf-8");
|
|
32
|
+
const userConfig = JSON.parse(content);
|
|
33
|
+
cachedConfig = {
|
|
34
|
+
...DEFAULT_CONFIG,
|
|
35
|
+
...userConfig
|
|
36
|
+
};
|
|
37
|
+
console.log("\u2699\uFE0F Loaded project-specific rules from .architectrc.json");
|
|
38
|
+
return cachedConfig;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.warn("\u26A0\uFE0F Found .architectrc.json but failed to parse it.");
|
|
41
|
+
cachedConfig = { ...DEFAULT_CONFIG };
|
|
42
|
+
return cachedConfig;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var clearConfigCache = () => {
|
|
46
|
+
cachedConfig = null;
|
|
47
|
+
};
|
|
48
|
+
var validateConfig = (config) => {
|
|
49
|
+
if (typeof config !== "object" || config === null) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const c = config;
|
|
53
|
+
if (c.techStack !== void 0 && typeof c.techStack !== "string") {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (c.rules !== void 0 && !Array.isArray(c.rules)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (c.bypassKeyword !== void 0 && typeof c.bypassKeyword !== "string") {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (c.commitFormat !== void 0 && typeof c.commitFormat !== "string") {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if (c.maxConcurrency !== void 0 && typeof c.maxConcurrency !== "number") {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
};
|
|
70
|
+
var execAsync = promisify(exec);
|
|
71
|
+
var SUPPORTED_EXTENSIONS = /\.(ts|tsx|js|jsx|mjs|cjs|py|cs|go|java|rs|kt|swift)$/;
|
|
72
|
+
var getLastCommitMessage = async () => {
|
|
73
|
+
try {
|
|
74
|
+
const { stdout } = await execAsync("git log -1 --pretty=%B");
|
|
75
|
+
return stdout.trim();
|
|
76
|
+
} catch {
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var getChangedFiles = async (options = {}) => {
|
|
81
|
+
const { targetBranch = "origin/main", extensions = SUPPORTED_EXTENSIONS } = options;
|
|
82
|
+
try {
|
|
83
|
+
const command = `git diff --name-only --diff-filter=ACMR ${targetBranch}...HEAD`;
|
|
84
|
+
const { stdout } = await execAsync(command);
|
|
85
|
+
return parseAndFilterFiles(stdout, extensions);
|
|
86
|
+
} catch {
|
|
87
|
+
try {
|
|
88
|
+
const command = `git diff --name-only --diff-filter=ACMR ${targetBranch}`;
|
|
89
|
+
const { stdout } = await execAsync(command);
|
|
90
|
+
return parseAndFilterFiles(stdout, extensions);
|
|
91
|
+
} catch {
|
|
92
|
+
console.warn("\u26A0\uFE0F Git diff failed. No files to audit.");
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var isGitRepository = async () => {
|
|
98
|
+
try {
|
|
99
|
+
await execAsync("git rev-parse --is-inside-work-tree");
|
|
100
|
+
return true;
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
var getCurrentBranch = async () => {
|
|
106
|
+
try {
|
|
107
|
+
const { stdout } = await execAsync("git branch --show-current");
|
|
108
|
+
return stdout.trim();
|
|
109
|
+
} catch {
|
|
110
|
+
return "unknown";
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var parseAndFilterFiles = (output, extensions) => {
|
|
114
|
+
return output.split("\n").map((file) => file.trim()).filter((file) => file.length > 0).filter((file) => extensions.test(file));
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// src/utils/parser.ts
|
|
118
|
+
var cleanJSON = (text) => {
|
|
119
|
+
return text.replace(/```json\s*/gi, "").replace(/```\s*/g, "").trim();
|
|
120
|
+
};
|
|
121
|
+
var parseAuditResponse = (responseText) => {
|
|
122
|
+
const cleaned = cleanJSON(responseText);
|
|
123
|
+
try {
|
|
124
|
+
const parsed = JSON.parse(cleaned);
|
|
125
|
+
if (!parsed.status || !["PASS", "FAIL"].includes(parsed.status)) {
|
|
126
|
+
return {
|
|
127
|
+
status: "FAIL",
|
|
128
|
+
message: "Invalid AI response format",
|
|
129
|
+
issues: []
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return parsed;
|
|
133
|
+
} catch {
|
|
134
|
+
const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
|
|
135
|
+
if (jsonMatch) {
|
|
136
|
+
try {
|
|
137
|
+
return JSON.parse(jsonMatch[0]);
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
status: "FAIL",
|
|
143
|
+
message: "Failed to parse AI response",
|
|
144
|
+
issues: []
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
var formatBytes = (bytes) => {
|
|
149
|
+
if (bytes === 0) return "0 B";
|
|
150
|
+
const k = 1024;
|
|
151
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
152
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
153
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
154
|
+
};
|
|
155
|
+
var formatDuration = (ms) => {
|
|
156
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
157
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
158
|
+
return `${(ms / 6e4).toFixed(1)}m`;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// src/utils/logger.ts
|
|
162
|
+
var colors = {
|
|
163
|
+
reset: "\x1B[0m",
|
|
164
|
+
bold: "\x1B[1m",
|
|
165
|
+
dim: "\x1B[2m",
|
|
166
|
+
red: "\x1B[31m",
|
|
167
|
+
green: "\x1B[32m",
|
|
168
|
+
yellow: "\x1B[33m",
|
|
169
|
+
blue: "\x1B[34m",
|
|
170
|
+
magenta: "\x1B[35m",
|
|
171
|
+
cyan: "\x1B[36m"};
|
|
172
|
+
var log = {
|
|
173
|
+
info: (msg) => console.log(`${colors.blue}\u2139${colors.reset} ${msg}`),
|
|
174
|
+
success: (msg) => console.log(`${colors.green}\u2705${colors.reset} ${msg}`),
|
|
175
|
+
warning: (msg) => console.warn(`${colors.yellow}\u26A0\uFE0F${colors.reset} ${msg}`),
|
|
176
|
+
error: (msg) => console.error(`${colors.red}\u274C${colors.reset} ${msg}`),
|
|
177
|
+
critical: (msg) => console.error(`${colors.red}${colors.bold}\u{1F6A8}${colors.reset} ${msg}`),
|
|
178
|
+
audit: (msg) => console.log(`${colors.cyan}\u{1F50D}${colors.reset} ${msg}`),
|
|
179
|
+
skip: (msg) => console.log(`${colors.magenta}\u23E9${colors.reset} ${msg}`),
|
|
180
|
+
file: (msg) => console.log(`${colors.dim} ${msg}${colors.reset}`),
|
|
181
|
+
// Issue formatting
|
|
182
|
+
issue: (severity, line, message) => {
|
|
183
|
+
const color = severity === "CRITICAL" ? colors.red : severity === "WARNING" ? colors.yellow : colors.blue;
|
|
184
|
+
console.log(` ${color}[${severity}] Line ${line}: ${message}${colors.reset}`);
|
|
185
|
+
},
|
|
186
|
+
// Progress bar
|
|
187
|
+
progress: (current, total, label) => {
|
|
188
|
+
const percent = Math.round(current / total * 100);
|
|
189
|
+
const barLength = 20;
|
|
190
|
+
const filled = Math.round(current / total * barLength);
|
|
191
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(barLength - filled);
|
|
192
|
+
process.stdout.write(`\r${colors.cyan}${bar}${colors.reset} ${percent}% | ${label}`);
|
|
193
|
+
},
|
|
194
|
+
progressEnd: () => {
|
|
195
|
+
console.log();
|
|
196
|
+
},
|
|
197
|
+
// Divider
|
|
198
|
+
divider: () => console.log(`${colors.dim}${"\u2500".repeat(50)}${colors.reset}`),
|
|
199
|
+
// Header
|
|
200
|
+
header: (title) => {
|
|
201
|
+
console.log();
|
|
202
|
+
console.log(`${colors.bold}${colors.cyan}\u{1F3D7}\uFE0F ${title}${colors.reset}`);
|
|
203
|
+
console.log(`${colors.dim}${"\u2500".repeat(50)}${colors.reset}`);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
var formatDuration2 = (ms) => {
|
|
207
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
208
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
209
|
+
return `${(ms / 6e4).toFixed(1)}m`;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// src/config/prompts.ts
|
|
213
|
+
var DEFAULT_COMMIT_PROMPT = `
|
|
214
|
+
### ROLE
|
|
215
|
+
You are a Strict Release Manager. You enforce "Conventional Commits" standards.
|
|
216
|
+
### RULES
|
|
217
|
+
1. Format MUST be: \`<type>(<scope>): <subject>\`
|
|
218
|
+
- Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert.
|
|
219
|
+
- Example: "feat(auth): add google login support"
|
|
220
|
+
2. Subject must be lowercase and imperative (e.g., "add" not "added").
|
|
221
|
+
3. Message must be descriptive enough to understand the change context.
|
|
222
|
+
`;
|
|
223
|
+
var BASE_AUDIT_PROMPT = `
|
|
224
|
+
### ROLE & OBJECTIVE
|
|
225
|
+
You are an Elite Software Architect. Your goal is to enforce "Clean Code", "SOLID Principles", and "Maintainability".
|
|
226
|
+
### GLOBAL STANDARDS
|
|
227
|
+
1. **Clean Code:** Variable/Function names must be semantic. No magic numbers.
|
|
228
|
+
2. **Split Code:** Suggest splitting complex functions/components.
|
|
229
|
+
3. **Performance:** Identify obvious bottlenecks.
|
|
230
|
+
4. **Error Handling:** Ensure proper boundary checks.
|
|
231
|
+
`;
|
|
232
|
+
var buildSystemPrompt = (config) => {
|
|
233
|
+
const parts = [BASE_AUDIT_PROMPT];
|
|
234
|
+
if (config.techStack) {
|
|
235
|
+
parts.push(`
|
|
236
|
+
### TECH STACK CONTEXT
|
|
237
|
+
The code is written in: ${config.techStack}
|
|
238
|
+
`);
|
|
239
|
+
}
|
|
240
|
+
if (config.rules && config.rules.length > 0) {
|
|
241
|
+
parts.push(`
|
|
242
|
+
### PROJECT SPECIFIC RULES (HIGHEST PRIORITY)
|
|
243
|
+
`);
|
|
244
|
+
config.rules.forEach((rule, index) => {
|
|
245
|
+
parts.push(`${index + 1}. ${rule}
|
|
246
|
+
`);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
parts.push(`
|
|
250
|
+
### OUTPUT FORMAT (JSON ONLY)
|
|
251
|
+
{ "status": "PASS" | "FAIL", "issues": [{ "line": number, "severity": "CRITICAL" | "WARNING", "message": "string", "suggestion": "string" }] }`);
|
|
252
|
+
return parts.join("");
|
|
253
|
+
};
|
|
254
|
+
var buildCommitPrompt = (customFormat) => {
|
|
255
|
+
const parts = [];
|
|
256
|
+
if (customFormat) {
|
|
257
|
+
parts.push(`
|
|
258
|
+
### ROLE
|
|
259
|
+
You are a Strict Release Manager.
|
|
260
|
+
### RULES (CUSTOM COMPANY POLICY)
|
|
261
|
+
You must enforce the following strict commit message format:
|
|
262
|
+
"${customFormat}"
|
|
263
|
+
|
|
264
|
+
Any commit message NOT following this pattern must be REJECTED.
|
|
265
|
+
`);
|
|
266
|
+
} else {
|
|
267
|
+
parts.push(DEFAULT_COMMIT_PROMPT);
|
|
268
|
+
}
|
|
269
|
+
parts.push(`
|
|
270
|
+
### OUTPUT (JSON ONLY)
|
|
271
|
+
{ "status": "PASS" | "FAIL", "message": "Reason for failure", "suggestion": "Corrected example" }`);
|
|
272
|
+
return parts.join("");
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// src/services/ai.ts
|
|
276
|
+
var modelInstance = null;
|
|
277
|
+
var getModel = () => {
|
|
278
|
+
if (modelInstance) {
|
|
279
|
+
return modelInstance;
|
|
280
|
+
}
|
|
281
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
282
|
+
if (!apiKey) {
|
|
283
|
+
throw new Error("GEMINI_API_KEY environment variable is required");
|
|
284
|
+
}
|
|
285
|
+
const genAI = new GoogleGenerativeAI(apiKey);
|
|
286
|
+
modelInstance = genAI.getGenerativeModel({
|
|
287
|
+
model: "gemini-1.5-pro",
|
|
288
|
+
generationConfig: {
|
|
289
|
+
temperature: 0.2,
|
|
290
|
+
maxOutputTokens: 8192
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
return modelInstance;
|
|
294
|
+
};
|
|
295
|
+
var auditCommit = async (message, config) => {
|
|
296
|
+
log.audit(`Auditing Commit Message: "${message}"...`);
|
|
297
|
+
const systemPrompt = buildCommitPrompt(config.commitFormat);
|
|
298
|
+
const model = getModel();
|
|
299
|
+
try {
|
|
300
|
+
const result = await model.generateContent([
|
|
301
|
+
systemPrompt,
|
|
302
|
+
`Commit Message: "${message}"`
|
|
303
|
+
]);
|
|
304
|
+
return parseAuditResponse(result.response.text());
|
|
305
|
+
} catch (error) {
|
|
306
|
+
log.warning("AI check failed. Skipping commit check.");
|
|
307
|
+
return { status: "PASS", message: "AI unavailable - skipped" };
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
var auditFile = async (filePath, content, systemPrompt) => {
|
|
311
|
+
const model = getModel();
|
|
312
|
+
try {
|
|
313
|
+
const result = await model.generateContent([
|
|
314
|
+
systemPrompt,
|
|
315
|
+
`Code to review:
|
|
316
|
+
${content}`
|
|
317
|
+
]);
|
|
318
|
+
return parseAuditResponse(result.response.text());
|
|
319
|
+
} catch (error) {
|
|
320
|
+
return {
|
|
321
|
+
status: "FAIL",
|
|
322
|
+
message: `Error auditing file: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
323
|
+
issues: []
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
var auditFilesWithConcurrency = async (files, config, maxConcurrency = 5) => {
|
|
328
|
+
const systemPrompt = buildSystemPrompt(config);
|
|
329
|
+
const results = [];
|
|
330
|
+
for (let i = 0; i < files.length; i += maxConcurrency) {
|
|
331
|
+
const batch = files.slice(i, i + maxConcurrency);
|
|
332
|
+
const batchPromises = batch.map(async (file) => {
|
|
333
|
+
const startTime = performance.now();
|
|
334
|
+
log.audit(`Auditing: ${file.path}`);
|
|
335
|
+
const result = await auditFile(file.path, file.content, systemPrompt);
|
|
336
|
+
const duration = performance.now() - startTime;
|
|
337
|
+
return {
|
|
338
|
+
filePath: file.path,
|
|
339
|
+
result,
|
|
340
|
+
duration
|
|
341
|
+
};
|
|
342
|
+
});
|
|
343
|
+
const batchResults = await Promise.all(batchPromises);
|
|
344
|
+
results.push(...batchResults);
|
|
345
|
+
log.progress(
|
|
346
|
+
Math.min(i + maxConcurrency, files.length),
|
|
347
|
+
files.length,
|
|
348
|
+
`${results.length}/${files.length} files audited`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
log.progressEnd();
|
|
352
|
+
return results;
|
|
353
|
+
};
|
|
354
|
+
var clearModelCache = () => {
|
|
355
|
+
modelInstance = null;
|
|
356
|
+
};
|
|
357
|
+
var MAX_FILE_SIZE = 500 * 1024;
|
|
358
|
+
var readFilesForAudit = async (filePaths) => {
|
|
359
|
+
const result = {
|
|
360
|
+
success: [],
|
|
361
|
+
skipped: []
|
|
362
|
+
};
|
|
363
|
+
const readPromises = filePaths.map(async (filePath) => {
|
|
364
|
+
const absolutePath = resolve(filePath);
|
|
365
|
+
if (!existsSync(absolutePath)) {
|
|
366
|
+
return { path: filePath, skipped: true, reason: "File not found" };
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
const stats = await stat(absolutePath);
|
|
370
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
371
|
+
return {
|
|
372
|
+
path: filePath,
|
|
373
|
+
skipped: true,
|
|
374
|
+
reason: `File too large (${formatBytes(stats.size)})`
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
const content = await readFile(absolutePath, "utf-8");
|
|
378
|
+
return {
|
|
379
|
+
path: filePath,
|
|
380
|
+
content,
|
|
381
|
+
size: stats.size,
|
|
382
|
+
skipped: false
|
|
383
|
+
};
|
|
384
|
+
} catch (error) {
|
|
385
|
+
return {
|
|
386
|
+
path: filePath,
|
|
387
|
+
skipped: true,
|
|
388
|
+
reason: error instanceof Error ? error.message : "Read error"
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
const results = await Promise.all(readPromises);
|
|
393
|
+
for (const item of results) {
|
|
394
|
+
if (item.skipped) {
|
|
395
|
+
result.skipped.push({ path: item.path, reason: item.reason });
|
|
396
|
+
} else {
|
|
397
|
+
result.success.push({
|
|
398
|
+
path: item.path,
|
|
399
|
+
content: item.content,
|
|
400
|
+
size: item.size
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (result.skipped.length > 0) {
|
|
405
|
+
log.warning(`Skipped ${result.skipped.length} file(s):`);
|
|
406
|
+
for (const { path, reason } of result.skipped) {
|
|
407
|
+
log.file(`${path}: ${reason}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return result;
|
|
411
|
+
};
|
|
412
|
+
var getFileExtension = (filePath) => {
|
|
413
|
+
const match = filePath.match(/\.([^.]+)$/);
|
|
414
|
+
return match?.[1] ?? "";
|
|
415
|
+
};
|
|
416
|
+
var isCodeFile = (filePath) => {
|
|
417
|
+
const codeExtensions = /* @__PURE__ */ new Set([
|
|
418
|
+
"ts",
|
|
419
|
+
"tsx",
|
|
420
|
+
"js",
|
|
421
|
+
"jsx",
|
|
422
|
+
"mjs",
|
|
423
|
+
"cjs",
|
|
424
|
+
"py",
|
|
425
|
+
"cs",
|
|
426
|
+
"go",
|
|
427
|
+
"java",
|
|
428
|
+
"rs",
|
|
429
|
+
"kt",
|
|
430
|
+
"swift",
|
|
431
|
+
"cpp",
|
|
432
|
+
"c",
|
|
433
|
+
"h",
|
|
434
|
+
"hpp",
|
|
435
|
+
"rb",
|
|
436
|
+
"php"
|
|
437
|
+
]);
|
|
438
|
+
return codeExtensions.has(getFileExtension(filePath));
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
export { BASE_AUDIT_PROMPT, DEFAULT_COMMIT_PROMPT, DEFAULT_CONFIG, auditCommit, auditFile, auditFilesWithConcurrency, buildCommitPrompt, buildSystemPrompt, cleanJSON, clearConfigCache, clearModelCache, formatBytes, formatDuration2 as formatDuration, formatDuration as formatDurationParser, getChangedFiles, getCurrentBranch, getFileExtension, getLastCommitMessage, isCodeFile, isGitRepository, loadProjectConfig, log, parseAuditResponse, readFilesForAudit, validateConfig };
|
|
442
|
+
//# sourceMappingURL=lib.js.map
|
|
443
|
+
//# sourceMappingURL=lib.js.map
|