@kuznai/inception-engine 0.23.0 → 0.24.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,395 @@
1
+ import assert from "node:assert/strict";
2
+ import { chmod, lstat, mkdir, readlink, rm, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { describe, it } from "node:test";
5
+ import { executeDeploy, planDeploy } from "../../../src/core/deploy.js";
6
+ import { lookupDeployment, registerDeployment, } from "../../../src/core/ownership.js";
7
+ import { UserError } from "../../../src/errors.js";
8
+ import { exists, makeTmpDir } from "../../helpers/fs.js";
9
+ import { createSkillSource, testSkillManifest, } from "../../helpers/skill-dir.js";
10
+ describe("executeDeploy (POSIX)", {
11
+ skip: process.platform === "win32",
12
+ }, () => {
13
+ it("creates symlinks", async () => {
14
+ const sourceDir = await makeTmpDir();
15
+ const home = await makeTmpDir();
16
+ try {
17
+ await createSkillSource(sourceDir, "skills/test-skill");
18
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
19
+ const { succeeded, failed } = await executeDeploy(actions, false, false, home);
20
+ assert.equal(succeeded, 1);
21
+ assert.equal(failed.length, 0);
22
+ const target = actions[0]?.target;
23
+ assert.ok(await exists(target));
24
+ assert.ok((await lstat(target)).isSymbolicLink());
25
+ const action = actions[0];
26
+ if (action?.kind !== "skill-dir") {
27
+ assert.fail("Expected skill-dir action");
28
+ }
29
+ assert.equal(await readlink(target), action.source);
30
+ }
31
+ finally {
32
+ await rm(sourceDir, { recursive: true, force: true });
33
+ await rm(home, { recursive: true, force: true });
34
+ }
35
+ });
36
+ it("registers deployment in registry on symlink deploy", async () => {
37
+ const sourceDir = await makeTmpDir();
38
+ const home = await makeTmpDir();
39
+ try {
40
+ const skillSource = await createSkillSource(sourceDir, "skills/test-skill");
41
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
42
+ await executeDeploy(actions, false, false, home);
43
+ const target = actions[0]?.target;
44
+ const entry = await lookupDeployment(home, target);
45
+ assert.ok(entry);
46
+ if (entry.kind !== "skill-dir") {
47
+ assert.fail("Expected skill-dir");
48
+ }
49
+ assert.equal(entry.skill, "test-skill");
50
+ assert.equal(entry.agent, "claude-code");
51
+ assert.equal(entry.source, skillSource);
52
+ assert.equal(entry.method, "symlink");
53
+ assert.ok(!(await exists(path.join(skillSource, ".inception-totem"))), "no .inception-totem should be written to source directory");
54
+ }
55
+ finally {
56
+ await rm(sourceDir, { recursive: true, force: true });
57
+ await rm(home, { recursive: true, force: true });
58
+ }
59
+ });
60
+ it("does not create symlinks in dry-run mode", async () => {
61
+ const sourceDir = await makeTmpDir();
62
+ const home = await makeTmpDir();
63
+ try {
64
+ await createSkillSource(sourceDir, "skills/test-skill");
65
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
66
+ const { succeeded, failed } = await executeDeploy(actions, true, false, home);
67
+ assert.equal(succeeded, 1);
68
+ assert.equal(failed.length, 0);
69
+ assert.ok(!(await exists(actions[0]?.target)));
70
+ }
71
+ finally {
72
+ await rm(sourceDir, { recursive: true, force: true });
73
+ await rm(home, { recursive: true, force: true });
74
+ }
75
+ });
76
+ it("overwrites existing symlink with a matching registry entry", async () => {
77
+ const sourceDir = await makeTmpDir();
78
+ const home = await makeTmpDir();
79
+ try {
80
+ await createSkillSource(sourceDir, "skills/test-skill");
81
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
82
+ await executeDeploy(actions, false, false, home);
83
+ const { succeeded, failed } = await executeDeploy(actions, false, false, home);
84
+ assert.equal(succeeded, 1);
85
+ assert.equal(failed.length, 0);
86
+ }
87
+ finally {
88
+ await rm(sourceDir, { recursive: true, force: true });
89
+ await rm(home, { recursive: true, force: true });
90
+ }
91
+ });
92
+ it("reports missing source during planning", async () => {
93
+ const home = await makeTmpDir();
94
+ try {
95
+ await assert.rejects(planDeploy(testSkillManifest, "/nonexistent/source", ["claude-code"], home), (err) => {
96
+ assert.ok(err instanceof UserError);
97
+ assert.equal(err.code, "DEPLOY_FAILED");
98
+ assert.match(err.message, /source not found/);
99
+ return true;
100
+ });
101
+ }
102
+ finally {
103
+ await rm(home, { recursive: true, force: true });
104
+ }
105
+ });
106
+ it("reports permission denied when the source tree is unreadable", async () => {
107
+ const sourceDir = await makeTmpDir();
108
+ const home = await makeTmpDir();
109
+ const skillsDir = path.join(sourceDir, "skills");
110
+ try {
111
+ await createSkillSource(sourceDir, "skills/test-skill");
112
+ await chmod(skillsDir, 0o000);
113
+ await assert.rejects(planDeploy(testSkillManifest, sourceDir, ["claude-code"], home), (err) => {
114
+ assert.ok(err instanceof UserError);
115
+ assert.equal(err.code, "DEPLOY_FAILED");
116
+ assert.match(err.message, /Permission denied/);
117
+ return true;
118
+ });
119
+ }
120
+ finally {
121
+ await chmod(skillsDir, 0o755);
122
+ await rm(sourceDir, { recursive: true, force: true });
123
+ await rm(home, { recursive: true, force: true });
124
+ }
125
+ });
126
+ it("reports permission denied when the skill directory is execute-only", {
127
+ skip: process.getuid?.() === 0,
128
+ }, async () => {
129
+ const sourceDir = await makeTmpDir();
130
+ const home = await makeTmpDir();
131
+ const skillDir = path.join(sourceDir, "skills", "test-skill");
132
+ try {
133
+ await createSkillSource(sourceDir, "skills/test-skill");
134
+ await chmod(skillDir, 0o111);
135
+ await assert.rejects(planDeploy(testSkillManifest, sourceDir, ["claude-code"], home), (err) => {
136
+ assert.ok(err instanceof UserError);
137
+ assert.equal(err.code, "DEPLOY_FAILED");
138
+ assert.match(err.message, /Permission denied reading skill directory/);
139
+ return true;
140
+ });
141
+ }
142
+ finally {
143
+ try {
144
+ await chmod(skillDir, 0o755);
145
+ }
146
+ catch {
147
+ // best effort
148
+ }
149
+ await rm(sourceDir, { recursive: true, force: true });
150
+ await rm(home, { recursive: true, force: true });
151
+ }
152
+ });
153
+ it("reports permission denied when SKILL.md is unreadable", {
154
+ skip: process.getuid?.() === 0,
155
+ }, async () => {
156
+ const sourceDir = await makeTmpDir();
157
+ const home = await makeTmpDir();
158
+ const skillMdPath = path.join(sourceDir, "skills", "test-skill", "SKILL.md");
159
+ try {
160
+ await createSkillSource(sourceDir, "skills/test-skill");
161
+ await chmod(skillMdPath, 0o000);
162
+ await assert.rejects(planDeploy(testSkillManifest, sourceDir, ["claude-code"], home), (err) => {
163
+ assert.ok(err instanceof UserError);
164
+ assert.equal(err.code, "DEPLOY_FAILED");
165
+ assert.match(err.message, /Permission denied reading SKILL\.md/);
166
+ return true;
167
+ });
168
+ }
169
+ finally {
170
+ try {
171
+ await chmod(skillMdPath, 0o644);
172
+ }
173
+ catch {
174
+ // best effort
175
+ }
176
+ await rm(sourceDir, { recursive: true, force: true });
177
+ await rm(home, { recursive: true, force: true });
178
+ }
179
+ });
180
+ it("refuses to overwrite an unmanaged target", async () => {
181
+ const sourceDir = await makeTmpDir();
182
+ const home = await makeTmpDir();
183
+ try {
184
+ await createSkillSource(sourceDir, "skills/test-skill");
185
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
186
+ const target = actions[0]?.target;
187
+ await mkdir(target, { recursive: true });
188
+ await writeFile(path.join(target, "something.txt"), "user content");
189
+ const { succeeded, failed } = await executeDeploy(actions, false, false, home);
190
+ assert.equal(succeeded, 0);
191
+ assert.equal(failed.length, 1);
192
+ assert.ok(failed[0]?.error.includes("not managed by inception-engine"));
193
+ assert.ok(await exists(path.join(target, "something.txt")));
194
+ }
195
+ finally {
196
+ await rm(sourceDir, { recursive: true, force: true });
197
+ await rm(home, { recursive: true, force: true });
198
+ }
199
+ });
200
+ it("refuses to overwrite a target with a mismatched registry entry", async () => {
201
+ const sourceDir = await makeTmpDir();
202
+ const home = await makeTmpDir();
203
+ try {
204
+ await createSkillSource(sourceDir, "skills/test-skill");
205
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
206
+ const target = actions[0]?.target;
207
+ await mkdir(target, { recursive: true });
208
+ await writeFile(path.join(target, "SKILL.md"), "other content");
209
+ await registerDeployment(home, target, {
210
+ kind: "skill-dir",
211
+ source: "/completely/different/source",
212
+ skill: "different-skill",
213
+ agent: "codex",
214
+ method: "symlink",
215
+ });
216
+ const { succeeded, failed } = await executeDeploy(actions, false, false, home);
217
+ assert.equal(succeeded, 0);
218
+ assert.equal(failed.length, 1);
219
+ assert.ok(failed[0]?.error.includes("not managed by inception-engine"));
220
+ assert.ok(await exists(path.join(target, "SKILL.md")));
221
+ }
222
+ finally {
223
+ await rm(sourceDir, { recursive: true, force: true });
224
+ await rm(home, { recursive: true, force: true });
225
+ }
226
+ });
227
+ it("removes the backup after a successful redeploy", async () => {
228
+ const sourceDir = await makeTmpDir();
229
+ const home = await makeTmpDir();
230
+ try {
231
+ await createSkillSource(sourceDir, "skills/test-skill");
232
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
233
+ const target = actions[0]?.target;
234
+ const backupPath = `${target}.inception-backup`;
235
+ await executeDeploy(actions, false, false, home);
236
+ await executeDeploy(actions, false, false, home);
237
+ assert.ok(!(await exists(backupPath)));
238
+ assert.ok(await exists(target));
239
+ }
240
+ finally {
241
+ await rm(sourceDir, { recursive: true, force: true });
242
+ await rm(home, { recursive: true, force: true });
243
+ }
244
+ });
245
+ });
246
+ describe("atomic redeploy behavior (POSIX)", {
247
+ skip: process.platform === "win32",
248
+ }, () => {
249
+ it("cleans up stale .inception-backup from a previous failed attempt", async () => {
250
+ const sourceDir = await makeTmpDir();
251
+ const home = await makeTmpDir();
252
+ try {
253
+ await createSkillSource(sourceDir, "skills/test-skill");
254
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
255
+ const target = actions[0]?.target;
256
+ const backupPath = `${target}.inception-backup`;
257
+ await executeDeploy(actions, false, false, home);
258
+ await mkdir(backupPath, { recursive: true });
259
+ await writeFile(path.join(backupPath, "stale.txt"), "leftover");
260
+ const { succeeded, failed } = await executeDeploy(actions, false, false, home);
261
+ assert.equal(succeeded, 1);
262
+ assert.equal(failed.length, 0);
263
+ assert.ok(!(await exists(backupPath)));
264
+ assert.ok(await exists(target));
265
+ }
266
+ finally {
267
+ await rm(sourceDir, { recursive: true, force: true });
268
+ await rm(home, { recursive: true, force: true });
269
+ }
270
+ });
271
+ it("does not create a backup on first deploy", async () => {
272
+ const sourceDir = await makeTmpDir();
273
+ const home = await makeTmpDir();
274
+ try {
275
+ await createSkillSource(sourceDir, "skills/test-skill");
276
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
277
+ const target = actions[0]?.target;
278
+ const backupPath = `${target}.inception-backup`;
279
+ const { succeeded } = await executeDeploy(actions, false, false, home);
280
+ assert.equal(succeeded, 1);
281
+ assert.ok(await exists(target));
282
+ assert.ok(!(await exists(backupPath)));
283
+ }
284
+ finally {
285
+ await rm(sourceDir, { recursive: true, force: true });
286
+ await rm(home, { recursive: true, force: true });
287
+ }
288
+ });
289
+ it("restores the backup when registry write fails", async () => {
290
+ const sourceDir = await makeTmpDir();
291
+ const home = await makeTmpDir();
292
+ const registryFile = path.join(home, ".inception-engine", "registry.json");
293
+ try {
294
+ await createSkillSource(sourceDir, "skills/test-skill");
295
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
296
+ const target = actions[0]?.target;
297
+ const backupPath = `${target}.inception-backup`;
298
+ const { succeeded: firstSucceeded } = await executeDeploy(actions, false, false, home);
299
+ assert.equal(firstSucceeded, 1);
300
+ const originalLink = await readlink(target);
301
+ await chmod(registryFile, 0o444);
302
+ const { succeeded, failed } = await executeDeploy(actions, false, false, home);
303
+ assert.equal(succeeded, 0);
304
+ assert.equal(failed.length, 1);
305
+ assert.ok(!(await exists(backupPath)));
306
+ assert.ok(await exists(target));
307
+ assert.ok((await lstat(target)).isSymbolicLink());
308
+ assert.equal(await readlink(target), originalLink);
309
+ }
310
+ finally {
311
+ try {
312
+ await chmod(registryFile, 0o644);
313
+ }
314
+ catch {
315
+ // best effort
316
+ }
317
+ await rm(sourceDir, { recursive: true, force: true });
318
+ await rm(home, { recursive: true, force: true });
319
+ }
320
+ });
321
+ it("restores the backup when copy-based redeploy fails", async () => {
322
+ const sourceDir = await makeTmpDir();
323
+ const home = await makeTmpDir();
324
+ try {
325
+ const skillSource = await createSkillSource(sourceDir, "skills/test-skill");
326
+ const target = path.join(home, ".claude", "skills", "test-skill");
327
+ await mkdir(path.dirname(target), { recursive: true });
328
+ const firstAction = {
329
+ kind: "skill-dir",
330
+ skill: "test-skill",
331
+ agent: "claude-code",
332
+ source: skillSource,
333
+ target,
334
+ method: "copy",
335
+ confidence: "documented",
336
+ };
337
+ const { succeeded: firstSucceeded } = await executeDeploy([firstAction], false, false, home);
338
+ assert.equal(firstSucceeded, 1);
339
+ assert.ok(await exists(target));
340
+ const unreadableSource = path.join(sourceDir, "unreadable-source");
341
+ await mkdir(unreadableSource, { recursive: true });
342
+ await writeFile(path.join(unreadableSource, "SKILL.md"), "---");
343
+ await chmod(unreadableSource, 0o000);
344
+ const failAction = {
345
+ kind: "skill-dir",
346
+ skill: "test-skill",
347
+ agent: "claude-code",
348
+ source: unreadableSource,
349
+ target,
350
+ method: "copy",
351
+ confidence: "documented",
352
+ };
353
+ const backupPath = `${target}.inception-backup`;
354
+ const { succeeded, failed } = await executeDeploy([failAction], false, false, home);
355
+ assert.equal(succeeded, 0);
356
+ assert.equal(failed.length, 1);
357
+ assert.ok(!(await exists(backupPath)));
358
+ assert.ok(await exists(target));
359
+ assert.ok(await exists(path.join(target, "SKILL.md")));
360
+ }
361
+ finally {
362
+ try {
363
+ await chmod(path.join(sourceDir, "unreadable-source"), 0o755);
364
+ }
365
+ catch {
366
+ // best effort
367
+ }
368
+ await rm(sourceDir, { recursive: true, force: true });
369
+ await rm(home, { recursive: true, force: true });
370
+ }
371
+ });
372
+ it("atomically replaces stale backups without leaking stale content", async () => {
373
+ const sourceDir = await makeTmpDir();
374
+ const home = await makeTmpDir();
375
+ try {
376
+ await createSkillSource(sourceDir, "skills/test-skill");
377
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
378
+ const target = actions[0]?.target;
379
+ const backupPath = `${target}.inception-backup`;
380
+ await executeDeploy(actions, false, false, home);
381
+ await mkdir(backupPath, { recursive: true });
382
+ await writeFile(path.join(backupPath, "stale.txt"), "leftover content");
383
+ const { succeeded, failed } = await executeDeploy(actions, false, false, home);
384
+ assert.equal(succeeded, 1);
385
+ assert.equal(failed.length, 0);
386
+ assert.ok(await exists(target));
387
+ assert.ok(!(await exists(backupPath)));
388
+ assert.ok(!(await exists(path.join(target, "stale.txt"))));
389
+ }
390
+ finally {
391
+ await rm(sourceDir, { recursive: true, force: true });
392
+ await rm(home, { recursive: true, force: true });
393
+ }
394
+ });
395
+ });
@@ -0,0 +1,188 @@
1
+ import assert from "node:assert/strict";
2
+ import { chmod, mkdir, rm, symlink, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { describe, it } from "node:test";
5
+ import { lookupDeployment, registerDeployment, } from "../../../src/core/ownership.js";
6
+ import { executeRevert, planRevert } from "../../../src/core/revert.js";
7
+ import { exists, makeTmpDir } from "../../helpers/fs.js";
8
+ import { testSkillManifest } from "../../helpers/skill-dir.js";
9
+ describe("executeRevert (POSIX)", {
10
+ skip: process.platform === "win32",
11
+ }, () => {
12
+ it("removes a symlink registered in the deployment registry", async () => {
13
+ const home = await makeTmpDir();
14
+ const sourceDir = await makeTmpDir();
15
+ try {
16
+ const actions = planRevert(testSkillManifest, ["claude-code"], home);
17
+ const target = actions[0]?.target;
18
+ await mkdir(path.dirname(target), { recursive: true });
19
+ await symlink(sourceDir, target, "dir");
20
+ await registerDeployment(home, target, {
21
+ kind: "skill-dir",
22
+ source: sourceDir,
23
+ skill: "test-skill",
24
+ agent: "claude-code",
25
+ method: "symlink",
26
+ });
27
+ assert.ok(await exists(target));
28
+ const { succeeded, skipped } = await executeRevert(actions, false, false, home);
29
+ assert.equal(succeeded, 1);
30
+ assert.equal(skipped, 0);
31
+ assert.ok(!(await exists(target)));
32
+ assert.equal(await lookupDeployment(home, target), null);
33
+ }
34
+ finally {
35
+ await rm(home, { recursive: true, force: true });
36
+ await rm(sourceDir, { recursive: true, force: true });
37
+ }
38
+ });
39
+ it("removes a copied directory registered in the deployment registry", async () => {
40
+ const home = await makeTmpDir();
41
+ try {
42
+ const actions = planRevert(testSkillManifest, ["claude-code"], home);
43
+ const target = actions[0]?.target;
44
+ await mkdir(target, { recursive: true });
45
+ await writeFile(path.join(target, "SKILL.md"), "test");
46
+ await registerDeployment(home, target, {
47
+ kind: "skill-dir",
48
+ source: "/original/source",
49
+ skill: "test-skill",
50
+ agent: "claude-code",
51
+ method: "copy",
52
+ });
53
+ const { succeeded, skipped } = await executeRevert(actions, false, false, home);
54
+ assert.equal(succeeded, 1);
55
+ assert.equal(skipped, 0);
56
+ assert.ok(!(await exists(target)));
57
+ assert.equal(await lookupDeployment(home, target), null);
58
+ }
59
+ finally {
60
+ await rm(home, { recursive: true, force: true });
61
+ }
62
+ });
63
+ it("skips missing targets", async () => {
64
+ const home = await makeTmpDir();
65
+ try {
66
+ const actions = planRevert(testSkillManifest, ["claude-code"], home);
67
+ const { succeeded, skipped } = await executeRevert(actions, false, false, home);
68
+ assert.equal(succeeded, 0);
69
+ assert.equal(skipped, 1);
70
+ }
71
+ finally {
72
+ await rm(home, { recursive: true, force: true });
73
+ }
74
+ });
75
+ it("records a failure when removal is blocked by permissions", async () => {
76
+ const home = await makeTmpDir();
77
+ const sourceDir = await makeTmpDir();
78
+ const actions = planRevert(testSkillManifest, ["claude-code"], home);
79
+ const target = actions[0]?.target ?? "";
80
+ const targetParent = path.dirname(target);
81
+ try {
82
+ await mkdir(target, { recursive: true });
83
+ await writeFile(path.join(target, "SKILL.md"), "test");
84
+ await registerDeployment(home, target, {
85
+ kind: "skill-dir",
86
+ source: sourceDir,
87
+ skill: "test-skill",
88
+ agent: "claude-code",
89
+ method: "copy",
90
+ });
91
+ await chmod(targetParent, 0o555);
92
+ const { succeeded, skipped, failed } = await executeRevert(actions, false, false, home);
93
+ assert.equal(succeeded, 0);
94
+ assert.equal(skipped, 0);
95
+ assert.equal(failed.length, 1);
96
+ }
97
+ finally {
98
+ await chmod(targetParent, 0o755);
99
+ await rm(home, { recursive: true, force: true });
100
+ await rm(sourceDir, { recursive: true, force: true });
101
+ }
102
+ });
103
+ it("does not remove in dry-run mode", async () => {
104
+ const home = await makeTmpDir();
105
+ const sourceDir = await makeTmpDir();
106
+ try {
107
+ const actions = planRevert(testSkillManifest, ["claude-code"], home);
108
+ const target = actions[0]?.target;
109
+ await mkdir(path.dirname(target), { recursive: true });
110
+ await symlink(sourceDir, target, "dir");
111
+ await registerDeployment(home, target, {
112
+ kind: "skill-dir",
113
+ source: sourceDir,
114
+ skill: "test-skill",
115
+ agent: "claude-code",
116
+ method: "symlink",
117
+ });
118
+ const { succeeded } = await executeRevert(actions, true, false, home);
119
+ assert.equal(succeeded, 1);
120
+ assert.ok(await exists(target));
121
+ assert.ok(await lookupDeployment(home, target));
122
+ }
123
+ finally {
124
+ await rm(home, { recursive: true, force: true });
125
+ await rm(sourceDir, { recursive: true, force: true });
126
+ }
127
+ });
128
+ it("skips a symlink target that is not in the registry", async () => {
129
+ const home = await makeTmpDir();
130
+ const sourceDir = await makeTmpDir();
131
+ try {
132
+ const actions = planRevert(testSkillManifest, ["claude-code"], home);
133
+ const target = actions[0]?.target;
134
+ await writeFile(path.join(sourceDir, "SKILL.md"), "---");
135
+ await mkdir(path.dirname(target), { recursive: true });
136
+ await symlink(sourceDir, target, "dir");
137
+ const { succeeded, skipped } = await executeRevert(actions, false, false, home);
138
+ assert.equal(succeeded, 0);
139
+ assert.equal(skipped, 1);
140
+ assert.ok(await exists(target));
141
+ }
142
+ finally {
143
+ await rm(home, { recursive: true, force: true });
144
+ await rm(sourceDir, { recursive: true, force: true });
145
+ }
146
+ });
147
+ it("skips a directory target that is not in the registry", async () => {
148
+ const home = await makeTmpDir();
149
+ try {
150
+ const actions = planRevert(testSkillManifest, ["claude-code"], home);
151
+ const target = actions[0]?.target;
152
+ await mkdir(target, { recursive: true });
153
+ await writeFile(path.join(target, "SKILL.md"), "user content");
154
+ const { succeeded, skipped } = await executeRevert(actions, false, false, home);
155
+ assert.equal(succeeded, 0);
156
+ assert.equal(skipped, 1);
157
+ assert.ok(await exists(target));
158
+ }
159
+ finally {
160
+ await rm(home, { recursive: true, force: true });
161
+ }
162
+ });
163
+ it("skips a target whose registry entry has mismatched ownership", async () => {
164
+ const home = await makeTmpDir();
165
+ const sourceDir = await makeTmpDir();
166
+ try {
167
+ const actions = planRevert(testSkillManifest, ["claude-code"], home);
168
+ const target = actions[0]?.target;
169
+ await mkdir(path.dirname(target), { recursive: true });
170
+ await symlink(sourceDir, target, "dir");
171
+ await registerDeployment(home, target, {
172
+ kind: "skill-dir",
173
+ source: sourceDir,
174
+ skill: "different-skill",
175
+ agent: "codex",
176
+ method: "symlink",
177
+ });
178
+ const { succeeded, skipped } = await executeRevert(actions, false, false, home);
179
+ assert.equal(succeeded, 0);
180
+ assert.equal(skipped, 1);
181
+ assert.ok(await exists(target));
182
+ }
183
+ finally {
184
+ await rm(home, { recursive: true, force: true });
185
+ await rm(sourceDir, { recursive: true, force: true });
186
+ }
187
+ });
188
+ });
@@ -0,0 +1 @@
1
+ export {};