@jcjeon/integration-cli 0.2.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/.gitignore +23 -0
- package/.npmignore +21 -0
- package/.prettierignore +6 -0
- package/.prettierrc +26 -0
- package/AGENTS.md +10 -0
- package/CLAUDE.md +10 -0
- package/README.md +384 -0
- package/apps/server/README.md +294 -0
- package/apps/server/eslint.config.mjs +20 -0
- package/apps/server/nest-cli.json +8 -0
- package/apps/server/package.json +89 -0
- package/apps/server/scripts/postinstall.js +53 -0
- package/apps/server/src/__mocks__/glob.js +6 -0
- package/apps/server/src/__mocks__/uuid.js +5 -0
- package/apps/server/src/app.controller.spec.ts +24 -0
- package/apps/server/src/app.controller.ts +13 -0
- package/apps/server/src/app.module.ts +18 -0
- package/apps/server/src/app.service.ts +8 -0
- package/apps/server/src/common/ji-paths.ts +41 -0
- package/apps/server/src/database/database.module.ts +27 -0
- package/apps/server/src/database/entities/agent-changelog.entity.ts +39 -0
- package/apps/server/src/database/entities/agent-session.entity.ts +29 -0
- package/apps/server/src/database/entities/conversation.entity.ts +41 -0
- package/apps/server/src/database/entities/session.entity.ts +16 -0
- package/apps/server/src/database/entities/task-agent-run.entity.ts +40 -0
- package/apps/server/src/database/entities/task-agent.entity.ts +42 -0
- package/apps/server/src/database/entities/task-requirement.entity.ts +27 -0
- package/apps/server/src/database/entities/task-run.entity.ts +41 -0
- package/apps/server/src/database/entities/task.entity.ts +44 -0
- package/apps/server/src/main.ts +65 -0
- package/apps/server/src/modules/agents/agent-model-settings.spec.ts +80 -0
- package/apps/server/src/modules/agents/agents.module.ts +11 -0
- package/apps/server/src/modules/agents/claude/claude-auth.manager.ts +83 -0
- package/apps/server/src/modules/agents/claude/claude-pty.manager.ts +380 -0
- package/apps/server/src/modules/agents/claude/claude.controller.ts +85 -0
- package/apps/server/src/modules/agents/claude/claude.gateway.ts +158 -0
- package/apps/server/src/modules/agents/claude/claude.module.ts +18 -0
- package/apps/server/src/modules/agents/claude/claude.service.ts +67 -0
- package/apps/server/src/modules/agents/claude/dto/create-session.dto.ts +24 -0
- package/apps/server/src/modules/agents/claude/dto/resize-session.dto.ts +13 -0
- package/apps/server/src/modules/agents/claude/dto/send-input.dto.ts +9 -0
- package/apps/server/src/modules/agents/claude/interfaces/claude-session.interface.ts +26 -0
- package/apps/server/src/modules/agents/claude/interfaces/pty-event.interface.ts +10 -0
- package/apps/server/src/modules/agents/claude/interfaces/stream-event.interface.ts +61 -0
- package/apps/server/src/modules/agents/codex/codex-auth.manager.ts +107 -0
- package/apps/server/src/modules/agents/codex/codex-session.manager.ts +357 -0
- package/apps/server/src/modules/agents/codex/codex.controller.ts +64 -0
- package/apps/server/src/modules/agents/codex/codex.gateway.ts +97 -0
- package/apps/server/src/modules/agents/codex/codex.module.ts +17 -0
- package/apps/server/src/modules/agents/codex/dto/configure-auth.dto.ts +7 -0
- package/apps/server/src/modules/agents/gemini/dto/configure-auth.dto.ts +15 -0
- package/apps/server/src/modules/agents/gemini/dto/create-session.dto.ts +9 -0
- package/apps/server/src/modules/agents/gemini/dto/send-input.dto.ts +9 -0
- package/apps/server/src/modules/agents/gemini/gemini-auth.manager.ts +157 -0
- package/apps/server/src/modules/agents/gemini/gemini-session.manager.ts +287 -0
- package/apps/server/src/modules/agents/gemini/gemini.controller.ts +93 -0
- package/apps/server/src/modules/agents/gemini/gemini.gateway.ts +149 -0
- package/apps/server/src/modules/agents/gemini/gemini.module.ts +17 -0
- package/apps/server/src/modules/agents/gemini/interfaces/gemini-session.interface.ts +18 -0
- package/apps/server/src/modules/agents/gemini/interfaces/stream-event.interface.ts +14 -0
- package/apps/server/src/modules/agents/session-termination.spec.ts +103 -0
- package/apps/server/src/modules/changelog/changelog.controller.ts +20 -0
- package/apps/server/src/modules/changelog/changelog.module.ts +14 -0
- package/apps/server/src/modules/changelog/changelog.service.spec.ts +531 -0
- package/apps/server/src/modules/changelog/changelog.service.ts +690 -0
- package/apps/server/src/modules/conversations/conversation.controller.spec.ts +106 -0
- package/apps/server/src/modules/conversations/conversation.controller.ts +60 -0
- package/apps/server/src/modules/conversations/conversation.module.ts +14 -0
- package/apps/server/src/modules/conversations/conversation.service.spec.ts +176 -0
- package/apps/server/src/modules/conversations/conversation.service.ts +54 -0
- package/apps/server/src/modules/conversations/dto/create-conversation.dto.ts +37 -0
- package/apps/server/src/modules/conversations/enums/conversation.enum.ts +13 -0
- package/apps/server/src/modules/fs/fs.controller.ts +29 -0
- package/apps/server/src/modules/fs/fs.module.ts +8 -0
- package/apps/server/src/modules/harness/dto/save-harness.dto.ts +9 -0
- package/apps/server/src/modules/harness/harness.controller.spec.ts +95 -0
- package/apps/server/src/modules/harness/harness.controller.ts +35 -0
- package/apps/server/src/modules/harness/harness.module.ts +11 -0
- package/apps/server/src/modules/harness/harness.service.spec.ts +217 -0
- package/apps/server/src/modules/harness/harness.service.ts +112 -0
- package/apps/server/src/modules/sessions/session.controller.spec.ts +68 -0
- package/apps/server/src/modules/sessions/session.controller.ts +43 -0
- package/apps/server/src/modules/sessions/session.module.ts +14 -0
- package/apps/server/src/modules/sessions/session.service.spec.ts +106 -0
- package/apps/server/src/modules/sessions/session.service.ts +35 -0
- package/apps/server/src/modules/tasks/dto/create-task.dto.ts +54 -0
- package/apps/server/src/modules/tasks/dto/execute-task.dto.ts +22 -0
- package/apps/server/src/modules/tasks/dto/merge-file.dto.ts +7 -0
- package/apps/server/src/modules/tasks/dto/rerun-task.dto.ts +14 -0
- package/apps/server/src/modules/tasks/dto/update-task.dto.ts +55 -0
- package/apps/server/src/modules/tasks/task-execution.service.ts +978 -0
- package/apps/server/src/modules/tasks/task.gateway.ts +140 -0
- package/apps/server/src/modules/tasks/tasks.controller.spec.ts +210 -0
- package/apps/server/src/modules/tasks/tasks.controller.ts +139 -0
- package/apps/server/src/modules/tasks/tasks.module.ts +30 -0
- package/apps/server/src/modules/tasks/tasks.service.spec.ts +552 -0
- package/apps/server/src/modules/tasks/tasks.service.ts +333 -0
- package/apps/server/test/app.e2e-spec.ts +28 -0
- package/apps/server/test/jest-e2e.json +9 -0
- package/apps/server/tsconfig.build.json +4 -0
- package/apps/server/tsconfig.json +13 -0
- package/apps/web/AGENTS.md +7 -0
- package/apps/web/CLAUDE.md +1 -0
- package/apps/web/README.md +36 -0
- package/apps/web/eslint.config.mjs +21 -0
- package/apps/web/next-env.d.ts +6 -0
- package/apps/web/next.config.ts +7 -0
- package/apps/web/package.json +49 -0
- package/apps/web/postcss.config.mjs +7 -0
- package/apps/web/public/file.svg +1 -0
- package/apps/web/public/globe.svg +1 -0
- package/apps/web/public/next.svg +1 -0
- package/apps/web/public/vercel.svg +1 -0
- package/apps/web/public/window.svg +1 -0
- package/apps/web/src/app/claude/page.tsx +5 -0
- package/apps/web/src/app/codex/page.tsx +126 -0
- package/apps/web/src/app/favicon.ico +0 -0
- package/apps/web/src/app/gemini/page.tsx +130 -0
- package/apps/web/src/app/globals.css +149 -0
- package/apps/web/src/app/layout.tsx +40 -0
- package/apps/web/src/app/login/page.tsx +67 -0
- package/apps/web/src/app/page.tsx +497 -0
- package/apps/web/src/app/task/[id]/page.tsx +11 -0
- package/apps/web/src/app/test/page.tsx +298 -0
- package/apps/web/src/components/ui/Modal.tsx +78 -0
- package/apps/web/src/components/ui/WorkingDirPicker.tsx +195 -0
- package/apps/web/src/components/ui/__tests__/Modal.test.tsx +68 -0
- package/apps/web/src/features/auth/api/__tests__/auth.api.test.ts +83 -0
- package/apps/web/src/features/auth/api/auth.api.ts +81 -0
- package/apps/web/src/features/auth/hooks/__tests__/useClaudeAuth.test.ts +166 -0
- package/apps/web/src/features/auth/hooks/__tests__/useCodexAuth.test.ts +127 -0
- package/apps/web/src/features/auth/hooks/__tests__/useGeminiAuth.test.ts +120 -0
- package/apps/web/src/features/auth/hooks/useClaudeAuth.ts +88 -0
- package/apps/web/src/features/auth/hooks/useCodexAuth.ts +149 -0
- package/apps/web/src/features/auth/hooks/useGeminiAuth.ts +125 -0
- package/apps/web/src/features/auth/ui/CodexLoginPanel.tsx +302 -0
- package/apps/web/src/features/auth/ui/GeminiLoginPanel.tsx +316 -0
- package/apps/web/src/features/auth/ui/LoginForm.tsx +190 -0
- package/apps/web/src/features/auth/ui/LoginPanel.tsx +114 -0
- package/apps/web/src/features/auth/ui/__tests__/LoginPanel.test.tsx +105 -0
- package/apps/web/src/features/chat/api/__tests__/sessions.api.test.ts +187 -0
- package/apps/web/src/features/chat/api/sessions.api.ts +161 -0
- package/apps/web/src/features/chat/container/ClaudePageContainer.tsx +152 -0
- package/apps/web/src/features/chat/hooks/__tests__/useCodexSessions.test.ts +131 -0
- package/apps/web/src/features/chat/hooks/__tests__/useGeminiSessions.test.ts +130 -0
- package/apps/web/src/features/chat/hooks/useAgentModelSettings.ts +54 -0
- package/apps/web/src/features/chat/hooks/useClaudeSessions.ts +323 -0
- package/apps/web/src/features/chat/hooks/useCodexSessions.ts +275 -0
- package/apps/web/src/features/chat/hooks/useGeminiSessions.ts +255 -0
- package/apps/web/src/features/chat/hooks/useSessionCommand.ts +66 -0
- package/apps/web/src/features/chat/hooks/useSessionRename.ts +61 -0
- package/apps/web/src/features/chat/hooks/useSessionWorkingDirectories.ts +34 -0
- package/apps/web/src/features/chat/hooks/useUnifiedSessions.ts +156 -0
- package/apps/web/src/features/chat/lib/agentModelOptions.ts +72 -0
- package/apps/web/src/features/chat/ui/AgentModelPicker.tsx +134 -0
- package/apps/web/src/features/chat/ui/AgentSelectModal.tsx +236 -0
- package/apps/web/src/features/chat/ui/ChatInput.tsx +162 -0
- package/apps/web/src/features/chat/ui/ChatMessage.tsx +204 -0
- package/apps/web/src/features/chat/ui/ChatWorkspace.tsx +207 -0
- package/apps/web/src/features/chat/ui/CheckingSkeleton.tsx +44 -0
- package/apps/web/src/features/chat/ui/ClaudeLoginView.tsx +44 -0
- package/apps/web/src/features/chat/ui/PermissionCard.tsx +37 -0
- package/apps/web/src/features/chat/ui/SessionSidebar.tsx +280 -0
- package/apps/web/src/features/chat/ui/__tests__/AgentSelectModal.test.tsx +58 -0
- package/apps/web/src/features/chat/ui/__tests__/ChatInput.test.tsx +134 -0
- package/apps/web/src/features/chat/ui/__tests__/ChatMessage.test.tsx +106 -0
- package/apps/web/src/features/chat/ui/__tests__/ChatWorkspace.test.tsx +66 -0
- package/apps/web/src/features/diff/ui/DiffFileRow.tsx +73 -0
- package/apps/web/src/features/diff/ui/DiffHunk.tsx +61 -0
- package/apps/web/src/features/diff/ui/FileChangeBadge.tsx +23 -0
- package/apps/web/src/features/diff/ui/__tests__/DiffFileRow.test.tsx +40 -0
- package/apps/web/src/features/diff/ui/__tests__/DiffHunk.test.tsx +24 -0
- package/apps/web/src/features/diff/ui/__tests__/FileChangeBadge.test.tsx +16 -0
- package/apps/web/src/features/fs/api/fs.api.ts +14 -0
- package/apps/web/src/features/fs/hooks/useDirBrowser.ts +50 -0
- package/apps/web/src/features/harness/api/__tests__/harness.api.test.ts +73 -0
- package/apps/web/src/features/harness/api/harness.api.ts +46 -0
- package/apps/web/src/features/harness/hooks/__tests__/useHarness.test.ts +65 -0
- package/apps/web/src/features/harness/hooks/useHarness.ts +66 -0
- package/apps/web/src/features/harness/ui/HarnessModal.tsx +171 -0
- package/apps/web/src/features/harness/ui/__tests__/HarnessModal.test.tsx +46 -0
- package/apps/web/src/features/status/ui/AgentStatusModal.tsx +267 -0
- package/apps/web/src/features/status/ui/__tests__/AgentStatusModal.test.tsx +71 -0
- package/apps/web/src/features/tasks/api/__tests__/changelog.api.test.ts +89 -0
- package/apps/web/src/features/tasks/api/__tests__/tasks.api.test.ts +282 -0
- package/apps/web/src/features/tasks/api/changelog.api.ts +52 -0
- package/apps/web/src/features/tasks/api/tasks.api.ts +175 -0
- package/apps/web/src/features/tasks/container/TaskDetailPageContainer.tsx +69 -0
- package/apps/web/src/features/tasks/hooks/__tests__/useChangelogCodeCopy.test.ts +48 -0
- package/apps/web/src/features/tasks/hooks/__tests__/useTaskChangelog.test.ts +48 -0
- package/apps/web/src/features/tasks/hooks/__tests__/useTaskCreate.test.ts +217 -0
- package/apps/web/src/features/tasks/hooks/__tests__/useTaskEdit.test.ts +152 -0
- package/apps/web/src/features/tasks/hooks/__tests__/useTaskExecution.test.ts +143 -0
- package/apps/web/src/features/tasks/hooks/__tests__/useTaskList.test.ts +168 -0
- package/apps/web/src/features/tasks/hooks/__tests__/useTaskNotification.test.ts +125 -0
- package/apps/web/src/features/tasks/hooks/__tests__/useTaskRuns.test.ts +51 -0
- package/apps/web/src/features/tasks/hooks/useChangelogCodeCopy.ts +52 -0
- package/apps/web/src/features/tasks/hooks/useCopyToClipboard.ts +47 -0
- package/apps/web/src/features/tasks/hooks/useTaskChangelog.ts +32 -0
- package/apps/web/src/features/tasks/hooks/useTaskCreate.ts +137 -0
- package/apps/web/src/features/tasks/hooks/useTaskDetail.ts +217 -0
- package/apps/web/src/features/tasks/hooks/useTaskEdit.ts +130 -0
- package/apps/web/src/features/tasks/hooks/useTaskExecution.ts +137 -0
- package/apps/web/src/features/tasks/hooks/useTaskList.ts +159 -0
- package/apps/web/src/features/tasks/hooks/useTaskNotification.ts +80 -0
- package/apps/web/src/features/tasks/hooks/useTaskRuns.ts +32 -0
- package/apps/web/src/features/tasks/ui/AgentOutputPanel.tsx +203 -0
- package/apps/web/src/features/tasks/ui/AgentRoleSelect.tsx +97 -0
- package/apps/web/src/features/tasks/ui/ChangelogPanel.tsx +321 -0
- package/apps/web/src/features/tasks/ui/RunHistoryPanel.tsx +193 -0
- package/apps/web/src/features/tasks/ui/TaskCreateModal.tsx +205 -0
- package/apps/web/src/features/tasks/ui/TaskDetailView.tsx +413 -0
- package/apps/web/src/features/tasks/ui/TaskEditModal.tsx +165 -0
- package/apps/web/src/features/tasks/ui/TaskListModal.tsx +591 -0
- package/apps/web/src/features/tasks/ui/__tests__/AgentRoleSelect.test.tsx +91 -0
- package/apps/web/src/features/tasks/ui/__tests__/ChangelogPanel.test.tsx +94 -0
- package/apps/web/src/features/tasks/ui/__tests__/RunHistoryPanel.test.tsx +71 -0
- package/apps/web/src/features/tasks/ui/__tests__/TaskCreateModal.test.tsx +153 -0
- package/apps/web/src/features/tasks/ui/__tests__/TaskEditModal.test.tsx +75 -0
- package/apps/web/src/features/tasks/ui/__tests__/TaskListModal.test.tsx +243 -0
- package/apps/web/src/hooks/useWorkingDir.ts +28 -0
- package/apps/web/src/lib/__tests__/ansi.test.ts +88 -0
- package/apps/web/src/lib/ansi.ts +105 -0
- package/apps/web/src/lib/constants.ts +4 -0
- package/apps/web/src/lib/quota.ts +22 -0
- package/apps/web/src/lib/theme.tsx +78 -0
- package/apps/web/src/lib/toast.tsx +175 -0
- package/apps/web/src/store/agentStatusStore.ts +38 -0
- package/apps/web/tsconfig.json +18 -0
- package/apps/web/vitest.config.ts +25 -0
- package/apps/web/vitest.setup.ts +10 -0
- package/package.json +85 -0
- package/packages/cli/dist/commands/check.d.ts +1 -0
- package/packages/cli/dist/commands/check.js +89 -0
- package/packages/cli/dist/commands/init.d.ts +5 -0
- package/packages/cli/dist/commands/init.js +183 -0
- package/packages/cli/dist/commands/start.d.ts +4 -0
- package/packages/cli/dist/commands/start.js +188 -0
- package/packages/cli/dist/index.d.ts +2 -0
- package/packages/cli/dist/index.js +71 -0
- package/packages/cli/dist/utils/agent-tools.d.ts +28 -0
- package/packages/cli/dist/utils/agent-tools.js +193 -0
- package/packages/cli/dist/utils/project-init.d.ts +12 -0
- package/packages/cli/dist/utils/project-init.js +258 -0
- package/packages/cli/dist/utils/proxy.d.ts +8 -0
- package/packages/cli/dist/utils/proxy.js +138 -0
- package/packages/cli/package.json +30 -0
- package/packages/cli/src/commands/check.ts +77 -0
- package/packages/cli/src/commands/init.ts +209 -0
- package/packages/cli/src/commands/start.ts +183 -0
- package/packages/cli/src/index.ts +91 -0
- package/packages/cli/src/utils/agent-tools.ts +201 -0
- package/packages/cli/src/utils/project-init.ts +252 -0
- package/packages/cli/src/utils/proxy.ts +123 -0
- package/packages/cli/tsconfig.json +14 -0
- package/packages/eslint-config/base.mjs +31 -0
- package/packages/eslint-config/nest.mjs +55 -0
- package/packages/eslint-config/next.mjs +23 -0
- package/packages/eslint-config/package.json +20 -0
- package/packages/typescript-config/base.json +16 -0
- package/packages/typescript-config/nestjs.json +17 -0
- package/packages/typescript-config/nextjs.json +15 -0
- package/packages/typescript-config/package.json +11 -0
- package/turbo.json +28 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# Integration CLI — Server
|
|
2
|
+
|
|
3
|
+
멀티 에이전트 태스크 실행 백엔드. Claude / Gemini / Codex CLI를 오케스트레이션하고 결과를 WebSocket으로 스트리밍한다.
|
|
4
|
+
|
|
5
|
+
## 기술 스택
|
|
6
|
+
|
|
7
|
+
- **Framework**: NestJS 11, TypeScript 5
|
|
8
|
+
- **Database**: SQLite (`~/.ji/ji.db`) via TypeORM 0.3
|
|
9
|
+
- **WebSocket**: Socket.IO 4 (`/tasks`, `/agents/claude`, `/agents/gemini`)
|
|
10
|
+
- **Agent CLI**: Claude Code CLI, Gemini CLI, Codex CLI (node-pty / spawn)
|
|
11
|
+
- **Test**: Jest 30, ts-jest, @nestjs/testing
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 실행
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install
|
|
19
|
+
npm run dev # watch mode (port 3001)
|
|
20
|
+
npm run start:prod # production
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
API 문서: `http://localhost:3001/docs` (Scalar UI)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 테스트
|
|
28
|
+
|
|
29
|
+
### 명령어
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm test # 전체 단위 테스트
|
|
33
|
+
npm run test:watch # watch 모드
|
|
34
|
+
npm run test:cov # 커버리지 리포트 (coverage/ 디렉토리 생성)
|
|
35
|
+
npm run test:e2e # E2E 테스트
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 구성 개요
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
src/
|
|
42
|
+
├── app.controller.spec.ts # 앱 루트 컨트롤러
|
|
43
|
+
├── __mocks__/
|
|
44
|
+
│ ├── uuid.js # uuid v14 (ESM-only) CJS mock
|
|
45
|
+
│ └── glob.js # TypeORM DirectoryLoader glob mock
|
|
46
|
+
└── modules/
|
|
47
|
+
├── tasks/
|
|
48
|
+
│ ├── tasks.service.spec.ts # TasksService 단위 테스트
|
|
49
|
+
│ └── tasks.controller.spec.ts # TasksController 단위 테스트
|
|
50
|
+
├── conversations/
|
|
51
|
+
│ ├── conversation.service.spec.ts # ConversationService 단위 테스트
|
|
52
|
+
│ └── conversation.controller.spec.ts # ConversationController 단위 테스트
|
|
53
|
+
├── sessions/
|
|
54
|
+
│ ├── session.service.spec.ts # SessionService 단위 테스트
|
|
55
|
+
│ └── session.controller.spec.ts # SessionController 단위 테스트
|
|
56
|
+
├── harness/
|
|
57
|
+
│ ├── harness.service.spec.ts # HarnessService 단위 테스트
|
|
58
|
+
│ └── harness.controller.spec.ts # HarnessController 단위 테스트
|
|
59
|
+
└── changelog/
|
|
60
|
+
└── changelog.service.spec.ts # GitChangelogService 단위 테스트
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 테스트 상세
|
|
66
|
+
|
|
67
|
+
### TasksService — `tasks.service.spec.ts` (29개)
|
|
68
|
+
|
|
69
|
+
Repository 5개, `TaskExecutionService`, `GitChangelogService`를 모두 mock으로 교체한 순수 단위 테스트.
|
|
70
|
+
|
|
71
|
+
| 그룹 | 시나리오 |
|
|
72
|
+
|------|---------|
|
|
73
|
+
| `findOne` | task 반환 / 미존재 시 `NotFoundException` |
|
|
74
|
+
| `findAll` | `archived: false` 필터 + 최신순 정렬 |
|
|
75
|
+
| `create` | 요구사항·에이전트 없음 / 요구사항 포함 / 에이전트 포함 / workingDir null |
|
|
76
|
+
| `update` | title 변경 / 요구사항 교체 / 빈 배열로 전체 삭제 / 에이전트 교체 / 미존재 예외 |
|
|
77
|
+
| `execute` | 정상 실행 → `spawnTask` 호출 / 이미 running 상태 → 재실행 없음 |
|
|
78
|
+
| `rerun` | 에이전트 초기화 + 다음 버전 계산 / 실행 이력 없을 때 버전 1 / running 상태 → 재실행 없음 |
|
|
79
|
+
| `stop` | `stopTask` 호출 + run 상태를 `stopped` 업데이트 |
|
|
80
|
+
| `getRuns` | 버전 내림차순 반환 |
|
|
81
|
+
| `archive` | `archived: true` 업데이트 / 미존재 예외 |
|
|
82
|
+
| `remove` | `delete` 호출 |
|
|
83
|
+
| `mergeAgentAll` | 전체 병합 / 에이전트 없음 예외 / `worktreePath` 없음 / `workingDir` 없으면 `process.cwd()` 사용 |
|
|
84
|
+
| `mergeAgentFile` | 단일 파일 병합 / 에이전트 없음 예외 / `worktreePath` 없음 |
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// 실행 상태 확인 예시
|
|
88
|
+
it('이미 running 상태이면 spawnTask를 호출하지 않는다', async () => {
|
|
89
|
+
taskRepo.findOne.mockResolvedValue({ ...baseTask, status: 'running' });
|
|
90
|
+
await service.execute('task-1');
|
|
91
|
+
expect(executionService.spawnTask).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### TasksController — `tasks.controller.spec.ts` (13개)
|
|
98
|
+
|
|
99
|
+
`TasksService`를 mock으로 교체. 컨트롤러가 올바른 인수로 서비스 메서드를 위임하는지 검증.
|
|
100
|
+
|
|
101
|
+
| 메서드 | 검증 항목 |
|
|
102
|
+
|--------|---------|
|
|
103
|
+
| `POST /tasks` | `service.create(dto)` 위임 |
|
|
104
|
+
| `GET /tasks` | `service.findAll()` 위임 |
|
|
105
|
+
| `GET /tasks/:id` | `service.findOne(id)` 위임 |
|
|
106
|
+
| `PATCH /tasks/:id` | `service.update(id, dto)` 위임 |
|
|
107
|
+
| `POST /tasks/:id/execute` | `service.execute(id)` 위임 |
|
|
108
|
+
| `POST /tasks/:id/stop` | `service.stop(id)` 위임 |
|
|
109
|
+
| `POST /tasks/:id/rerun` | `service.rerun(id, supplementNote)` / `supplementNote` 없을 때 undefined 전달 |
|
|
110
|
+
| `GET /tasks/:id/runs` | `service.getRuns(id)` 위임 |
|
|
111
|
+
| `POST /tasks/:id/archive` | `service.archive(id)` 위임 |
|
|
112
|
+
| `POST /tasks/:id/agents/:agentId/merge` | `agentId`를 `Number`로 변환 후 위임 |
|
|
113
|
+
| `POST /tasks/:id/agents/:agentId/merge-file` | `filePath` + `Number(agentId)` 위임 |
|
|
114
|
+
| `DELETE /tasks/:id` | `service.remove(id)` 위임 |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### ConversationService — `conversation.service.spec.ts` (14개)
|
|
119
|
+
|
|
120
|
+
`ConversationEntity` Repository를 mock으로 교체.
|
|
121
|
+
|
|
122
|
+
| 그룹 | 시나리오 |
|
|
123
|
+
|------|---------|
|
|
124
|
+
| `create` | 기본 메시지 저장 / `agentId` + `runId` 포함 저장 |
|
|
125
|
+
| `findBySession` | `sessionId` 필터 + ASC 정렬 / 빈 결과 |
|
|
126
|
+
| `findByRun` | `runId` 필터 + ASC 정렬 |
|
|
127
|
+
| `findOne` | ID 조회 / 미존재 `NotFoundException` |
|
|
128
|
+
| `remove` | 조회 후 삭제 / 미존재 `NotFoundException` |
|
|
129
|
+
| `removeBySession` | `sessionId` 기준 전체 삭제 |
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### ConversationController — `conversation.controller.spec.ts` (5개)
|
|
134
|
+
|
|
135
|
+
`ConversationService` mock. 각 엔드포인트 위임 검증.
|
|
136
|
+
|
|
137
|
+
| 엔드포인트 | 검증 |
|
|
138
|
+
|-----------|------|
|
|
139
|
+
| `POST /conversations` | `service.create(dto)` |
|
|
140
|
+
| `GET /conversations/session/:sessionId` | `service.findBySession(sessionId)` |
|
|
141
|
+
| `GET /conversations/:id` | `service.findOne(id)` |
|
|
142
|
+
| `DELETE /conversations/:id` | `service.remove(id)` |
|
|
143
|
+
| `DELETE /conversations/session/:sessionId` | `service.removeBySession(sessionId)` |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### SessionService — `session.service.spec.ts` (7개)
|
|
148
|
+
|
|
149
|
+
`SessionEntity` Repository mock.
|
|
150
|
+
|
|
151
|
+
| 그룹 | 시나리오 |
|
|
152
|
+
|------|---------|
|
|
153
|
+
| `findAll` | 전체 최신순 / `agentType: 'claude'` 필터 / `agentType: 'gemini'` 필터 / 빈 결과 |
|
|
154
|
+
| `findOne` | `sessionId` 조회 / 미존재 `NotFoundException` (메시지 포함) |
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
### SessionController — `session.controller.spec.ts` (3개)
|
|
159
|
+
|
|
160
|
+
`SessionService` mock. `agentType` query param 유무 모두 검증.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### HarnessService — `harness.service.spec.ts` (17개)
|
|
165
|
+
|
|
166
|
+
`fs` 모듈 전체를 `jest.mock('fs')`로 교체. 파일시스템 접근을 직접 제어.
|
|
167
|
+
|
|
168
|
+
| 그룹 | 시나리오 |
|
|
169
|
+
|------|---------|
|
|
170
|
+
| `findAll` | 존재하는 파일만 수집 / 없으면 빈 배열 / `md` → `tsx` 순서로 탐색 |
|
|
171
|
+
| `findOne` | `.md` 파일 반환 / `.tsx` 파일 반환 / 없으면 null / `readFileSync` utf8 호출 확인 |
|
|
172
|
+
| `save` | `.md` 저장 / `.tsx` 저장 / 확장자 변경 시 기존 파일 삭제 / 다른 확장자 없으면 삭제 안 함 |
|
|
173
|
+
| `remove` | `.md` 삭제 / `.tsx` 삭제 / 두 파일 모두 존재하면 둘 다 삭제 / 없으면 삭제 안 함 |
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// 확장자 변경 처리 예시
|
|
177
|
+
it('확장자 변경 시 기존 파일을 삭제한다', () => {
|
|
178
|
+
const oldPath = path.join(harnessDir, 'common.tsx');
|
|
179
|
+
(fs.existsSync as jest.Mock).mockImplementation((p) => p === oldPath);
|
|
180
|
+
|
|
181
|
+
service.save('common', 'content', 'md'); // tsx → md로 변경
|
|
182
|
+
|
|
183
|
+
expect(fs.unlinkSync).toHaveBeenCalledWith(oldPath);
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### HarnessController — `harness.controller.spec.ts` (6개)
|
|
190
|
+
|
|
191
|
+
`HarnessService` mock. `findOne`이 null 반환 시 기본값(`{ role, ext: 'md', content: '' }`) 반환 검증 포함.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
### GitChangelogService — `changelog.service.spec.ts` (30개)
|
|
196
|
+
|
|
197
|
+
`child_process`(`execSync`, `execFileSync`)와 `fs`를 모두 mock. private 메서드 `parseDiff`는 `(service as any).parseDiff()`로 직접 접근.
|
|
198
|
+
|
|
199
|
+
| 그룹 | 시나리오 |
|
|
200
|
+
|------|---------|
|
|
201
|
+
| `isGitRepo` | git repo 확인 (`true`) / 아닐 때 (`false`) |
|
|
202
|
+
| `getCurrentHead` | HEAD SHA 반환 + trim |
|
|
203
|
+
| `getCurrentBranch` | 브랜치명 반환 / 특수문자 → `-` 치환 + 40자 자름 / 에러 → `'unknown'` |
|
|
204
|
+
| `getRepoRoot` | `--show-toplevel` 결과 반환 |
|
|
205
|
+
| `mergeAll` | 전체 병합 성공 / worktree 없음 실패 / 충돌 시 `false` + abort 시도 |
|
|
206
|
+
| `mergeFile` | 단일 파일 병합 성공 / worktree 없음 실패 / checkout 실패 → 에러 메시지 |
|
|
207
|
+
| `getLatestRunId` | 최근 runId 반환 / 없으면 null |
|
|
208
|
+
| `getByTask` | 에이전트별 그룹핑 / runId 직접 지정 / 없으면 빈 배열 |
|
|
209
|
+
| `parseDiff` | `modified` 파일 / `added` 파일 / `deleted` 파일 / `renamed` 파일 / 멀티파일 / 빈 diff / 200KB 초과 truncation / `+++`·`---` 헤더 카운트 제외 |
|
|
210
|
+
| `captureAndSave` | 변경사항 있음 → DB 저장 + SHA 반환 / 없음 → DB 미저장 / 에러 → null |
|
|
211
|
+
| `mergeToMain` | merge 성공 / 충돌 → abort 후 예외 미전파 |
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// parseDiff truncation 예시
|
|
215
|
+
it('200KB 초과 patch는 truncated 처리한다', () => {
|
|
216
|
+
const longContent = '+' + 'x'.repeat(210_000);
|
|
217
|
+
// ...
|
|
218
|
+
expect(result[0].patch).toContain('(truncated)');
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Jest 설정 (`package.json`)
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
"jest": {
|
|
228
|
+
"rootDir": "src",
|
|
229
|
+
"testRegex": ".*\\.spec\\.ts$",
|
|
230
|
+
"transform": { "^.+\\.(t|j)s$": "ts-jest" },
|
|
231
|
+
"moduleNameMapper": {
|
|
232
|
+
"^uuid$": "<rootDir>/__mocks__/uuid.js",
|
|
233
|
+
"^glob$": "<rootDir>/__mocks__/glob.js"
|
|
234
|
+
},
|
|
235
|
+
"coverageDirectory": "../coverage"
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### `__mocks__/uuid.js`
|
|
240
|
+
|
|
241
|
+
uuid v14는 ESM-only 패키지여서 Jest(CJS)에서 직접 import 시 파싱 에러 발생. `jest.fn()`으로 감싼 CJS mock으로 대체.
|
|
242
|
+
|
|
243
|
+
### `__mocks__/glob.js`
|
|
244
|
+
|
|
245
|
+
TypeORM v0.3이 로드될 때 `DirectoryExportedClassesLoader`가 `glob`을 통해 `path-scurry`(ESM)를 불러온다. 단위 테스트에서는 디렉터리 스캔이 필요 없으므로 no-op mock으로 차단.
|
|
246
|
+
|
|
247
|
+
### `tsconfig.json`
|
|
248
|
+
|
|
249
|
+
`"isolatedModules": true` — ts-jest가 각 파일을 독립적으로 트랜스파일. 타입 체크를 생략해 테스트 속도를 높이고 NodeNext 모듈 해석의 부작용을 방지.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 커버리지 현황
|
|
254
|
+
|
|
255
|
+
> `npm run test:cov` 실행 기준
|
|
256
|
+
|
|
257
|
+
| 모듈 | Statements | Branches | Functions | Lines |
|
|
258
|
+
|------|-----------|----------|-----------|-------|
|
|
259
|
+
| `tasks/tasks.service.ts` | 99% | 85% | 100% | 100% |
|
|
260
|
+
| `tasks/tasks.controller.ts` | 100% | 75% | 100% | 100% |
|
|
261
|
+
| `conversations/conversation.service.ts` | 100% | 83% | 100% | 100% |
|
|
262
|
+
| `conversations/conversation.controller.ts` | 100% | 75% | 100% | 100% |
|
|
263
|
+
| `sessions/session.service.ts` | 100% | 88% | 100% | 100% |
|
|
264
|
+
| `sessions/session.controller.ts` | 100% | 75% | 100% | 100% |
|
|
265
|
+
| `harness/harness.service.ts` | 100% | 100% | 100% | 100% |
|
|
266
|
+
| `harness/harness.controller.ts` | 100% | 80% | 100% | 100% |
|
|
267
|
+
| `changelog/changelog.service.ts` | 85% | 81% | 87% | 85% |
|
|
268
|
+
|
|
269
|
+
**미커버 영역**: `task-execution.service.ts`, `*.gateway.ts`, 에이전트 모듈(`claude/`, `gemini/`, `codex/`) — CLI 서브프로세스·PTY 세션 의존으로 단위 테스트 제외. 통합/E2E 테스트 대상.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## 테스트 전략
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
┌─────────────────────────────────────────────────────────┐
|
|
277
|
+
│ 단위 테스트 (현재) │
|
|
278
|
+
│ Service / Controller — Repository·외부 의존 전부 mock │
|
|
279
|
+
├─────────────────────────────────────────────────────────┤
|
|
280
|
+
│ 통합 테스트 (미구현) │
|
|
281
|
+
│ 실제 SQLite in-memory DB + TypeORM │
|
|
282
|
+
│ 대상: TasksService CRUD, ConversationService │
|
|
283
|
+
├─────────────────────────────────────────────────────────┤
|
|
284
|
+
│ E2E 테스트 (test/app.e2e-spec.ts) │
|
|
285
|
+
│ 실 서버 기동 + supertest HTTP 요청 │
|
|
286
|
+
└─────────────────────────────────────────────────────────┘
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Mock 원칙
|
|
290
|
+
|
|
291
|
+
- **Repository**: `create / save / findOne / find / update / delete`를 `jest.fn()`으로 교체
|
|
292
|
+
- **ExternalProcess (`task-execution.service`)**: `jest.mock('./task-execution.service', () => ...)` 으로 모듈 레벨 대체 — CLI 프로세스를 실제로 실행하지 않음
|
|
293
|
+
- **Git CLI (`child_process`)**: `execSync` / `execFileSync`를 `jest.Mock`으로 교체
|
|
294
|
+
- **FileSystem (`fs`)**: `jest.mock('fs')`로 전체 대체 — 실 파일시스템 접근 없음
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: 전지창
|
|
3
|
+
* @Date: 2026-05-04 17:17:33
|
|
4
|
+
* @LastEditTime: 2026-05-04 17:21:47
|
|
5
|
+
* @LastEditors: 전지창
|
|
6
|
+
* @Description:
|
|
7
|
+
*/
|
|
8
|
+
import nestConfig from "@ji/eslint-config/nest";
|
|
9
|
+
|
|
10
|
+
export default [
|
|
11
|
+
...nestConfig,
|
|
12
|
+
{
|
|
13
|
+
languageOptions: {
|
|
14
|
+
parserOptions: {
|
|
15
|
+
projectService: true,
|
|
16
|
+
tsconfigRootDir: path.join(import.meta.dirname, ".."),
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
];
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ji/server",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"postinstall": "node scripts/postinstall.js",
|
|
8
|
+
"build": "nest build",
|
|
9
|
+
"dev": "nest start --watch",
|
|
10
|
+
"start": "nest start",
|
|
11
|
+
"start:debug": "nest start --debug --watch",
|
|
12
|
+
"start:prod": "node dist/main",
|
|
13
|
+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
14
|
+
"lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
15
|
+
"test": "jest",
|
|
16
|
+
"test:watch": "jest --watch",
|
|
17
|
+
"test:cov": "jest --coverage",
|
|
18
|
+
"test:e2e": "jest --config ./test/jest-e2e.json",
|
|
19
|
+
"clean": "rm -rf dist"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@nestjs/common": "^11.0.1",
|
|
23
|
+
"@nestjs/core": "^11.0.1",
|
|
24
|
+
"@nestjs/platform-express": "^11.0.1",
|
|
25
|
+
"@nestjs/platform-socket.io": "^11.1.19",
|
|
26
|
+
"@nestjs/swagger": "^11.4.3",
|
|
27
|
+
"@nestjs/typeorm": "^11.0.1",
|
|
28
|
+
"@nestjs/websockets": "^11.1.19",
|
|
29
|
+
"@scalar/nestjs-api-reference": "^1.1.16",
|
|
30
|
+
"better-sqlite3": "^12.9.0",
|
|
31
|
+
"class-transformer": "^0.5.1",
|
|
32
|
+
"class-validator": "^0.15.1",
|
|
33
|
+
"node-pty": "^1.1.0",
|
|
34
|
+
"reflect-metadata": "^0.2.2",
|
|
35
|
+
"rxjs": "^7.8.1",
|
|
36
|
+
"socket.io": "^4.8.3",
|
|
37
|
+
"typeorm": "^0.3.29",
|
|
38
|
+
"uuid": "^14.0.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@eslint/js": "^9.18.0",
|
|
42
|
+
"@ji/eslint-config": "*",
|
|
43
|
+
"@ji/typescript-config": "*",
|
|
44
|
+
"@nestjs/cli": "^11.0.0",
|
|
45
|
+
"@nestjs/schematics": "^11.0.0",
|
|
46
|
+
"@nestjs/testing": "^11.0.1",
|
|
47
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
48
|
+
"@types/express": "^5.0.0",
|
|
49
|
+
"@types/jest": "^30.0.0",
|
|
50
|
+
"@types/node": "^24.0.0",
|
|
51
|
+
"@types/supertest": "^7.0.0",
|
|
52
|
+
"@types/uuid": "^10.0.0",
|
|
53
|
+
"eslint": "^9.18.0",
|
|
54
|
+
"eslint-config-prettier": "^10.0.1",
|
|
55
|
+
"eslint-plugin-import": "^2.32.0",
|
|
56
|
+
"eslint-plugin-prettier": "^5.2.2",
|
|
57
|
+
"globals": "^17.0.0",
|
|
58
|
+
"jest": "^30.0.0",
|
|
59
|
+
"source-map-support": "^0.5.21",
|
|
60
|
+
"supertest": "^7.0.0",
|
|
61
|
+
"ts-jest": "^29.2.5",
|
|
62
|
+
"ts-loader": "^9.5.2",
|
|
63
|
+
"ts-node": "^10.9.2",
|
|
64
|
+
"tsconfig-paths": "^4.2.0",
|
|
65
|
+
"typescript": "^5.7.3",
|
|
66
|
+
"typescript-eslint": "^8.20.0"
|
|
67
|
+
},
|
|
68
|
+
"jest": {
|
|
69
|
+
"moduleFileExtensions": [
|
|
70
|
+
"js",
|
|
71
|
+
"json",
|
|
72
|
+
"ts"
|
|
73
|
+
],
|
|
74
|
+
"rootDir": "src",
|
|
75
|
+
"testRegex": ".*\\.spec\\.ts$",
|
|
76
|
+
"transform": {
|
|
77
|
+
"^.+\\.(t|j)s$": "ts-jest"
|
|
78
|
+
},
|
|
79
|
+
"moduleNameMapper": {
|
|
80
|
+
"^uuid$": "<rootDir>/__mocks__/uuid.js",
|
|
81
|
+
"^glob$": "<rootDir>/__mocks__/glob.js"
|
|
82
|
+
},
|
|
83
|
+
"collectCoverageFrom": [
|
|
84
|
+
"**/*.(t|j)s"
|
|
85
|
+
],
|
|
86
|
+
"coverageDirectory": "../coverage",
|
|
87
|
+
"testEnvironment": "node"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
// ~/.ji 데이터 디렉토리 생성
|
|
10
|
+
const dataDir = path.join(os.homedir(), '.ji');
|
|
11
|
+
if (!fs.existsSync(dataDir)) {
|
|
12
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
13
|
+
console.log(`[ji] Created data directory: ${dataDir}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// better-sqlite3 네이티브 모듈 확인 및 빌드
|
|
17
|
+
function isSqliteLoadable() {
|
|
18
|
+
try {
|
|
19
|
+
require('better-sqlite3');
|
|
20
|
+
return true;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!isSqliteLoadable()) {
|
|
27
|
+
console.log('[ji] Building better-sqlite3 for your platform...');
|
|
28
|
+
|
|
29
|
+
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
30
|
+
const result = spawnSync(npm, ['rebuild', 'better-sqlite3'], {
|
|
31
|
+
stdio: 'inherit',
|
|
32
|
+
cwd: path.join(__dirname, '..'),
|
|
33
|
+
shell: false,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (result.status !== 0) {
|
|
37
|
+
console.error(
|
|
38
|
+
'\n[ji] ⚠️ better-sqlite3 build failed.\n' +
|
|
39
|
+
' SQLite requires native build tools (node-gyp).\n' +
|
|
40
|
+
' Install them and retry:\n\n' +
|
|
41
|
+
' Windows : npm install --global windows-build-tools\n' +
|
|
42
|
+
' macOS : xcode-select --install\n' +
|
|
43
|
+
' Linux : sudo apt-get install build-essential python3\n\n' +
|
|
44
|
+
' Then run: npm rebuild better-sqlite3\n' +
|
|
45
|
+
' See: https://github.com/nodejs/node-gyp#installation\n',
|
|
46
|
+
);
|
|
47
|
+
// 빌드 실패해도 설치 자체는 중단하지 않음 (경고만 출력)
|
|
48
|
+
} else {
|
|
49
|
+
console.log('[ji] better-sqlite3 built successfully.');
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
console.log('[ji] better-sqlite3 is ready.');
|
|
53
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { TestingModule } from '@nestjs/testing';
|
|
2
|
+
import { Test } from '@nestjs/testing';
|
|
3
|
+
|
|
4
|
+
import { AppController } from './app.controller';
|
|
5
|
+
import { AppService } from './app.service';
|
|
6
|
+
|
|
7
|
+
describe('AppController', () => {
|
|
8
|
+
let appController: AppController;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
const app: TestingModule = await Test.createTestingModule({
|
|
12
|
+
controllers: [AppController],
|
|
13
|
+
providers: [AppService],
|
|
14
|
+
}).compile();
|
|
15
|
+
|
|
16
|
+
appController = app.get<AppController>(AppController);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('root', () => {
|
|
20
|
+
it('should return "Hello World!"', () => {
|
|
21
|
+
expect(appController.getHello()).toBe('Hello World!');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Controller, Get } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
import { AppService } from './app.service';
|
|
4
|
+
|
|
5
|
+
@Controller()
|
|
6
|
+
export class AppController {
|
|
7
|
+
constructor(private readonly appService: AppService) {}
|
|
8
|
+
|
|
9
|
+
@Get()
|
|
10
|
+
getHello(): string {
|
|
11
|
+
return this.appService.getHello();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
import { AppController } from './app.controller';
|
|
4
|
+
import { AppService } from './app.service';
|
|
5
|
+
import { DatabaseModule } from './database/database.module';
|
|
6
|
+
import { AgentsModule } from './modules/agents/agents.module';
|
|
7
|
+
import { ConversationModule } from './modules/conversations/conversation.module';
|
|
8
|
+
import { FsModule } from './modules/fs/fs.module';
|
|
9
|
+
import { HarnessModule } from './modules/harness/harness.module';
|
|
10
|
+
import { SessionModule } from './modules/sessions/session.module';
|
|
11
|
+
import { TasksModule } from './modules/tasks/tasks.module';
|
|
12
|
+
|
|
13
|
+
@Module({
|
|
14
|
+
imports: [DatabaseModule, AgentsModule, ConversationModule, FsModule, HarnessModule, SessionModule, TasksModule],
|
|
15
|
+
controllers: [AppController],
|
|
16
|
+
providers: [AppService],
|
|
17
|
+
})
|
|
18
|
+
export class AppModule {}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
export const JI_HOME = path.join(os.homedir(), '.ji');
|
|
6
|
+
|
|
7
|
+
export const JI_PATHS = {
|
|
8
|
+
home: JI_HOME,
|
|
9
|
+
db: path.join(JI_HOME, 'ji.db'),
|
|
10
|
+
logs: path.join(JI_HOME, 'logs'),
|
|
11
|
+
serverLog: path.join(JI_HOME, 'logs', 'server.log'),
|
|
12
|
+
worktrees: path.join(JI_HOME, 'worktrees'),
|
|
13
|
+
patches: path.join(JI_HOME, 'patches'),
|
|
14
|
+
harness: path.join(JI_HOME, 'harness'),
|
|
15
|
+
agents: {
|
|
16
|
+
gemini: {
|
|
17
|
+
dir: path.join(JI_HOME, 'agents', 'gemini'),
|
|
18
|
+
apiKey: path.join(JI_HOME, 'agents', 'gemini', 'api-key'),
|
|
19
|
+
settings: path.join(JI_HOME, 'agents', 'gemini', 'settings.json'),
|
|
20
|
+
},
|
|
21
|
+
codex: {
|
|
22
|
+
dir: path.join(JI_HOME, 'agents', 'codex'),
|
|
23
|
+
apiKey: path.join(JI_HOME, 'agents', 'codex', 'api-key'),
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
export function ensureJiDirs(): void {
|
|
29
|
+
const dirs = [
|
|
30
|
+
JI_PATHS.home,
|
|
31
|
+
JI_PATHS.logs,
|
|
32
|
+
JI_PATHS.worktrees,
|
|
33
|
+
JI_PATHS.patches,
|
|
34
|
+
JI_PATHS.harness,
|
|
35
|
+
JI_PATHS.agents.gemini.dir,
|
|
36
|
+
JI_PATHS.agents.codex.dir,
|
|
37
|
+
];
|
|
38
|
+
for (const dir of dirs) {
|
|
39
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
3
|
+
|
|
4
|
+
import { ensureJiDirs, JI_PATHS } from '../common/ji-paths';
|
|
5
|
+
import { AgentChangelogEntity } from './entities/agent-changelog.entity';
|
|
6
|
+
import { AgentSessionEntity } from './entities/agent-session.entity';
|
|
7
|
+
import { ConversationEntity } from './entities/conversation.entity';
|
|
8
|
+
import { SessionEntity } from './entities/session.entity';
|
|
9
|
+
import { TaskAgentEntity } from './entities/task-agent.entity';
|
|
10
|
+
import { TaskAgentRunEntity } from './entities/task-agent-run.entity';
|
|
11
|
+
import { TaskRequirementEntity } from './entities/task-requirement.entity';
|
|
12
|
+
import { TaskEntity } from './entities/task.entity';
|
|
13
|
+
import { TaskRunEntity } from './entities/task-run.entity';
|
|
14
|
+
|
|
15
|
+
ensureJiDirs();
|
|
16
|
+
|
|
17
|
+
@Module({
|
|
18
|
+
imports: [
|
|
19
|
+
TypeOrmModule.forRoot({
|
|
20
|
+
type: 'better-sqlite3',
|
|
21
|
+
database: JI_PATHS.db,
|
|
22
|
+
entities: [SessionEntity, AgentSessionEntity, ConversationEntity, TaskEntity, TaskRequirementEntity, TaskAgentEntity, TaskRunEntity, TaskAgentRunEntity, AgentChangelogEntity],
|
|
23
|
+
synchronize: true,
|
|
24
|
+
}),
|
|
25
|
+
],
|
|
26
|
+
})
|
|
27
|
+
export class DatabaseModule {}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
export type ChangeType = 'added' | 'modified' | 'deleted' | 'renamed';
|
|
4
|
+
|
|
5
|
+
@Entity('agent_changelogs')
|
|
6
|
+
export class AgentChangelogEntity {
|
|
7
|
+
@PrimaryGeneratedColumn()
|
|
8
|
+
id!: number;
|
|
9
|
+
|
|
10
|
+
@Column('text')
|
|
11
|
+
taskId!: string;
|
|
12
|
+
|
|
13
|
+
@Column('integer')
|
|
14
|
+
agentId!: number;
|
|
15
|
+
|
|
16
|
+
@Column('integer', { nullable: true })
|
|
17
|
+
runId!: number | null;
|
|
18
|
+
|
|
19
|
+
@Column('text')
|
|
20
|
+
filePath!: string;
|
|
21
|
+
|
|
22
|
+
@Column('text')
|
|
23
|
+
changeType!: ChangeType;
|
|
24
|
+
|
|
25
|
+
@Column('text', { nullable: true })
|
|
26
|
+
patch!: string | null;
|
|
27
|
+
|
|
28
|
+
@Column('text', { nullable: true })
|
|
29
|
+
patchPath!: string | null;
|
|
30
|
+
|
|
31
|
+
@Column('integer', { default: 0 })
|
|
32
|
+
additions!: number;
|
|
33
|
+
|
|
34
|
+
@Column('integer', { default: 0 })
|
|
35
|
+
deletions!: number;
|
|
36
|
+
|
|
37
|
+
@CreateDateColumn()
|
|
38
|
+
createdAt!: Date;
|
|
39
|
+
}
|