@cleocode/worktree 2026.5.38 → 2026.5.40
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/copy-on-write.d.ts +37 -0
- package/dist/copy-on-write.d.ts.map +1 -0
- package/dist/copy-on-write.js +118 -0
- package/dist/copy-on-write.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/worktree-create.d.ts +22 -4
- package/dist/worktree-create.d.ts.map +1 -1
- package/dist/worktree-create.js +75 -12
- package/dist/worktree-create.js.map +1 -1
- package/dist/worktree-destroy.d.ts +7 -4
- package/dist/worktree-destroy.d.ts.map +1 -1
- package/dist/worktree-destroy.js +68 -8
- package/dist/worktree-destroy.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy-on-write file utility for `@cleocode/worktree`.
|
|
3
|
+
*
|
|
4
|
+
* Provides efficient path copying using filesystem-level copy-on-write
|
|
5
|
+
* (reflink / clonefile) when available, falling back to regular recursive copy.
|
|
6
|
+
*
|
|
7
|
+
* Platform support:
|
|
8
|
+
* - macOS (darwin): APFS clonefile via `cp -cR`
|
|
9
|
+
* - Linux: btrfs / xfs / zfs reflink via `cp -R --reflink=auto`
|
|
10
|
+
* - Windows: `fs.copyFile` with `COPYFILE_FICLONE` (Node 24+) for files,
|
|
11
|
+
* regular recursive copy for directories
|
|
12
|
+
*
|
|
13
|
+
* @task T1161
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Copy multiple paths from a source directory to a target directory using
|
|
17
|
+
* copy-on-write when available.
|
|
18
|
+
*
|
|
19
|
+
* Each path in `paths` is treated as relative to `sourceDir` and copied to
|
|
20
|
+
* the corresponding location under `targetDir`.
|
|
21
|
+
*
|
|
22
|
+
* Missing source paths are skipped with a warning written to `stderr`.
|
|
23
|
+
* Existing target paths are skipped without overwriting.
|
|
24
|
+
*
|
|
25
|
+
* If a copy-on-write attempt fails, a regular recursive copy is attempted
|
|
26
|
+
* as fallback. If that also fails, the path is recorded in `failed`.
|
|
27
|
+
*
|
|
28
|
+
* @param paths - Array of relative paths to copy.
|
|
29
|
+
* @param sourceDir - Absolute path to the source directory.
|
|
30
|
+
* @param targetDir - Absolute path to the target directory.
|
|
31
|
+
* @returns Object with arrays of successfully copied and failed paths.
|
|
32
|
+
*/
|
|
33
|
+
export declare function copyPathsWithReflock(paths: string[], sourceDir: string, targetDir: string): Promise<{
|
|
34
|
+
copied: string[];
|
|
35
|
+
failed: string[];
|
|
36
|
+
}>;
|
|
37
|
+
//# sourceMappingURL=copy-on-write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-on-write.d.ts","sourceRoot":"","sources":["../src/copy-on-write.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,EAAE,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAyCjD"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy-on-write file utility for `@cleocode/worktree`.
|
|
3
|
+
*
|
|
4
|
+
* Provides efficient path copying using filesystem-level copy-on-write
|
|
5
|
+
* (reflink / clonefile) when available, falling back to regular recursive copy.
|
|
6
|
+
*
|
|
7
|
+
* Platform support:
|
|
8
|
+
* - macOS (darwin): APFS clonefile via `cp -cR`
|
|
9
|
+
* - Linux: btrfs / xfs / zfs reflink via `cp -R --reflink=auto`
|
|
10
|
+
* - Windows: `fs.copyFile` with `COPYFILE_FICLONE` (Node 24+) for files,
|
|
11
|
+
* regular recursive copy for directories
|
|
12
|
+
*
|
|
13
|
+
* @task T1161
|
|
14
|
+
*/
|
|
15
|
+
import { execFile } from 'node:child_process';
|
|
16
|
+
import { constants, existsSync, promises as fs, mkdirSync } from 'node:fs';
|
|
17
|
+
import { dirname, join } from 'node:path';
|
|
18
|
+
import { promisify } from 'node:util';
|
|
19
|
+
const execFileAsync = promisify(execFile);
|
|
20
|
+
/**
|
|
21
|
+
* Copy multiple paths from a source directory to a target directory using
|
|
22
|
+
* copy-on-write when available.
|
|
23
|
+
*
|
|
24
|
+
* Each path in `paths` is treated as relative to `sourceDir` and copied to
|
|
25
|
+
* the corresponding location under `targetDir`.
|
|
26
|
+
*
|
|
27
|
+
* Missing source paths are skipped with a warning written to `stderr`.
|
|
28
|
+
* Existing target paths are skipped without overwriting.
|
|
29
|
+
*
|
|
30
|
+
* If a copy-on-write attempt fails, a regular recursive copy is attempted
|
|
31
|
+
* as fallback. If that also fails, the path is recorded in `failed`.
|
|
32
|
+
*
|
|
33
|
+
* @param paths - Array of relative paths to copy.
|
|
34
|
+
* @param sourceDir - Absolute path to the source directory.
|
|
35
|
+
* @param targetDir - Absolute path to the target directory.
|
|
36
|
+
* @returns Object with arrays of successfully copied and failed paths.
|
|
37
|
+
*/
|
|
38
|
+
export async function copyPathsWithReflock(paths, sourceDir, targetDir) {
|
|
39
|
+
const copied = [];
|
|
40
|
+
const failed = [];
|
|
41
|
+
for (const relativePath of paths) {
|
|
42
|
+
const sourcePath = join(sourceDir, relativePath);
|
|
43
|
+
const targetPath = join(targetDir, relativePath);
|
|
44
|
+
if (!existsSync(sourcePath)) {
|
|
45
|
+
process.stderr.write(`[copy-on-write] skipping missing source: ${sourcePath}\n`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (existsSync(targetPath)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
process.stderr.write(`[copy-on-write] failed to create parent directory for: ${targetPath}\n`);
|
|
56
|
+
failed.push(relativePath);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
await copyWithReflock(sourcePath, targetPath);
|
|
61
|
+
copied.push(relativePath);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
try {
|
|
65
|
+
await copyRegular(sourcePath, targetPath);
|
|
66
|
+
copied.push(relativePath);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
failed.push(relativePath);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { copied, failed };
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Attempt a copy-on-write copy from source to target.
|
|
77
|
+
*
|
|
78
|
+
* @param sourcePath - Absolute path to the source file or directory.
|
|
79
|
+
* @param targetPath - Absolute path to the target location.
|
|
80
|
+
* @throws Error if the copy operation fails or the platform is unsupported.
|
|
81
|
+
*/
|
|
82
|
+
async function copyWithReflock(sourcePath, targetPath) {
|
|
83
|
+
const platform = process.platform;
|
|
84
|
+
if (platform === 'darwin') {
|
|
85
|
+
await execFileAsync('cp', ['-cR', sourcePath, targetPath]);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (platform === 'linux') {
|
|
89
|
+
await execFileAsync('cp', ['-R', '--reflink=auto', sourcePath, targetPath]);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (platform === 'win32') {
|
|
93
|
+
const stat = await fs.stat(sourcePath);
|
|
94
|
+
if (stat.isDirectory()) {
|
|
95
|
+
throw new Error('Windows directories do not support copy-on-write');
|
|
96
|
+
}
|
|
97
|
+
await fs.copyFile(sourcePath, targetPath, constants.COPYFILE_FICLONE);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Perform a regular recursive copy as fallback.
|
|
104
|
+
*
|
|
105
|
+
* @param sourcePath - Absolute path to the source file or directory.
|
|
106
|
+
* @param targetPath - Absolute path to the target location.
|
|
107
|
+
* @throws Error if the copy operation fails.
|
|
108
|
+
*/
|
|
109
|
+
async function copyRegular(sourcePath, targetPath) {
|
|
110
|
+
const stat = await fs.stat(sourcePath);
|
|
111
|
+
if (stat.isDirectory()) {
|
|
112
|
+
await fs.cp(sourcePath, targetPath, { recursive: true });
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=copy-on-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-on-write.js","sourceRoot":"","sources":["../src/copy-on-write.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
export type { CreateWorktreeOptions, CreateWorktreeResult, DestroyWorktreeOptions, DestroyWorktreeResult, ListWorktreesOptions, PruneWorktreesOptions, PruneWorktreesResult, WorktreeHook, WorktreeHookResult, WorktreeIncludePattern, WorktreeListEntry, } from '@cleocode/contracts';
|
|
24
24
|
export type { LegacyMergeResult, LegacyWorktreeConfig, LegacyWorktreeEntry, LegacyWorktreeHandle, LegacyWorktreeRequest, } from './compat.js';
|
|
25
25
|
export { legacyCreateWorktree, legacyListWorktrees, legacyMergeWorktree, legacyResolveWorktreeRoot, } from './compat.js';
|
|
26
|
+
export { copyPathsWithReflock } from './copy-on-write.js';
|
|
26
27
|
export { createWorktree } from './worktree-create.js';
|
|
27
28
|
export { destroyWorktree } from './worktree-destroy.js';
|
|
28
29
|
export { runWorktreeHooks } from './worktree-hooks.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,EAClB,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACV,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EACL,aAAa,EACb,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,EAClB,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACV,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EACL,aAAa,EACb,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* @adr ADR-055
|
|
22
22
|
*/
|
|
23
23
|
export { legacyCreateWorktree, legacyListWorktrees, legacyMergeWorktree, legacyResolveWorktreeRoot, } from './compat.js';
|
|
24
|
+
export { copyPathsWithReflock } from './copy-on-write.js';
|
|
24
25
|
export { createWorktree } from './worktree-create.js';
|
|
25
26
|
export { destroyWorktree } from './worktree-destroy.js';
|
|
26
27
|
export { runWorktreeHooks } from './worktree-hooks.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAwBH,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EACL,aAAa,EACb,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAwBH,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EACL,aAAa,EACb,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -12,20 +12,37 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @task T1161
|
|
14
14
|
*/
|
|
15
|
-
import type { CreateWorktreeOptions, CreateWorktreeResult } from '@cleocode/contracts';
|
|
15
|
+
import type { CreateWorktreeOptions, CreateWorktreeResult, WorktreeHookResult } from '@cleocode/contracts';
|
|
16
|
+
/**
|
|
17
|
+
* Extended result type including the bootstrap field.
|
|
18
|
+
*
|
|
19
|
+
* The contracts package will be updated separately to add this field to
|
|
20
|
+
* {@link CreateWorktreeResult}; this local extension allows the implementation
|
|
21
|
+
* to compile in the interim.
|
|
22
|
+
*/
|
|
23
|
+
interface CreateWorktreeResultWithBootstrap extends CreateWorktreeResult {
|
|
24
|
+
bootstrap: {
|
|
25
|
+
copiedPaths: string[];
|
|
26
|
+
failedPaths: string[];
|
|
27
|
+
hookResults: WorktreeHookResult[];
|
|
28
|
+
};
|
|
29
|
+
}
|
|
16
30
|
/**
|
|
17
31
|
* Create a git worktree for an agent task.
|
|
18
32
|
*
|
|
19
33
|
* Steps:
|
|
20
34
|
* 1. Resolve paths and project hash from the project root.
|
|
21
|
-
* 2. Remove stale worktree at the same path if it exists.
|
|
35
|
+
* 2. Remove stale worktree at the same path if it exists (dirty worktrees are preserved).
|
|
22
36
|
* 3. If `task/<taskId>` branch already exists (leftover from a prior aborted
|
|
23
37
|
* spawn), attach to it via `git worktree add <path> <branch>` (no `-b`).
|
|
24
38
|
* Otherwise create a new branch via `git worktree add -b <branch> <path> <baseRef>`.
|
|
25
39
|
* 4. Optionally apply `git worktree lock` to prevent pruning.
|
|
26
40
|
* 5. Run declarative `post-create` hooks.
|
|
27
41
|
* 6. Apply `.cleo/worktree-include` glob patterns (symlinks).
|
|
28
|
-
* 7.
|
|
42
|
+
* 7. Copy `node_modules` and `packages/ * /dist` via copy-on-write when not already
|
|
43
|
+
* covered by worktree-include patterns.
|
|
44
|
+
* 8. Run declarative `post-start` hooks.
|
|
45
|
+
* 9. Build and return the {@link CreateWorktreeResult}.
|
|
29
46
|
*
|
|
30
47
|
* Branch-reuse semantics: when a prior spawn aborted after creating the branch
|
|
31
48
|
* but before the worker committed anything, the branch still points to
|
|
@@ -41,5 +58,6 @@ import type { CreateWorktreeOptions, CreateWorktreeResult } from '@cleocode/cont
|
|
|
41
58
|
* @task T1161
|
|
42
59
|
* @task T1878
|
|
43
60
|
*/
|
|
44
|
-
export declare function createWorktree(projectRoot: string, options: CreateWorktreeOptions): Promise<
|
|
61
|
+
export declare function createWorktree(projectRoot: string, options: CreateWorktreeOptions): Promise<CreateWorktreeResultWithBootstrap>;
|
|
62
|
+
export {};
|
|
45
63
|
//# sourceMappingURL=worktree-create.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktree-create.d.ts","sourceRoot":"","sources":["../src/worktree-create.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"worktree-create.d.ts","sourceRoot":"","sources":["../src/worktree-create.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAEnB,MAAM,qBAAqB,CAAC;AAG7B;;;;;;GAMG;AACH,UAAU,iCAAkC,SAAQ,oBAAoB;IACtE,SAAS,EAAE;QACT,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,WAAW,EAAE,kBAAkB,EAAE,CAAC;KACnC,CAAC;CACH;AAiCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,iCAAiC,CAAC,CAmK5C"}
|
package/dist/worktree-create.js
CHANGED
|
@@ -12,25 +12,53 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @task T1161
|
|
14
14
|
*/
|
|
15
|
-
import { existsSync, mkdirSync, rmSync } from 'node:fs';
|
|
15
|
+
import { existsSync, mkdirSync, readdirSync, rmSync } from 'node:fs';
|
|
16
16
|
import { join } from 'node:path';
|
|
17
|
+
import { copyPathsWithReflock } from './copy-on-write.js';
|
|
17
18
|
import { getGitRoot, gitSilent, gitSync, resolveHeadRef } from './git.js';
|
|
18
19
|
import { computeProjectHash, resolveTaskWorktreePath, resolveWorktreeRootForHash, } from './paths.js';
|
|
19
20
|
import { runWorktreeHooks } from './worktree-hooks.js';
|
|
20
21
|
import { applyIncludePatterns, loadWorktreeIncludePatterns } from './worktree-include.js';
|
|
22
|
+
function isPathSpecifiedInInclude(patterns, targetPath) {
|
|
23
|
+
return patterns.some((p) => {
|
|
24
|
+
if (p.negated)
|
|
25
|
+
return false;
|
|
26
|
+
if (p.pattern === targetPath)
|
|
27
|
+
return true;
|
|
28
|
+
if (p.pattern.startsWith(`${targetPath}/`))
|
|
29
|
+
return true;
|
|
30
|
+
if (targetPath.startsWith(`${p.pattern}/`))
|
|
31
|
+
return true;
|
|
32
|
+
return false;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function isPackagesDistSpecifiedInInclude(patterns) {
|
|
36
|
+
return patterns.some((p) => {
|
|
37
|
+
if (p.negated)
|
|
38
|
+
return false;
|
|
39
|
+
if (p.pattern === 'packages/*/dist')
|
|
40
|
+
return true;
|
|
41
|
+
if (p.pattern.startsWith('packages/') && p.pattern.includes('/dist'))
|
|
42
|
+
return true;
|
|
43
|
+
return false;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
21
46
|
/**
|
|
22
47
|
* Create a git worktree for an agent task.
|
|
23
48
|
*
|
|
24
49
|
* Steps:
|
|
25
50
|
* 1. Resolve paths and project hash from the project root.
|
|
26
|
-
* 2. Remove stale worktree at the same path if it exists.
|
|
51
|
+
* 2. Remove stale worktree at the same path if it exists (dirty worktrees are preserved).
|
|
27
52
|
* 3. If `task/<taskId>` branch already exists (leftover from a prior aborted
|
|
28
53
|
* spawn), attach to it via `git worktree add <path> <branch>` (no `-b`).
|
|
29
54
|
* Otherwise create a new branch via `git worktree add -b <branch> <path> <baseRef>`.
|
|
30
55
|
* 4. Optionally apply `git worktree lock` to prevent pruning.
|
|
31
56
|
* 5. Run declarative `post-create` hooks.
|
|
32
57
|
* 6. Apply `.cleo/worktree-include` glob patterns (symlinks).
|
|
33
|
-
* 7.
|
|
58
|
+
* 7. Copy `node_modules` and `packages/ * /dist` via copy-on-write when not already
|
|
59
|
+
* covered by worktree-include patterns.
|
|
60
|
+
* 8. Run declarative `post-start` hooks.
|
|
61
|
+
* 9. Build and return the {@link CreateWorktreeResult}.
|
|
34
62
|
*
|
|
35
63
|
* Branch-reuse semantics: when a prior spawn aborted after creating the branch
|
|
36
64
|
* but before the worker committed anything, the branch still points to
|
|
@@ -57,15 +85,22 @@ export async function createWorktree(projectRoot, options) {
|
|
|
57
85
|
const baseRef = options.baseRef ?? resolveHeadRef(gitRoot);
|
|
58
86
|
const worktreePath = resolveTaskWorktreePath(projectHash, taskId);
|
|
59
87
|
// Remove stale worktree at this path if it exists (left from a prior run).
|
|
88
|
+
// Dirty worktrees are preserved to avoid losing uncommitted agent work.
|
|
60
89
|
if (existsSync(worktreePath)) {
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
90
|
+
const porcelain = gitSync(['status', '--porcelain'], worktreePath);
|
|
91
|
+
if (porcelain.trim() !== '') {
|
|
92
|
+
process.stderr.write(`[worktree] WARNING: preserving dirty worktree at ${worktreePath} (uncommitted changes detected)\n`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
gitSilent(['worktree', 'unlock', worktreePath], gitRoot);
|
|
96
|
+
if (!gitSilent(['worktree', 'remove', '--force', worktreePath], gitRoot)) {
|
|
97
|
+
rmSync(worktreePath, { recursive: true, force: true });
|
|
98
|
+
}
|
|
99
|
+
// Best-effort: delete the stale branch so we always start fresh when the
|
|
100
|
+
// directory existed (the branch-reuse path below handles the case where
|
|
101
|
+
// only the branch survives without a directory).
|
|
102
|
+
gitSilent(['branch', '-D', branch], gitRoot);
|
|
64
103
|
}
|
|
65
|
-
// Best-effort: delete the stale branch so we always start fresh when the
|
|
66
|
-
// directory existed (the branch-reuse path below handles the case where
|
|
67
|
-
// only the branch survives without a directory).
|
|
68
|
-
gitSilent(['branch', '-D', branch], gitRoot);
|
|
69
104
|
}
|
|
70
105
|
// Check whether the branch already exists without a worktree directory.
|
|
71
106
|
// This happens when a prior spawn created the branch but the worktree
|
|
@@ -99,13 +134,36 @@ export async function createWorktree(projectRoot, options) {
|
|
|
99
134
|
}
|
|
100
135
|
const createdAt = new Date().toISOString();
|
|
101
136
|
// Run post-create hooks before returning the handle.
|
|
102
|
-
const
|
|
137
|
+
const postCreateHookResults = await runWorktreeHooks(hooks, 'post-create', worktreePath);
|
|
103
138
|
// Apply .cleo/worktree-include patterns.
|
|
104
139
|
let appliedPatterns = [];
|
|
105
140
|
if (applyInclude) {
|
|
106
141
|
const patterns = loadWorktreeIncludePatterns(projectRoot);
|
|
107
142
|
appliedPatterns = applyIncludePatterns(patterns, projectRoot, worktreePath);
|
|
108
143
|
}
|
|
144
|
+
// Copy-on-write bootstrap: node_modules and packages/*/dist when not already
|
|
145
|
+
// specified in .cleo/worktree-include.
|
|
146
|
+
const pathsToCopy = [];
|
|
147
|
+
const includePatterns = applyInclude ? loadWorktreeIncludePatterns(projectRoot) : [];
|
|
148
|
+
if (!isPathSpecifiedInInclude(includePatterns, 'node_modules')) {
|
|
149
|
+
pathsToCopy.push('node_modules');
|
|
150
|
+
}
|
|
151
|
+
if (!isPackagesDistSpecifiedInInclude(includePatterns)) {
|
|
152
|
+
const packagesDir = join(projectRoot, 'packages');
|
|
153
|
+
if (existsSync(packagesDir)) {
|
|
154
|
+
for (const entry of readdirSync(packagesDir, { withFileTypes: true })) {
|
|
155
|
+
if (!entry.isDirectory())
|
|
156
|
+
continue;
|
|
157
|
+
const distRel = join('packages', entry.name, 'dist');
|
|
158
|
+
if (existsSync(join(projectRoot, distRel))) {
|
|
159
|
+
pathsToCopy.push(distRel);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const { copied: copiedPaths, failed: failedPaths } = await copyPathsWithReflock(pathsToCopy, projectRoot, worktreePath);
|
|
165
|
+
// Run post-start hooks after copy-on-write bootstrap.
|
|
166
|
+
const postStartHookResults = await runWorktreeHooks(hooks, 'post-start', worktreePath);
|
|
109
167
|
// Build env vars for agent spawn.
|
|
110
168
|
const currentPath = process.env['PATH'] ?? '';
|
|
111
169
|
const shimDir = join(projectRoot, '.cleo', 'bin', 'git-shim');
|
|
@@ -149,8 +207,13 @@ export async function createWorktree(projectRoot, options) {
|
|
|
149
207
|
reused,
|
|
150
208
|
envVars,
|
|
151
209
|
preamble,
|
|
152
|
-
hookResults,
|
|
210
|
+
hookResults: postCreateHookResults,
|
|
153
211
|
appliedPatterns,
|
|
212
|
+
bootstrap: {
|
|
213
|
+
copiedPaths,
|
|
214
|
+
failedPaths,
|
|
215
|
+
hookResults: postStartHookResults,
|
|
216
|
+
},
|
|
154
217
|
};
|
|
155
218
|
}
|
|
156
219
|
//# sourceMappingURL=worktree-create.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktree-create.js","sourceRoot":"","sources":["../src/worktree-create.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"worktree-create.js","sourceRoot":"","sources":["../src/worktree-create.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,9 +14,12 @@ import type { DestroyWorktreeOptions, DestroyWorktreeResult } from '@cleocode/co
|
|
|
14
14
|
* Destroy the git worktree for a task.
|
|
15
15
|
*
|
|
16
16
|
* Steps:
|
|
17
|
-
* 1.
|
|
18
|
-
* 2.
|
|
19
|
-
* 3.
|
|
17
|
+
* 1. Check for uncommitted changes (dirty detection).
|
|
18
|
+
* 2. Run pre-remove hooks.
|
|
19
|
+
* 3. Unlock the worktree (`git worktree unlock`).
|
|
20
|
+
* 4. Remove the worktree directory (`git worktree remove --force`).
|
|
21
|
+
* 5. Optionally delete the task branch.
|
|
22
|
+
* 6. Run post-destroy hooks.
|
|
20
23
|
*
|
|
21
24
|
* Integration (merging commits back to the target branch) is performed by
|
|
22
25
|
* `completeAgentWorktreeViaMerge` before this function is called. Non-fatal
|
|
@@ -30,5 +33,5 @@ import type { DestroyWorktreeOptions, DestroyWorktreeResult } from '@cleocode/co
|
|
|
30
33
|
* @task T1161
|
|
31
34
|
* @adr ADR-062
|
|
32
35
|
*/
|
|
33
|
-
export declare function destroyWorktree(projectRoot: string, options: DestroyWorktreeOptions): DestroyWorktreeResult
|
|
36
|
+
export declare function destroyWorktree(projectRoot: string, options: DestroyWorktreeOptions): Promise<DestroyWorktreeResult>;
|
|
34
37
|
//# sourceMappingURL=worktree-destroy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktree-destroy.d.ts","sourceRoot":"","sources":["../src/worktree-destroy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"worktree-destroy.d.ts","sourceRoot":"","sources":["../src/worktree-destroy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAKzF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CA0GhC"}
|
package/dist/worktree-destroy.js
CHANGED
|
@@ -12,13 +12,17 @@
|
|
|
12
12
|
import { existsSync, rmSync } from 'node:fs';
|
|
13
13
|
import { getGitRoot, gitSilent, gitSync } from './git.js';
|
|
14
14
|
import { computeProjectHash, resolveTaskWorktreePath } from './paths.js';
|
|
15
|
+
import { runWorktreeHooks } from './worktree-hooks.js';
|
|
15
16
|
/**
|
|
16
17
|
* Destroy the git worktree for a task.
|
|
17
18
|
*
|
|
18
19
|
* Steps:
|
|
19
|
-
* 1.
|
|
20
|
-
* 2.
|
|
21
|
-
* 3.
|
|
20
|
+
* 1. Check for uncommitted changes (dirty detection).
|
|
21
|
+
* 2. Run pre-remove hooks.
|
|
22
|
+
* 3. Unlock the worktree (`git worktree unlock`).
|
|
23
|
+
* 4. Remove the worktree directory (`git worktree remove --force`).
|
|
24
|
+
* 5. Optionally delete the task branch.
|
|
25
|
+
* 6. Run post-destroy hooks.
|
|
22
26
|
*
|
|
23
27
|
* Integration (merging commits back to the target branch) is performed by
|
|
24
28
|
* `completeAgentWorktreeViaMerge` before this function is called. Non-fatal
|
|
@@ -32,8 +36,8 @@ import { computeProjectHash, resolveTaskWorktreePath } from './paths.js';
|
|
|
32
36
|
* @task T1161
|
|
33
37
|
* @adr ADR-062
|
|
34
38
|
*/
|
|
35
|
-
export function destroyWorktree(projectRoot, options) {
|
|
36
|
-
const { taskId, deleteBranch = true } = options;
|
|
39
|
+
export async function destroyWorktree(projectRoot, options) {
|
|
40
|
+
const { taskId, deleteBranch = true, force = false, hooks = [] } = options;
|
|
37
41
|
const gitRoot = getGitRoot(projectRoot);
|
|
38
42
|
const projectHash = computeProjectHash(projectRoot);
|
|
39
43
|
const worktreePath = resolveTaskWorktreePath(projectHash, taskId);
|
|
@@ -41,7 +45,50 @@ export function destroyWorktree(projectRoot, options) {
|
|
|
41
45
|
let worktreeRemoved = false;
|
|
42
46
|
let branchDeleted = false;
|
|
43
47
|
let error;
|
|
44
|
-
|
|
48
|
+
const hookResults = [];
|
|
49
|
+
// Step 1: Dirty detection.
|
|
50
|
+
let dirty = false;
|
|
51
|
+
if (existsSync(worktreePath)) {
|
|
52
|
+
try {
|
|
53
|
+
const status = gitSync(['status', '--porcelain'], worktreePath);
|
|
54
|
+
dirty = status.length > 0;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// If git status fails, assume not dirty to avoid blocking cleanup.
|
|
58
|
+
dirty = false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (dirty && !force) {
|
|
62
|
+
return {
|
|
63
|
+
taskId,
|
|
64
|
+
worktreeRemoved: false,
|
|
65
|
+
branchDeleted: false,
|
|
66
|
+
error: 'Worktree has uncommitted changes - destroy aborted',
|
|
67
|
+
dirty,
|
|
68
|
+
force,
|
|
69
|
+
hookResults,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Step 2: Run pre-remove hooks.
|
|
73
|
+
if (hooks.length > 0 && existsSync(worktreePath)) {
|
|
74
|
+
try {
|
|
75
|
+
const preRemoveResults = await runWorktreeHooks(hooks, 'pre-remove', worktreePath);
|
|
76
|
+
hookResults.push(...preRemoveResults);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
80
|
+
return {
|
|
81
|
+
taskId,
|
|
82
|
+
worktreeRemoved: false,
|
|
83
|
+
branchDeleted: false,
|
|
84
|
+
error: `Pre-remove hook failed: ${message}`,
|
|
85
|
+
dirty,
|
|
86
|
+
force,
|
|
87
|
+
hookResults,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Step 3: Unlock + remove the worktree.
|
|
45
92
|
if (existsSync(worktreePath)) {
|
|
46
93
|
gitSilent(['worktree', 'unlock', worktreePath], gitRoot);
|
|
47
94
|
if (gitSilent(['worktree', 'remove', '--force', worktreePath], gitRoot)) {
|
|
@@ -62,7 +109,7 @@ export function destroyWorktree(projectRoot, options) {
|
|
|
62
109
|
else {
|
|
63
110
|
worktreeRemoved = true; // already gone
|
|
64
111
|
}
|
|
65
|
-
// Step
|
|
112
|
+
// Step 4: Optionally delete the branch.
|
|
66
113
|
if (deleteBranch) {
|
|
67
114
|
try {
|
|
68
115
|
const branchExists = gitSync(['branch', '--list', branch], gitRoot);
|
|
@@ -80,6 +127,19 @@ export function destroyWorktree(projectRoot, options) {
|
|
|
80
127
|
else {
|
|
81
128
|
branchDeleted = false;
|
|
82
129
|
}
|
|
83
|
-
|
|
130
|
+
// Step 5: Run post-destroy hooks (in git root since worktree is gone).
|
|
131
|
+
if (hooks.length > 0) {
|
|
132
|
+
try {
|
|
133
|
+
const postDestroyResults = await runWorktreeHooks(hooks, 'post-destroy', gitRoot);
|
|
134
|
+
hookResults.push(...postDestroyResults);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
138
|
+
if (!error) {
|
|
139
|
+
error = `Post-destroy hook failed: ${message}`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return { taskId, worktreeRemoved, branchDeleted, error, dirty, force, hookResults };
|
|
84
144
|
}
|
|
85
145
|
//# sourceMappingURL=worktree-destroy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktree-destroy.js","sourceRoot":"","sources":["../src/worktree-destroy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAE7C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"worktree-destroy.js","sourceRoot":"","sources":["../src/worktree-destroy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAE7C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,WAAmB,EACnB,OAA+B;IAE/B,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,EAAE,KAAK,GAAG,KAAK,EAAE,KAAK,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC;IAE3E,MAAM,OAAO,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACxC,MAAM,WAAW,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;IACpD,MAAM,YAAY,GAAG,uBAAuB,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,QAAQ,MAAM,EAAE,CAAC;IAEhC,IAAI,eAAe,GAAG,KAAK,CAAC;IAC5B,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,IAAI,KAAyB,CAAC;IAC9B,MAAM,WAAW,GAAiD,EAAE,CAAC;IAErE,2BAA2B;IAC3B,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,YAAY,CAAC,CAAC;YAChE,KAAK,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;YACnE,KAAK,GAAG,KAAK,CAAC;QAChB,CAAC;IACH,CAAC;IAED,IAAI,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;QACpB,OAAO;YACL,MAAM;YACN,eAAe,EAAE,KAAK;YACtB,aAAa,EAAE,KAAK;YACpB,KAAK,EAAE,oDAAoD;YAC3D,KAAK;YACL,KAAK;YACL,WAAW;SACZ,CAAC;IACJ,CAAC;IAED,gCAAgC;IAChC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC;YACH,MAAM,gBAAgB,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;YACnF,WAAW,CAAC,IAAI,CAAC,GAAG,gBAAgB,CAAC,CAAC;QACxC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO;gBACL,MAAM;gBACN,eAAe,EAAE,KAAK;gBACtB,aAAa,EAAE,KAAK;gBACpB,KAAK,EAAE,2BAA2B,OAAO,EAAE;gBAC3C,KAAK;gBACL,KAAK;gBACL,WAAW;aACZ,CAAC;QACJ,CAAC;IACH,CAAC;IAED,wCAAwC;IACxC,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7B,SAAS,CAAC,CAAC,UAAU,EAAE,QAAQ,EAAE,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;QACzD,IAAI,SAAS,CAAC,CAAC,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC;YACxE,eAAe,GAAG,IAAI,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC;gBACH,MAAM,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBACvD,eAAe,GAAG,IAAI,CAAC;YACzB,CAAC;YAAC,OAAO,IAAI,EAAE,CAAC;gBACd,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,KAAK,GAAG,8BAA8B,IAAI,YAAY,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9F,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;SAAM,CAAC;QACN,eAAe,GAAG,IAAI,CAAC,CAAC,eAAe;IACzC,CAAC;IAED,wCAAwC;IACxC,IAAI,YAAY,EAAE,CAAC;QACjB,IAAI,CAAC;YACH,MAAM,YAAY,GAAG,OAAO,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;YACpE,IAAI,YAAY,EAAE,CAAC;gBACjB,OAAO,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;YAC7C,CAAC;YACD,aAAa,GAAG,IAAI,CAAC;QACvB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,KAAK,GAAG,4BAA4B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YACzF,CAAC;QACH,CAAC;IACH,CAAC;SAAM,CAAC;QACN,aAAa,GAAG,KAAK,CAAC;IACxB,CAAC;IAED,uEAAuE;IACvE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,IAAI,CAAC;YACH,MAAM,kBAAkB,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;YAClF,WAAW,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,KAAK,GAAG,6BAA6B,OAAO,EAAE,CAAC;YACjD,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;AACtF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleocode/worktree",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.40",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Native CLEO worktree backend SDK — createWorktree, destroyWorktree, listWorktrees, pruneWorktrees with XDG path canon and declarative hooks (T1161)",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"author": "CLEO Code <hello@cleocode.dev>",
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@cleocode/contracts": "2026.5.
|
|
34
|
-
"@cleocode/paths": "2026.5.
|
|
33
|
+
"@cleocode/contracts": "2026.5.40",
|
|
34
|
+
"@cleocode/paths": "2026.5.40"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^24.3.0",
|