@hivehub/rulebook 1.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 (379) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +539 -0
  3. package/dist/agents/claude-code.d.ts +69 -0
  4. package/dist/agents/claude-code.d.ts.map +1 -0
  5. package/dist/agents/claude-code.js +180 -0
  6. package/dist/agents/claude-code.js.map +1 -0
  7. package/dist/agents/cursor-agent.d.ts +184 -0
  8. package/dist/agents/cursor-agent.d.ts.map +1 -0
  9. package/dist/agents/cursor-agent.js +299 -0
  10. package/dist/agents/cursor-agent.js.map +1 -0
  11. package/dist/agents/gemini-cli.d.ts +69 -0
  12. package/dist/agents/gemini-cli.d.ts.map +1 -0
  13. package/dist/agents/gemini-cli.js +180 -0
  14. package/dist/agents/gemini-cli.js.map +1 -0
  15. package/dist/cli/commands.d.ts +57 -0
  16. package/dist/cli/commands.d.ts.map +1 -0
  17. package/dist/cli/commands.js +1370 -0
  18. package/dist/cli/commands.js.map +1 -0
  19. package/dist/cli/docs-prompts.d.ts +3 -0
  20. package/dist/cli/docs-prompts.d.ts.map +1 -0
  21. package/dist/cli/docs-prompts.js +45 -0
  22. package/dist/cli/docs-prompts.js.map +1 -0
  23. package/dist/cli/prompts.d.ts +6 -0
  24. package/dist/cli/prompts.d.ts.map +1 -0
  25. package/dist/cli/prompts.js +376 -0
  26. package/dist/cli/prompts.js.map +1 -0
  27. package/dist/core/agent-manager.d.ts +89 -0
  28. package/dist/core/agent-manager.d.ts.map +1 -0
  29. package/dist/core/agent-manager.js +546 -0
  30. package/dist/core/agent-manager.js.map +1 -0
  31. package/dist/core/auto-fixer.d.ts +14 -0
  32. package/dist/core/auto-fixer.d.ts.map +1 -0
  33. package/dist/core/auto-fixer.js +207 -0
  34. package/dist/core/auto-fixer.js.map +1 -0
  35. package/dist/core/changelog-generator.d.ts +44 -0
  36. package/dist/core/changelog-generator.d.ts.map +1 -0
  37. package/dist/core/changelog-generator.js +222 -0
  38. package/dist/core/changelog-generator.js.map +1 -0
  39. package/dist/core/cli-bridge.d.ts +113 -0
  40. package/dist/core/cli-bridge.d.ts.map +1 -0
  41. package/dist/core/cli-bridge.js +1094 -0
  42. package/dist/core/cli-bridge.js.map +1 -0
  43. package/dist/core/config-manager.d.ts +65 -0
  44. package/dist/core/config-manager.d.ts.map +1 -0
  45. package/dist/core/config-manager.js +266 -0
  46. package/dist/core/config-manager.js.map +1 -0
  47. package/dist/core/coverage-checker.d.ts +14 -0
  48. package/dist/core/coverage-checker.d.ts.map +1 -0
  49. package/dist/core/coverage-checker.js +176 -0
  50. package/dist/core/coverage-checker.js.map +1 -0
  51. package/dist/core/custom-templates.d.ts +27 -0
  52. package/dist/core/custom-templates.d.ts.map +1 -0
  53. package/dist/core/custom-templates.js +122 -0
  54. package/dist/core/custom-templates.js.map +1 -0
  55. package/dist/core/dependency-checker.d.ts +21 -0
  56. package/dist/core/dependency-checker.d.ts.map +1 -0
  57. package/dist/core/dependency-checker.js +247 -0
  58. package/dist/core/dependency-checker.js.map +1 -0
  59. package/dist/core/detector.d.ts +3 -0
  60. package/dist/core/detector.d.ts.map +1 -0
  61. package/dist/core/detector.js +1443 -0
  62. package/dist/core/detector.js.map +1 -0
  63. package/dist/core/docs-generator.d.ts +9 -0
  64. package/dist/core/docs-generator.d.ts.map +1 -0
  65. package/dist/core/docs-generator.js +531 -0
  66. package/dist/core/docs-generator.js.map +1 -0
  67. package/dist/core/generator.d.ts +16 -0
  68. package/dist/core/generator.d.ts.map +1 -0
  69. package/dist/core/generator.js +561 -0
  70. package/dist/core/generator.js.map +1 -0
  71. package/dist/core/gitignore-generator.d.ts +13 -0
  72. package/dist/core/gitignore-generator.d.ts.map +1 -0
  73. package/dist/core/gitignore-generator.js +307 -0
  74. package/dist/core/gitignore-generator.js.map +1 -0
  75. package/dist/core/health-scorer.d.ts +22 -0
  76. package/dist/core/health-scorer.d.ts.map +1 -0
  77. package/dist/core/health-scorer.js +395 -0
  78. package/dist/core/health-scorer.js.map +1 -0
  79. package/dist/core/logger.d.ts +116 -0
  80. package/dist/core/logger.d.ts.map +1 -0
  81. package/dist/core/logger.js +289 -0
  82. package/dist/core/logger.js.map +1 -0
  83. package/dist/core/merger.d.ts +6 -0
  84. package/dist/core/merger.d.ts.map +1 -0
  85. package/dist/core/merger.js +131 -0
  86. package/dist/core/merger.js.map +1 -0
  87. package/dist/core/migrator.d.ts +19 -0
  88. package/dist/core/migrator.d.ts.map +1 -0
  89. package/dist/core/migrator.js +102 -0
  90. package/dist/core/migrator.js.map +1 -0
  91. package/dist/core/minimal-scaffolder.d.ts +8 -0
  92. package/dist/core/minimal-scaffolder.d.ts.map +1 -0
  93. package/dist/core/minimal-scaffolder.js +51 -0
  94. package/dist/core/minimal-scaffolder.js.map +1 -0
  95. package/dist/core/modern-console-new.d.ts +81 -0
  96. package/dist/core/modern-console-new.d.ts.map +1 -0
  97. package/dist/core/modern-console-new.js +340 -0
  98. package/dist/core/modern-console-new.js.map +1 -0
  99. package/dist/core/modern-console.d.ts +99 -0
  100. package/dist/core/modern-console.d.ts.map +1 -0
  101. package/dist/core/modern-console.js +568 -0
  102. package/dist/core/modern-console.js.map +1 -0
  103. package/dist/core/openspec-manager.d.ts +133 -0
  104. package/dist/core/openspec-manager.d.ts.map +1 -0
  105. package/dist/core/openspec-manager.js +605 -0
  106. package/dist/core/openspec-manager.js.map +1 -0
  107. package/dist/core/openspec-migrator.d.ts +27 -0
  108. package/dist/core/openspec-migrator.d.ts.map +1 -0
  109. package/dist/core/openspec-migrator.js +255 -0
  110. package/dist/core/openspec-migrator.js.map +1 -0
  111. package/dist/core/task-manager.d.ts +65 -0
  112. package/dist/core/task-manager.d.ts.map +1 -0
  113. package/dist/core/task-manager.js +318 -0
  114. package/dist/core/task-manager.js.map +1 -0
  115. package/dist/core/test-task-manager.d.ts +49 -0
  116. package/dist/core/test-task-manager.d.ts.map +1 -0
  117. package/dist/core/test-task-manager.js +121 -0
  118. package/dist/core/test-task-manager.js.map +1 -0
  119. package/dist/core/validator.d.ts +21 -0
  120. package/dist/core/validator.d.ts.map +1 -0
  121. package/dist/core/validator.js +177 -0
  122. package/dist/core/validator.js.map +1 -0
  123. package/dist/core/version-bumper.d.ts +19 -0
  124. package/dist/core/version-bumper.d.ts.map +1 -0
  125. package/dist/core/version-bumper.js +180 -0
  126. package/dist/core/version-bumper.js.map +1 -0
  127. package/dist/core/watcher.d.ts +9 -0
  128. package/dist/core/watcher.d.ts.map +1 -0
  129. package/dist/core/watcher.js +22 -0
  130. package/dist/core/watcher.js.map +1 -0
  131. package/dist/core/workflow-generator.d.ts +10 -0
  132. package/dist/core/workflow-generator.d.ts.map +1 -0
  133. package/dist/core/workflow-generator.js +279 -0
  134. package/dist/core/workflow-generator.js.map +1 -0
  135. package/dist/index.d.ts +3 -0
  136. package/dist/index.d.ts.map +1 -0
  137. package/dist/index.js +159 -0
  138. package/dist/index.js.map +1 -0
  139. package/dist/mcp/handlers/archive-task.d.ts +17 -0
  140. package/dist/mcp/handlers/archive-task.d.ts.map +1 -0
  141. package/dist/mcp/handlers/archive-task.js +36 -0
  142. package/dist/mcp/handlers/archive-task.js.map +1 -0
  143. package/dist/mcp/handlers/create-task.d.ts +17 -0
  144. package/dist/mcp/handlers/create-task.d.ts.map +1 -0
  145. package/dist/mcp/handlers/create-task.js +56 -0
  146. package/dist/mcp/handlers/create-task.js.map +1 -0
  147. package/dist/mcp/handlers/list-tasks.d.ts +22 -0
  148. package/dist/mcp/handlers/list-tasks.d.ts.map +1 -0
  149. package/dist/mcp/handlers/list-tasks.js +42 -0
  150. package/dist/mcp/handlers/list-tasks.js.map +1 -0
  151. package/dist/mcp/handlers/show-task.d.ts +25 -0
  152. package/dist/mcp/handlers/show-task.d.ts.map +1 -0
  153. package/dist/mcp/handlers/show-task.js +43 -0
  154. package/dist/mcp/handlers/show-task.js.map +1 -0
  155. package/dist/mcp/handlers/update-task.d.ts +17 -0
  156. package/dist/mcp/handlers/update-task.d.ts.map +1 -0
  157. package/dist/mcp/handlers/update-task.js +35 -0
  158. package/dist/mcp/handlers/update-task.js.map +1 -0
  159. package/dist/mcp/handlers/validate-task.d.ts +15 -0
  160. package/dist/mcp/handlers/validate-task.d.ts.map +1 -0
  161. package/dist/mcp/handlers/validate-task.js +27 -0
  162. package/dist/mcp/handlers/validate-task.js.map +1 -0
  163. package/dist/mcp/rulebook-config.d.ts +22 -0
  164. package/dist/mcp/rulebook-config.d.ts.map +1 -0
  165. package/dist/mcp/rulebook-config.js +65 -0
  166. package/dist/mcp/rulebook-config.js.map +1 -0
  167. package/dist/mcp/rulebook-server.d.ts +4 -0
  168. package/dist/mcp/rulebook-server.d.ts.map +1 -0
  169. package/dist/mcp/rulebook-server.js +246 -0
  170. package/dist/mcp/rulebook-server.js.map +1 -0
  171. package/dist/types.d.ts +190 -0
  172. package/dist/types.d.ts.map +1 -0
  173. package/dist/types.js +2 -0
  174. package/dist/types.js.map +1 -0
  175. package/dist/utils/file-system.d.ts +9 -0
  176. package/dist/utils/file-system.d.ts.map +1 -0
  177. package/dist/utils/file-system.js +51 -0
  178. package/dist/utils/file-system.js.map +1 -0
  179. package/dist/utils/git-hooks.d.ts +8 -0
  180. package/dist/utils/git-hooks.d.ts.map +1 -0
  181. package/dist/utils/git-hooks.js +440 -0
  182. package/dist/utils/git-hooks.js.map +1 -0
  183. package/dist/utils/rulesignore.d.ts +9 -0
  184. package/dist/utils/rulesignore.d.ts.map +1 -0
  185. package/dist/utils/rulesignore.js +42 -0
  186. package/dist/utils/rulesignore.js.map +1 -0
  187. package/package.json +106 -0
  188. package/templates/cli/AIDER.md +49 -0
  189. package/templates/cli/AMAZON_Q.md +25 -0
  190. package/templates/cli/AUGGIE.md +32 -0
  191. package/templates/cli/CLAUDE.md +32 -0
  192. package/templates/cli/CLAUDE_CODE.md +35 -0
  193. package/templates/cli/CLINE.md +32 -0
  194. package/templates/cli/CODEBUDDY.md +20 -0
  195. package/templates/cli/CODEIUM.md +20 -0
  196. package/templates/cli/CODEX.md +21 -0
  197. package/templates/cli/CONTINUE.md +34 -0
  198. package/templates/cli/CURSOR_CLI.md +28 -0
  199. package/templates/cli/FACTORY.md +18 -0
  200. package/templates/cli/GEMINI.md +35 -0
  201. package/templates/cli/KILOCODE.md +18 -0
  202. package/templates/cli/OPENCODE.md +18 -0
  203. package/templates/cli/_GENERIC_TEMPLATE.md +29 -0
  204. package/templates/commands/rulebook-task-apply.md +67 -0
  205. package/templates/commands/rulebook-task-archive.md +70 -0
  206. package/templates/commands/rulebook-task-create.md +93 -0
  207. package/templates/commands/rulebook-task-list.md +42 -0
  208. package/templates/commands/rulebook-task-show.md +52 -0
  209. package/templates/commands/rulebook-task-validate.md +53 -0
  210. package/templates/core/AGENT_AUTOMATION.md +184 -0
  211. package/templates/core/DAG.md +304 -0
  212. package/templates/core/DOCUMENTATION_RULES.md +37 -0
  213. package/templates/core/QUALITY_ENFORCEMENT.md +68 -0
  214. package/templates/core/RULEBOOK.md +1874 -0
  215. package/templates/frameworks/ANGULAR.md +36 -0
  216. package/templates/frameworks/DJANGO.md +83 -0
  217. package/templates/frameworks/ELECTRON.md +147 -0
  218. package/templates/frameworks/FLASK.md +38 -0
  219. package/templates/frameworks/FLUTTER.md +55 -0
  220. package/templates/frameworks/JQUERY.md +32 -0
  221. package/templates/frameworks/LARAVEL.md +38 -0
  222. package/templates/frameworks/NESTJS.md +43 -0
  223. package/templates/frameworks/NEXTJS.md +127 -0
  224. package/templates/frameworks/NUXT.md +40 -0
  225. package/templates/frameworks/RAILS.md +66 -0
  226. package/templates/frameworks/REACT.md +38 -0
  227. package/templates/frameworks/REACT_NATIVE.md +47 -0
  228. package/templates/frameworks/SPRING.md +39 -0
  229. package/templates/frameworks/SYMFONY.md +36 -0
  230. package/templates/frameworks/VUE.md +36 -0
  231. package/templates/frameworks/ZEND.md +35 -0
  232. package/templates/git/CI_CD_PATTERNS.md +661 -0
  233. package/templates/git/GITHUB_ACTIONS.md +728 -0
  234. package/templates/git/GITLAB_CI.md +730 -0
  235. package/templates/git/GIT_WORKFLOW.md +1157 -0
  236. package/templates/git/SECRETS_MANAGEMENT.md +585 -0
  237. package/templates/hooks/COMMIT_MSG.md +530 -0
  238. package/templates/hooks/POST_CHECKOUT.md +546 -0
  239. package/templates/hooks/PREPARE_COMMIT_MSG.md +619 -0
  240. package/templates/hooks/PRE_COMMIT.md +414 -0
  241. package/templates/hooks/PRE_PUSH.md +601 -0
  242. package/templates/hooks/csharp-pre-commit.sh +23 -0
  243. package/templates/hooks/csharp-pre-push.sh +23 -0
  244. package/templates/hooks/dart-pre-commit.sh +30 -0
  245. package/templates/hooks/dart-pre-push.sh +25 -0
  246. package/templates/hooks/elixir-pre-commit.sh +32 -0
  247. package/templates/hooks/elixir-pre-push.sh +31 -0
  248. package/templates/hooks/erlang-pre-commit.sh +30 -0
  249. package/templates/hooks/erlang-pre-push.sh +37 -0
  250. package/templates/hooks/go-pre-commit.sh +40 -0
  251. package/templates/hooks/go-pre-push.sh +31 -0
  252. package/templates/hooks/haskell-pre-commit.sh +41 -0
  253. package/templates/hooks/haskell-pre-push.sh +37 -0
  254. package/templates/hooks/java-pre-commit.sh +34 -0
  255. package/templates/hooks/java-pre-push.sh +24 -0
  256. package/templates/hooks/kotlin-pre-commit.sh +32 -0
  257. package/templates/hooks/kotlin-pre-push.sh +16 -0
  258. package/templates/hooks/php-pre-commit.sh +36 -0
  259. package/templates/hooks/php-pre-push.sh +26 -0
  260. package/templates/hooks/python-pre-commit.sh +51 -0
  261. package/templates/hooks/python-pre-push.sh +25 -0
  262. package/templates/hooks/ruby-pre-commit.sh +33 -0
  263. package/templates/hooks/ruby-pre-push.sh +32 -0
  264. package/templates/hooks/rust-pre-commit.sh +30 -0
  265. package/templates/hooks/rust-pre-push.sh +30 -0
  266. package/templates/hooks/scala-pre-commit.sh +32 -0
  267. package/templates/hooks/scala-pre-push.sh +24 -0
  268. package/templates/hooks/swift-pre-commit.sh +25 -0
  269. package/templates/hooks/swift-pre-push.sh +23 -0
  270. package/templates/hooks/typescript-pre-commit.sh +37 -0
  271. package/templates/hooks/typescript-pre-push.sh +36 -0
  272. package/templates/ides/COPILOT.md +37 -0
  273. package/templates/ides/CURSOR.md +43 -0
  274. package/templates/ides/JETBRAINS_AI.md +35 -0
  275. package/templates/ides/REPLIT.md +36 -0
  276. package/templates/ides/TABNINE.md +29 -0
  277. package/templates/ides/VSCODE.md +40 -0
  278. package/templates/ides/WINDSURF.md +36 -0
  279. package/templates/ides/ZED.md +32 -0
  280. package/templates/languages/ADA.md +58 -0
  281. package/templates/languages/C.md +333 -0
  282. package/templates/languages/CPP.md +743 -0
  283. package/templates/languages/CSHARP.md +417 -0
  284. package/templates/languages/DART.md +332 -0
  285. package/templates/languages/ELIXIR.md +454 -0
  286. package/templates/languages/ERLANG.md +361 -0
  287. package/templates/languages/GO.md +645 -0
  288. package/templates/languages/HASKELL.md +177 -0
  289. package/templates/languages/JAVA.md +607 -0
  290. package/templates/languages/JAVASCRIPT.md +631 -0
  291. package/templates/languages/JULIA.md +97 -0
  292. package/templates/languages/KOTLIN.md +511 -0
  293. package/templates/languages/LISP.md +100 -0
  294. package/templates/languages/LUA.md +74 -0
  295. package/templates/languages/OBJECTIVEC.md +90 -0
  296. package/templates/languages/PHP.md +416 -0
  297. package/templates/languages/PYTHON.md +682 -0
  298. package/templates/languages/R.md +350 -0
  299. package/templates/languages/RUBY.md +421 -0
  300. package/templates/languages/RUST.md +477 -0
  301. package/templates/languages/SAS.md +73 -0
  302. package/templates/languages/SCALA.md +348 -0
  303. package/templates/languages/SOLIDITY.md +580 -0
  304. package/templates/languages/SQL.md +137 -0
  305. package/templates/languages/SWIFT.md +466 -0
  306. package/templates/languages/TYPESCRIPT.md +591 -0
  307. package/templates/languages/ZIG.md +265 -0
  308. package/templates/modules/ATLASSIAN.md +255 -0
  309. package/templates/modules/CONTEXT7.md +54 -0
  310. package/templates/modules/FIGMA.md +267 -0
  311. package/templates/modules/GITHUB_MCP.md +64 -0
  312. package/templates/modules/GRAFANA.md +328 -0
  313. package/templates/modules/NOTION.md +247 -0
  314. package/templates/modules/PLAYWRIGHT.md +90 -0
  315. package/templates/modules/RULEBOOK_MCP.md +156 -0
  316. package/templates/modules/SERENA.md +337 -0
  317. package/templates/modules/SUPABASE.md +223 -0
  318. package/templates/modules/SYNAP.md +69 -0
  319. package/templates/modules/VECTORIZER.md +63 -0
  320. package/templates/services/AZURE_BLOB.md +184 -0
  321. package/templates/services/CASSANDRA.md +239 -0
  322. package/templates/services/DYNAMODB.md +308 -0
  323. package/templates/services/ELASTICSEARCH.md +347 -0
  324. package/templates/services/GCS.md +178 -0
  325. package/templates/services/INFLUXDB.md +265 -0
  326. package/templates/services/KAFKA.md +341 -0
  327. package/templates/services/MARIADB.md +183 -0
  328. package/templates/services/MEMCACHED.md +242 -0
  329. package/templates/services/MINIO.md +201 -0
  330. package/templates/services/MONGODB.md +268 -0
  331. package/templates/services/MYSQL.md +358 -0
  332. package/templates/services/NEO4J.md +247 -0
  333. package/templates/services/ORACLE.md +290 -0
  334. package/templates/services/POSTGRESQL.md +326 -0
  335. package/templates/services/RABBITMQ.md +286 -0
  336. package/templates/services/REDIS.md +292 -0
  337. package/templates/services/S3.md +298 -0
  338. package/templates/services/SQLITE.md +294 -0
  339. package/templates/services/SQLSERVER.md +294 -0
  340. package/templates/workflows/codespell.yml +31 -0
  341. package/templates/workflows/cpp-lint.yml +47 -0
  342. package/templates/workflows/cpp-publish.yml +119 -0
  343. package/templates/workflows/cpp-test.yml +77 -0
  344. package/templates/workflows/dotnet-lint.yml +29 -0
  345. package/templates/workflows/dotnet-publish.yml +40 -0
  346. package/templates/workflows/dotnet-test.yml +41 -0
  347. package/templates/workflows/elixir-lint.yml +45 -0
  348. package/templates/workflows/elixir-publish.yml +49 -0
  349. package/templates/workflows/elixir-test.yml +54 -0
  350. package/templates/workflows/erlang-lint.yml +47 -0
  351. package/templates/workflows/erlang-test.yml +62 -0
  352. package/templates/workflows/go-lint.yml +39 -0
  353. package/templates/workflows/go-publish.yml +95 -0
  354. package/templates/workflows/go-test.yml +59 -0
  355. package/templates/workflows/java-lint.yml +60 -0
  356. package/templates/workflows/java-publish.yml +120 -0
  357. package/templates/workflows/java-test.yml +85 -0
  358. package/templates/workflows/kotlin-lint.yml +34 -0
  359. package/templates/workflows/kotlin-publish.yml +56 -0
  360. package/templates/workflows/kotlin-test.yml +48 -0
  361. package/templates/workflows/php-lint.yml +39 -0
  362. package/templates/workflows/php-publish.yml +50 -0
  363. package/templates/workflows/php-test.yml +54 -0
  364. package/templates/workflows/python-lint.yml +47 -0
  365. package/templates/workflows/python-publish.yml +91 -0
  366. package/templates/workflows/python-test.yml +59 -0
  367. package/templates/workflows/rust-lint.yml +54 -0
  368. package/templates/workflows/rust-publish.yml +66 -0
  369. package/templates/workflows/rust-test.yml +75 -0
  370. package/templates/workflows/solidity-lint.yml +41 -0
  371. package/templates/workflows/solidity-test.yml +47 -0
  372. package/templates/workflows/swift-lint.yml +32 -0
  373. package/templates/workflows/swift-publish.yml +58 -0
  374. package/templates/workflows/swift-test.yml +44 -0
  375. package/templates/workflows/typescript-lint.yml +61 -0
  376. package/templates/workflows/typescript-publish.yml +60 -0
  377. package/templates/workflows/typescript-test.yml +73 -0
  378. package/templates/workflows/zig-lint.yml +27 -0
  379. package/templates/workflows/zig-test.yml +40 -0
