agent-enderun 1.0.7 → 1.0.9

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 (312) hide show
  1. package/.enderun/ARCHITECTURE.md +1 -0
  2. package/.enderun/BRAIN_DASHBOARD.md +1 -1
  3. package/.enderun/ENDERUN.md +2 -2
  4. package/.enderun/SECURITY.md +1 -0
  5. package/.enderun/STATUS.md +5 -6
  6. package/.enderun/agents/agent_army_schema.json +71 -77
  7. package/.enderun/agents/analyst.json +32 -0
  8. package/.enderun/agents/backend.json +32 -0
  9. package/.enderun/agents/database.json +32 -0
  10. package/.enderun/agents/devops.json +32 -0
  11. package/.enderun/agents/explorer.json +32 -0
  12. package/.enderun/agents/frontend.json +32 -0
  13. package/.enderun/agents/git.json +32 -0
  14. package/.enderun/agents/manager.json +32 -0
  15. package/.enderun/agents/mobile.json +32 -0
  16. package/.enderun/agents/native.json +32 -0
  17. package/.enderun/agents/quality.json +32 -0
  18. package/.enderun/agents/schema/agent-lifecycle-schema.json +35 -80
  19. package/.enderun/agents/security.json +32 -0
  20. package/.enderun/cli-commands.json +7 -5
  21. package/.enderun/config.json +1 -1
  22. package/.enderun/knowledge/ARCHITECTURE.md +1 -0
  23. package/.enderun/knowledge/SECURITY.md +1 -0
  24. package/.enderun/knowledge/SHIM_TEMPLATE.md +12 -1
  25. package/.enderun/knowledge/evaluation_engine.md +11 -0
  26. package/.enderun/knowledge/legacy_onboarding.md +11 -0
  27. package/.enderun/knowledge/project_scaffold_guidelines.md +11 -0
  28. package/.enderun/knowledge/reference_application_guidelines.md +11 -0
  29. package/.enderun/logs/manager.json +52 -23
  30. package/.enderun/memory/PROJECT_MEMORY.md +14 -12
  31. package/.enderun/observability/README.md +19 -0
  32. package/README.md +38 -101
  33. package/agent.md +1 -1
  34. package/bin/hermes-sandbox.js +9 -0
  35. package/bin/init-check.js +9 -0
  36. package/bin/validate-agent-army.js +34 -92
  37. package/dist/apps/backend/src/types/index.d.ts +9 -0
  38. package/dist/apps/backend/src/types/index.js +5 -0
  39. package/dist/apps/backend/src/types/index.js.map +1 -0
  40. package/dist/apps/web/src/main.d.ts +1 -0
  41. package/dist/apps/web/src/main.js +9 -0
  42. package/dist/apps/web/src/main.js.map +1 -0
  43. package/dist/apps/web/vite.config.d.ts +2 -0
  44. package/dist/apps/web/vite.config.js +6 -0
  45. package/dist/apps/web/vite.config.js.map +1 -0
  46. package/dist/framework-mcp/src/index.d.ts +1 -0
  47. package/dist/framework-mcp/src/index.js +71 -0
  48. package/dist/framework-mcp/src/index.js.map +1 -0
  49. package/dist/framework-mcp/src/tools/definitions.d.ts +2 -0
  50. package/dist/framework-mcp/src/tools/definitions.js +111 -0
  51. package/dist/framework-mcp/src/tools/definitions.js.map +1 -0
  52. package/dist/framework-mcp/src/tools/file_system/patch_file.d.ts +2 -0
  53. package/dist/framework-mcp/src/tools/file_system/patch_file.js +19 -0
  54. package/dist/framework-mcp/src/tools/file_system/patch_file.js.map +1 -0
  55. package/dist/framework-mcp/src/tools/file_system/read_file.d.ts +2 -0
  56. package/dist/framework-mcp/src/tools/file_system/read_file.js +8 -0
  57. package/dist/framework-mcp/src/tools/file_system/read_file.js.map +1 -0
  58. package/dist/framework-mcp/src/tools/file_system/replace_text.d.ts +2 -0
  59. package/dist/framework-mcp/src/tools/file_system/replace_text.js +32 -0
  60. package/dist/framework-mcp/src/tools/file_system/replace_text.js.map +1 -0
  61. package/dist/framework-mcp/src/tools/file_system/write_file.d.ts +2 -0
  62. package/dist/framework-mcp/src/tools/file_system/write_file.js +10 -0
  63. package/dist/framework-mcp/src/tools/file_system/write_file.js.map +1 -0
  64. package/dist/framework-mcp/src/tools/framework/get_status.d.ts +2 -0
  65. package/dist/framework-mcp/src/tools/framework/get_status.js +6 -0
  66. package/dist/framework-mcp/src/tools/framework/get_status.js.map +1 -0
  67. package/dist/framework-mcp/src/tools/framework/orchestrate.d.ts +2 -0
  68. package/dist/framework-mcp/src/tools/framework/orchestrate.js +6 -0
  69. package/dist/framework-mcp/src/tools/framework/orchestrate.js.map +1 -0
  70. package/dist/framework-mcp/src/tools/framework/update_contract_hash.d.ts +2 -0
  71. package/dist/framework-mcp/src/tools/framework/update_contract_hash.js +6 -0
  72. package/dist/framework-mcp/src/tools/framework/update_contract_hash.js.map +1 -0
  73. package/dist/framework-mcp/src/tools/framework/update_memory.d.ts +2 -0
  74. package/dist/framework-mcp/src/tools/framework/update_memory.js +9 -0
  75. package/dist/framework-mcp/src/tools/framework/update_memory.js.map +1 -0
  76. package/dist/framework-mcp/src/tools/index.d.ts +5 -0
  77. package/dist/framework-mcp/src/tools/index.js +26 -0
  78. package/dist/framework-mcp/src/tools/index.js.map +1 -0
  79. package/dist/framework-mcp/src/tools/messaging/log_action.d.ts +2 -0
  80. package/dist/framework-mcp/src/tools/messaging/log_action.js +23 -0
  81. package/dist/framework-mcp/src/tools/messaging/log_action.js.map +1 -0
  82. package/dist/framework-mcp/src/tools/messaging/send_message.d.ts +2 -0
  83. package/dist/framework-mcp/src/tools/messaging/send_message.js +24 -0
  84. package/dist/framework-mcp/src/tools/messaging/send_message.js.map +1 -0
  85. package/dist/framework-mcp/src/tools/types.d.ts +47 -0
  86. package/dist/framework-mcp/src/tools/types.js +2 -0
  87. package/dist/framework-mcp/src/tools/types.js.map +1 -0
  88. package/dist/framework-mcp/src/utils/cli.d.ts +5 -0
  89. package/dist/framework-mcp/src/utils/cli.js +21 -0
  90. package/dist/framework-mcp/src/utils/cli.js.map +1 -0
  91. package/dist/framework-mcp/src/utils/security.d.ts +12 -0
  92. package/dist/framework-mcp/src/utils/security.js +54 -0
  93. package/dist/framework-mcp/src/utils/security.js.map +1 -0
  94. package/dist/src/cli/adapters.d.ts +32 -0
  95. package/dist/src/cli/adapters.js +264 -0
  96. package/dist/src/cli/adapters.js.map +1 -0
  97. package/dist/src/cli/commands/app.d.ts +4 -0
  98. package/dist/src/cli/commands/app.js +32 -0
  99. package/dist/src/cli/commands/app.js.map +1 -0
  100. package/dist/src/cli/commands/check.d.ts +6 -0
  101. package/dist/src/cli/commands/check.js +110 -0
  102. package/dist/src/cli/commands/check.js.map +1 -0
  103. package/dist/src/cli/commands/compliance.d.ts +1 -0
  104. package/dist/src/cli/commands/compliance.js +51 -0
  105. package/dist/src/cli/commands/compliance.js.map +1 -0
  106. package/dist/src/cli/commands/contract.d.ts +8 -0
  107. package/dist/src/cli/commands/contract.js +73 -0
  108. package/dist/src/cli/commands/contract.js.map +1 -0
  109. package/dist/src/cli/commands/explorer.d.ts +2 -0
  110. package/dist/src/cli/commands/explorer.js +43 -0
  111. package/dist/src/cli/commands/explorer.js.map +1 -0
  112. package/dist/src/cli/commands/git.d.ts +2 -0
  113. package/dist/src/cli/commands/git.js +41 -0
  114. package/dist/src/cli/commands/git.js.map +1 -0
  115. package/dist/src/cli/commands/init.d.ts +1 -0
  116. package/dist/src/cli/commands/init.js +338 -0
  117. package/dist/src/cli/commands/init.js.map +1 -0
  118. package/dist/src/cli/commands/knowledge.d.ts +2 -0
  119. package/dist/src/cli/commands/knowledge.js +43 -0
  120. package/dist/src/cli/commands/knowledge.js.map +1 -0
  121. package/dist/src/cli/commands/lint.d.ts +4 -0
  122. package/dist/src/cli/commands/lint.js +24 -0
  123. package/dist/src/cli/commands/lint.js.map +1 -0
  124. package/dist/src/cli/commands/log.d.ts +9 -0
  125. package/dist/src/cli/commands/log.js +33 -0
  126. package/dist/src/cli/commands/log.js.map +1 -0
  127. package/dist/src/cli/commands/memory.d.ts +1 -0
  128. package/dist/src/cli/commands/memory.js +72 -0
  129. package/dist/src/cli/commands/memory.js.map +1 -0
  130. package/dist/src/cli/commands/orchestrate.d.ts +1 -0
  131. package/dist/src/cli/commands/orchestrate.js +111 -0
  132. package/dist/src/cli/commands/orchestrate.js.map +1 -0
  133. package/dist/src/cli/commands/script.d.ts +1 -0
  134. package/dist/src/cli/commands/script.js +20 -0
  135. package/dist/src/cli/commands/script.js.map +1 -0
  136. package/dist/src/cli/commands/security.d.ts +1 -0
  137. package/dist/src/cli/commands/security.js +37 -0
  138. package/dist/src/cli/commands/security.js.map +1 -0
  139. package/dist/src/cli/commands/status.d.ts +4 -0
  140. package/dist/src/cli/commands/status.js +56 -0
  141. package/dist/src/cli/commands/status.js.map +1 -0
  142. package/dist/src/cli/commands/trace.d.ts +4 -0
  143. package/dist/src/cli/commands/trace.js +42 -0
  144. package/dist/src/cli/commands/trace.js.map +1 -0
  145. package/dist/src/cli/index.d.ts +2 -0
  146. package/dist/src/cli/index.js +117 -0
  147. package/dist/src/cli/index.js.map +1 -0
  148. package/dist/src/cli/utils/app.d.ts +33 -0
  149. package/dist/src/cli/utils/app.js +710 -0
  150. package/dist/src/cli/utils/app.js.map +1 -0
  151. package/dist/src/cli/utils/claude.d.ts +8 -0
  152. package/dist/src/cli/utils/claude.js +56 -0
  153. package/dist/src/cli/utils/claude.js.map +1 -0
  154. package/dist/src/cli/utils/fs.d.ts +11 -0
  155. package/dist/src/cli/utils/fs.js +144 -0
  156. package/dist/src/cli/utils/fs.js.map +1 -0
  157. package/dist/src/cli/utils/memory.d.ts +14 -0
  158. package/dist/src/cli/utils/memory.js +173 -0
  159. package/dist/src/cli/utils/memory.js.map +1 -0
  160. package/dist/src/cli/utils/pkg.d.ts +24 -0
  161. package/dist/src/cli/utils/pkg.js +254 -0
  162. package/dist/src/cli/utils/pkg.js.map +1 -0
  163. package/dist/src/cli/utils/string.d.ts +6 -0
  164. package/dist/src/cli/utils/string.js +44 -0
  165. package/dist/src/cli/utils/string.js.map +1 -0
  166. package/dist/src/cli/utils/time.d.ts +2 -0
  167. package/dist/src/cli/utils/time.js +28 -0
  168. package/dist/src/cli/utils/time.js.map +1 -0
  169. package/dist/tests/adapter.test.d.ts +1 -0
  170. package/dist/tests/adapter.test.js +80 -0
  171. package/dist/tests/adapter.test.js.map +1 -0
  172. package/dist/tests/framework-init-test/framework-mcp/src/index.d.ts +1 -0
  173. package/dist/tests/framework-init-test/framework-mcp/src/index.js +71 -0
  174. package/dist/tests/framework-init-test/framework-mcp/src/index.js.map +1 -0
  175. package/dist/tests/framework-init-test/framework-mcp/src/tools/definitions.d.ts +2 -0
  176. package/dist/tests/framework-init-test/framework-mcp/src/tools/definitions.js +111 -0
  177. package/dist/tests/framework-init-test/framework-mcp/src/tools/definitions.js.map +1 -0
  178. package/dist/tests/framework-init-test/framework-mcp/src/tools/file_system/patch_file.d.ts +2 -0
  179. package/dist/tests/framework-init-test/framework-mcp/src/tools/file_system/patch_file.js +19 -0
  180. package/dist/tests/framework-init-test/framework-mcp/src/tools/file_system/patch_file.js.map +1 -0
  181. package/dist/tests/framework-init-test/framework-mcp/src/tools/file_system/read_file.d.ts +2 -0
  182. package/dist/tests/framework-init-test/framework-mcp/src/tools/file_system/read_file.js +8 -0
  183. package/dist/tests/framework-init-test/framework-mcp/src/tools/file_system/read_file.js.map +1 -0
  184. package/dist/tests/framework-init-test/framework-mcp/src/tools/file_system/replace_text.d.ts +2 -0
  185. package/dist/tests/framework-init-test/framework-mcp/src/tools/file_system/replace_text.js +32 -0
  186. package/dist/tests/framework-init-test/framework-mcp/src/tools/file_system/replace_text.js.map +1 -0
  187. package/dist/tests/framework-init-test/framework-mcp/src/tools/file_system/write_file.d.ts +2 -0
  188. package/dist/tests/framework-init-test/framework-mcp/src/tools/file_system/write_file.js +10 -0
  189. package/dist/tests/framework-init-test/framework-mcp/src/tools/file_system/write_file.js.map +1 -0
  190. package/dist/tests/framework-init-test/framework-mcp/src/tools/framework/get_status.d.ts +2 -0
  191. package/dist/tests/framework-init-test/framework-mcp/src/tools/framework/get_status.js +6 -0
  192. package/dist/tests/framework-init-test/framework-mcp/src/tools/framework/get_status.js.map +1 -0
  193. package/dist/tests/framework-init-test/framework-mcp/src/tools/framework/orchestrate.d.ts +2 -0
  194. package/dist/tests/framework-init-test/framework-mcp/src/tools/framework/orchestrate.js +6 -0
  195. package/dist/tests/framework-init-test/framework-mcp/src/tools/framework/orchestrate.js.map +1 -0
  196. package/dist/tests/framework-init-test/framework-mcp/src/tools/framework/update_contract_hash.d.ts +2 -0
  197. package/dist/tests/framework-init-test/framework-mcp/src/tools/framework/update_contract_hash.js +6 -0
  198. package/dist/tests/framework-init-test/framework-mcp/src/tools/framework/update_contract_hash.js.map +1 -0
  199. package/dist/tests/framework-init-test/framework-mcp/src/tools/framework/update_memory.d.ts +2 -0
  200. package/dist/tests/framework-init-test/framework-mcp/src/tools/framework/update_memory.js +9 -0
  201. package/dist/tests/framework-init-test/framework-mcp/src/tools/framework/update_memory.js.map +1 -0
  202. package/dist/tests/framework-init-test/framework-mcp/src/tools/index.d.ts +5 -0
  203. package/dist/tests/framework-init-test/framework-mcp/src/tools/index.js +26 -0
  204. package/dist/tests/framework-init-test/framework-mcp/src/tools/index.js.map +1 -0
  205. package/dist/tests/framework-init-test/framework-mcp/src/tools/messaging/log_action.d.ts +2 -0
  206. package/dist/tests/framework-init-test/framework-mcp/src/tools/messaging/log_action.js +23 -0
  207. package/dist/tests/framework-init-test/framework-mcp/src/tools/messaging/log_action.js.map +1 -0
  208. package/dist/tests/framework-init-test/framework-mcp/src/tools/messaging/send_message.d.ts +2 -0
  209. package/dist/tests/framework-init-test/framework-mcp/src/tools/messaging/send_message.js +24 -0
  210. package/dist/tests/framework-init-test/framework-mcp/src/tools/messaging/send_message.js.map +1 -0
  211. package/dist/tests/framework-init-test/framework-mcp/src/tools/types.d.ts +47 -0
  212. package/dist/tests/framework-init-test/framework-mcp/src/tools/types.js +2 -0
  213. package/dist/tests/framework-init-test/framework-mcp/src/tools/types.js.map +1 -0
  214. package/dist/tests/framework-init-test/framework-mcp/src/utils/cli.d.ts +5 -0
  215. package/dist/tests/framework-init-test/framework-mcp/src/utils/cli.js +21 -0
  216. package/dist/tests/framework-init-test/framework-mcp/src/utils/cli.js.map +1 -0
  217. package/dist/tests/framework-init-test/framework-mcp/src/utils/security.d.ts +12 -0
  218. package/dist/tests/framework-init-test/framework-mcp/src/utils/security.js +54 -0
  219. package/dist/tests/framework-init-test/framework-mcp/src/utils/security.js.map +1 -0
  220. package/dist/tests/framework-init-test/framework-mcp/tests/tools/file_system/replace_text.test.d.ts +1 -0
  221. package/dist/tests/framework-init-test/framework-mcp/tests/tools/file_system/replace_text.test.js +69 -0
  222. package/dist/tests/framework-init-test/framework-mcp/tests/tools/file_system/replace_text.test.js.map +1 -0
  223. package/dist/tests/framework-init-test/panda.config.d.ts +4 -0
  224. package/dist/tests/framework-init-test/panda.config.js +95 -0
  225. package/dist/tests/framework-init-test/panda.config.js.map +1 -0
  226. package/dist/tests/framework-init-test/vitest.config.d.ts +2 -0
  227. package/dist/tests/framework-init-test/vitest.config.js +19 -0
  228. package/dist/tests/framework-init-test/vitest.config.js.map +1 -0
  229. package/dist/tests/gemini.test.d.ts +1 -0
  230. package/dist/tests/gemini.test.js +38 -0
  231. package/dist/tests/gemini.test.js.map +1 -0
  232. package/dist/tests/initial.test.d.ts +1 -0
  233. package/dist/tests/initial.test.js +7 -0
  234. package/dist/tests/initial.test.js.map +1 -0
  235. package/dist/vitest.config.d.ts +2 -0
  236. package/dist/vitest.config.js +19 -0
  237. package/dist/vitest.config.js.map +1 -0
  238. package/docs/getting-started.md +4 -4
  239. package/eslint.config.js +1 -1
  240. package/framework-mcp/dist/index.js +2 -2
  241. package/framework-mcp/dist/src/index.js +70 -0
  242. package/framework-mcp/dist/src/tools/definitions.js +110 -0
  243. package/framework-mcp/dist/src/tools/file_system/patch_file.js +18 -0
  244. package/framework-mcp/dist/src/tools/file_system/read_file.js +7 -0
  245. package/framework-mcp/dist/src/tools/file_system/replace_text.js +31 -0
  246. package/framework-mcp/dist/src/tools/file_system/write_file.js +9 -0
  247. package/framework-mcp/dist/src/tools/framework/get_status.js +5 -0
  248. package/framework-mcp/dist/src/tools/framework/orchestrate.js +5 -0
  249. package/framework-mcp/dist/src/tools/framework/update_contract_hash.js +5 -0
  250. package/framework-mcp/dist/src/tools/framework/update_memory.js +8 -0
  251. package/framework-mcp/dist/src/tools/index.js +25 -0
  252. package/framework-mcp/dist/src/tools/messaging/log_action.js +22 -0
  253. package/framework-mcp/dist/src/tools/messaging/send_message.js +23 -0
  254. package/framework-mcp/dist/src/tools/types.js +1 -0
  255. package/framework-mcp/dist/src/utils/cli.js +20 -0
  256. package/framework-mcp/dist/src/utils/security.js +53 -0
  257. package/framework-mcp/dist/tests/tools/file_system/replace_text.test.js +68 -0
  258. package/framework-mcp/dist/tools/definitions.js +1 -0
  259. package/framework-mcp/dist/tools/file_system/replace_text.js +18 -1
  260. package/framework-mcp/dist/tools/framework/get_status.js +2 -2
  261. package/framework-mcp/dist/tools/framework/orchestrate.js +2 -2
  262. package/framework-mcp/dist/tools/framework/update_contract_hash.js +2 -2
  263. package/framework-mcp/dist/tools/framework/update_memory.js +1 -1
  264. package/framework-mcp/dist/tools/messaging/send_message.js +4 -2
  265. package/framework-mcp/dist/utils/cli.js +2 -2
  266. package/framework-mcp/dist/utils/security.js +23 -5
  267. package/framework-mcp/package.json +3 -3
  268. package/framework-mcp/src/index.ts +2 -2
  269. package/framework-mcp/src/tools/definitions.ts +1 -0
  270. package/framework-mcp/src/tools/file_system/replace_text.ts +21 -1
  271. package/framework-mcp/src/tools/framework/get_status.ts +3 -3
  272. package/framework-mcp/src/tools/framework/orchestrate.ts +3 -3
  273. package/framework-mcp/src/tools/framework/update_contract_hash.ts +3 -3
  274. package/framework-mcp/src/tools/framework/update_memory.ts +1 -1
  275. package/framework-mcp/src/tools/messaging/send_message.ts +5 -2
  276. package/framework-mcp/src/tools/types.ts +3 -0
  277. package/framework-mcp/src/utils/cli.ts +2 -2
  278. package/framework-mcp/src/utils/security.ts +23 -5
  279. package/framework-mcp/tests/tools/file_system/replace_text.test.js +68 -0
  280. package/framework-mcp/tests/tools/file_system/replace_text.test.ts +77 -0
  281. package/framework-mcp/tsconfig.json +1 -2
  282. package/mcp.json +4 -4
  283. package/package.json +4 -3
  284. package/src/cli/adapters.ts +16 -9
  285. package/src/cli/commands/check.ts +8 -5
  286. package/src/cli/commands/contract.ts +2 -2
  287. package/src/cli/commands/init.ts +213 -433
  288. package/src/cli/commands/memory.ts +5 -0
  289. package/src/cli/index.ts +12 -9
  290. package/src/cli/utils/app.ts +19 -16
  291. package/src/cli/utils/claude.ts +2 -1
  292. package/src/cli/utils/fs.ts +2 -1
  293. package/src/cli/utils/memory.ts +2 -2
  294. package/src/cli/utils/pkg.ts +72 -5
  295. package/.enderun/agents/analyst.md +0 -32
  296. package/.enderun/agents/backend.md +0 -49
  297. package/.enderun/agents/database.md +0 -41
  298. package/.enderun/agents/devops.md +0 -41
  299. package/.enderun/agents/explorer.md +0 -40
  300. package/.enderun/agents/frontend.md +0 -48
  301. package/.enderun/agents/git.md +0 -41
  302. package/.enderun/agents/manager.md +0 -43
  303. package/.enderun/agents/mobile.md +0 -42
  304. package/.enderun/agents/native.md +0 -41
  305. package/.enderun/agents/quality.md +0 -40
  306. package/.enderun/agents/security.md +0 -32
  307. package/docs/api-referans.md +0 -1137
  308. package/docs/is_akislari.md +0 -902
  309. package/docs/mimari.md +0 -926
  310. package/docs/moduller.md +0 -294
  311. package/docs/proje.md +0 -521
  312. package/docs/yap/304/261.md +0 -2150
