automagik-forge 0.1.13 → 0.1.14

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 (301) hide show
  1. package/README.md +143 -447
  2. package/dist/linux-x64/automagik-forge-mcp.zip +0 -0
  3. package/{npx-cli/automagik-forge-0.0.55.tgz → dist/linux-x64/automagik-forge.zip} +0 -0
  4. package/package.json +13 -23
  5. package/.cargo/config.toml +0 -13
  6. package/.claude/commands/commit.md +0 -376
  7. package/.claude/commands/prompt.md +0 -871
  8. package/.env.example +0 -20
  9. package/.github/actions/setup-node/action.yml +0 -29
  10. package/.github/images/automagik-logo.png +0 -0
  11. package/.github/workflows/pre-release.yml +0 -470
  12. package/.github/workflows/publish.yml +0 -145
  13. package/.github/workflows/test.yml +0 -63
  14. package/.mcp.json +0 -57
  15. package/AGENT.md +0 -40
  16. package/CLAUDE.md +0 -40
  17. package/CODE-OF-CONDUCT.md +0 -89
  18. package/Cargo.toml +0 -19
  19. package/Dockerfile +0 -43
  20. package/LICENSE +0 -201
  21. package/Makefile +0 -97
  22. package/backend/.sqlx/query-01b7e2bac1261d8be3d03c03df3e5220590da6c31c77f161074fc62752d63881.json +0 -12
  23. package/backend/.sqlx/query-03f2b02ba6dc5ea2b3cf6b1004caea0ad6bcc10ebd63f441d321a389f026e263.json +0 -12
  24. package/backend/.sqlx/query-0923b77d137a29fc54d399a873ff15fc4af894490bc65a4d344a7575cb0d8643.json +0 -12
  25. package/backend/.sqlx/query-0f808bcdb63c5f180836e448dd64c435c51758b2fc54a52ce9e67495b1ab200e.json +0 -68
  26. package/backend/.sqlx/query-1268afe9ca849daa6722e3df7ca8e9e61f0d37052e782bb5452ab8e1018d9b63.json +0 -12
  27. package/backend/.sqlx/query-1b082630a9622f8667ee7a9aba2c2d3176019a68c6bb83d33008594821415a57.json +0 -12
  28. package/backend/.sqlx/query-1c7b06ba1e112abf6b945a2ff08a0b40ec23f3738c2e7399f067b558cf8d490e.json +0 -12
  29. package/backend/.sqlx/query-1f619f01f46859a64ded531dd0ef61abacfe62e758abe7030a6aa745140b95ca.json +0 -104
  30. package/backend/.sqlx/query-1fca1ce14b4b20205364cd1f1f45ebe1d2e30cd745e59e189d56487b5639dfbb.json +0 -12
  31. package/backend/.sqlx/query-212828320e8d871ab9d83705a040b23bcf0393dc7252177fc539a74657f578ef.json +0 -32
  32. package/backend/.sqlx/query-290ce5c152be8d36e58ff42570f9157beb07ab9e77a03ec6fc30b4f56f9b8f6b.json +0 -56
  33. package/backend/.sqlx/query-2b471d2c2e8ffbe0cd42d2a91b814c0d79f9d09200f147e3cea33ba4ce673c8a.json +0 -68
  34. package/backend/.sqlx/query-354a48c705bb9bb2048c1b7f10fcb714e23f9db82b7a4ea6932486197b2ede6a.json +0 -92
  35. package/backend/.sqlx/query-36c9e3dd10648e94b949db5c91a774ecb1e10a899ef95da74066eccedca4d8b2.json +0 -12
  36. package/backend/.sqlx/query-36e4ba7bbd81b402d5a20b6005755eafbb174c8dda442081823406ac32809a94.json +0 -56
  37. package/backend/.sqlx/query-3a5b3c98a55ca183ab20c74708e3d7e579dda37972c059e7515c4ceee4bd8dd3.json +0 -62
  38. package/backend/.sqlx/query-3d0a1cabf2a52e9d90cdfd29c509ca89aeb448d0c1d2446c65cd43db40735e86.json +0 -62
  39. package/backend/.sqlx/query-3d6bd16fbce59efe30b7f67ea342e0e4ea6d1432389c02468ad79f1f742d4031.json +0 -56
  40. package/backend/.sqlx/query-4049ca413b285a05aca6b25385e9c8185575f01e9069e4e8581aa45d713f612f.json +0 -32
  41. package/backend/.sqlx/query-412bacd3477d86369082e90f52240407abce436cb81292d42b2dbe1e5c18eea1.json +0 -104
  42. package/backend/.sqlx/query-417a8b1ff4e51de82aea0159a3b97932224dc325b23476cb84153d690227fd8b.json +0 -62
  43. package/backend/.sqlx/query-461cc1b0bb6fd909afc9dd2246e8526b3771cfbb0b22ae4b5d17b51af587b9e2.json +0 -56
  44. package/backend/.sqlx/query-58408c7a8cdeeda0bef359f1f9bd91299a339dc2b191462fc58c9736a56d5227.json +0 -92
  45. package/backend/.sqlx/query-5a886026d75d515c01f347cc203c8d99dd04c61dc468e2e4c5aa548436d13834.json +0 -62
  46. package/backend/.sqlx/query-5b902137b11022d2e1a5c4f6a9c83fec1a856c6a710aff831abd2382ede76b43.json +0 -12
  47. package/backend/.sqlx/query-5ed1238e52e59bb5f76c0f153fd99a14093f7ce2585bf9843585608f17ec575b.json +0 -104
  48. package/backend/.sqlx/query-6e8b860b14decfc2227dc57213f38442943d3fbef5c8418fd6b634c6e0f5e2ea.json +0 -104
  49. package/backend/.sqlx/query-6ec414276994c4ccb2433eaa5b1b342168557d17ddf5a52dac84cb1b59b9de8f.json +0 -68
  50. package/backend/.sqlx/query-6ecfa16d0cf825aacf233544b5baf151e9adfdca26c226ad71020d291fd802d5.json +0 -62
  51. package/backend/.sqlx/query-72509d252c39fce77520aa816cb2acbc1fb35dc2605e7be893610599b2427f2e.json +0 -62
  52. package/backend/.sqlx/query-75239b2da188f749707d77f3c1544332ca70db3d6d6743b2601dc0d167536437.json +0 -62
  53. package/backend/.sqlx/query-83d10e29f8478aff33434f9ac67068e013b888b953a2657e2bb72a6f619d04f2.json +0 -50
  54. package/backend/.sqlx/query-8610803360ea18b9b9d078a6981ea56abfbfe84e6354fc1d5ae4c622e01410ed.json +0 -68
  55. package/backend/.sqlx/query-86d03eb70eef39c59296416867f2ee66c9f7cd8b7f961fbda2f89fc0a1c442c2.json +0 -12
  56. package/backend/.sqlx/query-87d0feb5a6b442bad9c60068ea7569599cc6fc91a0e2692ecb42e93b03201b9d.json +0 -68
  57. package/backend/.sqlx/query-8a67b3b3337248f06a57bdf8a908f7ef23177431eaed82dc08c94c3e5944340e.json +0 -12
  58. package/backend/.sqlx/query-8f01ebd64bdcde6a090479f14810d73ba23020e76fd70854ac57f2da251702c3.json +0 -12
  59. package/backend/.sqlx/query-90fd607fcb2dca72239ff25e618e21e174b195991eaa33722cbf5f76da84cfab.json +0 -62
  60. package/backend/.sqlx/query-92e8bdbcd80c5ff3db7a35cf79492048803ef305cbdef0d0a1fe5dc881ca8c71.json +0 -104
  61. package/backend/.sqlx/query-93a1605f90e9672dad29b472b6ad85fa9a55ea3ffa5abcb8724b09d61be254ca.json +0 -20
  62. package/backend/.sqlx/query-9472c8fb477958167f5fae40b85ac44252468c5226b2cdd7770f027332eed6d7.json +0 -104
  63. package/backend/.sqlx/query-96036c4f9e0f48bdc5a4a4588f0c5f288ac7aaa5425cac40fc33f337e1a351f2.json +0 -56
  64. package/backend/.sqlx/query-9edb2c01e91fd0f0fe7b56e988c7ae0393150f50be3f419a981e035c0121dfc7.json +0 -104
  65. package/backend/.sqlx/query-a157cf00616f703bfba21927f1eb1c9eec2a81c02da15f66efdba0b6c375de1b.json +0 -26
  66. package/backend/.sqlx/query-a31fff84f3b8e532fd1160447d89d700f06ae08821fee00c9a5b60492b05259c.json +0 -62
  67. package/backend/.sqlx/query-a5ba908419fb3e456bdd2daca41ba06cc3212ffffb8520fc7dbbcc8b60ada314.json +0 -12
  68. package/backend/.sqlx/query-a6d2961718dbc3b1a925e549f49a159c561bef58c105529275f274b27e2eba5b.json +0 -104
  69. package/backend/.sqlx/query-a9e93d5b09b29faf66e387e4d7596a792d81e75c4d3726e83c2963e8d7c9b56f.json +0 -104
  70. package/backend/.sqlx/query-ac5247c8d7fb86e4650c4b0eb9420031614c831b7b085083bac20c1af314c538.json +0 -12
  71. package/backend/.sqlx/query-afef9467be74c411c4cb119a8b2b1aea53049877dfc30cc60b486134ba4b4c9f.json +0 -68
  72. package/backend/.sqlx/query-b2b2c6b4d0b1a347b5c4cb63c3a46a265d4db53be9554989a814b069d0af82f2.json +0 -62
  73. package/backend/.sqlx/query-c50d2ff0b12e5bcc81e371089ee2d007e233e7db93aefba4fef08e7aa68f5ab7.json +0 -20
  74. package/backend/.sqlx/query-c614e6056b244ca07f1b9d44e7edc9d5819225c6f8d9e077070c6e518a17f50b.json +0 -12
  75. package/backend/.sqlx/query-c67259be8bf4ee0cfd32167b2aa3b7fe9192809181a8171bf1c2d6df731967ae.json +0 -12
  76. package/backend/.sqlx/query-d2d0a1b985ebbca6a2b3e882a221a219f3199890fa640afc946ef1a792d6d8de.json +0 -12
  77. package/backend/.sqlx/query-d30aa5786757f32bf2b9c5fe51a45e506c71c28c5994e430d9b0546adb15ffa2.json +0 -20
  78. package/backend/.sqlx/query-d3b9ea1de1576af71b312924ce7f4ea8ae5dbe2ac138ea3b4470f2d5cd734846.json +0 -12
  79. package/backend/.sqlx/query-ed8456646fa69ddd412441955f06ff22bfb790f29466450735e0b8bb1bc4ec94.json +0 -12
  80. package/backend/Cargo.toml +0 -71
  81. package/backend/build.rs +0 -32
  82. package/backend/migrations/20250617183714_init.sql +0 -44
  83. package/backend/migrations/20250620212427_execution_processes.sql +0 -25
  84. package/backend/migrations/20250620214100_remove_stdout_stderr_from_task_attempts.sql +0 -28
  85. package/backend/migrations/20250621120000_relate_activities_to_execution_processes.sql +0 -23
  86. package/backend/migrations/20250623120000_executor_sessions.sql +0 -17
  87. package/backend/migrations/20250623130000_add_executor_type_to_execution_processes.sql +0 -4
  88. package/backend/migrations/20250625000000_add_dev_script_to_projects.sql +0 -4
  89. package/backend/migrations/20250701000000_add_branch_to_task_attempts.sql +0 -2
  90. package/backend/migrations/20250701000001_add_pr_tracking_to_task_attempts.sql +0 -5
  91. package/backend/migrations/20250701120000_add_assistant_message_to_executor_sessions.sql +0 -2
  92. package/backend/migrations/20250708000000_add_base_branch_to_task_attempts.sql +0 -2
  93. package/backend/migrations/20250709000000_add_worktree_deleted_flag.sql +0 -2
  94. package/backend/migrations/20250710000000_add_setup_completion.sql +0 -3
  95. package/backend/migrations/20250715154859_add_task_templates.sql +0 -25
  96. package/backend/migrations/20250716143725_add_default_templates.sql +0 -174
  97. package/backend/migrations/20250716161432_update_executor_names_to_kebab_case.sql +0 -20
  98. package/backend/migrations/20250716170000_add_parent_task_to_tasks.sql +0 -7
  99. package/backend/migrations/20250717000000_drop_task_attempt_activities.sql +0 -9
  100. package/backend/migrations/20250719000000_add_cleanup_script_to_projects.sql +0 -2
  101. package/backend/migrations/20250720000000_add_cleanupscript_to_process_type_constraint.sql +0 -25
  102. package/backend/migrations/20250723000000_add_wish_to_tasks.sql +0 -7
  103. package/backend/migrations/20250724000000_remove_unique_wish_constraint.sql +0 -5
  104. package/backend/scripts/toast-notification.ps1 +0 -23
  105. package/backend/sounds/abstract-sound1.wav +0 -0
  106. package/backend/sounds/abstract-sound2.wav +0 -0
  107. package/backend/sounds/abstract-sound3.wav +0 -0
  108. package/backend/sounds/abstract-sound4.wav +0 -0
  109. package/backend/sounds/cow-mooing.wav +0 -0
  110. package/backend/sounds/phone-vibration.wav +0 -0
  111. package/backend/sounds/rooster.wav +0 -0
  112. package/backend/src/app_state.rs +0 -218
  113. package/backend/src/bin/generate_types.rs +0 -189
  114. package/backend/src/bin/mcp_task_server.rs +0 -191
  115. package/backend/src/execution_monitor.rs +0 -1193
  116. package/backend/src/executor.rs +0 -1053
  117. package/backend/src/executors/amp.rs +0 -697
  118. package/backend/src/executors/ccr.rs +0 -91
  119. package/backend/src/executors/charm_opencode.rs +0 -113
  120. package/backend/src/executors/claude.rs +0 -887
  121. package/backend/src/executors/cleanup_script.rs +0 -124
  122. package/backend/src/executors/dev_server.rs +0 -53
  123. package/backend/src/executors/echo.rs +0 -79
  124. package/backend/src/executors/gemini/config.rs +0 -67
  125. package/backend/src/executors/gemini/streaming.rs +0 -363
  126. package/backend/src/executors/gemini.rs +0 -765
  127. package/backend/src/executors/mod.rs +0 -23
  128. package/backend/src/executors/opencode_ai.rs +0 -113
  129. package/backend/src/executors/setup_script.rs +0 -130
  130. package/backend/src/executors/sst_opencode/filter.rs +0 -184
  131. package/backend/src/executors/sst_opencode/tools.rs +0 -139
  132. package/backend/src/executors/sst_opencode.rs +0 -756
  133. package/backend/src/lib.rs +0 -45
  134. package/backend/src/main.rs +0 -324
  135. package/backend/src/mcp/mod.rs +0 -1
  136. package/backend/src/mcp/task_server.rs +0 -850
  137. package/backend/src/middleware/mod.rs +0 -3
  138. package/backend/src/middleware/model_loaders.rs +0 -242
  139. package/backend/src/models/api_response.rs +0 -36
  140. package/backend/src/models/config.rs +0 -375
  141. package/backend/src/models/execution_process.rs +0 -430
  142. package/backend/src/models/executor_session.rs +0 -225
  143. package/backend/src/models/mod.rs +0 -12
  144. package/backend/src/models/project.rs +0 -356
  145. package/backend/src/models/task.rs +0 -345
  146. package/backend/src/models/task_attempt.rs +0 -1214
  147. package/backend/src/models/task_template.rs +0 -146
  148. package/backend/src/openapi.rs +0 -93
  149. package/backend/src/routes/auth.rs +0 -297
  150. package/backend/src/routes/config.rs +0 -385
  151. package/backend/src/routes/filesystem.rs +0 -228
  152. package/backend/src/routes/health.rs +0 -16
  153. package/backend/src/routes/mod.rs +0 -9
  154. package/backend/src/routes/projects.rs +0 -562
  155. package/backend/src/routes/stream.rs +0 -244
  156. package/backend/src/routes/task_attempts.rs +0 -1172
  157. package/backend/src/routes/task_templates.rs +0 -229
  158. package/backend/src/routes/tasks.rs +0 -353
  159. package/backend/src/services/analytics.rs +0 -216
  160. package/backend/src/services/git_service.rs +0 -1321
  161. package/backend/src/services/github_service.rs +0 -307
  162. package/backend/src/services/mod.rs +0 -13
  163. package/backend/src/services/notification_service.rs +0 -263
  164. package/backend/src/services/pr_monitor.rs +0 -214
  165. package/backend/src/services/process_service.rs +0 -940
  166. package/backend/src/utils/path.rs +0 -96
  167. package/backend/src/utils/shell.rs +0 -19
  168. package/backend/src/utils/text.rs +0 -24
  169. package/backend/src/utils/worktree_manager.rs +0 -578
  170. package/backend/src/utils.rs +0 -125
  171. package/backend/test.db +0 -0
  172. package/build-npm-package.sh +0 -61
  173. package/dev_assets_seed/config.json +0 -19
  174. package/frontend/.eslintrc.json +0 -25
  175. package/frontend/.prettierrc.json +0 -8
  176. package/frontend/components.json +0 -17
  177. package/frontend/index.html +0 -19
  178. package/frontend/package-lock.json +0 -7321
  179. package/frontend/package.json +0 -61
  180. package/frontend/postcss.config.js +0 -6
  181. package/frontend/public/android-chrome-192x192.png +0 -0
  182. package/frontend/public/android-chrome-512x512.png +0 -0
  183. package/frontend/public/apple-touch-icon.png +0 -0
  184. package/frontend/public/automagik-forge-logo-dark.svg +0 -3
  185. package/frontend/public/automagik-forge-logo.svg +0 -3
  186. package/frontend/public/automagik-forge-screenshot-overview.png +0 -0
  187. package/frontend/public/favicon-16x16.png +0 -0
  188. package/frontend/public/favicon-32x32.png +0 -0
  189. package/frontend/public/favicon.ico +0 -0
  190. package/frontend/public/site.webmanifest +0 -1
  191. package/frontend/public/viba-kanban-favicon.png +0 -0
  192. package/frontend/src/App.tsx +0 -157
  193. package/frontend/src/components/DisclaimerDialog.tsx +0 -106
  194. package/frontend/src/components/GitHubLoginDialog.tsx +0 -314
  195. package/frontend/src/components/OnboardingDialog.tsx +0 -185
  196. package/frontend/src/components/PrivacyOptInDialog.tsx +0 -130
  197. package/frontend/src/components/ProvidePatDialog.tsx +0 -98
  198. package/frontend/src/components/TaskTemplateManager.tsx +0 -336
  199. package/frontend/src/components/config-provider.tsx +0 -119
  200. package/frontend/src/components/context/TaskDetailsContextProvider.tsx +0 -470
  201. package/frontend/src/components/context/taskDetailsContext.ts +0 -125
  202. package/frontend/src/components/keyboard-shortcuts-demo.tsx +0 -35
  203. package/frontend/src/components/layout/navbar.tsx +0 -86
  204. package/frontend/src/components/logo.tsx +0 -44
  205. package/frontend/src/components/projects/ProjectCard.tsx +0 -155
  206. package/frontend/src/components/projects/project-detail.tsx +0 -251
  207. package/frontend/src/components/projects/project-form-fields.tsx +0 -238
  208. package/frontend/src/components/projects/project-form.tsx +0 -301
  209. package/frontend/src/components/projects/project-list.tsx +0 -200
  210. package/frontend/src/components/projects/projects-page.tsx +0 -20
  211. package/frontend/src/components/tasks/BranchSelector.tsx +0 -169
  212. package/frontend/src/components/tasks/DeleteFileConfirmationDialog.tsx +0 -94
  213. package/frontend/src/components/tasks/EditorSelectionDialog.tsx +0 -119
  214. package/frontend/src/components/tasks/TaskCard.tsx +0 -154
  215. package/frontend/src/components/tasks/TaskDetails/CollapsibleToolbar.tsx +0 -33
  216. package/frontend/src/components/tasks/TaskDetails/DiffCard.tsx +0 -109
  217. package/frontend/src/components/tasks/TaskDetails/DiffChunkSection.tsx +0 -135
  218. package/frontend/src/components/tasks/TaskDetails/DiffFile.tsx +0 -296
  219. package/frontend/src/components/tasks/TaskDetails/DiffTab.tsx +0 -32
  220. package/frontend/src/components/tasks/TaskDetails/DisplayConversationEntry.tsx +0 -392
  221. package/frontend/src/components/tasks/TaskDetails/LogsTab/Conversation.tsx +0 -256
  222. package/frontend/src/components/tasks/TaskDetails/LogsTab/ConversationEntry.tsx +0 -56
  223. package/frontend/src/components/tasks/TaskDetails/LogsTab/NormalizedConversationViewer.tsx +0 -92
  224. package/frontend/src/components/tasks/TaskDetails/LogsTab/Prompt.tsx +0 -22
  225. package/frontend/src/components/tasks/TaskDetails/LogsTab/SetupScriptRunning.tsx +0 -49
  226. package/frontend/src/components/tasks/TaskDetails/LogsTab.tsx +0 -186
  227. package/frontend/src/components/tasks/TaskDetails/ProcessesTab.tsx +0 -288
  228. package/frontend/src/components/tasks/TaskDetails/RelatedTasksTab.tsx +0 -216
  229. package/frontend/src/components/tasks/TaskDetails/TabNavigation.tsx +0 -93
  230. package/frontend/src/components/tasks/TaskDetailsHeader.tsx +0 -169
  231. package/frontend/src/components/tasks/TaskDetailsPanel.tsx +0 -126
  232. package/frontend/src/components/tasks/TaskDetailsToolbar.tsx +0 -302
  233. package/frontend/src/components/tasks/TaskFollowUpSection.tsx +0 -130
  234. package/frontend/src/components/tasks/TaskFormDialog.tsx +0 -400
  235. package/frontend/src/components/tasks/TaskKanbanBoard.tsx +0 -180
  236. package/frontend/src/components/tasks/Toolbar/CreateAttempt.tsx +0 -259
  237. package/frontend/src/components/tasks/Toolbar/CreatePRDialog.tsx +0 -243
  238. package/frontend/src/components/tasks/Toolbar/CurrentAttempt.tsx +0 -899
  239. package/frontend/src/components/tasks/index.ts +0 -2
  240. package/frontend/src/components/theme-provider.tsx +0 -82
  241. package/frontend/src/components/theme-toggle.tsx +0 -36
  242. package/frontend/src/components/ui/alert.tsx +0 -59
  243. package/frontend/src/components/ui/auto-expanding-textarea.tsx +0 -70
  244. package/frontend/src/components/ui/badge.tsx +0 -36
  245. package/frontend/src/components/ui/button.tsx +0 -56
  246. package/frontend/src/components/ui/card.tsx +0 -86
  247. package/frontend/src/components/ui/checkbox.tsx +0 -44
  248. package/frontend/src/components/ui/chip.tsx +0 -25
  249. package/frontend/src/components/ui/dialog.tsx +0 -124
  250. package/frontend/src/components/ui/dropdown-menu.tsx +0 -198
  251. package/frontend/src/components/ui/file-search-textarea.tsx +0 -292
  252. package/frontend/src/components/ui/folder-picker.tsx +0 -279
  253. package/frontend/src/components/ui/input.tsx +0 -25
  254. package/frontend/src/components/ui/label.tsx +0 -24
  255. package/frontend/src/components/ui/loader.tsx +0 -26
  256. package/frontend/src/components/ui/markdown-renderer.tsx +0 -75
  257. package/frontend/src/components/ui/select.tsx +0 -160
  258. package/frontend/src/components/ui/separator.tsx +0 -31
  259. package/frontend/src/components/ui/shadcn-io/kanban/index.tsx +0 -185
  260. package/frontend/src/components/ui/table.tsx +0 -117
  261. package/frontend/src/components/ui/tabs.tsx +0 -53
  262. package/frontend/src/components/ui/textarea.tsx +0 -22
  263. package/frontend/src/components/ui/tooltip.tsx +0 -28
  264. package/frontend/src/hooks/useNormalizedConversation.ts +0 -440
  265. package/frontend/src/index.css +0 -225
  266. package/frontend/src/lib/api.ts +0 -630
  267. package/frontend/src/lib/keyboard-shortcuts.ts +0 -266
  268. package/frontend/src/lib/responsive-config.ts +0 -70
  269. package/frontend/src/lib/types.ts +0 -39
  270. package/frontend/src/lib/utils.ts +0 -10
  271. package/frontend/src/main.tsx +0 -50
  272. package/frontend/src/pages/McpServers.tsx +0 -418
  273. package/frontend/src/pages/Settings.tsx +0 -610
  274. package/frontend/src/pages/project-tasks.tsx +0 -575
  275. package/frontend/src/pages/projects.tsx +0 -18
  276. package/frontend/src/vite-env.d.ts +0 -1
  277. package/frontend/tailwind.config.js +0 -125
  278. package/frontend/tsconfig.json +0 -26
  279. package/frontend/tsconfig.node.json +0 -10
  280. package/frontend/vite.config.ts +0 -33
  281. package/npx-cli/README.md +0 -159
  282. package/npx-cli/automagik-forge-0.1.0.tgz +0 -0
  283. package/npx-cli/automagik-forge-0.1.10.tgz +0 -0
  284. package/npx-cli/package.json +0 -17
  285. package/npx-cli/vibe-kanban-0.0.55.tgz +0 -0
  286. package/pnpm-workspace.yaml +0 -2
  287. package/rust-toolchain.toml +0 -11
  288. package/rustfmt.toml +0 -3
  289. package/scripts/load-env.js +0 -43
  290. package/scripts/mcp_test.js +0 -374
  291. package/scripts/prepare-db.js +0 -45
  292. package/scripts/setup-dev-environment.js +0 -274
  293. package/scripts/start-mcp-sse.js +0 -70
  294. package/scripts/test-debug.js +0 -32
  295. package/scripts/test-mcp-sse.js +0 -138
  296. package/scripts/test-simple.js +0 -44
  297. package/scripts/test-wish-final.js +0 -179
  298. package/scripts/test-wish-system.js +0 -221
  299. package/shared/types.ts +0 -182
  300. package/test-npm-package.sh +0 -42
  301. /package/{npx-cli/bin → bin}/cli.js +0 -0
