@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.
Files changed (264) hide show
  1. package/.gitignore +23 -0
  2. package/.npmignore +21 -0
  3. package/.prettierignore +6 -0
  4. package/.prettierrc +26 -0
  5. package/AGENTS.md +10 -0
  6. package/CLAUDE.md +10 -0
  7. package/README.md +384 -0
  8. package/apps/server/README.md +294 -0
  9. package/apps/server/eslint.config.mjs +20 -0
  10. package/apps/server/nest-cli.json +8 -0
  11. package/apps/server/package.json +89 -0
  12. package/apps/server/scripts/postinstall.js +53 -0
  13. package/apps/server/src/__mocks__/glob.js +6 -0
  14. package/apps/server/src/__mocks__/uuid.js +5 -0
  15. package/apps/server/src/app.controller.spec.ts +24 -0
  16. package/apps/server/src/app.controller.ts +13 -0
  17. package/apps/server/src/app.module.ts +18 -0
  18. package/apps/server/src/app.service.ts +8 -0
  19. package/apps/server/src/common/ji-paths.ts +41 -0
  20. package/apps/server/src/database/database.module.ts +27 -0
  21. package/apps/server/src/database/entities/agent-changelog.entity.ts +39 -0
  22. package/apps/server/src/database/entities/agent-session.entity.ts +29 -0
  23. package/apps/server/src/database/entities/conversation.entity.ts +41 -0
  24. package/apps/server/src/database/entities/session.entity.ts +16 -0
  25. package/apps/server/src/database/entities/task-agent-run.entity.ts +40 -0
  26. package/apps/server/src/database/entities/task-agent.entity.ts +42 -0
  27. package/apps/server/src/database/entities/task-requirement.entity.ts +27 -0
  28. package/apps/server/src/database/entities/task-run.entity.ts +41 -0
  29. package/apps/server/src/database/entities/task.entity.ts +44 -0
  30. package/apps/server/src/main.ts +65 -0
  31. package/apps/server/src/modules/agents/agent-model-settings.spec.ts +80 -0
  32. package/apps/server/src/modules/agents/agents.module.ts +11 -0
  33. package/apps/server/src/modules/agents/claude/claude-auth.manager.ts +83 -0
  34. package/apps/server/src/modules/agents/claude/claude-pty.manager.ts +380 -0
  35. package/apps/server/src/modules/agents/claude/claude.controller.ts +85 -0
  36. package/apps/server/src/modules/agents/claude/claude.gateway.ts +158 -0
  37. package/apps/server/src/modules/agents/claude/claude.module.ts +18 -0
  38. package/apps/server/src/modules/agents/claude/claude.service.ts +67 -0
  39. package/apps/server/src/modules/agents/claude/dto/create-session.dto.ts +24 -0
  40. package/apps/server/src/modules/agents/claude/dto/resize-session.dto.ts +13 -0
  41. package/apps/server/src/modules/agents/claude/dto/send-input.dto.ts +9 -0
  42. package/apps/server/src/modules/agents/claude/interfaces/claude-session.interface.ts +26 -0
  43. package/apps/server/src/modules/agents/claude/interfaces/pty-event.interface.ts +10 -0
  44. package/apps/server/src/modules/agents/claude/interfaces/stream-event.interface.ts +61 -0
  45. package/apps/server/src/modules/agents/codex/codex-auth.manager.ts +107 -0
  46. package/apps/server/src/modules/agents/codex/codex-session.manager.ts +357 -0
  47. package/apps/server/src/modules/agents/codex/codex.controller.ts +64 -0
  48. package/apps/server/src/modules/agents/codex/codex.gateway.ts +97 -0
  49. package/apps/server/src/modules/agents/codex/codex.module.ts +17 -0
  50. package/apps/server/src/modules/agents/codex/dto/configure-auth.dto.ts +7 -0
  51. package/apps/server/src/modules/agents/gemini/dto/configure-auth.dto.ts +15 -0
  52. package/apps/server/src/modules/agents/gemini/dto/create-session.dto.ts +9 -0
  53. package/apps/server/src/modules/agents/gemini/dto/send-input.dto.ts +9 -0
  54. package/apps/server/src/modules/agents/gemini/gemini-auth.manager.ts +157 -0
  55. package/apps/server/src/modules/agents/gemini/gemini-session.manager.ts +287 -0
  56. package/apps/server/src/modules/agents/gemini/gemini.controller.ts +93 -0
  57. package/apps/server/src/modules/agents/gemini/gemini.gateway.ts +149 -0
  58. package/apps/server/src/modules/agents/gemini/gemini.module.ts +17 -0
  59. package/apps/server/src/modules/agents/gemini/interfaces/gemini-session.interface.ts +18 -0
  60. package/apps/server/src/modules/agents/gemini/interfaces/stream-event.interface.ts +14 -0
  61. package/apps/server/src/modules/agents/session-termination.spec.ts +103 -0
  62. package/apps/server/src/modules/changelog/changelog.controller.ts +20 -0
  63. package/apps/server/src/modules/changelog/changelog.module.ts +14 -0
  64. package/apps/server/src/modules/changelog/changelog.service.spec.ts +531 -0
  65. package/apps/server/src/modules/changelog/changelog.service.ts +690 -0
  66. package/apps/server/src/modules/conversations/conversation.controller.spec.ts +106 -0
  67. package/apps/server/src/modules/conversations/conversation.controller.ts +60 -0
  68. package/apps/server/src/modules/conversations/conversation.module.ts +14 -0
  69. package/apps/server/src/modules/conversations/conversation.service.spec.ts +176 -0
  70. package/apps/server/src/modules/conversations/conversation.service.ts +54 -0
  71. package/apps/server/src/modules/conversations/dto/create-conversation.dto.ts +37 -0
  72. package/apps/server/src/modules/conversations/enums/conversation.enum.ts +13 -0
  73. package/apps/server/src/modules/fs/fs.controller.ts +29 -0
  74. package/apps/server/src/modules/fs/fs.module.ts +8 -0
  75. package/apps/server/src/modules/harness/dto/save-harness.dto.ts +9 -0
  76. package/apps/server/src/modules/harness/harness.controller.spec.ts +95 -0
  77. package/apps/server/src/modules/harness/harness.controller.ts +35 -0
  78. package/apps/server/src/modules/harness/harness.module.ts +11 -0
  79. package/apps/server/src/modules/harness/harness.service.spec.ts +217 -0
  80. package/apps/server/src/modules/harness/harness.service.ts +112 -0
  81. package/apps/server/src/modules/sessions/session.controller.spec.ts +68 -0
  82. package/apps/server/src/modules/sessions/session.controller.ts +43 -0
  83. package/apps/server/src/modules/sessions/session.module.ts +14 -0
  84. package/apps/server/src/modules/sessions/session.service.spec.ts +106 -0
  85. package/apps/server/src/modules/sessions/session.service.ts +35 -0
  86. package/apps/server/src/modules/tasks/dto/create-task.dto.ts +54 -0
  87. package/apps/server/src/modules/tasks/dto/execute-task.dto.ts +22 -0
  88. package/apps/server/src/modules/tasks/dto/merge-file.dto.ts +7 -0
  89. package/apps/server/src/modules/tasks/dto/rerun-task.dto.ts +14 -0
  90. package/apps/server/src/modules/tasks/dto/update-task.dto.ts +55 -0
  91. package/apps/server/src/modules/tasks/task-execution.service.ts +978 -0
  92. package/apps/server/src/modules/tasks/task.gateway.ts +140 -0
  93. package/apps/server/src/modules/tasks/tasks.controller.spec.ts +210 -0
  94. package/apps/server/src/modules/tasks/tasks.controller.ts +139 -0
  95. package/apps/server/src/modules/tasks/tasks.module.ts +30 -0
  96. package/apps/server/src/modules/tasks/tasks.service.spec.ts +552 -0
  97. package/apps/server/src/modules/tasks/tasks.service.ts +333 -0
  98. package/apps/server/test/app.e2e-spec.ts +28 -0
  99. package/apps/server/test/jest-e2e.json +9 -0
  100. package/apps/server/tsconfig.build.json +4 -0
  101. package/apps/server/tsconfig.json +13 -0
  102. package/apps/web/AGENTS.md +7 -0
  103. package/apps/web/CLAUDE.md +1 -0
  104. package/apps/web/README.md +36 -0
  105. package/apps/web/eslint.config.mjs +21 -0
  106. package/apps/web/next-env.d.ts +6 -0
  107. package/apps/web/next.config.ts +7 -0
  108. package/apps/web/package.json +49 -0
  109. package/apps/web/postcss.config.mjs +7 -0
  110. package/apps/web/public/file.svg +1 -0
  111. package/apps/web/public/globe.svg +1 -0
  112. package/apps/web/public/next.svg +1 -0
  113. package/apps/web/public/vercel.svg +1 -0
  114. package/apps/web/public/window.svg +1 -0
  115. package/apps/web/src/app/claude/page.tsx +5 -0
  116. package/apps/web/src/app/codex/page.tsx +126 -0
  117. package/apps/web/src/app/favicon.ico +0 -0
  118. package/apps/web/src/app/gemini/page.tsx +130 -0
  119. package/apps/web/src/app/globals.css +149 -0
  120. package/apps/web/src/app/layout.tsx +40 -0
  121. package/apps/web/src/app/login/page.tsx +67 -0
  122. package/apps/web/src/app/page.tsx +497 -0
  123. package/apps/web/src/app/task/[id]/page.tsx +11 -0
  124. package/apps/web/src/app/test/page.tsx +298 -0
  125. package/apps/web/src/components/ui/Modal.tsx +78 -0
  126. package/apps/web/src/components/ui/WorkingDirPicker.tsx +195 -0
  127. package/apps/web/src/components/ui/__tests__/Modal.test.tsx +68 -0
  128. package/apps/web/src/features/auth/api/__tests__/auth.api.test.ts +83 -0
  129. package/apps/web/src/features/auth/api/auth.api.ts +81 -0
  130. package/apps/web/src/features/auth/hooks/__tests__/useClaudeAuth.test.ts +166 -0
  131. package/apps/web/src/features/auth/hooks/__tests__/useCodexAuth.test.ts +127 -0
  132. package/apps/web/src/features/auth/hooks/__tests__/useGeminiAuth.test.ts +120 -0
  133. package/apps/web/src/features/auth/hooks/useClaudeAuth.ts +88 -0
  134. package/apps/web/src/features/auth/hooks/useCodexAuth.ts +149 -0
  135. package/apps/web/src/features/auth/hooks/useGeminiAuth.ts +125 -0
  136. package/apps/web/src/features/auth/ui/CodexLoginPanel.tsx +302 -0
  137. package/apps/web/src/features/auth/ui/GeminiLoginPanel.tsx +316 -0
  138. package/apps/web/src/features/auth/ui/LoginForm.tsx +190 -0
  139. package/apps/web/src/features/auth/ui/LoginPanel.tsx +114 -0
  140. package/apps/web/src/features/auth/ui/__tests__/LoginPanel.test.tsx +105 -0
  141. package/apps/web/src/features/chat/api/__tests__/sessions.api.test.ts +187 -0
  142. package/apps/web/src/features/chat/api/sessions.api.ts +161 -0
  143. package/apps/web/src/features/chat/container/ClaudePageContainer.tsx +152 -0
  144. package/apps/web/src/features/chat/hooks/__tests__/useCodexSessions.test.ts +131 -0
  145. package/apps/web/src/features/chat/hooks/__tests__/useGeminiSessions.test.ts +130 -0
  146. package/apps/web/src/features/chat/hooks/useAgentModelSettings.ts +54 -0
  147. package/apps/web/src/features/chat/hooks/useClaudeSessions.ts +323 -0
  148. package/apps/web/src/features/chat/hooks/useCodexSessions.ts +275 -0
  149. package/apps/web/src/features/chat/hooks/useGeminiSessions.ts +255 -0
  150. package/apps/web/src/features/chat/hooks/useSessionCommand.ts +66 -0
  151. package/apps/web/src/features/chat/hooks/useSessionRename.ts +61 -0
  152. package/apps/web/src/features/chat/hooks/useSessionWorkingDirectories.ts +34 -0
  153. package/apps/web/src/features/chat/hooks/useUnifiedSessions.ts +156 -0
  154. package/apps/web/src/features/chat/lib/agentModelOptions.ts +72 -0
  155. package/apps/web/src/features/chat/ui/AgentModelPicker.tsx +134 -0
  156. package/apps/web/src/features/chat/ui/AgentSelectModal.tsx +236 -0
  157. package/apps/web/src/features/chat/ui/ChatInput.tsx +162 -0
  158. package/apps/web/src/features/chat/ui/ChatMessage.tsx +204 -0
  159. package/apps/web/src/features/chat/ui/ChatWorkspace.tsx +207 -0
  160. package/apps/web/src/features/chat/ui/CheckingSkeleton.tsx +44 -0
  161. package/apps/web/src/features/chat/ui/ClaudeLoginView.tsx +44 -0
  162. package/apps/web/src/features/chat/ui/PermissionCard.tsx +37 -0
  163. package/apps/web/src/features/chat/ui/SessionSidebar.tsx +280 -0
  164. package/apps/web/src/features/chat/ui/__tests__/AgentSelectModal.test.tsx +58 -0
  165. package/apps/web/src/features/chat/ui/__tests__/ChatInput.test.tsx +134 -0
  166. package/apps/web/src/features/chat/ui/__tests__/ChatMessage.test.tsx +106 -0
  167. package/apps/web/src/features/chat/ui/__tests__/ChatWorkspace.test.tsx +66 -0
  168. package/apps/web/src/features/diff/ui/DiffFileRow.tsx +73 -0
  169. package/apps/web/src/features/diff/ui/DiffHunk.tsx +61 -0
  170. package/apps/web/src/features/diff/ui/FileChangeBadge.tsx +23 -0
  171. package/apps/web/src/features/diff/ui/__tests__/DiffFileRow.test.tsx +40 -0
  172. package/apps/web/src/features/diff/ui/__tests__/DiffHunk.test.tsx +24 -0
  173. package/apps/web/src/features/diff/ui/__tests__/FileChangeBadge.test.tsx +16 -0
  174. package/apps/web/src/features/fs/api/fs.api.ts +14 -0
  175. package/apps/web/src/features/fs/hooks/useDirBrowser.ts +50 -0
  176. package/apps/web/src/features/harness/api/__tests__/harness.api.test.ts +73 -0
  177. package/apps/web/src/features/harness/api/harness.api.ts +46 -0
  178. package/apps/web/src/features/harness/hooks/__tests__/useHarness.test.ts +65 -0
  179. package/apps/web/src/features/harness/hooks/useHarness.ts +66 -0
  180. package/apps/web/src/features/harness/ui/HarnessModal.tsx +171 -0
  181. package/apps/web/src/features/harness/ui/__tests__/HarnessModal.test.tsx +46 -0
  182. package/apps/web/src/features/status/ui/AgentStatusModal.tsx +267 -0
  183. package/apps/web/src/features/status/ui/__tests__/AgentStatusModal.test.tsx +71 -0
  184. package/apps/web/src/features/tasks/api/__tests__/changelog.api.test.ts +89 -0
  185. package/apps/web/src/features/tasks/api/__tests__/tasks.api.test.ts +282 -0
  186. package/apps/web/src/features/tasks/api/changelog.api.ts +52 -0
  187. package/apps/web/src/features/tasks/api/tasks.api.ts +175 -0
  188. package/apps/web/src/features/tasks/container/TaskDetailPageContainer.tsx +69 -0
  189. package/apps/web/src/features/tasks/hooks/__tests__/useChangelogCodeCopy.test.ts +48 -0
  190. package/apps/web/src/features/tasks/hooks/__tests__/useTaskChangelog.test.ts +48 -0
  191. package/apps/web/src/features/tasks/hooks/__tests__/useTaskCreate.test.ts +217 -0
  192. package/apps/web/src/features/tasks/hooks/__tests__/useTaskEdit.test.ts +152 -0
  193. package/apps/web/src/features/tasks/hooks/__tests__/useTaskExecution.test.ts +143 -0
  194. package/apps/web/src/features/tasks/hooks/__tests__/useTaskList.test.ts +168 -0
  195. package/apps/web/src/features/tasks/hooks/__tests__/useTaskNotification.test.ts +125 -0
  196. package/apps/web/src/features/tasks/hooks/__tests__/useTaskRuns.test.ts +51 -0
  197. package/apps/web/src/features/tasks/hooks/useChangelogCodeCopy.ts +52 -0
  198. package/apps/web/src/features/tasks/hooks/useCopyToClipboard.ts +47 -0
  199. package/apps/web/src/features/tasks/hooks/useTaskChangelog.ts +32 -0
  200. package/apps/web/src/features/tasks/hooks/useTaskCreate.ts +137 -0
  201. package/apps/web/src/features/tasks/hooks/useTaskDetail.ts +217 -0
  202. package/apps/web/src/features/tasks/hooks/useTaskEdit.ts +130 -0
  203. package/apps/web/src/features/tasks/hooks/useTaskExecution.ts +137 -0
  204. package/apps/web/src/features/tasks/hooks/useTaskList.ts +159 -0
  205. package/apps/web/src/features/tasks/hooks/useTaskNotification.ts +80 -0
  206. package/apps/web/src/features/tasks/hooks/useTaskRuns.ts +32 -0
  207. package/apps/web/src/features/tasks/ui/AgentOutputPanel.tsx +203 -0
  208. package/apps/web/src/features/tasks/ui/AgentRoleSelect.tsx +97 -0
  209. package/apps/web/src/features/tasks/ui/ChangelogPanel.tsx +321 -0
  210. package/apps/web/src/features/tasks/ui/RunHistoryPanel.tsx +193 -0
  211. package/apps/web/src/features/tasks/ui/TaskCreateModal.tsx +205 -0
  212. package/apps/web/src/features/tasks/ui/TaskDetailView.tsx +413 -0
  213. package/apps/web/src/features/tasks/ui/TaskEditModal.tsx +165 -0
  214. package/apps/web/src/features/tasks/ui/TaskListModal.tsx +591 -0
  215. package/apps/web/src/features/tasks/ui/__tests__/AgentRoleSelect.test.tsx +91 -0
  216. package/apps/web/src/features/tasks/ui/__tests__/ChangelogPanel.test.tsx +94 -0
  217. package/apps/web/src/features/tasks/ui/__tests__/RunHistoryPanel.test.tsx +71 -0
  218. package/apps/web/src/features/tasks/ui/__tests__/TaskCreateModal.test.tsx +153 -0
  219. package/apps/web/src/features/tasks/ui/__tests__/TaskEditModal.test.tsx +75 -0
  220. package/apps/web/src/features/tasks/ui/__tests__/TaskListModal.test.tsx +243 -0
  221. package/apps/web/src/hooks/useWorkingDir.ts +28 -0
  222. package/apps/web/src/lib/__tests__/ansi.test.ts +88 -0
  223. package/apps/web/src/lib/ansi.ts +105 -0
  224. package/apps/web/src/lib/constants.ts +4 -0
  225. package/apps/web/src/lib/quota.ts +22 -0
  226. package/apps/web/src/lib/theme.tsx +78 -0
  227. package/apps/web/src/lib/toast.tsx +175 -0
  228. package/apps/web/src/store/agentStatusStore.ts +38 -0
  229. package/apps/web/tsconfig.json +18 -0
  230. package/apps/web/vitest.config.ts +25 -0
  231. package/apps/web/vitest.setup.ts +10 -0
  232. package/package.json +85 -0
  233. package/packages/cli/dist/commands/check.d.ts +1 -0
  234. package/packages/cli/dist/commands/check.js +89 -0
  235. package/packages/cli/dist/commands/init.d.ts +5 -0
  236. package/packages/cli/dist/commands/init.js +183 -0
  237. package/packages/cli/dist/commands/start.d.ts +4 -0
  238. package/packages/cli/dist/commands/start.js +188 -0
  239. package/packages/cli/dist/index.d.ts +2 -0
  240. package/packages/cli/dist/index.js +71 -0
  241. package/packages/cli/dist/utils/agent-tools.d.ts +28 -0
  242. package/packages/cli/dist/utils/agent-tools.js +193 -0
  243. package/packages/cli/dist/utils/project-init.d.ts +12 -0
  244. package/packages/cli/dist/utils/project-init.js +258 -0
  245. package/packages/cli/dist/utils/proxy.d.ts +8 -0
  246. package/packages/cli/dist/utils/proxy.js +138 -0
  247. package/packages/cli/package.json +30 -0
  248. package/packages/cli/src/commands/check.ts +77 -0
  249. package/packages/cli/src/commands/init.ts +209 -0
  250. package/packages/cli/src/commands/start.ts +183 -0
  251. package/packages/cli/src/index.ts +91 -0
  252. package/packages/cli/src/utils/agent-tools.ts +201 -0
  253. package/packages/cli/src/utils/project-init.ts +252 -0
  254. package/packages/cli/src/utils/proxy.ts +123 -0
  255. package/packages/cli/tsconfig.json +14 -0
  256. package/packages/eslint-config/base.mjs +31 -0
  257. package/packages/eslint-config/nest.mjs +55 -0
  258. package/packages/eslint-config/next.mjs +23 -0
  259. package/packages/eslint-config/package.json +20 -0
  260. package/packages/typescript-config/base.json +16 -0
  261. package/packages/typescript-config/nestjs.json +17 -0
  262. package/packages/typescript-config/nextjs.json +15 -0
  263. package/packages/typescript-config/package.json +11 -0
  264. 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,8 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/nest-cli",
3
+ "collection": "@nestjs/schematics",
4
+ "sourceRoot": "src",
5
+ "compilerOptions": {
6
+ "deleteOutDir": true
7
+ }
8
+ }
@@ -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,6 @@
1
+ // glob을 mock하여 typeorm의 DirectoryExportedClassesLoader가 path-scurry(ESM)를 로드하지 못하도록 방지
2
+ module.exports = {
3
+ glob: jest.fn().mockResolvedValue([]),
4
+ globSync: jest.fn().mockReturnValue([]),
5
+ sync: jest.fn().mockReturnValue([]),
6
+ };
@@ -0,0 +1,5 @@
1
+ let counter = 0;
2
+
3
+ module.exports = {
4
+ v4: jest.fn(() => `mock-uuid-${++counter}`),
5
+ };
@@ -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,8 @@
1
+ import { Injectable } from '@nestjs/common';
2
+
3
+ @Injectable()
4
+ export class AppService {
5
+ getHello(): string {
6
+ return 'Hello World!';
7
+ }
8
+ }
@@ -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
+ }