@@ -0,0 +1,25 @@
1
+ import { TOOLS } from "./definitions.js";
2
+ import { handleReadFile } from "./file_system/read_file.js";
3
+ import { handleWriteFile } from "./file_system/write_file.js";
4
+ import { handleReplaceText } from "./file_system/replace_text.js";
5
+ import { handlePatchFile } from "./file_system/patch_file.js";
6
+ import { handleGetFrameworkStatus } from "./framework/get_status.js";
7
+ import { handleUpdateProjectMemory } from "./framework/update_memory.js";
8
+ import { handleOrchestrateLoop } from "./framework/orchestrate.js";
9
+ import { handleUpdateContractHash } from "./framework/update_contract_hash.js";
10
+ import { handleSendAgentMessage } from "./messaging/send_message.js";
11
+ import { handleLogAgentAction } from "./messaging/log_action.js";
12
+ // Map of tool names to their handler functions
13
+ export const toolHandlers = {
14
+ read_file: handleReadFile,
15
+ write_file: handleWriteFile,
16
+ replace_text: handleReplaceText,
17
+ patch_file: handlePatchFile,
18
+ get_framework_status: handleGetFrameworkStatus,
19
+ update_project_memory: handleUpdateProjectMemory,
20
+ orchestrate_loop: handleOrchestrateLoop,
21
+ send_agent_message: handleSendAgentMessage,
22
+ log_agent_action: handleLogAgentAction,
23
+ update_contract_hash: handleUpdateContractHash,
24
+ };
25
+ export { TOOLS };
@@ -0,0 +1,22 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { resolveFrameworkDir } from "../../utils/security.js";
4
+ export function handleLogAgentAction(projectRoot, args) {
5
+ const { agent, action, traceId, status, summary } = args;
6
+ const findings = args.findings ? args.findings.split(",").map(f => f.trim()) : [];
7
+ const frameworkDir = resolveFrameworkDir(projectRoot);
8
+ const agentName = agent.replace("@", "");
9
+ const logPath = path.join(projectRoot, frameworkDir, "logs", `${agentName}.json`);
10
+ const logEntry = {
11
+ timestamp: new Date().toISOString(),
12
+ agent,
13
+ action,
14
+ requestId: traceId,
15
+ status,
16
+ summary,
17
+ findings
18
+ };
19
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
20
+ fs.appendFileSync(logPath, JSON.stringify(logEntry) + "\n");
21
+ return { content: [{ type: "text", text: `✅ Action logged for ${agent} to ${path.join(frameworkDir, "logs", `${agentName}.json`)}` }] };
22
+ }
@@ -0,0 +1,23 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { resolveFrameworkDir } from "../../utils/security.js";
4
+ export function handleSendAgentMessage(projectRoot, args) {
5
+ const { to, category, content, traceId } = args;
6
+ // `from` is optional — falls back to "@mcp" for backward compatibility
7
+ const from = args.from || "@mcp";
8
+ const frameworkDir = resolveFrameworkDir(projectRoot);
9
+ const agentName = to.replace("@", "");
10
+ const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
11
+ const message = {
12
+ timestamp: new Date().toISOString(),
13
+ from,
14
+ to,
15
+ category,
16
+ traceId,
17
+ content,
18
+ status: "PENDING"
19
+ };
20
+ fs.mkdirSync(path.dirname(messagePath), { recursive: true });
21
+ fs.appendFileSync(messagePath, JSON.stringify(message) + "\n");
22
+ return { content: [{ type: "text", text: `✅ Message sent to ${to} (from: ${from})` }] };
23
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import { execFileSync } from "child_process";
2
+ /**
3
+ * Executes a CLI command safely using execFileSync (no shell injection risk).
4
+ * Captures stderr gracefully to prevent stream crashes.
5
+ */
6
+ export function safeExec(command, args, cwd, timeout) {
7
+ try {
8
+ return execFileSync(command, args, {
9
+ cwd,
10
+ encoding: "utf8",
11
+ timeout: timeout || 30000,
12
+ maxBuffer: 1024 * 1024,
13
+ stdio: ["pipe", "pipe", "pipe"],
14
+ });
15
+ }
16
+ catch (error) {
17
+ const err = error;
18
+ throw new Error(err.stderr || err.message || "Command execution failed", { cause: error });
19
+ }
20
+ }
@@ -0,0 +1,53 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ /**
4
+ * Validates and resolves a user-provided path to prevent path traversal attacks.
5
+ * Ensures the resolved path stays within the project root boundary.
6
+ */
7
+ export function safePath(projectRoot, userPath) {
8
+ const resolved = path.resolve(projectRoot, userPath);
9
+ const normalizedRoot = path.resolve(projectRoot);
10
+ if (!resolved.startsWith(normalizedRoot + path.sep) && resolved !== normalizedRoot) {
11
+ throw new Error(`Access denied: path "${userPath}" escapes project root.`);
12
+ }
13
+ return resolved;
14
+ }
15
+ /**
16
+ * Resolves the active framework directory.
17
+ * Priority: package.json `enderun.frameworkDir` → `.enderun` → adapter dirs.
18
+ * This ensures the canonical framework directory is always used,
19
+ * even when adapter-specific dirs (e.g. `.gemini`) exist alongside it.
20
+ */
21
+ export function resolveFrameworkDir(projectRoot) {
22
+ // 1. Authoritative source: read from package.json if present
23
+ try {
24
+ const pkgPath = path.join(projectRoot, "package.json");
25
+ if (fs.existsSync(pkgPath)) {
26
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
27
+ const enderunConfig = pkg["enderun"];
28
+ if (enderunConfig && typeof enderunConfig["frameworkDir"] === "string") {
29
+ return enderunConfig["frameworkDir"];
30
+ }
31
+ }
32
+ }
33
+ catch {
34
+ // ignore — fall through to filesystem scan
35
+ }
36
+ // 2. Filesystem scan — .enderun takes precedence over adapter-specific dirs
37
+ const candidates = [
38
+ ".enderun",
39
+ ".claude",
40
+ ".grok",
41
+ ".agent",
42
+ ".gemini/antigravity",
43
+ ".gemini/antigravity-cli",
44
+ ".gemini",
45
+ ];
46
+ for (const candidate of candidates) {
47
+ const candidatePath = path.join(projectRoot, candidate);
48
+ if (fs.existsSync(candidatePath)) {
49
+ return candidate;
50
+ }
51
+ }
52
+ return ".enderun";
53
+ }
@@ -0,0 +1,68 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { handleReplaceText } from "../../../src/tools/file_system/replace_text.js";
5
+ const TEST_DIR = path.join(__dirname, "../_temp_test_files");
6
+ const TEST_FILE = path.join(TEST_DIR, "test_file.txt");
7
+ beforeEach(() => {
8
+ fs.mkdirSync(TEST_DIR, { recursive: true });
9
+ });
10
+ afterEach(() => {
11
+ fs.rmSync(TEST_DIR, { recursive: true, force: true });
12
+ });
13
+ describe("handleReplaceText", () => {
14
+ it("should replace a single occurrence of text", () => {
15
+ fs.writeFileSync(TEST_FILE, "hello world", "utf8");
16
+ const args = {
17
+ path: TEST_FILE,
18
+ oldText: "world",
19
+ newText: "there",
20
+ };
21
+ const result = handleReplaceText(TEST_DIR, args);
22
+ expect(result.content[0].text).toContain("✅ Surgical edit successful");
23
+ expect(fs.readFileSync(TEST_FILE, "utf8")).toBe("hello there");
24
+ });
25
+ it("should throw an error if text is not found", () => {
26
+ fs.writeFileSync(TEST_FILE, "hello world", "utf8");
27
+ const args = {
28
+ path: TEST_FILE,
29
+ oldText: "missing",
30
+ newText: "found",
31
+ };
32
+ expect(() => handleReplaceText(TEST_DIR, args)).toThrowError("Text not found in file");
33
+ });
34
+ it("should throw an error for ambiguous replacement if allowMultiple is false", () => {
35
+ fs.writeFileSync(TEST_FILE, "hello world world", "utf8");
36
+ const args = {
37
+ path: TEST_FILE,
38
+ oldText: "world",
39
+ newText: "there",
40
+ allowMultiple: false,
41
+ };
42
+ expect(() => handleReplaceText(TEST_DIR, args)).toThrowError("Ambiguous replacement");
43
+ });
44
+ it("should replace all occurrences of text if allowMultiple is true", () => {
45
+ fs.writeFileSync(TEST_FILE, "hello world world", "utf8");
46
+ const args = {
47
+ path: TEST_FILE,
48
+ oldText: "world",
49
+ newText: "there",
50
+ allowMultiple: true,
51
+ };
52
+ const result = handleReplaceText(TEST_DIR, args);
53
+ expect(result.content[0].text).toContain("✅ Surgical edit successful");
54
+ expect(fs.readFileSync(TEST_FILE, "utf8")).toBe("hello there there");
55
+ });
56
+ it("should handle special characters in oldText when allowMultiple is true", () => {
57
+ fs.writeFileSync(TEST_FILE, "test.txt", "utf8");
58
+ const args = {
59
+ path: TEST_FILE,
60
+ oldText: ".txt",
61
+ newText: ".md",
62
+ allowMultiple: true,
63
+ };
64
+ const result = handleReplaceText(TEST_DIR, args);
65
+ expect(result.content[0].text).toContain("✅ Surgical edit successful");
66
+ expect(fs.readFileSync(TEST_FILE, "utf8")).toBe("test.md");
67
+ });
68
+ });
@@ -77,6 +77,7 @@ export const TOOLS = [
77
77
  inputSchema: {
78
78
  type: "object",
79
79
  properties: {
80
+ from: { type: "string", description: "Sender agent (e.g., @manager, @backend). Defaults to @mcp if omitted." },
80
81
  to: { type: "string", description: "Target agent (e.g., @backend, @qa)" },
81
82
  category: { type: "string", enum: ["ACTION", "DELEGATION", "INFO", "ALERT"] },
82
83
  content: { type: "string", description: "Message content" },
@@ -5,10 +5,27 @@ export function handleReplaceText(projectRoot, args) {
5
5
  let content = fs.readFileSync(filePath, "utf8");
6
6
  const oldText = args.oldText;
7
7
  const newText = args.newText;
8
+ const allowMultiple = args.allowMultiple || false; // Default to false
8
9
  if (!content.includes(oldText)) {
9
10
  throw new Error(`Text not found in file: ${oldText.slice(0, 100)}...`);
10
11
  }
11
- content = content.replace(oldText, newText);
12
+ // Surgical precision guard: reject ambiguous replacements unless allowMultiple is true.
13
+ if (!allowMultiple) {
14
+ const firstIndex = content.indexOf(oldText);
15
+ const lastIndex = content.lastIndexOf(oldText);
16
+ if (firstIndex !== lastIndex) {
17
+ const count = (content.match(new RegExp(oldText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g")) || []).length;
18
+ throw new Error(`Ambiguous replacement: "${oldText.slice(0, 80)}..." found ${count} times in ${args.path}. ` +
19
+ "Provide a longer, unique context string or set 'allow_multiple' to true.");
20
+ }
21
+ }
22
+ // Perform replacement(s)
23
+ if (allowMultiple) {
24
+ content = content.replace(new RegExp(oldText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), newText);
25
+ }
26
+ else {
27
+ content = content.replace(oldText, newText);
28
+ }
12
29
  fs.writeFileSync(filePath, content);
13
30
  return { content: [{ type: "text", text: `✅ Surgical edit successful in ${args.path}` }] };
14
31
  }
@@ -1,5 +1,5 @@
1
1
  import { safeExec } from "../../utils/cli.js";
2
- export function handleGetFrameworkStatus(projectRoot) {
3
- const output = safeExec("npx", ["agent-enderun", "status"], projectRoot);
2
+ export function handleGetFrameworkStatus(projectRoot, args) {
3
+ const output = safeExec("npx", ["agent-enderun", "status"], projectRoot, args.timeout);
4
4
  return { content: [{ type: "text", text: output }] };
5
5
  }
@@ -1,5 +1,5 @@
1
1
  import { safeExec } from "../../utils/cli.js";
2
- export function handleOrchestrateLoop(projectRoot) {
3
- const output = safeExec("npx", ["agent-enderun", "orchestrate"], projectRoot);
2
+ export function handleOrchestrateLoop(projectRoot, args) {
3
+ const output = safeExec("npx", ["agent-enderun", "orchestrate"], projectRoot, args.timeout);
4
4
  return { content: [{ type: "text", text: output }] };
5
5
  }
@@ -1,5 +1,5 @@
1
1
  import { safeExec } from "../../utils/cli.js";
2
- export function handleUpdateContractHash(projectRoot) {
3
- const output = safeExec("npx", ["agent-enderun", "update-contract"], projectRoot);
2
+ export function handleUpdateContractHash(projectRoot, args) {
3
+ const output = safeExec("npx", ["agent-enderun", "update-contract"], projectRoot, args.timeout);
4
4
  return { content: [{ type: "text", text: output }] };
5
5
  }
@@ -3,6 +3,6 @@ export function handleUpdateProjectMemory(projectRoot, args) {
3
3
  const section = args.section;
4
4
  const content = args.content;
5
5
  // Using execFileSync with array args prevents command injection
6
- safeExec("npx", ["agent-enderun", "update_project_memory", section, content], projectRoot);
6
+ safeExec("npx", ["agent-enderun", "update_project_memory", section, content], projectRoot, args.timeout);
7
7
  return { content: [{ type: "text", text: `✅ Section ${section} updated.` }] };
8
8
  }
@@ -3,12 +3,14 @@ import path from "path";
3
3
  import { resolveFrameworkDir } from "../../utils/security.js";
4
4
  export function handleSendAgentMessage(projectRoot, args) {
5
5
  const { to, category, content, traceId } = args;
6
+ // `from` is optional — falls back to "@mcp" for backward compatibility
7
+ const from = args.from || "@mcp";
6
8
  const frameworkDir = resolveFrameworkDir(projectRoot);
7
9
  const agentName = to.replace("@", "");
8
10
  const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
9
11
  const message = {
10
12
  timestamp: new Date().toISOString(),
11
- from: "@mcp",
13
+ from,
12
14
  to,
13
15
  category,
14
16
  traceId,
@@ -17,5 +19,5 @@ export function handleSendAgentMessage(projectRoot, args) {
17
19
  };
18
20
  fs.mkdirSync(path.dirname(messagePath), { recursive: true });
19
21
  fs.appendFileSync(messagePath, JSON.stringify(message) + "\n");
20
- return { content: [{ type: "text", text: `✅ Message sent to ${to}` }] };
22
+ return { content: [{ type: "text", text: `✅ Message sent to ${to} (from: ${from})` }] };
21
23
  }
@@ -3,12 +3,12 @@ import { execFileSync } from "child_process";
3
3
  * Executes a CLI command safely using execFileSync (no shell injection risk).
4
4
  * Captures stderr gracefully to prevent stream crashes.
5
5
  */
6
- export function safeExec(command, args, cwd) {
6
+ export function safeExec(command, args, cwd, timeout) {
7
7
  try {
8
8
  return execFileSync(command, args, {
9
9
  cwd,
10
10
  encoding: "utf8",
11
- timeout: 30000,
11
+ timeout: timeout || 30000,
12
12
  maxBuffer: 1024 * 1024,
13
13
  stdio: ["pipe", "pipe", "pipe"],
14
14
  });
@@ -13,17 +13,35 @@ export function safePath(projectRoot, userPath) {
13
13
  return resolved;
14
14
  }
15
15
  /**
16
- * Resolves the active framework directory by scanning known candidates.
16
+ * Resolves the active framework directory.
17
+ * Priority: package.json `enderun.frameworkDir` → `.enderun` → adapter dirs.
18
+ * This ensures the canonical framework directory is always used,
19
+ * even when adapter-specific dirs (e.g. `.gemini`) exist alongside it.
17
20
  */
18
21
  export function resolveFrameworkDir(projectRoot) {
22
+ // 1. Authoritative source: read from package.json if present
23
+ try {
24
+ const pkgPath = path.join(projectRoot, "package.json");
25
+ if (fs.existsSync(pkgPath)) {
26
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
27
+ const enderunConfig = pkg["enderun"];
28
+ if (enderunConfig && typeof enderunConfig["frameworkDir"] === "string") {
29
+ return enderunConfig["frameworkDir"];
30
+ }
31
+ }
32
+ }
33
+ catch {
34
+ // ignore — fall through to filesystem scan
35
+ }
36
+ // 2. Filesystem scan — .enderun takes precedence over adapter-specific dirs
19
37
  const candidates = [
20
- ".gemini/antigravity",
21
- ".gemini/antigravity-cli",
22
- ".gemini",
38
+ ".enderun",
23
39
  ".claude",
24
40
  ".grok",
25
41
  ".agent",
26
- ".enderun",
42
+ ".gemini/antigravity",
43
+ ".gemini/antigravity-cli",
44
+ ".gemini",
27
45
  ];
28
46
  for (const candidate of candidates) {
29
47
  const candidatePath = path.join(projectRoot, candidate);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-enderun/mcp",
3
- "version": "1.0.6",
3
+ "version": "1.0.9",
4
4
  "description": "Agent Enderun Model Context Protocol (MCP) Server",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -13,7 +13,7 @@
13
13
  "zod": "^3.24.2"
14
14
  },
15
15
  "devDependencies": {
16
- "@types/node": "^22.19.19",
17
- "typescript": "^5.9.3"
16
+ "@types/node": "^25.9.1",
17
+ "typescript": "^6.0.3"
18
18
  }
19
19
  }
@@ -13,8 +13,8 @@ import { TOOLS, toolHandlers } from "./tools/index.js";
13
13
 
14
14
  const server = new Server(
15
15
  {
16
- name: "agent-enderun",
17
- version: "1.0.0",
16
+ name: "@agent-enderun/mcp",
17
+ version: "1.0.9",
18
18
  },
19
19
  {
20
20
  capabilities: {
@@ -79,6 +79,7 @@ export const TOOLS: ToolDefinition[] = [
79
79
  inputSchema: {
80
80
  type: "object",
81
81
  properties: {
82
+ from: { type: "string", description: "Sender agent (e.g., @manager, @backend). Defaults to @mcp if omitted." },
82
83
  to: { type: "string", description: "Target agent (e.g., @backend, @qa)" },
83
84
  category: { type: "string", enum: ["ACTION", "DELEGATION", "INFO", "ALERT"] },
84
85
  content: { type: "string", description: "Message content" },
@@ -7,12 +7,32 @@ export function handleReplaceText(projectRoot: string, args: ToolArgs): ToolResu
7
7
  let content = fs.readFileSync(filePath, "utf8");
8
8
  const oldText = args.oldText as string;
9
9
  const newText = args.newText as string;
10
+ const allowMultiple = args.allowMultiple || false; // Default to false
10
11
 
11
12
  if (!content.includes(oldText)) {
12
13
  throw new Error(`Text not found in file: ${oldText.slice(0, 100)}...`);
13
14
  }
14
15
 
15
- content = content.replace(oldText, newText);
16
+ // Surgical precision guard: reject ambiguous replacements unless allowMultiple is true.
17
+ if (!allowMultiple) {
18
+ const firstIndex = content.indexOf(oldText);
19
+ const lastIndex = content.lastIndexOf(oldText);
20
+ if (firstIndex !== lastIndex) {
21
+ const count = (content.match(new RegExp(oldText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g")) || []).length;
22
+ throw new Error(
23
+ `Ambiguous replacement: "${oldText.slice(0, 80)}..." found ${count} times in ${args.path}. ` +
24
+ "Provide a longer, unique context string or set 'allow_multiple' to true."
25
+ );
26
+ }
27
+ }
28
+
29
+ // Perform replacement(s)
30
+ if (allowMultiple) {
31
+ content = content.replace(new RegExp(oldText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), newText);
32
+ } else {
33
+ content = content.replace(oldText, newText);
34
+ }
35
+
16
36
  fs.writeFileSync(filePath, content);
17
37
  return { content: [{ type: "text", text: `✅ Surgical edit successful in ${args.path}` }] };
18
38
  }
@@ -1,7 +1,7 @@
1
1
  import { safeExec } from "../../utils/cli.js";
2
- import { ToolResult } from "../types.js";
2
+ import { ToolArgs, ToolResult } from "../types.js";
3
3
 
4
- export function handleGetFrameworkStatus(projectRoot: string): ToolResult {
5
- const output = safeExec("npx", ["agent-enderun", "status"], projectRoot);
4
+ export function handleGetFrameworkStatus(projectRoot: string, args: ToolArgs): ToolResult {
5
+ const output = safeExec("npx", ["agent-enderun", "status"], projectRoot, args.timeout);
6
6
  return { content: [{ type: "text", text: output }] };
7
7
  }
@@ -1,7 +1,7 @@
1
1
  import { safeExec } from "../../utils/cli.js";
2
- import { ToolResult } from "../types.js";
2
+ import { ToolArgs, ToolResult } from "../types.js";
3
3
 
4
- export function handleOrchestrateLoop(projectRoot: string): ToolResult {
5
- const output = safeExec("npx", ["agent-enderun", "orchestrate"], projectRoot);
4
+ export function handleOrchestrateLoop(projectRoot: string, args: ToolArgs): ToolResult {
5
+ const output = safeExec("npx", ["agent-enderun", "orchestrate"], projectRoot, args.timeout);
6
6
  return { content: [{ type: "text", text: output }] };
7
7
  }
@@ -1,7 +1,7 @@
1
1
  import { safeExec } from "../../utils/cli.js";
2
- import { ToolResult } from "../types.js";
2
+ import { ToolArgs, ToolResult } from "../types.js";
3
3
 
4
- export function handleUpdateContractHash(projectRoot: string): ToolResult {
5
- const output = safeExec("npx", ["agent-enderun", "update-contract"], projectRoot);
4
+ export function handleUpdateContractHash(projectRoot: string, args: ToolArgs): ToolResult {
5
+ const output = safeExec("npx", ["agent-enderun", "update-contract"], projectRoot, args.timeout);
6
6
  return { content: [{ type: "text", text: output }] };
7
7
  }
@@ -5,6 +5,6 @@ export function handleUpdateProjectMemory(projectRoot: string, args: ToolArgs):
5
5
  const section = args.section as string;
6
6
  const content = args.content as string;
7
7
  // Using execFileSync with array args prevents command injection
8
- safeExec("npx", ["agent-enderun", "update_project_memory", section, content], projectRoot);
8
+ safeExec("npx", ["agent-enderun", "update_project_memory", section, content], projectRoot, args.timeout);
9
9
  return { content: [{ type: "text", text: `✅ Section ${section} updated.` }] };
10
10
  }
@@ -5,13 +5,16 @@ import { ToolArgs, ToolResult } from "../types.js";
5
5
 
6
6
  export function handleSendAgentMessage(projectRoot: string, args: ToolArgs): ToolResult {
7
7
  const { to, category, content, traceId } = args as Required<Pick<ToolArgs, "to" | "category" | "content" | "traceId">>;
8
+ // `from` is optional — falls back to "@mcp" for backward compatibility
9
+ const from = (args.from as string) || "@mcp";
10
+
8
11
  const frameworkDir = resolveFrameworkDir(projectRoot);
9
12
  const agentName = to.replace("@", "");
10
13
  const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
11
14
 
12
15
  const message = {
13
16
  timestamp: new Date().toISOString(),
14
- from: "@mcp",
17
+ from,
15
18
  to,
16
19
  category,
17
20
  traceId,
@@ -22,5 +25,5 @@ export function handleSendAgentMessage(projectRoot: string, args: ToolArgs): Too
22
25
  fs.mkdirSync(path.dirname(messagePath), { recursive: true });
23
26
  fs.appendFileSync(messagePath, JSON.stringify(message) + "\n");
24
27
 
25
- return { content: [{ type: "text", text: `✅ Message sent to ${to}` }] };
28
+ return { content: [{ type: "text", text: `✅ Message sent to ${to} (from: ${from})` }] };
26
29
  }
@@ -32,6 +32,9 @@ export interface ToolArgs {
32
32
  status?: "SUCCESS" | "FAILURE";
33
33
  summary?: string;
34
34
  findings?: string;
35
+ allowMultiple?: boolean;
36
+ timeout?: number;
37
+ from?: string;
35
38
  }
36
39
 
37
40
  export interface ToolResult {
@@ -4,12 +4,12 @@ import { execFileSync } from "child_process";
4
4
  * Executes a CLI command safely using execFileSync (no shell injection risk).
5
5
  * Captures stderr gracefully to prevent stream crashes.
6
6
  */
7
- export function safeExec(command: string, args: string[], cwd: string): string {
7
+ export function safeExec(command: string, args: string[], cwd: string, timeout?: number): string {
8
8
  try {
9
9
  return execFileSync(command, args, {
10
10
  cwd,
11
11
  encoding: "utf8",
12
- timeout: 30000,
12
+ timeout: timeout || 30000,
13
13
  maxBuffer: 1024 * 1024,
14
14
  stdio: ["pipe", "pipe", "pipe"],
15
15
  });
@@ -17,17 +17,35 @@ export function safePath(projectRoot: string, userPath: string): string {
17
17
  }
18
18
 
19
19
  /**
20
- * Resolves the active framework directory by scanning known candidates.
20
+ * Resolves the active framework directory.
21
+ * Priority: package.json `enderun.frameworkDir` → `.enderun` → adapter dirs.
22
+ * This ensures the canonical framework directory is always used,
23
+ * even when adapter-specific dirs (e.g. `.gemini`) exist alongside it.
21
24
  */
22
25
  export function resolveFrameworkDir(projectRoot: string): string {
26
+ // 1. Authoritative source: read from package.json if present
27
+ try {
28
+ const pkgPath = path.join(projectRoot, "package.json");
29
+ if (fs.existsSync(pkgPath)) {
30
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")) as Record<string, unknown>;
31
+ const enderunConfig = pkg["enderun"] as Record<string, unknown> | undefined;
32
+ if (enderunConfig && typeof enderunConfig["frameworkDir"] === "string") {
33
+ return enderunConfig["frameworkDir"];
34
+ }
35
+ }
36
+ } catch {
37
+ // ignore — fall through to filesystem scan
38
+ }
39
+
40
+ // 2. Filesystem scan — .enderun takes precedence over adapter-specific dirs
23
41
  const candidates = [
24
- ".gemini/antigravity",
25
- ".gemini/antigravity-cli",
26
- ".gemini",
42
+ ".enderun",
27
43
  ".claude",
28
44
  ".grok",
29
45
  ".agent",
30
- ".enderun",
46
+ ".gemini/antigravity",
47
+ ".gemini/antigravity-cli",
48
+ ".gemini",
31
49
  ];
32
50
 
33
51
  for (const candidate of candidates) {