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.
- package/dist/agent-model.e2e.test.js +250 -164
- package/dist/anthropic-account-identity.js +9 -1
- package/dist/anthropic-auth-plugin.js +10 -13
- package/dist/anthropic-auth-plugin.test.js +17 -0
- package/dist/anthropic-auth-state.js +1 -0
- package/dist/bash-tool-schema-plugin.js +32 -0
- package/dist/bash-tool-schema-plugin.test.js +49 -0
- package/dist/bin.js +73 -19
- package/dist/cli-commands/maintenance.js +46 -7
- package/dist/cli-commands/project.js +11 -27
- package/dist/cli-commands/send.js +30 -1
- package/dist/cli-commands/session.js +322 -90
- package/dist/cli-commands/task.js +34 -15
- package/dist/cli-parsing.test.js +83 -4
- package/dist/cli-runner.js +44 -8
- package/dist/cli-runner.test.js +27 -0
- package/dist/cli-send-thread.e2e.test.js +9 -9
- package/dist/cli.js +32 -0
- package/dist/commands/abort.js +5 -1
- package/dist/commands/action-buttons.js +2 -1
- package/dist/commands/add-project.js +4 -7
- package/dist/commands/agent.js +164 -32
- package/dist/commands/ask-question.js +23 -33
- package/dist/commands/ask-question.test.js +43 -1
- package/dist/commands/btw.js +28 -6
- package/dist/commands/file-upload.js +2 -1
- package/dist/commands/fork-subagent.js +2 -4
- package/dist/commands/fork.js +4 -4
- package/dist/commands/last-sessions.js +17 -10
- package/dist/commands/login.js +6 -0
- package/dist/commands/merge-worktree.js +47 -14
- package/dist/commands/model-variant.js +8 -1
- package/dist/commands/model.js +16 -9
- package/dist/commands/multioauth.js +1 -0
- package/dist/commands/new-worktree.js +170 -102
- package/dist/commands/queue.js +3 -2
- package/dist/commands/resume.js +4 -4
- package/dist/commands/session.js +78 -63
- package/dist/commands/user-command.js +6 -8
- package/dist/commands/worktrees.js +5 -13
- package/dist/config.js +16 -1
- package/dist/cpu-profiler.js +211 -0
- package/dist/cpu-profiler.test.js +160 -0
- package/dist/database.js +152 -3
- package/dist/db.js +33 -0
- package/dist/db.test.js +267 -1
- package/dist/default-channel-provisioning.test.js +148 -0
- package/dist/discord-bot.js +517 -488
- package/dist/discord-command-registration.js +25 -10
- package/dist/discord-command-registration.test.js +17 -0
- package/dist/discord-utils.js +97 -5
- package/dist/discord-utils.test.js +151 -1
- package/dist/errors.js +10 -0
- package/dist/exec-async.js +17 -5
- package/dist/external-directory-permissions.e2e.test.js +11 -7
- package/dist/external-directory-project-deny.e2e.test.js +5 -3
- package/dist/external-opencode-sync.js +94 -17
- package/dist/external-opencode-sync.test.js +206 -0
- package/dist/extract-sdk-error-message.test.js +13 -0
- package/dist/file-edit-log.js +197 -0
- package/dist/file-edit-log.test.js +258 -0
- package/dist/format-tables.js +52 -0
- package/dist/format-tables.test.js +93 -0
- package/dist/gateway-proxy-reconnect.e2e.test.js +28 -8
- package/dist/gateway-proxy.e2e.test.js +18 -18
- package/dist/git-worktree-core.js +182 -17
- package/dist/interaction-handler.js +416 -398
- package/dist/ipc-tools-plugin.js +68 -3
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +122 -58
- package/dist/kimaki-opencode-plugin.js +9 -3
- package/dist/kimaki-workspace-adaptor.js +33 -10
- package/dist/logger.js +11 -1
- package/dist/markdown.js +3 -1
- package/dist/markdown.test.js +11 -3
- package/dist/message-formatting.js +181 -51
- package/dist/message-formatting.test.js +529 -22
- package/dist/message-preprocessing.js +105 -1
- package/dist/oauth-rotation-shared.js +10 -0
- package/dist/openai-auth-plugin.js +1 -0
- package/dist/openai-auth-state.js +1 -0
- package/dist/opencode-command.js +28 -0
- package/dist/opencode-command.test.js +109 -1
- package/dist/opencode-interrupt-plugin.js +4 -0
- package/dist/opencode-interrupt-plugin.test.js +24 -0
- package/dist/opencode.js +82 -15
- package/dist/project-add.test.js +62 -0
- package/dist/question-abort-clears-dropdown.e2e.test.js +89 -0
- package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +150 -0
- package/dist/question-text-before-dropdown.e2e.test.js +52 -0
- package/dist/queue-advanced-abort.e2e.test.js +8 -10
- package/dist/queue-advanced-action-buttons.e2e.test.js +8 -8
- package/dist/queue-advanced-e2e-setup.js +78 -5
- package/dist/queue-advanced-footer.e2e.test.js +60 -269
- package/dist/queue-advanced-model-switch.e2e.test.js +9 -11
- package/dist/queue-advanced-permissions-typing.e2e.test.js +15 -15
- package/dist/queue-advanced-question.e2e.test.js +32 -27
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +10 -12
- package/dist/queue-advanced-typing.e2e.test.js +7 -9
- package/dist/queue-delete-message.e2e.test.js +3 -3
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +5 -5
- package/dist/queue-interrupt-drain.e2e.test.js +122 -12
- package/dist/queue-question-select-drain.e2e.test.js +34 -132
- package/dist/runtime-lifecycle.e2e.test.js +21 -51
- package/dist/schema.js +35 -1
- package/dist/session-handler/event-stream-state.js +391 -50
- package/dist/session-handler/event-stream-state.test.js +736 -8
- package/dist/session-handler/model-utils.js +172 -5
- package/dist/session-handler/model-utils.test.js +445 -0
- package/dist/session-handler/opencode-session-event-log.js +30 -1
- package/dist/session-handler/thread-ingress.test.js +41 -0
- package/dist/session-handler/thread-runtime-state.js +2 -0
- package/dist/session-handler/thread-session-runtime-summary.test.js +121 -0
- package/dist/session-handler/thread-session-runtime.js +574 -299
- package/dist/session-search.js +123 -4
- package/dist/session-search.test.js +248 -1
- package/dist/session-sleep.test.js +224 -0
- package/dist/session-title-rename.test.js +37 -39
- package/dist/sleep.e2e.test.js +494 -0
- package/dist/startup-time.e2e.test.js +2 -1
- package/dist/store.js +3 -0
- package/dist/system-message.js +102 -16
- package/dist/system-message.test.js +192 -15
- package/dist/task-runner.js +96 -5
- package/dist/task-schedule.js +87 -1
- package/dist/task-schedule.test.js +149 -1
- package/dist/test-utils.js +17 -9
- package/dist/thinking-utils.js +10 -0
- package/dist/thinking-utils.test.js +35 -0
- package/dist/thread-message-queue.e2e.test.js +65 -213
- package/dist/undo-redo.e2e.test.js +17 -9
- package/dist/voice-attachment.js +23 -1
- package/dist/voice-handler.js +21 -4
- package/dist/voice-message.e2e.test.js +144 -30
- package/dist/voice-routing-input.e2e.test.js +195 -0
- package/dist/voice-routing-question.e2e.test.js +125 -0
- package/dist/voice.js +28 -8
- package/dist/voice.test.js +96 -30
- package/dist/worktree-clone-isolation.e2e.test.js +188 -0
- package/dist/worktree-lifecycle.e2e.test.js +55 -36
- package/dist/worktrees.js +226 -443
- package/dist/worktrees.test.js +435 -58
- package/dist/xai-auth-plugin.js +1 -0
- package/dist/xai-auth-state.js +1 -0
- package/package.json +8 -6
- package/skills/bump-holocron-consumers/SKILL.md +137 -0
- package/skills/critique/SKILL.md +4 -4
- package/skills/errore/SKILL.md +60 -218
- package/skills/goke/SKILL.md +150 -10
- package/skills/holocron/SKILL.md +154 -17
- package/skills/npm-package/SKILL.md +55 -35
- package/skills/profano/SKILL.md +17 -2
- package/skills/spiceflow/SKILL.md +39 -3
- package/skills/strada/SKILL.md +39 -0
- package/skills/zele/SKILL.md +19 -0
- package/src/agent-model.e2e.test.ts +287 -186
- package/src/anthropic-account-identity.ts +20 -1
- package/src/anthropic-auth-plugin.test.ts +19 -0
- package/src/anthropic-auth-plugin.ts +12 -16
- package/src/anthropic-auth-state.ts +1 -0
- package/src/bash-tool-schema-plugin.test.ts +55 -0
- package/src/bash-tool-schema-plugin.ts +46 -0
- package/src/bin.ts +78 -26
- package/src/cli-commands/maintenance.ts +59 -13
- package/src/cli-commands/project.ts +14 -35
- package/src/cli-commands/send.ts +31 -1
- package/src/cli-commands/session.ts +374 -114
- package/src/cli-commands/task.ts +34 -15
- package/src/cli-parsing.test.ts +103 -4
- package/src/cli-runner.test.ts +49 -0
- package/src/cli-runner.ts +44 -7
- package/src/cli-send-thread.e2e.test.ts +9 -11
- package/src/cli.ts +53 -0
- package/src/commands/abort.ts +6 -1
- package/src/commands/action-buttons.ts +2 -1
- package/src/commands/add-project.ts +4 -8
- package/src/commands/agent.ts +205 -29
- package/src/commands/ask-question.test.ts +48 -1
- package/src/commands/ask-question.ts +23 -33
- package/src/commands/btw.ts +40 -5
- package/src/commands/file-upload.ts +2 -1
- package/src/commands/fork-subagent.ts +2 -3
- package/src/commands/fork.ts +4 -3
- package/src/commands/last-sessions.ts +10 -4
- package/src/commands/login.ts +6 -0
- package/src/commands/merge-worktree.ts +54 -16
- package/src/commands/model-variant.ts +9 -1
- package/src/commands/model.ts +20 -9
- package/src/commands/multioauth.ts +1 -0
- package/src/commands/new-worktree.ts +138 -55
- package/src/commands/queue.ts +3 -2
- package/src/commands/resume.ts +4 -3
- package/src/commands/session.ts +94 -70
- package/src/commands/user-command.ts +6 -6
- package/src/commands/worktrees.ts +5 -12
- package/src/config.ts +18 -1
- package/src/cpu-profiler.test.ts +183 -0
- package/src/cpu-profiler.ts +293 -0
- package/src/database.ts +235 -3
- package/src/db.test.ts +293 -0
- package/src/db.ts +38 -0
- package/src/default-channel-provisioning.test.ts +155 -0
- package/src/discord-bot.ts +78 -36
- package/src/discord-command-registration.test.ts +20 -0
- package/src/discord-command-registration.ts +42 -12
- package/src/discord-utils.test.ts +176 -0
- package/src/discord-utils.ts +152 -7
- package/src/errors.ts +12 -0
- package/src/exec-async.ts +20 -11
- package/src/external-directory-permissions.e2e.test.ts +11 -7
- package/src/external-directory-project-deny.e2e.test.ts +5 -3
- package/src/external-opencode-sync.test.ts +248 -0
- package/src/external-opencode-sync.ts +122 -19
- package/src/extract-sdk-error-message.test.ts +18 -0
- package/src/file-edit-log.test.ts +287 -0
- package/src/file-edit-log.ts +283 -0
- package/src/format-tables.test.ts +101 -0
- package/src/format-tables.ts +75 -0
- package/src/gateway-proxy-reconnect.e2e.test.ts +38 -8
- package/src/gateway-proxy.e2e.test.ts +18 -17
- package/src/git-worktree-core.ts +247 -20
- package/src/interaction-handler.ts +36 -13
- package/src/ipc-tools-plugin.ts +82 -5
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +188 -97
- package/src/kimaki-opencode-plugin.ts +9 -3
- package/src/kimaki-workspace-adaptor.ts +40 -11
- package/src/logger.ts +14 -4
- package/src/markdown.test.ts +13 -3
- package/src/markdown.ts +4 -1
- package/src/message-formatting.test.ts +672 -25
- package/src/message-formatting.ts +242 -53
- package/src/message-preprocessing.ts +116 -1
- package/src/oauth-rotation-shared.ts +10 -0
- package/src/openai-auth-plugin.ts +1 -0
- package/src/openai-auth-state.ts +1 -0
- package/src/opencode-command.test.ts +140 -0
- package/src/opencode-command.ts +38 -0
- package/src/opencode-interrupt-plugin.test.ts +32 -0
- package/src/opencode-interrupt-plugin.ts +3 -0
- package/src/opencode.ts +115 -15
- package/src/project-add.test.ts +80 -0
- package/src/question-abort-clears-dropdown.e2e.test.ts +109 -0
- package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +172 -0
- package/src/question-text-before-dropdown.e2e.test.ts +67 -0
- package/src/queue-advanced-abort.e2e.test.ts +8 -9
- package/src/queue-advanced-action-buttons.e2e.test.ts +8 -8
- package/src/queue-advanced-e2e-setup.ts +83 -4
- package/src/queue-advanced-footer.e2e.test.ts +67 -315
- package/src/queue-advanced-model-switch.e2e.test.ts +9 -10
- package/src/queue-advanced-permissions-typing.e2e.test.ts +15 -15
- package/src/queue-advanced-question.e2e.test.ts +36 -30
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +11 -11
- package/src/queue-advanced-typing.e2e.test.ts +8 -8
- package/src/queue-delete-message.e2e.test.ts +3 -3
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +5 -5
- package/src/queue-interrupt-drain.e2e.test.ts +133 -12
- package/src/queue-question-select-drain.e2e.test.ts +36 -161
- package/src/runtime-lifecycle.e2e.test.ts +21 -54
- package/src/schema.sql +15 -2
- package/src/schema.ts +37 -1
- package/src/session-handler/event-stream-state.test.ts +790 -7
- package/src/session-handler/event-stream-state.ts +568 -53
- package/src/session-handler/model-utils.test.ts +537 -0
- package/src/session-handler/model-utils.ts +281 -5
- package/src/session-handler/opencode-session-event-log.ts +31 -1
- package/src/session-handler/thread-ingress.test.ts +54 -0
- package/src/session-handler/thread-runtime-state.ts +3 -0
- package/src/session-handler/thread-session-runtime-summary.test.ts +169 -0
- package/src/session-handler/thread-session-runtime.ts +712 -358
- package/src/session-search.test.ts +281 -0
- package/src/session-search.ts +241 -7
- package/src/session-sleep.test.ts +294 -0
- package/src/session-title-rename.test.ts +54 -48
- package/src/sleep.e2e.test.ts +565 -0
- package/src/startup-time.e2e.test.ts +5 -1
- package/src/store.ts +22 -0
- package/src/system-message.test.ts +226 -15
- package/src/system-message.ts +130 -16
- package/src/task-runner.ts +138 -3
- package/src/task-schedule.test.ts +170 -0
- package/src/task-schedule.ts +141 -1
- package/src/test-utils.ts +21 -10
- package/src/thinking-utils.test.ts +46 -0
- package/src/thinking-utils.ts +21 -0
- package/src/thread-message-queue.e2e.test.ts +65 -245
- package/src/undo-redo.e2e.test.ts +17 -9
- package/src/voice-attachment.ts +29 -1
- package/src/voice-handler.ts +23 -4
- package/src/voice-message.e2e.test.ts +149 -29
- package/src/voice-routing-input.e2e.test.ts +203 -0
- package/src/voice-routing-question.e2e.test.ts +131 -0
- package/src/voice.test.ts +107 -29
- package/src/voice.ts +34 -6
- package/src/worktree-clone-isolation.e2e.test.ts +213 -0
- package/src/worktree-lifecycle.e2e.test.ts +55 -35
- package/src/worktrees.test.ts +505 -64
- package/src/worktrees.ts +257 -603
- package/src/xai-auth-plugin.ts +1 -0
- package/src/xai-auth-state.ts +1 -0
- package/dist/message-finish-field.e2e.test.js +0 -169
- package/dist/subagent-rate-limit-plugin.js +0 -184
- package/src/message-finish-field.e2e.test.ts +0 -196
- package/src/subagent-rate-limit-plugin.ts +0 -228
package/dist/worktrees.test.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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('
|
|
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
|
|
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('
|
|
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
|
|
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"`);
|
package/dist/xai-auth-plugin.js
CHANGED
package/dist/xai-auth-state.js
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "kimaki",
|
|
3
3
|
"module": "index.ts",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "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.
|
|
35
|
-
"
|
|
34
|
+
"discord-digital-twin": "^0.1.2",
|
|
35
|
+
"opencode-deterministic-provider": "^0.0.1",
|
|
36
36
|
"opencode-cached-provider": "^0.0.1",
|
|
37
|
-
"
|
|
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
|
-
"
|
|
76
|
-
"
|
|
77
|
+
"traforo": "^0.7.2",
|
|
78
|
+
"@subrouter/opencode": "^0.5.0"
|
|
77
79
|
},
|
|
78
80
|
"optionalDependencies": {
|
|
79
81
|
"@snazzah/davey": "^0.1.10",
|