@@ -0,0 +1,1094 @@
1
+ import { spawn } from 'child_process';
2
+ import { CursorAgentStreamParser, parseStreamLine } from '../agents/cursor-agent.js';
3
+ import { ClaudeCodeStreamParser, parseClaudeCodeLine } from '../agents/claude-code.js';
4
+ import { GeminiStreamParser, parseGeminiLine } from '../agents/gemini-cli.js';
5
+ import { appendFile, mkdir } from 'fs/promises';
6
+ import { join } from 'path';
7
+ import { existsSync } from 'fs';
8
+ export class CLIBridge {
9
+ logger;
10
+ config;
11
+ activeProcesses = new Map();
12
+ onLog;
13
+ debugLogFile;
14
+ debugLogInitialized = false;
15
+ constructor(logger, config) {
16
+ this.logger = logger;
17
+ this.config = config;
18
+ }
19
+ /**
20
+ * Initialize debug logging (called once after singleton creation)
21
+ */
22
+ initializeDebugLog() {
23
+ if (this.debugLogInitialized) {
24
+ return;
25
+ }
26
+ this.debugLogInitialized = true;
27
+ // Skip debug logging during tests
28
+ if (process.env.NODE_ENV === 'test' || process.env.VITEST) {
29
+ return;
30
+ }
31
+ // Create logs directory if it doesn't exist
32
+ const logsDir = join(process.cwd(), 'logs');
33
+ if (!existsSync(logsDir)) {
34
+ mkdir(logsDir, { recursive: true }).catch(() => {
35
+ // Ignore mkdir errors
36
+ });
37
+ }
38
+ // Create debug log file with timestamp in logs directory
39
+ const logTimestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
40
+ this.debugLogFile = join(logsDir, `debug-agent-${logTimestamp}.log`);
41
+ this.debugLog(`=== DEBUG LOG START ===`);
42
+ this.debugLog(`Session started at: ${new Date().toISOString()}`);
43
+ this.debugLog(`Working directory: ${process.cwd()}`);
44
+ this.debugLog(`Node version: ${process.version}`);
45
+ this.debugLog(`Platform: ${process.platform}`);
46
+ }
47
+ /**
48
+ * Write to debug log file
49
+ */
50
+ async debugLog(message) {
51
+ if (!this.debugLogFile)
52
+ return;
53
+ try {
54
+ const timestamp = new Date().toISOString();
55
+ await appendFile(this.debugLogFile, `[${timestamp}] ${message}\n`);
56
+ }
57
+ catch {
58
+ // Ignore errors in debug logging
59
+ }
60
+ }
61
+ /**
62
+ * Set callback for logging (used by watcher UI)
63
+ */
64
+ setLogCallback(callback) {
65
+ this.onLog = callback;
66
+ }
67
+ /**
68
+ * Detect available CLI tools
69
+ */
70
+ async detectCLITools() {
71
+ this.initializeDebugLog(); // Initialize debug log on first real use
72
+ const tools = [
73
+ { name: 'cursor-agent', command: 'cursor-agent', available: false },
74
+ // { name: 'claude-code', command: 'claude', available: false }, // Temporarily disabled for v0.10.0
75
+ // { name: 'gemini-cli', command: 'gemini', available: false }, // Temporarily disabled for v0.10.0
76
+ ];
77
+ for (const tool of tools) {
78
+ try {
79
+ const proc = spawn(tool.command, ['--version']);
80
+ let stdout = '';
81
+ if (proc.stdout) {
82
+ proc.stdout.setEncoding('utf8');
83
+ proc.stdout.on('data', (data) => {
84
+ stdout += data;
85
+ });
86
+ }
87
+ const exitCode = await new Promise((resolve, reject) => {
88
+ const timeout = setTimeout(() => {
89
+ proc.kill('SIGTERM');
90
+ reject(new Error('Timeout'));
91
+ }, 5000);
92
+ proc.on('exit', (code) => {
93
+ clearTimeout(timeout);
94
+ resolve(code ?? 1);
95
+ });
96
+ proc.on('error', (error) => {
97
+ clearTimeout(timeout);
98
+ reject(error);
99
+ });
100
+ });
101
+ if (exitCode === 0) {
102
+ tool.available = true;
103
+ tool.version = stdout.trim();
104
+ this.logger.info(`Detected CLI tool: ${tool.name}`, { version: tool.version });
105
+ }
106
+ }
107
+ catch (error) {
108
+ this.logger.debug(`CLI tool not available: ${tool.name}`, { error: String(error) });
109
+ }
110
+ }
111
+ return tools.filter((tool) => tool.available);
112
+ }
113
+ /**
114
+ * Send command to CLI tool
115
+ */
116
+ async sendCommandToCLI(toolName, command, options = {}) {
117
+ const startTime = Date.now();
118
+ // Check for deprecated tools
119
+ const deprecatedTools = ['cursor-cli', 'claude-cli', 'gemini-cli-legacy'];
120
+ if (deprecatedTools.includes(toolName)) {
121
+ const duration = Date.now() - startTime;
122
+ return {
123
+ success: false,
124
+ output: '',
125
+ error: `Tool '${toolName}' is deprecated and not supported. Please use 'cursor-agent', 'claude-code', or 'gemini-cli' instead.`,
126
+ duration,
127
+ exitCode: 1,
128
+ };
129
+ }
130
+ // Check for supported tools
131
+ const supportedTools = ['cursor-agent']; // Temporarily only cursor-agent for v0.10.0
132
+ if (!supportedTools.includes(toolName)) {
133
+ const duration = Date.now() - startTime;
134
+ return {
135
+ success: false,
136
+ output: '',
137
+ error: `Tool '${toolName}' is not supported. Supported tools are: ${supportedTools.join(', ')}`,
138
+ duration,
139
+ exitCode: 1,
140
+ };
141
+ }
142
+ // cursor-agent needs more time to connect to remote server and process
143
+ // Set to 30 minutes for long-running tasks
144
+ const defaultTimeout = toolName === 'cursor-agent' ? 1800000 : 30000; // 30 minutes for cursor-agent
145
+ const timeout = options.timeout || this.config.timeouts?.cliResponse || defaultTimeout;
146
+ this.logger.cliCommand(command, toolName);
147
+ try {
148
+ if (toolName === 'cursor-agent') {
149
+ // cursor-agent expects: cursor-agent -p --force --approve-mcps --output-format stream-json --stream-partial-output "PROMPT"
150
+ // Using -p (print mode) for non-interactive use with all tools enabled
151
+ // --force: Allow commands unless explicitly denied
152
+ // --approve-mcps: Allow all MCP servers without asking
153
+ // --output-format stream-json: Stream output in JSON format
154
+ // --stream-partial-output: Stream partial output as individual text deltas
155
+ const args = [
156
+ '-p',
157
+ '--force',
158
+ '--approve-mcps',
159
+ '--output-format',
160
+ 'stream-json',
161
+ '--stream-partial-output',
162
+ command,
163
+ ];
164
+ await this.debugLog(`\n=== CURSOR-AGENT COMMAND START ===`);
165
+ await this.debugLog(`Command: ${command}`);
166
+ await this.debugLog(`Full command line: cursor-agent ${args.join(' ')}`);
167
+ await this.debugLog(`Timestamp: ${new Date().toISOString()}`);
168
+ await this.debugLog(`===================================\n`);
169
+ const proc = spawn(toolName, args, {
170
+ cwd: options.workingDirectory,
171
+ env: {
172
+ ...process.env,
173
+ ...options.env,
174
+ // Disable Node.js buffering for immediate output
175
+ NODE_NO_READLINE: '1',
176
+ },
177
+ stdio: ['ignore', 'pipe', 'pipe'],
178
+ shell: false,
179
+ });
180
+ if (this.onLog) {
181
+ this.onLog('info', '🔗 Connecting to cursor-agent...');
182
+ this.onLog('info', ' (This may take 30-60 seconds to connect to remote server)');
183
+ }
184
+ else {
185
+ console.log('🔗 Connecting to cursor-agent...');
186
+ console.log(' (This may take 30-60 seconds to connect to remote server)');
187
+ }
188
+ // Progress indicator (silent in watcher mode)
189
+ let dots = 0;
190
+ const progressInterval = setInterval(() => {
191
+ dots = (dots + 1) % 4;
192
+ if (!this.onLog) {
193
+ process.stdout.write('\r⏳ Waiting' + '.'.repeat(dots) + ' '.repeat(3 - dots));
194
+ }
195
+ }, 500);
196
+ // Stream output in real-time with parser
197
+ let hasOutput = false;
198
+ let stdout = '';
199
+ let stderr = '';
200
+ const parser = new CursorAgentStreamParser();
201
+ // Promise to resolve when parser completes
202
+ let resolveCompletion;
203
+ const completionPromise = new Promise((resolve) => {
204
+ resolveCompletion = resolve;
205
+ });
206
+ // Set completion callback
207
+ parser.onComplete(() => {
208
+ // Silent completion - no debug logs
209
+ if (resolveCompletion) {
210
+ resolveCompletion();
211
+ }
212
+ // Give a small delay for any remaining output, then kill
213
+ setTimeout(() => {
214
+ if (!proc.killed) {
215
+ proc.kill('SIGTERM');
216
+ }
217
+ }, 500);
218
+ });
219
+ // Set event callback to forward to onLog or console
220
+ parser.onEvent((type, message) => {
221
+ if (this.onLog) {
222
+ // Watcher mode - send to UI
223
+ const logType = type === 'completion' ? 'success' : 'info';
224
+ this.onLog(logType, message);
225
+ }
226
+ else {
227
+ // CLI mode - print to console with colors
228
+ if (type === 'tool') {
229
+ // Tool calls in cyan
230
+ console.log(` \x1b[36m${message}\x1b[0m`);
231
+ }
232
+ else if (type === 'text') {
233
+ // Text generation in gray
234
+ console.log(` \x1b[90m${message}\x1b[0m`);
235
+ }
236
+ else if (type === 'completion') {
237
+ // Completion in green
238
+ console.log(`\n\x1b[32m${message}\x1b[0m`);
239
+ }
240
+ }
241
+ });
242
+ if (proc.stdout) {
243
+ proc.stdout.setEncoding('utf8');
244
+ let buffer = '';
245
+ proc.stdout.on('data', (data) => {
246
+ if (!hasOutput) {
247
+ clearInterval(progressInterval);
248
+ if (this.onLog) {
249
+ this.onLog('success', '✅ Received first response from cursor-agent!');
250
+ }
251
+ else {
252
+ console.log('\n✅ Received first response from cursor-agent!');
253
+ console.log(''); // Empty line for better formatting
254
+ }
255
+ hasOutput = true;
256
+ this.debugLog('First response received from cursor-agent');
257
+ }
258
+ stdout += data;
259
+ buffer += data;
260
+ // Log raw output to debug file
261
+ this.debugLog(`STDOUT RAW: ${data}`);
262
+ // Process complete lines
263
+ const lines = buffer.split('\n');
264
+ buffer = lines.pop() || ''; // Keep incomplete line in buffer
265
+ for (const line of lines) {
266
+ if (line.trim()) {
267
+ // Parse and process each JSON event
268
+ this.debugLog(`STDOUT LINE: ${line}`);
269
+ const event = parseStreamLine(line);
270
+ if (event) {
271
+ this.debugLog(`EVENT PARSED: type=${event.type}`);
272
+ parser.processEvent(event);
273
+ }
274
+ else {
275
+ this.debugLog(`EVENT PARSE FAILED: ${line.substring(0, 100)}`);
276
+ }
277
+ }
278
+ }
279
+ });
280
+ }
281
+ if (proc.stderr) {
282
+ proc.stderr.setEncoding('utf8');
283
+ proc.stderr.on('data', (data) => {
284
+ if (!hasOutput) {
285
+ clearInterval(progressInterval);
286
+ hasOutput = true;
287
+ }
288
+ stderr += data;
289
+ // Only write to stderr in CLI mode, not watcher mode
290
+ if (!this.onLog) {
291
+ process.stderr.write('⚠️ ' + data);
292
+ }
293
+ });
294
+ }
295
+ // Store active process
296
+ const processId = `${toolName}-${Date.now()}`;
297
+ this.activeProcesses.set(processId, proc);
298
+ // Wait for process to complete with timeout or completion
299
+ const result = await new Promise((resolve, reject) => {
300
+ const timeoutId = setTimeout(() => {
301
+ proc.kill('SIGTERM');
302
+ reject(new Error(`Command timed out after ${timeout} milliseconds`));
303
+ }, timeout);
304
+ // Resolve when parser detects completion
305
+ completionPromise.then(async () => {
306
+ await this.debugLog('Parser completion event received');
307
+ await this.debugLog(`Process PID: ${proc.pid}, killed: ${proc.killed}, exitCode: ${proc.exitCode}`);
308
+ clearInterval(progressInterval);
309
+ clearTimeout(timeoutId);
310
+ // FORCE KILL IMMEDIATELY after completion
311
+ if (!proc.killed && proc.exitCode === null) {
312
+ await this.debugLog(`Forcing process kill immediately after completion`);
313
+ try {
314
+ proc.kill('SIGTERM');
315
+ // Give 100ms for graceful shutdown, then SIGKILL
316
+ setTimeout(() => {
317
+ if (!proc.killed && proc.exitCode === null) {
318
+ proc.kill('SIGKILL');
319
+ }
320
+ }, 100);
321
+ }
322
+ catch (error) {
323
+ await this.debugLog(`Error killing process: ${error}`);
324
+ }
325
+ }
326
+ // Wait max 500ms for process to exit
327
+ const checkExit = setInterval(async () => {
328
+ if (proc.killed || proc.exitCode !== null) {
329
+ await this.debugLog(`Process exited with code: ${proc.exitCode}`);
330
+ clearInterval(checkExit);
331
+ resolve({ exitCode: proc.exitCode ?? 0, stdout, stderr });
332
+ }
333
+ }, 50);
334
+ // Force resolve after 500ms
335
+ setTimeout(async () => {
336
+ clearInterval(checkExit);
337
+ if (!proc.killed) {
338
+ await this.debugLog(`Process STILL alive after 500ms, forcing SIGKILL`);
339
+ try {
340
+ proc.kill('SIGKILL');
341
+ }
342
+ catch (error) {
343
+ await this.debugLog(`Error force killing: ${error}`);
344
+ }
345
+ }
346
+ resolve({ exitCode: 0, stdout, stderr });
347
+ }, 500);
348
+ });
349
+ proc.on('exit', async (code, signal) => {
350
+ await this.debugLog(`Process 'exit' event: code=${code}, signal=${signal}`);
351
+ clearInterval(progressInterval);
352
+ clearTimeout(timeoutId);
353
+ resolve({ exitCode: code ?? 0, stdout, stderr });
354
+ });
355
+ proc.on('error', async (error) => {
356
+ await this.debugLog(`Process 'error' event: ${error.message}`);
357
+ clearInterval(progressInterval);
358
+ clearTimeout(timeoutId);
359
+ reject(error);
360
+ });
361
+ });
362
+ const duration = Date.now() - startTime;
363
+ // Debug logging for cursor-agent completion
364
+ await this.debugLog(`\n=== CURSOR-AGENT COMMAND END ===`);
365
+ await this.debugLog(`Duration: ${duration}ms`);
366
+ await this.debugLog(`Exit code: ${result.exitCode}`);
367
+ await this.debugLog(`Total stdout length: ${stdout.length} chars`);
368
+ await this.debugLog(`Total stderr length: ${stderr.length} chars`);
369
+ await this.debugLog(`Parser completed: ${parser.isCompleted()}`);
370
+ await this.debugLog(`Active processes remaining: ${this.activeProcesses.size}`);
371
+ await this.debugLog(`=================================\n`);
372
+ // Remove from active processes
373
+ this.activeProcesses.delete(processId);
374
+ // Get parsed result from stream parser
375
+ const parsedResult = parser.getResult();
376
+ const response = {
377
+ success: result.exitCode === 0,
378
+ output: parsedResult.text || result.stdout, // Use parsed text if available
379
+ error: result.stderr,
380
+ duration,
381
+ exitCode: result.exitCode,
382
+ };
383
+ // Log summary
384
+ if (this.onLog) {
385
+ this.onLog('info', `Summary: ${parsedResult.text.length} chars, ${parsedResult.toolCalls.length} tools, ${Math.round(duration / 1000)}s`);
386
+ this.onLog('info', `Debug log: ${this.debugLogFile}`);
387
+ }
388
+ else {
389
+ console.log('\n📊 Summary:');
390
+ console.log(` Text generated: ${parsedResult.text.length} chars`);
391
+ console.log(` Tool calls: ${parsedResult.toolCalls.length}`);
392
+ console.log(` Duration: ${Math.round(duration / 1000)}s`);
393
+ }
394
+ this.logger.cliResponse(toolName, parsedResult.text || result.stdout, duration);
395
+ return response;
396
+ }
397
+ else if (toolName === 'claude-code') {
398
+ // claude-code expects: claude --headless "PROMPT"
399
+ const args = ['--headless', command];
400
+ const proc = spawn('claude', args, {
401
+ cwd: options.workingDirectory,
402
+ env: {
403
+ ...process.env,
404
+ ...options.env,
405
+ },
406
+ stdio: ['ignore', 'pipe', 'pipe'],
407
+ shell: false,
408
+ });
409
+ if (this.onLog) {
410
+ this.onLog('info', '🔗 Connecting to claude-code...');
411
+ }
412
+ else {
413
+ console.log('🔗 Connecting to claude-code...');
414
+ }
415
+ // Progress indicator (silent in watcher mode)
416
+ let dots = 0;
417
+ const progressInterval = setInterval(() => {
418
+ dots = (dots + 1) % 4;
419
+ if (!this.onLog) {
420
+ process.stdout.write('\r⏳ Waiting' + '.'.repeat(dots) + ' '.repeat(3 - dots));
421
+ }
422
+ }, 500);
423
+ // Stream output in real-time with parser
424
+ let hasOutput = false;
425
+ let stdout = '';
426
+ let stderr = '';
427
+ const parser = new ClaudeCodeStreamParser();
428
+ // Promise to resolve when parser completes
429
+ let resolveCompletion;
430
+ const completionPromise = new Promise((resolve) => {
431
+ resolveCompletion = resolve;
432
+ });
433
+ // Set completion callback
434
+ parser.onComplete(() => {
435
+ console.log('\n✅ claude-code completed, terminating process...');
436
+ if (resolveCompletion) {
437
+ resolveCompletion();
438
+ }
439
+ // Give a small delay for any remaining output, then kill
440
+ setTimeout(() => {
441
+ if (!proc.killed) {
442
+ proc.kill('SIGTERM');
443
+ }
444
+ }, 500);
445
+ });
446
+ if (proc.stdout) {
447
+ proc.stdout.setEncoding('utf8');
448
+ let buffer = '';
449
+ proc.stdout.on('data', (data) => {
450
+ if (!hasOutput) {
451
+ clearInterval(progressInterval);
452
+ console.log('\n✅ Received first response from claude-code!');
453
+ console.log(''); // Empty line for better formatting
454
+ hasOutput = true;
455
+ }
456
+ stdout += data;
457
+ buffer += data;
458
+ // Process complete lines
459
+ const lines = buffer.split('\n');
460
+ buffer = lines.pop() || ''; // Keep incomplete line in buffer
461
+ for (const line of lines) {
462
+ if (line.trim()) {
463
+ // Parse and process each line
464
+ const event = parseClaudeCodeLine(line);
465
+ if (event) {
466
+ parser.processEvent(event);
467
+ // Check if parser completed
468
+ if (parser.isCompleted()) {
469
+ console.log('✅ Parser reports completed!');
470
+ }
471
+ }
472
+ }
473
+ }
474
+ });
475
+ }
476
+ if (proc.stderr) {
477
+ proc.stderr.setEncoding('utf8');
478
+ proc.stderr.on('data', (data) => {
479
+ if (!hasOutput) {
480
+ clearInterval(progressInterval);
481
+ hasOutput = true;
482
+ }
483
+ stderr += data;
484
+ // Only write to stderr in CLI mode, not watcher mode
485
+ if (!this.onLog) {
486
+ process.stderr.write('⚠️ ' + data);
487
+ }
488
+ });
489
+ }
490
+ // Store active process
491
+ const processId = `${toolName}-${Date.now()}`;
492
+ this.activeProcesses.set(processId, proc);
493
+ // Wait for process to complete with timeout or completion
494
+ const result = await new Promise((resolve, reject) => {
495
+ const timeoutId = setTimeout(() => {
496
+ proc.kill('SIGTERM');
497
+ reject(new Error(`Command timed out after ${timeout} milliseconds`));
498
+ }, timeout);
499
+ // Resolve when parser detects completion
500
+ completionPromise.then(() => {
501
+ console.log('✅ completionPromise resolved!');
502
+ clearInterval(progressInterval);
503
+ clearTimeout(timeoutId);
504
+ // Process might still be alive, wait a bit for graceful exit
505
+ const checkExit = setInterval(() => {
506
+ if (proc.killed || proc.exitCode !== null) {
507
+ clearInterval(checkExit);
508
+ resolve({ exitCode: proc.exitCode ?? 0, stdout, stderr });
509
+ }
510
+ }, 100);
511
+ // Force resolve after 2 seconds if still hanging
512
+ setTimeout(() => {
513
+ clearInterval(checkExit);
514
+ if (!proc.killed) {
515
+ proc.kill('SIGKILL');
516
+ }
517
+ resolve({ exitCode: 0, stdout, stderr });
518
+ }, 2000);
519
+ });
520
+ proc.on('exit', (code, _signal) => {
521
+ clearInterval(progressInterval);
522
+ clearTimeout(timeoutId);
523
+ resolve({ exitCode: code ?? 0, stdout, stderr });
524
+ });
525
+ proc.on('error', (error) => {
526
+ clearInterval(progressInterval);
527
+ clearTimeout(timeoutId);
528
+ reject(error);
529
+ });
530
+ });
531
+ const duration = Date.now() - startTime;
532
+ // Remove from active processes
533
+ this.activeProcesses.delete(processId);
534
+ // Get parsed result from stream parser
535
+ const parsedResult = parser.getResult();
536
+ const response = {
537
+ success: result.exitCode === 0,
538
+ output: parsedResult.text || result.stdout, // Use parsed text if available
539
+ error: result.stderr,
540
+ duration,
541
+ exitCode: result.exitCode,
542
+ };
543
+ // Log summary
544
+ console.log('\n📊 Summary:');
545
+ console.log(` Text generated: ${parsedResult.text.length} chars`);
546
+ console.log(` Tool calls: ${parsedResult.toolCalls.length}`);
547
+ console.log(` Duration: ${Math.round(duration / 1000)}s`);
548
+ this.logger.cliResponse(toolName, parsedResult.text || result.stdout, duration);
549
+ return response;
550
+ }
551
+ else if (toolName === 'gemini-cli') {
552
+ // gemini-cli expects: gemini "PROMPT"
553
+ const args = [command];
554
+ const proc = spawn('gemini', args, {
555
+ cwd: options.workingDirectory,
556
+ env: {
557
+ ...process.env,
558
+ ...options.env,
559
+ },
560
+ stdio: ['ignore', 'pipe', 'pipe'],
561
+ shell: false,
562
+ });
563
+ if (this.onLog) {
564
+ this.onLog('info', '🔗 Connecting to gemini-cli...');
565
+ }
566
+ else {
567
+ console.log('🔗 Connecting to gemini-cli...');
568
+ }
569
+ // Progress indicator (silent in watcher mode)
570
+ let dots = 0;
571
+ const progressInterval = setInterval(() => {
572
+ dots = (dots + 1) % 4;
573
+ if (!this.onLog) {
574
+ process.stdout.write('\r⏳ Waiting' + '.'.repeat(dots) + ' '.repeat(3 - dots));
575
+ }
576
+ }, 500);
577
+ // Stream output in real-time with parser
578
+ let hasOutput = false;
579
+ let stdout = '';
580
+ let stderr = '';
581
+ const parser = new GeminiStreamParser();
582
+ // Promise to resolve when parser completes
583
+ let resolveCompletion;
584
+ const completionPromise = new Promise((resolve) => {
585
+ resolveCompletion = resolve;
586
+ });
587
+ // Set completion callback
588
+ parser.onComplete(() => {
589
+ console.log('\n✅ gemini-cli completed, terminating process...');
590
+ if (resolveCompletion) {
591
+ resolveCompletion();
592
+ }
593
+ // Give a small delay for any remaining output, then kill
594
+ setTimeout(() => {
595
+ if (!proc.killed) {
596
+ proc.kill('SIGTERM');
597
+ }
598
+ }, 500);
599
+ });
600
+ if (proc.stdout) {
601
+ proc.stdout.setEncoding('utf8');
602
+ let buffer = '';
603
+ proc.stdout.on('data', (data) => {
604
+ if (!hasOutput) {
605
+ clearInterval(progressInterval);
606
+ console.log('\n✅ Received first response from gemini-cli!');
607
+ console.log(''); // Empty line for better formatting
608
+ hasOutput = true;
609
+ }
610
+ stdout += data;
611
+ buffer += data;
612
+ // Process complete lines
613
+ const lines = buffer.split('\n');
614
+ buffer = lines.pop() || ''; // Keep incomplete line in buffer
615
+ for (const line of lines) {
616
+ if (line.trim()) {
617
+ // Parse and process each line
618
+ const event = parseGeminiLine(line);
619
+ if (event) {
620
+ parser.processEvent(event);
621
+ // Check if parser completed
622
+ if (parser.isCompleted()) {
623
+ console.log('✅ Parser reports completed!');
624
+ }
625
+ }
626
+ }
627
+ }
628
+ });
629
+ }
630
+ if (proc.stderr) {
631
+ proc.stderr.setEncoding('utf8');
632
+ proc.stderr.on('data', (data) => {
633
+ if (!hasOutput) {
634
+ clearInterval(progressInterval);
635
+ hasOutput = true;
636
+ }
637
+ stderr += data;
638
+ // Only write to stderr in CLI mode, not watcher mode
639
+ if (!this.onLog) {
640
+ process.stderr.write('⚠️ ' + data);
641
+ }
642
+ });
643
+ }
644
+ // Store active process
645
+ const processId = `${toolName}-${Date.now()}`;
646
+ this.activeProcesses.set(processId, proc);
647
+ // Wait for process to complete with timeout or completion
648
+ const result = await new Promise((resolve, reject) => {
649
+ const timeoutId = setTimeout(() => {
650
+ proc.kill('SIGTERM');
651
+ reject(new Error(`Command timed out after ${timeout} milliseconds`));
652
+ }, timeout);
653
+ // Resolve when parser detects completion
654
+ completionPromise.then(() => {
655
+ console.log('✅ completionPromise resolved!');
656
+ clearInterval(progressInterval);
657
+ clearTimeout(timeoutId);
658
+ // Process might still be alive, wait a bit for graceful exit
659
+ const checkExit = setInterval(() => {
660
+ if (proc.killed || proc.exitCode !== null) {
661
+ clearInterval(checkExit);
662
+ resolve({ exitCode: proc.exitCode ?? 0, stdout, stderr });
663
+ }
664
+ }, 100);
665
+ // Force resolve after 2 seconds if still hanging
666
+ setTimeout(() => {
667
+ clearInterval(checkExit);
668
+ if (!proc.killed) {
669
+ proc.kill('SIGKILL');
670
+ }
671
+ resolve({ exitCode: 0, stdout, stderr });
672
+ }, 2000);
673
+ });
674
+ proc.on('exit', (code, _signal) => {
675
+ clearInterval(progressInterval);
676
+ clearTimeout(timeoutId);
677
+ resolve({ exitCode: code ?? 0, stdout, stderr });
678
+ });
679
+ proc.on('error', (error) => {
680
+ clearInterval(progressInterval);
681
+ clearTimeout(timeoutId);
682
+ reject(error);
683
+ });
684
+ });
685
+ const duration = Date.now() - startTime;
686
+ // Remove from active processes
687
+ this.activeProcesses.delete(processId);
688
+ // Get parsed result from stream parser
689
+ const parsedResult = parser.getResult();
690
+ const response = {
691
+ success: result.exitCode === 0,
692
+ output: parsedResult.text || result.stdout, // Use parsed text if available
693
+ error: result.stderr,
694
+ duration,
695
+ exitCode: result.exitCode,
696
+ };
697
+ // Log summary
698
+ console.log('\n📊 Summary:');
699
+ console.log(` Text generated: ${parsedResult.text.length} chars`);
700
+ console.log(` Tool calls: ${parsedResult.toolCalls.length}`);
701
+ console.log(` Duration: ${Math.round(duration / 1000)}s`);
702
+ this.logger.cliResponse(toolName, parsedResult.text || result.stdout, duration);
703
+ return response;
704
+ }
705
+ else {
706
+ // For other tools, keep using execa (need to import it)
707
+ throw new Error(`Tool ${toolName} not yet implemented with spawn`);
708
+ }
709
+ }
710
+ catch (error) {
711
+ const duration = Date.now() - startTime;
712
+ const response = {
713
+ success: false,
714
+ output: '',
715
+ error: error instanceof Error ? error.message : String(error),
716
+ duration,
717
+ exitCode: error.exitCode || 1,
718
+ };
719
+ this.logger.error(`CLI command failed: ${command}`, {
720
+ tool: toolName,
721
+ error: error instanceof Error ? error.message : String(error),
722
+ duration,
723
+ });
724
+ return response;
725
+ }
726
+ }
727
+ /**
728
+ * Wait for CLI completion with timeout handling
729
+ */
730
+ async waitForCompletion(toolName, command, timeout) {
731
+ const response = await this.sendCommandToCLI(toolName, command, { timeout });
732
+ if (!response.success && response.error?.includes('timeout')) {
733
+ this.logger.warn(`CLI timeout detected for ${toolName}`, { command });
734
+ return await this.handleTimeout(toolName, command);
735
+ }
736
+ return response;
737
+ }
738
+ /**
739
+ * Handle CLI timeout by sending continue command
740
+ */
741
+ async handleTimeout(toolName, originalCommand) {
742
+ this.logger.info(`Handling timeout for ${toolName}`, { originalCommand });
743
+ // Send continue command
744
+ const continueResponse = await this.sendCommandToCLI(toolName, 'continue', {
745
+ timeout: this.config.timeouts.cliResponse,
746
+ });
747
+ if (continueResponse.success) {
748
+ this.logger.info(`Continue command successful for ${toolName}`);
749
+ return continueResponse;
750
+ }
751
+ else {
752
+ this.logger.error(`Continue command failed for ${toolName}`, {
753
+ error: continueResponse.error,
754
+ });
755
+ return continueResponse;
756
+ }
757
+ }
758
+ /**
759
+ * Send task implementation command
760
+ */
761
+ async sendTaskCommand(toolName, task) {
762
+ let command;
763
+ if (toolName === 'cursor-agent') {
764
+ command = `Implement task: ${task.title}. Description: ${task.description}`;
765
+ }
766
+ else if (toolName === 'claude-code') {
767
+ command = `Implement the following task: ${task.title}. Description: ${task.description}`;
768
+ }
769
+ else if (toolName === 'gemini-cli') {
770
+ command = `Please implement this task: ${task.title}. Description: ${task.description}`;
771
+ }
772
+ else {
773
+ command = `Implement task "${task.title}" from OpenSpec. Description: ${task.description}`;
774
+ }
775
+ // cursor-agent needs extra time for complex tasks (30 minutes)
776
+ const timeout = toolName === 'cursor-agent' ? 1800000 : undefined;
777
+ return await this.sendCommandToCLI(toolName, command, { timeout });
778
+ }
779
+ /**
780
+ * Send continue implementation command
781
+ */
782
+ async sendContinueCommand(toolName, iterations = 10) {
783
+ let command;
784
+ if (toolName === 'cursor-agent') {
785
+ command = `Continue implementation ${iterations} times`;
786
+ }
787
+ else if (toolName === 'claude-code') {
788
+ command = `Continue the implementation ${iterations} more times`;
789
+ }
790
+ else if (toolName === 'gemini-cli') {
791
+ command = `Please continue the implementation ${iterations} times`;
792
+ }
793
+ else {
794
+ command = `Continue implementation ${iterations}x`;
795
+ }
796
+ // cursor-agent needs extra time (30 minutes)
797
+ const timeout = toolName === 'cursor-agent' ? 1800000 : undefined;
798
+ return await this.sendCommandToCLI(toolName, command, { timeout });
799
+ }
800
+ /**
801
+ * Send test command
802
+ */
803
+ async sendTestCommand(toolName) {
804
+ let command;
805
+ if (toolName === 'cursor-agent') {
806
+ command = `Execute comprehensive test suite with automatic error correction:
807
+
808
+ 1. Run the project's test command (npm test, cargo test, pytest, go test, etc.)
809
+ 2. Analyze ALL test failures and errors in detail
810
+ 3. If there are ANY test failures:
811
+ a. Read the failing test files and implementation code
812
+ b. Identify the root cause of each failure
813
+ c. Fix the implementation code OR fix the test if it's incorrect
814
+ d. Run tests again to verify fixes
815
+ e. Repeat steps a-d until ALL tests pass (100%)
816
+ 4. Check test coverage:
817
+ a. Run coverage command (npm run test:coverage, cargo llvm-cov, pytest --cov, etc.)
818
+ b. If coverage is below threshold (95%): Add more tests to increase coverage
819
+ c. Run coverage again until threshold is met
820
+ 5. If all tests pass with adequate coverage: Report success with coverage percentage
821
+ 6. Maximum 5 correction attempts
822
+
823
+ CRITICAL: ALL tests must pass (100%) and coverage must meet threshold (95%+) before proceeding.`;
824
+ }
825
+ else if (toolName === 'claude-code') {
826
+ command =
827
+ 'Run all tests and ensure 100% passing. If tests fail, fix the issues and rerun. Check coverage meets 95%+ threshold. Maximum 5 attempts.';
828
+ }
829
+ else if (toolName === 'gemini-cli') {
830
+ command = 'Please run tests, fix failures until all pass, and ensure coverage is 95%+.';
831
+ }
832
+ else {
833
+ command = 'Run tests, fix failures, ensure all pass and coverage is 95%+';
834
+ }
835
+ return await this.sendCommandToCLI(toolName, command, { timeout: 600000 }); // 10 minutes for multiple test runs
836
+ }
837
+ /**
838
+ * Send lint command
839
+ */
840
+ async sendLintCommand(toolName) {
841
+ let command;
842
+ if (toolName === 'cursor-agent') {
843
+ command = `Execute linting quality checks with automatic error correction:
844
+
845
+ 1. Run the project's lint command (npm run lint, cargo clippy, ruff check, etc.)
846
+ 2. Analyze ALL lint errors and warnings carefully
847
+ 3. If there are ANY lint issues:
848
+ a. Read the files with lint errors
849
+ b. Fix ALL issues according to the linter's suggestions
850
+ c. Run lint again to verify fixes
851
+ d. Repeat steps a-c until linting passes with ZERO warnings
852
+ 4. If linting passes: Report success
853
+ 5. Maximum 3 correction attempts
854
+
855
+ CRITICAL: Do NOT proceed until linting passes with 0 warnings.`;
856
+ }
857
+ else if (toolName === 'claude-code') {
858
+ command =
859
+ 'Run linting checks and fix all issues. Keep fixing until linting passes with 0 warnings. Maximum 3 attempts.';
860
+ }
861
+ else if (toolName === 'gemini-cli') {
862
+ command =
863
+ 'Please run lint checks, fix any issues found, and repeat until passing with 0 warnings.';
864
+ }
865
+ else {
866
+ command = 'Run lint checks and fix any issues until passing with 0 warnings';
867
+ }
868
+ return await this.sendCommandToCLI(toolName, command, { timeout: 300000 }); // 5 minutes for multiple attempts
869
+ }
870
+ /**
871
+ * Send format command
872
+ */
873
+ async sendFormatCommand(toolName) {
874
+ let command;
875
+ if (toolName === 'cursor-agent') {
876
+ command = `Apply code formatting according to project standards:
877
+
878
+ 1. Run the project's format command (npm run format, cargo fmt, black ., gofmt -w ., etc.)
879
+ 2. If there are any formatting issues:
880
+ a. Apply automatic formatting fixes
881
+ b. Verify formatting is correct by running format check again
882
+ 3. Ensure all files pass formatting standards
883
+ 4. Report success when formatting is complete
884
+
885
+ This is typically automatic and should complete quickly.`;
886
+ }
887
+ else if (toolName === 'claude-code') {
888
+ command = 'Format all code according to project standards using the project formatter.';
889
+ }
890
+ else if (toolName === 'gemini-cli') {
891
+ command = 'Please format the code according to project standards.';
892
+ }
893
+ else {
894
+ command = 'Format code according to project standards';
895
+ }
896
+ return await this.sendCommandToCLI(toolName, command, { timeout: 120000 }); // 2 minutes
897
+ }
898
+ /**
899
+ * Send commit command
900
+ */
901
+ async sendCommitCommand(toolName, message) {
902
+ let command;
903
+ if (toolName === 'cursor-agent') {
904
+ command = `Commit all changes to Git with proper commit message:
905
+
906
+ 1. Stage all changes: git add .
907
+ 2. Create commit with this EXACT message:
908
+ "${message}"
909
+ 3. Verify commit was created successfully
910
+ 4. DO NOT push (user will push manually)
911
+
912
+ Report the commit hash when complete.`;
913
+ }
914
+ else if (toolName === 'claude-code') {
915
+ command = `Stage all changes (git add .) and commit with message: "${message}". Do not push.`;
916
+ }
917
+ else if (toolName === 'gemini-cli') {
918
+ command = `Please commit changes with message: "${message}". Do not push.`;
919
+ }
920
+ else {
921
+ command = `Commit changes with message: ${message}`;
922
+ }
923
+ return await this.sendCommandToCLI(toolName, command, { timeout: 30000 });
924
+ }
925
+ /**
926
+ * Check if CLI tool is responsive
927
+ */
928
+ async checkCLIHealth(toolName) {
929
+ try {
930
+ const response = await this.sendCommandToCLI(toolName, 'ping', { timeout: 5000 });
931
+ return response.success;
932
+ }
933
+ catch {
934
+ return false;
935
+ }
936
+ }
937
+ /**
938
+ * Get CLI tool capabilities
939
+ */
940
+ async getCLICapabilities(toolName) {
941
+ try {
942
+ // Use very short timeout in test environment to avoid hanging on Windows
943
+ const timeout = process.env.NODE_ENV === 'test' || process.env.VITEST ? 100 : 10000;
944
+ const response = await this.sendCommandToCLI(toolName, 'capabilities', { timeout });
945
+ if (response.success) {
946
+ return response.output.split('\n').filter((line) => line.trim());
947
+ }
948
+ return [];
949
+ }
950
+ catch {
951
+ return [];
952
+ }
953
+ }
954
+ /**
955
+ * Execute workflow step
956
+ */
957
+ async executeWorkflowStep(toolName, step, context) {
958
+ const startTime = Date.now();
959
+ this.logger.info(`Executing workflow step: ${step}`, { tool: toolName, context });
960
+ let response;
961
+ switch (step) {
962
+ case 'implement':
963
+ if (context?.task) {
964
+ response = await this.sendTaskCommand(toolName, context.task);
965
+ }
966
+ else {
967
+ response = await this.sendCommandToCLI(toolName, 'Implement current task');
968
+ }
969
+ break;
970
+ case 'test':
971
+ response = await this.sendTestCommand(toolName);
972
+ break;
973
+ case 'lint':
974
+ response = await this.sendLintCommand(toolName);
975
+ break;
976
+ case 'format':
977
+ response = await this.sendFormatCommand(toolName);
978
+ break;
979
+ case 'commit': {
980
+ const message = context?.message || 'Auto-commit from rulebook agent';
981
+ response = await this.sendCommitCommand(toolName, message);
982
+ break;
983
+ }
984
+ default:
985
+ throw new Error(`Unknown workflow step: ${step}`);
986
+ }
987
+ const duration = Date.now() - startTime;
988
+ this.logger.info(`Workflow step completed: ${step}`, {
989
+ tool: toolName,
990
+ success: response.success,
991
+ duration,
992
+ });
993
+ return response;
994
+ }
995
+ /**
996
+ * Kill all active processes (cleanup)
997
+ */
998
+ async killAllProcesses() {
999
+ if (this.activeProcesses.size === 0) {
1000
+ return;
1001
+ }
1002
+ await this.debugLog(`\n=== KILLING ALL ACTIVE PROCESSES ===`);
1003
+ await this.debugLog(`Active processes: ${this.activeProcesses.size}`);
1004
+ const killPromises = [];
1005
+ for (const [processId, proc] of this.activeProcesses.entries()) {
1006
+ killPromises.push((async () => {
1007
+ try {
1008
+ if (!proc.killed && proc.exitCode === null) {
1009
+ await this.debugLog(`Killing process ${processId} (PID: ${proc.pid})`);
1010
+ proc.kill('SIGKILL');
1011
+ // Wait for exit
1012
+ await new Promise((resolve) => {
1013
+ const timeout = setTimeout(() => {
1014
+ resolve();
1015
+ }, 500);
1016
+ proc.once('exit', () => {
1017
+ clearTimeout(timeout);
1018
+ resolve();
1019
+ });
1020
+ });
1021
+ }
1022
+ this.activeProcesses.delete(processId);
1023
+ }
1024
+ catch (error) {
1025
+ await this.debugLog(`Error killing process ${processId}: ${error}`);
1026
+ }
1027
+ })());
1028
+ }
1029
+ await Promise.all(killPromises);
1030
+ await this.debugLog(`All processes killed. Remaining: ${this.activeProcesses.size}`);
1031
+ await this.debugLog(`===================================\n`);
1032
+ }
1033
+ /**
1034
+ * Smart continue detection based on CLI output patterns
1035
+ */
1036
+ async smartContinueDetection(toolName, lastOutput) {
1037
+ // Patterns that indicate CLI is still processing
1038
+ const processingPatterns = [
1039
+ /thinking/i,
1040
+ /processing/i,
1041
+ /analyzing/i,
1042
+ /generating/i,
1043
+ /working/i,
1044
+ /please wait/i,
1045
+ /\.\.\./g,
1046
+ /loading/i,
1047
+ ];
1048
+ // Patterns that indicate CLI has stopped
1049
+ const stoppedPatterns = [
1050
+ /ready/i,
1051
+ /done/i,
1052
+ /complete/i,
1053
+ /finished/i,
1054
+ /awaiting/i,
1055
+ /waiting for/i,
1056
+ /next command/i,
1057
+ ];
1058
+ // Check for processing patterns
1059
+ const isProcessing = processingPatterns.some((pattern) => pattern.test(lastOutput));
1060
+ if (isProcessing) {
1061
+ this.logger.debug(`CLI appears to be processing: ${toolName}`);
1062
+ return false; // Don't send continue
1063
+ }
1064
+ // Check for stopped patterns
1065
+ const isStopped = stoppedPatterns.some((pattern) => pattern.test(lastOutput));
1066
+ if (isStopped) {
1067
+ this.logger.debug(`CLI appears to be stopped: ${toolName}`);
1068
+ return true; // Send continue
1069
+ }
1070
+ // Default behavior: send continue if no clear indication
1071
+ this.logger.debug(`Unclear CLI state, defaulting to continue: ${toolName}`);
1072
+ return true;
1073
+ }
1074
+ }
1075
+ /**
1076
+ * Singleton instance of CLIBridge
1077
+ */
1078
+ let cliBridgeInstance = null;
1079
+ /**
1080
+ * Create CLI bridge instance (singleton)
1081
+ */
1082
+ export function createCLIBridge(logger, config) {
1083
+ if (!cliBridgeInstance) {
1084
+ cliBridgeInstance = new CLIBridge(logger, config);
1085
+ }
1086
+ return cliBridgeInstance;
1087
+ }
1088
+ /**
1089
+ * Reset singleton (for testing only)
1090
+ */
1091
+ export function resetCLIBridge() {
1092
+ cliBridgeInstance = null;
1093
+ }
1094
+ //# sourceMappingURL=cli-bridge.js.map