argustack 0.2.0 → 0.3.1

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 (546) hide show
  1. package/README.md +31 -115
  2. package/dist/adapters/board/board-sync.js +2 -2
  3. package/dist/adapters/board/board-sync.js.map +1 -1
  4. package/dist/adapters/board/md-parser.js +3 -3
  5. package/dist/adapters/board/md-parser.js.map +1 -1
  6. package/dist/adapters/board/skill-discovery.js +2 -2
  7. package/dist/adapters/board/skill-discovery.js.map +1 -1
  8. package/dist/adapters/board/skill-runner.js +2 -2
  9. package/dist/adapters/board/skill-runner.js.map +1 -1
  10. package/dist/adapters/board/store.d.ts.map +1 -1
  11. package/dist/adapters/board/store.js +11 -11
  12. package/dist/adapters/board/store.js.map +1 -1
  13. package/dist/adapters/csv/mapper.js +11 -11
  14. package/dist/adapters/csv/mapper.js.map +1 -1
  15. package/dist/adapters/csv/parser.d.ts.map +1 -1
  16. package/dist/adapters/csv/parser.js +8 -7
  17. package/dist/adapters/csv/parser.js.map +1 -1
  18. package/dist/adapters/csv/provider.js +4 -4
  19. package/dist/adapters/csv/provider.js.map +1 -1
  20. package/dist/adapters/db/client.js +2 -2
  21. package/dist/adapters/db/client.js.map +1 -1
  22. package/dist/adapters/db/index.js +1 -0
  23. package/dist/adapters/db/index.js.map +1 -1
  24. package/dist/adapters/db/mapper.js +1 -1
  25. package/dist/adapters/db/mapper.js.map +1 -1
  26. package/dist/adapters/db/provider.js +4 -4
  27. package/dist/adapters/db/provider.js.map +1 -1
  28. package/dist/adapters/db/sql-validator.js +2 -2
  29. package/dist/adapters/db/sql-validator.js.map +1 -1
  30. package/dist/adapters/docker/cli-docker-control.d.ts +13 -0
  31. package/dist/adapters/docker/cli-docker-control.d.ts.map +1 -0
  32. package/dist/adapters/docker/cli-docker-control.js +70 -0
  33. package/dist/adapters/docker/cli-docker-control.js.map +1 -0
  34. package/dist/adapters/docker/index.d.ts +2 -0
  35. package/dist/adapters/docker/index.d.ts.map +1 -0
  36. package/dist/adapters/docker/index.js +2 -0
  37. package/dist/adapters/docker/index.js.map +1 -0
  38. package/dist/adapters/git/mapper.js +1 -1
  39. package/dist/adapters/git/mapper.js.map +1 -1
  40. package/dist/adapters/git/provider.js +4 -4
  41. package/dist/adapters/git/provider.js.map +1 -1
  42. package/dist/adapters/github/mapper.js +3 -3
  43. package/dist/adapters/github/mapper.js.map +1 -1
  44. package/dist/adapters/github/provider.d.ts.map +1 -1
  45. package/dist/adapters/github/provider.js +2 -2
  46. package/dist/adapters/github/provider.js.map +1 -1
  47. package/dist/adapters/jira/mapper.js +4 -4
  48. package/dist/adapters/jira/mapper.js.map +1 -1
  49. package/dist/adapters/jira/provider.js +8 -8
  50. package/dist/adapters/jira/provider.js.map +1 -1
  51. package/dist/adapters/jira-proxy/client.js +4 -4
  52. package/dist/adapters/jira-proxy/client.js.map +1 -1
  53. package/dist/adapters/jira-proxy/config-loader.d.ts +9 -0
  54. package/dist/adapters/jira-proxy/config-loader.d.ts.map +1 -1
  55. package/dist/adapters/jira-proxy/config-loader.js +26 -3
  56. package/dist/adapters/jira-proxy/config-loader.js.map +1 -1
  57. package/dist/adapters/jira-proxy/index.d.ts +1 -1
  58. package/dist/adapters/jira-proxy/index.d.ts.map +1 -1
  59. package/dist/adapters/jira-proxy/index.js +1 -1
  60. package/dist/adapters/jira-proxy/index.js.map +1 -1
  61. package/dist/adapters/jira-proxy/mapper.js +2 -2
  62. package/dist/adapters/jira-proxy/mapper.js.map +1 -1
  63. package/dist/adapters/jira-proxy/provider.d.ts.map +1 -1
  64. package/dist/adapters/jira-proxy/provider.js +19 -8
  65. package/dist/adapters/jira-proxy/provider.js.map +1 -1
  66. package/dist/adapters/lmstudio/embedding-provider.js +4 -4
  67. package/dist/adapters/lmstudio/embedding-provider.js.map +1 -1
  68. package/dist/adapters/lsp/jsonrpc.js +1 -1
  69. package/dist/adapters/lsp/jsonrpc.js.map +1 -1
  70. package/dist/adapters/lsp/typescript-lsp.js +7 -7
  71. package/dist/adapters/lsp/typescript-lsp.js.map +1 -1
  72. package/dist/adapters/neo4j/client.js +2 -2
  73. package/dist/adapters/neo4j/client.js.map +1 -1
  74. package/dist/adapters/neo4j/graph-store.js +3 -3
  75. package/dist/adapters/neo4j/graph-store.js.map +1 -1
  76. package/dist/adapters/neo4j/mapper.js +4 -4
  77. package/dist/adapters/neo4j/mapper.js.map +1 -1
  78. package/dist/adapters/ollama/chat-llm.d.ts +24 -0
  79. package/dist/adapters/ollama/chat-llm.d.ts.map +1 -0
  80. package/dist/adapters/ollama/chat-llm.js +53 -0
  81. package/dist/adapters/ollama/chat-llm.js.map +1 -0
  82. package/dist/adapters/ollama/embedding-provider.d.ts +30 -0
  83. package/dist/adapters/ollama/embedding-provider.d.ts.map +1 -0
  84. package/dist/adapters/ollama/embedding-provider.js +124 -0
  85. package/dist/adapters/ollama/embedding-provider.js.map +1 -0
  86. package/dist/adapters/ollama/http-ollama-control.d.ts +24 -0
  87. package/dist/adapters/ollama/http-ollama-control.d.ts.map +1 -0
  88. package/dist/adapters/ollama/http-ollama-control.js +224 -0
  89. package/dist/adapters/ollama/http-ollama-control.js.map +1 -0
  90. package/dist/adapters/ollama/index.d.ts +6 -0
  91. package/dist/adapters/ollama/index.d.ts.map +1 -0
  92. package/dist/adapters/ollama/index.js +4 -0
  93. package/dist/adapters/ollama/index.js.map +1 -0
  94. package/dist/adapters/platform/index.d.ts +2 -0
  95. package/dist/adapters/platform/index.d.ts.map +1 -0
  96. package/dist/adapters/platform/index.js +2 -0
  97. package/dist/adapters/platform/index.js.map +1 -0
  98. package/dist/adapters/platform/node-platform-probe.d.ts +9 -0
  99. package/dist/adapters/platform/node-platform-probe.d.ts.map +1 -0
  100. package/dist/adapters/platform/node-platform-probe.js +88 -0
  101. package/dist/adapters/platform/node-platform-probe.js.map +1 -0
  102. package/dist/adapters/postgres/code-meta.js +13 -13
  103. package/dist/adapters/postgres/code-meta.js.map +1 -1
  104. package/dist/adapters/postgres/index.d.ts +2 -0
  105. package/dist/adapters/postgres/index.d.ts.map +1 -1
  106. package/dist/adapters/postgres/index.js +2 -0
  107. package/dist/adapters/postgres/index.js.map +1 -1
  108. package/dist/adapters/postgres/migrate-helpers.d.ts +43 -0
  109. package/dist/adapters/postgres/migrate-helpers.d.ts.map +1 -0
  110. package/dist/adapters/postgres/migrate-helpers.js +136 -0
  111. package/dist/adapters/postgres/migrate-helpers.js.map +1 -0
  112. package/dist/adapters/postgres/readiness-probe.d.ts +22 -0
  113. package/dist/adapters/postgres/readiness-probe.d.ts.map +1 -0
  114. package/dist/adapters/postgres/readiness-probe.js +47 -0
  115. package/dist/adapters/postgres/readiness-probe.js.map +1 -0
  116. package/dist/adapters/postgres/schema.d.ts +11 -1
  117. package/dist/adapters/postgres/schema.d.ts.map +1 -1
  118. package/dist/adapters/postgres/schema.js +159 -113
  119. package/dist/adapters/postgres/schema.js.map +1 -1
  120. package/dist/adapters/postgres/storage-commits.d.ts +13 -0
  121. package/dist/adapters/postgres/storage-commits.d.ts.map +1 -0
  122. package/dist/adapters/postgres/storage-commits.js +63 -0
  123. package/dist/adapters/postgres/storage-commits.js.map +1 -0
  124. package/dist/adapters/postgres/storage-dbschema.d.ts +14 -0
  125. package/dist/adapters/postgres/storage-dbschema.d.ts.map +1 -0
  126. package/dist/adapters/postgres/storage-dbschema.js +63 -0
  127. package/dist/adapters/postgres/storage-dbschema.js.map +1 -0
  128. package/dist/adapters/postgres/storage-graph.d.ts +18 -0
  129. package/dist/adapters/postgres/storage-graph.d.ts.map +1 -0
  130. package/dist/adapters/postgres/storage-graph.js +127 -0
  131. package/dist/adapters/postgres/storage-graph.js.map +1 -0
  132. package/dist/adapters/postgres/storage-issues.d.ts +26 -0
  133. package/dist/adapters/postgres/storage-issues.d.ts.map +1 -0
  134. package/dist/adapters/postgres/storage-issues.js +233 -0
  135. package/dist/adapters/postgres/storage-issues.js.map +1 -0
  136. package/dist/adapters/postgres/storage-prs.d.ts +15 -0
  137. package/dist/adapters/postgres/storage-prs.d.ts.map +1 -0
  138. package/dist/adapters/postgres/storage-prs.js +142 -0
  139. package/dist/adapters/postgres/storage-prs.js.map +1 -0
  140. package/dist/adapters/postgres/storage-query.d.ts +26 -0
  141. package/dist/adapters/postgres/storage-query.d.ts.map +1 -0
  142. package/dist/adapters/postgres/storage-query.js +74 -0
  143. package/dist/adapters/postgres/storage-query.js.map +1 -0
  144. package/dist/adapters/postgres/storage-search.d.ts +18 -0
  145. package/dist/adapters/postgres/storage-search.d.ts.map +1 -0
  146. package/dist/adapters/postgres/storage-search.js +90 -0
  147. package/dist/adapters/postgres/storage-search.js.map +1 -0
  148. package/dist/adapters/postgres/storage.d.ts +47 -31
  149. package/dist/adapters/postgres/storage.d.ts.map +1 -1
  150. package/dist/adapters/postgres/storage.js +86 -602
  151. package/dist/adapters/postgres/storage.js.map +1 -1
  152. package/dist/adapters/postgres/workspace-store.d.ts +26 -0
  153. package/dist/adapters/postgres/workspace-store.d.ts.map +1 -0
  154. package/dist/adapters/postgres/workspace-store.js +94 -0
  155. package/dist/adapters/postgres/workspace-store.js.map +1 -0
  156. package/dist/adapters/qdrant/client.js +1 -1
  157. package/dist/adapters/qdrant/client.js.map +1 -1
  158. package/dist/adapters/qdrant/mapper.d.ts.map +1 -1
  159. package/dist/adapters/qdrant/mapper.js +11 -4
  160. package/dist/adapters/qdrant/mapper.js.map +1 -1
  161. package/dist/adapters/qdrant/vector-store.d.ts +1 -0
  162. package/dist/adapters/qdrant/vector-store.d.ts.map +1 -1
  163. package/dist/adapters/qdrant/vector-store.js +40 -10
  164. package/dist/adapters/qdrant/vector-store.js.map +1 -1
  165. package/dist/adapters/tree-sitter/parser.d.ts.map +1 -1
  166. package/dist/adapters/tree-sitter/parser.js +12 -12
  167. package/dist/adapters/tree-sitter/parser.js.map +1 -1
  168. package/dist/adapters/voyage/embedding-provider.js +2 -2
  169. package/dist/adapters/voyage/embedding-provider.js.map +1 -1
  170. package/dist/cli/add/code.d.ts +3 -0
  171. package/dist/cli/add/code.d.ts.map +1 -0
  172. package/dist/cli/add/code.js +97 -0
  173. package/dist/cli/add/code.js.map +1 -0
  174. package/dist/cli/add/csv.d.ts +3 -0
  175. package/dist/cli/add/csv.d.ts.map +1 -0
  176. package/dist/cli/add/csv.js +26 -0
  177. package/dist/cli/add/csv.js.map +1 -0
  178. package/dist/cli/add/db.d.ts +3 -0
  179. package/dist/cli/add/db.d.ts.map +1 -0
  180. package/dist/cli/add/db.js +46 -0
  181. package/dist/cli/add/db.js.map +1 -0
  182. package/dist/cli/add/git.d.ts +3 -0
  183. package/dist/cli/add/git.d.ts.map +1 -0
  184. package/dist/cli/add/git.js +26 -0
  185. package/dist/cli/add/git.js.map +1 -0
  186. package/dist/cli/add/github.d.ts +3 -0
  187. package/dist/cli/add/github.d.ts.map +1 -0
  188. package/dist/cli/add/github.js +32 -0
  189. package/dist/cli/add/github.js.map +1 -0
  190. package/dist/cli/add/index.d.ts +3 -0
  191. package/dist/cli/add/index.d.ts.map +1 -0
  192. package/dist/cli/add/index.js +18 -0
  193. package/dist/cli/add/index.js.map +1 -0
  194. package/dist/cli/add/jira.d.ts +3 -0
  195. package/dist/cli/add/jira.d.ts.map +1 -0
  196. package/dist/cli/add/jira.js +45 -0
  197. package/dist/cli/add/jira.js.map +1 -0
  198. package/dist/cli/add/shared.d.ts +31 -0
  199. package/dist/cli/add/shared.d.ts.map +1 -0
  200. package/dist/cli/add/shared.js +102 -0
  201. package/dist/cli/add/shared.js.map +1 -0
  202. package/dist/cli/board-server.js +1 -1
  203. package/dist/cli/board-server.js.map +1 -1
  204. package/dist/cli/board.d.ts.map +1 -1
  205. package/dist/cli/board.js +11 -12
  206. package/dist/cli/board.js.map +1 -1
  207. package/dist/cli/code.d.ts.map +1 -1
  208. package/dist/cli/code.js +95 -193
  209. package/dist/cli/code.js.map +1 -1
  210. package/dist/cli/embed.d.ts.map +1 -1
  211. package/dist/cli/embed.js +21 -26
  212. package/dist/cli/embed.js.map +1 -1
  213. package/dist/cli/graph.d.ts.map +1 -1
  214. package/dist/cli/graph.js +37 -55
  215. package/dist/cli/graph.js.map +1 -1
  216. package/dist/cli/index.js +28 -25
  217. package/dist/cli/index.js.map +1 -1
  218. package/dist/cli/init/cleanup.d.ts +11 -0
  219. package/dist/cli/init/cleanup.d.ts.map +1 -0
  220. package/dist/cli/init/cleanup.js +39 -0
  221. package/dist/cli/init/cleanup.js.map +1 -0
  222. package/dist/cli/init/generators.js +27 -27
  223. package/dist/cli/init/generators.js.map +1 -1
  224. package/dist/cli/init/index.d.ts +0 -10
  225. package/dist/cli/init/index.d.ts.map +1 -1
  226. package/dist/cli/init/index.js +386 -354
  227. package/dist/cli/init/index.js.map +1 -1
  228. package/dist/cli/init/presenter.d.ts +30 -0
  229. package/dist/cli/init/presenter.d.ts.map +1 -0
  230. package/dist/cli/init/presenter.js +152 -0
  231. package/dist/cli/init/presenter.js.map +1 -0
  232. package/dist/cli/init/prompts.d.ts +28 -0
  233. package/dist/cli/init/prompts.d.ts.map +1 -0
  234. package/dist/cli/init/prompts.js +110 -0
  235. package/dist/cli/init/prompts.js.map +1 -0
  236. package/dist/cli/init/setup-csv.js +2 -2
  237. package/dist/cli/init/setup-csv.js.map +1 -1
  238. package/dist/cli/init/setup-db.d.ts.map +1 -1
  239. package/dist/cli/init/setup-db.js +17 -14
  240. package/dist/cli/init/setup-db.js.map +1 -1
  241. package/dist/cli/init/setup-git.js +16 -16
  242. package/dist/cli/init/setup-git.js.map +1 -1
  243. package/dist/cli/init/setup-github.d.ts.map +1 -1
  244. package/dist/cli/init/setup-github.js +7 -5
  245. package/dist/cli/init/setup-github.js.map +1 -1
  246. package/dist/cli/init/setup-jira.d.ts.map +1 -1
  247. package/dist/cli/init/setup-jira.js +11 -9
  248. package/dist/cli/init/setup-jira.js.map +1 -1
  249. package/dist/cli/init/types.d.ts +4 -0
  250. package/dist/cli/init/types.d.ts.map +1 -1
  251. package/dist/cli/init/types.js +4 -4
  252. package/dist/cli/init/types.js.map +1 -1
  253. package/dist/cli/mcp-install.d.ts +1 -1
  254. package/dist/cli/mcp-install.d.ts.map +1 -1
  255. package/dist/cli/mcp-install.js +10 -24
  256. package/dist/cli/mcp-install.js.map +1 -1
  257. package/dist/cli/migrate-to-hub-impl.d.ts +51 -0
  258. package/dist/cli/migrate-to-hub-impl.d.ts.map +1 -0
  259. package/dist/cli/migrate-to-hub-impl.js +171 -0
  260. package/dist/cli/migrate-to-hub-impl.js.map +1 -0
  261. package/dist/cli/migrate-to-hub.d.ts +3 -0
  262. package/dist/cli/migrate-to-hub.d.ts.map +1 -0
  263. package/dist/cli/migrate-to-hub.js +90 -0
  264. package/dist/cli/migrate-to-hub.js.map +1 -0
  265. package/dist/cli/push.d.ts.map +1 -1
  266. package/dist/cli/push.js +62 -65
  267. package/dist/cli/push.js.map +1 -1
  268. package/dist/cli/sources.d.ts +0 -7
  269. package/dist/cli/sources.d.ts.map +1 -1
  270. package/dist/cli/sources.js +47 -112
  271. package/dist/cli/sources.js.map +1 -1
  272. package/dist/cli/status.d.ts +0 -3
  273. package/dist/cli/status.d.ts.map +1 -1
  274. package/dist/cli/status.js +95 -70
  275. package/dist/cli/status.js.map +1 -1
  276. package/dist/cli/sync.d.ts +0 -10
  277. package/dist/cli/sync.d.ts.map +1 -1
  278. package/dist/cli/sync.js +186 -317
  279. package/dist/cli/sync.js.map +1 -1
  280. package/dist/cli/workspace/add.d.ts +3 -0
  281. package/dist/cli/workspace/add.d.ts.map +1 -0
  282. package/dist/cli/workspace/add.js +38 -0
  283. package/dist/cli/workspace/add.js.map +1 -0
  284. package/dist/cli/workspace/index.d.ts +3 -0
  285. package/dist/cli/workspace/index.d.ts.map +1 -0
  286. package/dist/cli/workspace/index.js +16 -0
  287. package/dist/cli/workspace/index.js.map +1 -0
  288. package/dist/cli/workspace/info.d.ts +3 -0
  289. package/dist/cli/workspace/info.d.ts.map +1 -0
  290. package/dist/cli/workspace/info.js +69 -0
  291. package/dist/cli/workspace/info.js.map +1 -0
  292. package/dist/cli/workspace/list.d.ts +3 -0
  293. package/dist/cli/workspace/list.d.ts.map +1 -0
  294. package/dist/cli/workspace/list.js +36 -0
  295. package/dist/cli/workspace/list.js.map +1 -0
  296. package/dist/cli/workspace/remove.d.ts +3 -0
  297. package/dist/cli/workspace/remove.d.ts.map +1 -0
  298. package/dist/cli/workspace/remove.js +43 -0
  299. package/dist/cli/workspace/remove.js.map +1 -0
  300. package/dist/cli/workspace/shared.d.ts +7 -0
  301. package/dist/cli/workspace/shared.d.ts.map +1 -0
  302. package/dist/cli/workspace/shared.js +17 -0
  303. package/dist/cli/workspace/shared.js.map +1 -0
  304. package/dist/cli/workspace/use.d.ts +3 -0
  305. package/dist/cli/workspace/use.d.ts.map +1 -0
  306. package/dist/cli/workspace/use.js +27 -0
  307. package/dist/cli/workspace/use.js.map +1 -0
  308. package/dist/code-intel/file-discovery.js +3 -3
  309. package/dist/code-intel/file-discovery.js.map +1 -1
  310. package/dist/code-intel/indexer.js +8 -8
  311. package/dist/code-intel/indexer.js.map +1 -1
  312. package/dist/code-intel/lsp-resolver.js +5 -5
  313. package/dist/code-intel/lsp-resolver.js.map +1 -1
  314. package/dist/code-intel/ranker.d.ts +14 -0
  315. package/dist/code-intel/ranker.d.ts.map +1 -1
  316. package/dist/code-intel/ranker.js +54 -0
  317. package/dist/code-intel/ranker.js.map +1 -1
  318. package/dist/code-intel/resolver.js +4 -4
  319. package/dist/code-intel/resolver.js.map +1 -1
  320. package/dist/code-intel/tsconfig-paths.js +2 -2
  321. package/dist/code-intel/tsconfig-paths.js.map +1 -1
  322. package/dist/code-intel/watcher.js +4 -4
  323. package/dist/code-intel/watcher.js.map +1 -1
  324. package/dist/core/board/board-column.value-object.js +1 -1
  325. package/dist/core/board/board-column.value-object.js.map +1 -1
  326. package/dist/core/board/pipeline.value-object.js +1 -1
  327. package/dist/core/board/pipeline.value-object.js.map +1 -1
  328. package/dist/core/board/skill-execution.entity.js +1 -1
  329. package/dist/core/board/skill-execution.entity.js.map +1 -1
  330. package/dist/core/board/task-title.value-object.js +1 -1
  331. package/dist/core/board/task-title.value-object.js.map +1 -1
  332. package/dist/core/ports/chat-llm.d.ts +36 -0
  333. package/dist/core/ports/chat-llm.d.ts.map +1 -0
  334. package/dist/core/ports/chat-llm.js +2 -0
  335. package/dist/core/ports/chat-llm.js.map +1 -0
  336. package/dist/core/ports/code-meta.d.ts +4 -0
  337. package/dist/core/ports/code-meta.d.ts.map +1 -1
  338. package/dist/core/ports/code-vector-store.d.ts +5 -0
  339. package/dist/core/ports/code-vector-store.d.ts.map +1 -1
  340. package/dist/core/ports/docker-control.d.ts +44 -0
  341. package/dist/core/ports/docker-control.d.ts.map +1 -0
  342. package/dist/core/ports/docker-control.js +8 -0
  343. package/dist/core/ports/docker-control.js.map +1 -0
  344. package/dist/core/ports/hub-readiness-probe.d.ts +22 -0
  345. package/dist/core/ports/hub-readiness-probe.d.ts.map +1 -0
  346. package/dist/core/ports/hub-readiness-probe.js +2 -0
  347. package/dist/core/ports/hub-readiness-probe.js.map +1 -0
  348. package/dist/core/ports/index.d.ts +4 -0
  349. package/dist/core/ports/index.d.ts.map +1 -1
  350. package/dist/core/ports/ollama-control.d.ts +61 -0
  351. package/dist/core/ports/ollama-control.d.ts.map +1 -0
  352. package/dist/core/ports/ollama-control.js +7 -0
  353. package/dist/core/ports/ollama-control.js.map +1 -0
  354. package/dist/core/ports/platform-probe.d.ts +47 -0
  355. package/dist/core/ports/platform-probe.d.ts.map +1 -0
  356. package/dist/core/ports/platform-probe.js +8 -0
  357. package/dist/core/ports/platform-probe.js.map +1 -0
  358. package/dist/core/ports/storage.d.ts +62 -29
  359. package/dist/core/ports/storage.d.ts.map +1 -1
  360. package/dist/core/ports/workspace-store.d.ts +52 -0
  361. package/dist/core/ports/workspace-store.d.ts.map +1 -0
  362. package/dist/core/ports/workspace-store.js +2 -0
  363. package/dist/core/ports/workspace-store.js.map +1 -0
  364. package/dist/core/types/index.d.ts +2 -0
  365. package/dist/core/types/index.d.ts.map +1 -1
  366. package/dist/core/types/init.d.ts +29 -0
  367. package/dist/core/types/init.d.ts.map +1 -0
  368. package/dist/core/types/init.js +2 -0
  369. package/dist/core/types/init.js.map +1 -0
  370. package/dist/core/types/workspace.d.ts +78 -0
  371. package/dist/core/types/workspace.d.ts.map +1 -0
  372. package/dist/core/types/workspace.js +2 -0
  373. package/dist/core/types/workspace.js.map +1 -0
  374. package/dist/mcp/helpers.d.ts +121 -30
  375. package/dist/mcp/helpers.d.ts.map +1 -1
  376. package/dist/mcp/helpers.js +240 -264
  377. package/dist/mcp/helpers.js.map +1 -1
  378. package/dist/mcp/server.d.ts.map +1 -1
  379. package/dist/mcp/server.js +43 -5
  380. package/dist/mcp/server.js.map +1 -1
  381. package/dist/mcp/tools/code-graph.d.ts.map +1 -1
  382. package/dist/mcp/tools/code-graph.js +109 -74
  383. package/dist/mcp/tools/code-graph.js.map +1 -1
  384. package/dist/mcp/tools/code-hybrid.d.ts.map +1 -1
  385. package/dist/mcp/tools/code-hybrid.js +75 -51
  386. package/dist/mcp/tools/code-hybrid.js.map +1 -1
  387. package/dist/mcp/tools/code-search.d.ts.map +1 -1
  388. package/dist/mcp/tools/code-search.js +32 -32
  389. package/dist/mcp/tools/code-search.js.map +1 -1
  390. package/dist/mcp/tools/database.d.ts.map +1 -1
  391. package/dist/mcp/tools/database.js +86 -97
  392. package/dist/mcp/tools/database.js.map +1 -1
  393. package/dist/mcp/tools/estimate.d.ts.map +1 -1
  394. package/dist/mcp/tools/estimate.js +106 -82
  395. package/dist/mcp/tools/estimate.js.map +1 -1
  396. package/dist/mcp/tools/graph.d.ts.map +1 -1
  397. package/dist/mcp/tools/graph.js +142 -157
  398. package/dist/mcp/tools/graph.js.map +1 -1
  399. package/dist/mcp/tools/issue.d.ts.map +1 -1
  400. package/dist/mcp/tools/issue.js +172 -172
  401. package/dist/mcp/tools/issue.js.map +1 -1
  402. package/dist/mcp/tools/push.d.ts.map +1 -1
  403. package/dist/mcp/tools/push.js +70 -100
  404. package/dist/mcp/tools/push.js.map +1 -1
  405. package/dist/mcp/tools/query.d.ts.map +1 -1
  406. package/dist/mcp/tools/query.js +187 -246
  407. package/dist/mcp/tools/query.js.map +1 -1
  408. package/dist/mcp/tools/search.d.ts.map +1 -1
  409. package/dist/mcp/tools/search.js +25 -22
  410. package/dist/mcp/tools/search.js.map +1 -1
  411. package/dist/mcp/tools/workspace.d.ts.map +1 -1
  412. package/dist/mcp/tools/workspace.js +120 -73
  413. package/dist/mcp/tools/workspace.js.map +1 -1
  414. package/dist/use-cases/build-graph.d.ts +1 -1
  415. package/dist/use-cases/build-graph.d.ts.map +1 -1
  416. package/dist/use-cases/build-graph.js +63 -50
  417. package/dist/use-cases/build-graph.js.map +1 -1
  418. package/dist/use-cases/code-search.d.ts +29 -5
  419. package/dist/use-cases/code-search.d.ts.map +1 -1
  420. package/dist/use-cases/code-search.js +98 -9
  421. package/dist/use-cases/code-search.js.map +1 -1
  422. package/dist/use-cases/embed.d.ts +1 -1
  423. package/dist/use-cases/embed.d.ts.map +1 -1
  424. package/dist/use-cases/embed.js +5 -5
  425. package/dist/use-cases/embed.js.map +1 -1
  426. package/dist/use-cases/index-code.js +2 -2
  427. package/dist/use-cases/index-code.js.map +1 -1
  428. package/dist/use-cases/init/bootstrap-hub.d.ts +55 -0
  429. package/dist/use-cases/init/bootstrap-hub.d.ts.map +1 -0
  430. package/dist/use-cases/init/bootstrap-hub.js +116 -0
  431. package/dist/use-cases/init/bootstrap-hub.js.map +1 -0
  432. package/dist/use-cases/init/check-dims-conflict.d.ts +33 -0
  433. package/dist/use-cases/init/check-dims-conflict.d.ts.map +1 -0
  434. package/dist/use-cases/init/check-dims-conflict.js +39 -0
  435. package/dist/use-cases/init/check-dims-conflict.js.map +1 -0
  436. package/dist/use-cases/init/check-hub-exists.d.ts +23 -0
  437. package/dist/use-cases/init/check-hub-exists.d.ts.map +1 -0
  438. package/dist/use-cases/init/check-hub-exists.js +39 -0
  439. package/dist/use-cases/init/check-hub-exists.js.map +1 -0
  440. package/dist/use-cases/init/check-ports.d.ts +18 -0
  441. package/dist/use-cases/init/check-ports.d.ts.map +1 -0
  442. package/dist/use-cases/init/check-ports.js +25 -0
  443. package/dist/use-cases/init/check-ports.js.map +1 -0
  444. package/dist/use-cases/init/check-versions.d.ts +37 -0
  445. package/dist/use-cases/init/check-versions.d.ts.map +1 -0
  446. package/dist/use-cases/init/check-versions.js +50 -0
  447. package/dist/use-cases/init/check-versions.js.map +1 -0
  448. package/dist/use-cases/init/clear-stale-active.d.ts +20 -0
  449. package/dist/use-cases/init/clear-stale-active.d.ts.map +1 -0
  450. package/dist/use-cases/init/clear-stale-active.js +26 -0
  451. package/dist/use-cases/init/clear-stale-active.js.map +1 -0
  452. package/dist/use-cases/init/ensure-embedding-model.d.ts +20 -0
  453. package/dist/use-cases/init/ensure-embedding-model.d.ts.map +1 -0
  454. package/dist/use-cases/init/ensure-embedding-model.js +24 -0
  455. package/dist/use-cases/init/ensure-embedding-model.js.map +1 -0
  456. package/dist/use-cases/init/ensure-ollama-running.d.ts +19 -0
  457. package/dist/use-cases/init/ensure-ollama-running.d.ts.map +1 -0
  458. package/dist/use-cases/init/ensure-ollama-running.js +24 -0
  459. package/dist/use-cases/init/ensure-ollama-running.js.map +1 -0
  460. package/dist/use-cases/init/health-check-existing-llm.d.ts +25 -0
  461. package/dist/use-cases/init/health-check-existing-llm.d.ts.map +1 -0
  462. package/dist/use-cases/init/health-check-existing-llm.js +42 -0
  463. package/dist/use-cases/init/health-check-existing-llm.js.map +1 -0
  464. package/dist/use-cases/init/install-ollama.d.ts +27 -0
  465. package/dist/use-cases/init/install-ollama.d.ts.map +1 -0
  466. package/dist/use-cases/init/install-ollama.js +51 -0
  467. package/dist/use-cases/init/install-ollama.js.map +1 -0
  468. package/dist/use-cases/init/probe-llm.d.ts +32 -0
  469. package/dist/use-cases/init/probe-llm.d.ts.map +1 -0
  470. package/dist/use-cases/init/probe-llm.js +42 -0
  471. package/dist/use-cases/init/probe-llm.js.map +1 -0
  472. package/dist/use-cases/init/resolve-hub-ports.d.ts +46 -0
  473. package/dist/use-cases/init/resolve-hub-ports.d.ts.map +1 -0
  474. package/dist/use-cases/init/resolve-hub-ports.js +77 -0
  475. package/dist/use-cases/init/resolve-hub-ports.js.map +1 -0
  476. package/dist/use-cases/init/validate-workspace-name.d.ts +26 -0
  477. package/dist/use-cases/init/validate-workspace-name.d.ts.map +1 -0
  478. package/dist/use-cases/init/validate-workspace-name.js +39 -0
  479. package/dist/use-cases/init/validate-workspace-name.js.map +1 -0
  480. package/dist/use-cases/init/wait-for-docker.d.ts +16 -0
  481. package/dist/use-cases/init/wait-for-docker.d.ts.map +1 -0
  482. package/dist/use-cases/init/wait-for-docker.js +16 -0
  483. package/dist/use-cases/init/wait-for-docker.js.map +1 -0
  484. package/dist/use-cases/init/write-config-env.d.ts +32 -0
  485. package/dist/use-cases/init/write-config-env.d.ts.map +1 -0
  486. package/dist/use-cases/init/write-config-env.js +118 -0
  487. package/dist/use-cases/init/write-config-env.js.map +1 -0
  488. package/dist/use-cases/move-task.js +2 -2
  489. package/dist/use-cases/move-task.js.map +1 -1
  490. package/dist/use-cases/pull-db.d.ts +1 -1
  491. package/dist/use-cases/pull-db.d.ts.map +1 -1
  492. package/dist/use-cases/pull-db.js +3 -3
  493. package/dist/use-cases/pull-db.js.map +1 -1
  494. package/dist/use-cases/pull-git.d.ts +1 -1
  495. package/dist/use-cases/pull-git.d.ts.map +1 -1
  496. package/dist/use-cases/pull-git.js +5 -5
  497. package/dist/use-cases/pull-git.js.map +1 -1
  498. package/dist/use-cases/pull-github.d.ts +1 -1
  499. package/dist/use-cases/pull-github.d.ts.map +1 -1
  500. package/dist/use-cases/pull-github.js +6 -6
  501. package/dist/use-cases/pull-github.js.map +1 -1
  502. package/dist/use-cases/pull.d.ts +1 -1
  503. package/dist/use-cases/pull.d.ts.map +1 -1
  504. package/dist/use-cases/pull.js +6 -6
  505. package/dist/use-cases/pull.js.map +1 -1
  506. package/dist/use-cases/push.d.ts +2 -2
  507. package/dist/use-cases/push.d.ts.map +1 -1
  508. package/dist/use-cases/push.js +8 -8
  509. package/dist/use-cases/push.js.map +1 -1
  510. package/dist/use-cases/register-code-project.d.ts +8 -0
  511. package/dist/use-cases/register-code-project.d.ts.map +1 -1
  512. package/dist/use-cases/register-code-project.js +8 -0
  513. package/dist/use-cases/register-code-project.js.map +1 -1
  514. package/dist/use-cases/watch-code.js +1 -1
  515. package/dist/use-cases/watch-code.js.map +1 -1
  516. package/dist/workspace/active-workspace.d.ts +26 -0
  517. package/dist/workspace/active-workspace.d.ts.map +1 -0
  518. package/dist/workspace/active-workspace.js +69 -0
  519. package/dist/workspace/active-workspace.js.map +1 -0
  520. package/dist/workspace/adf.d.ts.map +1 -1
  521. package/dist/workspace/adf.js +20 -19
  522. package/dist/workspace/adf.js.map +1 -1
  523. package/dist/workspace/config.d.ts +11 -0
  524. package/dist/workspace/config.d.ts.map +1 -1
  525. package/dist/workspace/config.js +76 -22
  526. package/dist/workspace/config.js.map +1 -1
  527. package/dist/workspace/hub-config.d.ts +74 -0
  528. package/dist/workspace/hub-config.d.ts.map +1 -0
  529. package/dist/workspace/hub-config.js +164 -0
  530. package/dist/workspace/hub-config.js.map +1 -0
  531. package/dist/workspace/registry.d.ts +23 -3
  532. package/dist/workspace/registry.d.ts.map +1 -1
  533. package/dist/workspace/registry.js +32 -14
  534. package/dist/workspace/registry.js.map +1 -1
  535. package/dist/workspace/resolver.d.ts +20 -18
  536. package/dist/workspace/resolver.d.ts.map +1 -1
  537. package/dist/workspace/resolver.js +100 -59
  538. package/dist/workspace/resolver.js.map +1 -1
  539. package/package.json +4 -2
  540. package/templates/hub-config.env +65 -0
  541. package/templates/hub-docker-compose.yml +88 -0
  542. package/templates/migrate-to-hub.sql +90 -0
  543. package/dist/cli/workspaces.d.ts +0 -3
  544. package/dist/cli/workspaces.d.ts.map +0 -1
  545. package/dist/cli/workspaces.js +0 -34
  546. package/dist/cli/workspaces.js.map +0 -1
