activo 0.4.0 → 0.4.1

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 CHANGED
@@ -1,191 +1,34 @@
1
1
  # ACTIVO
2
2
 
3
- AI 기반 코드 품질 분석 CLI 도구 (Ollama 전용)
3
+ AI 기반 코드 품질 분석 CLI (Ollama)
4
4
 
5
- ![Screenshot](screenshot.png)
5
+ ![Demo](demo.gif)
6
6
 
7
7
  ## 설치
8
8
 
9
9
  ```bash
10
- # 글로벌 설치 (권장)
11
10
  npm install -g activo
12
-
13
- # 또는 npx로 바로 실행
14
- npx activo
15
- ```
16
-
17
- ## 사용법
18
-
19
- ```bash
20
- # 대화형 모드
21
- activo
22
-
23
- # 프롬프트와 함께 실행
24
- activo "src 폴더 구조 보여줘"
25
-
26
- # 비대화형 모드
27
- activo --print "package.json 분석해줘"
28
-
29
- # 특정 모델 사용
30
- activo --model qwen2.5:7b
31
11
  ```
32
12
 
33
- ## 주요 기능
34
-
35
- ### 기본 도구
36
- - **파일 작업**: 읽기, 쓰기, 디렉토리 목록
37
- - **검색**: grep (패턴), glob (파일명)
38
- - **명령 실행**: 셸 명령 (안전 필터 적용)
39
-
40
- ### 코드 분석
41
- - **AST 분석**: 함수, 클래스, 타입 구조 파악 (TypeScript Compiler API)
42
- - **복잡도 분석**: 순환 복잡도 계산 및 리포트
43
- - **호출 그래프**: 함수 간 호출 관계 추적
44
- - **심볼 사용처**: 특정 심볼이 어디서 사용되는지 검색
45
-
46
- ### 파일 요약 캐싱
47
- - **LLM 요약**: 파일 내용을 LLM으로 요약 후 캐싱
48
- - **아웃라인**: 함수/클래스 시그니처 빠른 추출 (LLM 없음)
49
- - **변경 감지**: 파일 해시로 변경 시에만 재생성
50
-
51
- ### 의미 기반 검색 (RAG)
52
- - **코드베이스 인덱싱**: 벡터 임베딩 생성 (`nomic-embed-text`)
53
- - **의미 검색**: "로그인 관련 코드" → 관련 코드 찾기
54
- - **유사 코드 찾기**: 코드 패턴 검색
55
-
56
- ### 프로젝트 메모리
57
- - **컨텍스트 저장**: 프로젝트 정보, 기술 스택, 컨벤션
58
- - **노트/사실**: 중요 정보 기억
59
- - **대화 요약**: 세션 간 컨텍스트 유지
60
-
61
- ### 프론트엔드 분석
62
- - **React**: 컴포넌트 구조, Hooks 사용, 클래스 컴포넌트 감지
63
- - **Vue**: Options/Composition/Script Setup API 분석
64
- - **jQuery**: deprecated 메서드 검출 (bind, live, size 등)
65
-
66
- ### SQL/데이터베이스 분석
67
- - **SQL**: Java 내 @Query, JDBC 쿼리 분석 (SELECT *, N+1 패턴)
68
- - **MyBatis**: XML 매퍼 분석, ${} 인젝션 위험, 동적 SQL
69
-
70
- ### 웹 표준 분석
71
- - **CSS/SCSS/LESS**: !important, 중첩 깊이, vendor prefix
72
- - **HTML/JSP**: 접근성(a11y), SEO, 시맨틱 태그, deprecated 태그
73
-
74
- ### 의존성/API/Python
75
- - **의존성 검사**: package.json, pom.xml 취약점, deprecated 패키지
76
- - **OpenAPI 분석**: Swagger 스펙 검증, 엔드포인트 문서화 품질
77
- - **Python 분석**: Django/Flask/FastAPI 패턴, PEP8, 보안 이슈
78
-
79
- ### 개발 표준
80
- - **PDF 변환**: 개발표준 PDF → Markdown 변환
81
- - **코드 품질 분석**: 규칙 기반 코드 점검
82
-
83
- ## 도구 목록
84
-
85
- | 카테고리 | 도구 | 설명 |
86
- |----------|------|------|
87
- | **기본** | `read_file` | 파일 읽기 |
88
- | | `write_file` | 파일 쓰기 |
89
- | | `list_directory` | 디렉토리 목록 |
90
- | | `grep_search` | 패턴 검색 |
91
- | | `glob_search` | 파일명 검색 |
92
- | | `run_command` | 셸 명령 실행 |
93
- | **캐시** | `summarize_file` | 파일 요약 (LLM + 캐싱) |
94
- | | `get_file_outline` | 구조 추출 (빠름) |
95
- | | `batch_summarize` | 다중 파일 요약 |
96
- | **AST (TS/JS)** | `ast_analyze` | 심층 코드 분석 |
97
- | | `get_call_graph` | 호출 그래프 |
98
- | | `find_symbol_usage` | 심볼 사용처 |
99
- | | `complexity_report` | 복잡도 리포트 |
100
- | **Java** | `java_analyze` | Java AST 분석 |
101
- | | `java_complexity` | Java 복잡도 리포트 |
102
- | | `spring_check` | Spring 패턴 검사 |
103
- | **Frontend** | `react_check` | React 컴포넌트/Hooks 분석 |
104
- | | `vue_check` | Vue 컴포넌트 분석 |
105
- | | `jquery_check` | jQuery deprecated 메서드 검사 |
106
- | **SQL/DB** | `sql_check` | Java 내 SQL 쿼리 분석 |
107
- | | `mybatis_check` | MyBatis XML 매퍼 분석 |
108
- | **Web** | `css_check` | CSS/SCSS/LESS 분석 |
109
- | | `html_check` | HTML/JSP 접근성/SEO 분석 |
110
- | **의존성** | `dependency_check` | package.json, pom.xml 취약점 검사 |
111
- | **API** | `openapi_check` | OpenAPI/Swagger 스펙 분석 |
112
- | **Python** | `python_check` | Python/Django/Flask 분석 |
113
- | **통합** | `analyze_all` | 디렉토리 전체 자동 분석 |
114
- | **RAG** | `index_codebase` | 벡터 인덱싱 |
115
- | | `semantic_search` | 의미 검색 |
116
- | | `find_similar_code` | 유사 코드 찾기 |
117
- | **메모리** | `init_project_memory` | 프로젝트 컨텍스트 |
118
- | | `add_note` | 노트 저장 |
119
- | | `get_project_context` | 컨텍스트 조회 |
120
- | | `search_memory` | 메모리 검색 |
121
-
122
13
  ## 요구사항
