@mmnto/totem 1.112.0 → 1.113.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.
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Verified residue deletion — the finish `git worktree remove` does not do
3
+ * (mmnto-ai/totem#2580 slice-2).
4
+ *
5
+ * Git exits 0 on a removal that leaves the directory standing (the husk class
6
+ * the estate sensor keeps finding), so the `wt remove` verb never trusts the
7
+ * exit code: it deletes, then RE-VERIFIES, and this module is the deletion
8
+ * half of that contract.
9
+ *
10
+ * Three passes, in order, and the order is load-bearing:
11
+ * 1. **Strip** every reparse point / symlink / junction, WITHOUT following
12
+ * it. The link itself is unlinked; its target is not touched, not
13
+ * descended into, and not deleted. This is the same code on POSIX
14
+ * (symlinks) and win32 (symlinks + junctions) — `lstat` answers
15
+ * `isSymbolicLink()` for a directory junction, and libuv's `unlink`
16
+ * removes a directory reparse point by dropping the link. A worktree
17
+ * commonly carries a `node_modules` full of pnpm links, and one followed
18
+ * junction would delete a store OUTSIDE the tree.
19
+ * 2. **Delete** recursively with retries — Windows hands back EBUSY/EPERM
20
+ * while an indexer or a just-exited git still holds a handle, and a
21
+ * single attempt reads that as a permanent failure.
22
+ * 3. **Re-verify** with a no-follow existence probe. The result is reported,
23
+ * never asserted: the caller decides, and `wt remove` only exits 0 when
24
+ * this says the directory is gone.
25
+ *
26
+ * Nothing here throws for an ordinary filesystem outcome. A failed strip, a
27
+ * failed delete, and a surviving directory are all RETURNED, so the caller can
28
+ * name the survivors in its error rather than surfacing a raw ENOTEMPTY.
29
+ */
30
+ export interface ResidueRemovalResult {
31
+ /** True only when a no-follow probe confirms the directory is gone. */
32
+ removed: boolean;
33
+ /** Links unlinked by the strip pass — reported so the finish is not silent. */
34
+ strippedLinks: string[];
35
+ /** Up to {@link SURVIVOR_SAMPLE_LIMIT} paths still present, for the error. */
36
+ survivors: string[];
37
+ /** The last delete failure, when one happened. */
38
+ lastError?: string;
39
+ /** How many strip+delete attempts ran. */
40
+ attempts: number;
41
+ }
42
+ export interface ResidueRemovalOptions {
43
+ dir: string;
44
+ /** Test seam — production callers take the default. */
45
+ attempts?: number;
46
+ /** Test seam — production callers take the default. */
47
+ retryDelayMs?: number;
48
+ }
49
+ /**
50
+ * No-follow existence probe: a dangling link still counts as PRESENT, and the
51
+ * probe FAILS CLOSED — only ENOENT/ENOTDIR mean "absent"; any other errno
52
+ * reports the path as present, so an unknowable path reads as a survivor and
53
+ * the caller fails loud instead of claiming a verified removal (#2580 bot
54
+ * round, CR findings 8 + 9 — retires the errno-tri-state deferral).
55
+ */
56
+ export declare function residuePathExists(p: string): boolean;
57
+ /**
58
+ * Depth-first strip of every reparse point under `dir`. Recursion enters REAL
59
+ * directories only — a junction answers `isSymbolicLink()` first and is
60
+ * unlinked, so the walk can never leave the tree (and cannot cycle).
61
+ */
62
+ export declare function stripReparsePoints(dir: string): string[];
63
+ /**
64
+ * Strip links, delete recursively with retries, then verify absence. Never
65
+ * throws on a filesystem outcome — `removed: false` plus `survivors` is the
66
+ * failure report, and the caller turns that into its own loud error.
67
+ */
68
+ export declare function removeWorktreeResidue(options: ResidueRemovalOptions): Promise<ResidueRemovalResult>;
69
+ //# sourceMappingURL=worktree-residue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree-residue.d.ts","sourceRoot":"","sources":["../src/worktree-residue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAeH,MAAM,WAAW,oBAAoB;IACnC,uEAAuE;IACvE,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,8EAA8E;IAC9E,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAMD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAgBpD;AA2BD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA+CxD;AA2BD;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAyC/B"}
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Verified residue deletion — the finish `git worktree remove` does not do
3
+ * (mmnto-ai/totem#2580 slice-2).
4
+ *
5
+ * Git exits 0 on a removal that leaves the directory standing (the husk class
6
+ * the estate sensor keeps finding), so the `wt remove` verb never trusts the
7
+ * exit code: it deletes, then RE-VERIFIES, and this module is the deletion
8
+ * half of that contract.
9
+ *
10
+ * Three passes, in order, and the order is load-bearing:
11
+ * 1. **Strip** every reparse point / symlink / junction, WITHOUT following
12
+ * it. The link itself is unlinked; its target is not touched, not
13
+ * descended into, and not deleted. This is the same code on POSIX
14
+ * (symlinks) and win32 (symlinks + junctions) — `lstat` answers
15
+ * `isSymbolicLink()` for a directory junction, and libuv's `unlink`
16
+ * removes a directory reparse point by dropping the link. A worktree
17
+ * commonly carries a `node_modules` full of pnpm links, and one followed
18
+ * junction would delete a store OUTSIDE the tree.
19
+ * 2. **Delete** recursively with retries — Windows hands back EBUSY/EPERM
20
+ * while an indexer or a just-exited git still holds a handle, and a
21
+ * single attempt reads that as a permanent failure.
22
+ * 3. **Re-verify** with a no-follow existence probe. The result is reported,
23
+ * never asserted: the caller decides, and `wt remove` only exits 0 when
24
+ * this says the directory is gone.
25
+ *
26
+ * Nothing here throws for an ordinary filesystem outcome. A failed strip, a
27
+ * failed delete, and a surviving directory are all RETURNED, so the caller can
28
+ * name the survivors in its error rather than surfacing a raw ENOTEMPTY.
29
+ */
30
+ import * as fs from 'node:fs';
31
+ import * as path from 'node:path';
32
+ /** Delete attempts (each = strip pass + recursive rm) before giving up. */
33
+ const RESIDUE_ATTEMPTS = 3;
34
+ /** Pause between whole attempts — long enough for a handle to be released. */
35
+ const RESIDUE_RETRY_DELAY_MS = 150;
36
+ /** `fs.rmSync`'s own inner retry budget for EBUSY/EPERM/ENOTEMPTY. */
37
+ const RM_MAX_RETRIES = 3;
38
+ const RM_RETRY_DELAY_MS = 100;
39
+ /** Cap on survivor paths collected for the failure message. */
40
+ const SURVIVOR_SAMPLE_LIMIT = 10;
41
+ function describe(err) {
42
+ return err instanceof Error ? err.message : String(err);
43
+ }
44
+ /**
45
+ * No-follow existence probe: a dangling link still counts as PRESENT, and the
46
+ * probe FAILS CLOSED — only ENOENT/ENOTDIR mean "absent"; any other errno
47
+ * reports the path as present, so an unknowable path reads as a survivor and
48
+ * the caller fails loud instead of claiming a verified removal (#2580 bot
49
+ * round, CR findings 8 + 9 — retires the errno-tri-state deferral).
50
+ */
51
+ export function residuePathExists(p) {
52
+ // totem-context: intentional cleanup — the catch IS the answer, not a swallow: ENOENT/ENOTDIR are the two errnos that PROVE absence, and every other lstat failure deliberately reports "present" so the finish reports a survivor rather than a false verified-removal.
53
+ try {
54
+ fs.lstatSync(p);
55
+ return true;
56
+ // totem-context: intentional cleanup — see directive above the try; dual placement so the rule fires on either the catch-keyword line or the catch-body line.
57
+ }
58
+ catch (err) {
59
+ // A non-object throw carries no errno and cannot PROVE absence — it reads
60
+ // as present, the same fail-closed answer as any unrecognized failure
61
+ // (round 2, GCA null-safety finding).
62
+ const code = err !== null && typeof err === 'object' && 'code' in err
63
+ ? err.code
64
+ : undefined;
65
+ return code !== 'ENOENT' && code !== 'ENOTDIR';
66
+ }
67
+ }
68
+ /**
69
+ * Unlink one link WITHOUT following it. `unlink` covers file symlinks on every
70
+ * platform and directory reparse points on win32 (libuv drops the link rather
71
+ * than the target); `rmdir` is the fallback for the directory-symlink shape
72
+ * POSIX reports as EPERM/EISDIR on unlink. Neither call descends, so the
73
+ * target is untouched by construction.
74
+ */
75
+ function unlinkNoFollow(target) {
76
+ // totem-context: intentional cleanup — a failed `unlink` falls through to the `rmdir` shape below; the strip pass reports what it could not remove instead of aborting the whole finish on one stubborn link.
77
+ try {
78
+ fs.unlinkSync(target);
79
+ return true;
80
+ // totem-context: intentional cleanup — see directive above the try; dual placement so the rule fires on either the catch-keyword line or the catch-body line.
81
+ }
82
+ catch {
83
+ // totem-context: intentional cleanup — a link that resists BOTH removal shapes is left for the recursive delete to hit; the caller learns about it from the survivor list, never from a swallowed throw.
84
+ try {
85
+ fs.rmdirSync(target);
86
+ return true;
87
+ // totem-context: intentional cleanup — see directive above the try; dual placement so the rule fires on either the catch-keyword line or the catch-body line.
88
+ }
89
+ catch {
90
+ return false;
91
+ }
92
+ }
93
+ }
94
+ /**
95
+ * Depth-first strip of every reparse point under `dir`. Recursion enters REAL
96
+ * directories only — a junction answers `isSymbolicLink()` first and is
97
+ * unlinked, so the walk can never leave the tree (and cannot cycle).
98
+ */
99
+ export function stripReparsePoints(dir) {
100
+ const stripped = [];
101
+ const walk = (current) => {
102
+ let entries;
103
+ // totem-context: intentional cleanup — an unreadable or already-deleted directory contributes no links to strip; the recursive delete below still runs over it and the final verify is what decides success.
104
+ try {
105
+ entries = fs.readdirSync(current, { withFileTypes: true });
106
+ // totem-context: intentional cleanup — see directive above the try; dual placement so the rule fires on either the catch-keyword line or the catch-body line.
107
+ }
108
+ catch {
109
+ return;
110
+ }
111
+ for (const entry of entries) {
112
+ const child = path.join(current, entry.name);
113
+ let stat;
114
+ // totem-context: intentional cleanup — an entry that vanished between readdir and lstat needs no strip; skipping it is the correct outcome and the verify pass still catches anything left behind.
115
+ try {
116
+ stat = fs.lstatSync(child);
117
+ // totem-context: intentional cleanup — see directive above the try; dual placement so the rule fires on either the catch-keyword line or the catch-body line.
118
+ }
119
+ catch {
120
+ continue;
121
+ }
122
+ // Checked BEFORE `isDirectory()`: a junction reports both on win32, and
123
+ // descending into one is precisely the out-of-tree deletion this pass
124
+ // exists to prevent.
125
+ if (stat.isSymbolicLink()) {
126
+ if (unlinkNoFollow(child))
127
+ stripped.push(child);
128
+ continue;
129
+ }
130
+ if (stat.isDirectory())
131
+ walk(child);
132
+ }
133
+ };
134
+ if (!residuePathExists(dir))
135
+ return stripped;
136
+ // A worktree path that is ITSELF a link is unlinked whole — never walked.
137
+ // totem-context: intentional cleanup — an unreadable top-level lstat leaves the walk to the recursive delete; the finish's verify pass is the authority on whether anything survived.
138
+ try {
139
+ if (fs.lstatSync(dir).isSymbolicLink()) {
140
+ if (unlinkNoFollow(dir))
141
+ stripped.push(dir);
142
+ return stripped;
143
+ }
144
+ // totem-context: intentional cleanup — see directive above the try; dual placement so the rule fires on either the catch-keyword line or the catch-body line.
145
+ }
146
+ catch {
147
+ return stripped;
148
+ }
149
+ walk(dir);
150
+ return stripped;
151
+ }
152
+ /** Up to `SURVIVOR_SAMPLE_LIMIT` paths still present under (and including) `dir`. */
153
+ function collectSurvivors(dir) {
154
+ if (!residuePathExists(dir))
155
+ return [];
156
+ const survivors = [dir];
157
+ const walk = (current) => {
158
+ if (survivors.length >= SURVIVOR_SAMPLE_LIMIT)
159
+ return;
160
+ let entries;
161
+ // totem-context: intentional cleanup — an unreadable survivor still counts (its parent is already listed); this walk only enriches a failure message and must never throw over one.
162
+ try {
163
+ entries = fs.readdirSync(current, { withFileTypes: true });
164
+ // totem-context: intentional cleanup — see directive above the try; dual placement so the rule fires on either the catch-keyword line or the catch-body line.
165
+ }
166
+ catch {
167
+ return;
168
+ }
169
+ for (const entry of entries) {
170
+ if (survivors.length >= SURVIVOR_SAMPLE_LIMIT)
171
+ return;
172
+ const child = path.join(current, entry.name);
173
+ survivors.push(child);
174
+ if (entry.isDirectory())
175
+ walk(child);
176
+ }
177
+ };
178
+ walk(dir);
179
+ return survivors;
180
+ }
181
+ /**
182
+ * Strip links, delete recursively with retries, then verify absence. Never
183
+ * throws on a filesystem outcome — `removed: false` plus `survivors` is the
184
+ * failure report, and the caller turns that into its own loud error.
185
+ */
186
+ export async function removeWorktreeResidue(options) {
187
+ const dir = path.resolve(options.dir);
188
+ const maxAttempts = options.attempts ?? RESIDUE_ATTEMPTS;
189
+ const retryDelayMs = options.retryDelayMs ?? RESIDUE_RETRY_DELAY_MS;
190
+ const strippedLinks = [];
191
+ let lastError;
192
+ let attempts = 0;
193
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
194
+ attempts = attempt + 1;
195
+ if (!residuePathExists(dir))
196
+ break;
197
+ strippedLinks.push(...stripReparsePoints(dir));
198
+ // totem-context: intentional cleanup — a failed delete is RETRIED and then REPORTED (`removed: false` + survivors), because a Windows EBUSY from a lingering handle is an ordinary transient here; the verify pass below, not this catch, decides the outcome.
199
+ try {
200
+ // totem-ignore-next-line mmnto-ai/totem#2580 — the rmSync rules are ast-grep patterns (`fs.rmSync($PATH, { $$$OPTS })`) that fire on ANY options-bearing call, compliant ones included; they cannot express "options LACKING maxRetries". Suppression is warranted on that ground: this call sets maxRetries/retryDelay below and IS the removal verb's retry-hardened deletion routine, not test-teardown temp cleanup.
201
+ fs.rmSync(dir, {
202
+ recursive: true,
203
+ force: true,
204
+ maxRetries: RM_MAX_RETRIES,
205
+ retryDelay: RM_RETRY_DELAY_MS,
206
+ });
207
+ lastError = undefined;
208
+ // totem-context: intentional cleanup — see directive above the try; dual placement so the rule fires on either the catch-keyword line or the catch-body line.
209
+ }
210
+ catch (err) {
211
+ lastError = describe(err);
212
+ }
213
+ if (!residuePathExists(dir))
214
+ break;
215
+ if (attempt < maxAttempts - 1) {
216
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
217
+ }
218
+ }
219
+ const removed = !residuePathExists(dir);
220
+ return {
221
+ removed,
222
+ strippedLinks,
223
+ survivors: removed ? [] : collectSurvivors(dir),
224
+ ...(lastError === undefined ? {} : { lastError }),
225
+ attempts,
226
+ };
227
+ }
228
+ //# sourceMappingURL=worktree-residue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree-residue.js","sourceRoot":"","sources":["../src/worktree-residue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Residue-deletion tests (mmnto-ai/totem#2580 slice-2).
3
+ *
4
+ * Invariant 3 is the reason this module exists as its own file, and it is
5
+ * asserted with a REAL link fixture rather than a mock: a junction inside a
6
+ * fake worktree pointing at a directory OUTSIDE it, whose sentinel file must
7
+ * survive the worktree's deletion. On win32 the link is created as a
8
+ * `'junction'`, which needs no elevation — a `'dir'` symlink would require
9
+ * Developer Mode and turn the invariant into a test that silently skips.
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=worktree-residue.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree-residue.test.d.ts","sourceRoot":"","sources":["../src/worktree-residue.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Residue-deletion tests (mmnto-ai/totem#2580 slice-2).
3
+ *
4
+ * Invariant 3 is the reason this module exists as its own file, and it is
5
+ * asserted with a REAL link fixture rather than a mock: a junction inside a
6
+ * fake worktree pointing at a directory OUTSIDE it, whose sentinel file must
7
+ * survive the worktree's deletion. On win32 the link is created as a
8
+ * `'junction'`, which needs no elevation — a `'dir'` symlink would require
9
+ * Developer Mode and turn the invariant into a test that silently skips.
10
+ */
11
+ import * as fs from 'node:fs';
12
+ import * as os from 'node:os';
13
+ import * as path from 'node:path';
14
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
15
+ // Module-namespace exports are not spyable under ESM, so `node:fs` is mocked
16
+ // ONCE with pass-through fns whose implementations individual tests override
17
+ // via `mockImplementationOnce` (the ecl-gc.test.ts house pattern); every other
18
+ // fs call, and every unconsumed call, is the real thing.
19
+ vi.mock('node:fs', async (importOriginal) => {
20
+ const actual = await importOriginal();
21
+ const lstatSync = vi.fn(actual.lstatSync);
22
+ const rmSync = vi.fn(actual.rmSync);
23
+ return { ...actual, default: { ...actual, lstatSync, rmSync }, lstatSync, rmSync };
24
+ });
25
+ import { removeWorktreeResidue, residuePathExists, stripReparsePoints, } from './worktree-residue.js';
26
+ const IS_WIN32 = process.platform === 'win32';
27
+ /** win32: `'junction'` needs no elevation. POSIX: an ordinary dir symlink. */
28
+ const DIR_LINK = IS_WIN32 ? 'junction' : 'dir';
29
+ let root;
30
+ beforeEach(() => {
31
+ root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'totem-residue-')));
32
+ });
33
+ afterEach(() => {
34
+ fs.rmSync(root, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
35
+ });
36
+ /** An outside directory holding a sentinel that must survive every deletion. */
37
+ function outsideWithSentinel(name = 'outside') {
38
+ const dir = path.join(root, name);
39
+ fs.mkdirSync(dir, { recursive: true });
40
+ const sentinel = path.join(dir, 'sentinel.txt');
41
+ fs.writeFileSync(sentinel, 'must survive', 'utf-8');
42
+ return { dir, sentinel };
43
+ }
44
+ // ─── Invariant 3 ────────────────────────────────────────
45
+ describe('invariant 3: residue deletion never follows a link out of the tree', () => {
46
+ it('deletes a junction/symlink inside the worktree and leaves its TARGET intact', async () => {
47
+ const { dir: outside, sentinel } = outsideWithSentinel();
48
+ const wt = path.join(root, 'repo-seat-slug');
49
+ fs.mkdirSync(path.join(wt, 'node_modules', '.pnpm'), { recursive: true });
50
+ fs.writeFileSync(path.join(wt, 'file.txt'), 'inside', 'utf-8');
51
+ const link = path.join(wt, 'node_modules', 'linked-package');
52
+ fs.symlinkSync(outside, link, DIR_LINK);
53
+ // Sanity: the fixture really is a link, or the invariant asserts nothing.
54
+ expect(fs.lstatSync(link).isSymbolicLink()).toBe(true);
55
+ const result = await removeWorktreeResidue({ dir: wt });
56
+ expect(result.removed).toBe(true);
57
+ expect(result.survivors).toEqual([]);
58
+ expect(result.strippedLinks.map((p) => path.resolve(p))).toContain(path.resolve(link));
59
+ expect(residuePathExists(wt)).toBe(false);
60
+ // The whole point: the link went, the target and its contents did not.
61
+ expect(fs.existsSync(outside)).toBe(true);
62
+ expect(fs.readFileSync(sentinel, 'utf-8')).toBe('must survive');
63
+ });
64
+ it('unlinks a worktree path that is ITSELF a link, without walking into it', () => {
65
+ const { dir: outside, sentinel } = outsideWithSentinel('outside-whole');
66
+ const wt = path.join(root, 'linked-worktree');
67
+ fs.symlinkSync(outside, wt, DIR_LINK);
68
+ const stripped = stripReparsePoints(wt);
69
+ expect(stripped.map((p) => path.resolve(p))).toEqual([path.resolve(wt)]);
70
+ expect(residuePathExists(wt)).toBe(false);
71
+ expect(fs.readFileSync(sentinel, 'utf-8')).toBe('must survive');
72
+ });
73
+ it('strips nested links at every depth', async () => {
74
+ const { dir: outside, sentinel } = outsideWithSentinel('outside-deep');
75
+ const wt = path.join(root, 'deep-worktree');
76
+ const deep = path.join(wt, 'a', 'b', 'c');
77
+ fs.mkdirSync(deep, { recursive: true });
78
+ fs.symlinkSync(outside, path.join(wt, 'top-link'), DIR_LINK);
79
+ fs.symlinkSync(outside, path.join(deep, 'deep-link'), DIR_LINK);
80
+ const result = await removeWorktreeResidue({ dir: wt });
81
+ expect(result.strippedLinks).toHaveLength(2);
82
+ expect(result.removed).toBe(true);
83
+ expect(fs.readFileSync(sentinel, 'utf-8')).toBe('must survive');
84
+ });
85
+ // A dangling link is the shape `existsSync` gets wrong: it follows, sees
86
+ // nothing, and reports absent. The finish must still remove it.
87
+ it('removes a DANGLING link and reports the directory gone', async () => {
88
+ const wt = path.join(root, 'dangling-worktree');
89
+ fs.mkdirSync(wt, { recursive: true });
90
+ const target = path.join(root, 'transient');
91
+ fs.mkdirSync(target, { recursive: true });
92
+ const link = path.join(wt, 'gone-link');
93
+ fs.symlinkSync(target, link, DIR_LINK);
94
+ fs.rmSync(target, { recursive: true, force: true });
95
+ const result = await removeWorktreeResidue({ dir: wt });
96
+ expect(result.removed).toBe(true);
97
+ expect(residuePathExists(wt)).toBe(false);
98
+ });
99
+ });
100
+ // ─── Ordinary outcomes ──────────────────────────────────
101
+ describe('removeWorktreeResidue', () => {
102
+ it('reports removed for a path that is already gone, in one attempt', async () => {
103
+ const result = await removeWorktreeResidue({ dir: path.join(root, 'never-existed') });
104
+ expect(result.removed).toBe(true);
105
+ expect(result.attempts).toBe(1);
106
+ expect(result.strippedLinks).toEqual([]);
107
+ });
108
+ it('deletes an ordinary residue tree', async () => {
109
+ const wt = path.join(root, 'plain');
110
+ fs.mkdirSync(path.join(wt, 'src'), { recursive: true });
111
+ fs.writeFileSync(path.join(wt, 'src', 'a.ts'), 'x', 'utf-8');
112
+ const result = await removeWorktreeResidue({ dir: wt });
113
+ expect(result.removed).toBe(true);
114
+ expect(residuePathExists(wt)).toBe(false);
115
+ });
116
+ // The survivor report is what the CLI's hard error names, so it must list
117
+ // the directory itself plus what is inside it. Forced with a zero attempt
118
+ // budget rather than a permission fixture: making `rm` genuinely fail is
119
+ // platform-specific (an open handle on win32, a chmod'd parent on POSIX that
120
+ // does nothing when the suite runs as root), and a test that only sometimes
121
+ // reaches the arm it names asserts nothing. The arm reached here is the same
122
+ // one a real EBUSY reaches — "attempts exhausted, directory still present".
123
+ it('reports survivors and removed:false when the delete budget is exhausted', async () => {
124
+ const wt = path.join(root, 'survivor');
125
+ fs.mkdirSync(path.join(wt, 'src'), { recursive: true });
126
+ fs.writeFileSync(path.join(wt, 'src', 'a.ts'), 'x', 'utf-8');
127
+ const result = await removeWorktreeResidue({ dir: wt, attempts: 0 });
128
+ expect(result.removed).toBe(false);
129
+ expect(result.survivors[0]).toBe(path.resolve(wt));
130
+ expect(result.survivors.map((p) => path.basename(p))).toContain('a.ts');
131
+ expect(residuePathExists(wt)).toBe(true);
132
+ });
133
+ it('honours the injected attempt budget', async () => {
134
+ const wt = path.join(root, 'budget');
135
+ fs.mkdirSync(wt, { recursive: true });
136
+ const result = await removeWorktreeResidue({ dir: wt, attempts: 1, retryDelayMs: 1 });
137
+ expect(result.attempts).toBe(1);
138
+ expect(result.removed).toBe(true);
139
+ });
140
+ // The zero-attempt case above never ENTERS the loop, so the retry counter
141
+ // and `lastError` — a real contract, rendered by `wt remove`'s hard error —
142
+ // need a delete that genuinely throws (bot round, CR finding 7).
143
+ it('retries and reports lastError when the recursive delete keeps failing', async () => {
144
+ const wt = path.join(root, 'busy');
145
+ fs.mkdirSync(wt, { recursive: true });
146
+ const boom = () => {
147
+ throw Object.assign(new Error('EBUSY: resource busy or locked'), { code: 'EBUSY' });
148
+ };
149
+ // Exactly the attempt budget: both throws are consumed in-test, and the
150
+ // afterEach cleanup gets the real `rmSync` back.
151
+ vi.mocked(fs.rmSync).mockImplementationOnce(boom).mockImplementationOnce(boom);
152
+ const result = await removeWorktreeResidue({ dir: wt, attempts: 2, retryDelayMs: 1 });
153
+ expect(result.attempts).toBe(2);
154
+ expect(result.removed).toBe(false);
155
+ expect(result.lastError).toContain('EBUSY');
156
+ });
157
+ it('fails CLOSED: a non-ENOENT probe failure reads as a survivor, never a removal', () => {
158
+ vi.mocked(fs.lstatSync).mockImplementationOnce(() => {
159
+ throw Object.assign(new Error('EACCES: permission denied'), { code: 'EACCES' });
160
+ });
161
+ expect(residuePathExists(path.join(root, 'denied'))).toBe(true);
162
+ });
163
+ it('reads ENOENT as absent through the same mock seam', () => {
164
+ vi.mocked(fs.lstatSync).mockImplementationOnce(() => {
165
+ throw Object.assign(new Error('ENOENT: no such file or directory'), { code: 'ENOENT' });
166
+ });
167
+ expect(residuePathExists(path.join(root, 'missing'))).toBe(false);
168
+ });
169
+ it('fails CLOSED on a non-object throw — no errno cannot prove absence', () => {
170
+ // Round 2, GCA null-safety finding: a throw that is not an object carries
171
+ // no `code`; the probe must answer a survivor, not crash on the extraction.
172
+ vi.mocked(fs.lstatSync).mockImplementationOnce(() => {
173
+ throw null;
174
+ });
175
+ expect(residuePathExists(path.join(root, 'null-throw'))).toBe(true);
176
+ });
177
+ });
178
+ //# sourceMappingURL=worktree-residue.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree-residue.test.js","sourceRoot":"","sources":["../src/worktree-residue.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmnto/totem",
3
- "version": "1.112.0",
3
+ "version": "1.113.0",
4
4
  "description": "Core engine for Totem, the local-first toolkit that keeps AI-agent work queryable, enforceable, and derivable as plain files in your codebase",
5
5
  "type": "module",
6
6
  "engines": {