@@ -1,49 +1,51 @@
1
1
  import { z } from 'zod/v4';
2
- import { loadWorkspace, createAdapters, textResponse, errorResponse, getErrorMessage, str, } from '../helpers.js';
2
+ import { loadWorkspace, createAdapters, textResponse, errorResponse, workspaceNotFoundResponse, getErrorMessage, str, strOr, hasText, ANNOTATIONS, } from '../helpers.js';
3
3
  import { groupReviewsByPr, groupFilesByCommit } from './formatters.js';
4
+ const workspaceIdParam = z.string().optional().describe('Workspace id or name (defaults to active workspace)');
4
5
  export function registerIssueTools(server) {
5
6
  server.registerTool('get_issue', {
6
- description: 'Get full details of a specific issue by key — description, comments, changelogs, worklogs, links, all custom fields. Use for deep-dive into a single issue. For cross-source timeline use issue_timeline instead.',
7
+ title: 'Get issue details',
8
+ description: 'Get full details of an issue by key — fields, comments, changelog. For full cross-source timeline use issue_timeline.',
7
9
  inputSchema: {
10
+ workspace_id: workspaceIdParam,
8
11
  issue_key: z.string().describe('Issue key (e.g. "PROJ-123")'),
9
12
  },
10
- }, async ({ issue_key: issueKey }) => {
11
- const ws = loadWorkspace();
13
+ annotations: ANNOTATIONS.READ_ONLY,
14
+ }, async ({ workspace_id: workspaceIdInput, issue_key: issueKey }) => {
15
+ const ws = await loadWorkspace(workspaceIdInput);
12
16
  if (!ws.ok) {
13
- return errorResponse(`Workspace not found: ${ws.reason}`);
17
+ return workspaceNotFoundResponse(ws.reason);
14
18
  }
15
- const { storage } = await createAdapters(ws.root);
19
+ const { storage, workspaceId } = await createAdapters(ws.workspaceId);
16
20
  const key = issueKey.toUpperCase();
17
21
  try {
18
- const issueResult = await storage.query(`SELECT * FROM issues WHERE issue_key = $1`, [key]);
22
+ const issueResult = await storage.queryForWorkspace(workspaceId, `SELECT * FROM issues WHERE workspace_id = $1 AND issue_key = $2`, [key]);
19
23
  if (issueResult.rows.length === 0) {
20
- await storage.close();
21
- return errorResponse(`Issue ${issueKey} not found in local database.`);
24
+ return errorResponse(`Issue ${issueKey} not found in workspace ${workspaceId}.`);
22
25
  }
23
26
  const issue = issueResult.rows[0];
24
- const commentsResult = await storage.query(`SELECT author, body, created FROM issue_comments WHERE issue_key = $1 ORDER BY created`, [key]);
25
- const changelogsResult = await storage.query(`SELECT author, field, from_value, to_value, changed_at
26
- FROM issue_changelogs WHERE issue_key = $1 ORDER BY changed_at DESC LIMIT 20`, [key]);
27
- await storage.close();
27
+ const commentsResult = await storage.queryForWorkspace(workspaceId, `SELECT author, body, created FROM issue_comments WHERE workspace_id = $1 AND issue_key = $2 ORDER BY created`, [key]);
28
+ const changelogsResult = await storage.queryForWorkspace(workspaceId, `SELECT author, field, from_value, to_value, changed_at
29
+ FROM issue_changelogs WHERE workspace_id = $1 AND issue_key = $2 ORDER BY changed_at DESC LIMIT 20`, [key]);
28
30
  const sections = [];
29
31
  sections.push(`# ${str(issue.issue_key)}: ${str(issue.summary)}`);
30
32
  sections.push('');
31
- sections.push(`Type: ${str(issue.issue_type) || 'N/A'} | Status: ${str(issue.status) || 'N/A'} | Priority: ${str(issue.priority) || 'N/A'}`);
32
- sections.push(`Assignee: ${str(issue.assignee) || 'Unassigned'} | Reporter: ${str(issue.reporter) || 'N/A'}`);
33
- sections.push(`Created: ${str(issue.created) || 'N/A'} | Updated: ${str(issue.updated) || 'N/A'}`);
33
+ sections.push(`Type: ${strOr(issue.issue_type, 'N/A')} | Status: ${strOr(issue.status, 'N/A')} | Priority: ${strOr(issue.priority, 'N/A')}`);
34
+ sections.push(`Assignee: ${strOr(issue.assignee, 'Unassigned')} | Reporter: ${strOr(issue.reporter, 'N/A')}`);
35
+ sections.push(`Created: ${strOr(issue.created, 'N/A')} | Updated: ${strOr(issue.updated, 'N/A')}`);
34
36
  if (Array.isArray(issue.labels) && issue.labels.length > 0) {
35
37
  sections.push(`Labels: ${issue.labels.join(', ')}`);
36
38
  }
37
39
  if (Array.isArray(issue.components) && issue.components.length > 0) {
38
40
  sections.push(`Components: ${issue.components.join(', ')}`);
39
41
  }
40
- if (issue.parent_key) {
42
+ if (hasText(issue.parent_key)) {
41
43
  sections.push(`Parent: ${issue.parent_key}`);
42
44
  }
43
45
  sections.push('');
44
46
  sections.push('## Description');
45
- sections.push(str(issue.description) || '(no description)');
46
- if (issue.custom_fields && Object.keys(issue.custom_fields).length > 0) {
47
+ sections.push(strOr(issue.description, '(no description)'));
48
+ if (issue.custom_fields !== undefined && Object.keys(issue.custom_fields).length > 0) {
47
49
  sections.push('');
48
50
  sections.push('## Custom Fields');
49
51
  for (const [field, value] of Object.entries(issue.custom_fields)) {
@@ -55,18 +57,18 @@ export function registerIssueTools(server) {
55
57
  if (commentsResult.rows.length > 0) {
56
58
  sections.push('');
57
59
  sections.push(`## Comments (${String(commentsResult.rows.length)})`);
58
- for (const rawComment of commentsResult.rows) {
59
- const c = rawComment;
60
+ for (const raw of commentsResult.rows) {
61
+ const c = raw;
60
62
  sections.push(`--- ${str(c.author)} (${str(c.created)}) ---`);
61
- sections.push(str(c.body) || '(empty)');
63
+ sections.push(strOr(c.body, '(empty)'));
62
64
  sections.push('');
63
65
  }
64
66
  }
65
67
  if (changelogsResult.rows.length > 0) {
66
68
  sections.push('');
67
69
  sections.push(`## Recent Changes (${String(changelogsResult.rows.length)})`);
68
- for (const rawChangelog of changelogsResult.rows) {
69
- const ch = rawChangelog;
70
+ for (const raw of changelogsResult.rows) {
71
+ const ch = raw;
70
72
  sections.push(` ${str(ch.changed_at)}: ${str(ch.author)} changed ${str(ch.field)}: "${str(ch.from_value)}" → "${str(ch.to_value)}"`);
71
73
  }
72
74
  }
@@ -77,56 +79,54 @@ export function registerIssueTools(server) {
77
79
  }
78
80
  });
79
81
  server.registerTool('issue_stats', {
80
- description: 'Get aggregate statistics about issues — counts by status, type, project, assignee. Use for project health overview, sprint planning, or answering "how many bugs do we have?" Returns grouped counts, not individual issues.',
82
+ title: 'Issue statistics',
83
+ description: 'Aggregate issue statistics for the active workspace — counts by status, type, project, assignee.',
81
84
  inputSchema: {
82
- project: z.string().optional().describe('Filter stats by project key'),
85
+ workspace_id: workspaceIdParam,
86
+ project: z.string().optional().describe('Filter by project key'),
83
87
  },
84
- }, async ({ project }) => {
85
- const ws = loadWorkspace();
88
+ annotations: ANNOTATIONS.READ_ONLY,
89
+ }, async ({ workspace_id: workspaceIdInput, project }) => {
90
+ const ws = await loadWorkspace(workspaceIdInput);
86
91
  if (!ws.ok) {
87
- return errorResponse(`Workspace not found: ${ws.reason}`);
92
+ return workspaceNotFoundResponse(ws.reason);
88
93
  }
89
- const { storage } = await createAdapters(ws.root);
94
+ const { storage, workspaceId } = await createAdapters(ws.workspaceId);
90
95
  try {
91
- const filterClause = project ? `WHERE project_key = $1` : '';
92
- const filterParams = project ? [project.toUpperCase()] : [];
96
+ const projectClause = hasText(project) ? `AND project_key = $2` : '';
97
+ const params = hasText(project) ? [project.toUpperCase()] : [];
93
98
  const [total, byStatus, byType, byProject, byAssignee] = await Promise.all([
94
- storage.query(`SELECT COUNT(*) as count FROM issues ${filterClause}`, filterParams),
95
- storage.query(`SELECT status, COUNT(*) as count FROM issues ${filterClause} GROUP BY status ORDER BY count DESC`, filterParams),
96
- storage.query(`SELECT issue_type, COUNT(*) as count FROM issues ${filterClause} GROUP BY issue_type ORDER BY count DESC`, filterParams),
97
- storage.query(`SELECT project_key, COUNT(*) as count FROM issues ${filterClause} GROUP BY project_key ORDER BY count DESC`, filterParams),
98
- storage.query(`SELECT assignee, COUNT(*) as count FROM issues ${filterClause} GROUP BY assignee ORDER BY count DESC LIMIT 15`, filterParams),
99
+ storage.queryForWorkspace(workspaceId, `SELECT COUNT(*) as count FROM issues WHERE workspace_id = $1 ${projectClause}`, params),
100
+ storage.queryForWorkspace(workspaceId, `SELECT status, COUNT(*) as count FROM issues WHERE workspace_id = $1 ${projectClause} GROUP BY status ORDER BY count DESC`, params),
101
+ storage.queryForWorkspace(workspaceId, `SELECT issue_type, COUNT(*) as count FROM issues WHERE workspace_id = $1 ${projectClause} GROUP BY issue_type ORDER BY count DESC`, params),
102
+ storage.queryForWorkspace(workspaceId, `SELECT project_key, COUNT(*) as count FROM issues WHERE workspace_id = $1 ${projectClause} GROUP BY project_key ORDER BY count DESC`, params),
103
+ storage.queryForWorkspace(workspaceId, `SELECT assignee, COUNT(*) as count FROM issues WHERE workspace_id = $1 ${projectClause} GROUP BY assignee ORDER BY count DESC LIMIT 15`, params),
99
104
  ]);
100
- await storage.close();
101
105
  const sections = [];
102
106
  const totalRow = total.rows[0];
103
- sections.push(`# Issue Statistics${project ? ` (${project})` : ''}`);
107
+ sections.push(`# Issue Statistics${hasText(project) ? ` (${project})` : ''} — workspace ${workspaceId}`);
104
108
  sections.push(`Total issues: ${str(totalRow?.count)}`);
105
- sections.push('');
106
- sections.push('## By Status');
107
- for (const rawRow of byStatus.rows) {
108
- const r = rawRow;
109
- sections.push(` ${str(r.status) || 'null'}: ${str(r.count)}`);
109
+ sections.push('\n## By Status');
110
+ for (const raw of byStatus.rows) {
111
+ const r = raw;
112
+ sections.push(` ${strOr(r.status, 'null')}: ${str(r.count)}`);
110
113
  }
111
- sections.push('');
112
- sections.push('## By Type');
113
- for (const rawRow of byType.rows) {
114
- const r = rawRow;
115
- sections.push(` ${str(r.issue_type) || 'null'}: ${str(r.count)}`);
114
+ sections.push('\n## By Type');
115
+ for (const raw of byType.rows) {
116
+ const r = raw;
117
+ sections.push(` ${strOr(r.issue_type, 'null')}: ${str(r.count)}`);
116
118
  }
117
- if (!project) {
118
- sections.push('');
119
- sections.push('## By Project');
120
- for (const rawRow of byProject.rows) {
121
- const r = rawRow;
119
+ if (!hasText(project)) {
120
+ sections.push('\n## By Project');
121
+ for (const raw of byProject.rows) {
122
+ const r = raw;
122
123
  sections.push(` ${str(r.project_key)}: ${str(r.count)}`);
123
124
  }
124
125
  }
125
- sections.push('');
126
- sections.push('## Top Assignees');
127
- for (const rawRow of byAssignee.rows) {
128
- const r = rawRow;
129
- sections.push(` ${str(r.assignee) || 'Unassigned'}: ${str(r.count)}`);
126
+ sections.push('\n## Top Assignees');
127
+ for (const raw of byAssignee.rows) {
128
+ const r = raw;
129
+ sections.push(` ${strOr(r.assignee, 'Unassigned')}: ${str(r.count)}`);
130
130
  }
131
131
  return textResponse(sections.join('\n'));
132
132
  }
@@ -135,44 +135,42 @@ export function registerIssueTools(server) {
135
135
  }
136
136
  });
137
137
  server.registerTool('issue_commits', {
138
- description: 'Cross-reference: find all Git commits that mention a Jira issue key in commit message. Shows what code was actually changed. Requires Git sync. For full timeline with PRs use issue_timeline.',
138
+ title: 'Commits for an issue',
139
+ description: 'Git commits mentioning the issue key. Requires Git sync.',
139
140
  inputSchema: {
141
+ workspace_id: workspaceIdParam,
140
142
  issue_key: z.string().describe('Issue key (e.g. "PROJ-123")'),
141
- repo_path: z.string().optional().describe('Filter by repository path (substring match)'),
143
+ repo_path: z.string().optional().describe('Filter by repo path substring'),
142
144
  },
143
- }, async ({ issue_key: issueKey, repo_path: repoPath }) => {
144
- const ws = loadWorkspace();
145
+ annotations: ANNOTATIONS.READ_ONLY,
146
+ }, async ({ workspace_id: workspaceIdInput, issue_key: issueKey, repo_path: repoPath }) => {
147
+ const ws = await loadWorkspace(workspaceIdInput);
145
148
  if (!ws.ok) {
146
- return errorResponse(`Workspace not found: ${ws.reason}`);
149
+ return workspaceNotFoundResponse(ws.reason);
147
150
  }
148
- const { storage } = await createAdapters(ws.root);
151
+ const { storage, workspaceId } = await createAdapters(ws.workspaceId);
149
152
  const key = issueKey.toUpperCase();
150
153
  try {
151
- const repoFilter = repoPath ? `AND c.repo_path ILIKE $2` : '';
152
- const commitsParams = [key];
153
- if (repoPath) {
154
- commitsParams.push(`%${repoPath}%`);
155
- }
156
- const commitsResult = await storage.query(`SELECT c.hash, c.message, c.author, c.committed_at, c.repo_path
154
+ const repoFilter = hasText(repoPath) ? `AND c.repo_path ILIKE $3` : '';
155
+ const params = hasText(repoPath) ? [key, `%${repoPath}%`] : [key];
156
+ const commitsResult = await storage.queryForWorkspace(workspaceId, `SELECT c.hash, c.message, c.author, c.committed_at, c.repo_path
157
157
  FROM commits c
158
- JOIN commit_issue_refs r ON c.hash = r.commit_hash
159
- WHERE r.issue_key = $1 ${repoFilter}
160
- ORDER BY c.committed_at DESC`, commitsParams);
158
+ JOIN commit_issue_refs r ON r.workspace_id = c.workspace_id AND r.commit_hash = c.hash
159
+ WHERE c.workspace_id = $1 AND r.issue_key = $2 ${repoFilter}
160
+ ORDER BY c.committed_at DESC`, params);
161
161
  if (commitsResult.rows.length === 0) {
162
- await storage.close();
163
- return textResponse(`No commits found mentioning ${issueKey}. Make sure you've run "argustack sync git".`);
162
+ return textResponse(`No commits mentioning ${issueKey}. Run "argustack sync git" with this workspace.`);
164
163
  }
165
164
  const hashes = commitsResult.rows.map((r) => r.hash).filter(Boolean);
166
- const hashPlaceholders = hashes.map((_, i) => `$${String(i + 1)}`).join(',');
167
- const filesResult = await storage.query(`SELECT commit_hash, file_path, status, additions, deletions
168
- FROM commit_files WHERE commit_hash IN (${hashPlaceholders})`, hashes);
169
- await storage.close();
165
+ const hashPlaceholders = hashes.map((_, i) => `$${String(i + 2)}`).join(',');
166
+ const filesResult = await storage.queryForWorkspace(workspaceId, `SELECT commit_hash, file_path, status, additions, deletions
167
+ FROM commit_files WHERE workspace_id = $1 AND commit_hash IN (${hashPlaceholders})`, hashes);
170
168
  const filesByCommit = groupFilesByCommit(filesResult.rows);
171
169
  const sections = [];
172
170
  sections.push(`# Commits for ${issueKey} (${String(commitsResult.rows.length)})`);
173
171
  sections.push('');
174
- for (const rawRow of commitsResult.rows) {
175
- const row = rawRow;
172
+ for (const raw of commitsResult.rows) {
173
+ const row = raw;
176
174
  const shortHash = (row.hash ?? '').substring(0, 7);
177
175
  sections.push(`## ${shortHash} — ${str(row.author)} (${str(row.committed_at).substring(0, 10)})`);
178
176
  sections.push(str(row.message));
@@ -192,44 +190,47 @@ export function registerIssueTools(server) {
192
190
  }
193
191
  });
194
192
  server.registerTool('issue_prs', {
195
- description: 'Cross-reference: find all GitHub PRs that mention a Jira issue key. Shows which PRs implemented a ticket, with review status and merge info. Requires GitHub sync.',
193
+ title: 'PRs for an issue',
194
+ description: 'GitHub PRs mentioning the issue key. Requires GitHub sync.',
196
195
  inputSchema: {
196
+ workspace_id: workspaceIdParam,
197
197
  issue_key: z.string().describe('Issue key (e.g. "PROJ-123")'),
198
198
  },
199
- }, async ({ issue_key: issueKey }) => {
200
- const ws = loadWorkspace();
199
+ annotations: ANNOTATIONS.READ_ONLY,
200
+ }, async ({ workspace_id: workspaceIdInput, issue_key: issueKey }) => {
201
+ const ws = await loadWorkspace(workspaceIdInput);
201
202
  if (!ws.ok) {
202
- return errorResponse(`Workspace not found: ${ws.reason}`);
203
+ return workspaceNotFoundResponse(ws.reason);
203
204
  }
204
- const { storage } = await createAdapters(ws.root);
205
+ const { storage, workspaceId } = await createAdapters(ws.workspaceId);
205
206
  const key = issueKey.toUpperCase();
206
207
  try {
207
- const prsResult = await storage.query(`SELECT p.number, p.title, p.state, p.author, p.created_at, p.merged_at,
208
+ const prsResult = await storage.queryForWorkspace(workspaceId, `SELECT p.number, p.title, p.state, p.author, p.created_at, p.merged_at,
208
209
  p.additions, p.deletions, p.base_ref, p.head_ref, p.repo_full_name
209
210
  FROM pull_requests p
210
- JOIN pr_issue_refs r ON p.repo_full_name = r.repo_full_name AND p.number = r.pr_number
211
- WHERE r.issue_key = $1
211
+ JOIN pr_issue_refs r ON r.workspace_id = p.workspace_id
212
+ AND r.repo_full_name = p.repo_full_name AND r.pr_number = p.number
213
+ WHERE p.workspace_id = $1 AND r.issue_key = $2
212
214
  ORDER BY p.created_at DESC`, [key]);
213
215
  if (prsResult.rows.length === 0) {
214
- await storage.close();
215
- return textResponse(`No PRs found mentioning ${issueKey}. Make sure GitHub sync is configured.`);
216
+ return textResponse(`No PRs mentioning ${issueKey}.\n` +
217
+ 'If you expected results, run `argustack sync github` to refresh GitHub sync data.');
216
218
  }
217
219
  const prNumbers = prsResult.rows.map((r) => r.number);
218
- const repoFullName = prsResult.rows[0].repo_full_name;
219
- const reviewPlaceholders = prNumbers.map((_, i) => `$${String(i + 2)}`).join(',');
220
- const reviewsResult = await storage.query(`SELECT pr_number, reviewer, state, submitted_at FROM pr_reviews
221
- WHERE repo_full_name = $1 AND pr_number IN (${reviewPlaceholders})
220
+ const repoFullName = prsResult.rows[0].repo_full_name ?? '';
221
+ const reviewPlaceholders = prNumbers.map((_, i) => `$${String(i + 3)}`).join(',');
222
+ const reviewsResult = await storage.queryForWorkspace(workspaceId, `SELECT pr_number, reviewer, state, submitted_at FROM pr_reviews
223
+ WHERE workspace_id = $1 AND repo_full_name = $2 AND pr_number IN (${reviewPlaceholders})
222
224
  ORDER BY submitted_at`, [repoFullName, ...prNumbers]);
223
- await storage.close();
224
225
  const reviewsByPr = groupReviewsByPr(reviewsResult.rows);
225
226
  const sections = [];
226
227
  sections.push(`# Pull Requests for ${issueKey} (${String(prsResult.rows.length)})`);
227
228
  sections.push('');
228
- for (const rawRow of prsResult.rows) {
229
- const pr = rawRow;
229
+ for (const raw of prsResult.rows) {
230
+ const pr = raw;
230
231
  sections.push(`## #${str(pr.number)} — ${str(pr.title)}`);
231
232
  sections.push(`State: ${str(pr.state)} | Author: ${str(pr.author)} | ${str(pr.base_ref)} ← ${str(pr.head_ref)}`);
232
- sections.push(`+${str(pr.additions)} -${str(pr.deletions)} | Created: ${str(pr.created_at ?? '').substring(0, 10)}${pr.merged_at ? ` | Merged: ${str(pr.merged_at).substring(0, 10)}` : ''}`);
233
+ sections.push(`+${str(pr.additions)} -${str(pr.deletions)} | Created: ${str(pr.created_at ?? '').substring(0, 10)}${hasText(pr.merged_at) ? ` | Merged: ${str(pr.merged_at).substring(0, 10)}` : ''}`);
233
234
  const reviews = reviewsByPr.get(pr.number ?? 0) ?? [];
234
235
  if (reviews.length > 0) {
235
236
  sections.push('Reviews:');
@@ -246,34 +247,40 @@ export function registerIssueTools(server) {
246
247
  }
247
248
  });
248
249
  server.registerTool('issue_timeline', {
249
- description: 'Full cross-source timeline for a Jira issue: changelog events, Git commits, GitHub PRs with reviews — all in chronological order. Best tool for understanding the complete history of a ticket. Combines data from Jira + Git + GitHub in one call.',
250
+ title: 'Cross-source issue timeline',
251
+ description: 'Cross-source timeline for an issue: changelog + commits + PRs in chronological order.',
250
252
  inputSchema: {
253
+ workspace_id: workspaceIdParam,
251
254
  issue_key: z.string().describe('Issue key (e.g. "PROJ-123")'),
252
255
  },
253
- }, async ({ issue_key: issueKey }) => {
254
- const ws = loadWorkspace();
256
+ annotations: ANNOTATIONS.READ_ONLY,
257
+ }, async ({ workspace_id: workspaceIdInput, issue_key: issueKey }) => {
258
+ const ws = await loadWorkspace(workspaceIdInput);
255
259
  if (!ws.ok) {
256
- return errorResponse(`Workspace not found: ${ws.reason}`);
260
+ return workspaceNotFoundResponse(ws.reason);
257
261
  }
258
- const { storage } = await createAdapters(ws.root);
262
+ const { storage, workspaceId } = await createAdapters(ws.workspaceId);
259
263
  const key = issueKey.toUpperCase();
260
264
  try {
261
265
  const [issueResult, changelogsResult, commitsResult, prsResult, commitFilesResult] = await Promise.all([
262
- storage.query(`SELECT issue_key, summary, status, issue_type, assignee, reporter, created, updated, resolved FROM issues WHERE issue_key = $1`, [key]),
263
- storage.query(`SELECT author, field, from_value, to_value, changed_at FROM issue_changelogs WHERE issue_key = $1 ORDER BY changed_at`, [key]),
264
- storage.query(`SELECT c.hash, c.message, c.author, c.email, c.committed_at
265
- FROM commits c JOIN commit_issue_refs r ON c.hash = r.commit_hash
266
- WHERE r.issue_key = $1 ORDER BY c.committed_at`, [key]),
267
- storage.query(`SELECT p.number, p.title, p.state, p.author, p.created_at, p.merged_at, p.base_ref,
266
+ storage.queryForWorkspace(workspaceId, `SELECT issue_key, summary, status, issue_type, assignee, reporter, created, updated, resolved
267
+ FROM issues WHERE workspace_id = $1 AND issue_key = $2`, [key]),
268
+ storage.queryForWorkspace(workspaceId, `SELECT author, field, from_value, to_value, changed_at FROM issue_changelogs
269
+ WHERE workspace_id = $1 AND issue_key = $2 ORDER BY changed_at`, [key]),
270
+ storage.queryForWorkspace(workspaceId, `SELECT c.hash, c.message, c.author, c.email, c.committed_at
271
+ FROM commits c JOIN commit_issue_refs r ON r.workspace_id = c.workspace_id AND r.commit_hash = c.hash
272
+ WHERE c.workspace_id = $1 AND r.issue_key = $2 ORDER BY c.committed_at`, [key]),
273
+ storage.queryForWorkspace(workspaceId, `SELECT p.number, p.title, p.state, p.author, p.created_at, p.merged_at, p.base_ref,
268
274
  p.additions, p.deletions, p.repo_full_name
269
- FROM pull_requests p JOIN pr_issue_refs r ON p.repo_full_name = r.repo_full_name AND p.number = r.pr_number
270
- WHERE r.issue_key = $1 ORDER BY p.created_at`, [key]),
271
- storage.query(`SELECT cf.commit_hash, cf.file_path, cf.status, cf.additions, cf.deletions
272
- FROM commit_files cf JOIN commit_issue_refs r ON cf.commit_hash = r.commit_hash
273
- WHERE r.issue_key = $1`, [key]),
275
+ FROM pull_requests p JOIN pr_issue_refs r ON r.workspace_id = p.workspace_id
276
+ AND r.repo_full_name = p.repo_full_name AND r.pr_number = p.number
277
+ WHERE p.workspace_id = $1 AND r.issue_key = $2 ORDER BY p.created_at`, [key]),
278
+ storage.queryForWorkspace(workspaceId, `SELECT cf.commit_hash, cf.file_path, cf.status, cf.additions, cf.deletions
279
+ FROM commit_files cf
280
+ JOIN commit_issue_refs r ON r.workspace_id = cf.workspace_id AND r.commit_hash = cf.commit_hash
281
+ WHERE cf.workspace_id = $1 AND r.issue_key = $2`, [key]),
274
282
  ]);
275
283
  if (issueResult.rows.length === 0) {
276
- await storage.close();
277
284
  return textResponse(`Issue ${key} not found.`);
278
285
  }
279
286
  const issue = issueResult.rows[0];
@@ -281,62 +288,56 @@ export function registerIssueTools(server) {
281
288
  let reviewsByPr = new Map();
282
289
  if (prRows.length > 0) {
283
290
  const prNumbers = prRows.map((pr) => pr.number);
284
- const repoFullName = prRows[0]?.repo_full_name;
285
- const reviewPlaceholders = prNumbers.map((_, i) => `$${String(i + 2)}`).join(',');
286
- const reviewsResult = await storage.query(`SELECT pr_number, reviewer, state, submitted_at FROM pr_reviews
287
- WHERE repo_full_name = $1 AND pr_number IN (${reviewPlaceholders})
291
+ const repoFullName = prRows[0]?.repo_full_name ?? '';
292
+ const reviewPlaceholders = prNumbers.map((_, i) => `$${String(i + 3)}`).join(',');
293
+ const reviewsResult = await storage.queryForWorkspace(workspaceId, `SELECT pr_number, reviewer, state, submitted_at FROM pr_reviews
294
+ WHERE workspace_id = $1 AND repo_full_name = $2 AND pr_number IN (${reviewPlaceholders})
288
295
  ORDER BY submitted_at`, [repoFullName, ...prNumbers]);
289
296
  reviewsByPr = groupReviewsByPr(reviewsResult.rows);
290
297
  }
291
- await storage.close();
292
298
  const filesByCommit = groupFilesByCommit(commitFilesResult.rows);
293
299
  const events = [];
294
- if (issue.created) {
300
+ if (hasText(issue.created)) {
295
301
  events.push({ date: str(issue.created), type: 'created', text: 'Issue created' });
296
302
  }
297
303
  for (const raw of changelogsResult.rows) {
298
304
  const ch = raw;
299
- if (ch.changed_at) {
305
+ if (hasText(ch.changed_at)) {
300
306
  events.push({
301
- date: str(ch.changed_at),
302
- type: 'changelog',
307
+ date: str(ch.changed_at), type: 'changelog',
303
308
  text: `${str(ch.author)} changed ${str(ch.field)}: "${str(ch.from_value)}" → "${str(ch.to_value)}"`,
304
309
  });
305
310
  }
306
311
  }
307
312
  for (const raw of commitsResult.rows) {
308
313
  const c = raw;
309
- if (c.committed_at) {
314
+ if (hasText(c.committed_at)) {
310
315
  const firstLine = (c.message ?? '').split('\n')[0];
311
316
  events.push({
312
- date: str(c.committed_at),
313
- type: 'commit',
317
+ date: str(c.committed_at), type: 'commit',
314
318
  text: `Commit ${c.hash.substring(0, 7)} — "${firstLine}" (${str(c.author)})`,
315
319
  });
316
320
  }
317
321
  }
318
322
  for (const pr of prRows) {
319
- if (pr.created_at) {
323
+ if (hasText(pr.created_at)) {
320
324
  events.push({
321
- date: str(pr.created_at),
322
- type: 'pr_opened',
325
+ date: str(pr.created_at), type: 'pr_opened',
323
326
  text: `PR #${String(pr.number)} opened — "${str(pr.title)}" (${str(pr.author)})`,
324
327
  });
325
328
  }
326
329
  const reviews = reviewsByPr.get(pr.number) ?? [];
327
330
  for (const r of reviews) {
328
- if (r.submitted_at) {
331
+ if (hasText(r.submitted_at)) {
329
332
  events.push({
330
- date: str(r.submitted_at),
331
- type: 'pr_reviewed',
333
+ date: str(r.submitted_at), type: 'pr_reviewed',
332
334
  text: `PR #${String(pr.number)} reviewed — ${str(r.state)} (${str(r.reviewer)})`,
333
335
  });
334
336
  }
335
337
  }
336
- if (pr.merged_at) {
338
+ if (hasText(pr.merged_at)) {
337
339
  events.push({
338
- date: str(pr.merged_at),
339
- type: 'pr_merged',
340
+ date: str(pr.merged_at), type: 'pr_merged',
340
341
  text: `PR #${String(pr.number)} merged into ${str(pr.base_ref)}`,
341
342
  });
342
343
  }
@@ -346,7 +347,7 @@ export function registerIssueTools(server) {
346
347
  sections.push(`=== ISSUE: ${key} ===`);
347
348
  sections.push(`Summary: ${str(issue.summary)}`);
348
349
  sections.push(`Status: ${str(issue.status)} | Type: ${str(issue.issue_type)} | Assignee: ${str(issue.assignee)}`);
349
- sections.push(`Created: ${str(issue.created ?? '').substring(0, 10)} | Resolved: ${str(issue.resolved ?? '').substring(0, 10) || 'n/a'}`);
350
+ sections.push(`Created: ${str(issue.created ?? '').substring(0, 10)} | Resolved: ${strOr(str(issue.resolved ?? '').substring(0, 10), 'n/a')}`);
350
351
  sections.push('');
351
352
  sections.push(`--- TIMELINE (${String(events.length)} events) ---`);
352
353
  for (const ev of events) {
@@ -382,70 +383,69 @@ export function registerIssueTools(server) {
382
383
  }
383
384
  });
384
385
  server.registerTool('commit_stats', {
385
- description: 'Aggregate statistics about Git commits — total count, top authors, most changed files, commits per day/week. Use for "who is most active?" or "what files change most?" questions. Requires Git sync.',
386
+ title: 'Git commit statistics',
387
+ description: 'Aggregate Git commit statistics for the active workspace.',
386
388
  inputSchema: {
387
- since: z.string().optional().describe('Stats from this date (YYYY-MM-DD)'),
388
- author: z.string().optional().describe('Filter stats by author name'),
389
- repo_path: z.string().optional().describe('Filter by repository path (substring match)'),
389
+ workspace_id: workspaceIdParam,
390
+ since: z.string().optional(),
391
+ author: z.string().optional(),
392
+ repo_path: z.string().optional(),
390
393
  },
391
- }, async ({ since, author, repo_path: repoPath }) => {
392
- const ws = loadWorkspace();
394
+ annotations: ANNOTATIONS.READ_ONLY,
395
+ }, async ({ workspace_id: workspaceIdInput, since, author, repo_path: repoPath }) => {
396
+ const ws = await loadWorkspace(workspaceIdInput);
393
397
  if (!ws.ok) {
394
- return errorResponse(`Workspace not found: ${ws.reason}`);
398
+ return workspaceNotFoundResponse(ws.reason);
395
399
  }
396
- const { storage } = await createAdapters(ws.root);
400
+ const { storage, workspaceId } = await createAdapters(ws.workspaceId);
397
401
  try {
398
- const conditions = [];
399
- const joinConditions = [];
402
+ const conditions = ['workspace_id = $1'];
403
+ const joinConditions = ['c.workspace_id = $1'];
400
404
  const params = [];
401
- let paramIdx = 1;
402
- if (since) {
405
+ let paramIdx = 2;
406
+ if (hasText(since)) {
403
407
  conditions.push(`committed_at >= $${String(paramIdx)}`);
404
408
  joinConditions.push(`c.committed_at >= $${String(paramIdx)}`);
405
409
  params.push(since);
406
410
  paramIdx++;
407
411
  }
408
- if (author) {
412
+ if (hasText(author)) {
409
413
  conditions.push(`author ILIKE $${String(paramIdx)}`);
410
414
  joinConditions.push(`c.author ILIKE $${String(paramIdx)}`);
411
415
  params.push(`%${author}%`);
412
416
  paramIdx++;
413
417
  }
414
- if (repoPath) {
418
+ if (hasText(repoPath)) {
415
419
  conditions.push(`repo_path ILIKE $${String(paramIdx)}`);
416
420
  joinConditions.push(`c.repo_path ILIKE $${String(paramIdx)}`);
417
421
  params.push(`%${repoPath}%`);
418
422
  paramIdx++;
419
423
  }
420
- const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
421
- const joinWhere = joinConditions.length > 0 ? `WHERE ${joinConditions.join(' AND ')}` : '';
424
+ const where = `WHERE ${conditions.join(' AND ')}`;
425
+ const joinWhere = `WHERE ${joinConditions.join(' AND ')}`;
422
426
  const [total, byAuthor, hotFiles, issueRefCount] = await Promise.all([
423
- storage.query(`SELECT COUNT(*) as count FROM commits ${where}`, params),
424
- storage.query(`SELECT author, COUNT(*) as count FROM commits ${where} GROUP BY author ORDER BY count DESC LIMIT 15`, params),
425
- storage.query(`SELECT cf.file_path, COUNT(*) as changes
426
- FROM commit_files cf
427
- JOIN commits c ON cf.commit_hash = c.hash
427
+ storage.queryForWorkspace(workspaceId, `SELECT COUNT(*) as count FROM commits ${where}`, params),
428
+ storage.queryForWorkspace(workspaceId, `SELECT author, COUNT(*) as count FROM commits ${where} GROUP BY author ORDER BY count DESC LIMIT 15`, params),
429
+ storage.queryForWorkspace(workspaceId, `SELECT cf.file_path, COUNT(*) as changes
430
+ FROM commit_files cf JOIN commits c ON c.workspace_id = cf.workspace_id AND c.hash = cf.commit_hash
428
431
  ${joinWhere}
429
432
  GROUP BY cf.file_path ORDER BY changes DESC LIMIT 15`, params),
430
- storage.query(`SELECT COUNT(DISTINCT issue_key) as count FROM commit_issue_refs`, []),
433
+ storage.queryForWorkspace(workspaceId, `SELECT COUNT(DISTINCT issue_key) as count FROM commit_issue_refs WHERE workspace_id = $1`, []),
431
434
  ]);
432
- await storage.close();
433
435
  const sections = [];
434
436
  const totalRow = total.rows[0];
435
437
  const refsRow = issueRefCount.rows[0];
436
- sections.push(`# Git Statistics${since ? ` (since ${since})` : ''}${author ? ` (author: ${author})` : ''}`);
438
+ sections.push(`# Git Statistics${hasText(since) ? ` (since ${since})` : ''}${hasText(author) ? ` (author: ${author})` : ''} — workspace ${workspaceId}`);
437
439
  sections.push(`Total commits: ${str(totalRow?.count)}`);
438
440
  sections.push(`Linked issue keys: ${str(refsRow?.count)}`);
439
- sections.push('');
440
- sections.push('## Top Authors');
441
- for (const rawRow of byAuthor.rows) {
442
- const r = rawRow;
443
- sections.push(` ${str(r.author) || 'unknown'}: ${str(r.count)}`);
441
+ sections.push('\n## Top Authors');
442
+ for (const raw of byAuthor.rows) {
443
+ const r = raw;
444
+ sections.push(` ${strOr(r.author, 'unknown')}: ${str(r.count)}`);
444
445
  }
445
- sections.push('');
446
- sections.push('## Most Changed Files');
447
- for (const rawRow of hotFiles.rows) {
448
- const r = rawRow;
446
+ sections.push('\n## Most Changed Files');
447
+ for (const raw of hotFiles.rows) {
448
+ const r = raw;
449
449
  sections.push(` ${str(r.file_path)}: ${str(r.changes)} changes`);
450
450
  }
451
451
  return textResponse(sections.join('\n'));