kimaki 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/dist/agent-model.e2e.test.js +250 -164
  2. package/dist/anthropic-account-identity.js +9 -1
  3. package/dist/anthropic-auth-plugin.js +10 -13
  4. package/dist/anthropic-auth-plugin.test.js +17 -0
  5. package/dist/anthropic-auth-state.js +1 -0
  6. package/dist/bash-tool-schema-plugin.js +32 -0
  7. package/dist/bash-tool-schema-plugin.test.js +49 -0
  8. package/dist/bin.js +73 -19
  9. package/dist/cli-commands/maintenance.js +46 -7
  10. package/dist/cli-commands/project.js +11 -27
  11. package/dist/cli-commands/send.js +30 -1
  12. package/dist/cli-commands/session.js +322 -90
  13. package/dist/cli-commands/task.js +34 -15
  14. package/dist/cli-parsing.test.js +83 -4
  15. package/dist/cli-runner.js +44 -8
  16. package/dist/cli-runner.test.js +27 -0
  17. package/dist/cli-send-thread.e2e.test.js +9 -9
  18. package/dist/cli.js +32 -0
  19. package/dist/commands/abort.js +5 -1
  20. package/dist/commands/action-buttons.js +2 -1
  21. package/dist/commands/add-project.js +4 -7
  22. package/dist/commands/agent.js +164 -32
  23. package/dist/commands/ask-question.js +23 -33
  24. package/dist/commands/ask-question.test.js +43 -1
  25. package/dist/commands/btw.js +28 -6
  26. package/dist/commands/file-upload.js +2 -1
  27. package/dist/commands/fork-subagent.js +2 -4
  28. package/dist/commands/fork.js +4 -4
  29. package/dist/commands/last-sessions.js +17 -10
  30. package/dist/commands/login.js +6 -0
  31. package/dist/commands/merge-worktree.js +47 -14
  32. package/dist/commands/model-variant.js +8 -1
  33. package/dist/commands/model.js +16 -9
  34. package/dist/commands/multioauth.js +1 -0
  35. package/dist/commands/new-worktree.js +170 -102
  36. package/dist/commands/queue.js +3 -2
  37. package/dist/commands/resume.js +4 -4
  38. package/dist/commands/session.js +78 -63
  39. package/dist/commands/user-command.js +6 -8
  40. package/dist/commands/worktrees.js +5 -13
  41. package/dist/config.js +16 -1
  42. package/dist/cpu-profiler.js +211 -0
  43. package/dist/cpu-profiler.test.js +160 -0
  44. package/dist/database.js +152 -3
  45. package/dist/db.js +33 -0
  46. package/dist/db.test.js +267 -1
  47. package/dist/default-channel-provisioning.test.js +148 -0
  48. package/dist/discord-bot.js +517 -488
  49. package/dist/discord-command-registration.js +25 -10
  50. package/dist/discord-command-registration.test.js +17 -0
  51. package/dist/discord-utils.js +97 -5
  52. package/dist/discord-utils.test.js +151 -1
  53. package/dist/errors.js +10 -0
  54. package/dist/exec-async.js +17 -5
  55. package/dist/external-directory-permissions.e2e.test.js +11 -7
  56. package/dist/external-directory-project-deny.e2e.test.js +5 -3
  57. package/dist/external-opencode-sync.js +94 -17
  58. package/dist/external-opencode-sync.test.js +206 -0
  59. package/dist/extract-sdk-error-message.test.js +13 -0
  60. package/dist/file-edit-log.js +197 -0
  61. package/dist/file-edit-log.test.js +258 -0
  62. package/dist/format-tables.js +52 -0
  63. package/dist/format-tables.test.js +93 -0
  64. package/dist/gateway-proxy-reconnect.e2e.test.js +28 -8
  65. package/dist/gateway-proxy.e2e.test.js +18 -18
  66. package/dist/git-worktree-core.js +182 -17
  67. package/dist/interaction-handler.js +416 -398
  68. package/dist/ipc-tools-plugin.js +68 -3
  69. package/dist/kimaki-opencode-plugin-loading.e2e.test.js +122 -58
  70. package/dist/kimaki-opencode-plugin.js +9 -3
  71. package/dist/kimaki-workspace-adaptor.js +33 -10
  72. package/dist/logger.js +11 -1
  73. package/dist/markdown.js +3 -1
  74. package/dist/markdown.test.js +11 -3
  75. package/dist/message-formatting.js +181 -51
  76. package/dist/message-formatting.test.js +529 -22
  77. package/dist/message-preprocessing.js +105 -1
  78. package/dist/oauth-rotation-shared.js +10 -0
  79. package/dist/openai-auth-plugin.js +1 -0
  80. package/dist/openai-auth-state.js +1 -0
  81. package/dist/opencode-command.js +28 -0
  82. package/dist/opencode-command.test.js +109 -1
  83. package/dist/opencode-interrupt-plugin.js +4 -0
  84. package/dist/opencode-interrupt-plugin.test.js +24 -0
  85. package/dist/opencode.js +82 -15
  86. package/dist/project-add.test.js +62 -0
  87. package/dist/question-abort-clears-dropdown.e2e.test.js +89 -0
  88. package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +150 -0
  89. package/dist/question-text-before-dropdown.e2e.test.js +52 -0
  90. package/dist/queue-advanced-abort.e2e.test.js +8 -10
  91. package/dist/queue-advanced-action-buttons.e2e.test.js +8 -8
  92. package/dist/queue-advanced-e2e-setup.js +78 -5
  93. package/dist/queue-advanced-footer.e2e.test.js +60 -269
  94. package/dist/queue-advanced-model-switch.e2e.test.js +9 -11
  95. package/dist/queue-advanced-permissions-typing.e2e.test.js +15 -15
  96. package/dist/queue-advanced-question.e2e.test.js +32 -27
  97. package/dist/queue-advanced-typing-interrupt.e2e.test.js +10 -12
  98. package/dist/queue-advanced-typing.e2e.test.js +7 -9
  99. package/dist/queue-delete-message.e2e.test.js +3 -3
  100. package/dist/queue-drain-after-interactive-ui.e2e.test.js +5 -5
  101. package/dist/queue-interrupt-drain.e2e.test.js +122 -12
  102. package/dist/queue-question-select-drain.e2e.test.js +34 -132
  103. package/dist/runtime-lifecycle.e2e.test.js +21 -51
  104. package/dist/schema.js +35 -1
  105. package/dist/session-handler/event-stream-state.js +391 -50
  106. package/dist/session-handler/event-stream-state.test.js +736 -8
  107. package/dist/session-handler/model-utils.js +172 -5
  108. package/dist/session-handler/model-utils.test.js +445 -0
  109. package/dist/session-handler/opencode-session-event-log.js +30 -1
  110. package/dist/session-handler/thread-ingress.test.js +41 -0
  111. package/dist/session-handler/thread-runtime-state.js +2 -0
  112. package/dist/session-handler/thread-session-runtime-summary.test.js +121 -0
  113. package/dist/session-handler/thread-session-runtime.js +574 -299
  114. package/dist/session-search.js +123 -4
  115. package/dist/session-search.test.js +248 -1
  116. package/dist/session-sleep.test.js +224 -0
  117. package/dist/session-title-rename.test.js +37 -39
  118. package/dist/sleep.e2e.test.js +494 -0
  119. package/dist/startup-time.e2e.test.js +2 -1
  120. package/dist/store.js +3 -0
  121. package/dist/system-message.js +102 -16
  122. package/dist/system-message.test.js +192 -15
  123. package/dist/task-runner.js +96 -5
  124. package/dist/task-schedule.js +87 -1
  125. package/dist/task-schedule.test.js +149 -1
  126. package/dist/test-utils.js +17 -9
  127. package/dist/thinking-utils.js +10 -0
  128. package/dist/thinking-utils.test.js +35 -0
  129. package/dist/thread-message-queue.e2e.test.js +65 -213
  130. package/dist/undo-redo.e2e.test.js +17 -9
  131. package/dist/voice-attachment.js +23 -1
  132. package/dist/voice-handler.js +21 -4
  133. package/dist/voice-message.e2e.test.js +144 -30
  134. package/dist/voice-routing-input.e2e.test.js +195 -0
  135. package/dist/voice-routing-question.e2e.test.js +125 -0
  136. package/dist/voice.js +28 -8
  137. package/dist/voice.test.js +96 -30
  138. package/dist/worktree-clone-isolation.e2e.test.js +188 -0
  139. package/dist/worktree-lifecycle.e2e.test.js +55 -36
  140. package/dist/worktrees.js +226 -443
  141. package/dist/worktrees.test.js +435 -58
  142. package/dist/xai-auth-plugin.js +1 -0
  143. package/dist/xai-auth-state.js +1 -0
  144. package/package.json +8 -6
  145. package/skills/bump-holocron-consumers/SKILL.md +137 -0
  146. package/skills/critique/SKILL.md +4 -4
  147. package/skills/errore/SKILL.md +60 -218
  148. package/skills/goke/SKILL.md +150 -10
  149. package/skills/holocron/SKILL.md +154 -17
  150. package/skills/npm-package/SKILL.md +55 -35
  151. package/skills/profano/SKILL.md +17 -2
  152. package/skills/spiceflow/SKILL.md +39 -3
  153. package/skills/strada/SKILL.md +39 -0
  154. package/skills/zele/SKILL.md +19 -0
  155. package/src/agent-model.e2e.test.ts +287 -186
  156. package/src/anthropic-account-identity.ts +20 -1
  157. package/src/anthropic-auth-plugin.test.ts +19 -0
  158. package/src/anthropic-auth-plugin.ts +12 -16
  159. package/src/anthropic-auth-state.ts +1 -0
  160. package/src/bash-tool-schema-plugin.test.ts +55 -0
  161. package/src/bash-tool-schema-plugin.ts +46 -0
  162. package/src/bin.ts +78 -26
  163. package/src/cli-commands/maintenance.ts +59 -13
  164. package/src/cli-commands/project.ts +14 -35
  165. package/src/cli-commands/send.ts +31 -1
  166. package/src/cli-commands/session.ts +374 -114
  167. package/src/cli-commands/task.ts +34 -15
  168. package/src/cli-parsing.test.ts +103 -4
  169. package/src/cli-runner.test.ts +49 -0
  170. package/src/cli-runner.ts +44 -7
  171. package/src/cli-send-thread.e2e.test.ts +9 -11
  172. package/src/cli.ts +53 -0
  173. package/src/commands/abort.ts +6 -1
  174. package/src/commands/action-buttons.ts +2 -1
  175. package/src/commands/add-project.ts +4 -8
  176. package/src/commands/agent.ts +205 -29
  177. package/src/commands/ask-question.test.ts +48 -1
  178. package/src/commands/ask-question.ts +23 -33
  179. package/src/commands/btw.ts +40 -5
  180. package/src/commands/file-upload.ts +2 -1
  181. package/src/commands/fork-subagent.ts +2 -3
  182. package/src/commands/fork.ts +4 -3
  183. package/src/commands/last-sessions.ts +10 -4
  184. package/src/commands/login.ts +6 -0
  185. package/src/commands/merge-worktree.ts +54 -16
  186. package/src/commands/model-variant.ts +9 -1
  187. package/src/commands/model.ts +20 -9
  188. package/src/commands/multioauth.ts +1 -0
  189. package/src/commands/new-worktree.ts +138 -55
  190. package/src/commands/queue.ts +3 -2
  191. package/src/commands/resume.ts +4 -3
  192. package/src/commands/session.ts +94 -70
  193. package/src/commands/user-command.ts +6 -6
  194. package/src/commands/worktrees.ts +5 -12
  195. package/src/config.ts +18 -1
  196. package/src/cpu-profiler.test.ts +183 -0
  197. package/src/cpu-profiler.ts +293 -0
  198. package/src/database.ts +235 -3
  199. package/src/db.test.ts +293 -0
  200. package/src/db.ts +38 -0
  201. package/src/default-channel-provisioning.test.ts +155 -0
  202. package/src/discord-bot.ts +78 -36
  203. package/src/discord-command-registration.test.ts +20 -0
  204. package/src/discord-command-registration.ts +42 -12
  205. package/src/discord-utils.test.ts +176 -0
  206. package/src/discord-utils.ts +152 -7
  207. package/src/errors.ts +12 -0
  208. package/src/exec-async.ts +20 -11
  209. package/src/external-directory-permissions.e2e.test.ts +11 -7
  210. package/src/external-directory-project-deny.e2e.test.ts +5 -3
  211. package/src/external-opencode-sync.test.ts +248 -0
  212. package/src/external-opencode-sync.ts +122 -19
  213. package/src/extract-sdk-error-message.test.ts +18 -0
  214. package/src/file-edit-log.test.ts +287 -0
  215. package/src/file-edit-log.ts +283 -0
  216. package/src/format-tables.test.ts +101 -0
  217. package/src/format-tables.ts +75 -0
  218. package/src/gateway-proxy-reconnect.e2e.test.ts +38 -8
  219. package/src/gateway-proxy.e2e.test.ts +18 -17
  220. package/src/git-worktree-core.ts +247 -20
  221. package/src/interaction-handler.ts +36 -13
  222. package/src/ipc-tools-plugin.ts +82 -5
  223. package/src/kimaki-opencode-plugin-loading.e2e.test.ts +188 -97
  224. package/src/kimaki-opencode-plugin.ts +9 -3
  225. package/src/kimaki-workspace-adaptor.ts +40 -11
  226. package/src/logger.ts +14 -4
  227. package/src/markdown.test.ts +13 -3
  228. package/src/markdown.ts +4 -1
  229. package/src/message-formatting.test.ts +672 -25
  230. package/src/message-formatting.ts +242 -53
  231. package/src/message-preprocessing.ts +116 -1
  232. package/src/oauth-rotation-shared.ts +10 -0
  233. package/src/openai-auth-plugin.ts +1 -0
  234. package/src/openai-auth-state.ts +1 -0
  235. package/src/opencode-command.test.ts +140 -0
  236. package/src/opencode-command.ts +38 -0
  237. package/src/opencode-interrupt-plugin.test.ts +32 -0
  238. package/src/opencode-interrupt-plugin.ts +3 -0
  239. package/src/opencode.ts +115 -15
  240. package/src/project-add.test.ts +80 -0
  241. package/src/question-abort-clears-dropdown.e2e.test.ts +109 -0
  242. package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +172 -0
  243. package/src/question-text-before-dropdown.e2e.test.ts +67 -0
  244. package/src/queue-advanced-abort.e2e.test.ts +8 -9
  245. package/src/queue-advanced-action-buttons.e2e.test.ts +8 -8
  246. package/src/queue-advanced-e2e-setup.ts +83 -4
  247. package/src/queue-advanced-footer.e2e.test.ts +67 -315
  248. package/src/queue-advanced-model-switch.e2e.test.ts +9 -10
  249. package/src/queue-advanced-permissions-typing.e2e.test.ts +15 -15
  250. package/src/queue-advanced-question.e2e.test.ts +36 -30
  251. package/src/queue-advanced-typing-interrupt.e2e.test.ts +11 -11
  252. package/src/queue-advanced-typing.e2e.test.ts +8 -8
  253. package/src/queue-delete-message.e2e.test.ts +3 -3
  254. package/src/queue-drain-after-interactive-ui.e2e.test.ts +5 -5
  255. package/src/queue-interrupt-drain.e2e.test.ts +133 -12
  256. package/src/queue-question-select-drain.e2e.test.ts +36 -161
  257. package/src/runtime-lifecycle.e2e.test.ts +21 -54
  258. package/src/schema.sql +15 -2
  259. package/src/schema.ts +37 -1
  260. package/src/session-handler/event-stream-state.test.ts +790 -7
  261. package/src/session-handler/event-stream-state.ts +568 -53
  262. package/src/session-handler/model-utils.test.ts +537 -0
  263. package/src/session-handler/model-utils.ts +281 -5
  264. package/src/session-handler/opencode-session-event-log.ts +31 -1
  265. package/src/session-handler/thread-ingress.test.ts +54 -0
  266. package/src/session-handler/thread-runtime-state.ts +3 -0
  267. package/src/session-handler/thread-session-runtime-summary.test.ts +169 -0
  268. package/src/session-handler/thread-session-runtime.ts +712 -358
  269. package/src/session-search.test.ts +281 -0
  270. package/src/session-search.ts +241 -7
  271. package/src/session-sleep.test.ts +294 -0
  272. package/src/session-title-rename.test.ts +54 -48
  273. package/src/sleep.e2e.test.ts +565 -0
  274. package/src/startup-time.e2e.test.ts +5 -1
  275. package/src/store.ts +22 -0
  276. package/src/system-message.test.ts +226 -15
  277. package/src/system-message.ts +130 -16
  278. package/src/task-runner.ts +138 -3
  279. package/src/task-schedule.test.ts +170 -0
  280. package/src/task-schedule.ts +141 -1
  281. package/src/test-utils.ts +21 -10
  282. package/src/thinking-utils.test.ts +46 -0
  283. package/src/thinking-utils.ts +21 -0
  284. package/src/thread-message-queue.e2e.test.ts +65 -245
  285. package/src/undo-redo.e2e.test.ts +17 -9
  286. package/src/voice-attachment.ts +29 -1
  287. package/src/voice-handler.ts +23 -4
  288. package/src/voice-message.e2e.test.ts +149 -29
  289. package/src/voice-routing-input.e2e.test.ts +203 -0
  290. package/src/voice-routing-question.e2e.test.ts +131 -0
  291. package/src/voice.test.ts +107 -29
  292. package/src/voice.ts +34 -6
  293. package/src/worktree-clone-isolation.e2e.test.ts +213 -0
  294. package/src/worktree-lifecycle.e2e.test.ts +55 -35
  295. package/src/worktrees.test.ts +505 -64
  296. package/src/worktrees.ts +257 -603
  297. package/src/xai-auth-plugin.ts +1 -0
  298. package/src/xai-auth-state.ts +1 -0
  299. package/dist/message-finish-field.e2e.test.js +0 -169
  300. package/dist/subagent-rate-limit-plugin.js +0 -184
  301. package/src/message-finish-field.e2e.test.ts +0 -196
  302. package/src/subagent-rate-limit-plugin.ts +0 -228
