airaknit 1.1.2-rc.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 (440) hide show
  1. package/LICENSE +84 -0
  2. package/README.md +202 -0
  3. package/bin/airaknit +9 -0
  4. package/bin/airaknit-project +14 -0
  5. package/bin/kanna +9 -0
  6. package/dist/client/assets/CompactSummaryMessage-Yw0BDWEJ.js +1 -0
  7. package/dist/client/assets/ExitPlanModeMessage-DIdkQ4uF.js +1 -0
  8. package/dist/client/assets/LocalFilePreviewDialog-DQx2eiCc.js +3 -0
  9. package/dist/client/assets/LocalProjectsSection-C4xlWkgS.js +1 -0
  10. package/dist/client/assets/TextMessage-B5G39DEJ.js +1 -0
  11. package/dist/client/assets/UserMessage-CIkWk-0L.js +1 -0
  12. package/dist/client/assets/_basePickBy-CVrAFfnZ.js +1 -0
  13. package/dist/client/assets/_baseUniq-JL-aaF4P.js +1 -0
  14. package/dist/client/assets/arc-B07zg7ol.js +1 -0
  15. package/dist/client/assets/architecture-YZFGNWBL-PSLVJL3p.js +1 -0
  16. package/dist/client/assets/architectureDiagram-Q4EWVU46-DfWIF1G_.js +36 -0
  17. package/dist/client/assets/array-BGFCBI0e.js +1 -0
  18. package/dist/client/assets/blockDiagram-DXYQGD6D-CzwIeo_B.js +132 -0
  19. package/dist/client/assets/bundle-mjs-BDE2gWbQ.js +1 -0
  20. package/dist/client/assets/button-DO50qOGv.js +1 -0
  21. package/dist/client/assets/c4Diagram-AHTNJAMY-CR8DCQRE.js +10 -0
  22. package/dist/client/assets/channel-Dj-UUfaF.js +1 -0
  23. package/dist/client/assets/chunk-2KRD3SAO-dznP-cn8.js +1 -0
  24. package/dist/client/assets/chunk-336JU56O-Dqss5Vu6.js +2 -0
  25. package/dist/client/assets/chunk-426QAEUC-CEKis_0_.js +1 -0
  26. package/dist/client/assets/chunk-4BX2VUAB-BYOv0Gm1.js +1 -0
  27. package/dist/client/assets/chunk-4TB4RGXK-BxzubH5S.js +206 -0
  28. package/dist/client/assets/chunk-55IACEB6-BSlTj03a.js +1 -0
  29. package/dist/client/assets/chunk-5FUZZQ4R-9Au93Bi1.js +62 -0
  30. package/dist/client/assets/chunk-5PVQY5BW-BhksHFEZ.js +2 -0
  31. package/dist/client/assets/chunk-67CJDMHE-BFwhz-8t.js +1 -0
  32. package/dist/client/assets/chunk-7N4EOEYR-BDUPds87.js +1 -0
  33. package/dist/client/assets/chunk-AA7GKIK3-CEWTdyXO.js +1 -0
  34. package/dist/client/assets/chunk-BO2N2NFS-D0LvxnhU.js +103 -0
  35. package/dist/client/assets/chunk-BSJP7CBP-BNJnK6sq.js +1 -0
  36. package/dist/client/assets/chunk-Bj-mKKzh.js +1 -0
  37. package/dist/client/assets/chunk-CIAEETIT-CYhfoCeN.js +1 -0
  38. package/dist/client/assets/chunk-EDXVE4YY-C5ovJLc0.js +1 -0
  39. package/dist/client/assets/chunk-ENJZ2VHE-HOhYaeGr.js +10 -0
  40. package/dist/client/assets/chunk-FMBD7UC4-BLCiKcAQ.js +15 -0
  41. package/dist/client/assets/chunk-FOC6F5B3-B6GtY2ek.js +1 -0
  42. package/dist/client/assets/chunk-ICPOFSXX-DPoIZoC5.js +122 -0
  43. package/dist/client/assets/chunk-K5T4RW27-BsKN63rv.js +94 -0
  44. package/dist/client/assets/chunk-KGLVRYIC-BUGn9uuY.js +1 -0
  45. package/dist/client/assets/chunk-LIHQZDEY-DhaZyo03.js +1 -0
  46. package/dist/client/assets/chunk-ORNJ4GCN-DlpeeJyi.js +1 -0
  47. package/dist/client/assets/chunk-OYMX7WX6-Dc9q7aYA.js +231 -0
  48. package/dist/client/assets/chunk-QZHKN3VN-BEdrPoSb.js +1 -0
  49. package/dist/client/assets/chunk-U2HBQHQK-CIB3Bjjd.js +70 -0
  50. package/dist/client/assets/chunk-X2U36JSP-CtB-o8Yp.js +1 -0
  51. package/dist/client/assets/chunk-XPW4576I-C6iHhX_8.js +32 -0
  52. package/dist/client/assets/chunk-YZCP3GAM-CTmKr6ZH.js +1 -0
  53. package/dist/client/assets/chunk-ZZ45TVLE-BgU8A2RF.js +1 -0
  54. package/dist/client/assets/classDiagram-6PBFFD2Q-Bqk5e679.js +1 -0
  55. package/dist/client/assets/classDiagram-v2-HSJHXN6E-6pSaZOkC.js +1 -0
  56. package/dist/client/assets/client-BrKWI4CM.js +1 -0
  57. package/dist/client/assets/client-CGgNRU9w.js +1 -0
  58. package/dist/client/assets/client-DMSLRzg9.js +6 -0
  59. package/dist/client/assets/clone-DWcL7whJ.js +1 -0
  60. package/dist/client/assets/cose-bilkent-S5V4N54A-CrV5wsV_.js +1 -0
  61. package/dist/client/assets/cytoscape.esm--aLzKuep.js +321 -0
  62. package/dist/client/assets/dagre-CuRxWcrj.js +1 -0
  63. package/dist/client/assets/dagre-KV5264BT-BIDiVnkA.js +4 -0
  64. package/dist/client/assets/defaultLocale-CRZydyG6.js +1 -0
  65. package/dist/client/assets/diagram-5BDNPKRD-i1kjKRCB.js +10 -0
  66. package/dist/client/assets/diagram-G4DWMVQ6-9ZSLuhbl.js +24 -0
  67. package/dist/client/assets/diagram-MMDJMWI5-B4_CUjgv.js +43 -0
  68. package/dist/client/assets/diagram-TYMM5635-Ct5eTGS8.js +24 -0
  69. package/dist/client/assets/dist-CuB4kiSK.js +1 -0
  70. package/dist/client/assets/erDiagram-SMLLAGMA-Cy38ercc.js +85 -0
  71. package/dist/client/assets/flowDiagram-DWJPFMVM-CZKuYl0V.js +162 -0
  72. package/dist/client/assets/ganttDiagram-T4ZO3ILL-DLPjCh7a.js +292 -0
  73. package/dist/client/assets/gitGraph-7Q5UKJZL-DqbrtEp9.js +1 -0
  74. package/dist/client/assets/gitGraphDiagram-UUTBAWPF-BoRBkDhQ.js +106 -0
  75. package/dist/client/assets/graphlib-BcQ6qlQh.js +1 -0
  76. package/dist/client/assets/highlighted-body-OFNGDK62-BEpBVDTX.js +1 -0
  77. package/dist/client/assets/index-CetCiuqP.js +105 -0
  78. package/dist/client/assets/index-N29Mip7A.css +1 -0
  79. package/dist/client/assets/info-OMHHGYJF-D98DRBJX.js +1 -0
  80. package/dist/client/assets/infoDiagram-42DDH7IO-BAcdTWbt.js +2 -0
  81. package/dist/client/assets/init-B8gtcn7T.js +1 -0
  82. package/dist/client/assets/isArrayLikeObject-D8SJFmkN.js +1 -0
  83. package/dist/client/assets/isEmpty-BF3YX5Jk.js +1 -0
  84. package/dist/client/assets/ishikawaDiagram-UXIWVN3A-Ynu2VKdC.js +70 -0
  85. package/dist/client/assets/journeyDiagram-VCZTEJTY-BjfhQaN3.js +139 -0
  86. package/dist/client/assets/jsx-runtime-CyI9ICYU.js +1 -0
  87. package/dist/client/assets/kanban-definition-6JOO6SKY-JLXH9zUJ.js +89 -0
  88. package/dist/client/assets/katex-B94qP8b6.js +265 -0
  89. package/dist/client/assets/lib--QVjyxmL.js +29 -0
  90. package/dist/client/assets/lib-B6rgJiZ9.js +1 -0
  91. package/dist/client/assets/line-DCrYfLBn.js +1 -0
  92. package/dist/client/assets/linear-_4upLmeo.js +1 -0
  93. package/dist/client/assets/mermaid-GHXKKRXX-rwJHYUmW.js +1 -0
  94. package/dist/client/assets/mermaid-parser.core-KZinfW8o.js +4 -0
  95. package/dist/client/assets/mermaid.core-QqY9gSNe.js +11 -0
  96. package/dist/client/assets/mindmap-definition-QFDTVHPH-TWgHDAzp.js +96 -0
  97. package/dist/client/assets/ordinal-CCj7PWgZ.js +1 -0
  98. package/dist/client/assets/packet-4T2RLAQJ-DEvfkn3F.js +1 -0
  99. package/dist/client/assets/path-DZF-JdEe.js +1 -0
  100. package/dist/client/assets/pie-ZZUOXDRM-72e6WVjb.js +1 -0
  101. package/dist/client/assets/pieDiagram-DEJITSTG-Cl8PCsoj.js +30 -0
  102. package/dist/client/assets/preload-helper-rov5CBGT.js +1 -0
  103. package/dist/client/assets/pty-client-DZ27IS00.js +1 -0
  104. package/dist/client/assets/ptyInstancesStore-D9ag7SYd.js +1 -0
  105. package/dist/client/assets/quadrantDiagram-34T5L4WZ-CHyVGp9E.js +7 -0
  106. package/dist/client/assets/radar-PYXPWWZC-Cp7xd_EY.js +1 -0
  107. package/dist/client/assets/react-CClhXMB2.js +1 -0
  108. package/dist/client/assets/react-Dd6D81m0.js +1 -0
  109. package/dist/client/assets/react-dom--G6_6fQ_.js +1 -0
  110. package/dist/client/assets/requirementDiagram-MS252O5E-DaanG2iM.js +84 -0
  111. package/dist/client/assets/rough.esm-BsmKo2S5.js +1 -0
  112. package/dist/client/assets/sankeyDiagram-XADWPNL6-B_fhLY36.js +10 -0
  113. package/dist/client/assets/sequenceDiagram-FGHM5R23-C5FNrveI.js +157 -0
  114. package/dist/client/assets/src-DeTlMJAU.js +1 -0
  115. package/dist/client/assets/stateDiagram-FHFEXIEX-nTTcdjjQ.js +1 -0
  116. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-Dw0632j_.js +1 -0
  117. package/dist/client/assets/timeline-definition-GMOUNBTQ-DkQV1yP8.js +120 -0
  118. package/dist/client/assets/treeView-SZITEDCU-ZLIgC7_K.js +1 -0
  119. package/dist/client/assets/treemap-W4RFUUIX-BqaMbB6N.js +1 -0
  120. package/dist/client/assets/uiIdentityOverlay-Ba7GNj7m.js +1 -0
  121. package/dist/client/assets/vennDiagram-DHZGUBPP-DbZ2xgs6.js +34 -0
  122. package/dist/client/assets/wardley-RL74JXVD-DXQS8zf4.js +1 -0
  123. package/dist/client/assets/wardleyDiagram-NUSXRM2D-BzCJ6MAu.js +20 -0
  124. package/dist/client/assets/xychartDiagram-5P7HB3ND-BSlFecop.js +7 -0
  125. package/dist/client/favicon.png +0 -0
  126. package/dist/client/favicon.svg +15 -0
  127. package/dist/client/fonts/body-medium.woff2 +0 -0
  128. package/dist/client/fonts/body-regular-italic.woff2 +0 -0
  129. package/dist/client/fonts/body-regular.woff2 +0 -0
  130. package/dist/client/fonts/body-semibold.woff2 +0 -0
  131. package/dist/client/index.html +31 -0
  132. package/dist/client/manifest.webmanifest +12 -0
  133. package/dist/client/sw.js +32 -0
  134. package/package.json +122 -0
  135. package/src/nats/auth-callout/callout-config.test.ts +93 -0
  136. package/src/nats/auth-callout/callout-config.ts +109 -0
  137. package/src/nats/auth-callout/callout.integration.test.ts +332 -0
  138. package/src/nats/auth-callout/keys.ts +103 -0
  139. package/src/nats/auth-callout/responder.ts +241 -0
  140. package/src/nats/auth-callout/scope-policy.test.ts +159 -0
  141. package/src/nats/auth-callout/scope-policy.ts +210 -0
  142. package/src/nats/auth-callout/token.test.ts +163 -0
  143. package/src/nats/auth-callout/token.ts +157 -0
  144. package/src/nats/nats-daemon-callout.ts +194 -0
  145. package/src/nats/nats-daemon.test.ts +77 -0
  146. package/src/nats/nats-daemon.ts +50 -0
  147. package/src/nats/nats-token.test.ts +61 -0
  148. package/src/nats/nats-token.ts +59 -0
  149. package/src/runner/coordination-mcp-integration.test.ts +134 -0
  150. package/src/runner/nats-coordination-client.test.ts +49 -0
  151. package/src/runner/nats-coordination-client.ts +94 -0
  152. package/src/runner/runner-agent.test.ts +469 -0
  153. package/src/runner/runner-agent.ts +453 -0
  154. package/src/runner/runner-credential.test.ts +93 -0
  155. package/src/runner/runner-credential.ts +82 -0
  156. package/src/runner/runner-nats.test.ts +495 -0
  157. package/src/runner/runner-nats.ts +323 -0
  158. package/src/runner/runner-pair.test.ts +107 -0
  159. package/src/runner/runner-pair.ts +81 -0
  160. package/src/runner/runner.test.ts +135 -0
  161. package/src/runner/runner.ts +212 -0
  162. package/src/runner/turn-factories.test.ts +97 -0
  163. package/src/runner/turn-factories.ts +475 -0
  164. package/src/server/agent-config-journey.test.ts +106 -0
  165. package/src/server/agent.ts +8 -0
  166. package/src/server/auto-continue/auth-error-detector.ts +66 -0
  167. package/src/server/auto-continue/limit-detector.ts +194 -0
  168. package/src/server/bm25.test.ts +92 -0
  169. package/src/server/bm25.ts +101 -0
  170. package/src/server/chat-events-jetstream.test.ts +135 -0
  171. package/src/server/claude-harness.ts +360 -0
  172. package/src/server/claude-pty/agent-normalizers.ts +309 -0
  173. package/src/server/claude-pty/auth.test.ts +38 -0
  174. package/src/server/claude-pty/auth.ts +32 -0
  175. package/src/server/claude-pty/claude-session-registry.adapter.ts +81 -0
  176. package/src/server/claude-pty/claude-session-registry.test.ts +149 -0
  177. package/src/server/claude-pty/driver.test.ts +902 -0
  178. package/src/server/claude-pty/driver.ts +807 -0
  179. package/src/server/claude-pty/jsonl-path.adapter.ts +57 -0
  180. package/src/server/claude-pty/jsonl-path.test.ts +114 -0
  181. package/src/server/claude-pty/jsonl-to-event.test.ts +241 -0
  182. package/src/server/claude-pty/jsonl-to-event.ts +174 -0
  183. package/src/server/claude-pty/output-ring.test.ts +35 -0
  184. package/src/server/claude-pty/output-ring.ts +25 -0
  185. package/src/server/claude-pty/parity-matrix.test.ts +227 -0
  186. package/src/server/claude-pty/pid-registry.adapter.ts +135 -0
  187. package/src/server/claude-pty/pid-registry.test.ts +122 -0
  188. package/src/server/claude-pty/preflight/binary-fingerprint.adapter.ts +20 -0
  189. package/src/server/claude-pty/preflight/binary-fingerprint.test.ts +32 -0
  190. package/src/server/claude-pty/pty-instance-registry.test.ts +177 -0
  191. package/src/server/claude-pty/pty-instance-registry.ts +166 -0
  192. package/src/server/claude-pty/pty-memory-sampler.adapter.test.ts +103 -0
  193. package/src/server/claude-pty/pty-memory-sampler.adapter.ts +85 -0
  194. package/src/server/claude-pty/pty-process.adapter.ts +66 -0
  195. package/src/server/claude-pty/pty-process.test.ts +49 -0
  196. package/src/server/claude-pty/resolve-binary.adapter.ts +106 -0
  197. package/src/server/claude-pty/resolve-binary.test.ts +118 -0
  198. package/src/server/claude-pty/runtime-dir.adapter.ts +19 -0
  199. package/src/server/claude-pty/settings-writer.adapter.ts +27 -0
  200. package/src/server/claude-pty/settings-writer.test.ts +22 -0
  201. package/src/server/claude-pty/smoke-test-io.adapter.ts +28 -0
  202. package/src/server/claude-pty/smoke-test.test.ts +191 -0
  203. package/src/server/claude-pty/smoke-test.ts +185 -0
  204. package/src/server/claude-pty/subagent-orchestrator.ts +887 -0
  205. package/src/server/claude-pty/tool-callback.ts +274 -0
  206. package/src/server/claude-pty/tui-control.test.ts +272 -0
  207. package/src/server/claude-pty/tui-control.ts +182 -0
  208. package/src/server/claude-pty/tui-source.adapter.test.ts +360 -0
  209. package/src/server/claude-pty/tui-source.adapter.ts +343 -0
  210. package/src/server/claude-pty/tunnel-gateway.ts +12 -0
  211. package/src/server/claude-pty-mcp/canonical-args.ts +15 -0
  212. package/src/server/claude-pty-mcp/fs-stat.adapter.ts +8 -0
  213. package/src/server/claude-pty-mcp/history-primer.ts +90 -0
  214. package/src/server/claude-pty-mcp/http-server.adapter.ts +33 -0
  215. package/src/server/claude-pty-mcp/mcp-http.ts +177 -0
  216. package/src/server/claude-pty-mcp/mcp.ts +412 -0
  217. package/src/server/claude-pty-mcp/mention-parser.ts +25 -0
  218. package/src/server/claude-pty-mcp/paths.ts +24 -0
  219. package/src/server/claude-pty-mcp/permission-gate.ts +243 -0
  220. package/src/server/claude-pty-mcp/terminal-pid-registry.adapter.ts +107 -0
  221. package/src/server/claude-pty-mcp/tools/ask-user-question.test.ts +119 -0
  222. package/src/server/claude-pty-mcp/tools/ask-user-question.ts +61 -0
  223. package/src/server/claude-pty-mcp/tools/bash.adapter.ts +76 -0
  224. package/src/server/claude-pty-mcp/tools/bash.test.ts +56 -0
  225. package/src/server/claude-pty-mcp/tools/delegate-subagent.test.ts +155 -0
  226. package/src/server/claude-pty-mcp/tools/delegate-subagent.ts +111 -0
  227. package/src/server/claude-pty-mcp/tools/edit.adapter.ts +95 -0
  228. package/src/server/claude-pty-mcp/tools/edit.test.ts +93 -0
  229. package/src/server/claude-pty-mcp/tools/exit-plan-mode.test.ts +61 -0
  230. package/src/server/claude-pty-mcp/tools/exit-plan-mode.ts +50 -0
  231. package/src/server/claude-pty-mcp/tools/glob.adapter.ts +86 -0
  232. package/src/server/claude-pty-mcp/tools/glob.test.ts +61 -0
  233. package/src/server/claude-pty-mcp/tools/grep.adapter.ts +126 -0
  234. package/src/server/claude-pty-mcp/tools/grep.test.ts +62 -0
  235. package/src/server/claude-pty-mcp/tools/read.adapter.ts +58 -0
  236. package/src/server/claude-pty-mcp/tools/read.test.ts +62 -0
  237. package/src/server/claude-pty-mcp/tools/tool-callback-shim.ts +42 -0
  238. package/src/server/claude-pty-mcp/tools/webfetch.test.ts +81 -0
  239. package/src/server/claude-pty-mcp/tools/webfetch.ts +82 -0
  240. package/src/server/claude-pty-mcp/tools/websearch.test.ts +40 -0
  241. package/src/server/claude-pty-mcp/tools/websearch.ts +42 -0
  242. package/src/server/claude-pty-mcp/tools/write.adapter.ts +60 -0
  243. package/src/server/claude-pty-mcp/tools/write.test.ts +52 -0
  244. package/src/server/claude-pty-mcp/uploads.adapter.ts +98 -0
  245. package/src/server/claude-pty-mcp/uploads.ts +38 -0
  246. package/src/server/claude-turn.test.ts +176 -0
  247. package/src/server/cli-runtime.test.ts +456 -0
  248. package/src/server/cli-runtime.ts +374 -0
  249. package/src/server/cli-supervisor.ts +81 -0
  250. package/src/server/cli.ts +78 -0
  251. package/src/server/client-log-forwarder.test.ts +74 -0
  252. package/src/server/client-log-forwarder.ts +75 -0
  253. package/src/server/codex-app-server-protocol.ts +449 -0
  254. package/src/server/codex-app-server.test.ts +2990 -0
  255. package/src/server/codex-app-server.ts +1713 -0
  256. package/src/server/coordination-integration.test.ts +63 -0
  257. package/src/server/coordination-mcp.test.ts +149 -0
  258. package/src/server/coordination-mcp.ts +197 -0
  259. package/src/server/delegation-coordinator.test.ts +675 -0
  260. package/src/server/delegation-coordinator.ts +454 -0
  261. package/src/server/discovery.test.ts +211 -0
  262. package/src/server/discovery.ts +301 -0
  263. package/src/server/event-store-agent-config.test.ts +124 -0
  264. package/src/server/event-store-coordination.test.ts +149 -0
  265. package/src/server/event-store-profile.test.ts +132 -0
  266. package/src/server/event-store-repo.test.ts +154 -0
  267. package/src/server/event-store-runner-team.test.ts +104 -0
  268. package/src/server/event-store.test.ts +342 -0
  269. package/src/server/event-store.ts +2208 -0
  270. package/src/server/events.ts +379 -0
  271. package/src/server/extension-router.test.ts +183 -0
  272. package/src/server/extension-router.ts +114 -0
  273. package/src/server/extensions/agents/server.test.ts +191 -0
  274. package/src/server/extensions/agents/server.ts +108 -0
  275. package/src/server/extensions/c3/server.test.ts +284 -0
  276. package/src/server/extensions/c3/server.ts +212 -0
  277. package/src/server/extensions/code/server.test.ts +200 -0
  278. package/src/server/extensions/code/server.ts +150 -0
  279. package/src/server/extensions.config.ts +10 -0
  280. package/src/server/external-open.ts +69 -0
  281. package/src/server/generate-fork-context.ts +58 -0
  282. package/src/server/generate-merge-context.test.ts +290 -0
  283. package/src/server/generate-merge-context.ts +141 -0
  284. package/src/server/generate-title.ts +36 -0
  285. package/src/server/git-clone-policy.test.ts +138 -0
  286. package/src/server/git-clone-policy.ts +27 -0
  287. package/src/server/harness-types.ts +1 -0
  288. package/src/server/journey-verification.test.ts +640 -0
  289. package/src/server/journey-verification.ts +195 -0
  290. package/src/server/machine-name.ts +22 -0
  291. package/src/server/nats-auth.test.ts +92 -0
  292. package/src/server/nats-auth.ts +6 -0
  293. package/src/server/nats-bind-guard.test.ts +71 -0
  294. package/src/server/nats-bind-guard.ts +42 -0
  295. package/src/server/nats-bridge.test.ts +141 -0
  296. package/src/server/nats-bridge.ts +111 -0
  297. package/src/server/nats-connector.test.ts +56 -0
  298. package/src/server/nats-connector.ts +71 -0
  299. package/src/server/nats-daemon-manager.test.ts +76 -0
  300. package/src/server/nats-daemon-manager.ts +174 -0
  301. package/src/server/nats-publisher.test.ts +356 -0
  302. package/src/server/nats-publisher.ts +271 -0
  303. package/src/server/nats-responders.test.ts +1018 -0
  304. package/src/server/nats-responders.ts +925 -0
  305. package/src/server/nats-streams.test.ts +112 -0
  306. package/src/server/nats-streams.ts +129 -0
  307. package/src/server/oauth-pool/oauth-responders.ts +185 -0
  308. package/src/server/oauth-pool/oauth-settings-store.ts +173 -0
  309. package/src/server/oauth-pool/oauth-token-pool.ts +303 -0
  310. package/src/server/orchestration.test.ts +1063 -0
  311. package/src/server/orchestration.ts +716 -0
  312. package/src/server/pairing-endpoints.test.ts +171 -0
  313. package/src/server/pairing-store.test.ts +154 -0
  314. package/src/server/pairing-store.ts +124 -0
  315. package/src/server/paths.ts +27 -0
  316. package/src/server/pr3-liveness.test.ts +252 -0
  317. package/src/server/process-utils.ts +10 -0
  318. package/src/server/project-cli.ts +180 -0
  319. package/src/server/provider-catalog.test.ts +177 -0
  320. package/src/server/provider-catalog.ts +146 -0
  321. package/src/server/pty-responders.ts +345 -0
  322. package/src/server/push-notifications.test.ts +234 -0
  323. package/src/server/push-notifications.ts +188 -0
  324. package/src/server/quick-response.test.ts +196 -0
  325. package/src/server/quick-response.ts +154 -0
  326. package/src/server/read-models-agent-config.test.ts +59 -0
  327. package/src/server/read-models-coordination.test.ts +69 -0
  328. package/src/server/read-models.test.ts +332 -0
  329. package/src/server/read-models.ts +258 -0
  330. package/src/server/repo-journey.test.ts +96 -0
  331. package/src/server/repo-manager.test.ts +118 -0
  332. package/src/server/repo-manager.ts +97 -0
  333. package/src/server/repo-status.test.ts +54 -0
  334. package/src/server/repo-status.ts +82 -0
  335. package/src/server/restart.test.ts +27 -0
  336. package/src/server/restart.ts +30 -0
  337. package/src/server/runner-incompatible-gate.test.ts +383 -0
  338. package/src/server/runner-manager.test.ts +72 -0
  339. package/src/server/runner-manager.ts +312 -0
  340. package/src/server/runner-pairing-urls.test.ts +59 -0
  341. package/src/server/runner-pairing-urls.ts +67 -0
  342. package/src/server/runner-proxy.test.ts +456 -0
  343. package/src/server/runner-proxy.ts +494 -0
  344. package/src/server/runner-router.test.ts +429 -0
  345. package/src/server/runner-router.ts +212 -0
  346. package/src/server/runner-routing.test.ts +584 -0
  347. package/src/server/runtime-registry.test.ts +436 -0
  348. package/src/server/runtime-registry.ts +307 -0
  349. package/src/server/sandbox-health.test.ts +127 -0
  350. package/src/server/sandbox-health.ts +94 -0
  351. package/src/server/sandbox-journey.test.ts +232 -0
  352. package/src/server/sandbox-manager.test.ts +146 -0
  353. package/src/server/sandbox-manager.ts +159 -0
  354. package/src/server/server.test.ts +287 -0
  355. package/src/server/server.ts +1108 -0
  356. package/src/server/session-discovery.test.ts +129 -0
  357. package/src/server/session-discovery.ts +475 -0
  358. package/src/server/session-index.test.ts +362 -0
  359. package/src/server/session-index.ts +119 -0
  360. package/src/server/session-seed.ts +288 -0
  361. package/src/server/share.test.ts +108 -0
  362. package/src/server/share.ts +113 -0
  363. package/src/server/skill-discovery.test.ts +201 -0
  364. package/src/server/skill-discovery.ts +77 -0
  365. package/src/server/storage/test-helpers.ts +67 -0
  366. package/src/server/terminal-manager.test.ts +309 -0
  367. package/src/server/terminal-manager.ts +354 -0
  368. package/src/server/transcript-consumer.test.ts +339 -0
  369. package/src/server/transcript-consumer.ts +162 -0
  370. package/src/server/transcript-search.test.ts +193 -0
  371. package/src/server/transcript-search.ts +83 -0
  372. package/src/server/transcript-utils.ts +52 -0
  373. package/src/server/update-manager.test.ts +107 -0
  374. package/src/server/update-manager.ts +230 -0
  375. package/src/server/workflow-engine.test.ts +251 -0
  376. package/src/server/workflow-engine.ts +169 -0
  377. package/src/server/workflow-mcp.ts +49 -0
  378. package/src/server/workflow-store.test.ts +155 -0
  379. package/src/server/workflow-store.ts +139 -0
  380. package/src/server/workspace-agent-integration.test.ts +167 -0
  381. package/src/server/workspace-agent-routes.test.ts +127 -0
  382. package/src/server/workspace-agent-routes.ts +89 -0
  383. package/src/server/workspace-agent.test.ts +103 -0
  384. package/src/server/workspace-agent.ts +102 -0
  385. package/src/server/workspace-cli.test.ts +79 -0
  386. package/src/server/workspace-config-manager.test.ts +109 -0
  387. package/src/server/workspace-config-manager.ts +83 -0
  388. package/src/server/workspace-directory-policy.test.ts +109 -0
  389. package/src/server/workspace-directory-policy.ts +56 -0
  390. package/src/shared/agent-config-types.ts +25 -0
  391. package/src/shared/branding.test.ts +42 -0
  392. package/src/shared/branding.ts +54 -0
  393. package/src/shared/compression.test.ts +85 -0
  394. package/src/shared/compression.ts +42 -0
  395. package/src/shared/coordination-store.test.ts +24 -0
  396. package/src/shared/coordination-store.ts +26 -0
  397. package/src/shared/dev-ports.test.ts +84 -0
  398. package/src/shared/dev-ports.ts +100 -0
  399. package/src/shared/extension-types.ts +45 -0
  400. package/src/shared/fork-presets.ts +54 -0
  401. package/src/shared/harness-types.test.ts +15 -0
  402. package/src/shared/harness-types.ts +21 -0
  403. package/src/shared/log-sink.test.ts +112 -0
  404. package/src/shared/log-sink.ts +185 -0
  405. package/src/shared/mention-pattern.ts +7 -0
  406. package/src/shared/merge-presets.ts +41 -0
  407. package/src/shared/nats-subjects.test.ts +61 -0
  408. package/src/shared/nats-subjects.ts +131 -0
  409. package/src/shared/permission-policy.ts +136 -0
  410. package/src/shared/ports.ts +3 -0
  411. package/src/shared/preset-types.ts +15 -0
  412. package/src/shared/profile-types.ts +49 -0
  413. package/src/shared/projectFileUrl.ts +36 -0
  414. package/src/shared/protocol.ts +153 -0
  415. package/src/shared/pty-instance.ts +43 -0
  416. package/src/shared/puggy/diagnostics/result.ts +18 -0
  417. package/src/shared/puggy/expressions/evaluate.ts +292 -0
  418. package/src/shared/puggy/index.test.ts +101 -0
  419. package/src/shared/puggy/index.ts +69 -0
  420. package/src/shared/puggy/parser/ast.ts +110 -0
  421. package/src/shared/puggy/parser/parser.ts +624 -0
  422. package/src/shared/puggy/renderer/html.ts +447 -0
  423. package/src/shared/runner-protocol.test.ts +277 -0
  424. package/src/shared/runner-protocol.ts +210 -0
  425. package/src/shared/runner-team-types.ts +73 -0
  426. package/src/shared/runtime-types.ts +48 -0
  427. package/src/shared/sandbox-types.ts +53 -0
  428. package/src/shared/tailwind-build.test.ts +12 -0
  429. package/src/shared/tinkaria-system-prompt.ts +101 -0
  430. package/src/shared/tools.test.ts +335 -0
  431. package/src/shared/tools.ts +397 -0
  432. package/src/shared/transcript-entries.ts +27 -0
  433. package/src/shared/transcript-render.test.ts +225 -0
  434. package/src/shared/transcript-render.ts +467 -0
  435. package/src/shared/types.ts +1031 -0
  436. package/src/shared/vite-config.test.ts +47 -0
  437. package/src/shared/web-context.test.ts +110 -0
  438. package/src/shared/web-context.ts +76 -0
  439. package/src/shared/workflow-types.ts +51 -0
  440. package/src/shared/workspace-types.ts +100 -0
