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.
Files changed (161) hide show
  1. package/README.md +203 -1
  2. package/data/2026-03-04_20-54.json +181 -0
  3. package/data/2026-03-04_20-56.json +181 -0
  4. package/data/apex-rulesets/egov.yaml +469 -0
  5. package/data/apex-rulesets/modernize.yaml +687 -0
  6. package/data/apex-rulesets/quality.yaml +1677 -0
  7. package/data/apex-rulesets/rule-schema.yaml +587 -0
  8. package/data/apex-rulesets/secure.yaml +1688 -0
  9. package/data/apex-rulesets/spring.yaml +455 -0
  10. package/data/apex-rulesets/sql-format.yaml +99 -0
  11. package/data/apex-rulesets/sql-oracle.yaml +281 -0
  12. package/data/apex-rulesets/sql.yaml +1660 -0
  13. package/dist/cli/headless.d.ts.map +1 -1
  14. package/dist/cli/headless.js +32 -10
  15. package/dist/cli/headless.js.map +1 -1
  16. package/dist/cli/index.js +31 -3
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/core/agent.d.ts +3 -3
  19. package/dist/core/agent.d.ts.map +1 -1
  20. package/dist/core/agent.js +203 -384
  21. package/dist/core/agent.js.map +1 -1
  22. package/dist/core/commands.d.ts +2 -1
  23. package/dist/core/commands.d.ts.map +1 -1
  24. package/dist/core/commands.js +61 -9
  25. package/dist/core/commands.js.map +1 -1
  26. package/dist/core/config.d.ts +14 -0
  27. package/dist/core/config.d.ts.map +1 -1
  28. package/dist/core/config.js +41 -4
  29. package/dist/core/config.js.map +1 -1
  30. package/dist/core/conversation.d.ts +2 -2
  31. package/dist/core/conversation.d.ts.map +1 -1
  32. package/dist/core/conversation.js.map +1 -1
  33. package/dist/core/intentRouter.d.ts +43 -0
  34. package/dist/core/intentRouter.d.ts.map +1 -0
  35. package/dist/core/intentRouter.js +804 -0
  36. package/dist/core/intentRouter.js.map +1 -0
  37. package/dist/core/llm/anthropic.d.ts +24 -0
  38. package/dist/core/llm/anthropic.d.ts.map +1 -0
  39. package/dist/core/llm/anthropic.js +226 -0
  40. package/dist/core/llm/anthropic.js.map +1 -0
  41. package/dist/core/llm/ollama.d.ts +5 -14
  42. package/dist/core/llm/ollama.d.ts.map +1 -1
  43. package/dist/core/llm/ollama.js +3 -0
  44. package/dist/core/llm/ollama.js.map +1 -1
  45. package/dist/core/llm/types.d.ts +22 -0
  46. package/dist/core/llm/types.d.ts.map +1 -0
  47. package/dist/core/llm/types.js +2 -0
  48. package/dist/core/llm/types.js.map +1 -0
  49. package/dist/core/mcp/client.d.ts +6 -0
  50. package/dist/core/mcp/client.d.ts.map +1 -1
  51. package/dist/core/mcp/client.js +16 -0
  52. package/dist/core/mcp/client.js.map +1 -1
  53. package/dist/core/mcp/init.d.ts +12 -0
  54. package/dist/core/mcp/init.d.ts.map +1 -0
  55. package/dist/core/mcp/init.js +55 -0
  56. package/dist/core/mcp/init.js.map +1 -0
  57. package/dist/core/mcp/logger.d.ts +14 -0
  58. package/dist/core/mcp/logger.d.ts.map +1 -0
  59. package/dist/core/mcp/logger.js +50 -0
  60. package/dist/core/mcp/logger.js.map +1 -0
  61. package/dist/core/tools/analyzePatterns.d.ts +3 -0
  62. package/dist/core/tools/analyzePatterns.d.ts.map +1 -0
  63. package/dist/core/tools/analyzePatterns.js +293 -0
  64. package/dist/core/tools/analyzePatterns.js.map +1 -0
  65. package/dist/core/tools/apexPaths.d.ts +14 -0
  66. package/dist/core/tools/apexPaths.d.ts.map +1 -0
  67. package/dist/core/tools/apexPaths.js +54 -0
  68. package/dist/core/tools/apexPaths.js.map +1 -0
  69. package/dist/core/tools/apexUtils.d.ts +36 -0
  70. package/dist/core/tools/apexUtils.d.ts.map +1 -0
  71. package/dist/core/tools/apexUtils.js +83 -0
  72. package/dist/core/tools/apexUtils.js.map +1 -0
  73. package/dist/core/tools/explainIssue.d.ts +3 -0
  74. package/dist/core/tools/explainIssue.d.ts.map +1 -0
  75. package/dist/core/tools/explainIssue.js +181 -0
  76. package/dist/core/tools/explainIssue.js.map +1 -0
  77. package/dist/core/tools/fixGen.d.ts +3 -0
  78. package/dist/core/tools/fixGen.d.ts.map +1 -0
  79. package/dist/core/tools/fixGen.js +338 -0
  80. package/dist/core/tools/fixGen.js.map +1 -0
  81. package/dist/core/tools/generateImprovements.d.ts +21 -0
  82. package/dist/core/tools/generateImprovements.d.ts.map +1 -0
  83. package/dist/core/tools/generateImprovements.js +602 -0
  84. package/dist/core/tools/generateImprovements.js.map +1 -0
  85. package/dist/core/tools/generateReport.d.ts +3 -0
  86. package/dist/core/tools/generateReport.d.ts.map +1 -0
  87. package/dist/core/tools/generateReport.js +315 -0
  88. package/dist/core/tools/generateReport.js.map +1 -0
  89. package/dist/core/tools/index.d.ts +7 -0
  90. package/dist/core/tools/index.d.ts.map +1 -1
  91. package/dist/core/tools/index.js +62 -23
  92. package/dist/core/tools/index.js.map +1 -1
  93. package/dist/core/tools/recommendProfile.d.ts +3 -0
  94. package/dist/core/tools/recommendProfile.d.ts.map +1 -0
  95. package/dist/core/tools/recommendProfile.js +334 -0
  96. package/dist/core/tools/recommendProfile.js.map +1 -0
  97. package/dist/core/tools/ruleGen.d.ts +3 -0
  98. package/dist/core/tools/ruleGen.d.ts.map +1 -0
  99. package/dist/core/tools/ruleGen.js +1103 -0
  100. package/dist/core/tools/ruleGen.js.map +1 -0
  101. package/dist/core/tools/standards.d.ts.map +1 -1
  102. package/dist/core/tools/standards.js +7 -3
  103. package/dist/core/tools/standards.js.map +1 -1
  104. package/dist/ui/App.d.ts.map +1 -1
  105. package/dist/ui/App.js +86 -35
  106. package/dist/ui/App.js.map +1 -1
  107. package/dist/ui/components/InputBox.d.ts +1 -3
  108. package/dist/ui/components/InputBox.d.ts.map +1 -1
  109. package/dist/ui/components/InputBox.js +146 -5
  110. package/dist/ui/components/InputBox.js.map +1 -1
  111. package/dist/ui/components/MessageList.d.ts +3 -1
  112. package/dist/ui/components/MessageList.d.ts.map +1 -1
  113. package/dist/ui/components/MessageList.js +13 -7
  114. package/dist/ui/components/MessageList.js.map +1 -1
  115. package/dist/ui/components/StatusBar.d.ts +1 -1
  116. package/dist/ui/components/StatusBar.d.ts.map +1 -1
  117. package/dist/ui/components/StatusBar.js +3 -2
  118. package/dist/ui/components/StatusBar.js.map +1 -1
  119. package/dist/ui/components/ToolStatus.d.ts +3 -1
  120. package/dist/ui/components/ToolStatus.d.ts.map +1 -1
  121. package/dist/ui/components/ToolStatus.js +19 -4
  122. package/dist/ui/components/ToolStatus.js.map +1 -1
  123. package/package.json +7 -1
  124. package/demo.gif +0 -0
  125. package/demo.tape +0 -53
  126. package/screenshot.png +0 -0
  127. package/src/cli/banner.ts +0 -38
  128. package/src/cli/headless.ts +0 -63
  129. package/src/cli/index.ts +0 -57
  130. package/src/core/agent.ts +0 -711
  131. package/src/core/commands.ts +0 -118
  132. package/src/core/config.ts +0 -98
  133. package/src/core/conversation.ts +0 -235
  134. package/src/core/llm/ollama.ts +0 -351
  135. package/src/core/mcp/client.ts +0 -143
  136. package/src/core/tools/analyzeAll.ts +0 -482
  137. package/src/core/tools/ast.ts +0 -826
  138. package/src/core/tools/builtIn.ts +0 -221
  139. package/src/core/tools/cache.ts +0 -570
  140. package/src/core/tools/cssAnalysis.ts +0 -324
  141. package/src/core/tools/dependencyAnalysis.ts +0 -363
  142. package/src/core/tools/embeddings.ts +0 -746
  143. package/src/core/tools/frontendAst.ts +0 -802
  144. package/src/core/tools/htmlAnalysis.ts +0 -466
  145. package/src/core/tools/index.ts +0 -160
  146. package/src/core/tools/javaAst.ts +0 -1030
  147. package/src/core/tools/javaQuality.integration.test.ts +0 -537
  148. package/src/core/tools/memory.ts +0 -655
  149. package/src/core/tools/mybatisAnalysis.ts +0 -322
  150. package/src/core/tools/openapiAnalysis.ts +0 -431
  151. package/src/core/tools/pythonAnalysis.ts +0 -477
  152. package/src/core/tools/sqlAnalysis.ts +0 -298
  153. package/src/core/tools/standards.test.ts +0 -186
  154. package/src/core/tools/standards.ts +0 -889
  155. package/src/core/tools/types.ts +0 -38
  156. package/src/ui/App.tsx +0 -334
  157. package/src/ui/components/InputBox.tsx +0 -37
  158. package/src/ui/components/MessageList.tsx +0 -80
  159. package/src/ui/components/StatusBar.tsx +0 -36
  160. package/src/ui/components/ToolStatus.tsx +0 -38
  161. 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
- });