@@ -3,9 +3,9 @@
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { describe, expect, test } from 'vitest';
6
- import { buildSubmoduleReferencePlan, createWorktreeWithSubmodules, execAsync, getManagedWorktreeDirectory, mergeWorktree, parseGitmodulesFileContent, parseGitWorktreeListPorcelain, resolveSessionWorkingDirectory, } from './worktrees.js';
7
- import { parseGitmodulesFileContent as parseCoreGitmodulesFileContent } from './git-worktree-core.js';
8
- import { TargetDirtyWorktreeError } from './errors.js';
6
+ import { execAsync, getManagedWorktreeDirectory, formatMergeWorktreeError, mergeWorktree, parseGitWorktreeListPorcelain, resolveSessionWorkingDirectory, validateBranchRef, } from './worktrees.js';
7
+ import { createWorktreeCore, parseGitmodulesFileContent as parseCoreGitmodulesFileContent, removeWorktreeCore, removeWorktreeFromOwnRepository, resolveGitCommit, resolveGitCommonDirectory, validateWorktreeIdentity, } from './git-worktree-core.js';
8
+ import { GitCommandError, PushError, RebaseConflictError, TargetDirtyWorktreeError, } from './errors.js';
9
9
  import { formatAutoWorktreeName, formatWorktreeName, shortenWorktreeSlug, } from './commands/new-worktree.js';
