@cerbur/clutch-dsh-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 (114) hide show
  1. package/README.md +136 -0
  2. package/cordis.patch.yml +5 -0
  3. package/lib/client/WorktreeModeAction.d.ts +11 -0
  4. package/lib/client/WorktreeModeAction.d.ts.map +1 -0
  5. package/lib/client/WorktreeModeAction.js +14 -0
  6. package/lib/client/WorktreeModeAction.js.map +1 -0
  7. package/lib/client/WorktreeSurface.d.ts +33 -0
  8. package/lib/client/WorktreeSurface.d.ts.map +1 -0
  9. package/lib/client/WorktreeSurface.js +834 -0
  10. package/lib/client/WorktreeSurface.js.map +1 -0
  11. package/lib/client/entry.d.ts +18 -0
  12. package/lib/client/entry.d.ts.map +1 -0
  13. package/lib/client/entry.js +100 -0
  14. package/lib/client/entry.js.map +1 -0
  15. package/lib/client/index.d.ts +7 -0
  16. package/lib/client/index.d.ts.map +1 -0
  17. package/lib/client/index.js +6 -0
  18. package/lib/client/index.js.map +1 -0
  19. package/lib/client/navigation.d.ts +7 -0
  20. package/lib/client/navigation.d.ts.map +1 -0
  21. package/lib/client/navigation.js +5 -0
  22. package/lib/client/navigation.js.map +1 -0
  23. package/lib/client/overlay-bounds.d.ts +11 -0
  24. package/lib/client/overlay-bounds.d.ts.map +1 -0
  25. package/lib/client/overlay-bounds.js +9 -0
  26. package/lib/client/overlay-bounds.js.map +1 -0
  27. package/lib/client/sidebar-overlay-geometry.d.ts +10 -0
  28. package/lib/client/sidebar-overlay-geometry.d.ts.map +1 -0
  29. package/lib/client/sidebar-overlay-geometry.js +96 -0
  30. package/lib/client/sidebar-overlay-geometry.js.map +1 -0
  31. package/lib/client/view-mode-store.d.ts +5 -0
  32. package/lib/client/view-mode-store.d.ts.map +1 -0
  33. package/lib/client/view-mode-store.js +15 -0
  34. package/lib/client/view-mode-store.js.map +1 -0
  35. package/lib/client/view-mode.d.ts +52 -0
  36. package/lib/client/view-mode.d.ts.map +1 -0
  37. package/lib/client/view-mode.js +76 -0
  38. package/lib/client/view-mode.js.map +1 -0
  39. package/lib/client/virtual-workspace-membership.d.ts +27 -0
  40. package/lib/client/virtual-workspace-membership.d.ts.map +1 -0
  41. package/lib/client/virtual-workspace-membership.js +95 -0
  42. package/lib/client/virtual-workspace-membership.js.map +1 -0
  43. package/lib/client/worktree-connection.d.ts +42 -0
  44. package/lib/client/worktree-connection.d.ts.map +1 -0
  45. package/lib/client/worktree-connection.js +139 -0
  46. package/lib/client/worktree-connection.js.map +1 -0
  47. package/lib/client/worktree-view.d.ts +63 -0
  48. package/lib/client/worktree-view.d.ts.map +1 -0
  49. package/lib/client/worktree-view.js +116 -0
  50. package/lib/client/worktree-view.js.map +1 -0
  51. package/lib/client.js +1952 -0
  52. package/lib/client.js.map +1 -0
  53. package/lib/contract/index.contract.d.ts +2 -0
  54. package/lib/contract/index.contract.d.ts.map +1 -0
  55. package/lib/contract/index.contract.js +51 -0
  56. package/lib/contract/index.contract.js.map +1 -0
  57. package/lib/contract/index.d.ts +181 -0
  58. package/lib/contract/index.d.ts.map +1 -0
  59. package/lib/contract/index.js +45 -0
  60. package/lib/contract/index.js.map +1 -0
  61. package/lib/host/dsh-read-adapter.d.ts +58 -0
  62. package/lib/host/dsh-read-adapter.d.ts.map +1 -0
  63. package/lib/host/dsh-read-adapter.js +59 -0
  64. package/lib/host/dsh-read-adapter.js.map +1 -0
  65. package/lib/host/index.d.ts +16 -0
  66. package/lib/host/index.d.ts.map +1 -0
  67. package/lib/host/index.js +14 -0
  68. package/lib/host/index.js.map +1 -0
  69. package/lib/host/remote.d.ts +16 -0
  70. package/lib/host/remote.d.ts.map +1 -0
  71. package/lib/host/remote.js +61 -0
  72. package/lib/host/remote.js.map +1 -0
  73. package/lib/host/service.d.ts +58 -0
  74. package/lib/host/service.d.ts.map +1 -0
  75. package/lib/host/service.js +121 -0
  76. package/lib/host/service.js.map +1 -0
  77. package/lib/index.d.ts +23 -0
  78. package/lib/index.d.ts.map +1 -0
  79. package/lib/index.js +22 -0
  80. package/lib/index.js.map +1 -0
  81. package/lib/manage/index.d.ts +9 -0
  82. package/lib/manage/index.d.ts.map +1 -0
  83. package/lib/manage/index.js +8 -0
  84. package/lib/manage/index.js.map +1 -0
  85. package/lib/manage/manager.d.ts +86 -0
  86. package/lib/manage/manager.d.ts.map +1 -0
  87. package/lib/manage/manager.js +584 -0
  88. package/lib/manage/manager.js.map +1 -0
  89. package/lib/manage/types.d.ts +41 -0
  90. package/lib/manage/types.d.ts.map +1 -0
  91. package/lib/manage/types.js +2 -0
  92. package/lib/manage/types.js.map +1 -0
  93. package/lib/provider/git.d.ts +42 -0
  94. package/lib/provider/git.d.ts.map +1 -0
  95. package/lib/provider/git.js +222 -0
  96. package/lib/provider/git.js.map +1 -0
  97. package/lib/provider/index.d.ts +13 -0
  98. package/lib/provider/index.d.ts.map +1 -0
  99. package/lib/provider/index.js +12 -0
  100. package/lib/provider/index.js.map +1 -0
  101. package/lib/provider/sidecar.d.ts +85 -0
  102. package/lib/provider/sidecar.d.ts.map +1 -0
  103. package/lib/provider/sidecar.js +410 -0
  104. package/lib/provider/sidecar.js.map +1 -0
  105. package/lib/provider/types.d.ts +126 -0
  106. package/lib/provider/types.d.ts.map +1 -0
  107. package/lib/provider/types.js +46 -0
  108. package/lib/provider/types.js.map +1 -0
  109. package/lib/typert.host.d.ts +3 -0
  110. package/lib/typert.host.js +290 -0
  111. package/lib/typert.remote-client.d.ts +32 -0
  112. package/lib/typert.remote-client.d.ts.map +1 -0
  113. package/lib/typert.remote-client.js +284 -0
  114. package/package.json +118 -0