123
14
 
124
15
  - Node.js 18+
125
16
  - [Ollama](https://ollama.ai) 실행 중
17
+ - 모델: `ollama pull mistral:latest`
126
18
 
127
- ## Ollama 설정
128
-
129
- ### 1. Ollama 설치
130
-
131
- ```bash
132
- # macOS
133
- brew install ollama
134
-
135
- # Windows
136
- # https://ollama.ai 에서 다운로드
137
-
138
- # Linux
139
- curl -fsSL https://ollama.ai/install.sh | sh
140
- ```
141
-
142
- ### 2. 모델 다운로드
143
-
144
- ```bash
145
- # 권장 모델
146
- ollama pull mistral:latest
147
-
148
- # 한국어 지원 모델
149
- ollama pull qwen2.5:7b
150
-
151
- # 코드 특화 모델
152
- ollama pull codellama:7b
153
-
154
- # 임베딩 모델 (의미 검색용)
155
- ollama pull nomic-embed-text
156
- ```
157
-
158
- ### 3. Ollama 실행
19
+ ## 사용법
159
20
 
160
21
  ```bash
161
- ollama serve
162
- ```
163
-
164
- ### 4. activo 설정
22
+ # 대화형 모드
23
+ activo
165
24
 
166
- `~/.activo/config.json`:
167
- ```json
168
- {
169
- "ollama": {
170
- "baseUrl": "http://localhost:11434",
171
- "model": "mistral:latest"
172
- }
173
- }
174
- ```
25
+ # 프롬프트와 함께 실행
26
+ activo "src 폴더 분석해줘"
175
27
 
176
- 또는 CLI 옵션으로 모델 지정:
177
- ```bash
28
+ # 특정 모델 사용
178
29
  activo --model qwen2.5:7b
179
30
  ```
180
31
 
181
- ## 단축키
182
-
183
- | 키 | 동작 |
184
- |---|------|
185
- | `Enter` | 메시지 전송 |
186
- | `ESC` | 진행 중 작업 취소 |
187
- | `Ctrl+C` x2 | 종료 |
188
-
189
32
  ## 라이선스
190
33
 
191
34
  MIT
package/demo.gif ADDED
Binary file
package/demo.tape ADDED
@@ -0,0 +1,53 @@
1
+ # VHS Demo for ACTIVO - Full Feature Showcase
2
+ Output demo.gif
3
+
4
+ Set FontSize 13
5
+ Set Width 900
6
+ Set Height 600
7
+ Set Theme "Dracula"
8
+ Set TypingSpeed 50ms
9
+
10
+ # 1. Start activo in centerpoint project
11
+ Type "cd /Users/mhb8436/Workspaces/centerpoint/server && activo"
12
+ Enter
13
+ Sleep 3s
14
+
15
+ # 2. Directory structure
16
+ Type "프로젝트 구조 보여줘"
17
+ Enter
18
+ Sleep 6s
19
+
20
+ # 3. Java code analysis
21
+ Type "api-server 자바 코드 분석해줘"
22
+ Enter
23
+ Sleep 10s
24
+
25
+ # 4. Spring pattern check
26
+ Type "Spring 패턴 검사해줘"
27
+ Enter
28
+ Sleep 8s
29
+
30
+ # 5. Dependency check
31
+ Type "pom.xml 의존성 분석해줘"
32
+ Enter
33
+ Sleep 6s
34
+
35
+ # 6. Find specific code
36
+ Type "WebSecurityConfig 파일 찾아서 보여줘"
37
+ Enter
38
+ Sleep 6s
39
+
40
+ # 7. SQL analysis
41
+ Type "SQL 쿼리 분석해줘"
42
+ Enter
43
+ Sleep 6s
44
+
45
+ # 8. Full analysis
46
+ Type "전체 코드 품질 분석해줘"
47
+ Enter
48
+ Sleep 10s
49
+
50
+ # 9. Exit
51
+ Type "/exit"
52
+ Enter
53
+ Sleep 1s
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "activo",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "AI-powered code quality analyzer with React Ink TUI, Tool Calling, and MCP support",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,186 @@
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
+ });