10
10
  import { setDataDir } from './config.js';
11
11
  const GIT_TIMEOUT_MS = 60_000;
@@ -26,31 +26,18 @@ function createTestRoot() {
26
26
  fs.mkdirSync(tmpRoot, { recursive: true });
27
27
  return fs.mkdtempSync(path.join(tmpRoot, 'worktrees-test-'));
28
28
  }
29
- describe('worktrees', () => {
30
- test('parseGitmodulesFileContent parses paths and urls', () => {
31
- const parsed = parseGitmodulesFileContent(`
32
- [submodule "errore"]
33
- path = errore
34
- url = https://github.com/remorses/errore.git
35
- [submodule "gateway-proxy"]
36
- path = gateway-proxy
37
- url = https://github.com/remorses/gateway-proxy.git
38
- `);
39
- expect(parsed).toMatchInlineSnapshot(`
40
- [
41
- {
42
- "name": "errore",
43
- "path": "errore",
44
- "url": "https://github.com/remorses/errore.git",
45
- },
46
- {
47
- "name": "gateway-proxy",
48
- "path": "gateway-proxy",
49
- "url": "https://github.com/remorses/gateway-proxy.git",
50
- },
51
- ]
52
- `);
29
+ async function createTestWorktree({ directory, name, }) {
30
+ const baseCommit = await resolveGitCommit({ directory, ref: 'HEAD' });
31
+ if (baseCommit instanceof Error)
32
+ return baseCommit;
33
+ return createWorktreeCore({
34
+ projectDirectory: directory,
35
+ targetDirectory: getManagedWorktreeDirectory({ directory, name }),
36
+ branchName: name,
37
+ baseCommit,
53
38
  });
39
+ }
40
+ describe('worktrees', () => {
54
41
  test('plugin-safe gitmodules parser handles standard indented entries', () => {
55
42
  const parsed = parseCoreGitmodulesFileContent(`
56
43
  [submodule "errore"]
@@ -67,34 +54,7 @@ describe('worktrees', () => {
67
54
  ]
68
55
  `);
69
56
  });
70
- test('buildSubmoduleReferencePlan uses local references when available', () => {
71
- const sourceDirectory = '/repo';
72
- const plan = buildSubmoduleReferencePlan({
73
- sourceDirectory,
74
- submodulePaths: ['errore', 'gateway-proxy', 'traforo'],
75
- existingSourceSubmoduleDirectories: new Set([
76
- '/repo/errore',
77
- '/repo/gateway-proxy',
78
- ]),
79
- });
80
- expect(plan).toMatchInlineSnapshot(`
81
- [
82
- {
83
- "path": "errore",
84
- "referenceDirectory": "/repo/errore",
85
- },
86
- {
87
- "path": "gateway-proxy",
88
- "referenceDirectory": "/repo/gateway-proxy",
89
- },
90
- {
91
- "path": "traforo",
92
- "referenceDirectory": null,
93
- },
94
- ]
95
- `);
96
- });
97
- test('createWorktreeWithSubmodules resolves local-only submodule commits from local source checkout', async () => {
57
+ test('worktree creation resolves local-only submodule commits from local source checkout', async () => {
98
58
  const sandbox = createTestRoot();
99
59
  const submoduleRemote = path.join(sandbox, 'errore-remote.git');
100
60
  const submoduleLocal = path.join(sandbox, 'errore-local');
@@ -168,7 +128,7 @@ describe('worktrees', () => {
168
128
  cwd: parentRepo,
169
129
  args: ['commit', '-m', 'pin local-only submodule commit'],
170
130
  });
171
- const worktreeResult = await createWorktreeWithSubmodules({
131
+ const worktreeResult = await createTestWorktree({
172
132
  directory: parentRepo,
173
133
  name: worktreeName,
174
134
  });
@@ -180,12 +140,28 @@ describe('worktrees', () => {
180
140
  cwd: path.join(worktreeResult.directory, 'errore'),
181
141
  args: ['rev-parse', 'HEAD'],
182
142
  });
143
+ const parentCommonDirectory = await resolveGitCommonDirectory({
144
+ directory: parentRepo,
145
+ });
146
+ if (parentCommonDirectory instanceof Error) {
147
+ throw parentCommonDirectory;
148
+ }
149
+ const submoduleCommonDirectory = await resolveGitCommonDirectory({
150
+ directory: path.join(worktreeResult.directory, 'errore'),
151
+ });
152
+ if (submoduleCommonDirectory instanceof Error) {
153
+ throw submoduleCommonDirectory;
154
+ }
183
155
  expect({
184
156
  localOnlyShaLength: localOnlySha.length,
185
157
  worktreeSubmoduleShaLength: worktreeSubmoduleSha.length,
186
158
  sameCommit: localOnlySha === worktreeSubmoduleSha,
159
+ belongsToParentClone: !path
160
+ .relative(parentCommonDirectory, submoduleCommonDirectory)
161
+ .startsWith(`..${path.sep}`),
187
162
  }).toMatchInlineSnapshot(`
188
163
  {
164
+ "belongsToParentClone": true,
189
165
  "localOnlyShaLength": 40,
190
166
  "sameCommit": true,
191
167
  "worktreeSubmoduleShaLength": 40,
@@ -204,7 +180,7 @@ describe('worktrees', () => {
204
180
  fs.rmSync(sandbox, { recursive: true, force: true });
205
181
  }
206
182
  });
207
- test('createWorktreeWithSubmodules uses current HEAD even when origin does not have the commit', async () => {
183
+ test('worktree creation uses current HEAD even when origin does not have the commit', async () => {
208
184
  const sandbox = createTestRoot();
209
185
  const parentRemote = path.join(sandbox, 'parent-remote.git');
210
186
  const parentLocal = path.join(sandbox, 'parent-local');
@@ -235,7 +211,7 @@ describe('worktrees', () => {
235
211
  cwd: parentLocal,
236
212
  args: ['rev-parse', 'origin/main'],
237
213
  });
238
- const worktreeResult = await createWorktreeWithSubmodules({
214
+ const worktreeResult = await createTestWorktree({
239
215
  directory: parentLocal,
240
216
  name: worktreeName,
241
217
  });
@@ -275,6 +251,171 @@ describe('worktrees', () => {
275
251
  fs.rmSync(sandbox, { recursive: true, force: true });
276
252
  }
277
253
  });
254
+ test('worktree creation survives an unavailable submodule remote', async () => {
255
+ const sandbox = createTestRoot();
256
+ const submoduleRemote = path.join(sandbox, 'missing-remote.git');
257
+ const submoduleSource = path.join(sandbox, 'submodule-source');
258
+ const parentRepo = path.join(sandbox, 'parent');
259
+ const worktreeName = `opencode/kimaki-missing-submodule-${Date.now()}`;
260
+ try {
261
+ await git({ cwd: sandbox, args: ['init', '--bare', '-b', 'main', submoduleRemote] });
262
+ await git({ cwd: sandbox, args: ['clone', submoduleRemote, submoduleSource] });
263
+ await git({ cwd: submoduleSource, args: ['config', 'user.email', 'kimaki-tests@example.com'] });
264
+ await git({ cwd: submoduleSource, args: ['config', 'user.name', 'Kimaki Tests'] });
265
+ fs.writeFileSync(path.join(submoduleSource, 'README.md'), 'submodule\n');
266
+ await git({ cwd: submoduleSource, args: ['add', 'README.md'] });
267
+ await git({ cwd: submoduleSource, args: ['commit', '-m', 'submodule'] });
268
+ await git({ cwd: submoduleSource, args: ['push', 'origin', 'HEAD:main'] });
269
+ await git({ cwd: sandbox, args: ['init', '-b', 'main', parentRepo] });
270
+ await git({ cwd: parentRepo, args: ['config', 'user.email', 'kimaki-tests@example.com'] });
271
+ await git({ cwd: parentRepo, args: ['config', 'user.name', 'Kimaki Tests'] });
272
+ fs.writeFileSync(path.join(parentRepo, 'README.md'), 'parent\n');
273
+ await git({ cwd: parentRepo, args: ['add', 'README.md'] });
274
+ await git({ cwd: parentRepo, args: ['commit', '-m', 'parent'] });
275
+ await git({
276
+ cwd: parentRepo,
277
+ args: [
278
+ '-c',
279
+ 'protocol.file.allow=always',
280
+ 'submodule',
281
+ 'add',
282
+ submoduleRemote,
283
+ 'missing',
284
+ ],
285
+ });
286
+ await git({ cwd: parentRepo, args: ['commit', '-am', 'add submodule'] });
287
+ await git({ cwd: parentRepo, args: ['submodule', 'deinit', '-f', '--all'] });
288
+ fs.rmSync(path.join(parentRepo, '.git', 'modules', 'missing'), {
289
+ recursive: true,
290
+ force: true,
291
+ });
292
+ fs.rmSync(submoduleRemote, { recursive: true, force: true });
293
+ const result = await createTestWorktree({
294
+ directory: parentRepo,
295
+ name: worktreeName,
296
+ });
297
+ if (result instanceof Error)
298
+ throw result;
299
+ expect({
300
+ directoryExists: fs.existsSync(result.directory),
301
+ submoduleUnavailable: !fs.existsSync(path.join(result.directory, 'missing', '.git')),
302
+ }).toMatchInlineSnapshot(`
303
+ {
304
+ "directoryExists": true,
305
+ "submoduleUnavailable": true,
306
+ }
307
+ `);
308
+ }
309
+ finally {
310
+ const managedDirectory = getManagedWorktreeDirectory({
311
+ directory: parentRepo,
312
+ name: worktreeName,
313
+ });
314
+ if (fs.existsSync(parentRepo)) {
315
+ await git({
316
+ cwd: parentRepo,
317
+ args: ['worktree', 'remove', '--force', managedDirectory],
318
+ }).catch(() => '');
319
+ }
320
+ fs.rmSync(sandbox, { recursive: true, force: true });
321
+ }
322
+ });
323
+ test('worktree removal succeeds when its branch was already deleted', async () => {
324
+ const sandbox = createTestRoot();
325
+ const parentRepo = path.join(sandbox, 'parent');
326
+ const worktreeDirectory = path.join(sandbox, 'worktree');
327
+ const branchName = 'opencode/kimaki-removed-branch';
328
+ try {
329
+ await git({ cwd: sandbox, args: ['init', '-b', 'main', parentRepo] });
330
+ await git({ cwd: parentRepo, args: ['config', 'user.email', 'kimaki-tests@example.com'] });
331
+ await git({ cwd: parentRepo, args: ['config', 'user.name', 'Kimaki Tests'] });
332
+ fs.writeFileSync(path.join(parentRepo, 'README.md'), 'parent\n');
333
+ await git({ cwd: parentRepo, args: ['add', 'README.md'] });
334
+ await git({ cwd: parentRepo, args: ['commit', '-m', 'parent'] });
335
+ await git({
336
+ cwd: parentRepo,
337
+ args: ['worktree', 'add', '-b', branchName, worktreeDirectory],
338
+ });
339
+ await git({ cwd: worktreeDirectory, args: ['switch', '--detach'] });
340
+ await git({ cwd: parentRepo, args: ['branch', '-D', branchName] });
341
+ const result = await removeWorktreeCore({
342
+ projectDirectory: parentRepo,
343
+ worktreeDirectory,
344
+ branchName,
345
+ });
346
+ if (result instanceof Error)
347
+ throw result;
348
+ expect(fs.existsSync(worktreeDirectory)).toBe(false);
349
+ }
350
+ finally {
351
+ fs.rmSync(sandbox, { recursive: true, force: true });
352
+ }
353
+ });
354
+ test('worktree identity mismatch is detected and cleaned from its actual clone', async () => {
355
+ const sandbox = createTestRoot();
356
+ const remote = path.join(sandbox, 'remote.git');
357
+ const requestedClone = path.join(sandbox, 'requested');
358
+ const otherClone = path.join(sandbox, 'other');
359
+ const worktreeDirectory = path.join(sandbox, 'wrong-worktree');
360
+ const branchName = 'opencode/kimaki-wrong-clone';
361
+ try {
362
+ await git({ cwd: sandbox, args: ['init', '--bare', '-b', 'main', remote] });
363
+ await git({ cwd: sandbox, args: ['clone', remote, otherClone] });
364
+ await git({ cwd: otherClone, args: ['config', 'user.email', 'kimaki-tests@example.com'] });
365
+ await git({ cwd: otherClone, args: ['config', 'user.name', 'Kimaki Tests'] });
366
+ fs.writeFileSync(path.join(otherClone, 'README.md'), 'old\n');
367
+ await git({ cwd: otherClone, args: ['add', 'README.md'] });
368
+ await git({ cwd: otherClone, args: ['commit', '-m', 'old'] });
369
+ await git({ cwd: otherClone, args: ['push', 'origin', 'HEAD:main'] });
370
+ await git({ cwd: sandbox, args: ['clone', remote, requestedClone] });
371
+ await git({ cwd: requestedClone, args: ['config', 'user.email', 'kimaki-tests@example.com'] });
372
+ await git({ cwd: requestedClone, args: ['config', 'user.name', 'Kimaki Tests'] });
373
+ fs.writeFileSync(path.join(requestedClone, 'requested.txt'), 'new\n');
374
+ await git({ cwd: requestedClone, args: ['add', 'requested.txt'] });
375
+ await git({ cwd: requestedClone, args: ['commit', '-m', 'requested'] });
376
+ const baseCommit = await git({
377
+ cwd: requestedClone,
378
+ args: ['rev-parse', 'HEAD'],
379
+ });
380
+ await git({
381
+ cwd: otherClone,
382
+ args: ['worktree', 'add', '-b', branchName, worktreeDirectory, 'HEAD'],
383
+ });
384
+ const validation = await validateWorktreeIdentity({
385
+ projectDirectory: requestedClone,
386
+ worktreeDirectory,
387
+ baseCommit,
388
+ });
389
+ expect(validation).toBeInstanceOf(Error);
390
+ const cleanup = await removeWorktreeFromOwnRepository({
391
+ worktreeDirectory,
392
+ branchName,
393
+ });
394
+ if (cleanup instanceof Error)
395
+ throw cleanup;
396
+ const otherWorktrees = await git({
397
+ cwd: otherClone,
398
+ args: ['worktree', 'list', '--porcelain'],
399
+ });
400
+ const branchExists = await execAsync(`git show-ref --verify --quiet ${JSON.stringify(`refs/heads/${branchName}`)}`, { cwd: otherClone })
401
+ .then(() => true)
402
+ .catch(() => false);
403
+ expect({
404
+ directoryRemoved: !fs.existsSync(worktreeDirectory),
405
+ registrationRemoved: !otherWorktrees.includes(worktreeDirectory),
406
+ branchRemoved: !branchExists,
407
+ }).toMatchInlineSnapshot(`
408
+ {
409
+ "branchRemoved": true,
410
+ "directoryRemoved": true,
411
+ "registrationRemoved": true,
412
+ }
413
+ `);
414
+ }
415
+ finally {
416
+ fs.rmSync(sandbox, { recursive: true, force: true });
417
+ }
418
+ });
278
419
  test('mergeWorktree rejects dirty checked-out target before local push', async () => {
279
420
  const sandbox = createTestRoot();
280
421
  const parentRepo = path.join(sandbox, 'parent');
@@ -305,6 +446,242 @@ describe('worktrees', () => {
305
446
  fs.rmSync(sandbox, { recursive: true, force: true });
306
447
  }
307
448
  });
449
+ test('formatMergeWorktreeError includes git command, exit code, and stderr', () => {
450
+ const execErr = Object.assign(new Error('Command failed: git push'), {
451
+ stderr: 'fatal: refusing to update checked out branch: refs/heads/main\nhint: set receive.denyCurrentBranch to updateInstead\n',
452
+ stdout: '',
453
+ code: 1,
454
+ });
455
+ const gitErr = new GitCommandError({
456
+ command: 'git -C /wt push --receive-pack=git -c receive.denyCurrentBranch=updateInstead receive-pack /repo/.git HEAD:main',
457
+ cause: execErr,
458
+ });
459
+ const err = new PushError({ target: 'main', cause: gitErr });
460
+ expect(formatMergeWorktreeError(err)).toMatchInlineSnapshot(`
461
+ "Merge failed: Push to main failed
462
+ Worktree rebase succeeded. Local branch was not updated. This is not a push to origin.
463
+
464
+ \`\`\`
465
+ git -C /wt push --receive-pack=git -c receive.denyCurrentBranch=updateInstead receive-pack /repo/.git HEAD:main
466
+ exit 1
467
+
468
+ fatal: refusing to update checked out branch: refs/heads/main
469
+ hint: set receive.denyCurrentBranch to updateInstead
470
+ \`\`\`"
471
+ `);
472
+ });
473
+ test('formatMergeWorktreeError keeps timeout cause when git has no stderr', () => {
474
+ const timeoutErr = new Error('Command timed out after 30000ms: git -C /wt push /repo/.git HEAD:main');
475
+ const gitErr = new GitCommandError({
476
+ command: 'git -C /wt push /repo/.git HEAD:main',
477
+ cause: timeoutErr,
478
+ });
479
+ const err = new PushError({ target: 'main', cause: gitErr });
480
+ expect(formatMergeWorktreeError(err)).toMatchInlineSnapshot(`
481
+ "Merge failed: Push to main failed
482
+ Worktree rebase succeeded. Local branch was not updated. This is not a push to origin.
483
+
484
+ \`\`\`
485
+ git -C /wt push /repo/.git HEAD:main
486
+
487
+ Command timed out after 30000ms: git -C /wt push /repo/.git HEAD:main
488
+ \`\`\`"
489
+ `);
490
+ });
491
+ test('formatMergeWorktreeError truncates to maxLength', () => {
492
+ const execErr = Object.assign(new Error('Command failed: git push'), {
493
+ stderr: 'fatal: ' + 'x'.repeat(200),
494
+ code: 1,
495
+ });
496
+ const gitErr = new GitCommandError({
497
+ command: 'git -C /wt push /repo/.git HEAD:main',
498
+ cause: execErr,
499
+ });
500
+ const formatted = formatMergeWorktreeError(new PushError({ target: 'main', cause: gitErr }), { maxLength: 80 });
501
+ expect(formatted.length).toBeLessThanOrEqual(80);
502
+ expect(formatted.endsWith('… [truncated]')).toBe(true);
503
+ });
504
+ test('mergeWorktree push failure includes git stderr in formatted error', async () => {
505
+ const sandbox = createTestRoot();
506
+ const parentRepo = path.join(sandbox, 'parent');
507
+ const worktreeDir = path.join(sandbox, 'feature-worktree');
508
+ try {
509
+ fs.mkdirSync(parentRepo, { recursive: true });
510
+ await git({ cwd: parentRepo, args: ['init', '-b', 'main'] });
511
+ await git({ cwd: parentRepo, args: ['config', 'user.email', 'kimaki-tests@example.com'] });
512
+ await git({ cwd: parentRepo, args: ['config', 'user.name', 'Kimaki Tests'] });
513
+ await git({
514
+ cwd: parentRepo,
515
+ args: ['config', 'core.hooksPath', path.join(parentRepo, '.git', 'hooks')],
516
+ });
517
+ fs.writeFileSync(path.join(parentRepo, 'README.md'), 'v1\n', 'utf-8');
518
+ await git({ cwd: parentRepo, args: ['add', 'README.md'] });
519
+ await git({ cwd: parentRepo, args: ['commit', '-m', 'init'] });
520
+ await git({ cwd: parentRepo, args: ['worktree', 'add', '-b', 'feature', worktreeDir] });
521
+ fs.writeFileSync(path.join(worktreeDir, 'feature.md'), 'feature\n', 'utf-8');
522
+ await git({ cwd: worktreeDir, args: ['add', 'feature.md'] });
523
+ await git({ cwd: worktreeDir, args: ['commit', '-m', 'feature'] });
524
+ const hookPath = path.join(parentRepo, '.git', 'hooks', 'pre-receive');
525
+ fs.writeFileSync(hookPath, '#!/bin/sh\ncat >/dev/null\necho "hook rejected the push" >&2\nexit 1\n', 'utf-8');
526
+ fs.chmodSync(hookPath, 0o755);
527
+ const result = await mergeWorktree({
528
+ worktreeDir,
529
+ mainRepoDir: parentRepo,
530
+ worktreeName: 'feature',
531
+ targetBranch: 'main',
532
+ });
533
+ expect(result).toBeInstanceOf(PushError);
534
+ if (!(result instanceof PushError))
535
+ return;
536
+ const formatted = formatMergeWorktreeError(result);
537
+ expect(formatted.startsWith('Merge failed: Push to main failed')).toBe(true);
538
+ expect(formatted).toContain('hook rejected the push');
539
+ expect(formatted).toContain('exit 1');
540
+ expect(formatted).toContain('git -C ');
541
+ }
542
+ finally {
543
+ fs.rmSync(sandbox, { recursive: true, force: true });
544
+ }
545
+ });
546
+ test('mergeWorktree squash creates one target commit with the complete tree', async () => {
547
+ const sandbox = createTestRoot();
548
+ const parentRepo = path.join(sandbox, 'parent');
549
+ const worktreeDir = path.join(sandbox, 'feature-worktree');
550
+ try {
551
+ fs.mkdirSync(parentRepo, { recursive: true });
552
+ await git({ cwd: parentRepo, args: ['init', '-b', 'main'] });
553
+ await git({ cwd: parentRepo, args: ['config', 'user.email', 'kimaki-tests@example.com'] });
554
+ await git({ cwd: parentRepo, args: ['config', 'user.name', 'Kimaki Tests'] });
555
+ fs.writeFileSync(path.join(parentRepo, 'README.md'), 'base\n', 'utf-8');
556
+ await git({ cwd: parentRepo, args: ['add', 'README.md'] });
557
+ await git({ cwd: parentRepo, args: ['commit', '-m', 'init'] });
558
+ await git({ cwd: parentRepo, args: ['worktree', 'add', '-b', 'feature', worktreeDir] });
559
+ fs.writeFileSync(path.join(worktreeDir, 'first.md'), 'first\n', 'utf-8');
560
+ await git({ cwd: worktreeDir, args: ['add', 'first.md'] });
561
+ await git({ cwd: worktreeDir, args: ['commit', '-m', 'add first file'] });
562
+ fs.writeFileSync(path.join(worktreeDir, 'second.md'), 'second\n', 'utf-8');
563
+ await git({ cwd: worktreeDir, args: ['add', 'second.md'] });
564
+ await git({ cwd: worktreeDir, args: ['commit', '-m', 'add second file'] });
565
+ fs.writeFileSync(path.join(parentRepo, 'target.md'), 'target\n', 'utf-8');
566
+ await git({ cwd: parentRepo, args: ['add', 'target.md'] });
567
+ await git({ cwd: parentRepo, args: ['commit', '-m', 'advance target'] });
568
+ const targetBeforeMerge = await git({
569
+ cwd: parentRepo,
570
+ args: ['rev-parse', 'main'],
571
+ });
572
+ const result = await mergeWorktree({
573
+ worktreeDir,
574
+ mainRepoDir: parentRepo,
575
+ worktreeName: 'feature',
576
+ targetBranch: 'main',
577
+ strategy: 'squash',
578
+ });
579
+ if (result instanceof Error)
580
+ throw result;
581
+ const parent = await git({ cwd: parentRepo, args: ['rev-parse', 'main^'] });
582
+ expect({
583
+ sourceCommitCount: result.commitCount,
584
+ targetCommitCount: Number(await git({
585
+ cwd: parentRepo,
586
+ args: ['rev-list', '--count', `${targetBeforeMerge}..main`],
587
+ })),
588
+ parentMatchesTarget: parent === targetBeforeMerge,
589
+ subject: await git({ cwd: parentRepo, args: ['log', '-1', '--format=%s', 'main'] }),
590
+ files: (await git({ cwd: parentRepo, args: ['ls-tree', '-r', '--name-only', 'main'] })).split('\n'),
591
+ worktreeHead: await git({ cwd: worktreeDir, args: ['rev-parse', '--abbrev-ref', 'HEAD'] }),
592
+ featureBranchExists: await execAsync('git show-ref --verify --quiet refs/heads/feature', { cwd: parentRepo }).then(() => true, () => false),
593
+ }).toMatchInlineSnapshot(`
594
+ {
595
+ "featureBranchExists": false,
596
+ "files": [
597
+ "README.md",
598
+ "first.md",
599
+ "second.md",
600
+ "target.md",
601
+ ],
602
+ "parentMatchesTarget": true,
603
+ "sourceCommitCount": 2,
604
+ "subject": "Merge worktree feature",
605
+ "targetCommitCount": 1,
606
+ "worktreeHead": "HEAD",
607
+ }
608
+ `);
609
+ }
610
+ finally {
611
+ fs.rmSync(sandbox, { recursive: true, force: true });
612
+ }
613
+ });
614
+ test('mergeWorktree keeps reporting a paused rebase conflict on retry', async () => {
615
+ const sandbox = createTestRoot();
616
+ const parentRepo = path.join(sandbox, 'parent');
617
+ const worktreeDir = path.join(sandbox, 'feature-worktree');
618
+ try {
619
+ fs.mkdirSync(parentRepo, { recursive: true });
620
+ await git({ cwd: parentRepo, args: ['init', '-b', 'main'] });
621
+ await git({ cwd: parentRepo, args: ['config', 'user.email', 'kimaki-tests@example.com'] });
622
+ await git({ cwd: parentRepo, args: ['config', 'user.name', 'Kimaki Tests'] });
623
+ fs.writeFileSync(path.join(parentRepo, 'conflict.txt'), 'base\n', 'utf-8');
624
+ await git({ cwd: parentRepo, args: ['add', 'conflict.txt'] });
625
+ await git({ cwd: parentRepo, args: ['commit', '-m', 'init'] });
626
+ await git({ cwd: parentRepo, args: ['worktree', 'add', '-b', 'feature', worktreeDir] });
627
+ fs.writeFileSync(path.join(worktreeDir, 'conflict.txt'), 'feature\n', 'utf-8');
628
+ await git({ cwd: worktreeDir, args: ['commit', '-am', 'feature change'] });
629
+ fs.writeFileSync(path.join(parentRepo, 'conflict.txt'), 'target\n', 'utf-8');
630
+ await git({ cwd: parentRepo, args: ['commit', '-am', 'target change'] });
631
+ const first = await mergeWorktree({
632
+ worktreeDir,
633
+ mainRepoDir: parentRepo,
634
+ worktreeName: 'feature',
635
+ targetBranch: 'main',
636
+ strategy: 'squash',
637
+ });
638
+ const retry = await mergeWorktree({
639
+ worktreeDir,
640
+ mainRepoDir: parentRepo,
641
+ worktreeName: 'feature',
642
+ targetBranch: 'main',
643
+ strategy: 'squash',
644
+ });
645
+ expect({
646
+ first: first instanceof Error ? first.name : 'success',
647
+ retry: retry instanceof Error ? retry.name : 'success',
648
+ }).toMatchInlineSnapshot(`
649
+ {
650
+ "first": "RebaseConflictError",
651
+ "retry": "RebaseConflictError",
652
+ }
653
+ `);
654
+ expect(first).toBeInstanceOf(RebaseConflictError);
655
+ expect(retry).toBeInstanceOf(RebaseConflictError);
656
+ }
657
+ finally {
658
+ await execAsync('git rebase --abort', { cwd: worktreeDir }).catch(() => undefined);
659
+ fs.rmSync(sandbox, { recursive: true, force: true });
660
+ }
661
+ });
662
+ test('validateBranchRef does not execute shell syntax from a branch value', async () => {
663
+ const sandbox = createTestRoot();
664
+ const marker = path.join(sandbox, 'injected');
665
+ try {
666
+ await git({ cwd: sandbox, args: ['init', '-b', 'main'] });
667
+ const result = await validateBranchRef({
668
+ directory: sandbox,
669
+ ref: `feature/$(touch ${marker})`,
670
+ });
671
+ expect({
672
+ rejected: result instanceof Error,
673
+ commandExecuted: fs.existsSync(marker),
674
+ }).toMatchInlineSnapshot(`
675
+ {
676
+ "commandExecuted": false,
677
+ "rejected": true,
678
+ }
679
+ `);
680
+ }
681
+ finally {
682
+ fs.rmSync(sandbox, { recursive: true, force: true });
683
+ }
684
+ });
308
685
  test('shortenWorktreeSlug leaves short slugs alone', () => {
309
686
  expect(shortenWorktreeSlug('short-name')).toMatchInlineSnapshot(`"short-name"`);
310
687
  expect(shortenWorktreeSlug('exactly-twenty-chars')).toMatchInlineSnapshot(`"exactly-twenty-chars"`);
@@ -1,3 +1,4 @@
1
+ // LEGACY rotation, superseded by @subrouter/opencode. See oauth-rotation-shared.ts.
1
2
  /**
2
3
  * xAI OAuth rotation plugin for OpenCode.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // LEGACY rotation, superseded by @subrouter/opencode. See oauth-rotation-shared.ts.
1
2
  /**
2
3
  * xAI OAuth account store and rotation.
3
4
  * Mirrors openai-auth-state.ts but for xAI/Grok OAuth accounts.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "kimaki",
3
3
  "module": "index.ts",
4
4
  "type": "module",
5
- "version": "0.26.0",
5
+ "version": "0.28.0",
6
6
  "repository": "https://github.com/remorses/kimaki",
7
7
  "bin": "bin.js",
8
8
  "exports": {
@@ -31,10 +31,10 @@
31
31
  "lintcn": "^0.7.1",
32
32
  "tsx": "^4.20.5",
33
33
  "undici": "^8.0.2",
34
- "discord-digital-twin": "^0.1.1",
35
- "db": "^0.0.0",
34
+ "discord-digital-twin": "^0.1.2",
35
+ "opencode-deterministic-provider": "^0.0.1",
36
36
  "opencode-cached-provider": "^0.0.1",
37
- "opencode-deterministic-provider": "^0.0.1"
37
+ "db": "^0.0.0"
38
38
  },
39
39
  "dependencies": {
40
40
  "@ai-sdk/google": "^3.0.53",
@@ -70,10 +70,12 @@
70
70
  "yaml": "^2.8.3",
71
71
  "zod": "^4.3.6",
72
72
  "zustand": "^5.0.11",
73
+ "@subrouter/cli": "^0.6.0",
74
+ "errore": "^0.14.1",
73
75
  "opencode-injection-guard": "^0.2.1",
74
76
  "libsqlproxy": "^0.1.0",
75
- "errore": "^0.14.1",
76
- "traforo": "^0.7.2"
77
+ "traforo": "^0.7.2",
78
+ "@subrouter/opencode": "^0.5.0"
77
79
  },
78
80
  "optionalDependencies": {
79
81
  "@snazzah/davey": "^0.1.10",