@@ -0,0 +1,410 @@
1
+ import { lstat, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { SIDECAR_SCHEMA_VERSION, WorktreeProviderError, providerError, } from './types.js';
5
+ const WORKTREE_KEYS = ['absolutePath', 'branch', 'status', 'workspaceId', 'worktreeId'];
6
+ const BINDING_KEYS = ['sessionId', 'status', 'workspaceId', 'worktreeId'];
7
+ const SNAPSHOT_KEYS = ['bindings', 'schemaVersion', 'workspaceId', 'worktrees'];
8
+ function isObject(value) {
9
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
10
+ }
11
+ /*
12
+ * 每个 schema version 使用精确字段白名单,避免新版或注入字段被旧代码静默接受、随后在
13
+ * 重写时丢失。格式演进必须显式提升 schema version。
14
+ *
15
+ * Each schema version uses an exact key allowlist so newer or injected fields
16
+ * are not silently accepted and then lost on rewrite. Format evolution must be
17
+ * represented by an explicit schema-version change.
18
+ */
19
+ function hasExactKeys(value, keys) {
20
+ return JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...keys].sort());
21
+ }
22
+ // 可读取但不满足 schema/invariant 的数据属于 `SIDECAR_CORRUPT`;文件系统故障另报 `SIDECAR_UNAVAILABLE`。
23
+ // Readable data that violates the schema or invariants is `SIDECAR_CORRUPT`;
24
+ // filesystem failures are reported separately as `SIDECAR_UNAVAILABLE`.
25
+ function corrupt(pathname, message, details = {}) {
26
+ return providerError('SIDECAR_CORRUPT', `${message}: ${pathname}`, { path: pathname, ...details });
27
+ }
28
+ function assertWorktreeRecord(value, pathname) {
29
+ if (!isObject(value) ||
30
+ !hasExactKeys(value, WORKTREE_KEYS) ||
31
+ typeof value.worktreeId !== 'string' ||
32
+ typeof value.workspaceId !== 'string' ||
33
+ typeof value.absolutePath !== 'string' ||
34
+ !path.isAbsolute(value.absolutePath) ||
35
+ typeof value.branch !== 'string' ||
36
+ (value.status !== 'active' && value.status !== 'removed')) {
37
+ throw corrupt(pathname, 'invalid Worktree record');
38
+ }
39
+ }
40
+ function assertBinding(value, pathname) {
41
+ if (!isObject(value) ||
42
+ !hasExactKeys(value, BINDING_KEYS) ||
43
+ typeof value.workspaceId !== 'string' ||
44
+ typeof value.worktreeId !== 'string' ||
45
+ typeof value.sessionId !== 'string' ||
46
+ (value.status !== 'active' && value.status !== 'detached')) {
47
+ throw corrupt(pathname, 'invalid Session binding');
48
+ }
49
+ }
50
+ /**
51
+ * 校验完整 sidecar 快照及跨记录不变量:shard Workspace 身份一致、Worktree ID 唯一、
52
+ * 每个 Session 至多一个 active binding,且 active binding 必须指向 active Worktree。
53
+ *
54
+ * Validates a complete sidecar snapshot and its cross-record invariants: one
55
+ * Workspace identity per shard, unique Worktree IDs, at most one active binding
56
+ * per Session, and every active binding targeting an active Worktree.
57
+ *
58
+ * 提供 `generatedWorktreeRoot` 时,还要求 Provider 生成的 ID 合法且路径精确等于
59
+ * `<generatedWorktreeRoot>/<worktreeId>`。detached binding 可继续指向 removed 记录,
60
+ * 以保留删除后的关系历史。
61
+ *
62
+ * When `generatedWorktreeRoot` is supplied, Provider-generated IDs must be
63
+ * valid and paths must equal `<generatedWorktreeRoot>/<worktreeId>` exactly.
64
+ * Detached bindings may continue to reference removed records so relationships
65
+ * survive Worktree removal.
66
+ */
67
+ export function validateSidecarSnapshot(value, pathname, generatedWorktreeRoot) {
68
+ if (!isObject(value) ||
69
+ !hasExactKeys(value, SNAPSHOT_KEYS) ||
70
+ value.schemaVersion !== SIDECAR_SCHEMA_VERSION ||
71
+ typeof value.workspaceId !== 'string' ||
72
+ !Array.isArray(value.worktrees) ||
73
+ !Array.isArray(value.bindings)) {
74
+ throw corrupt(pathname, 'invalid sidecar snapshot');
75
+ }
76
+ for (const record of value.worktrees)
77
+ assertWorktreeRecord(record, pathname);
78
+ for (const binding of value.bindings)
79
+ assertBinding(binding, pathname);
80
+ const workspaceId = value.workspaceId;
81
+ const worktreeIds = new Set();
82
+ for (const record of value.worktrees) {
83
+ if (record.workspaceId !== workspaceId) {
84
+ throw corrupt(pathname, 'Worktree record belongs to another Workspace');
85
+ }
86
+ if (worktreeIds.has(record.worktreeId)) {
87
+ throw corrupt(pathname, 'sidecar contains duplicate Worktree IDs');
88
+ }
89
+ worktreeIds.add(record.worktreeId);
90
+ if (generatedWorktreeRoot) {
91
+ // 这是词法路径约束;关键父目录的物理 symlink 边界由 I/O 前的检查单独负责。
92
+ // This is a lexical path constraint; physical symlink boundaries for the
93
+ // critical parent directories are checked separately before I/O.
94
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(record.worktreeId)) {
95
+ throw corrupt(pathname, 'Worktree record has an invalid generated ID');
96
+ }
97
+ const expectedPath = path.resolve(generatedWorktreeRoot, record.worktreeId);
98
+ if (path.resolve(record.absolutePath) !== expectedPath) {
99
+ throw corrupt(pathname, 'Worktree path is outside the generated DSH Home root');
100
+ }
101
+ }
102
+ }
103
+ for (const binding of value.bindings) {
104
+ if (binding.workspaceId !== workspaceId) {
105
+ throw corrupt(pathname, 'Session binding belongs to another Workspace');
106
+ }
107
+ }
108
+ const activeSessions = new Set();
109
+ for (const binding of value.bindings) {
110
+ if (binding.status !== 'active')
111
+ continue;
112
+ if (activeSessions.has(binding.sessionId)) {
113
+ throw corrupt(pathname, 'Session has more than one active Worktree binding');
114
+ }
115
+ activeSessions.add(binding.sessionId);
116
+ }
117
+ // 只有 detached binding 可以保留对非 active Worktree 的关系;active 状态绝不静默降级。
118
+ // Only detached bindings may retain a relationship to a non-active Worktree;
119
+ // an active relationship is never silently downgraded.
120
+ const worktreeStatus = new Map(value.worktrees.map((record) => [record.worktreeId, record.status]));
121
+ for (const binding of value.bindings) {
122
+ if (binding.status === 'active' && worktreeStatus.get(binding.worktreeId) !== 'active') {
123
+ throw corrupt(pathname, 'active binding does not point to an active Worktree');
124
+ }
125
+ }
126
+ return {
127
+ schemaVersion: SIDECAR_SCHEMA_VERSION,
128
+ workspaceId,
129
+ worktrees: value.worktrees,
130
+ bindings: value.bindings,
131
+ };
132
+ }
133
+ function emptySnapshot(workspaceId) {
134
+ return {
135
+ schemaVersion: SIDECAR_SCHEMA_VERSION,
136
+ workspaceId,
137
+ worktrees: [],
138
+ bindings: [],
139
+ };
140
+ }
141
+ function sameWorktree(left, right) {
142
+ return (left.worktreeId === right.worktreeId &&
143
+ left.workspaceId === right.workspaceId &&
144
+ left.absolutePath === right.absolutePath &&
145
+ left.branch === right.branch &&
146
+ left.status === right.status);
147
+ }
148
+ function sameBinding(left, right) {
149
+ return (left.workspaceId === right.workspaceId &&
150
+ left.worktreeId === right.worktreeId &&
151
+ left.sessionId === right.sessionId &&
152
+ left.status === right.status);
153
+ }
154
+ /*
155
+ * 互斥范围是“同一 repository 实例 + 同一 Workspace key”。它让该实例内的 mutation
156
+ * 依次读取最新快照,但不会假装协调其他进程或其他 repository 实例;异常也会在 finally
157
+ * 中释放队列,避免后续操作永久阻塞。
158
+ *
159
+ * Mutual exclusion is scoped to one repository instance and one Workspace key.
160
+ * It makes mutations in that instance read the latest snapshot serially, but
161
+ * does not pretend to coordinate another process or repository instance;
162
+ * failures still release the queue in `finally` so later work cannot deadlock.
163
+ */
164
+ class KeyedMutex {
165
+ tails = new Map();
166
+ async run(key, operation) {
167
+ const previous = this.tails.get(key) ?? Promise.resolve();
168
+ let release;
169
+ const current = new Promise((resolve) => {
170
+ release = resolve;
171
+ });
172
+ this.tails.set(key, current);
173
+ await previous;
174
+ try {
175
+ return await operation();
176
+ }
177
+ finally {
178
+ release();
179
+ if (this.tails.get(key) === current)
180
+ this.tails.delete(key);
181
+ }
182
+ }
183
+ }
184
+ /**
185
+ * 位于 DSH Home 下、按 Workspace 分 shard 的 sidecar repository;它只持久化外部
186
+ * Worktree/Session 关系,不读取或写入 DSH 原始数据及 Workspace 业务文件。
187
+ *
188
+ * Workspace-sharded sidecar repository under DSH Home; it persists only the
189
+ * external Worktree/Session relationship and never reads or writes DSH-owned
190
+ * data or Workspace business files.
191
+ *
192
+ * 同一实例内的 mutation 按 Workspace 串行化,并通过同目录临时文件加 rename 提供完整旧版或
193
+ * 完整新版的原子可见性;这不等同于跨进程锁,也不承诺 fsync 级崩溃持久性。
194
+ *
195
+ * Mutations are serialized per Workspace within one instance, and a temporary
196
+ * file plus same-directory rename provides complete-old-or-complete-new atomic
197
+ * visibility. This is neither a cross-process lock nor an fsync-level crash
198
+ * durability guarantee.
199
+ */
200
+ export class WorkspaceShardedSidecarRepository {
201
+ pluginRoot;
202
+ worktreeRoot;
203
+ workspaceRoot;
204
+ dshHome;
205
+ mutex = new KeyedMutex();
206
+ constructor({ dshHome }) {
207
+ if (!path.isAbsolute(dshHome)) {
208
+ throw providerError('SIDECAR_UNAVAILABLE', 'DSH Home must be an absolute path', { dshHome });
209
+ }
210
+ this.dshHome = path.resolve(dshHome);
211
+ this.pluginRoot = path.join(this.dshHome, 'clutch-dsh-worktree');
212
+ this.worktreeRoot = path.join(this.pluginRoot, 'worktree');
213
+ this.workspaceRoot = path.join(this.pluginRoot, 'workspaces');
214
+ }
215
+ /**
216
+ * 将 Workspace ID 编码为单个稳定文件名,所有 shard 都限定在 Provider 自有目录中。
217
+ * Encodes a Workspace ID into one stable filename, keeping every shard under
218
+ * the Provider-owned directory.
219
+ */
220
+ getShardPath(workspaceId) {
221
+ return path.join(this.workspaceRoot, `${encodeURIComponent(workspaceId)}.json`);
222
+ }
223
+ /**
224
+ * 读取并完整校验 shard;文件不存在表示尚无关系并返回空快照,损坏内容绝不自动修复。
225
+ * Reads and fully validates a shard; a missing file means no relationships yet
226
+ * and returns an empty snapshot, while corrupt content is never auto-repaired.
227
+ */
228
+ async read(workspaceId) {
229
+ return this.readFromDisk(workspaceId);
230
+ }
231
+ /**
232
+ * 在 Workspace 互斥区内重新读取最新快照、应用转换、复验全部不变量,再决定是否原子写入。
233
+ * Re-reads the latest snapshot inside the Workspace mutex, applies the
234
+ * transition, revalidates every invariant, and only then decides whether to
235
+ * write atomically.
236
+ *
237
+ * mutation 抛错、返回非法快照或改变 Workspace 身份时,已有 shard 不会被本方法替换。
238
+ * If the mutation throws, returns an invalid snapshot, or changes Workspace
239
+ * identity, this method does not replace the existing shard.
240
+ */
241
+ async mutate(workspaceId, mutation) {
242
+ return this.mutex.run(workspaceId, async () => {
243
+ const current = await this.readFromDisk(workspaceId);
244
+ const result = await mutation(current);
245
+ const next = validateSidecarSnapshot(result.snapshot, this.getShardPath(workspaceId), this.worktreeRoot);
246
+ if (next.workspaceId !== workspaceId) {
247
+ throw corrupt(this.getShardPath(workspaceId), 'sidecar mutation changed the Workspace identity');
248
+ }
249
+ if (result.changed !== false)
250
+ await this.writeToDisk(next);
251
+ return result.result;
252
+ });
253
+ }
254
+ /**
255
+ * 按 Worktree ID 幂等插入;完全相同的记录直接复用,同一 ID 的不同 metadata 被视为损坏。
256
+ * Idempotently inserts by Worktree ID; an identical record is reused, while
257
+ * different metadata under the same ID is treated as corruption.
258
+ */
259
+ async upsertWorktree(record) {
260
+ const { health: _health, ...persistedRecord } = record;
261
+ void _health;
262
+ return this.mutate(record.workspaceId, (snapshot) => {
263
+ const existing = snapshot.worktrees.find((candidate) => candidate.worktreeId === persistedRecord.worktreeId);
264
+ if (existing) {
265
+ if (!sameWorktree(existing, persistedRecord)) {
266
+ throw providerError('SIDECAR_CORRUPT', 'Worktree ID already contains different metadata', {
267
+ worktreeId: persistedRecord.worktreeId,
268
+ });
269
+ }
270
+ return { result: existing, snapshot, changed: false };
271
+ }
272
+ return {
273
+ result: persistedRecord,
274
+ snapshot: { ...snapshot, worktrees: [...snapshot.worktrees, persistedRecord] },
275
+ };
276
+ });
277
+ }
278
+ /**
279
+ * 按 Session ID 幂等插入 binding;完全相同的关系直接复用,任何既有不同关系都返回冲突。
280
+ * Idempotently inserts a binding by Session ID; an identical relationship is
281
+ * reused, while any different existing relationship returns a conflict.
282
+ */
283
+ async upsertBinding(binding) {
284
+ return this.mutate(binding.workspaceId, (snapshot) => {
285
+ const existing = snapshot.bindings.find((candidate) => candidate.sessionId === binding.sessionId);
286
+ if (existing) {
287
+ if (!sameBinding(existing, binding)) {
288
+ throw providerError('SESSION_ALREADY_BOUND', 'Session is already bound to another Worktree', {
289
+ sessionId: binding.sessionId,
290
+ worktreeId: existing.worktreeId,
291
+ });
292
+ }
293
+ return { result: existing, snapshot, changed: false };
294
+ }
295
+ return {
296
+ result: binding,
297
+ snapshot: { ...snapshot, bindings: [...snapshot.bindings, binding] },
298
+ };
299
+ });
300
+ }
301
+ async readFromDisk(workspaceId) {
302
+ await this.assertStorageBoundary();
303
+ const shardPath = this.getShardPath(workspaceId);
304
+ let text;
305
+ try {
306
+ text = await readFile(shardPath, 'utf8');
307
+ }
308
+ catch (error) {
309
+ // ENOENT 是未初始化状态;权限、I/O 等其他失败意味着存储不可用,不能伪装成空索引。
310
+ // ENOENT is an uninitialized state; permission, I/O, and other failures
311
+ // mean storage is unavailable and must not masquerade as an empty index.
312
+ if (error.code === 'ENOENT')
313
+ return emptySnapshot(workspaceId);
314
+ throw providerError('SIDECAR_UNAVAILABLE', `Unable to read sidecar shard: ${shardPath}`, {
315
+ path: shardPath,
316
+ cause: String(error),
317
+ });
318
+ }
319
+ let parsed;
320
+ try {
321
+ parsed = JSON.parse(text);
322
+ }
323
+ catch (error) {
324
+ throw providerError('SIDECAR_CORRUPT', `Unable to parse sidecar shard: ${shardPath}`, {
325
+ path: shardPath,
326
+ cause: String(error),
327
+ });
328
+ }
329
+ const snapshot = validateSidecarSnapshot(parsed, shardPath, this.worktreeRoot);
330
+ if (snapshot.workspaceId !== workspaceId) {
331
+ throw corrupt(shardPath, 'sidecar shard Workspace identity does not match its filename');
332
+ }
333
+ return snapshot;
334
+ }
335
+ async writeToDisk(snapshot) {
336
+ await this.assertStorageBoundary();
337
+ const shardPath = this.getShardPath(snapshot.workspaceId);
338
+ // 临时文件与目标 shard 位于同一目录,从而避免跨文件系统 rename;随机后缀防止并发残留碰撞。
339
+ // The temporary file shares the shard directory to avoid a cross-filesystem
340
+ // rename, and its random suffix prevents collisions with concurrent debris.
341
+ const temporaryPath = `${shardPath}.${process.pid}.${randomUUID()}.tmp`;
342
+ try {
343
+ await mkdir(this.workspaceRoot, { recursive: true });
344
+ await writeFile(temporaryPath, `${JSON.stringify(snapshot, null, 2)}\n`, {
345
+ encoding: 'utf8',
346
+ mode: 0o600,
347
+ });
348
+ // 读取者只会观察到完整旧文件或完整新文件;rename 前的临时内容从不作为 shard 读取。
349
+ // Readers observe either the complete old file or complete new file; the
350
+ // pre-rename temporary contents are never read as the shard.
351
+ await rename(temporaryPath, shardPath);
352
+ }
353
+ catch (error) {
354
+ throw providerError('SIDECAR_UNAVAILABLE', `Unable to atomically write sidecar shard: ${shardPath}`, {
355
+ path: shardPath,
356
+ temporaryPath,
357
+ cause: String(error),
358
+ });
359
+ }
360
+ finally {
361
+ try {
362
+ await unlink(temporaryPath);
363
+ }
364
+ catch (error) {
365
+ if (error.code !== 'ENOENT') {
366
+ // 临时文件的尽力清理失败不能覆盖原始写入结果。
367
+ // A failed best-effort temporary-file cleanup must not replace the
368
+ // original write result.
369
+ void error;
370
+ }
371
+ }
372
+ }
373
+ }
374
+ /*
375
+ * 每次 I/O 前拒绝关键存储父目录本身为 symlink,避免 Provider 自有相对布局被重定向到
376
+ * Workspace 或 DSH 原始数据位置。目录不存在是首次初始化的正常状态。
377
+ *
378
+ * Before each I/O operation, reject critical storage parent directories that
379
+ * are themselves symlinks so the Provider-owned relative layout cannot be
380
+ * redirected into a Workspace or DSH-owned data. Missing directories are a
381
+ * normal first-initialization state.
382
+ */
383
+ async assertStorageBoundary() {
384
+ for (const [pathname, label] of [
385
+ [this.dshHome, 'DSH Home'],
386
+ [this.pluginRoot, 'plugin sidecar root'],
387
+ [this.worktreeRoot, 'Worktree root'],
388
+ [this.workspaceRoot, 'Workspace shard root'],
389
+ ]) {
390
+ try {
391
+ if ((await lstat(pathname)).isSymbolicLink()) {
392
+ throw providerError('SIDECAR_UNAVAILABLE', `${label} must not be a symlink: ${pathname}`, {
393
+ path: pathname,
394
+ });
395
+ }
396
+ }
397
+ catch (error) {
398
+ if (error.code === 'ENOENT')
399
+ continue;
400
+ if (error instanceof WorktreeProviderError)
401
+ throw error;
402
+ throw providerError('SIDECAR_UNAVAILABLE', `Unable to inspect ${label}: ${pathname}`, {
403
+ path: pathname,
404
+ cause: String(error),
405
+ });
406
+ }
407
+ }
408
+ }
409
+ }
410
+ //# sourceMappingURL=sidecar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidecar.js","sourceRoot":"","sources":["../../src/provider/sidecar.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ import type { SessionBinding, WorktreeErrorCode, WorktreeErrorDetails, WorktreeRecord, WorkspaceId } from '../contract/index.js';
2
+ /**
3
+ * 当前 sidecar 磁盘格式的唯一受支持版本;未知版本会被视为损坏,而不是被静默猜测或迁移。
4
+ * The only supported on-disk sidecar format; unknown versions are treated as
5
+ * corruption rather than guessed or silently migrated.
6
+ */
7
+ export declare const SIDECAR_SCHEMA_VERSION: 1;
8
+ /**
9
+ * 从 DSH 投影出的最小 Workspace 事实;Provider 不拥有、复制或写回这些数据。
10
+ * Minimal Workspace facts projected from DSH; the Provider neither owns,
11
+ * persists, nor writes these values back.
12
+ */
13
+ export interface DshWorkspaceSummary {
14
+ readonly workspaceId: WorkspaceId;
15
+ readonly projectId?: string;
16
+ readonly rootPath: string;
17
+ }
18
+ /**
19
+ * 从 DSH Session header 投影出的只读事实;`cwd` 仅用于校验绑定关系,不成为 sidecar 数据。
20
+ * Read-only facts projected from a DSH Session header; `cwd` is used only to
21
+ * validate a binding and never becomes sidecar-owned data.
22
+ */
23
+ export interface DshSessionSummary {
24
+ readonly sessionId: string;
25
+ readonly workspaceId?: WorkspaceId;
26
+ readonly projectId?: string;
27
+ readonly cwd: string;
28
+ }
29
+ /**
30
+ * DSH 唯一数据源的只读端口;该接口刻意不暴露任何 Workspace 或 Session mutation。
31
+ * Read-only port to the DSH source of truth; it deliberately exposes no
32
+ * Workspace or Session mutation capability.
33
+ */
34
+ export interface DshReadAdapter {
35
+ getWorkspace(workspaceId: WorkspaceId): Promise<DshWorkspaceSummary | undefined>;
36
+ getSession(sessionId: string): Promise<DshSessionSummary | undefined>;
37
+ listSessions(): Promise<readonly DshSessionSummary[]>;
38
+ }
39
+ /**
40
+ * `git worktree list --porcelain` 的最小投影;detached HEAD 等条目可以没有本地 branch。
41
+ * Minimal projection of `git worktree list --porcelain`; entries such as a
42
+ * detached HEAD may have no local branch.
43
+ */
44
+ export interface GitWorktreeInfo {
45
+ readonly absolutePath: string;
46
+ readonly branch?: string;
47
+ }
48
+ /**
49
+ * Git worktree 的窄端口:只允许校验、列举、创建和删除,不提供任意 Git、远程或业务文件操作。
50
+ * Narrow Git worktree port: it permits validation, listing, creation, and
51
+ * removal only, with no arbitrary Git, remote, or working-file operations.
52
+ */
53
+ export interface GitWorktreeAdapter {
54
+ validateRepository(workspaceRoot: string): Promise<void>;
55
+ listBranches(workspaceRoot: string): Promise<readonly string[]>;
56
+ listWorktrees(workspaceRoot: string): Promise<readonly GitWorktreeInfo[]>;
57
+ createWorktree(workspaceRoot: string, targetPath: string, branch: string, newBranch?: string): Promise<void>;
58
+ removeWorktree(workspaceRoot: string, targetPath: string): Promise<void>;
59
+ }
60
+ /**
61
+ * 单个 Workspace shard 的完整外部关系快照;这里只保存 Worktree 元数据和 Session binding。
62
+ * Complete external-relation snapshot for one Workspace shard; it contains
63
+ * only Worktree metadata and Session bindings.
64
+ */
65
+ export interface SidecarSnapshot {
66
+ readonly schemaVersion: typeof SIDECAR_SCHEMA_VERSION;
67
+ readonly workspaceId: WorkspaceId;
68
+ readonly worktrees: readonly WorktreeRecord[];
69
+ readonly bindings: readonly SessionBinding[];
70
+ }
71
+ /**
72
+ * 一次 mutation 的返回值和完整下一快照;`changed: false` 表示幂等命中并跳过磁盘替换。
73
+ * Result and complete next snapshot of one mutation; `changed: false` marks an
74
+ * idempotent hit and skips disk replacement.
75
+ *
76
+ * 即使跳过写入,具体 repository 仍会校验返回的快照,避免幂等分支绕过不变量。
77
+ * Even when persistence is skipped, the concrete repository still validates
78
+ * the returned snapshot so an idempotent branch cannot bypass invariants.
79
+ */
80
+ export interface SidecarMutation<T> {
81
+ readonly result: T;
82
+ readonly snapshot: SidecarSnapshot;
83
+ readonly changed?: boolean;
84
+ }
85
+ /**
86
+ * sidecar 持久化端口;调用方通过一次 `mutate` 提交完整状态转换,不自行拼接 read/write 竞态窗口。
87
+ * Sidecar persistence port; callers submit a complete state transition through
88
+ * one `mutate` call instead of composing a racy read/write pair themselves.
89
+ */
90
+ export interface SidecarStore {
91
+ read(workspaceId: WorkspaceId): Promise<SidecarSnapshot>;
92
+ mutate<T>(workspaceId: WorkspaceId, mutation: (snapshot: SidecarSnapshot) => SidecarMutation<T> | Promise<SidecarMutation<T>>): Promise<T>;
93
+ }
94
+ /**
95
+ * Provider/Manage 边界使用的结构化错误;稳定 code 供调用方分支,details 保留诊断上下文。
96
+ * Structured error used across the Provider/Manage boundary; callers branch on
97
+ * the stable code while details retain diagnostic context.
98
+ */
99
+ export declare class WorktreeProviderError extends Error {
100
+ readonly code: WorktreeErrorCode;
101
+ readonly details: WorktreeErrorDetails;
102
+ constructor(code: WorktreeErrorCode, message: string, details?: WorktreeErrorDetails);
103
+ /**
104
+ * 生成可安全投影到 Remote/日志边界的纯数据表示,不包含 stack 或运行时原型。
105
+ * Produces a plain-data representation safe for Remote/logging boundaries,
106
+ * excluding the stack and runtime prototype.
107
+ */
108
+ toJSON(): {
109
+ code: WorktreeErrorCode;
110
+ message: string;
111
+ details: WorktreeErrorDetails;
112
+ };
113
+ }
114
+ /**
115
+ * 统一构造 Provider 错误,确保 code、message 和 details 使用同一错误类型传播。
116
+ * Constructs Provider errors consistently so code, message, and details travel
117
+ * through a single error type.
118
+ */
119
+ export declare function providerError(code: WorktreeErrorCode, message: string, details?: WorktreeErrorDetails): WorktreeProviderError;
120
+ /**
121
+ * 仅识别当前运行时中的真实 `WorktreeProviderError` 实例;反序列化后的同形对象不会通过。
122
+ * Recognizes genuine `WorktreeProviderError` instances in this runtime only;
123
+ * a structurally identical deserialized object does not pass this guard.
124
+ */
125
+ export declare function isWorktreeProviderError(error: unknown): error is WorktreeProviderError;
126
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/provider/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * 当前 sidecar 磁盘格式的唯一受支持版本;未知版本会被视为损坏,而不是被静默猜测或迁移。
3
+ * The only supported on-disk sidecar format; unknown versions are treated as
4
+ * corruption rather than guessed or silently migrated.
5
+ */
6
+ export const SIDECAR_SCHEMA_VERSION = 1;
7
+ /**
8
+ * Provider/Manage 边界使用的结构化错误;稳定 code 供调用方分支,details 保留诊断上下文。
9
+ * Structured error used across the Provider/Manage boundary; callers branch on
10
+ * the stable code while details retain diagnostic context.
11
+ */
12
+ export class WorktreeProviderError extends Error {
13
+ code;
14
+ details;
15
+ constructor(code, message, details = {}) {
16
+ super(message);
17
+ this.name = 'WorktreeProviderError';
18
+ this.code = code;
19
+ this.details = details;
20
+ }
21
+ /**
22
+ * 生成可安全投影到 Remote/日志边界的纯数据表示,不包含 stack 或运行时原型。
23
+ * Produces a plain-data representation safe for Remote/logging boundaries,
24
+ * excluding the stack and runtime prototype.
25
+ */
26
+ toJSON() {
27
+ return { code: this.code, message: this.message, details: this.details };
28
+ }
29
+ }
30
+ /**
31
+ * 统一构造 Provider 错误,确保 code、message 和 details 使用同一错误类型传播。
32
+ * Constructs Provider errors consistently so code, message, and details travel
33
+ * through a single error type.
34
+ */
35
+ export function providerError(code, message, details = {}) {
36
+ return new WorktreeProviderError(code, message, details);
37
+ }
38
+ /**
39
+ * 仅识别当前运行时中的真实 `WorktreeProviderError` 实例;反序列化后的同形对象不会通过。
40
+ * Recognizes genuine `WorktreeProviderError` instances in this runtime only;
41
+ * a structurally identical deserialized object does not pass this guard.
42
+ */
43
+ export function isWorktreeProviderError(error) {
44
+ return error instanceof WorktreeProviderError;
45
+ }
46
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/provider/types.ts"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAU,CAAC;AAwGjD;;;;GAIG;AACH,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IACrC,IAAI,CAAoB;IACxB,OAAO,CAAuB;IAEvC,YAAY,IAAuB,EAAE,OAAe,EAAE,UAAgC,EAAE;QACtF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;;;OAIG;IACH,MAAM;QACJ,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;IAC3E,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,IAAuB,EACvB,OAAe,EACf,UAAgC,EAAE;IAElC,OAAO,IAAI,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC3D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACpD,OAAO,KAAK,YAAY,qBAAqB,CAAC;AAChD,CAAC"}
@@ -0,0 +1,3 @@
1
+ /* Generated by @deepseek-ai/dsh-typert-generator from FaceModel — do not edit. */
2
+
3
+ export declare const TYPERT: unknown