@otto-assistant/bridge 0.4.102 → 0.4.103
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 +1 -0
- package/dist/anthropic-auth-plugin.js +22 -1
- package/dist/anthropic-auth-state.js +31 -0
- package/dist/btw-prefix-detection.js +17 -0
- package/dist/btw-prefix-detection.test.js +63 -0
- package/dist/cli.js +101 -15
- package/dist/commands/agent.js +21 -2
- package/dist/commands/ask-question.js +50 -4
- package/dist/commands/ask-question.test.js +92 -0
- package/dist/commands/btw.js +71 -66
- package/dist/commands/new-worktree.js +92 -35
- package/dist/commands/queue.js +17 -0
- package/dist/commands/worktrees.js +196 -139
- package/dist/context-awareness-plugin.js +16 -8
- package/dist/context-awareness-plugin.test.js +4 -2
- package/dist/discord-bot.js +35 -2
- package/dist/discord-command-registration.js +9 -2
- package/dist/memory-overview-plugin.js +3 -1
- package/dist/opencode.js +9 -0
- package/dist/queue-question-select-drain.e2e.test.js +135 -10
- package/dist/session-handler/thread-runtime-state.js +27 -0
- package/dist/session-handler/thread-session-runtime.js +58 -28
- package/dist/session-title-rename.test.js +12 -0
- package/dist/skill-filter.js +31 -0
- package/dist/skill-filter.test.js +65 -0
- package/dist/store.js +2 -0
- package/dist/system-message.js +12 -3
- package/dist/system-message.test.js +10 -6
- package/dist/thread-message-queue.e2e.test.js +109 -0
- package/dist/worktree-lifecycle.e2e.test.js +4 -1
- package/dist/worktrees.js +106 -12
- package/dist/worktrees.test.js +232 -6
- package/package.json +2 -2
- package/skills/goke/SKILL.md +13 -619
- package/skills/new-skill/SKILL.md +34 -10
- package/skills/npm-package/SKILL.md +336 -2
- package/skills/profano/SKILL.md +24 -0
- package/skills/zele/SKILL.md +50 -21
- package/src/agent-model.e2e.test.ts +1 -0
- package/src/anthropic-auth-plugin.ts +24 -4
- package/src/anthropic-auth-state.ts +45 -0
- package/src/btw-prefix-detection.test.ts +73 -0
- package/src/btw-prefix-detection.ts +23 -0
- package/src/cli.ts +138 -46
- package/src/commands/agent.ts +24 -2
- package/src/commands/ask-question.test.ts +111 -0
- package/src/commands/ask-question.ts +69 -4
- package/src/commands/btw.ts +105 -85
- package/src/commands/new-worktree.ts +107 -40
- package/src/commands/queue.ts +22 -0
- package/src/commands/worktrees.ts +246 -154
- package/src/context-awareness-plugin.test.ts +4 -2
- package/src/context-awareness-plugin.ts +16 -8
- package/src/discord-bot.ts +40 -2
- package/src/discord-command-registration.ts +12 -2
- package/src/memory-overview-plugin.ts +3 -1
- package/src/opencode.ts +9 -0
- package/src/queue-question-select-drain.e2e.test.ts +174 -10
- package/src/session-handler/thread-runtime-state.ts +36 -1
- package/src/session-handler/thread-session-runtime.ts +72 -32
- package/src/session-title-rename.test.ts +18 -0
- package/src/skill-filter.test.ts +83 -0
- package/src/skill-filter.ts +42 -0
- package/src/store.ts +17 -0
- package/src/system-message.test.ts +10 -6
- package/src/system-message.ts +12 -3
- package/src/thread-message-queue.e2e.test.ts +126 -0
- package/src/worktree-lifecycle.e2e.test.ts +6 -1
- package/src/worktrees.test.ts +274 -9
- package/src/worktrees.ts +144 -23
package/src/worktrees.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
import crypto from 'node:crypto'
|
|
6
6
|
import fs from 'node:fs'
|
|
7
|
-
import os from 'node:os'
|
|
8
7
|
import path from 'node:path'
|
|
9
8
|
import * as errore from 'errore'
|
|
9
|
+
import { getDataDir } from './config.js'
|
|
10
10
|
import { execAsync } from './exec-async.js'
|
|
11
11
|
import { createLogger, LogPrefix } from './logger.js'
|
|
12
12
|
|
|
@@ -530,24 +530,38 @@ async function resolveDefaultWorktreeTarget(
|
|
|
530
530
|
return 'HEAD'
|
|
531
531
|
}
|
|
532
532
|
|
|
533
|
-
|
|
533
|
+
/**
|
|
534
|
+
* Build the on-disk directory for a managed worktree.
|
|
535
|
+
*
|
|
536
|
+
* Layout: `<kimakiDataDir>/worktrees/<8charProjectHash>/<basename>`
|
|
537
|
+
*
|
|
538
|
+
* - Lives under the kimaki data dir instead of the long
|
|
539
|
+
* `~/.local/share/opencode/worktree/<40-char-hash>/<name>` path so folder
|
|
540
|
+
* names stay short and readable (agents tend to give up and reuse the old
|
|
541
|
+
* worktree when paths get absurdly long).
|
|
542
|
+
* - The 8-char project hash keeps worktrees from different projects that
|
|
543
|
+
* happen to share a slug from colliding.
|
|
544
|
+
* - Strips the `opencode/kimaki-` (or `opencode-kimaki-`) prefix from the
|
|
545
|
+
* folder name since it's redundant noise on disk. The git branch name
|
|
546
|
+
* itself still uses `opencode/kimaki-<slug>` so merge/cleanup logic is
|
|
547
|
+
* unchanged.
|
|
548
|
+
*/
|
|
549
|
+
export function getManagedWorktreeDirectory({
|
|
534
550
|
directory,
|
|
535
551
|
name,
|
|
536
552
|
}: {
|
|
537
553
|
directory: string
|
|
538
554
|
name: string
|
|
539
555
|
}): string {
|
|
540
|
-
const projectHash = crypto
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
'
|
|
548
|
-
|
|
549
|
-
safeName,
|
|
550
|
-
)
|
|
556
|
+
const projectHash = crypto
|
|
557
|
+
.createHash('sha1')
|
|
558
|
+
.update(directory)
|
|
559
|
+
.digest('hex')
|
|
560
|
+
.slice(0, 8)
|
|
561
|
+
const withoutPrefix = name
|
|
562
|
+
.replace(/^opencode\/kimaki-/, '')
|
|
563
|
+
.replaceAll('/', '-')
|
|
564
|
+
return path.join(getDataDir(), 'worktrees', projectHash, withoutPrefix)
|
|
551
565
|
}
|
|
552
566
|
|
|
553
567
|
/**
|
|
@@ -724,6 +738,8 @@ export async function deleteWorktree({
|
|
|
724
738
|
}: {
|
|
725
739
|
projectDirectory: string
|
|
726
740
|
worktreeDirectory: string
|
|
741
|
+
// Branch name to delete after removing the worktree.
|
|
742
|
+
// Pass empty string for detached HEAD worktrees — branch deletion is skipped.
|
|
727
743
|
worktreeName: string
|
|
728
744
|
}): Promise<void | Error> {
|
|
729
745
|
let removeResult = await git(
|
|
@@ -749,24 +765,27 @@ export async function deleteWorktree({
|
|
|
749
765
|
}
|
|
750
766
|
}
|
|
751
767
|
if (removeResult instanceof Error) {
|
|
752
|
-
return new Error(`Failed to remove worktree ${worktreeName}`, {
|
|
768
|
+
return new Error(`Failed to remove worktree ${worktreeName || worktreeDirectory}`, {
|
|
753
769
|
cause: removeResult,
|
|
754
770
|
})
|
|
755
771
|
}
|
|
756
772
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
773
|
+
// Skip branch deletion for detached HEAD worktrees (no branch to delete)
|
|
774
|
+
if (worktreeName) {
|
|
775
|
+
const deleteBranchResult = await git(
|
|
776
|
+
projectDirectory,
|
|
777
|
+
`branch -d ${JSON.stringify(worktreeName)}`,
|
|
778
|
+
)
|
|
779
|
+
if (deleteBranchResult instanceof Error) {
|
|
780
|
+
return new Error(`Failed to delete branch ${worktreeName}`, {
|
|
781
|
+
cause: deleteBranchResult,
|
|
782
|
+
})
|
|
783
|
+
}
|
|
765
784
|
}
|
|
766
785
|
|
|
767
786
|
const pruneResult = await git(projectDirectory, 'worktree prune')
|
|
768
787
|
if (pruneResult instanceof Error) {
|
|
769
|
-
logger.warn(`Failed to prune worktrees after deleting ${worktreeName}`)
|
|
788
|
+
logger.warn(`Failed to prune worktrees after deleting ${worktreeName || worktreeDirectory}`)
|
|
770
789
|
}
|
|
771
790
|
}
|
|
772
791
|
|
|
@@ -1246,3 +1265,105 @@ export async function validateWorktreeDirectory({
|
|
|
1246
1265
|
|
|
1247
1266
|
return absoluteCandidate
|
|
1248
1267
|
}
|
|
1268
|
+
|
|
1269
|
+
// Parsed entry from `git worktree list --porcelain`.
|
|
1270
|
+
// Represents any worktree (kimaki, opencode, manual) visible to git.
|
|
1271
|
+
export type GitWorktree = {
|
|
1272
|
+
directory: string
|
|
1273
|
+
branch: string | null // null for detached HEAD
|
|
1274
|
+
head: string
|
|
1275
|
+
detached: boolean
|
|
1276
|
+
locked: boolean
|
|
1277
|
+
prunable: boolean
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
type PartialGitWorktree = {
|
|
1281
|
+
directory?: string
|
|
1282
|
+
branch?: string | null
|
|
1283
|
+
head?: string
|
|
1284
|
+
detached?: boolean
|
|
1285
|
+
locked?: boolean
|
|
1286
|
+
prunable?: boolean
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
function flushGitWorktreeEntry(current: PartialGitWorktree): GitWorktree | null {
|
|
1290
|
+
if (!current.directory) {
|
|
1291
|
+
return null
|
|
1292
|
+
}
|
|
1293
|
+
return {
|
|
1294
|
+
directory: current.directory,
|
|
1295
|
+
branch: current.branch ?? null,
|
|
1296
|
+
head: current.head ?? '',
|
|
1297
|
+
detached: current.detached ?? false,
|
|
1298
|
+
locked: current.locked ?? false,
|
|
1299
|
+
prunable: current.prunable ?? false,
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// Parse `git worktree list --porcelain` output into structured entries.
|
|
1304
|
+
// Skips the first entry (the main checkout) since that's the project root.
|
|
1305
|
+
export function parseGitWorktreeListPorcelain(
|
|
1306
|
+
output: string,
|
|
1307
|
+
): GitWorktree[] {
|
|
1308
|
+
const entries: GitWorktree[] = []
|
|
1309
|
+
let current: PartialGitWorktree = {}
|
|
1310
|
+
|
|
1311
|
+
for (const line of output.split('\n')) {
|
|
1312
|
+
if (line.startsWith('worktree ')) {
|
|
1313
|
+
const flushed = flushGitWorktreeEntry(current)
|
|
1314
|
+
if (flushed) {
|
|
1315
|
+
entries.push(flushed)
|
|
1316
|
+
}
|
|
1317
|
+
current = { directory: line.slice('worktree '.length) }
|
|
1318
|
+
continue
|
|
1319
|
+
}
|
|
1320
|
+
if (line.startsWith('HEAD ')) {
|
|
1321
|
+
current.head = line.slice('HEAD '.length)
|
|
1322
|
+
continue
|
|
1323
|
+
}
|
|
1324
|
+
if (line.startsWith('branch ')) {
|
|
1325
|
+
// "branch refs/heads/opencode/kimaki-foo" → "opencode/kimaki-foo"
|
|
1326
|
+
current.branch = line.slice('branch '.length).replace(/^refs\/heads\//, '')
|
|
1327
|
+
continue
|
|
1328
|
+
}
|
|
1329
|
+
if (line === 'detached') {
|
|
1330
|
+
current.detached = true
|
|
1331
|
+
continue
|
|
1332
|
+
}
|
|
1333
|
+
// "locked" or "locked <reason>"
|
|
1334
|
+
if (line === 'locked' || line.startsWith('locked ')) {
|
|
1335
|
+
current.locked = true
|
|
1336
|
+
continue
|
|
1337
|
+
}
|
|
1338
|
+
if (line.startsWith('prunable')) {
|
|
1339
|
+
current.prunable = true
|
|
1340
|
+
continue
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
// Flush last entry
|
|
1344
|
+
const flushed = flushGitWorktreeEntry(current)
|
|
1345
|
+
if (flushed) {
|
|
1346
|
+
entries.push(flushed)
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// Skip the first entry — it's the main checkout (project root)
|
|
1350
|
+
return entries.slice(1)
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// List all git worktrees for a project directory (excluding the main checkout).
|
|
1354
|
+
// Returns Error on git failure, empty array if no worktrees exist.
|
|
1355
|
+
export async function listGitWorktrees({
|
|
1356
|
+
projectDirectory,
|
|
1357
|
+
timeout,
|
|
1358
|
+
}: {
|
|
1359
|
+
projectDirectory: string
|
|
1360
|
+
timeout?: number
|
|
1361
|
+
}): Promise<GitWorktree[] | Error> {
|
|
1362
|
+
const result = await git(projectDirectory, 'worktree list --porcelain', {
|
|
1363
|
+
timeout,
|
|
1364
|
+
})
|
|
1365
|
+
if (result instanceof Error) {
|
|
1366
|
+
return result
|
|
1367
|
+
}
|
|
1368
|
+
return parseGitWorktreeListPorcelain(result)
|
|
1369
|
+
}
|