engineering-memory 1.11.7 → 1.11.9
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/dispatcher/sections.mjs +1 -1
- package/install/files.mjs +114 -8
- package/install/installer.mjs +20 -4
- package/install/launcher.mjs +95 -0
- package/package.json +1 -1
- package/runtime/build.json +3 -0
- package/runtime/dist/src/config.js +3 -0
- package/runtime/dist/src/index.js +25 -81
- package/runtime/dist/src/mcp/server.js +14 -7
- package/runtime/dist/src/mcp/tool-host.js +12 -0
- package/runtime/dist/src/runtime/bridge-service.js +80 -20
- package/runtime/dist/src/runtime/create-bridge-service.js +82 -0
- package/runtime/dist/src/runtime/runtime-entry.js +16 -0
- package/runtime/dist/src/runtime/runtime-host.js +265 -0
- package/runtime/dist/src/runtime/worktree-pool.js +145 -32
- package/runtime/dist/src/utilities/files.js +4 -1
- package/skill/references/lifecycle.md +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { mkdir, readFile, readdir } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
-
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
5
5
|
import * as z from 'zod/v4';
|
|
6
6
|
import { GitInspector } from '../git/git-inspector.js';
|
|
7
7
|
import { atomicWrite, assertManagedPath, canonicalPath, ensureManagedDirectory, pathExists, readJson, removeFile, safeSegment, } from '../utilities/files.js';
|
|
@@ -37,11 +37,29 @@ const allocationSchema = z.object({
|
|
|
37
37
|
pendingDelivery: z.boolean(),
|
|
38
38
|
recoveredWithoutReservation: z.boolean().optional(),
|
|
39
39
|
reason: z.string().optional(),
|
|
40
|
+
root: z.string().optional(),
|
|
41
|
+
layout: z.union([z.literal(1), z.literal(2)]).optional(),
|
|
40
42
|
});
|
|
41
43
|
const registrySchema = z.object({
|
|
42
44
|
schemaVersion: z.literal(1),
|
|
43
45
|
projects: z.record(z.string(), z.object({ folder: z.string().regex(/^[a-zA-Z0-9._-]+$/), entries: z.array(allocationSchema) })),
|
|
44
46
|
});
|
|
47
|
+
export function managedBase(root, layout) {
|
|
48
|
+
return layout === 1
|
|
49
|
+
? join(root, 'engineeringmemory')
|
|
50
|
+
: join(root, 'engineering_memory', 'worktrees');
|
|
51
|
+
}
|
|
52
|
+
function projectDirectory(root, folder, layout) {
|
|
53
|
+
return layout === 1
|
|
54
|
+
? join(root, 'engineeringmemory', folder, 'worktrees')
|
|
55
|
+
: join(root, 'engineering_memory', 'worktrees', folder);
|
|
56
|
+
}
|
|
57
|
+
function worktreeName(folder, slot, layout) {
|
|
58
|
+
return folder + (layout === 1 ? '-worktree' : '_worktree') + slot;
|
|
59
|
+
}
|
|
60
|
+
function managedPath(root, folder, slot, layout) {
|
|
61
|
+
return join(projectDirectory(root, folder, layout), worktreeName(folder, slot, layout));
|
|
62
|
+
}
|
|
45
63
|
export class WorktreePool {
|
|
46
64
|
stateRoot;
|
|
47
65
|
ownerId;
|
|
@@ -53,6 +71,9 @@ export class WorktreePool {
|
|
|
53
71
|
startTimes = new Map();
|
|
54
72
|
ownStart;
|
|
55
73
|
documents;
|
|
74
|
+
legacyDocuments;
|
|
75
|
+
overrideDocuments;
|
|
76
|
+
overrideProblem;
|
|
56
77
|
before = new Map();
|
|
57
78
|
pendingWork;
|
|
58
79
|
constructor(stateRoot, options = {}) {
|
|
@@ -63,6 +84,7 @@ export class WorktreePool {
|
|
|
63
84
|
this.now = options.now ?? Date.now;
|
|
64
85
|
this.alive = options.processAlive ?? processAlive;
|
|
65
86
|
this.documents = options.documentsRoot;
|
|
87
|
+
this.legacyDocuments = options.legacyDocumentsRoot ?? options.documentsRoot;
|
|
66
88
|
this.pendingWork = options.pendingWork ?? (async () => false);
|
|
67
89
|
this.registryPath = join(stateRoot, 'worktree-pool.json');
|
|
68
90
|
}
|
|
@@ -79,8 +101,12 @@ export class WorktreePool {
|
|
|
79
101
|
const commonDir = await this.commonDirectory(input.repoRoot);
|
|
80
102
|
return this.transaction(async (registry) => {
|
|
81
103
|
if (!registry.projects[input.projectId]) {
|
|
82
|
-
const
|
|
83
|
-
const
|
|
104
|
+
const separator = process.platform === 'win32' ? '\\' : '/';
|
|
105
|
+
const bases = [];
|
|
106
|
+
for (const root of await this.scanRoots())
|
|
107
|
+
for (const layout of [1, 2])
|
|
108
|
+
bases.push(key(managedBase(root, layout)) + separator);
|
|
109
|
+
const orphan = (await this.gitPaths(input.repoRoot)).find((p) => bases.some((base) => key(p.path).startsWith(base)));
|
|
84
110
|
if (orphan)
|
|
85
111
|
throw refuse('Existing managed Git worktrees have no project index. Run worktree.reconcile before allocating more directories.');
|
|
86
112
|
}
|
|
@@ -107,7 +133,7 @@ export class WorktreePool {
|
|
|
107
133
|
if (paths.some((p) => p.branch === entry.branch && key(p.path) !== key(entry.repoRoot)))
|
|
108
134
|
throw refuse('The recorded branch is checked out at another location. Reconcile the moved worktree before resuming.');
|
|
109
135
|
if (paths.some((p) => key(p.path) === key(entry.repoRoot))) {
|
|
110
|
-
await assertManagedPath(await this.
|
|
136
|
+
await assertManagedPath(entry.root ?? (await this.legacyDocumentsDirectory()), entry.repoRoot, true);
|
|
111
137
|
if (await pathExists(entry.repoRoot))
|
|
112
138
|
throw refuse('The missing worktree reappeared; reconcile before recovery.');
|
|
113
139
|
await this.command(input.repoRoot, ['worktree', 'remove', entry.repoRoot]);
|
|
@@ -151,8 +177,10 @@ export class WorktreePool {
|
|
|
151
177
|
if (checkout)
|
|
152
178
|
throw refuse('This branch already has a checkout. Resume its task instead of creating or resetting the branch.');
|
|
153
179
|
let reusable;
|
|
180
|
+
const activeRoot = key(await this.documentsDirectory());
|
|
181
|
+
const legacyRoot = await this.legacyDocumentsDirectory();
|
|
154
182
|
for (const candidate of project.entries
|
|
155
|
-
.filter((e) => e.managed && e.commonDir === commonDir)
|
|
183
|
+
.filter((e) => e.managed && e.commonDir === commonDir && key(e.root ?? legacyRoot) === activeRoot)
|
|
156
184
|
.sort((a, b) => a.slot - b.slot)) {
|
|
157
185
|
if (candidate.pendingDelivery ||
|
|
158
186
|
['creating', 'quarantined'].includes(candidate.phase) ||
|
|
@@ -168,7 +196,7 @@ export class WorktreePool {
|
|
|
168
196
|
if (entry) {
|
|
169
197
|
const old = structuredClone(entry);
|
|
170
198
|
await this.releaseReservation(old);
|
|
171
|
-
const next = await this.entry(input, commonDir, old.repoRoot, old.slot, true);
|
|
199
|
+
const next = await this.entry(input, commonDir, old.repoRoot, old.slot, true, old.root ?? (await this.legacyDocumentsDirectory()), old.layout ?? 1);
|
|
172
200
|
project.entries[project.entries.indexOf(entry)] = next;
|
|
173
201
|
entry = next;
|
|
174
202
|
await this.save(registry);
|
|
@@ -179,14 +207,15 @@ export class WorktreePool {
|
|
|
179
207
|
if (project.entries.filter((e) => e.managed).length >= policy.maxWorktrees)
|
|
180
208
|
throw refuse('The managed worktree limit is full. worktree.list explains protected directories; finish delivery or safely release a clean task. No files were deleted.');
|
|
181
209
|
let slot = 1;
|
|
182
|
-
const
|
|
183
|
-
|
|
210
|
+
const active = await this.documentsDirectory();
|
|
211
|
+
const root = projectDirectory(active, project.folder, 2);
|
|
212
|
+
await ensureManagedDirectory(active, root);
|
|
184
213
|
while (project.entries.some((e) => e.slot === slot) ||
|
|
185
|
-
(await pathExists(join(root, project.folder
|
|
214
|
+
(await pathExists(join(root, worktreeName(project.folder, slot, 2)))))
|
|
186
215
|
slot++;
|
|
187
|
-
const path = join(root, project.folder
|
|
216
|
+
const path = join(root, worktreeName(project.folder, slot, 2));
|
|
188
217
|
await assertManagedPath(root, path, true);
|
|
189
|
-
entry = await this.entry(input, commonDir, path, slot, true);
|
|
218
|
+
entry = await this.entry(input, commonDir, path, slot, true, await this.documentsDirectory(), 2);
|
|
190
219
|
project.entries.push(entry);
|
|
191
220
|
await this.save(registry);
|
|
192
221
|
await this.git.createWorktree(input.repoRoot, input.branch, path, input.baseCommit);
|
|
@@ -210,6 +239,12 @@ export class WorktreePool {
|
|
|
210
239
|
.flatMap((p) => p.entries)
|
|
211
240
|
.find((e) => e.taskId === taskId && e.phase !== 'released') ?? null));
|
|
212
241
|
}
|
|
242
|
+
owns(entry) {
|
|
243
|
+
return (entry.phase !== 'released' &&
|
|
244
|
+
entry.owner !== null &&
|
|
245
|
+
entry.owner.id === this.ownerId &&
|
|
246
|
+
entry.owner.pid === process.pid);
|
|
247
|
+
}
|
|
213
248
|
async forPath(projectId, repoRoot) {
|
|
214
249
|
return this.transaction(async (registry) => structuredClone(registry.projects[projectId]?.entries.find((e) => key(e.repoRoot) === key(repoRoot)) ??
|
|
215
250
|
null));
|
|
@@ -326,24 +361,19 @@ export class WorktreePool {
|
|
|
326
361
|
let project = registry.projects[projectId];
|
|
327
362
|
const locations = await this.gitPaths(repoRoot);
|
|
328
363
|
const candidates = [];
|
|
329
|
-
const
|
|
364
|
+
const managedRoots = await this.scanRoots();
|
|
330
365
|
for (const location of locations) {
|
|
331
366
|
if (project?.entries.some((entry) => key(entry.repoRoot) === key(location.path)))
|
|
332
367
|
continue;
|
|
333
368
|
if (!(await pathExists(location.path)))
|
|
334
369
|
continue;
|
|
335
370
|
const parent = dirname(location.path);
|
|
336
|
-
const folder = dirname(parent).split(/[\\/]/).pop();
|
|
337
371
|
const name = location.path.split(/[\\/]/).pop();
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const prefix = folder + '-worktree';
|
|
341
|
-
const slot = Number(name.slice(prefix.length));
|
|
342
|
-
if (!name.startsWith(prefix) ||
|
|
343
|
-
!Number.isSafeInteger(slot) ||
|
|
344
|
-
slot < 1 ||
|
|
345
|
-
name !== prefix + slot)
|
|
372
|
+
const placement = this.placement(managedRoots, parent, name);
|
|
373
|
+
if (!placement)
|
|
346
374
|
continue;
|
|
375
|
+
const { documents, folder, slot, layout } = placement;
|
|
376
|
+
const managedRoot = managedBase(documents, layout);
|
|
347
377
|
try {
|
|
348
378
|
await assertManagedPath(managedRoot, location.path, false);
|
|
349
379
|
if ((await this.commonDirectory(location.path)) !== commonDir)
|
|
@@ -357,7 +387,7 @@ export class WorktreePool {
|
|
|
357
387
|
continue;
|
|
358
388
|
if (Object.entries(registry.projects).some(([id, value]) => id !== projectId && key(value.folder) === key(folder)))
|
|
359
389
|
continue;
|
|
360
|
-
candidates.push({ path: location.path, folder, slot });
|
|
390
|
+
candidates.push({ path: location.path, folder, slot, root: documents, layout });
|
|
361
391
|
}
|
|
362
392
|
if (!project) {
|
|
363
393
|
const folders = [...new Set(candidates.map((candidate) => candidate.folder))];
|
|
@@ -405,6 +435,8 @@ export class WorktreePool {
|
|
|
405
435
|
repoRoot: await canonicalPath(location.path),
|
|
406
436
|
slot: location.slot,
|
|
407
437
|
managed: true,
|
|
438
|
+
root: resolve(location.root),
|
|
439
|
+
layout: location.layout,
|
|
408
440
|
externalTaskId: reservation?.decision.externalTaskId ??
|
|
409
441
|
'unregistered-' + sha256(key(location.path)).slice(0, 20),
|
|
410
442
|
taskId: reservation?.decision.taskId,
|
|
@@ -451,9 +483,11 @@ export class WorktreePool {
|
|
|
451
483
|
});
|
|
452
484
|
return discoverOnly ? [] : this.list(projectId, policy);
|
|
453
485
|
}
|
|
454
|
-
async entry(input, commonDir, repoRoot, slot, managed) {
|
|
486
|
+
async entry(input, commonDir, repoRoot, slot, managed, root, layout) {
|
|
455
487
|
const generation = randomUUID();
|
|
456
488
|
return {
|
|
489
|
+
...(root ? { root: resolve(root) } : {}),
|
|
490
|
+
...(layout ? { layout } : {}),
|
|
457
491
|
projectId: input.projectId,
|
|
458
492
|
repoFingerprint: input.repoFingerprint,
|
|
459
493
|
commonDir,
|
|
@@ -626,9 +660,9 @@ export class WorktreePool {
|
|
|
626
660
|
.slice(0, 70);
|
|
627
661
|
if (/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(folder))
|
|
628
662
|
folder = 'project-' + folder;
|
|
629
|
-
const root = join(await this.documentsDirectory(), 'engineeringmemory');
|
|
630
663
|
if (Object.values(registry.projects).some((p) => key(p.folder) === key(folder)) ||
|
|
631
|
-
(await pathExists(
|
|
664
|
+
(await pathExists(projectDirectory(await this.documentsDirectory(), folder, 2))) ||
|
|
665
|
+
(await pathExists(join(managedBase(await this.legacyDocumentsDirectory(), 1), folder))))
|
|
632
666
|
folder += '-' + sha256(projectId).slice(0, 8);
|
|
633
667
|
if (Object.values(registry.projects).some((p) => key(p.folder) === key(folder)))
|
|
634
668
|
throw refuse('The managed project folder identity conflicts with another project.');
|
|
@@ -697,9 +731,87 @@ export class WorktreePool {
|
|
|
697
731
|
return null;
|
|
698
732
|
}
|
|
699
733
|
}
|
|
734
|
+
async managedRoot() {
|
|
735
|
+
const root = managedBase(await this.documentsDirectory(), 2);
|
|
736
|
+
return {
|
|
737
|
+
root,
|
|
738
|
+
override: join(this.stateRoot, 'worktree-root.json'),
|
|
739
|
+
synced: /onedrive|dropbox|icloud|mobile documents|google\s?drive/i.test(root),
|
|
740
|
+
...(this.overrideProblem ? { problem: this.overrideProblem } : {}),
|
|
741
|
+
};
|
|
742
|
+
}
|
|
700
743
|
async documentsDirectory() {
|
|
744
|
+
if (this.overrideDocuments)
|
|
745
|
+
return this.overrideDocuments;
|
|
746
|
+
const overridePath = join(this.stateRoot, 'worktree-root.json');
|
|
747
|
+
this.overrideProblem = undefined;
|
|
748
|
+
let override = null;
|
|
749
|
+
try {
|
|
750
|
+
override = await readJson(overridePath);
|
|
751
|
+
}
|
|
752
|
+
catch (error) {
|
|
753
|
+
this.overrideProblem = `${overridePath} could not be read and is ignored: ${error instanceof Error ? error.message : String(error)}`;
|
|
754
|
+
}
|
|
755
|
+
if (override && typeof override.root === 'string' && isAbsolute(override.root)) {
|
|
756
|
+
try {
|
|
757
|
+
await mkdir(override.root, { recursive: true }).catch((error) => {
|
|
758
|
+
if (!['EEXIST', 'EPERM'].includes(error.code ?? ''))
|
|
759
|
+
throw error;
|
|
760
|
+
});
|
|
761
|
+
this.overrideDocuments = resolve(override.root);
|
|
762
|
+
return this.overrideDocuments;
|
|
763
|
+
}
|
|
764
|
+
catch (error) {
|
|
765
|
+
this.overrideProblem = `${overridePath} names ${override.root}, which could not be created and is ignored: ${error instanceof Error ? error.message : String(error)}`;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
else if (override) {
|
|
769
|
+
this.overrideProblem = `${overridePath} must contain {"root": "<absolute directory>"} and is ignored.`;
|
|
770
|
+
}
|
|
771
|
+
return this.defaultDocumentsDirectory();
|
|
772
|
+
}
|
|
773
|
+
async defaultDocumentsDirectory() {
|
|
701
774
|
if (this.documents)
|
|
702
775
|
return resolve(this.documents);
|
|
776
|
+
this.documents =
|
|
777
|
+
process.platform === 'win32' ? (process.env.SystemDrive ?? 'C:') + '\\' : homedir();
|
|
778
|
+
return resolve(this.documents);
|
|
779
|
+
}
|
|
780
|
+
async scanRoots() {
|
|
781
|
+
return [
|
|
782
|
+
...new Map([
|
|
783
|
+
await this.documentsDirectory(),
|
|
784
|
+
await this.defaultDocumentsDirectory(),
|
|
785
|
+
await this.legacyDocumentsDirectory(),
|
|
786
|
+
].map((root) => [key(root), root])).values(),
|
|
787
|
+
];
|
|
788
|
+
}
|
|
789
|
+
placement(roots, parent, name) {
|
|
790
|
+
for (const documents of roots) {
|
|
791
|
+
const parentName = parent.split(/[\\/]/).pop() ?? '';
|
|
792
|
+
const grandParent = dirname(parent);
|
|
793
|
+
const legacyFolder = grandParent.split(/[\\/]/).pop() ?? '';
|
|
794
|
+
const attempts = [
|
|
795
|
+
{ folder: parentName, layout: 2 },
|
|
796
|
+
{ folder: legacyFolder, layout: 1 },
|
|
797
|
+
];
|
|
798
|
+
for (const { folder, layout } of attempts) {
|
|
799
|
+
if (!folder || key(parent) !== key(projectDirectory(documents, folder, layout)))
|
|
800
|
+
continue;
|
|
801
|
+
const prefix = folder + (layout === 1 ? '-worktree' : '_worktree');
|
|
802
|
+
const slot = Number(name.slice(prefix.length));
|
|
803
|
+
if (name.startsWith(prefix) &&
|
|
804
|
+
Number.isSafeInteger(slot) &&
|
|
805
|
+
slot >= 1 &&
|
|
806
|
+
name === prefix + slot)
|
|
807
|
+
return { documents, folder, slot, layout };
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
async legacyDocumentsDirectory() {
|
|
813
|
+
if (this.legacyDocuments)
|
|
814
|
+
return resolve(this.legacyDocuments);
|
|
703
815
|
if (process.platform === 'win32') {
|
|
704
816
|
const result = await this.runner.run('powershell.exe', [
|
|
705
817
|
'-NoProfile',
|
|
@@ -709,19 +821,18 @@ export class WorktreePool {
|
|
|
709
821
|
], {});
|
|
710
822
|
if (result.exitCode !== 0 || !result.stdout.trim())
|
|
711
823
|
throw refuse('The Windows Documents folder could not be resolved.');
|
|
712
|
-
this.
|
|
824
|
+
this.legacyDocuments = result.stdout.trim();
|
|
713
825
|
}
|
|
714
826
|
else if (process.platform === 'linux') {
|
|
715
827
|
const result = await this.runner.run('xdg-user-dir', ['DOCUMENTS'], {}).catch(() => null);
|
|
716
|
-
this.
|
|
828
|
+
this.legacyDocuments =
|
|
717
829
|
result?.exitCode === 0 && result.stdout.trim()
|
|
718
830
|
? result.stdout.trim()
|
|
719
831
|
: join(homedir(), 'Documents');
|
|
720
832
|
}
|
|
721
833
|
else
|
|
722
|
-
this.
|
|
723
|
-
|
|
724
|
-
return resolve(this.documents);
|
|
834
|
+
this.legacyDocuments = join(homedir(), 'Documents');
|
|
835
|
+
return resolve(this.legacyDocuments);
|
|
725
836
|
}
|
|
726
837
|
async transaction(work) {
|
|
727
838
|
await mkdir(this.stateRoot, { recursive: true });
|
|
@@ -764,8 +875,10 @@ export class WorktreePool {
|
|
|
764
875
|
throw refuse('The registry contains conflicting task ownership. Restore a validated backup with worktree.reconcile.');
|
|
765
876
|
paths.add(key(entry.repoRoot));
|
|
766
877
|
if (entry.managed) {
|
|
767
|
-
const documents = await this.
|
|
768
|
-
const expected =
|
|
878
|
+
const documents = entry.root ?? (await this.legacyDocumentsDirectory());
|
|
879
|
+
const expected = entry.slot
|
|
880
|
+
? managedPath(documents, project.folder, entry.slot, entry.layout ?? 1)
|
|
881
|
+
: '';
|
|
769
882
|
if (!entry.slot || key(entry.repoRoot) !== key(expected))
|
|
770
883
|
throw refuse('A managed worktree path does not match its project Documents directory. Reconcile before continuing.');
|
|
771
884
|
await assertManagedPath(documents, entry.repoRoot, true);
|
|
@@ -118,7 +118,10 @@ export async function assertManagedPath(root, target, allowMissingTarget) {
|
|
|
118
118
|
export async function ensureManagedDirectory(root, directory) {
|
|
119
119
|
const resolvedRoot = resolve(root);
|
|
120
120
|
const resolvedDirectory = ensureWithinRoot(resolvedRoot, directory);
|
|
121
|
-
await mkdir(resolvedRoot, { recursive: true })
|
|
121
|
+
await mkdir(resolvedRoot, { recursive: true }).catch((error) => {
|
|
122
|
+
if (!isNodeError(error) || !['EEXIST', 'EPERM'].includes(error.code ?? ''))
|
|
123
|
+
throw error;
|
|
124
|
+
});
|
|
122
125
|
const rootStat = await lstat(resolvedRoot);
|
|
123
126
|
if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
|
|
124
127
|
throw new Error(`Managed root must be a real directory: ${resolvedRoot}`);
|
|
@@ -14,7 +14,7 @@ Before a new write or scaffold task, read `session.entry` and the project Git pr
|
|
|
14
14
|
|
|
15
15
|
Call `task.branch` with the stable `externalTaskId` and `repoRoot`. Its native forms show configured branch names, the current commit, another branch, and the explicit keep-current option. New branches always use a separate managed worktree. Configured remote bases are fetched and their exact commits pinned; after a fetch failure select an explicit local source through a new native decision, never silently use a cached branch. Existing task decisions survive retries and restarts. Resume their recorded branch rather than recreating or resetting it.
|
|
16
16
|
|
|
17
|
-
Use the returned `repoRoot` for **every** file read/write, terminal, context, validation and Git/delivery operation. The user-local pool is shared by Codex and Claude. Managed directories live in the
|
|
17
|
+
Use the returned `repoRoot` for **every** file read/write, terminal, context, validation and Git/delivery operation. The user-local pool is shared by Codex and Claude. Managed directories live under `engineering_memory/worktrees/<stable-project-folder>/<folder>_worktreeN`, at the root of the system drive on Windows (`C:\engineering_memory\worktrees\...`) and in the home directory elsewhere, or under the absolute directory named by `worktree-root.json` in the user-level API state directory when that file exists; `worktree.list` reports the effective root, and worktrees created by earlier clients under the Documents folder keep working where they are. Do not supply arbitrary `worktreePath` values or create ad-hoc siblings. Independent clones cannot reuse each other's worktrees. `worktree.list` explains which slots are active, inactive but protected, or safely reusable. The versioned backend policy defaults to 50 directories per project/computer, 30-second heartbeats and 10-minute inactivity. Protected inactive directories still count toward the limit. Only a global admin changes `worktree.set_policy`; it is not an environment setting.
|
|
18
18
|
|
|
19
19
|
Renew `task.heartbeat` using the exact returned task, path and ownership generation during actual work and at approximately the cached heartbeat interval during long local commands. The bridge renews while its own task operation is running. Never run a perpetual heartbeat for an idle chat or MCP process. Inactivity never authorizes takeover of a live owner or deletion of files. Before writing after interruption, use `session.resume`; follow any returned worktree redirect and resume there. A stale generation cannot renew, release, verify or commit another owner's work.
|
|
20
20
|
|