activo 0.4.4 → 0.5.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 +203 -1
- package/data/2026-03-04_20-54.json +181 -0
- package/data/2026-03-04_20-56.json +181 -0
- package/data/apex-rulesets/egov.yaml +469 -0
- package/data/apex-rulesets/modernize.yaml +687 -0
- package/data/apex-rulesets/quality.yaml +1677 -0
- package/data/apex-rulesets/rule-schema.yaml +587 -0
- package/data/apex-rulesets/secure.yaml +1688 -0
- package/data/apex-rulesets/spring.yaml +455 -0
- package/data/apex-rulesets/sql-format.yaml +99 -0
- package/data/apex-rulesets/sql-oracle.yaml +281 -0
- package/data/apex-rulesets/sql.yaml +1660 -0
- package/dist/cli/headless.d.ts.map +1 -1
- package/dist/cli/headless.js +32 -10
- package/dist/cli/headless.js.map +1 -1
- package/dist/cli/index.js +31 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/core/agent.d.ts +3 -3
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +203 -384
- package/dist/core/agent.js.map +1 -1
- package/dist/core/commands.d.ts +2 -1
- package/dist/core/commands.d.ts.map +1 -1
- package/dist/core/commands.js +61 -9
- package/dist/core/commands.js.map +1 -1
- package/dist/core/config.d.ts +14 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +41 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/conversation.d.ts +2 -2
- package/dist/core/conversation.d.ts.map +1 -1
- package/dist/core/conversation.js.map +1 -1
- package/dist/core/intentRouter.d.ts +43 -0
- package/dist/core/intentRouter.d.ts.map +1 -0
- package/dist/core/intentRouter.js +804 -0
- package/dist/core/intentRouter.js.map +1 -0
- package/dist/core/llm/anthropic.d.ts +24 -0
- package/dist/core/llm/anthropic.d.ts.map +1 -0
- package/dist/core/llm/anthropic.js +226 -0
- package/dist/core/llm/anthropic.js.map +1 -0
- package/dist/core/llm/ollama.d.ts +5 -14
- package/dist/core/llm/ollama.d.ts.map +1 -1
- package/dist/core/llm/ollama.js +3 -0
- package/dist/core/llm/ollama.js.map +1 -1
- package/dist/core/llm/types.d.ts +22 -0
- package/dist/core/llm/types.d.ts.map +1 -0
- package/dist/core/llm/types.js +2 -0
- package/dist/core/llm/types.js.map +1 -0
- package/dist/core/mcp/client.d.ts +6 -0
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +16 -0
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/init.d.ts +12 -0
- package/dist/core/mcp/init.d.ts.map +1 -0
- package/dist/core/mcp/init.js +55 -0
- package/dist/core/mcp/init.js.map +1 -0
- package/dist/core/mcp/logger.d.ts +14 -0
- package/dist/core/mcp/logger.d.ts.map +1 -0
- package/dist/core/mcp/logger.js +50 -0
- package/dist/core/mcp/logger.js.map +1 -0
- package/dist/core/tools/analyzePatterns.d.ts +3 -0
- package/dist/core/tools/analyzePatterns.d.ts.map +1 -0
- package/dist/core/tools/analyzePatterns.js +293 -0
- package/dist/core/tools/analyzePatterns.js.map +1 -0
- package/dist/core/tools/apexPaths.d.ts +14 -0
- package/dist/core/tools/apexPaths.d.ts.map +1 -0
- package/dist/core/tools/apexPaths.js +54 -0
- package/dist/core/tools/apexPaths.js.map +1 -0
- package/dist/core/tools/apexUtils.d.ts +36 -0
- package/dist/core/tools/apexUtils.d.ts.map +1 -0
- package/dist/core/tools/apexUtils.js +83 -0
- package/dist/core/tools/apexUtils.js.map +1 -0
- package/dist/core/tools/explainIssue.d.ts +3 -0
- package/dist/core/tools/explainIssue.d.ts.map +1 -0
- package/dist/core/tools/explainIssue.js +181 -0
- package/dist/core/tools/explainIssue.js.map +1 -0
- package/dist/core/tools/fixGen.d.ts +3 -0
- package/dist/core/tools/fixGen.d.ts.map +1 -0
- package/dist/core/tools/fixGen.js +338 -0
- package/dist/core/tools/fixGen.js.map +1 -0
- package/dist/core/tools/generateImprovements.d.ts +21 -0
- package/dist/core/tools/generateImprovements.d.ts.map +1 -0
- package/dist/core/tools/generateImprovements.js +602 -0
- package/dist/core/tools/generateImprovements.js.map +1 -0
- package/dist/core/tools/generateReport.d.ts +3 -0
- package/dist/core/tools/generateReport.d.ts.map +1 -0
- package/dist/core/tools/generateReport.js +315 -0
- package/dist/core/tools/generateReport.js.map +1 -0
- package/dist/core/tools/index.d.ts +7 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +62 -23
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/recommendProfile.d.ts +3 -0
- package/dist/core/tools/recommendProfile.d.ts.map +1 -0
- package/dist/core/tools/recommendProfile.js +334 -0
- package/dist/core/tools/recommendProfile.js.map +1 -0
- package/dist/core/tools/ruleGen.d.ts +3 -0
- package/dist/core/tools/ruleGen.d.ts.map +1 -0
- package/dist/core/tools/ruleGen.js +1103 -0
- package/dist/core/tools/ruleGen.js.map +1 -0
- package/dist/core/tools/standards.d.ts.map +1 -1
- package/dist/core/tools/standards.js +7 -3
- package/dist/core/tools/standards.js.map +1 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +86 -35
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/InputBox.d.ts +1 -3
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +146 -5
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/MessageList.d.ts +3 -1
- package/dist/ui/components/MessageList.d.ts.map +1 -1
- package/dist/ui/components/MessageList.js +13 -7
- package/dist/ui/components/MessageList.js.map +1 -1
- package/dist/ui/components/StatusBar.d.ts +1 -1
- package/dist/ui/components/StatusBar.d.ts.map +1 -1
- package/dist/ui/components/StatusBar.js +3 -2
- package/dist/ui/components/StatusBar.js.map +1 -1
- package/dist/ui/components/ToolStatus.d.ts +3 -1
- package/dist/ui/components/ToolStatus.d.ts.map +1 -1
- package/dist/ui/components/ToolStatus.js +19 -4
- package/dist/ui/components/ToolStatus.js.map +1 -1
- package/package.json +7 -1
- package/demo.gif +0 -0
- package/demo.tape +0 -53
- package/screenshot.png +0 -0
- package/src/cli/banner.ts +0 -38
- package/src/cli/headless.ts +0 -63
- package/src/cli/index.ts +0 -57
- package/src/core/agent.ts +0 -711
- package/src/core/commands.ts +0 -118
- package/src/core/config.ts +0 -98
- package/src/core/conversation.ts +0 -235
- package/src/core/llm/ollama.ts +0 -351
- package/src/core/mcp/client.ts +0 -143
- package/src/core/tools/analyzeAll.ts +0 -482
- package/src/core/tools/ast.ts +0 -826
- package/src/core/tools/builtIn.ts +0 -221
- package/src/core/tools/cache.ts +0 -570
- package/src/core/tools/cssAnalysis.ts +0 -324
- package/src/core/tools/dependencyAnalysis.ts +0 -363
- package/src/core/tools/embeddings.ts +0 -746
- package/src/core/tools/frontendAst.ts +0 -802
- package/src/core/tools/htmlAnalysis.ts +0 -466
- package/src/core/tools/index.ts +0 -160
- package/src/core/tools/javaAst.ts +0 -1030
- package/src/core/tools/javaQuality.integration.test.ts +0 -537
- package/src/core/tools/memory.ts +0 -655
- package/src/core/tools/mybatisAnalysis.ts +0 -322
- package/src/core/tools/openapiAnalysis.ts +0 -431
- package/src/core/tools/pythonAnalysis.ts +0 -477
- package/src/core/tools/sqlAnalysis.ts +0 -298
- package/src/core/tools/standards.test.ts +0 -186
- package/src/core/tools/standards.ts +0 -889
- package/src/core/tools/types.ts +0 -38
- package/src/ui/App.tsx +0 -334
- package/src/ui/components/InputBox.tsx +0 -37
- package/src/ui/components/MessageList.tsx +0 -80
- package/src/ui/components/StatusBar.tsx +0 -36
- package/src/ui/components/ToolStatus.tsx +0 -38
- package/tsconfig.json +0 -21
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
import { Tool, ToolResult } from "./types.js";
|
|
2
|
-
import * as fs from "fs";
|
|
3
|
-
import * as path from "path";
|
|
4
|
-
|
|
5
|
-
interface SqlQuery {
|
|
6
|
-
type: "jpa" | "jdbc" | "native" | "jpql";
|
|
7
|
-
query: string;
|
|
8
|
-
location: string;
|
|
9
|
-
line: number;
|
|
10
|
-
issues: string[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface SqlAnalysisResult {
|
|
14
|
-
file: string;
|
|
15
|
-
queries: SqlQuery[];
|
|
16
|
-
summary: {
|
|
17
|
-
total: number;
|
|
18
|
-
withIssues: number;
|
|
19
|
-
issueTypes: Record<string, number>;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// SQL 쿼리 추출 및 분석
|
|
24
|
-
function analyzeJavaForSql(filePath: string): SqlAnalysisResult {
|
|
25
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
26
|
-
const lines = content.split("\n");
|
|
27
|
-
const queries: SqlQuery[] = [];
|
|
28
|
-
|
|
29
|
-
// @Query 어노테이션 찾기
|
|
30
|
-
const queryAnnotationRegex = /@Query\s*\(\s*(?:value\s*=\s*)?["'`]([^"'`]+)["'`]/g;
|
|
31
|
-
const nativeQueryRegex = /@Query\s*\([^)]*nativeQuery\s*=\s*true[^)]*value\s*=\s*["'`]([^"'`]+)["'`]/g;
|
|
32
|
-
const namedQueryRegex = /@NamedQuery\s*\([^)]*query\s*=\s*["'`]([^"'`]+)["'`]/g;
|
|
33
|
-
|
|
34
|
-
// JDBC/JPA 문자열 쿼리 찾기
|
|
35
|
-
const createQueryRegex = /(?:createQuery|createNativeQuery|prepareStatement)\s*\(\s*["'`]([^"'`]+)["'`]/g;
|
|
36
|
-
const jdbcExecuteRegex = /(?:executeQuery|executeUpdate|execute)\s*\(\s*["'`]([^"'`]+)["'`]/g;
|
|
37
|
-
|
|
38
|
-
// 문자열 변수에 할당된 SQL 찾기
|
|
39
|
-
const sqlStringRegex = /(?:String\s+)?(?:sql|query|hql|jpql)\s*=\s*["'`]([^"'`]*(?:SELECT|INSERT|UPDATE|DELETE|FROM|WHERE)[^"'`]*)["'`]/gi;
|
|
40
|
-
|
|
41
|
-
// 멀티라인 SQL 문자열 찾기
|
|
42
|
-
const multiLineSqlRegex = /["'`]\s*(SELECT|INSERT|UPDATE|DELETE)\s+[\s\S]*?["'`]\s*(?:\+\s*["'`][\s\S]*?["'`]\s*)*/gi;
|
|
43
|
-
|
|
44
|
-
const findLineNumber = (match: RegExpExecArray): number => {
|
|
45
|
-
const index = match.index;
|
|
46
|
-
let lineNum = 1;
|
|
47
|
-
for (let i = 0; i < index && i < content.length; i++) {
|
|
48
|
-
if (content[i] === "\n") lineNum++;
|
|
49
|
-
}
|
|
50
|
-
return lineNum;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const analyzeQuery = (sql: string): string[] => {
|
|
54
|
-
const issues: string[] = [];
|
|
55
|
-
const upperSql = sql.toUpperCase().trim();
|
|
56
|
-
|
|
57
|
-
// SELECT * 검사
|
|
58
|
-
if (/SELECT\s+\*\s+FROM/i.test(sql)) {
|
|
59
|
-
issues.push("SELECT * 사용 - 필요한 컬럼만 명시 권장");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// WHERE 절 없는 UPDATE/DELETE
|
|
63
|
-
if (/^(UPDATE|DELETE)\s+/i.test(upperSql) && !/WHERE/i.test(sql)) {
|
|
64
|
-
issues.push("WHERE 절 없음 - 전체 테이블 영향 위험");
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// 서브쿼리 검사
|
|
68
|
-
const subqueryCount = (sql.match(/\(\s*SELECT/gi) || []).length;
|
|
69
|
-
if (subqueryCount >= 2) {
|
|
70
|
-
issues.push(`중첩 서브쿼리 ${subqueryCount}개 - 성능 저하 가능`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// JOIN 개수 검사
|
|
74
|
-
const joinCount = (sql.match(/\bJOIN\b/gi) || []).length;
|
|
75
|
-
if (joinCount >= 4) {
|
|
76
|
-
issues.push(`JOIN ${joinCount}개 - 쿼리 복잡도 높음`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// LIKE '%...' 패턴 (인덱스 미사용)
|
|
80
|
-
if (/LIKE\s+['"]%/i.test(sql)) {
|
|
81
|
-
issues.push("LIKE '%...' 패턴 - 인덱스 사용 불가");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// OR 조건 과다
|
|
85
|
-
const orCount = (sql.match(/\bOR\b/gi) || []).length;
|
|
86
|
-
if (orCount >= 3) {
|
|
87
|
-
issues.push(`OR 조건 ${orCount}개 - IN 절로 변환 검토`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ORDER BY RAND() 검사
|
|
91
|
-
if (/ORDER\s+BY\s+RAND\s*\(\)/i.test(sql)) {
|
|
92
|
-
issues.push("ORDER BY RAND() - 대용량 테이블에서 성능 저하");
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// N+1 가능성 힌트 (단순 ID 조회)
|
|
96
|
-
if (/WHERE\s+\w+\.?id\s*=\s*[?:]/i.test(sql) && /SELECT/i.test(sql)) {
|
|
97
|
-
issues.push("단일 ID 조회 - 반복 호출 시 N+1 문제 가능");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 문자열 연결 (SQL Injection 위험)
|
|
101
|
-
if (/\+\s*["']|["']\s*\+/.test(sql)) {
|
|
102
|
-
issues.push("문자열 연결 감지 - SQL Injection 위험");
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return issues;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
// @Query 어노테이션 처리
|
|
109
|
-
let match;
|
|
110
|
-
while ((match = queryAnnotationRegex.exec(content)) !== null) {
|
|
111
|
-
const query = match[1].replace(/\s+/g, " ").trim();
|
|
112
|
-
queries.push({
|
|
113
|
-
type: "jpql",
|
|
114
|
-
query,
|
|
115
|
-
location: "@Query",
|
|
116
|
-
line: findLineNumber(match),
|
|
117
|
-
issues: analyzeQuery(query),
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Native Query 처리
|
|
122
|
-
while ((match = nativeQueryRegex.exec(content)) !== null) {
|
|
123
|
-
const query = match[1].replace(/\s+/g, " ").trim();
|
|
124
|
-
queries.push({
|
|
125
|
-
type: "native",
|
|
126
|
-
query,
|
|
127
|
-
location: "@Query(nativeQuery)",
|
|
128
|
-
line: findLineNumber(match),
|
|
129
|
-
issues: analyzeQuery(query),
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// @NamedQuery 처리
|
|
134
|
-
while ((match = namedQueryRegex.exec(content)) !== null) {
|
|
135
|
-
const query = match[1].replace(/\s+/g, " ").trim();
|
|
136
|
-
queries.push({
|
|
137
|
-
type: "jpa",
|
|
138
|
-
query,
|
|
139
|
-
location: "@NamedQuery",
|
|
140
|
-
line: findLineNumber(match),
|
|
141
|
-
issues: analyzeQuery(query),
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// createQuery/prepareStatement 처리
|
|
146
|
-
while ((match = createQueryRegex.exec(content)) !== null) {
|
|
147
|
-
const query = match[1].replace(/\s+/g, " ").trim();
|
|
148
|
-
if (/SELECT|INSERT|UPDATE|DELETE/i.test(query)) {
|
|
149
|
-
queries.push({
|
|
150
|
-
type: "jdbc",
|
|
151
|
-
query,
|
|
152
|
-
location: "createQuery/prepareStatement",
|
|
153
|
-
line: findLineNumber(match),
|
|
154
|
-
issues: analyzeQuery(query),
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// executeQuery 처리
|
|
160
|
-
while ((match = jdbcExecuteRegex.exec(content)) !== null) {
|
|
161
|
-
const query = match[1].replace(/\s+/g, " ").trim();
|
|
162
|
-
if (/SELECT|INSERT|UPDATE|DELETE/i.test(query)) {
|
|
163
|
-
queries.push({
|
|
164
|
-
type: "jdbc",
|
|
165
|
-
query,
|
|
166
|
-
location: "execute*",
|
|
167
|
-
line: findLineNumber(match),
|
|
168
|
-
issues: analyzeQuery(query),
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// SQL 문자열 변수 처리
|
|
174
|
-
while ((match = sqlStringRegex.exec(content)) !== null) {
|
|
175
|
-
const query = match[1].replace(/\s+/g, " ").trim();
|
|
176
|
-
queries.push({
|
|
177
|
-
type: "jdbc",
|
|
178
|
-
query,
|
|
179
|
-
location: "String 변수",
|
|
180
|
-
line: findLineNumber(match),
|
|
181
|
-
issues: analyzeQuery(query),
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// 이슈 통계
|
|
186
|
-
const issueTypes: Record<string, number> = {};
|
|
187
|
-
queries.forEach((q) => {
|
|
188
|
-
q.issues.forEach((issue) => {
|
|
189
|
-
const key = issue.split(" - ")[0];
|
|
190
|
-
issueTypes[key] = (issueTypes[key] || 0) + 1;
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
file: filePath,
|
|
196
|
-
queries,
|
|
197
|
-
summary: {
|
|
198
|
-
total: queries.length,
|
|
199
|
-
withIssues: queries.filter((q) => q.issues.length > 0).length,
|
|
200
|
-
issueTypes,
|
|
201
|
-
},
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// 도구 정의
|
|
206
|
-
export const sqlTools: Tool[] = [
|
|
207
|
-
{
|
|
208
|
-
name: "sql_check",
|
|
209
|
-
description:
|
|
210
|
-
"Java 파일에서 SQL 쿼리를 추출하고 품질 검사합니다. @Query, JDBC, JPA 쿼리를 분석하여 SELECT *, 인덱스 미사용 패턴, N+1 가능성 등을 검출합니다.",
|
|
211
|
-
parameters: {
|
|
212
|
-
type: "object",
|
|
213
|
-
properties: {
|
|
214
|
-
path: {
|
|
215
|
-
type: "string",
|
|
216
|
-
description: "분석할 Java 파일 또는 디렉토리 경로",
|
|
217
|
-
},
|
|
218
|
-
recursive: {
|
|
219
|
-
type: "boolean",
|
|
220
|
-
description: "디렉토리인 경우 하위 폴더 포함 여부 (기본: true)",
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
required: ["path"],
|
|
224
|
-
},
|
|
225
|
-
handler: async (args: Record<string, unknown>): Promise<ToolResult> => {
|
|
226
|
-
const targetPath = args.path as string;
|
|
227
|
-
const recursive = args.recursive !== false;
|
|
228
|
-
|
|
229
|
-
if (!fs.existsSync(targetPath)) {
|
|
230
|
-
return {
|
|
231
|
-
success: false,
|
|
232
|
-
content: "",
|
|
233
|
-
error: `경로를 찾을 수 없습니다: ${targetPath}`,
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const results: SqlAnalysisResult[] = [];
|
|
238
|
-
const stats = fs.statSync(targetPath);
|
|
239
|
-
|
|
240
|
-
if (stats.isFile()) {
|
|
241
|
-
if (targetPath.endsWith(".java")) {
|
|
242
|
-
results.push(analyzeJavaForSql(targetPath));
|
|
243
|
-
}
|
|
244
|
-
} else if (stats.isDirectory()) {
|
|
245
|
-
const walkDir = (dir: string) => {
|
|
246
|
-
const files = fs.readdirSync(dir);
|
|
247
|
-
for (const file of files) {
|
|
248
|
-
const filePath = path.join(dir, file);
|
|
249
|
-
const fileStat = fs.statSync(filePath);
|
|
250
|
-
if (fileStat.isDirectory() && recursive) {
|
|
251
|
-
if (!file.startsWith(".") && file !== "node_modules" && file !== "target" && file !== "build") {
|
|
252
|
-
walkDir(filePath);
|
|
253
|
-
}
|
|
254
|
-
} else if (file.endsWith(".java")) {
|
|
255
|
-
const result = analyzeJavaForSql(filePath);
|
|
256
|
-
if (result.queries.length > 0) {
|
|
257
|
-
results.push(result);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
walkDir(targetPath);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// 전체 통계
|
|
266
|
-
const totalQueries = results.reduce((sum, r) => sum + r.summary.total, 0);
|
|
267
|
-
const totalWithIssues = results.reduce((sum, r) => sum + r.summary.withIssues, 0);
|
|
268
|
-
const allIssueTypes: Record<string, number> = {};
|
|
269
|
-
results.forEach((r) => {
|
|
270
|
-
Object.entries(r.summary.issueTypes).forEach(([key, count]) => {
|
|
271
|
-
allIssueTypes[key] = (allIssueTypes[key] || 0) + count;
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
const output = {
|
|
276
|
-
analyzed: results.length,
|
|
277
|
-
totalQueries,
|
|
278
|
-
queriesWithIssues: totalWithIssues,
|
|
279
|
-
issueTypes: allIssueTypes,
|
|
280
|
-
files: results.map((r) => ({
|
|
281
|
-
file: r.file,
|
|
282
|
-
queries: r.queries.map((q) => ({
|
|
283
|
-
line: q.line,
|
|
284
|
-
type: q.type,
|
|
285
|
-
location: q.location,
|
|
286
|
-
query: q.query.length > 100 ? q.query.substring(0, 100) + "..." : q.query,
|
|
287
|
-
issues: q.issues,
|
|
288
|
-
})),
|
|
289
|
-
})),
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
return {
|
|
293
|
-
success: true,
|
|
294
|
-
content: JSON.stringify(output, null, 2),
|
|
295
|
-
};
|
|
296
|
-
},
|
|
297
|
-
},
|
|
298
|
-
];
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
|
|
5
|
-
// Test helpers
|
|
6
|
-
const TEST_DIR = ".activo-test";
|
|
7
|
-
const STANDARDS_DIR = `${TEST_DIR}/standards`;
|
|
8
|
-
const RAG_DIR = `${TEST_DIR}/standards-rag`;
|
|
9
|
-
|
|
10
|
-
// Helper to create test directory
|
|
11
|
-
function setupTestDir() {
|
|
12
|
-
if (fs.existsSync(TEST_DIR)) {
|
|
13
|
-
fs.rmSync(TEST_DIR, { recursive: true });
|
|
14
|
-
}
|
|
15
|
-
fs.mkdirSync(STANDARDS_DIR, { recursive: true });
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Helper to cleanup test directory
|
|
19
|
-
function cleanupTestDir() {
|
|
20
|
-
if (fs.existsSync(TEST_DIR)) {
|
|
21
|
-
fs.rmSync(TEST_DIR, { recursive: true });
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Helper to create test markdown file
|
|
26
|
-
function createTestMarkdown(filename: string, content: string) {
|
|
27
|
-
fs.writeFileSync(path.join(STANDARDS_DIR, filename), content);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe("Standards Tools", () => {
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
setupTestDir();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
afterEach(() => {
|
|
36
|
-
cleanupTestDir();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe("splitStandardsIntoChunks", () => {
|
|
40
|
-
it("should split markdown by sections", () => {
|
|
41
|
-
const content = `# Development Standards
|
|
42
|
-
|
|
43
|
-
## Introduction
|
|
44
|
-
This is the introduction.
|
|
45
|
-
|
|
46
|
-
## RULE-001: Variable Naming
|
|
47
|
-
- Severity: error
|
|
48
|
-
- Rule: Use camelCase for variables
|
|
49
|
-
|
|
50
|
-
## RULE-002: Function Naming
|
|
51
|
-
- Severity: warning
|
|
52
|
-
- Rule: Use descriptive names
|
|
53
|
-
`;
|
|
54
|
-
createTestMarkdown("test.md", content);
|
|
55
|
-
|
|
56
|
-
// Read and verify file was created
|
|
57
|
-
const savedContent = fs.readFileSync(path.join(STANDARDS_DIR, "test.md"), "utf-8");
|
|
58
|
-
expect(savedContent).toContain("RULE-001");
|
|
59
|
-
expect(savedContent).toContain("RULE-002");
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("should handle empty files", () => {
|
|
63
|
-
createTestMarkdown("empty.md", "");
|
|
64
|
-
const savedContent = fs.readFileSync(path.join(STANDARDS_DIR, "empty.md"), "utf-8");
|
|
65
|
-
expect(savedContent).toBe("");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("should handle files without rules", () => {
|
|
69
|
-
const content = `# Simple Document
|
|
70
|
-
|
|
71
|
-
Just some text without rules.
|
|
72
|
-
`;
|
|
73
|
-
createTestMarkdown("simple.md", content);
|
|
74
|
-
const savedContent = fs.readFileSync(path.join(STANDARDS_DIR, "simple.md"), "utf-8");
|
|
75
|
-
expect(savedContent).toContain("Simple Document");
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
describe("cosineSimilarity", () => {
|
|
80
|
-
it("should return 1 for identical vectors", () => {
|
|
81
|
-
const a = [1, 2, 3];
|
|
82
|
-
const b = [1, 2, 3];
|
|
83
|
-
// Inline test for cosine similarity logic
|
|
84
|
-
let dotProduct = 0;
|
|
85
|
-
let normA = 0;
|
|
86
|
-
let normB = 0;
|
|
87
|
-
for (let i = 0; i < a.length; i++) {
|
|
88
|
-
dotProduct += a[i] * b[i];
|
|
89
|
-
normA += a[i] * a[i];
|
|
90
|
-
normB += b[i] * b[i];
|
|
91
|
-
}
|
|
92
|
-
const similarity = dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
93
|
-
expect(similarity).toBeCloseTo(1, 5);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("should return 0 for orthogonal vectors", () => {
|
|
97
|
-
const a = [1, 0];
|
|
98
|
-
const b = [0, 1];
|
|
99
|
-
let dotProduct = 0;
|
|
100
|
-
let normA = 0;
|
|
101
|
-
let normB = 0;
|
|
102
|
-
for (let i = 0; i < a.length; i++) {
|
|
103
|
-
dotProduct += a[i] * b[i];
|
|
104
|
-
normA += a[i] * a[i];
|
|
105
|
-
normB += b[i] * b[i];
|
|
106
|
-
}
|
|
107
|
-
const similarity = dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
108
|
-
expect(similarity).toBeCloseTo(0, 5);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("should handle different length vectors", () => {
|
|
112
|
-
const a = [1, 2, 3];
|
|
113
|
-
const b = [1, 2];
|
|
114
|
-
// Should return 0 or handle gracefully
|
|
115
|
-
if (a.length !== b.length) {
|
|
116
|
-
expect(true).toBe(true); // Different lengths not comparable
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
describe("File hash calculation", () => {
|
|
122
|
-
it("should generate consistent hashes for same content", () => {
|
|
123
|
-
const crypto = require("crypto");
|
|
124
|
-
const content = "test content";
|
|
125
|
-
const hash1 = crypto.createHash("md5").update(content).digest("hex");
|
|
126
|
-
const hash2 = crypto.createHash("md5").update(content).digest("hex");
|
|
127
|
-
expect(hash1).toBe(hash2);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it("should generate different hashes for different content", () => {
|
|
131
|
-
const crypto = require("crypto");
|
|
132
|
-
const hash1 = crypto.createHash("md5").update("content1").digest("hex");
|
|
133
|
-
const hash2 = crypto.createHash("md5").update("content2").digest("hex");
|
|
134
|
-
expect(hash1).not.toBe(hash2);
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
describe("RAG Directory Structure", () => {
|
|
140
|
-
beforeEach(() => {
|
|
141
|
-
setupTestDir();
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
afterEach(() => {
|
|
145
|
-
cleanupTestDir();
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it("should create RAG directory when needed", () => {
|
|
149
|
-
fs.mkdirSync(RAG_DIR, { recursive: true });
|
|
150
|
-
expect(fs.existsSync(RAG_DIR)).toBe(true);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("should save and load index file", () => {
|
|
154
|
-
fs.mkdirSync(RAG_DIR, { recursive: true });
|
|
155
|
-
const index = {
|
|
156
|
-
version: "1.0",
|
|
157
|
-
model: "nomic-embed-text",
|
|
158
|
-
createdAt: new Date().toISOString(),
|
|
159
|
-
updatedAt: new Date().toISOString(),
|
|
160
|
-
totalChunks: 10,
|
|
161
|
-
};
|
|
162
|
-
const indexPath = path.join(RAG_DIR, "index.json");
|
|
163
|
-
fs.writeFileSync(indexPath, JSON.stringify(index, null, 2));
|
|
164
|
-
|
|
165
|
-
const loaded = JSON.parse(fs.readFileSync(indexPath, "utf-8"));
|
|
166
|
-
expect(loaded.version).toBe("1.0");
|
|
167
|
-
expect(loaded.totalChunks).toBe(10);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it("should save and load embeddings file", () => {
|
|
171
|
-
fs.mkdirSync(RAG_DIR, { recursive: true });
|
|
172
|
-
const embeddings = [
|
|
173
|
-
{
|
|
174
|
-
chunk: { filepath: "test.md", section: "Test", content: "Test content" },
|
|
175
|
-
embedding: [0.1, 0.2, 0.3],
|
|
176
|
-
hash: "abc123",
|
|
177
|
-
},
|
|
178
|
-
];
|
|
179
|
-
const dataPath = path.join(RAG_DIR, "embeddings.json");
|
|
180
|
-
fs.writeFileSync(dataPath, JSON.stringify(embeddings));
|
|
181
|
-
|
|
182
|
-
const loaded = JSON.parse(fs.readFileSync(dataPath, "utf-8"));
|
|
183
|
-
expect(loaded.length).toBe(1);
|
|
184
|
-
expect(loaded[0].chunk.filepath).toBe("test.md");
|
|
185
|
-
});
|
|
186
|
-
});
|