@@ -1,756 +0,0 @@
1
- use async_trait::async_trait;
2
- use command_group::{AsyncCommandGroup, AsyncGroupChild};
3
- use serde_json::{json, Value};
4
- use tokio::{
5
- io::{AsyncBufReadExt, BufReader},
6
- process::Command,
7
- };
8
- use uuid::Uuid;
9
-
10
- use crate::{
11
- executor::{Executor, ExecutorError, NormalizedConversation, NormalizedEntry},
12
- models::{execution_process::ExecutionProcess, executor_session::ExecutorSession, task::Task},
13
- utils::shell::get_shell_command,
14
- };
15
-
16
- // Sub-modules for utilities
17
- pub mod filter;
18
- pub mod tools;
19
-
20
- use self::{
21
- filter::{parse_session_id_from_line, tool_usage_regex, OpenCodeFilter},
22
- tools::{determine_action_type, generate_tool_content, normalize_tool_name},
23
- };
24
-
25
- struct Content {
26
- pub stdout: Option<String>,
27
- pub stderr: Option<String>,
28
- }
29
-
30
- /// Process a single line for session extraction and content formatting
31
- async fn process_line_for_content(
32
- line: &str,
33
- session_extracted: &mut bool,
34
- worktree_path: &str,
35
- pool: &sqlx::SqlitePool,
36
- execution_process_id: uuid::Uuid,
37
- ) -> Option<Content> {
38
- if !*session_extracted {
39
- if let Some(session_id) = parse_session_id_from_line(line) {
40
- if let Err(e) =
41
- ExecutorSession::update_session_id(pool, execution_process_id, &session_id).await
42
- {
43
- tracing::error!(
44
- "Failed to update session ID for execution process {}: {}",
45
- execution_process_id,
46
- e
47
- );
48
- } else {
49
- tracing::info!(
50
- "Updated session ID {} for execution process {}",
51
- session_id,
52
- execution_process_id
53
- );
54
- *session_extracted = true;
55
- }
56
-
57
- // Don't return any content for session lines
58
- return None;
59
- }
60
- }
61
-
62
- // Check if line is noise - if so, discard it
63
- if OpenCodeFilter::is_noise(line) {
64
- return None;
65
- }
66
-
67
- if OpenCodeFilter::is_stderr(line) {
68
- // If it's stderr, we don't need to process it further
69
- return Some(Content {
70
- stdout: None,
71
- stderr: Some(line.to_string()),
72
- });
73
- }
74
-
75
- // Format clean content as normalized JSON
76
- let formatted = format_opencode_content_as_normalized_json(line, worktree_path);
77
- Some(Content {
78
- stdout: Some(formatted),
79
- stderr: None,
80
- })
81
- }
82
-
83
- /// Stream stderr from OpenCode process with filtering to separate clean output from noise
84
- pub async fn stream_opencode_stderr_to_db(
85
- output: impl tokio::io::AsyncRead + Unpin,
86
- pool: sqlx::SqlitePool,
87
- attempt_id: Uuid,
88
- execution_process_id: Uuid,
89
- worktree_path: String,
90
- ) {
91
- let mut reader = BufReader::new(output);
92
- let mut line = String::new();
93
- let mut session_extracted = false;
94
-
95
- loop {
96
- line.clear();
97
-
98
- match reader.read_line(&mut line).await {
99
- Ok(0) => break, // EOF
100
- Ok(_) => {
101
- line = line.trim_end_matches(['\r', '\n']).to_string();
102
-
103
- let content = process_line_for_content(
104
- &line,
105
- &mut session_extracted,
106
- &worktree_path,
107
- &pool,
108
- execution_process_id,
109
- )
110
- .await;
111
-
112
- if let Some(Content { stdout, stderr }) = content {
113
- tracing::debug!(
114
- "Processed OpenCode content for attempt {}: stdout={:?} stderr={:?}",
115
- attempt_id,
116
- stdout,
117
- stderr,
118
- );
119
- if let Err(e) = ExecutionProcess::append_output(
120
- &pool,
121
- execution_process_id,
122
- stdout.as_deref(),
123
- stderr.as_deref(),
124
- )
125
- .await
126
- {
127
- tracing::error!(
128
- "Failed to write OpenCode line for attempt {}: {}",
129
- attempt_id,
130
- e
131
- );
132
- }
133
- }
134
- }
135
- Err(e) => {
136
- tracing::error!("Error reading stderr for attempt {}: {}", attempt_id, e);
137
- break;
138
- }
139
- }
140
- }
141
- }
142
-
143
- /// Format OpenCode clean content as normalized JSON entries for direct database storage
144
- fn format_opencode_content_as_normalized_json(content: &str, worktree_path: &str) -> String {
145
- let mut results = Vec::new();
146
- let base_timestamp = chrono::Utc::now();
147
- let mut entry_counter = 0u32;
148
-
149
- for line in content.lines() {
150
- let trimmed = line.trim();
151
- if trimmed.is_empty() {
152
- continue;
153
- }
154
-
155
- // Generate unique timestamp for each entry by adding microseconds
156
- let unique_timestamp =
157
- base_timestamp + chrono::Duration::microseconds(entry_counter as i64);
158
- let timestamp_str = unique_timestamp.to_rfc3339_opts(chrono::SecondsFormat::Micros, true);
159
- entry_counter += 1;
160
-
161
- // Try to parse as existing JSON first
162
- if let Ok(parsed_json) = serde_json::from_str::<Value>(trimmed) {
163
- results.push(parsed_json.to_string());
164
- continue;
165
- }
166
-
167
- // Strip ANSI codes before processing
168
- let cleaned = OpenCodeFilter::strip_ansi_codes(trimmed);
169
- let cleaned_trim = cleaned.trim();
170
-
171
- if cleaned_trim.is_empty() {
172
- continue;
173
- }
174
-
175
- // Check for tool usage patterns after ANSI stripping: | ToolName {...}
176
- if let Some(captures) = tool_usage_regex().captures(cleaned_trim) {
177
- if let (Some(tool_name), Some(tool_input)) = (captures.get(1), captures.get(2)) {
178
- // Parse tool input
179
- let input: serde_json::Value =
180
- serde_json::from_str(tool_input.as_str()).unwrap_or(serde_json::Value::Null);
181
-
182
- // Normalize tool name for frontend compatibility (e.g., "Todo" → "todowrite")
183
- let normalized_tool_name = normalize_tool_name(tool_name.as_str());
184
-
185
- let normalized_entry = json!({
186
- "timestamp": timestamp_str,
187
- "entry_type": {
188
- "type": "tool_use",
189
- "tool_name": normalized_tool_name,
190
- "action_type": determine_action_type(&normalized_tool_name, &input, worktree_path)
191
- },
192
- "content": generate_tool_content(&normalized_tool_name, &input, worktree_path),
193
- "metadata": input
194
- });
195
- results.push(normalized_entry.to_string());
196
- continue;
197
- }
198
- }
199
-
200
- // Regular assistant message
201
- let normalized_entry = json!({
202
- "timestamp": timestamp_str,
203
- "entry_type": {
204
- "type": "assistant_message"
205
- },
206
- "content": cleaned_trim,
207
- "metadata": null
208
- });
209
- results.push(normalized_entry.to_string());
210
- }
211
-
212
- // Ensure each JSON entry is on its own line
213
- results.join("\n") + "\n"
214
- }
215
-
216
- /// An executor that uses SST Opencode CLI to process tasks
217
- pub struct SstOpencodeExecutor {
218
- executor_type: String,
219
- command: String,
220
- }
221
-
222
- impl Default for SstOpencodeExecutor {
223
- fn default() -> Self {
224
- Self::new()
225
- }
226
- }
227
-
228
- impl SstOpencodeExecutor {
229
- /// Create a new SstOpencodeExecutor with default settings
230
- pub fn new() -> Self {
231
- Self {
232
- executor_type: "SST Opencode".to_string(),
233
- command: "npx -y opencode-ai@latest run --print-logs".to_string(),
234
- }
235
- }
236
- }
237
-
238
- /// An executor that resumes an SST Opencode session
239
-
240
- #[async_trait]
241
- impl Executor for SstOpencodeExecutor {
242
- async fn spawn(
243
- &self,
244
- pool: &sqlx::SqlitePool,
245
- task_id: Uuid,
246
- worktree_path: &str,
247
- ) -> Result<AsyncGroupChild, ExecutorError> {
248
- // Get the task to fetch its description
249
- let task = Task::find_by_id(pool, task_id)
250
- .await?
251
- .ok_or(ExecutorError::TaskNotFound)?;
252
-
253
- let prompt = if let Some(task_description) = task.description {
254
- format!(
255
- r#"project_id: {}
256
-
257
- Task title: {}
258
- Task description: {}"#,
259
- task.project_id, task.title, task_description
260
- )
261
- } else {
262
- format!(
263
- r#"project_id: {}
264
-
265
- Task title: {}"#,
266
- task.project_id, task.title
267
- )
268
- };
269
-
270
- // Use shell command for cross-platform compatibility
271
- let (shell_cmd, shell_arg) = get_shell_command();
272
- let opencode_command = &self.command;
273
-
274
- let mut command = Command::new(shell_cmd);
275
- command
276
- .kill_on_drop(true)
277
- .stdin(std::process::Stdio::piped())
278
- .stdout(std::process::Stdio::null()) // Ignore stdout for OpenCode
279
- .stderr(std::process::Stdio::piped())
280
- .current_dir(worktree_path)
281
- .arg(shell_arg)
282
- .arg(opencode_command)
283
- .env("NODE_NO_WARNINGS", "1");
284
-
285
- let mut child = command
286
- .group_spawn() // Create new process group so we can kill entire tree
287
- .map_err(|e| {
288
- crate::executor::SpawnContext::from_command(&command, &self.executor_type)
289
- .with_task(task_id, Some(task.title.clone()))
290
- .with_context(format!("{} CLI execution for new task", self.executor_type))
291
- .spawn_error(e)
292
- })?;
293
-
294
- // Write prompt to stdin safely
295
- if let Some(mut stdin) = child.inner().stdin.take() {
296
- use tokio::io::AsyncWriteExt;
297
- tracing::debug!(
298
- "Writing prompt to OpenCode stdin for task {}: {:?}",
299
- task_id,
300
- prompt
301
- );
302
- stdin.write_all(prompt.as_bytes()).await.map_err(|e| {
303
- let context =
304
- crate::executor::SpawnContext::from_command(&command, &self.executor_type)
305
- .with_task(task_id, Some(task.title.clone()))
306
- .with_context(format!(
307
- "Failed to write prompt to {} CLI stdin",
308
- self.executor_type
309
- ));
310
- ExecutorError::spawn_failed(e, context)
311
- })?;
312
- stdin.shutdown().await.map_err(|e| {
313
- let context =
314
- crate::executor::SpawnContext::from_command(&command, &self.executor_type)
315
- .with_task(task_id, Some(task.title.clone()))
316
- .with_context(format!("Failed to close {} CLI stdin", self.executor_type));
317
- ExecutorError::spawn_failed(e, context)
318
- })?;
319
- }
320
-
321
- Ok(child)
322
- }
323
-
324
- /// Execute with OpenCode filtering for stderr
325
- async fn execute_streaming(
326
- &self,
327
- pool: &sqlx::SqlitePool,
328
- task_id: Uuid,
329
- attempt_id: Uuid,
330
- execution_process_id: Uuid,
331
- worktree_path: &str,
332
- ) -> Result<command_group::AsyncGroupChild, ExecutorError> {
333
- let mut child = self.spawn(pool, task_id, worktree_path).await?;
334
-
335
- // Take stderr pipe for OpenCode filtering
336
- let stderr = child
337
- .inner()
338
- .stderr
339
- .take()
340
- .expect("Failed to take stderr from child process");
341
-
342
- // Start OpenCode stderr filtering task
343
- let pool_clone = pool.clone();
344
- let worktree_path_clone = worktree_path.to_string();
345
- tokio::spawn(stream_opencode_stderr_to_db(
346
- stderr,
347
- pool_clone,
348
- attempt_id,
349
- execution_process_id,
350
- worktree_path_clone,
351
- ));
352
-
353
- Ok(child)
354
- }
355
-
356
- fn normalize_logs(
357
- &self,
358
- logs: &str,
359
- _worktree_path: &str,
360
- ) -> Result<NormalizedConversation, String> {
361
- let mut entries = Vec::new();
362
-
363
- for line in logs.lines() {
364
- let trimmed = line.trim();
365
- if trimmed.is_empty() {
366
- continue;
367
- }
368
-
369
- // Simple passthrough: directly deserialize normalized JSON entries
370
- if let Ok(entry) = serde_json::from_str::<NormalizedEntry>(trimmed) {
371
- entries.push(entry);
372
- }
373
- }
374
-
375
- Ok(NormalizedConversation {
376
- entries,
377
- session_id: None, // Session ID is stored directly in the database
378
- executor_type: "sst-opencode".to_string(),
379
- prompt: None,
380
- summary: None,
381
- })
382
- }
383
-
384
- /// Execute follow-up with OpenCode filtering for stderr
385
- async fn execute_followup_streaming(
386
- &self,
387
- pool: &sqlx::SqlitePool,
388
- task_id: Uuid,
389
- attempt_id: Uuid,
390
- execution_process_id: Uuid,
391
- session_id: &str,
392
- prompt: &str,
393
- worktree_path: &str,
394
- ) -> Result<command_group::AsyncGroupChild, ExecutorError> {
395
- let mut child = self
396
- .spawn_followup(pool, task_id, session_id, prompt, worktree_path)
397
- .await?;
398
-
399
- // Take stderr pipe for OpenCode filtering
400
- let stderr = child
401
- .inner()
402
- .stderr
403
- .take()
404
- .expect("Failed to take stderr from child process");
405
-
406
- // Start OpenCode stderr filtering task
407
- let pool_clone = pool.clone();
408
- let worktree_path_clone = worktree_path.to_string();
409
- tokio::spawn(stream_opencode_stderr_to_db(
410
- stderr,
411
- pool_clone,
412
- attempt_id,
413
- execution_process_id,
414
- worktree_path_clone,
415
- ));
416
-
417
- Ok(child)
418
- }
419
-
420
- async fn spawn_followup(
421
- &self,
422
- _pool: &sqlx::SqlitePool,
423
- _task_id: Uuid,
424
- session_id: &str,
425
- prompt: &str,
426
- worktree_path: &str,
427
- ) -> Result<AsyncGroupChild, ExecutorError> {
428
- use std::process::Stdio;
429
-
430
- use tokio::io::AsyncWriteExt;
431
-
432
- // Use shell command for cross-platform compatibility
433
- let (shell_cmd, shell_arg) = get_shell_command();
434
- let opencode_command = format!("{} --session {}", self.command, session_id);
435
-
436
- let mut command = Command::new(shell_cmd);
437
- command
438
- .kill_on_drop(true)
439
- .stdin(Stdio::piped())
440
- .stdout(Stdio::null()) // Ignore stdout for OpenCode
441
- .stderr(Stdio::piped())
442
- .current_dir(worktree_path)
443
- .arg(shell_arg)
444
- .arg(&opencode_command)
445
- .env("NODE_NO_WARNINGS", "1");
446
-
447
- let mut child = command.group_spawn().map_err(|e| {
448
- crate::executor::SpawnContext::from_command(&command, &self.executor_type)
449
- .with_context(format!(
450
- "{} CLI followup execution for session {}",
451
- self.executor_type, session_id
452
- ))
453
- .spawn_error(e)
454
- })?;
455
-
456
- // Write prompt to stdin safely
457
- if let Some(mut stdin) = child.inner().stdin.take() {
458
- tracing::debug!(
459
- "Writing prompt to {} stdin for session {}: {:?}",
460
- self.executor_type,
461
- session_id,
462
- prompt
463
- );
464
- stdin.write_all(prompt.as_bytes()).await.map_err(|e| {
465
- let context =
466
- crate::executor::SpawnContext::from_command(&command, &self.executor_type)
467
- .with_context(format!(
468
- "Failed to write prompt to {} CLI stdin for session {}",
469
- self.executor_type, session_id
470
- ));
471
- ExecutorError::spawn_failed(e, context)
472
- })?;
473
- stdin.shutdown().await.map_err(|e| {
474
- let context =
475
- crate::executor::SpawnContext::from_command(&command, &self.executor_type)
476
- .with_context(format!(
477
- "Failed to close {} CLI stdin for session {}",
478
- self.executor_type, session_id
479
- ));
480
- ExecutorError::spawn_failed(e, context)
481
- })?;
482
- }
483
-
484
- Ok(child)
485
- }
486
- }
487
-
488
- #[cfg(test)]
489
- mod tests {
490
- use super::*;
491
- use crate::{
492
- executor::ActionType,
493
- executors::sst_opencode::{
494
- format_opencode_content_as_normalized_json, SstOpencodeExecutor,
495
- },
496
- };
497
-
498
- // Test the actual format that comes from the database (normalized JSON entries)
499
- #[test]
500
- fn test_normalize_logs_with_database_format() {
501
- let executor = SstOpencodeExecutor::new();
502
-
503
- // This is what the database should contain after our streaming function processes it
504
- let logs = r#"{"timestamp":"2025-07-16T18:04:00Z","entry_type":{"type":"tool_use","tool_name":"Read","action_type":{"action":"file_read","path":"hello.js"}},"content":"`hello.js`","metadata":{"filePath":"/path/to/repo/hello.js"}}
505
- {"timestamp":"2025-07-16T18:04:01Z","entry_type":{"type":"assistant_message"},"content":"I'll read the hello.js file to see its current contents.","metadata":null}
506
- {"timestamp":"2025-07-16T18:04:02Z","entry_type":{"type":"tool_use","tool_name":"bash","action_type":{"action":"command_run","command":"ls -la"}},"content":"`ls -la`","metadata":{"command":"ls -la"}}
507
- {"timestamp":"2025-07-16T18:04:03Z","entry_type":{"type":"assistant_message"},"content":"The file exists and contains a hello world function.","metadata":null}"#;
508
-
509
- let result = executor.normalize_logs(logs, "/path/to/repo").unwrap();
510
-
511
- assert_eq!(result.entries.len(), 4);
512
-
513
- // First entry: file read tool use
514
- assert!(matches!(
515
- result.entries[0].entry_type,
516
- crate::executor::NormalizedEntryType::ToolUse { .. }
517
- ));
518
- if let crate::executor::NormalizedEntryType::ToolUse {
519
- tool_name,
520
- action_type,
521
- } = &result.entries[0].entry_type
522
- {
523
- assert_eq!(tool_name, "Read");
524
- assert!(matches!(action_type, ActionType::FileRead { .. }));
525
- }
526
- assert_eq!(result.entries[0].content, "`hello.js`");
527
- assert!(result.entries[0].timestamp.is_some());
528
-
529
- // Second entry: assistant message
530
- assert!(matches!(
531
- result.entries[1].entry_type,
532
- crate::executor::NormalizedEntryType::AssistantMessage
533
- ));
534
- assert!(result.entries[1].content.contains("read the hello.js file"));
535
-
536
- // Third entry: bash tool use
537
- assert!(matches!(
538
- result.entries[2].entry_type,
539
- crate::executor::NormalizedEntryType::ToolUse { .. }
540
- ));
541
- if let crate::executor::NormalizedEntryType::ToolUse {
542
- tool_name,
543
- action_type,
544
- } = &result.entries[2].entry_type
545
- {
546
- assert_eq!(tool_name, "bash");
547
- assert!(matches!(action_type, ActionType::CommandRun { .. }));
548
- }
549
-
550
- // Fourth entry: assistant message
551
- assert!(matches!(
552
- result.entries[3].entry_type,
553
- crate::executor::NormalizedEntryType::AssistantMessage
554
- ));
555
- assert!(result.entries[3].content.contains("The file exists"));
556
- }
557
-
558
- #[test]
559
- fn test_normalize_logs_with_session_id() {
560
- let executor = SstOpencodeExecutor::new();
561
-
562
- // Test session ID in JSON metadata - current implementation always returns None for session_id
563
- let logs = r#"{"timestamp":"2025-07-16T18:04:00Z","entry_type":{"type":"assistant_message"},"content":"Session started","metadata":null,"session_id":"ses_abc123"}
564
- {"timestamp":"2025-07-16T18:04:01Z","entry_type":{"type":"assistant_message"},"content":"Hello world","metadata":null}"#;
565
-
566
- let result = executor.normalize_logs(logs, "/tmp").unwrap();
567
- assert_eq!(result.session_id, None); // Session ID is stored directly in the database
568
- assert_eq!(result.entries.len(), 2);
569
- }
570
-
571
- #[test]
572
- fn test_normalize_logs_legacy_fallback() {
573
- let executor = SstOpencodeExecutor::new();
574
-
575
- // Current implementation doesn't handle legacy format - it only parses JSON entries
576
- let logs = r#"INFO session=ses_legacy123 starting
577
- | Read {"filePath":"/path/to/file.js"}
578
- This is a plain assistant message"#;
579
-
580
- let result = executor.normalize_logs(logs, "/tmp").unwrap();
581
-
582
- // Session ID is always None in current implementation
583
- assert_eq!(result.session_id, None);
584
-
585
- // Current implementation skips non-JSON lines, so no entries will be parsed
586
- assert_eq!(result.entries.len(), 0);
587
- }
588
-
589
- #[test]
590
- fn test_format_opencode_content_as_normalized_json() {
591
- let content = r#"| Read {"filePath":"/path/to/repo/hello.js"}
592
- I'll read this file to understand its contents.
593
- | bash {"command":"ls -la"}
594
- The file listing shows several items."#;
595
-
596
- let result = format_opencode_content_as_normalized_json(content, "/path/to/repo");
597
- let lines: Vec<&str> = result
598
- .split('\n')
599
- .filter(|line| !line.trim().is_empty())
600
- .collect();
601
-
602
- // Should have 4 entries (2 tool uses + 2 assistant messages)
603
- assert_eq!(lines.len(), 4);
604
-
605
- // Parse all entries and verify unique timestamps
606
- let mut timestamps = Vec::new();
607
- for line in &lines {
608
- let json: serde_json::Value = serde_json::from_str(line).unwrap();
609
- let timestamp = json["timestamp"].as_str().unwrap().to_string();
610
- timestamps.push(timestamp);
611
- }
612
-
613
- // Verify all timestamps are unique (no duplicates)
614
- let mut unique_timestamps = timestamps.clone();
615
- unique_timestamps.sort();
616
- unique_timestamps.dedup();
617
- assert_eq!(
618
- timestamps.len(),
619
- unique_timestamps.len(),
620
- "All timestamps should be unique"
621
- );
622
-
623
- // Parse the first line (should be Read tool use)
624
- let first_json: serde_json::Value = serde_json::from_str(lines[0]).unwrap();
625
- assert_eq!(first_json["entry_type"]["type"], "tool_use");
626
- assert_eq!(first_json["entry_type"]["tool_name"], "Read");
627
- assert_eq!(first_json["content"], "`hello.js`");
628
-
629
- // Parse the second line (should be assistant message)
630
- let second_json: serde_json::Value = serde_json::from_str(lines[1]).unwrap();
631
- assert_eq!(second_json["entry_type"]["type"], "assistant_message");
632
- assert!(second_json["content"]
633
- .as_str()
634
- .unwrap()
635
- .contains("read this file"));
636
-
637
- // Parse the third line (should be bash tool use)
638
- let third_json: serde_json::Value = serde_json::from_str(lines[2]).unwrap();
639
- assert_eq!(third_json["entry_type"]["type"], "tool_use");
640
- assert_eq!(third_json["entry_type"]["tool_name"], "bash");
641
- assert_eq!(third_json["content"], "`ls -la`");
642
-
643
- // Verify timestamps include microseconds for uniqueness
644
- for timestamp in timestamps {
645
- assert!(
646
- timestamp.contains('.'),
647
- "Timestamp should include microseconds: {}",
648
- timestamp
649
- );
650
- }
651
- }
652
-
653
- #[test]
654
- fn test_format_opencode_content_todo_operations() {
655
- let content = r#"| TodoWrite {"todos":[{"id":"1","content":"Fix bug","status":"completed","priority":"high"},{"id":"2","content":"Add feature","status":"in_progress","priority":"medium"}]}"#;
656
-
657
- let result = format_opencode_content_as_normalized_json(content, "/tmp");
658
- let json: serde_json::Value = serde_json::from_str(&result).unwrap();
659
-
660
- assert_eq!(json["entry_type"]["type"], "tool_use");
661
- assert_eq!(json["entry_type"]["tool_name"], "todowrite"); // Normalized from "TodoWrite"
662
- assert_eq!(json["entry_type"]["action_type"]["action"], "other"); // Changed from task_create to other
663
-
664
- // Should contain formatted todo list
665
- let content_str = json["content"].as_str().unwrap();
666
- assert!(content_str.contains("TODO List:"));
667
- assert!(content_str.contains("✅ Fix bug (high)"));
668
- assert!(content_str.contains("🔄 Add feature (medium)"));
669
- }
670
-
671
- #[test]
672
- fn test_format_opencode_content_todo_tool() {
673
- // Test the "Todo" tool (case-sensitive, different from todowrite/todoread)
674
- let content = r#"| Todo {"todos":[{"id":"1","content":"Review code","status":"pending","priority":"high"},{"id":"2","content":"Write tests","status":"in_progress","priority":"low"}]}"#;
675
-
676
- let result = format_opencode_content_as_normalized_json(content, "/tmp");
677
- let json: serde_json::Value = serde_json::from_str(&result).unwrap();
678
-
679
- assert_eq!(json["entry_type"]["type"], "tool_use");
680
- assert_eq!(json["entry_type"]["tool_name"], "todowrite"); // Normalized from "Todo"
681
- assert_eq!(json["entry_type"]["action_type"]["action"], "other"); // Changed from task_create to other
682
-
683
- // Should contain formatted todo list with proper emojis
684
- let content_str = json["content"].as_str().unwrap();
685
- assert!(content_str.contains("TODO List:"));
686
- assert!(content_str.contains("⏳ Review code (high)"));
687
- assert!(content_str.contains("🔄 Write tests (low)"));
688
- }
689
-
690
- #[test]
691
- fn test_opencode_filter_noise_detection() {
692
- use crate::executors::sst_opencode::filter::OpenCodeFilter;
693
-
694
- // Test noise detection
695
- assert!(OpenCodeFilter::is_noise(""));
696
- assert!(OpenCodeFilter::is_noise(" "));
697
- assert!(OpenCodeFilter::is_noise("█▀▀█ █▀▀█ Banner"));
698
- assert!(OpenCodeFilter::is_noise("@ anthropic/claude-sonnet-4"));
699
- assert!(OpenCodeFilter::is_noise("~ https://opencode.ai/s/abc123"));
700
- assert!(OpenCodeFilter::is_noise("DEBUG some debug info"));
701
- assert!(OpenCodeFilter::is_noise("INFO session info"));
702
- assert!(OpenCodeFilter::is_noise("┌─────────────────┐"));
703
-
704
- // Test clean content detection (not noise)
705
- assert!(!OpenCodeFilter::is_noise("| Read {\"file\":\"test.js\"}"));
706
- assert!(!OpenCodeFilter::is_noise("Assistant response text"));
707
- assert!(!OpenCodeFilter::is_noise("{\"type\":\"content\"}"));
708
- assert!(!OpenCodeFilter::is_noise("session=abc123 started"));
709
- assert!(!OpenCodeFilter::is_noise("Normal conversation text"));
710
- }
711
-
712
- #[test]
713
- fn test_normalize_logs_edge_cases() {
714
- let executor = SstOpencodeExecutor::new();
715
-
716
- // Empty content
717
- let result = executor.normalize_logs("", "/tmp").unwrap();
718
- assert_eq!(result.entries.len(), 0);
719
-
720
- // Only whitespace
721
- let result = executor.normalize_logs(" \n\t\n ", "/tmp").unwrap();
722
- assert_eq!(result.entries.len(), 0);
723
-
724
- // Malformed JSON (current implementation skips invalid JSON)
725
- let malformed = r#"{"timestamp":"2025-01-16T18:04:00Z","content":"incomplete"#;
726
- let result = executor.normalize_logs(malformed, "/tmp").unwrap();
727
- assert_eq!(result.entries.len(), 0); // Current implementation skips invalid JSON
728
-
729
- // Mixed valid and invalid JSON
730
- let mixed = r#"{"timestamp":"2025-01-16T18:04:00Z","entry_type":{"type":"assistant_message"},"content":"Valid entry","metadata":null}
731
- Invalid line that's not JSON
732
- {"timestamp":"2025-01-16T18:04:01Z","entry_type":{"type":"assistant_message"},"content":"Another valid entry","metadata":null}"#;
733
- let result = executor.normalize_logs(mixed, "/tmp").unwrap();
734
- assert_eq!(result.entries.len(), 2); // Only valid JSON entries are parsed
735
- }
736
-
737
- #[test]
738
- fn test_ansi_code_stripping() {
739
- use crate::executors::sst_opencode::filter::OpenCodeFilter;
740
-
741
- // Test ANSI escape sequence removal
742
- let ansi_text = "\x1b[31mRed text\x1b[0m normal text";
743
- let cleaned = OpenCodeFilter::strip_ansi_codes(ansi_text);
744
- assert_eq!(cleaned, "Red text normal text");
745
-
746
- // Test unicode escape sequences
747
- let unicode_ansi = "Text with \\u001b[32mgreen\\u001b[0m color";
748
- let cleaned = OpenCodeFilter::strip_ansi_codes(unicode_ansi);
749
- assert_eq!(cleaned, "Text with green color");
750
-
751
- // Test text without ANSI codes (unchanged)
752
- let plain_text = "Regular text without codes";
753
- let cleaned = OpenCodeFilter::strip_ansi_codes(plain_text);
754
- assert_eq!(cleaned, plain_text);
755
- }
756
- }