dsh-git-worktree 0.1.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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/README.zh.md +37 -0
  4. package/lib/adapters/files.d.ts +10 -0
  5. package/lib/adapters/files.js +101 -0
  6. package/lib/adapters/files.js.map +1 -0
  7. package/lib/adapters/git.d.ts +18 -0
  8. package/lib/adapters/git.js +172 -0
  9. package/lib/adapters/git.js.map +1 -0
  10. package/lib/adapters/lookup.d.ts +28 -0
  11. package/lib/adapters/lookup.js +41 -0
  12. package/lib/adapters/lookup.js.map +1 -0
  13. package/lib/adapters/registry.d.ts +15 -0
  14. package/lib/adapters/registry.js +242 -0
  15. package/lib/adapters/registry.js.map +1 -0
  16. package/lib/adapters/safe-file.d.ts +19 -0
  17. package/lib/adapters/safe-file.js +94 -0
  18. package/lib/adapters/safe-file.js.map +1 -0
  19. package/lib/commands.d.ts +12 -0
  20. package/lib/commands.js +121 -0
  21. package/lib/commands.js.map +1 -0
  22. package/lib/index.d.ts +76 -0
  23. package/lib/index.js +93 -0
  24. package/lib/index.js.map +1 -0
  25. package/lib/managed-worktree-path.d.ts +27 -0
  26. package/lib/managed-worktree-path.js +53 -0
  27. package/lib/managed-worktree-path.js.map +1 -0
  28. package/lib/ports.d.ts +204 -0
  29. package/lib/ports.js +10 -0
  30. package/lib/ports.js.map +1 -0
  31. package/lib/session-checkout-apply.d.ts +110 -0
  32. package/lib/session-checkout-apply.js +753 -0
  33. package/lib/session-checkout-apply.js.map +1 -0
  34. package/lib/session-checkout-module.d.ts +3 -0
  35. package/lib/session-checkout-module.js +2122 -0
  36. package/lib/session-checkout-module.js.map +1 -0
  37. package/lib/tools.d.ts +12 -0
  38. package/lib/tools.js +384 -0
  39. package/lib/tools.js.map +1 -0
  40. package/lib/types.d.ts +275 -0
  41. package/lib/types.js +40 -0
  42. package/lib/types.js.map +1 -0
  43. package/package.json +60 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 wloops
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # dsh-git-worktree
2
+
3
+ Domi-grade git worktree isolation and delivery for DeepSeek Harness: permanent worktrees, a ready-for-review / apply / finish / discard lifecycle, conflict handling, and safe cleanup.
4
+
5
+ > Ported from [Domi](https://github.com/wloops/domi)'s production worktree system (Session Target / Isolated Checkout), with the Local-preview surface removed to match the DSH sandbox model.
6
+
7
+ ## What you get
8
+
9
+ | Capability | Description |
10
+ | --- | --- |
11
+ | `worktree_create` / `worktree_list` / `worktree_remove` | Permanent detached worktrees under `<repo>/.dsh-worktrees/`, recorded in a per-repo manifest and registered as DSH workspaces |
12
+ | `worktree_ready_for_review` | Agent submits review facts (summary, validation tests, suggested commit message) |
13
+ | `worktree_apply` | Deterministically merge the worktree's changes into your Local checkout — conflict-aware, fingerprint-CAS, never touches Local until the plan is verified |
14
+ | `worktree_finish` | Commit the task delta onto your Local branch while preserving your own staged/working state |
15
+ | `worktree_discard` | Drop the worktree (dirty confirmation required) |
16
+ | `/worktree` | Human-facing command surface |
17
+ | Safe cleanup | Retained worktrees expire, residue is quarantined, dirty worktrees are never silently deleted, no global `git worktree prune` |
18
+
19
+ ## Why worktrees live inside the repo
20
+
21
+ DSH's `workspace-write` sandbox grants exactly the session workspace root. Worktrees therefore live at `<repo>/.dsh-worktrees/<name>` (same pattern as Codex's `.codex/worktrees`), so agent file tools and git operations stay inside the sandbox.
22
+
23
+ ## Install
24
+
25
+ ```sh
26
+ dsh plugin --profile web add dsh-git-worktree
27
+ # or from GitHub
28
+ dsh plugin --profile web add github:wloops/dsh-git-worktree#<tag>
29
+ ```
30
+
31
+ ## Status
32
+
33
+ Pre-release (`0.1.0`). The state machine and apply engine are ported from Domi's production code (journal-based crash recovery, fingerprint CAS, Windows cleanup retries); the DSH tool surface is new and being hardened. Requires DSH `0.1.0-rc.6` line (`@deepseek-ai/dsh-tools` / `dsh-subprocess` / `dsh-commands`).
34
+
35
+ ## License
36
+
37
+ MIT
package/README.zh.md ADDED
@@ -0,0 +1,37 @@
1
+ # dsh-git-worktree
2
+
3
+ 为 DeepSeek Harness 打造的 Domi 级 git worktree 隔离与交付插件:永久 worktree、ready-for-review / apply / finish / discard 完整生命周期、冲突处理与安全清理。
4
+
5
+ > 移植自 [Domi](https://github.com/wloops/domi) 的生产 worktree 系统(Session Target / Isolated Checkout),为适配 DSH 沙箱模型移除了 Local Preview 面。
6
+
7
+ ## 能力
8
+
9
+ | 能力 | 说明 |
10
+ | --- | --- |
11
+ | `worktree_create` / `worktree_list` / `worktree_remove` | 仓库内 `.dsh-worktrees/` 下的永久 detached worktree,manifest 记录 + 注册为 DSH 工作区 |
12
+ | `worktree_ready_for_review` | Agent 提交验收信息(摘要、验证项、建议提交信息) |
13
+ | `worktree_apply` | 确定性合并 worktree 改动回 Local checkout——冲突感知、指纹 CAS、plan 验证前绝不触碰 Local |
14
+ | `worktree_finish` | 把任务增量提交到 Local 分支,同时保留你自己的 staged/working 状态 |
15
+ | `worktree_discard` | 丢弃 worktree(dirty 需要显式确认) |
16
+ | `/worktree` | 人类可用的命令入口 |
17
+ | 安全清理 | 保留期到期自动清理、残余 quarantine、dirty 永不静默删除、不执行全局 `git worktree prune` |
18
+
19
+ ## 为什么 worktree 放在仓库内
20
+
21
+ DSH 的 `workspace-write` 沙箱只授予会话工作区根。因此 worktree 位于 `<repo>/.dsh-worktrees/<name>`(与 Codex 的 `.codex/worktrees` 同模式),agent 的文件工具和 git 操作都留在沙箱内。
22
+
23
+ ## 安装
24
+
25
+ ```sh
26
+ dsh plugin --profile web add dsh-git-worktree
27
+ # 或从 GitHub
28
+ dsh plugin --profile web add github:wloops/dsh-git-worktree#<tag>
29
+ ```
30
+
31
+ ## 状态
32
+
33
+ 预发布(`0.1.0`)。状态机与 apply 引擎移植自 Domi 生产代码(journal 崩溃恢复、指纹 CAS、Windows 清理重试);DSH 工具面为新增并正在加固。要求 DSH `0.1.0-rc.6` 线(`@deepseek-ai/dsh-tools` / `dsh-subprocess` / `dsh-commands`)。
34
+
35
+ ## 许可证
36
+
37
+ MIT
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Node fs adapter for the session-checkout files port: directory identity
3
+ * (device/inode/birthtime) for quarantine CAS, empty-tree residue collection,
4
+ * symlink-guarded recursive removal, and byte measurement. Ported from Domi's
5
+ * production-adapters unchanged.
6
+ * @module dsh-git-worktree/adapters/files
7
+ */
8
+ import type { SessionCheckoutFilesPort } from '../ports.js';
9
+ /** Build the node-fs-backed files port. */
10
+ export declare function createNodeFilesPort(): SessionCheckoutFilesPort;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Node fs adapter for the session-checkout files port: directory identity
3
+ * (device/inode/birthtime) for quarantine CAS, empty-tree residue collection,
4
+ * symlink-guarded recursive removal, and byte measurement. Ported from Domi's
5
+ * production-adapters unchanged.
6
+ * @module dsh-git-worktree/adapters/files
7
+ */
8
+ import { existsSync, lstatSync, mkdirSync, readdirSync, rmdirSync } from 'node:fs';
9
+ import { lstat, readdir, realpath, rename, rm } from 'node:fs/promises';
10
+ import { dirname, join, resolve } from 'node:path';
11
+ import { SessionCheckoutError } from '../index.js';
12
+ async function measureDirectoryBytes(path) {
13
+ if (!existsSync(path))
14
+ return 0;
15
+ const stat = await lstat(path);
16
+ if (stat.isSymbolicLink())
17
+ return 0;
18
+ if (!stat.isDirectory())
19
+ return stat.size;
20
+ let total = 0;
21
+ for (const entry of await readdir(path))
22
+ total += await measureDirectoryBytes(join(path, entry));
23
+ return total;
24
+ }
25
+ function removeEmptyDirectoryTree(path) {
26
+ if (!existsSync(path))
27
+ return true;
28
+ const stat = lstatSync(path);
29
+ if (!stat.isDirectory() || stat.isSymbolicLink())
30
+ return false;
31
+ for (const entry of readdirSync(path)) {
32
+ if (!removeEmptyDirectoryTree(join(path, entry)))
33
+ return false;
34
+ }
35
+ rmdirSync(path);
36
+ return true;
37
+ }
38
+ async function inspectDirectoryIdentity(path) {
39
+ if (!existsSync(path))
40
+ return null;
41
+ const stat = await lstat(path, { bigint: true });
42
+ if (!stat.isDirectory() || stat.isSymbolicLink())
43
+ return null;
44
+ return {
45
+ device: stat.dev.toString(),
46
+ inode: stat.ino.toString(),
47
+ birthtimeNs: stat.birthtimeNs.toString(),
48
+ };
49
+ }
50
+ function directoryIdentitiesEqual(left, right) {
51
+ return left.device === right.device && left.inode === right.inode && left.birthtimeNs === right.birthtimeNs;
52
+ }
53
+ async function quarantineDirectoryTree(path, expectedIdentity, quarantinePath) {
54
+ const sourceParent = resolve(dirname(path));
55
+ const quarantineParent = resolve(dirname(quarantinePath));
56
+ const sameParent = process.platform === 'win32'
57
+ ? sourceParent.toLowerCase() === quarantineParent.toLowerCase()
58
+ : sourceParent === quarantineParent;
59
+ if (!sameParent || existsSync(quarantinePath)) {
60
+ throw new SessionCheckoutError('checkout_mismatch', 'Worktree quarantine 路径无效或已被占用');
61
+ }
62
+ await rename(path, quarantinePath);
63
+ const actualIdentity = await inspectDirectoryIdentity(quarantinePath);
64
+ if (actualIdentity && directoryIdentitiesEqual(actualIdentity, expectedIdentity))
65
+ return;
66
+ if (!existsSync(path)) {
67
+ try {
68
+ await rename(quarantinePath, path);
69
+ }
70
+ catch { /* Keep the quarantine; never delete an identity-mismatched object. */ }
71
+ }
72
+ throw new SessionCheckoutError('checkout_mismatch', 'Worktree 目录对象已被替换,未执行递归清理');
73
+ }
74
+ async function removeDirectoryTree(path) {
75
+ if (!existsSync(path))
76
+ return;
77
+ const stat = await lstat(path);
78
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
79
+ throw new SessionCheckoutError('checkout_mismatch', '拒绝删除非目录或符号链接形式的 Worktree 残余');
80
+ }
81
+ await rm(path, {
82
+ recursive: true,
83
+ force: true,
84
+ maxRetries: process.platform === 'win32' ? 6 : 2,
85
+ retryDelay: 150,
86
+ });
87
+ }
88
+ /** Build the node-fs-backed files port. */
89
+ export function createNodeFilesPort() {
90
+ return {
91
+ exists: existsSync,
92
+ canonicalize: async (path) => realpath(resolve(path)),
93
+ inspectDirectoryIdentity,
94
+ ensureDirectory: (path) => mkdirSync(path, { recursive: true }),
95
+ removeEmptyDirectoryTree,
96
+ quarantineDirectoryTree,
97
+ removeDirectoryTree,
98
+ measureDirectoryBytes,
99
+ };
100
+ }
101
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/adapters/files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * DSH adapter for the session-checkout git port: runs git through
3
+ * `ctx.subprocess` (tree-scoped termination, bounded collected output,
4
+ * scrubbed ambient environment) with Domi's hardening preserved — disabled
5
+ * git hooks, no optional locks, no terminal prompts, C locale, and a 5-minute
6
+ * hard cap for worktree removal on Windows. Internal refs use the
7
+ * `refs/dsh-worktree/session-checkouts/<key>` namespace so plugin artifacts
8
+ * never collide with user refs.
9
+ * @module dsh-git-worktree/adapters/git
10
+ */
11
+ import type { Context } from '@deepseek-ai/cordis';
12
+ import type { SessionCheckoutGitPort } from '../ports.js';
13
+ export interface GitPortOptions {
14
+ /** Directory holding the empty `core.hooksPath` target; created by the caller. */
15
+ hooksPath: string;
16
+ }
17
+ /** Build the `ctx.subprocess`-backed git port. */
18
+ export declare function createDshGitPort(ctx: Context, options: GitPortOptions): SessionCheckoutGitPort;
@@ -0,0 +1,172 @@
1
+ /**
2
+ * DSH adapter for the session-checkout git port: runs git through
3
+ * `ctx.subprocess` (tree-scoped termination, bounded collected output,
4
+ * scrubbed ambient environment) with Domi's hardening preserved — disabled
5
+ * git hooks, no optional locks, no terminal prompts, C locale, and a 5-minute
6
+ * hard cap for worktree removal on Windows. Internal refs use the
7
+ * `refs/dsh-worktree/session-checkouts/<key>` namespace so plugin artifacts
8
+ * never collide with user refs.
9
+ * @module dsh-git-worktree/adapters/git
10
+ */
11
+ import { createHash } from 'node:crypto';
12
+ import { realpath } from 'node:fs/promises';
13
+ import { resolve } from 'node:path';
14
+ import { SessionCheckoutError } from '../index.js';
15
+ /** Ordinary git commands keep a 10s cap; worktree removal gets 5 minutes on Windows. */
16
+ const GIT_COMMAND_TIMEOUT_MS = 30_000;
17
+ const WORKTREE_REMOVE_TIMEOUT_MS = 5 * 60_000;
18
+ /** Collected-output cap per stream; git output is small but removals can warn. */
19
+ const GIT_COLLECT_BYTES = 1 << 20;
20
+ function gitTimeoutMs(args) {
21
+ return args[0] === 'worktree' && args[1] === 'remove'
22
+ ? WORKTREE_REMOVE_TIMEOUT_MS
23
+ : GIT_COMMAND_TIMEOUT_MS;
24
+ }
25
+ /**
26
+ * Run one git command through `ctx.subprocess`. Returns an outcome object —
27
+ * the caller decides whether a non-zero exit is an error. Never
28
+ * shell-interpreted; argv passes verbatim.
29
+ */
30
+ async function runGit(ctx, cwd, args, options) {
31
+ const graceMs = gitTimeoutMs(args);
32
+ const controller = new AbortController();
33
+ const timeout = setTimeout(() => controller.abort(), graceMs);
34
+ timeout.unref?.();
35
+ try {
36
+ const handle = ctx.subprocess.spawn({
37
+ argv: [
38
+ 'git',
39
+ '--no-pager',
40
+ '--no-optional-locks',
41
+ '-c', 'core.quotePath=false',
42
+ '-c', 'core.fsmonitor=false',
43
+ '-c', `core.hooksPath=${options.hooksPath}`,
44
+ ...args,
45
+ ],
46
+ cwd,
47
+ stdio: {
48
+ stdin: 'ignore',
49
+ stdout: { maxBytes: GIT_COLLECT_BYTES },
50
+ stderr: { maxBytes: GIT_COLLECT_BYTES },
51
+ },
52
+ graceMs: GIT_COMMAND_TIMEOUT_MS,
53
+ signal: controller.signal,
54
+ env: {
55
+ GIT_OPTIONAL_LOCKS: '0',
56
+ GIT_TERMINAL_PROMPT: '0',
57
+ LC_ALL: 'C',
58
+ LANG: 'C',
59
+ },
60
+ });
61
+ const outcome = await handle.done;
62
+ const stdout = handle.collected.stdout?.readFrom(0).text ?? '';
63
+ const stderr = handle.collected.stderr?.readFrom(0).text ?? '';
64
+ if (outcome.signal === 'SIGTERM' || outcome.signal === 'SIGKILL' || controller.signal.aborted) {
65
+ return { code: -1, stdout, stderr: `git ${args.join(' ')} 超时(${graceMs}ms),已终止` };
66
+ }
67
+ return { code: outcome.exitCode ?? -1, stdout: stdout.trim(), stderr: stderr.trim() };
68
+ }
69
+ catch (error) {
70
+ // Synchronous spawn failure (git missing from PATH): report as a failed command.
71
+ return { code: -1, stdout: '', stderr: error instanceof Error ? error.message : String(error) };
72
+ }
73
+ finally {
74
+ clearTimeout(timeout);
75
+ }
76
+ }
77
+ async function runGitChecked(ctx, cwd, args, options) {
78
+ const result = await runGit(ctx, cwd, args, options);
79
+ if (result.code !== 0) {
80
+ throw new SessionCheckoutError('git_operation_failed', `Git 操作失败: git ${args.join(' ')}${result.stderr ? `: ${result.stderr}` : ''}`);
81
+ }
82
+ return result.stdout;
83
+ }
84
+ function checkoutRefRoot(checkoutId) {
85
+ const key = createHash('sha256').update(checkoutId).digest('hex').slice(0, 24);
86
+ return `refs/dsh-worktree/session-checkouts/${key}`;
87
+ }
88
+ function assertArtifactName(artifactName) {
89
+ const segments = artifactName.split('/');
90
+ if (segments.length === 0
91
+ || segments.some((segment) => !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(segment))) {
92
+ throw new SessionCheckoutError('invalid_input', '内部 Git artifact 名称无效');
93
+ }
94
+ }
95
+ function internalArtifactRef(checkoutId, artifactName) {
96
+ assertArtifactName(artifactName);
97
+ return `${checkoutRefRoot(checkoutId)}/${artifactName}`;
98
+ }
99
+ function applyBaseRef(checkoutId) {
100
+ return internalArtifactRef(checkoutId, 'apply-base');
101
+ }
102
+ /** Build the `ctx.subprocess`-backed git port. */
103
+ export function createDshGitPort(ctx, options) {
104
+ const runSessionGit = (cwd, args) => runGit(ctx, cwd, args, options);
105
+ const runSessionGitChecked = (cwd, args) => runGitChecked(ctx, cwd, args, options);
106
+ return {
107
+ inspect: async (root) => {
108
+ const topLevel = await runSessionGit(root, ['rev-parse', '--show-toplevel']);
109
+ if (topLevel.code !== 0 || !topLevel.stdout)
110
+ return null;
111
+ const commonDir = await runSessionGitChecked(root, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
112
+ const gitDir = await runSessionGitChecked(root, ['rev-parse', '--path-format=absolute', '--absolute-git-dir']);
113
+ const headOid = await runSessionGitChecked(root, ['rev-parse', 'HEAD']);
114
+ const symbolic = await runSessionGit(root, ['symbolic-ref', '--quiet', 'HEAD']);
115
+ const headRef = symbolic.code === 0 && symbolic.stdout ? symbolic.stdout : 'HEAD';
116
+ const branch = headRef.startsWith('refs/heads/') ? headRef.slice('refs/heads/'.length) : null;
117
+ return {
118
+ root: await realpath(resolve(topLevel.stdout)),
119
+ commonDir: await realpath(resolve(commonDir)),
120
+ gitDir: await realpath(resolve(gitDir)),
121
+ branch,
122
+ headOid,
123
+ headRef,
124
+ };
125
+ },
126
+ findContainingWorktreeRoot: async (root) => {
127
+ const topLevel = await runSessionGit(root, ['rev-parse', '--show-toplevel']);
128
+ if (topLevel.code !== 0 || !topLevel.stdout)
129
+ return null;
130
+ return realpath(resolve(topLevel.stdout));
131
+ },
132
+ status: async (root) => {
133
+ const output = await runSessionGitChecked(root, ['status', '--porcelain=v1', '-z', '--untracked-files=all']);
134
+ return { dirty: output.length > 0 };
135
+ },
136
+ createDetachedWorktree: async (localRoot, managedRoot, baseOid) => {
137
+ await runSessionGitChecked(localRoot, ['worktree', 'add', '--detach', managedRoot, baseOid]);
138
+ },
139
+ removeWorktree: async (localRoot, managedRoot) => {
140
+ // Git treats a reviewed-but-uncommitted final snapshot as modified/untracked;
141
+ // the caller has already verified checkout identity and the full fingerprint.
142
+ await runSessionGitChecked(localRoot, ['worktree', 'remove', '--force', managedRoot]);
143
+ },
144
+ retainApplyBase: async (localRoot, checkoutId, oid) => {
145
+ await runSessionGitChecked(localRoot, ['update-ref', applyBaseRef(checkoutId), oid]);
146
+ },
147
+ releaseApplyBase: async (localRoot, checkoutId) => {
148
+ await runSessionGitChecked(localRoot, ['update-ref', '-d', applyBaseRef(checkoutId)]);
149
+ },
150
+ retainInternalArtifact: async (localRoot, checkoutId, artifactName, oid) => {
151
+ await runSessionGitChecked(localRoot, ['update-ref', internalArtifactRef(checkoutId, artifactName), oid]);
152
+ },
153
+ releaseInternalArtifacts: async (localRoot, checkoutId, artifactPrefix) => {
154
+ const prefix = artifactPrefix
155
+ ? internalArtifactRef(checkoutId, artifactPrefix)
156
+ : `${checkoutRefRoot(checkoutId)}/`;
157
+ const output = await runSessionGitChecked(localRoot, ['for-each-ref', '--format=%(refname)', prefix]);
158
+ const refs = output.split(/\r?\n/).map((ref) => ref.trim()).filter(Boolean);
159
+ for (const ref of refs)
160
+ await runSessionGitChecked(localRoot, ['update-ref', '-d', ref]);
161
+ },
162
+ isAncestor: async (root, ancestorOid, descendantOid) => {
163
+ const result = await runSessionGit(root, ['merge-base', '--is-ancestor', ancestorOid, descendantOid]);
164
+ if (result.code === 0)
165
+ return true;
166
+ if (result.code === 1)
167
+ return false;
168
+ throw new Error(result.stderr || '无法证明 Git commit ancestry');
169
+ },
170
+ };
171
+ }
172
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/adapters/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * DSH lookup adapter: maps sessions and projects onto the harness's
3
+ * `workspaceRegistry` service (retrieved dynamically, like the ecosystem's
4
+ * plugins, because the published package version lags the rc.6 runtime). A
5
+ * session's project is found by scanning workspace session accounts; a
6
+ * project record maps to a workspace's `id`/`title`/`path`. Missing service or
7
+ * records degrade to `undefined`, which the state machine treats as
8
+ * recovery/orphan facts rather than failures.
9
+ * @module dsh-git-worktree/adapters/lookup
10
+ */
11
+ import type { Context } from '@deepseek-ai/cordis';
12
+ import type { SessionCheckoutLookupPort } from '../ports.js';
13
+ /** Minimal structural view of the harness workspace registry (see `@deepseek-ai/dsh-workspace`). */
14
+ export interface WorkspaceLike {
15
+ readonly id: string;
16
+ readonly path: string;
17
+ readonly title: string;
18
+ readonly sessionIds: readonly string[];
19
+ }
20
+ export interface WorkspaceRegistryLike {
21
+ list(): WorkspaceLike[];
22
+ get(id: string): WorkspaceLike | undefined;
23
+ resolveByPath(path: string): Promise<WorkspaceLike | undefined>;
24
+ create(path: string, title?: string): Promise<WorkspaceLike>;
25
+ delete(id: string): Promise<boolean>;
26
+ }
27
+ /** Build the lookup port over the harness workspace registry (absent → all lookups miss). */
28
+ export declare function createDshLookupPort(ctx: Context): SessionCheckoutLookupPort;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * DSH lookup adapter: maps sessions and projects onto the harness's
3
+ * `workspaceRegistry` service (retrieved dynamically, like the ecosystem's
4
+ * plugins, because the published package version lags the rc.6 runtime). A
5
+ * session's project is found by scanning workspace session accounts; a
6
+ * project record maps to a workspace's `id`/`title`/`path`. Missing service or
7
+ * records degrade to `undefined`, which the state machine treats as
8
+ * recovery/orphan facts rather than failures.
9
+ * @module dsh-git-worktree/adapters/lookup
10
+ */
11
+ function isWorkspaceRegistryLike(value) {
12
+ return Boolean(value)
13
+ && typeof value === 'object'
14
+ && typeof value.list === 'function'
15
+ && typeof value.get === 'function';
16
+ }
17
+ /** Build the lookup port over the harness workspace registry (absent → all lookups miss). */
18
+ export function createDshLookupPort(ctx) {
19
+ const registry = () => {
20
+ const candidate = ctx.get('workspaceRegistry');
21
+ return isWorkspaceRegistryLike(candidate) ? candidate : undefined;
22
+ };
23
+ return {
24
+ getSession: (sessionId) => {
25
+ const workspaces = registry()?.list() ?? [];
26
+ for (const workspace of workspaces) {
27
+ if (workspace.sessionIds.includes(sessionId)) {
28
+ return { id: sessionId, projectId: workspace.id };
29
+ }
30
+ }
31
+ return undefined;
32
+ },
33
+ getProject: (projectId) => {
34
+ const workspace = registry()?.get(projectId);
35
+ return workspace
36
+ ? { id: workspace.id, name: workspace.title, root: workspace.path }
37
+ : undefined;
38
+ },
39
+ };
40
+ }
41
+ //# sourceMappingURL=lookup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lookup.js","sourceRoot":"","sources":["../../src/adapters/lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAqBH,SAAS,uBAAuB,CAAC,KAAc;IAC7C,OAAO,OAAO,CAAC,KAAK,CAAC;WAChB,OAAO,KAAK,KAAK,QAAQ;WACzB,OAAQ,KAA+B,CAAC,IAAI,KAAK,UAAU;WAC3D,OAAQ,KAA+B,CAAC,GAAG,KAAK,UAAU,CAAA;AACjE,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,mBAAmB,CAAC,GAAY;IAC9C,MAAM,QAAQ,GAAG,GAAsC,EAAE;QACvD,MAAM,SAAS,GAAY,GAAG,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;QACvD,OAAO,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAA;IACnE,CAAC,CAAA;IAED,OAAO;QACL,UAAU,EAAE,CAAC,SAAS,EAA4C,EAAE;YAClE,MAAM,UAAU,GAAG,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;YAC3C,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;gBACnC,IAAI,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;oBAC7C,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,EAAE,CAAA;gBACnD,CAAC;YACH,CAAC;YACD,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,UAAU,EAAE,CAAC,SAAS,EAA4C,EAAE;YAClE,MAAM,SAAS,GAAG,QAAQ,EAAE,EAAE,GAAG,CAAC,SAAS,CAAC,CAAA;YAC5C,OAAO,SAAS;gBACd,CAAC,CAAC,EAAE,EAAE,EAAE,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE;gBACnE,CAAC,CAAC,SAAS,CAAA;QACf,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Durable registry adapter: the session-checkout registry (version 2) as one
3
+ * atomic JSON file under the plugin's state directory. Validation gates every
4
+ * read — a corrupt file fails loud instead of being silently overwritten;
5
+ * v1 records migrate conservatively to `recovery_required` like Domi.
6
+ * @module dsh-git-worktree/adapters/registry
7
+ */
8
+ import type { ManagedCheckoutsRegistry, SessionCheckoutRegistryPort } from '../ports.js';
9
+ /** Atomic JSON registry file with `.tmp`/`.bak` crash recovery (see safe-file). */
10
+ export declare class AtomicJsonCheckoutRegistry implements SessionCheckoutRegistryPort {
11
+ private readonly path;
12
+ constructor(path: string);
13
+ read(): ManagedCheckoutsRegistry;
14
+ write(registry: ManagedCheckoutsRegistry): void;
15
+ }