@cleocode/worktree 2026.10.27 → 2026.11.1-canary.11
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/.source-stamp.json +4 -0
- package/dist/git.d.ts +0 -13
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +0 -18
- package/dist/git.js.map +1 -1
- package/dist/index.d.ts +13 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -6
- package/dist/index.js.map +1 -1
- package/dist/worktree-create.d.ts +32 -1
- package/dist/worktree-create.d.ts.map +1 -1
- package/dist/worktree-create.js +153 -34
- package/dist/worktree-create.js.map +1 -1
- package/dist/worktree-destroy.d.ts +5 -5
- package/dist/worktree-destroy.d.ts.map +1 -1
- package/dist/worktree-destroy.js +79 -120
- package/dist/worktree-destroy.js.map +1 -1
- package/dist/worktree-dispose.d.ts +193 -0
- package/dist/worktree-dispose.d.ts.map +1 -0
- package/dist/worktree-dispose.js +608 -0
- package/dist/worktree-dispose.js.map +1 -0
- package/dist/worktree-locate.d.ts +2 -0
- package/dist/worktree-locate.d.ts.map +1 -1
- package/dist/worktree-locate.js +12 -1
- package/dist/worktree-locate.js.map +1 -1
- package/dist/worktree-lock.d.ts +56 -7
- package/dist/worktree-lock.d.ts.map +1 -1
- package/dist/worktree-lock.js +315 -100
- package/dist/worktree-lock.js.map +1 -1
- package/dist/worktree-prune.d.ts +1 -3
- package/dist/worktree-prune.d.ts.map +1 -1
- package/dist/worktree-prune.js +20 -241
- package/dist/worktree-prune.js.map +1 -1
- package/native/worktree-napi.darwin-arm64.node +0 -0
- package/native/worktree-napi.linux-arm64-gnu.node +0 -0
- package/native/worktree-napi.linux-x64-gnu.node +0 -0
- package/native/worktree-napi.win32-x64-msvc.node +0 -0
- package/package.json +4 -4
package/dist/worktree-destroy.js
CHANGED
|
@@ -9,12 +9,11 @@
|
|
|
9
9
|
* @task T1161
|
|
10
10
|
* @adr ADR-062
|
|
11
11
|
*/
|
|
12
|
-
import { existsSync
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
13
|
import { countUnmergedCommits, getGitRoot, gitSilent, gitSync } from './git.js';
|
|
14
|
-
import { destroyWorktree as napiDestroyWorktree } from './napi-binding.js';
|
|
15
14
|
import { computeProjectHash, resolveTaskWorktreePath } from './paths.js';
|
|
16
15
|
import { appendWorktreeAuditLog, removeWorktreeFromSentinelIndex } from './worktree-audit.js';
|
|
17
|
-
import {
|
|
16
|
+
import { disposeWorktree } from './worktree-dispose.js';
|
|
18
17
|
import { runWorktreeHooks } from './worktree-hooks.js';
|
|
19
18
|
import { locateTaskWorktree } from './worktree-locate.js';
|
|
20
19
|
import { releaseWorktreeTaskLock } from './worktree-lock.js';
|
|
@@ -22,12 +21,12 @@ import { releaseWorktreeTaskLock } from './worktree-lock.js';
|
|
|
22
21
|
* Destroy the git worktree for a task.
|
|
23
22
|
*
|
|
24
23
|
* Steps:
|
|
25
|
-
* 1.
|
|
24
|
+
* 1. Ask {@link disposeWorktree} (T13924) whether the worktree may go: kept
|
|
25
|
+
* unless nothing in it is lost; `force` archives the unpreserved work first.
|
|
26
26
|
* 2. Run pre-remove hooks.
|
|
27
|
-
* 3.
|
|
28
|
-
* 4.
|
|
29
|
-
* 5.
|
|
30
|
-
* 6. Run post-destroy hooks.
|
|
27
|
+
* 3. Dispose of it (re-proved right before removal).
|
|
28
|
+
* 4. Optionally delete the task branch.
|
|
29
|
+
* 5. Run post-destroy hooks.
|
|
31
30
|
*
|
|
32
31
|
* Integration (merging commits back to the target branch) is performed by
|
|
33
32
|
* `completeAgentWorktreeViaMerge` before this function is called. Non-fatal
|
|
@@ -57,139 +56,90 @@ export async function destroyWorktree(projectRoot, options) {
|
|
|
57
56
|
let branchDeleted = false;
|
|
58
57
|
let error;
|
|
59
58
|
const hookResults = [];
|
|
60
|
-
// Step 1:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
// Step 1: the one disposal engine decides first (T13924). Without proof that
|
|
60
|
+
// nothing in the worktree is lost it is kept; `force` archives the
|
|
61
|
+
// unpreserved work (ref + patch + receipt) first. The dependents/evidence
|
|
62
|
+
// guard always holds. The pre-remove hooks run only once removal is decided.
|
|
63
|
+
const reason = options.reason ?? 'manual';
|
|
64
|
+
const dispose = (probe) => disposeWorktree({
|
|
65
|
+
worktreePath,
|
|
66
|
+
gitRoot,
|
|
67
|
+
projectRoot,
|
|
68
|
+
taskId,
|
|
69
|
+
reason,
|
|
70
|
+
evidence: options.evidence,
|
|
71
|
+
force,
|
|
72
|
+
probe,
|
|
73
|
+
});
|
|
74
|
+
const refuse = (disposal) => {
|
|
75
|
+
const message = disposal.kept
|
|
76
|
+
? (disposal.keptReason ?? '')
|
|
77
|
+
: `Worktree kept: ${disposal.keptReason ?? 'not proven preserved'}. Commit and push it, or pass --force to archive it and remove it.`;
|
|
78
|
+
appendWorktreeAuditLog(projectRoot, {
|
|
79
|
+
action: 'destroy',
|
|
80
|
+
xdgPath: worktreePath,
|
|
81
|
+
taskId,
|
|
82
|
+
branch,
|
|
83
|
+
reason,
|
|
84
|
+
success: false,
|
|
85
|
+
error: message,
|
|
86
|
+
});
|
|
73
87
|
return {
|
|
74
88
|
taskId,
|
|
75
89
|
worktreeRemoved: false,
|
|
76
90
|
branchDeleted: false,
|
|
77
|
-
error:
|
|
78
|
-
dirty,
|
|
91
|
+
error: message,
|
|
92
|
+
dirty: disposal.unpreserved !== undefined,
|
|
79
93
|
force,
|
|
80
94
|
hookResults,
|
|
95
|
+
...(disposal.kept ? { kept: disposal.kept } : {}),
|
|
96
|
+
...(disposal.archive ? { archive: disposal.archive } : {}),
|
|
81
97
|
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (existsSync(worktreePath)) {
|
|
86
|
-
const kept = guardWorktreeDisposal(worktreePath, {
|
|
87
|
-
taskId,
|
|
88
|
-
reason: options.reason ?? 'manual',
|
|
89
|
-
gitRoot,
|
|
90
|
-
projectRoot,
|
|
91
|
-
evidence: options.evidence,
|
|
92
|
-
});
|
|
93
|
-
if (kept !== null) {
|
|
94
|
-
const message = describeDisposalBlockers(worktreePath, kept);
|
|
95
|
-
appendWorktreeAuditLog(projectRoot, {
|
|
96
|
-
action: 'destroy',
|
|
97
|
-
xdgPath: worktreePath,
|
|
98
|
-
taskId,
|
|
99
|
-
branch,
|
|
100
|
-
reason: options.reason ?? 'manual',
|
|
101
|
-
success: false,
|
|
102
|
-
error: message,
|
|
103
|
-
});
|
|
104
|
-
return {
|
|
105
|
-
taskId,
|
|
106
|
-
worktreeRemoved: false,
|
|
107
|
-
branchDeleted: false,
|
|
108
|
-
error: message,
|
|
109
|
-
dirty,
|
|
110
|
-
force,
|
|
111
|
-
hookResults,
|
|
112
|
-
kept,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
// Step 2: Run pre-remove hooks.
|
|
117
|
-
if (hooks.length > 0 && existsSync(worktreePath)) {
|
|
118
|
-
try {
|
|
119
|
-
const preRemoveResults = await runWorktreeHooks(hooks, 'pre-remove', worktreePath);
|
|
120
|
-
hookResults.push(...preRemoveResults);
|
|
121
|
-
}
|
|
122
|
-
catch (err) {
|
|
123
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
124
|
-
return {
|
|
125
|
-
taskId,
|
|
126
|
-
worktreeRemoved: false,
|
|
127
|
-
branchDeleted: false,
|
|
128
|
-
error: `Pre-remove hook failed: ${message}`,
|
|
129
|
-
dirty,
|
|
130
|
-
force,
|
|
131
|
-
hookResults,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// Step 3: Unlock + remove the worktree (T9982 — git plumbing routed through
|
|
136
|
-
// @cleocode/worktree-napi → worktrunk-core::git_wt::destroy_worktree).
|
|
137
|
-
// The TS layer still owns the unlock + filesystem-rm fallback so a locked or
|
|
138
|
-
// partially-removed worktree can be force-cleaned without a second napi call.
|
|
98
|
+
};
|
|
99
|
+
let dirty = false;
|
|
100
|
+
let archive;
|
|
139
101
|
if (existsSync(worktreePath)) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
});
|
|
147
|
-
worktreeRemoved = result.removed;
|
|
148
|
-
// T11033 — NAPI may report success even when untracked directories
|
|
149
|
-
// (e.g. .cleo/) survive inside the worktree. Verify on-disk reality.
|
|
150
|
-
if (worktreeRemoved && existsSync(worktreePath)) {
|
|
151
|
-
worktreeRemoved = false; // trigger filesystem fallback below
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
catch (err) {
|
|
155
|
-
// napi reported failure — fall back to filesystem rm so the audit log
|
|
156
|
-
// still captures the cleanup attempt.
|
|
157
|
-
try {
|
|
158
|
-
rmSync(worktreePath, { recursive: true, force: true });
|
|
159
|
-
worktreeRemoved = true;
|
|
160
|
-
}
|
|
161
|
-
catch (err2) {
|
|
162
|
-
if (!error) {
|
|
163
|
-
error = `Failed to remove worktree: napi=${err instanceof Error ? err.message : String(err)}; fs=${err2 instanceof Error ? err2.message : String(err2)}`;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
// Filesystem fallback when NAPI reported success but directory survived.
|
|
168
|
-
if (!worktreeRemoved && existsSync(worktreePath)) {
|
|
102
|
+
const plan = dispose(true);
|
|
103
|
+
if (plan.keptReason !== undefined)
|
|
104
|
+
return refuse(plan);
|
|
105
|
+
dirty = plan.unpreserved !== undefined;
|
|
106
|
+
// Step 2: Run pre-remove hooks.
|
|
107
|
+
if (hooks.length > 0) {
|
|
169
108
|
try {
|
|
170
|
-
|
|
171
|
-
|
|
109
|
+
const preRemoveResults = await runWorktreeHooks(hooks, 'pre-remove', worktreePath);
|
|
110
|
+
hookResults.push(...preRemoveResults);
|
|
172
111
|
}
|
|
173
|
-
catch (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
112
|
+
catch (err) {
|
|
113
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
114
|
+
return {
|
|
115
|
+
taskId,
|
|
116
|
+
worktreeRemoved: false,
|
|
117
|
+
branchDeleted: false,
|
|
118
|
+
error: `Pre-remove hook failed: ${message}`,
|
|
119
|
+
dirty,
|
|
120
|
+
force,
|
|
121
|
+
hookResults,
|
|
122
|
+
};
|
|
177
123
|
}
|
|
178
124
|
}
|
|
125
|
+
// Step 3: dispose for real; the engine re-proves right before removing.
|
|
126
|
+
const disposal = dispose(false);
|
|
127
|
+
if (!disposal.removed)
|
|
128
|
+
return refuse(disposal);
|
|
129
|
+
archive = disposal.archive;
|
|
130
|
+
worktreeRemoved = true;
|
|
179
131
|
}
|
|
180
132
|
else if (located !== null) {
|
|
181
133
|
// Registered with git but absent from disk — a stale registration. Pruning
|
|
182
134
|
// it IS the removal; without this the entry lingers forever.
|
|
183
135
|
gitSilent(['worktree', 'prune'], gitRoot);
|
|
184
136
|
worktreeRemoved = true;
|
|
137
|
+
releaseWorktreeTaskLock(projectHash, taskId);
|
|
185
138
|
}
|
|
186
139
|
else {
|
|
187
140
|
worktreeRemoved = true; // genuinely absent: not on disk, not registered
|
|
188
|
-
}
|
|
189
|
-
// T12506: the worktree is gone, so its per-task lock guards nothing — free
|
|
190
|
-
// it so the next spawn of this task provisions without waiting for a TTL.
|
|
191
|
-
if (worktreeRemoved)
|
|
192
141
|
releaseWorktreeTaskLock(projectHash, taskId);
|
|
142
|
+
}
|
|
193
143
|
// Step 4: Optionally delete the branch.
|
|
194
144
|
if (deleteBranch) {
|
|
195
145
|
try {
|
|
@@ -253,6 +203,15 @@ export async function destroyWorktree(projectRoot, options) {
|
|
|
253
203
|
if (destroySucceeded) {
|
|
254
204
|
removeWorktreeFromSentinelIndex(gitRoot, taskId);
|
|
255
205
|
}
|
|
256
|
-
return {
|
|
206
|
+
return {
|
|
207
|
+
taskId,
|
|
208
|
+
worktreeRemoved,
|
|
209
|
+
branchDeleted,
|
|
210
|
+
error,
|
|
211
|
+
dirty,
|
|
212
|
+
force,
|
|
213
|
+
hookResults,
|
|
214
|
+
...(archive ? { archive } : {}),
|
|
215
|
+
};
|
|
257
216
|
}
|
|
258
217
|
//# 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,
|
|
1
|
+
{"version":3,"file":"worktree-destroy.js","sourceRoot":"","sources":["../src/worktree-destroy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one worktree disposal engine (T13924, durability C-F2).
|
|
3
|
+
*
|
|
4
|
+
* Every path that deletes a worktree (complete, cancel, `worktree destroy`,
|
|
5
|
+
* `worktree prune --orphaned`, the merge sweep, gc and the sentient prune)
|
|
6
|
+
* calls {@link disposeWorktree}. It removes a worktree only with proof that
|
|
7
|
+
* nothing in it is lost:
|
|
8
|
+
*
|
|
9
|
+
* - **guard**: nothing outside links into it and no evidence lives only there
|
|
10
|
+
* ({@link guardWorktreeDisposal}, T13696/T13700);
|
|
11
|
+
* - **not in use**: the caller's in-use probe (a live task lock or claim)
|
|
12
|
+
* answers `null`; a probe that throws counts as in use;
|
|
13
|
+
* - **content**: no tracked change, and every untracked or ignored path is
|
|
14
|
+
* either `node_modules`, rebuildable output of a merged checkout, or the
|
|
15
|
+
* identical tree in the main checkout ({@link unprovenContent});
|
|
16
|
+
* - **commits**: every commit HEAD reaches is also held by a remote-tracking
|
|
17
|
+
* ref or the project's local default branch ({@link unpreservedCommits}).
|
|
18
|
+
*
|
|
19
|
+
* Every probe fails closed: a git or read error is "not proven", never clean.
|
|
20
|
+
* Without proof the worktree is kept with the failing clause named. With
|
|
21
|
+
* `force` (`cleo worktree destroy --force` only) the unpreserved work is
|
|
22
|
+
* archived first, a ref under `refs/cleo/archive/` plus a patch and a receipt
|
|
23
|
+
* under `<projectRoot>/.cleo/archive/worktrees/`, and the worktree is removed
|
|
24
|
+
* only once the archive is verified.
|
|
25
|
+
*
|
|
26
|
+
* Nothing else in the repository may call the raw removals (arch gate 41,
|
|
27
|
+
* `scripts/lint-no-raw-worktree-removal.mjs`).
|
|
28
|
+
*
|
|
29
|
+
* @task T13924
|
|
30
|
+
* @task T14276
|
|
31
|
+
*/
|
|
32
|
+
import type { DisposeWorktreeOptions, DisposeWorktreeResult, WorktreeArchiveReceipt } from '@cleocode/contracts';
|
|
33
|
+
/**
|
|
34
|
+
* What one cleanup run may spend proving trees identical (T14146). Shared
|
|
35
|
+
* across every checkout the run inspects; once spent, the rest is unproven.
|
|
36
|
+
*/
|
|
37
|
+
export interface CompareBudget {
|
|
38
|
+
/** Entries (files, directories, links) still allowed. */
|
|
39
|
+
entries: number;
|
|
40
|
+
/** File bytes still allowed to be read. */
|
|
41
|
+
bytes: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A fresh per-run budget: 200 000 entries and 2 GiB of file bytes.
|
|
45
|
+
*
|
|
46
|
+
* @returns A budget to share across one run's checks.
|
|
47
|
+
*/
|
|
48
|
+
export declare function newCompareBudget(): CompareBudget;
|
|
49
|
+
/**
|
|
50
|
+
* Build output a merged, clean checkout can always rebuild from its source,
|
|
51
|
+
* so losing it loses nothing (T14146): `dist/`, `.turbo/` and `coverage/`
|
|
52
|
+
* (and anything inside them), and `*.tsbuildinfo` files, only where the build
|
|
53
|
+
* writes them: the checkout root or a `packages/<name>/` root. The same name
|
|
54
|
+
* deeper down (`secrets/dist/key.pem`) is not build output and stays strict.
|
|
55
|
+
*
|
|
56
|
+
* @param rel - Path relative to the checkout root, `/`-separated.
|
|
57
|
+
* @returns Whether the path is rebuildable output.
|
|
58
|
+
*/
|
|
59
|
+
export declare function isRegenerable(rel: string): boolean;
|
|
60
|
+
/** How {@link unprovenContent} treats ignored content (T14146). */
|
|
61
|
+
export interface UnprovenContentOptions {
|
|
62
|
+
/** Shared per-run comparison budget; a fresh one when omitted. */
|
|
63
|
+
budget?: CompareBudget;
|
|
64
|
+
/** Ignored `node_modules` counts as redundant (worktrees, not repository copies). */
|
|
65
|
+
exemptNodeModules?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Ignored build output ({@link isRegenerable}) counts as redundant. Only for
|
|
68
|
+
* a checkout whose commits are merged: its source rebuilds the output.
|
|
69
|
+
*/
|
|
70
|
+
exemptRegenerable?: boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* What in a checkout is not proven redundant, or `null` when nothing is.
|
|
74
|
+
*
|
|
75
|
+
* Tracked changes never are; untracked content is when the main checkout
|
|
76
|
+
* holds the identical tree at the same path, or (exempt by default) when it
|
|
77
|
+
* is `node_modules`. Ignored content (which
|
|
78
|
+
* `--porcelain` alone never lists: an edited `.env`, an agent's nested
|
|
79
|
+
* `.claude/worktrees/x/`) is redundant only when exempted by `opts`
|
|
80
|
+
* (`node_modules`, merged build output) or when the main checkout holds the
|
|
81
|
+
* identical tree at the same path (what `.worktreeinclude` seeded, unchanged).
|
|
82
|
+
* Fail-closed: a git or read error is unproven.
|
|
83
|
+
*
|
|
84
|
+
* @param path - Checkout root.
|
|
85
|
+
* @param projectRoot - The main checkout seeded copies are compared against.
|
|
86
|
+
* @param opts - Budget and exemptions (default: `node_modules` exempt, nothing else).
|
|
87
|
+
* @returns Why the checkout must be kept, or `null`.
|
|
88
|
+
* @task T14142
|
|
89
|
+
* @task T14146
|
|
90
|
+
*/
|
|
91
|
+
export declare function unprovenContent(path: string, projectRoot: string, opts?: UnprovenContentOptions): string | null;
|
|
92
|
+
/**
|
|
93
|
+
* Whether the checkout's commits survive its removal, or why not.
|
|
94
|
+
*
|
|
95
|
+
* Every commit HEAD reaches must also be reachable from a remote-tracking ref
|
|
96
|
+
* or from the project's local default branch (a local integration before any
|
|
97
|
+
* push). Another local branch does not count: it is often a task branch this
|
|
98
|
+
* engine, or `git branch -D`, removes later, which would chain the loss. An
|
|
99
|
+
* unborn HEAD holds no commits. Fail-closed: any git error is "commits not
|
|
100
|
+
* proven preserved".
|
|
101
|
+
*
|
|
102
|
+
* @param path - Checkout root.
|
|
103
|
+
* @param defaultBranch - The project's default branch; resolved as in
|
|
104
|
+
* {@link DisposeWorktreeOptions.defaultBranch} when omitted.
|
|
105
|
+
* @returns Why its commits are not preserved, or `null`.
|
|
106
|
+
* @task T13924
|
|
107
|
+
*/
|
|
108
|
+
export declare function unpreservedCommits(path: string, defaultBranch?: string): string | null;
|
|
109
|
+
/**
|
|
110
|
+
* Archive everything a worktree holds before it is removed (T13924): an
|
|
111
|
+
* archive commit whose parent is HEAD and whose tree is the checkout's
|
|
112
|
+
* tracked, untracked and ignored content (bar `node_modules`, and ignored
|
|
113
|
+
* build output), held by `refs/cleo/archive/<name>/<stamp>`, plus a binary patch from
|
|
114
|
+
* HEAD and a JSON receipt under `<projectRoot>/.cleo/archive/worktrees/`. The
|
|
115
|
+
* worktree's own index and files are not touched (a temporary index is used).
|
|
116
|
+
*
|
|
117
|
+
* @param worktreePath - Checkout to archive.
|
|
118
|
+
* @param gitRoot - Repository the archive ref is written to.
|
|
119
|
+
* @param projectRoot - Main checkout whose `.cleo/archive/worktrees/` holds the patch and receipt.
|
|
120
|
+
* @param meta - Owning task and disposal reason, recorded on the receipt.
|
|
121
|
+
* @returns The verified receipt, or why the archive could not be written.
|
|
122
|
+
* @task T13924
|
|
123
|
+
*/
|
|
124
|
+
export declare function archiveWorktree(worktreePath: string, gitRoot: string, projectRoot: string, meta: {
|
|
125
|
+
taskId: string | null;
|
|
126
|
+
reason: string;
|
|
127
|
+
}): WorktreeArchiveReceipt | {
|
|
128
|
+
error: string;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Dispose of a worktree with per-item proof that nothing is lost (T13924).
|
|
132
|
+
* See the module comment for the proof; this is the only function that
|
|
133
|
+
* deletes a worktree.
|
|
134
|
+
*
|
|
135
|
+
* @param opts - The worktree, its task, the reason and the overrides.
|
|
136
|
+
* @returns Whether it was removed, or why it was kept, and any archive written.
|
|
137
|
+
* @task T13924
|
|
138
|
+
*/
|
|
139
|
+
export declare function disposeWorktree(opts: DisposeWorktreeOptions): DisposeWorktreeResult;
|
|
140
|
+
/** Options for {@link disposeOwnedWorktree}. */
|
|
141
|
+
export interface DisposeOwnedWorktreeOptions {
|
|
142
|
+
/** Project hash the task lock is keyed by. */
|
|
143
|
+
projectHash: string;
|
|
144
|
+
/** Task whose lock `lockToken` must hold. */
|
|
145
|
+
taskId: string;
|
|
146
|
+
/** The task's canonical worktree path; any other path is kept. */
|
|
147
|
+
worktreePath: string;
|
|
148
|
+
/** Branch the checkout must be on; a checkout on another branch is kept. */
|
|
149
|
+
branch: string;
|
|
150
|
+
/** Token of the caller's own lock acquisition. */
|
|
151
|
+
lockToken: string;
|
|
152
|
+
/** Why it is disposed (audit and archive receipt). */
|
|
153
|
+
reason: string;
|
|
154
|
+
/** Repository root; read from the worktree when omitted. */
|
|
155
|
+
gitRoot?: string;
|
|
156
|
+
/** Main checkout; the repository's main worktree when omitted. */
|
|
157
|
+
projectRoot?: string;
|
|
158
|
+
/** Archive unpreserved work, then remove (as {@link disposeWorktree}). */
|
|
159
|
+
force?: boolean;
|
|
160
|
+
/** Extra in-use probe, run under the lock right before the removal. */
|
|
161
|
+
inUse?: () => string | null;
|
|
162
|
+
}
|
|
163
|
+
/** Outcome of {@link disposeOwnedWorktree}. */
|
|
164
|
+
export interface DisposeOwnedWorktreeResult {
|
|
165
|
+
/** `removed`: the directory is gone. `kept`: it is untouched. */
|
|
166
|
+
outcome: 'removed' | 'kept';
|
|
167
|
+
/** Why it was kept, or how it went. */
|
|
168
|
+
reason: string;
|
|
169
|
+
/** Whether the caller's lock was released (only ever with its token, after a removal). */
|
|
170
|
+
lockReleased: boolean;
|
|
171
|
+
/** The engine's result, when the engine ran. */
|
|
172
|
+
disposal?: DisposeWorktreeResult;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Dispose of a worktree only while the caller still owns its task lock
|
|
176
|
+
* (T14407). For a caller that created the worktree under the lock and must
|
|
177
|
+
* now remove it (a failed provision or spawn).
|
|
178
|
+
*
|
|
179
|
+
* Under the lock's own mutex, held for the whole check and disposal (a live
|
|
180
|
+
* holder's mutex is never broken as stale), it checks that `lockToken` still
|
|
181
|
+
* holds the lock, that `worktreePath` is the task's canonical path and that
|
|
182
|
+
* the checkout there is on `branch`, then runs {@link disposeWorktree}'s proof
|
|
183
|
+
* and removal, then releases the lock with `lockToken`. A concurrent re-spawn
|
|
184
|
+
* cannot re-enter or reclaim the lock in between, so it never loses a
|
|
185
|
+
* worktree it owns. Fails closed: any failed or unreadable lookup, token
|
|
186
|
+
* mismatch or path mismatch keeps the worktree. The branch is not deleted.
|
|
187
|
+
*
|
|
188
|
+
* @param opts - Lock identity, exact path and branch, and disposal options.
|
|
189
|
+
* @returns Whether it was removed or kept, why, and whether the lock was released.
|
|
190
|
+
* @task T14407
|
|
191
|
+
*/
|
|
192
|
+
export declare function disposeOwnedWorktree(opts: DisposeOwnedWorktreeOptions): DisposeOwnedWorktreeResult;
|
|
193
|
+
//# sourceMappingURL=worktree-dispose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree-dispose.d.ts","sourceRoot":"","sources":["../src/worktree-dispose.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAkBH,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,qBAAqB,CAAC;AAwD7B;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,CAEhD;AAQD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAMlD;AAsCD,mEAAmE;AACnE,MAAM,WAAW,sBAAsB;IACrC,kEAAkE;IAClE,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,qFAAqF;IACrF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAoBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE,sBAA2B,GAChC,MAAM,GAAG,IAAI,CAwBf;AAiCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAetF;AAcD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC9C,sBAAsB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CA4E5C;AAqDD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,qBAAqB,CAEnF;AA2FD,gDAAgD;AAChD,MAAM,WAAW,2BAA2B;IAC1C,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,+CAA+C;AAC/C,MAAM,WAAW,0BAA0B;IACzC,iEAAiE;IACjE,OAAO,EAAE,SAAS,GAAG,MAAM,CAAC;IAC5B,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,YAAY,EAAE,OAAO,CAAC;IACtB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,2BAA2B,GAChC,0BAA0B,CAyD5B"}
|