@@ -0,0 +1,258 @@
1
+ import type {
2
+ ChatRuntime,
3
+ ChatSnapshot,
4
+ SessionStatus,
5
+ LocalWorkspacesSnapshot,
6
+ ProviderCatalogEntry,
7
+ RepoSummary,
8
+ SidebarChatRow,
9
+ SidebarData,
10
+ SidebarWorkspaceGroup,
11
+ TranscriptEntry,
12
+ TranscriptRenderUnit,
13
+ } from "../shared/types"
14
+ import { compareIndependentWorkspaces } from "../shared/types"
15
+ import type { ChatRecord, StoreState, WorkspaceCoordinationState } from "./events"
16
+ import { createEmptyCoordinationState } from "./events"
17
+ import type { WorkspaceCoordinationSnapshot } from "../shared/workspace-types"
18
+ import type { AgentConfigSnapshot } from "../shared/agent-config-types"
19
+ import type { WorkflowRunsSnapshot } from "../shared/workflow-types"
20
+ import { resolveLocalPath } from "./paths"
21
+ import { SERVER_PROVIDERS } from "./provider-catalog"
22
+ import { foldTranscriptRenderUnits } from "../shared/transcript-render"
23
+
24
+ export const TRANSCRIPT_RENDER_WINDOW_SIZE = 200
25
+
26
+ export function isTranscriptRenderLoadingStatus(status: SessionStatus | undefined): boolean {
27
+ return status === "starting" || status === "running" || status === "waiting_for_user" || status === "awaiting_agents"
28
+ }
29
+
30
+ export function deriveTranscriptRenderUnits(
31
+ entries: TranscriptEntry[],
32
+ status?: SessionStatus,
33
+ ): TranscriptRenderUnit[] {
34
+ return foldTranscriptRenderUnits(entries, { isLoading: isTranscriptRenderLoadingStatus(status) })
35
+ }
36
+
37
+ export function deriveStatus(
38
+ chat: ChatRecord,
39
+ activeStatus?: SessionStatus,
40
+ hasActiveBlockingDelegations?: (chatId: string) => boolean,
41
+ ): SessionStatus {
42
+ if (activeStatus) return activeStatus
43
+ if (chat.lastTurnOutcome === "failed") return "failed"
44
+ if (hasActiveBlockingDelegations?.(chat.id)) return "awaiting_agents"
45
+ return "idle"
46
+ }
47
+
48
+ export function deriveSidebarData(
49
+ state: StoreState,
50
+ activeStatuses: Map<string, SessionStatus>,
51
+ hasActiveBlockingDelegations?: (chatId: string) => boolean,
52
+ ): SidebarData {
53
+ const projects = [...state.workspacesById.values()]
54
+ .filter((project) => !project.deletedAt)
55
+ .sort((a, b) => b.updatedAt - a.updatedAt)
56
+
57
+ const workspaceGroups: SidebarWorkspaceGroup[] = projects.map((project) => {
58
+ const chats: SidebarChatRow[] = [...state.chatsById.values()]
59
+ .filter((chat) => chat.workspaceId === project.id && !chat.deletedAt)
60
+ .sort((a, b) => (b.lastMessageAt ?? b.updatedAt) - (a.lastMessageAt ?? a.updatedAt))
61
+ .map((chat) => ({
62
+ _id: chat.id,
63
+ _creationTime: chat.createdAt,
64
+ chatId: chat.id,
65
+ title: chat.title,
66
+ status: deriveStatus(chat, activeStatuses.get(chat.id), hasActiveBlockingDelegations),
67
+ unread: chat.unread,
68
+ localPath: project.localPath,
69
+ provider: chat.provider,
70
+ model: chat.model ?? null,
71
+ lastMessageAt: chat.lastMessageAt,
72
+ hasAutomation: false,
73
+ }))
74
+
75
+ return {
76
+ groupKey: project.id,
77
+ localPath: project.localPath,
78
+ chats,
79
+ }
80
+ })
81
+
82
+ const independentWorkspaces = [...state.independentWorkspacesById.values()]
83
+ .sort(compareIndependentWorkspaces)
84
+
85
+ return { workspaceGroups, independentWorkspaces }
86
+ }
87
+
88
+ export function deriveLocalWorkspacesSnapshot(
89
+ state: StoreState,
90
+ discoveredProjects: Array<{ localPath: string; title: string; modifiedAt: number }>,
91
+ machineName: string
92
+ ): LocalWorkspacesSnapshot {
93
+ const workspaces = new Map<string, LocalWorkspacesSnapshot["workspaces"][number]>()
94
+
95
+ for (const project of discoveredProjects) {
96
+ const normalizedPath = resolveLocalPath(project.localPath)
97
+ workspaces.set(normalizedPath, {
98
+ localPath: normalizedPath,
99
+ title: project.title,
100
+ source: "discovered",
101
+ lastOpenedAt: project.modifiedAt,
102
+ chatCount: 0,
103
+ })
104
+ }
105
+
106
+ for (const project of [...state.workspacesById.values()].filter((entry) => !entry.deletedAt)) {
107
+ const chats = [...state.chatsById.values()].filter((chat) => chat.workspaceId === project.id && !chat.deletedAt)
108
+ const lastOpenedAt = chats.reduce(
109
+ (latest, chat) => Math.max(latest, chat.lastMessageAt ?? chat.updatedAt ?? 0),
110
+ project.updatedAt
111
+ )
112
+
113
+ workspaces.set(project.localPath, {
114
+ localPath: project.localPath,
115
+ title: project.title,
116
+ source: "saved",
117
+ lastOpenedAt,
118
+ chatCount: chats.length,
119
+ })
120
+ }
121
+
122
+ return {
123
+ machine: {
124
+ id: "local",
125
+ displayName: machineName,
126
+ },
127
+ workspaces: [...workspaces.values()].sort((a, b) => (b.lastOpenedAt ?? 0) - (a.lastOpenedAt ?? 0)),
128
+ }
129
+ }
130
+
131
+ export function deriveChatSnapshot(
132
+ state: StoreState,
133
+ activeStatuses: Map<string, SessionStatus>,
134
+ chatId: string,
135
+ messageCount: number,
136
+ availableSkills?: string[],
137
+ hasActiveBlockingDelegations?: (chatId: string) => boolean,
138
+ renderUnits: TranscriptRenderUnit[] = [],
139
+ availableProviders: ProviderCatalogEntry[] = [...SERVER_PROVIDERS],
140
+ ): ChatSnapshot | null {
141
+ const chat = state.chatsById.get(chatId)
142
+ if (!chat || chat.deletedAt) return null
143
+ const project = state.workspacesById.get(chat.workspaceId)
144
+ if (!project || project.deletedAt) return null
145
+
146
+ const runtime: ChatRuntime = {
147
+ chatId: chat.id,
148
+ workspaceId: project.id,
149
+ localPath: project.localPath,
150
+ title: chat.title,
151
+ status: deriveStatus(chat, activeStatuses.get(chat.id), hasActiveBlockingDelegations),
152
+ provider: chat.provider,
153
+ model: chat.model ?? null,
154
+ planMode: chat.planMode,
155
+ sessionToken: chat.sessionToken,
156
+ }
157
+ const queuedTurn = state.queuedTurnsByChat.get(chatId) ?? null
158
+
159
+ return {
160
+ runtime,
161
+ messageCount,
162
+ renderUnits,
163
+ availableProviders,
164
+ availableSkills: availableSkills ?? [],
165
+ queuedTurn: queuedTurn ? { ...queuedTurn } : null,
166
+ }
167
+ }
168
+
169
+ /** Derive a coordination snapshot from any state with coordinationByWorkspace. */
170
+ export function deriveCoordinationSnapshot(
171
+ state: { coordinationByWorkspace: Map<string, WorkspaceCoordinationState> },
172
+ workspaceId: string,
173
+ ): WorkspaceCoordinationSnapshot {
174
+ const coord = state.coordinationByWorkspace.get(workspaceId) ?? createEmptyCoordinationState()
175
+
176
+ const todos = [...coord.todos.values()]
177
+ .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
178
+
179
+ const claims = [...coord.claims.values()]
180
+ .filter((c) => c.status !== "released")
181
+
182
+ const worktrees = [...coord.worktrees.values()]
183
+ .filter((w) => w.status !== "removed")
184
+
185
+ const rules = [...coord.rules.values()]
186
+
187
+ return {
188
+ workspaceId,
189
+ todos,
190
+ claims,
191
+ worktrees,
192
+ rules,
193
+ lastUpdated: coord.lastUpdated,
194
+ }
195
+ }
196
+
197
+ /** Convenience overload accepting full StoreState (used by nats-publisher). */
198
+ export function deriveWorkspaceCoordinationSnapshot(
199
+ state: StoreState,
200
+ workspaceId: string,
201
+ ): WorkspaceCoordinationSnapshot {
202
+ return deriveCoordinationSnapshot(state, workspaceId)
203
+ }
204
+
205
+ /** Derive repo list snapshot for a workspace. */
206
+ export function deriveRepoListSnapshot(state: StoreState, workspaceId: string): { workspaceId: string; repos: RepoSummary[] } {
207
+ const repos: RepoSummary[] = []
208
+ for (const repo of state.reposById.values()) {
209
+ if (repo.workspaceId === workspaceId) {
210
+ repos.push({
211
+ id: repo.id,
212
+ workspaceId: repo.workspaceId,
213
+ label: repo.label,
214
+ origin: repo.origin,
215
+ localPath: repo.localPath,
216
+ status: repo.status,
217
+ branch: repo.branch,
218
+ })
219
+ }
220
+ }
221
+ return { workspaceId, repos }
222
+ }
223
+
224
+ /** Derive agent config snapshot for a workspace. */
225
+ export function deriveAgentConfigSnapshot(
226
+ state: StoreState,
227
+ workspaceId: string,
228
+ ): AgentConfigSnapshot {
229
+ const configMap = state.agentConfigsByWorkspace.get(workspaceId)
230
+ const configs = configMap ? [...configMap.values()] : []
231
+ const lastUpdated = configs.length > 0
232
+ ? new Date(Math.max(...configs.map((c) => c.updatedAt))).toISOString()
233
+ : new Date(0).toISOString()
234
+ return { workspaceId, configs, lastUpdated }
235
+ }
236
+
237
+ /** Derive workflow runs snapshot for a workspace. */
238
+ export function deriveWorkflowRunsSnapshot(
239
+ state: StoreState,
240
+ workspaceId: string,
241
+ ): WorkflowRunsSnapshot {
242
+ const runsMap = state.workflowRunsByWorkspace.get(workspaceId) ?? new Map()
243
+ const runs = [...runsMap.values()]
244
+ .sort((a, b) => b.startedAt - a.startedAt)
245
+ .slice(0, 50)
246
+ const activeRunIds = runs
247
+ .filter((r) => r.status === "running")
248
+ .map((r) => r.runId)
249
+ return { workspaceId, runs, activeRunIds }
250
+ }
251
+
252
+ export function deriveSandboxSnapshot(
253
+ state: StoreState,
254
+ workspaceId: string,
255
+ ): import("../shared/sandbox-types").SandboxSnapshot {
256
+ const sandbox = state.sandboxByWorkspace.get(workspaceId) ?? null
257
+ return { workspaceId, sandbox, health: null }
258
+ }
@@ -0,0 +1,96 @@
1
+ import { describe, test, expect, afterEach } from "bun:test"
2
+ import { EventStore } from "./event-store"
3
+ import { deriveRepoListSnapshot, deriveWorkspaceCoordinationSnapshot } from "./read-models"
4
+ import { rmSync, mkdirSync } from "node:fs"
5
+ import { join } from "node:path"
6
+
7
+ const TEST_DIR = join(import.meta.dir, ".test-repo-journey")
8
+
9
+ afterEach(() => {
10
+ rmSync(TEST_DIR, { recursive: true, force: true })
11
+ })
12
+
13
+ async function createStoreWithProject() {
14
+ mkdirSync(TEST_DIR, { recursive: true })
15
+ const store = new EventStore(TEST_DIR)
16
+ await store.initialize()
17
+ const project = await store.openProject("/tmp/repo-journey-test", "RepoJourney")
18
+ return { store, workspaceId: project.id }
19
+ }
20
+
21
+ describe("repo journey", () => {
22
+ test("stage 7: add local repo appears in snapshot", async () => {
23
+ const { store, workspaceId } = await createStoreWithProject()
24
+
25
+ await store.addRepo("r-1", workspaceId, "/home/user/my-repo", "git@github.com:user/repo.git", "My Repo", "main")
26
+
27
+ const snapshot = deriveRepoListSnapshot(store.state, workspaceId)
28
+ expect(snapshot.workspaceId).toBe(workspaceId)
29
+ expect(snapshot.repos).toHaveLength(1)
30
+ expect(snapshot.repos[0].id).toBe("r-1")
31
+ expect(snapshot.repos[0].localPath).toBe("/home/user/my-repo")
32
+ expect(snapshot.repos[0].origin).toBe("git@github.com:user/repo.git")
33
+ expect(snapshot.repos[0].label).toBe("My Repo")
34
+ expect(snapshot.repos[0].branch).toBe("main")
35
+ expect(snapshot.repos[0].status).toBe("cloned")
36
+ })
37
+
38
+ test("stage 8: clone repo starts as pending", async () => {
39
+ const { store, workspaceId } = await createStoreWithProject()
40
+
41
+ await store.startRepoClone("r-2", workspaceId, "git@github.com:org/lib.git", "/tmp/clones/lib", "Lib Clone")
42
+
43
+ const snapshot = deriveRepoListSnapshot(store.state, workspaceId)
44
+ expect(snapshot.repos).toHaveLength(1)
45
+ expect(snapshot.repos[0].id).toBe("r-2")
46
+ expect(snapshot.repos[0].status).toBe("pending")
47
+ expect(snapshot.repos[0].origin).toBe("git@github.com:org/lib.git")
48
+ })
49
+
50
+ test("stage 10: remove repo disappears from snapshot", async () => {
51
+ const { store, workspaceId } = await createStoreWithProject()
52
+
53
+ await store.addRepo("r-3", workspaceId, "/home/user/old-repo", null, "Old Repo", null)
54
+ const before = deriveRepoListSnapshot(store.state, workspaceId)
55
+ expect(before.repos).toHaveLength(1)
56
+
57
+ await store.removeRepo("r-3", workspaceId)
58
+ const after = deriveRepoListSnapshot(store.state, workspaceId)
59
+ expect(after.repos).toHaveLength(0)
60
+ })
61
+
62
+ test("repo state survives compact and replay", async () => {
63
+ const { store, workspaceId } = await createStoreWithProject()
64
+
65
+ await store.addRepo("r-4", workspaceId, "/home/user/persist-repo", "https://github.com/user/persist.git", "Persist", "develop")
66
+
67
+ const snapshotBefore = deriveRepoListSnapshot(store.state, workspaceId)
68
+ expect(snapshotBefore.repos).toHaveLength(1)
69
+
70
+ await store.compact()
71
+
72
+ const store2 = new EventStore(TEST_DIR)
73
+ await store2.initialize()
74
+
75
+ const snapshotAfter = deriveRepoListSnapshot(store2.state, workspaceId)
76
+ expect(snapshotAfter.repos).toHaveLength(1)
77
+ expect(snapshotAfter.repos[0].id).toBe("r-4")
78
+ expect(snapshotAfter.repos[0].localPath).toBe("/home/user/persist-repo")
79
+ expect(snapshotAfter.repos[0].origin).toBe("https://github.com/user/persist.git")
80
+ expect(snapshotAfter.repos[0].label).toBe("Persist")
81
+ expect(snapshotAfter.repos[0].branch).toBe("develop")
82
+ })
83
+
84
+ test("claim release updates coordination snapshot", async () => {
85
+ const { store, workspaceId } = await createStoreWithProject()
86
+
87
+ await store.createClaim(workspaceId, "c-1", "Refactor auth", ["src/auth.ts"], "session-a")
88
+ const before = deriveWorkspaceCoordinationSnapshot(store.state, workspaceId)
89
+ expect(before.claims).toHaveLength(1)
90
+ expect(before.claims[0].id).toBe("c-1")
91
+
92
+ await store.releaseClaim(workspaceId, "c-1")
93
+ const after = deriveWorkspaceCoordinationSnapshot(store.state, workspaceId)
94
+ expect(after.claims).toHaveLength(0)
95
+ })
96
+ })
@@ -0,0 +1,118 @@
1
+ import { describe, test, expect, afterEach } from "bun:test"
2
+ import { mkdtemp, rm, writeFile } from "node:fs/promises"
3
+ import { join } from "node:path"
4
+ import { tmpdir } from "node:os"
5
+ import { $ } from "bun"
6
+ import { RepoManager } from "./repo-manager"
7
+
8
+ const mgr = new RepoManager()
9
+
10
+ let tempDirs: string[] = []
11
+
12
+ async function makeTempDir(): Promise<string> {
13
+ const dir = await mkdtemp(join(tmpdir(), "repo-mgr-test-"))
14
+ tempDirs.push(dir)
15
+ return dir
16
+ }
17
+
18
+ async function initRepo(dir: string): Promise<void> {
19
+ await $`git -C ${dir} init`.quiet()
20
+ await $`git -C ${dir} config user.email "test@test.com"`.quiet()
21
+ await $`git -C ${dir} config user.name "Test"`.quiet()
22
+ }
23
+
24
+ async function commitFile(dir: string, name = "file.txt", content = "hello"): Promise<void> {
25
+ await writeFile(join(dir, name), content)
26
+ await $`git -C ${dir} add ${name}`.quiet()
27
+ await $`git -C ${dir} commit -m "add ${name}"`.quiet()
28
+ }
29
+
30
+ async function getBranch(dir: string): Promise<string> {
31
+ return (await $`git -C ${dir} branch --show-current`.text()).trim()
32
+ }
33
+
34
+ afterEach(async () => {
35
+ for (const d of tempDirs) {
36
+ await rm(d, { recursive: true, force: true }).catch(() => {})
37
+ }
38
+ tempDirs = []
39
+ })
40
+
41
+ describe("RepoManager", () => {
42
+ test("addLocal — valid repo without remote", async () => {
43
+ const dir = await makeTempDir()
44
+ await initRepo(dir)
45
+ await commitFile(dir)
46
+ const branch = await getBranch(dir)
47
+
48
+ const result = await mgr.addLocal(dir)
49
+ expect(result.origin).toBeNull()
50
+ expect(result.branch).toBe(branch)
51
+ })
52
+
53
+ test("addLocal — repo with remote", async () => {
54
+ const dir = await makeTempDir()
55
+ await initRepo(dir)
56
+ await commitFile(dir)
57
+ await $`git -C ${dir} remote add origin https://example.com/repo.git`.quiet()
58
+
59
+ const result = await mgr.addLocal(dir)
60
+ expect(result.origin).toBe("https://example.com/repo.git")
61
+ expect(result.branch).toBeTruthy()
62
+ })
63
+
64
+ test("addLocal — invalid path throws", async () => {
65
+ await expect(mgr.addLocal("/tmp/nonexistent-path-xyz-999")).rejects.toThrow()
66
+ })
67
+
68
+ test("addLocal — not a git repo throws", async () => {
69
+ const dir = await makeTempDir()
70
+ await expect(mgr.addLocal(dir)).rejects.toThrow()
71
+ })
72
+
73
+ test("status — clean repo", async () => {
74
+ const dir = await makeTempDir()
75
+ await initRepo(dir)
76
+ await commitFile(dir)
77
+ const branch = await getBranch(dir)
78
+
79
+ const s = await mgr.status(dir)
80
+ expect(s.branch).toBe(branch)
81
+ expect(s.ahead).toBe(0)
82
+ expect(s.behind).toBe(0)
83
+ expect(s.dirty).toBe(false)
84
+ })
85
+
86
+ test("status — dirty repo", async () => {
87
+ const dir = await makeTempDir()
88
+ await initRepo(dir)
89
+ await commitFile(dir)
90
+ await writeFile(join(dir, "file.txt"), "modified")
91
+
92
+ const s = await mgr.status(dir)
93
+ expect(s.dirty).toBe(true)
94
+ })
95
+
96
+ test("clone — bare repo", async () => {
97
+ const bare = await makeTempDir()
98
+ await $`git init --bare ${bare}`.quiet()
99
+
100
+ const target = join(await makeTempDir(), "cloned")
101
+ await mgr.clone(bare, target)
102
+
103
+ const check = await $`git -C ${target} rev-parse --git-dir`.quiet()
104
+ expect(check.exitCode).toBe(0)
105
+ })
106
+
107
+ test("remove — is a no-op", async () => {
108
+ const dir = await makeTempDir()
109
+ await initRepo(dir)
110
+ await commitFile(dir)
111
+
112
+ await mgr.remove(dir)
113
+
114
+ // Directory and git repo still exist
115
+ const check = await $`git -C ${dir} rev-parse --git-dir`.quiet()
116
+ expect(check.exitCode).toBe(0)
117
+ })
118
+ })
@@ -0,0 +1,97 @@
1
+ import { $ } from "bun"
2
+ import { stat } from "node:fs/promises"
3
+
4
+ const LOG_PREFIX = "[RepoManager]"
5
+
6
+ export class RepoManager {
7
+ /** Validate path is a git repo, read remote origin + current branch */
8
+ async addLocal(localPath: string): Promise<{ origin: string | null; branch: string | null }> {
9
+ try {
10
+ await stat(localPath)
11
+ } catch (error) {
12
+ throw new Error(`${LOG_PREFIX} Path does not exist: ${localPath}`)
13
+ }
14
+
15
+ const gitCheck = await $`git -C ${localPath} rev-parse --git-dir`.quiet()
16
+ if (gitCheck.exitCode !== 0) {
17
+ throw new Error(`${LOG_PREFIX} Not a git repository: ${localPath}`)
18
+ }
19
+
20
+ let origin: string | null = null
21
+ const originResult = await $`git -C ${localPath} remote get-url origin`.quiet().nothrow()
22
+ if (originResult.exitCode === 0) {
23
+ origin = originResult.text().trim() || null
24
+ }
25
+
26
+ let branch: string | null = null
27
+ const branchResult = await $`git -C ${localPath} branch --show-current`.quiet()
28
+ if (branchResult.exitCode === 0) {
29
+ branch = branchResult.text().trim() || null
30
+ }
31
+
32
+ return { origin, branch }
33
+ }
34
+
35
+ /** Clone a remote repo to target path */
36
+ async clone(origin: string, targetPath: string): Promise<void> {
37
+ const result = await $`git clone ${origin} ${targetPath}`.quiet()
38
+ if (result.exitCode !== 0) {
39
+ throw new Error(`${LOG_PREFIX} Clone failed: ${result.stderr.toString().trim()}`)
40
+ }
41
+ }
42
+
43
+ /** Pull latest changes */
44
+ async pull(localPath: string, branch?: string): Promise<string> {
45
+ const args = branch ? [branch] : []
46
+ const result = await $`git -C ${localPath} pull ${args}`.quiet()
47
+ if (result.exitCode !== 0) {
48
+ throw new Error(`${LOG_PREFIX} Pull failed: ${result.stderr.toString().trim()}`)
49
+ }
50
+ return result.text().trim()
51
+ }
52
+
53
+ /** Push changes */
54
+ async push(localPath: string, branch?: string): Promise<string> {
55
+ const args = branch ? [branch] : []
56
+ const result = await $`git -C ${localPath} push ${args}`.quiet()
57
+ if (result.exitCode !== 0) {
58
+ throw new Error(`${LOG_PREFIX} Push failed: ${result.stderr.toString().trim()}`)
59
+ }
60
+ return result.text().trim()
61
+ }
62
+
63
+ /** Get repo status: branch, ahead/behind, dirty flag */
64
+ async status(localPath: string): Promise<{ branch: string; ahead: number; behind: number; dirty: boolean }> {
65
+ const result = await $`git -C ${localPath} status --porcelain=v2 --branch`.quiet()
66
+ if (result.exitCode !== 0) {
67
+ throw new Error(`${LOG_PREFIX} Status failed: ${result.stderr.toString().trim()}`)
68
+ }
69
+
70
+ const output = result.text()
71
+ let branch = ""
72
+ let ahead = 0
73
+ let behind = 0
74
+ let dirty = false
75
+
76
+ for (const line of output.split("\n")) {
77
+ if (line.startsWith("# branch.head ")) {
78
+ branch = line.slice("# branch.head ".length).trim()
79
+ } else if (line.startsWith("# branch.ab ")) {
80
+ const match = line.match(/\+(\d+) -(\d+)/)
81
+ if (match) {
82
+ ahead = parseInt(match[1], 10)
83
+ behind = parseInt(match[2], 10)
84
+ }
85
+ } else if (line.length > 0 && !line.startsWith("#")) {
86
+ dirty = true
87
+ }
88
+ }
89
+
90
+ return { branch, ahead, behind, dirty }
91
+ }
92
+
93
+ /** No-op — signals intent to remove, does NOT delete files */
94
+ async remove(_localPath: string): Promise<void> {
95
+ // intentional no-op
96
+ }
97
+ }
@@ -0,0 +1,54 @@
1
+ import { afterEach, describe, expect, test } from "bun:test"
2
+ import { mkdtemp, rm, writeFile } from "node:fs/promises"
3
+ import { tmpdir } from "node:os"
4
+ import path from "node:path"
5
+ import { execFile } from "node:child_process"
6
+ import { promisify } from "node:util"
7
+ import { readRepoStatus } from "./repo-status"
8
+
9
+ const execFileAsync = promisify(execFile)
10
+ const tempDirs: string[] = []
11
+
12
+ afterEach(async () => {
13
+ await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })))
14
+ })
15
+
16
+ async function makeTempRepo() {
17
+ const dir = await mkdtemp(path.join(tmpdir(), "tinkaria-repo-status-"))
18
+ tempDirs.push(dir)
19
+ await execFileAsync("git", ["init", "-b", "main"], { cwd: dir })
20
+ await execFileAsync("git", ["config", "user.email", "test@example.com"], { cwd: dir })
21
+ await execFileAsync("git", ["config", "user.name", "Test User"], { cwd: dir })
22
+ await writeFile(path.join(dir, "tracked.txt"), "hello\n")
23
+ await execFileAsync("git", ["add", "tracked.txt"], { cwd: dir })
24
+ await execFileAsync("git", ["commit", "-m", "init"], { cwd: dir })
25
+ return dir
26
+ }
27
+
28
+ describe("readRepoStatus", () => {
29
+ test("returns null-ish git metadata for non-repos", async () => {
30
+ const dir = await mkdtemp(path.join(tmpdir(), "tinkaria-non-repo-"))
31
+ tempDirs.push(dir)
32
+
33
+ const status = await readRepoStatus(dir)
34
+ expect(status.isRepo).toBe(false)
35
+ expect(status.branch).toBeNull()
36
+ })
37
+
38
+ test("reports branch and dirty counts for repos", async () => {
39
+ const dir = await makeTempRepo()
40
+ await execFileAsync("git", ["checkout", "-b", "feat/status-bar"], { cwd: dir })
41
+ await writeFile(path.join(dir, "tracked.txt"), "changed\n")
42
+ await writeFile(path.join(dir, "staged.txt"), "staged\n")
43
+ await writeFile(path.join(dir, "new.txt"), "new\n")
44
+ await execFileAsync("git", ["add", "staged.txt"], { cwd: dir })
45
+
46
+ const status = await readRepoStatus(dir)
47
+
48
+ expect(status.isRepo).toBe(true)
49
+ expect(status.branch).toBe("feat/status-bar")
50
+ expect(status.stagedCount).toBe(1)
51
+ expect(status.unstagedCount).toBe(1)
52
+ expect(status.untrackedCount).toBe(1)
53
+ })
54
+ })