@kuznai/inception-engine 0.18.0 → 0.20.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.
- package/README.md +72 -43
- package/dist/{config → src/config}/agents.js +174 -24
- package/dist/src/core/adapters/agent-definitions.d.ts +8 -0
- package/dist/src/core/adapters/agent-definitions.js +162 -0
- package/dist/{core → src/core}/adapters/frontmatter.d.ts +10 -0
- package/dist/{core → src/core}/adapters/frontmatter.js +23 -6
- package/dist/{core → src/core}/adapters/index.d.ts +1 -1
- package/dist/{core → src/core}/adapters/index.js +4 -4
- package/dist/{core → src/core}/adapters/mcp.d.ts +2 -2
- package/dist/{core → src/core}/adapters/mcp.js +21 -24
- package/dist/{core → src/core}/adapters/permissions.js +16 -20
- package/dist/{core → src/core}/adapters/rules.d.ts +2 -2
- package/dist/src/core/adapters/rules.js +126 -0
- package/dist/src/core/capabilities.d.ts +47 -0
- package/dist/src/core/capabilities.js +280 -0
- package/dist/{core → src/core}/deploy.d.ts +1 -1
- package/dist/{core → src/core}/deploy.js +273 -92
- package/dist/{core → src/core}/init.js +126 -52
- package/dist/src/core/merge-patch.d.ts +4 -0
- package/dist/src/core/merge-patch.js +46 -0
- package/dist/{core → src/core}/ownership.d.ts +2 -2
- package/dist/{core → src/core}/ownership.js +13 -0
- package/dist/{core → src/core}/preflight.d.ts +1 -1
- package/dist/src/core/preflight.js +280 -0
- package/dist/{core → src/core}/resolve.d.ts +1 -1
- package/dist/{core → src/core}/resolve.js +7 -4
- package/dist/{core → src/core}/revert.js +95 -33
- package/dist/{core → src/core}/runtime-paths.d.ts +2 -1
- package/dist/{core → src/core}/runtime-paths.js +30 -23
- package/dist/{core → src/core}/validation.d.ts +10 -1
- package/dist/{core → src/core}/validation.js +75 -0
- package/dist/src/formatters.d.ts +5 -0
- package/dist/src/formatters.js +58 -0
- package/dist/{index.js → src/index.js} +8 -24
- package/dist/src/logger.js +62 -0
- package/dist/{schemas → src/schemas}/manifest.d.ts +22 -0
- package/dist/{schemas → src/schemas}/manifest.js +19 -5
- package/dist/{schemas → src/schemas}/registry.d.ts +74 -0
- package/dist/{schemas → src/schemas}/registry.js +18 -3
- package/dist/{types.d.ts → src/types.d.ts} +60 -0
- package/dist/test/helpers/fs.d.ts +2 -0
- package/dist/test/helpers/fs.js +17 -0
- package/dist/test/helpers/path.d.ts +10 -0
- package/dist/test/helpers/path.js +15 -0
- package/dist/test/helpers/skill-dir-scenarios.d.ts +7 -0
- package/dist/test/helpers/skill-dir-scenarios.js +206 -0
- package/dist/test/helpers/skill-dir.d.ts +7 -0
- package/dist/test/helpers/skill-dir.js +46 -0
- package/dist/test/os/cross-platform/cross-platform.test.d.ts +1 -0
- package/dist/test/os/cross-platform/cross-platform.test.js +328 -0
- package/dist/test/os/posix/skill-dir.test.d.ts +1 -0
- package/dist/test/os/posix/skill-dir.test.js +19 -0
- package/dist/test/os/windows/revert-integration.test.d.ts +1 -0
- package/dist/test/os/windows/revert-integration.test.js +72 -0
- package/dist/test/os/windows/skill-dir.test.d.ts +1 -0
- package/dist/test/os/windows/skill-dir.test.js +19 -0
- package/dist/test/unit/adapters.test.d.ts +1 -0
- package/dist/test/unit/adapters.test.js +947 -0
- package/dist/test/unit/capabilities.test.d.ts +1 -0
- package/dist/test/unit/capabilities.test.js +71 -0
- package/dist/test/unit/cli.test.d.ts +1 -0
- package/dist/test/unit/cli.test.js +834 -0
- package/dist/test/unit/deploy.test.d.ts +1 -0
- package/dist/test/unit/deploy.test.js +2481 -0
- package/dist/test/unit/detect.test.d.ts +1 -0
- package/dist/test/unit/detect.test.js +192 -0
- package/dist/test/unit/enterprise.test.d.ts +1 -0
- package/dist/test/unit/enterprise.test.js +106 -0
- package/dist/test/unit/frontmatter.test.d.ts +1 -0
- package/dist/test/unit/frontmatter.test.js +102 -0
- package/dist/test/unit/gemini-north-star.test.d.ts +1 -0
- package/dist/test/unit/gemini-north-star.test.js +243 -0
- package/dist/test/unit/manifest.test.d.ts +1 -0
- package/dist/test/unit/manifest.test.js +737 -0
- package/dist/test/unit/migration-golden.test.d.ts +1 -0
- package/dist/test/unit/migration-golden.test.js +197 -0
- package/dist/test/unit/ownership.test.d.ts +1 -0
- package/dist/test/unit/ownership.test.js +587 -0
- package/dist/test/unit/preflight.test.d.ts +1 -0
- package/dist/test/unit/preflight.test.js +501 -0
- package/dist/test/unit/resolve.test.d.ts +1 -0
- package/dist/test/unit/resolve.test.js +119 -0
- package/dist/test/unit/revert.test.d.ts +1 -0
- package/dist/test/unit/revert.test.js +1004 -0
- package/dist/test/unit/toml.test.d.ts +1 -0
- package/dist/test/unit/toml.test.js +55 -0
- package/package.json +1 -1
- package/dist/core/adapters/agent-definitions.d.ts +0 -20
- package/dist/core/adapters/agent-definitions.js +0 -90
- package/dist/core/adapters/rules.js +0 -103
- package/dist/core/preflight.js +0 -98
- package/dist/logger.js +0 -61
- /package/dist/{config → src/config}/agents.d.ts +0 -0
- /package/dist/{config → src/config}/manifest.d.ts +0 -0
- /package/dist/{config → src/config}/manifest.js +0 -0
- /package/dist/{core → src/core}/adapters/permissions.d.ts +0 -0
- /package/dist/{core → src/core}/adapters/toml.d.ts +0 -0
- /package/dist/{core → src/core}/adapters/toml.js +0 -0
- /package/dist/{core → src/core}/detect.d.ts +0 -0
- /package/dist/{core → src/core}/detect.js +0 -0
- /package/dist/{core → src/core}/init.d.ts +0 -0
- /package/dist/{core → src/core}/revert.d.ts +0 -0
- /package/dist/{errors.d.ts → src/errors.d.ts} +0 -0
- /package/dist/{errors.js → src/errors.js} +0 -0
- /package/dist/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/{logger.d.ts → src/logger.d.ts} +0 -0
- /package/dist/{schemas → src/schemas}/errors.d.ts +0 -0
- /package/dist/{schemas → src/schemas}/errors.js +0 -0
- /package/dist/{types.js → src/types.js} +0 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { describe, it } from "node:test";
|
|
5
|
+
import { defaultRegistryPersistence, lookupDeployment, registerDeployment, registryPath, unregisterDeployment, verifyDeployment, } from "../../src/core/ownership.js";
|
|
6
|
+
import { exists, makeTmpDir } from "../helpers/fs.js";
|
|
7
|
+
describe("registerDeployment", () => {
|
|
8
|
+
it("creates registry file and adds entry", async () => {
|
|
9
|
+
const home = await makeTmpDir();
|
|
10
|
+
try {
|
|
11
|
+
const target = "/fake/target/skill";
|
|
12
|
+
await registerDeployment(home, target, {
|
|
13
|
+
kind: "skill-dir",
|
|
14
|
+
source: "/fake/source/skill",
|
|
15
|
+
skill: "my-skill",
|
|
16
|
+
agent: "claude-code",
|
|
17
|
+
method: "symlink",
|
|
18
|
+
});
|
|
19
|
+
assert.ok(await exists(registryPath(home)));
|
|
20
|
+
const registry = JSON.parse(await readFile(registryPath(home), "utf-8"));
|
|
21
|
+
assert.equal(registry.version, 1);
|
|
22
|
+
assert.ok(registry.deployments[target]);
|
|
23
|
+
assert.equal(registry.deployments[target].kind, "skill-dir");
|
|
24
|
+
assert.equal(registry.deployments[target].skill, "my-skill");
|
|
25
|
+
assert.equal(registry.deployments[target].agent, "claude-code");
|
|
26
|
+
assert.equal(registry.deployments[target].source, "/fake/source/skill");
|
|
27
|
+
assert.equal(registry.deployments[target].method, "symlink");
|
|
28
|
+
assert.ok(registry.deployments[target].deployed);
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
await rm(home, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
it("overwrites existing entry for same target", async () => {
|
|
35
|
+
const home = await makeTmpDir();
|
|
36
|
+
try {
|
|
37
|
+
const target = "/fake/target/skill";
|
|
38
|
+
await registerDeployment(home, target, {
|
|
39
|
+
kind: "skill-dir",
|
|
40
|
+
source: "/old/source",
|
|
41
|
+
skill: "my-skill",
|
|
42
|
+
agent: "claude-code",
|
|
43
|
+
method: "symlink",
|
|
44
|
+
});
|
|
45
|
+
await registerDeployment(home, target, {
|
|
46
|
+
kind: "skill-dir",
|
|
47
|
+
source: "/new/source",
|
|
48
|
+
skill: "my-skill",
|
|
49
|
+
agent: "claude-code",
|
|
50
|
+
method: "symlink",
|
|
51
|
+
});
|
|
52
|
+
const registry = JSON.parse(await readFile(registryPath(home), "utf-8"));
|
|
53
|
+
assert.equal(registry.deployments[target].source, "/new/source");
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
await rm(home, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
it("sets registry file permissions to 0o644 on POSIX", {
|
|
60
|
+
skip: process.platform === "win32",
|
|
61
|
+
}, async () => {
|
|
62
|
+
const home = await makeTmpDir();
|
|
63
|
+
try {
|
|
64
|
+
await registerDeployment(home, "/fake/target", {
|
|
65
|
+
kind: "skill-dir",
|
|
66
|
+
source: "/fake/source",
|
|
67
|
+
skill: "my-skill",
|
|
68
|
+
agent: "claude-code",
|
|
69
|
+
method: "symlink",
|
|
70
|
+
});
|
|
71
|
+
const { stat } = await import("node:fs/promises");
|
|
72
|
+
const statResult = await stat(registryPath(home));
|
|
73
|
+
assert.equal(statResult.mode & 0o777, 0o644);
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
await rm(home, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
it("preserves other entries when adding a new one", async () => {
|
|
80
|
+
const home = await makeTmpDir();
|
|
81
|
+
try {
|
|
82
|
+
await registerDeployment(home, "/target/a", {
|
|
83
|
+
kind: "skill-dir",
|
|
84
|
+
source: "/src/a",
|
|
85
|
+
skill: "a",
|
|
86
|
+
agent: "claude-code",
|
|
87
|
+
method: "symlink",
|
|
88
|
+
});
|
|
89
|
+
await registerDeployment(home, "/target/b", {
|
|
90
|
+
kind: "skill-dir",
|
|
91
|
+
source: "/src/b",
|
|
92
|
+
skill: "b",
|
|
93
|
+
agent: "codex",
|
|
94
|
+
method: "copy",
|
|
95
|
+
});
|
|
96
|
+
const registry = JSON.parse(await readFile(registryPath(home), "utf-8"));
|
|
97
|
+
assert.ok(registry.deployments["/target/a"]);
|
|
98
|
+
assert.ok(registry.deployments["/target/b"]);
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
await rm(home, { recursive: true });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
it("surfaces persistence failures from the registry writer", async () => {
|
|
105
|
+
const home = await makeTmpDir();
|
|
106
|
+
try {
|
|
107
|
+
await assert.rejects(registerDeployment(home, "/fake/target", {
|
|
108
|
+
kind: "skill-dir",
|
|
109
|
+
source: "/fake/source",
|
|
110
|
+
skill: "my-skill",
|
|
111
|
+
agent: "claude-code",
|
|
112
|
+
method: "copy",
|
|
113
|
+
}, {
|
|
114
|
+
load: defaultRegistryPersistence.load,
|
|
115
|
+
async save() {
|
|
116
|
+
throw new Error("simulated registry persistence failure");
|
|
117
|
+
},
|
|
118
|
+
}), /simulated registry persistence failure/);
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
await rm(home, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
describe("unregisterDeployment", () => {
|
|
126
|
+
it("removes the entry for the given target", async () => {
|
|
127
|
+
const home = await makeTmpDir();
|
|
128
|
+
try {
|
|
129
|
+
await registerDeployment(home, "/target/a", {
|
|
130
|
+
kind: "skill-dir",
|
|
131
|
+
source: "/src/a",
|
|
132
|
+
skill: "a",
|
|
133
|
+
agent: "claude-code",
|
|
134
|
+
method: "symlink",
|
|
135
|
+
});
|
|
136
|
+
await unregisterDeployment(home, "/target/a");
|
|
137
|
+
const entry = await lookupDeployment(home, "/target/a");
|
|
138
|
+
assert.equal(entry, null);
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
await rm(home, { recursive: true });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
it("is a no-op if target is not in registry", async () => {
|
|
145
|
+
const home = await makeTmpDir();
|
|
146
|
+
try {
|
|
147
|
+
await registerDeployment(home, "/target/a", {
|
|
148
|
+
kind: "skill-dir",
|
|
149
|
+
source: "/src/a",
|
|
150
|
+
skill: "a",
|
|
151
|
+
agent: "claude-code",
|
|
152
|
+
method: "symlink",
|
|
153
|
+
});
|
|
154
|
+
// Should not throw
|
|
155
|
+
await unregisterDeployment(home, "/target/nonexistent");
|
|
156
|
+
const entry = await lookupDeployment(home, "/target/a");
|
|
157
|
+
assert.ok(entry);
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
await rm(home, { recursive: true });
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
describe("lookupDeployment", () => {
|
|
165
|
+
it("returns the entry when target is registered", async () => {
|
|
166
|
+
const home = await makeTmpDir();
|
|
167
|
+
try {
|
|
168
|
+
await registerDeployment(home, "/target/skill", {
|
|
169
|
+
kind: "skill-dir",
|
|
170
|
+
source: "/src/skill",
|
|
171
|
+
skill: "my-skill",
|
|
172
|
+
agent: "codex",
|
|
173
|
+
method: "copy",
|
|
174
|
+
});
|
|
175
|
+
const entry = await lookupDeployment(home, "/target/skill");
|
|
176
|
+
assert.ok(entry);
|
|
177
|
+
assert.equal(entry.kind, "skill-dir");
|
|
178
|
+
assert.equal(entry.source, "/src/skill");
|
|
179
|
+
assert.equal(entry.skill, "my-skill");
|
|
180
|
+
assert.equal(entry.agent, "codex");
|
|
181
|
+
assert.equal(entry.method, "copy");
|
|
182
|
+
assert.ok(entry.deployed);
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
await rm(home, { recursive: true });
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
it("returns null when target is not registered", async () => {
|
|
189
|
+
const home = await makeTmpDir();
|
|
190
|
+
try {
|
|
191
|
+
const entry = await lookupDeployment(home, "/nonexistent");
|
|
192
|
+
assert.equal(entry, null);
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
await rm(home, { recursive: true });
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
it("returns null when registry file does not exist", async () => {
|
|
199
|
+
const home = await makeTmpDir();
|
|
200
|
+
try {
|
|
201
|
+
const entry = await lookupDeployment(home, "/anything");
|
|
202
|
+
assert.equal(entry, null);
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
await rm(home, { recursive: true });
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
it("returns null when registry file is corrupted", async () => {
|
|
209
|
+
const home = await makeTmpDir();
|
|
210
|
+
try {
|
|
211
|
+
const dir = path.dirname(registryPath(home));
|
|
212
|
+
await mkdir(dir, { recursive: true });
|
|
213
|
+
const { writeFile } = await import("node:fs/promises");
|
|
214
|
+
await writeFile(registryPath(home), "not valid json!!!");
|
|
215
|
+
const entry = await lookupDeployment(home, "/anything");
|
|
216
|
+
assert.equal(entry, null);
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
await rm(home, { recursive: true });
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
describe("verifyDeployment", () => {
|
|
224
|
+
it("returns entry when all fields match", async () => {
|
|
225
|
+
const home = await makeTmpDir();
|
|
226
|
+
try {
|
|
227
|
+
const target = "/target/skill";
|
|
228
|
+
await registerDeployment(home, target, {
|
|
229
|
+
kind: "skill-dir",
|
|
230
|
+
source: "/src/skill",
|
|
231
|
+
skill: "my-skill",
|
|
232
|
+
agent: "claude-code",
|
|
233
|
+
method: "symlink",
|
|
234
|
+
});
|
|
235
|
+
const entry = await verifyDeployment(home, target, {
|
|
236
|
+
kind: "skill-dir",
|
|
237
|
+
source: "/src/skill",
|
|
238
|
+
skill: "my-skill",
|
|
239
|
+
agent: "claude-code",
|
|
240
|
+
});
|
|
241
|
+
assert.ok(entry);
|
|
242
|
+
if (entry.kind !== "skill-dir")
|
|
243
|
+
assert.fail("Expected skill-dir");
|
|
244
|
+
assert.equal(entry.source, "/src/skill");
|
|
245
|
+
assert.equal(entry.skill, "my-skill");
|
|
246
|
+
assert.equal(entry.agent, "claude-code");
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
await rm(home, { recursive: true });
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
it("returns null when kind does not match", async () => {
|
|
253
|
+
const home = await makeTmpDir();
|
|
254
|
+
try {
|
|
255
|
+
const target = "/target/skill";
|
|
256
|
+
await registerDeployment(home, target, {
|
|
257
|
+
kind: "skill-dir",
|
|
258
|
+
source: "/src/skill",
|
|
259
|
+
skill: "my-skill",
|
|
260
|
+
agent: "claude-code",
|
|
261
|
+
method: "symlink",
|
|
262
|
+
});
|
|
263
|
+
const entry = await verifyDeployment(home, target, {
|
|
264
|
+
kind: "file-write",
|
|
265
|
+
source: "/src/skill",
|
|
266
|
+
skill: "my-skill",
|
|
267
|
+
agent: "claude-code",
|
|
268
|
+
});
|
|
269
|
+
assert.equal(entry, null);
|
|
270
|
+
}
|
|
271
|
+
finally {
|
|
272
|
+
await rm(home, { recursive: true });
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
it("returns null when source does not match", async () => {
|
|
276
|
+
const home = await makeTmpDir();
|
|
277
|
+
try {
|
|
278
|
+
const target = "/target/skill";
|
|
279
|
+
await registerDeployment(home, target, {
|
|
280
|
+
kind: "skill-dir",
|
|
281
|
+
source: "/src/skill",
|
|
282
|
+
skill: "my-skill",
|
|
283
|
+
agent: "claude-code",
|
|
284
|
+
method: "symlink",
|
|
285
|
+
});
|
|
286
|
+
const entry = await verifyDeployment(home, target, {
|
|
287
|
+
kind: "skill-dir",
|
|
288
|
+
source: "/different/source",
|
|
289
|
+
skill: "my-skill",
|
|
290
|
+
agent: "claude-code",
|
|
291
|
+
});
|
|
292
|
+
assert.equal(entry, null);
|
|
293
|
+
}
|
|
294
|
+
finally {
|
|
295
|
+
await rm(home, { recursive: true });
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
it("returns null when skill does not match", async () => {
|
|
299
|
+
const home = await makeTmpDir();
|
|
300
|
+
try {
|
|
301
|
+
const target = "/target/skill";
|
|
302
|
+
await registerDeployment(home, target, {
|
|
303
|
+
kind: "skill-dir",
|
|
304
|
+
source: "/src/skill",
|
|
305
|
+
skill: "my-skill",
|
|
306
|
+
agent: "claude-code",
|
|
307
|
+
method: "symlink",
|
|
308
|
+
});
|
|
309
|
+
const entry = await verifyDeployment(home, target, {
|
|
310
|
+
kind: "skill-dir",
|
|
311
|
+
source: "/src/skill",
|
|
312
|
+
skill: "different-skill",
|
|
313
|
+
agent: "claude-code",
|
|
314
|
+
});
|
|
315
|
+
assert.equal(entry, null);
|
|
316
|
+
}
|
|
317
|
+
finally {
|
|
318
|
+
await rm(home, { recursive: true });
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
it("returns null when agent does not match", async () => {
|
|
322
|
+
const home = await makeTmpDir();
|
|
323
|
+
try {
|
|
324
|
+
const target = "/target/skill";
|
|
325
|
+
await registerDeployment(home, target, {
|
|
326
|
+
kind: "skill-dir",
|
|
327
|
+
source: "/src/skill",
|
|
328
|
+
skill: "my-skill",
|
|
329
|
+
agent: "claude-code",
|
|
330
|
+
method: "symlink",
|
|
331
|
+
});
|
|
332
|
+
const entry = await verifyDeployment(home, target, {
|
|
333
|
+
kind: "skill-dir",
|
|
334
|
+
source: "/src/skill",
|
|
335
|
+
skill: "my-skill",
|
|
336
|
+
agent: "codex",
|
|
337
|
+
});
|
|
338
|
+
assert.equal(entry, null);
|
|
339
|
+
}
|
|
340
|
+
finally {
|
|
341
|
+
await rm(home, { recursive: true });
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
it("returns null when no entry exists", async () => {
|
|
345
|
+
const home = await makeTmpDir();
|
|
346
|
+
try {
|
|
347
|
+
const entry = await verifyDeployment(home, "/nonexistent", {
|
|
348
|
+
kind: "skill-dir",
|
|
349
|
+
source: "/src/skill",
|
|
350
|
+
skill: "my-skill",
|
|
351
|
+
agent: "claude-code",
|
|
352
|
+
});
|
|
353
|
+
assert.equal(entry, null);
|
|
354
|
+
}
|
|
355
|
+
finally {
|
|
356
|
+
await rm(home, { recursive: true });
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
it("returns entry for config-patch when kind, skill, agent match (no source check)", async () => {
|
|
360
|
+
const home = await makeTmpDir();
|
|
361
|
+
try {
|
|
362
|
+
const target = "/target/config.json";
|
|
363
|
+
await registerDeployment(home, target, {
|
|
364
|
+
kind: "config-patch",
|
|
365
|
+
patch: { key: "value" },
|
|
366
|
+
undoPatch: { key: null },
|
|
367
|
+
skill: "my-skill",
|
|
368
|
+
agent: "claude-code",
|
|
369
|
+
});
|
|
370
|
+
const entry = await verifyDeployment(home, target, {
|
|
371
|
+
kind: "config-patch",
|
|
372
|
+
skill: "my-skill",
|
|
373
|
+
agent: "claude-code",
|
|
374
|
+
});
|
|
375
|
+
assert.ok(entry);
|
|
376
|
+
assert.equal(entry.kind, "config-patch");
|
|
377
|
+
assert.equal(entry.skill, "my-skill");
|
|
378
|
+
assert.equal(entry.agent, "claude-code");
|
|
379
|
+
}
|
|
380
|
+
finally {
|
|
381
|
+
await rm(home, { recursive: true });
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
it("returns null when verifying config-patch but agent mismatches", async () => {
|
|
385
|
+
const home = await makeTmpDir();
|
|
386
|
+
try {
|
|
387
|
+
const target = "/target/config.json";
|
|
388
|
+
await registerDeployment(home, target, {
|
|
389
|
+
kind: "config-patch",
|
|
390
|
+
patch: { key: "value" },
|
|
391
|
+
undoPatch: { key: null },
|
|
392
|
+
skill: "my-skill",
|
|
393
|
+
agent: "claude-code",
|
|
394
|
+
});
|
|
395
|
+
const entry = await verifyDeployment(home, target, {
|
|
396
|
+
kind: "config-patch",
|
|
397
|
+
skill: "my-skill",
|
|
398
|
+
agent: "codex",
|
|
399
|
+
});
|
|
400
|
+
assert.equal(entry, null);
|
|
401
|
+
}
|
|
402
|
+
finally {
|
|
403
|
+
await rm(home, { recursive: true });
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
it("returns null when kind in expected does not match registry entry kind", async () => {
|
|
407
|
+
const home = await makeTmpDir();
|
|
408
|
+
try {
|
|
409
|
+
const target = "/target/config.json";
|
|
410
|
+
await registerDeployment(home, target, {
|
|
411
|
+
kind: "config-patch",
|
|
412
|
+
patch: { key: "value" },
|
|
413
|
+
undoPatch: { key: null },
|
|
414
|
+
skill: "my-skill",
|
|
415
|
+
agent: "claude-code",
|
|
416
|
+
});
|
|
417
|
+
const entry = await verifyDeployment(home, target, {
|
|
418
|
+
kind: "skill-dir",
|
|
419
|
+
source: "/src/skill",
|
|
420
|
+
skill: "my-skill",
|
|
421
|
+
agent: "claude-code",
|
|
422
|
+
});
|
|
423
|
+
assert.equal(entry, null);
|
|
424
|
+
}
|
|
425
|
+
finally {
|
|
426
|
+
await rm(home, { recursive: true });
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
describe("registerDeployment - config-patch", () => {
|
|
431
|
+
it("stores patch and undoPatch in registry", async () => {
|
|
432
|
+
const home = await makeTmpDir();
|
|
433
|
+
try {
|
|
434
|
+
const target = "/target/config.json";
|
|
435
|
+
await registerDeployment(home, target, {
|
|
436
|
+
kind: "config-patch",
|
|
437
|
+
patch: { a: 99, b: "new" },
|
|
438
|
+
undoPatch: { a: 1, b: null },
|
|
439
|
+
skill: "my-skill",
|
|
440
|
+
agent: "claude-code",
|
|
441
|
+
});
|
|
442
|
+
const raw = JSON.parse(await readFile(registryPath(home), "utf-8"));
|
|
443
|
+
const entry = raw.deployments[target];
|
|
444
|
+
assert.ok(entry);
|
|
445
|
+
assert.equal(entry.kind, "config-patch");
|
|
446
|
+
assert.equal(entry.patch.a, 99);
|
|
447
|
+
assert.equal(entry.patch.b, "new");
|
|
448
|
+
assert.equal(entry.undoPatch.a, 1);
|
|
449
|
+
assert.equal(entry.undoPatch.b, null);
|
|
450
|
+
assert.equal(entry.skill, "my-skill");
|
|
451
|
+
assert.equal(entry.agent, "claude-code");
|
|
452
|
+
assert.ok(entry.deployed);
|
|
453
|
+
}
|
|
454
|
+
finally {
|
|
455
|
+
await rm(home, { recursive: true });
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
describe("registerDeployment - frontmatter-emit", () => {
|
|
460
|
+
it("stores patch-level provenance and derived surfaceId", async () => {
|
|
461
|
+
const home = await makeTmpDir();
|
|
462
|
+
try {
|
|
463
|
+
const target = "/target/rules/my-mcp.md";
|
|
464
|
+
await registerDeployment(home, target, {
|
|
465
|
+
kind: "frontmatter-emit",
|
|
466
|
+
patch: { "mcp-servers": { "my-mcp": { command: "npx" } } },
|
|
467
|
+
undoPatch: { "mcp-servers": null },
|
|
468
|
+
created: true,
|
|
469
|
+
hadFrontmatter: false,
|
|
470
|
+
skill: "my-mcp",
|
|
471
|
+
agent: "antigravity",
|
|
472
|
+
});
|
|
473
|
+
const raw = JSON.parse(await readFile(registryPath(home), "utf-8"));
|
|
474
|
+
const entry = raw.deployments[target];
|
|
475
|
+
assert.ok(entry);
|
|
476
|
+
assert.equal(entry.kind, "frontmatter-emit");
|
|
477
|
+
assert.deepEqual(entry.patch, {
|
|
478
|
+
"mcp-servers": { "my-mcp": { command: "npx" } },
|
|
479
|
+
});
|
|
480
|
+
assert.deepEqual(entry.undoPatch, { "mcp-servers": null });
|
|
481
|
+
assert.equal(entry.created, true);
|
|
482
|
+
assert.equal(entry.hadFrontmatter, false);
|
|
483
|
+
assert.equal(entry.surfaceId, "frontmatter-emit:antigravity:my-mcp");
|
|
484
|
+
}
|
|
485
|
+
finally {
|
|
486
|
+
await rm(home, { recursive: true });
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
it("removes migratedFrom entries with the same surfaceId", async () => {
|
|
490
|
+
const home = await makeTmpDir();
|
|
491
|
+
try {
|
|
492
|
+
const oldTarget = "/target/old.md";
|
|
493
|
+
const newTarget = "/target/new.md";
|
|
494
|
+
await registerDeployment(home, oldTarget, {
|
|
495
|
+
kind: "frontmatter-emit",
|
|
496
|
+
patch: { "mcp-servers": { "my-mcp": { command: "npx" } } },
|
|
497
|
+
undoPatch: { "mcp-servers": null },
|
|
498
|
+
created: true,
|
|
499
|
+
hadFrontmatter: false,
|
|
500
|
+
skill: "my-mcp",
|
|
501
|
+
agent: "antigravity",
|
|
502
|
+
});
|
|
503
|
+
await registerDeployment(home, newTarget, {
|
|
504
|
+
kind: "frontmatter-emit",
|
|
505
|
+
patch: { "mcp-servers": { "my-mcp": { command: "npx" } } },
|
|
506
|
+
undoPatch: { "mcp-servers": null },
|
|
507
|
+
created: true,
|
|
508
|
+
hadFrontmatter: false,
|
|
509
|
+
skill: "my-mcp",
|
|
510
|
+
agent: "antigravity",
|
|
511
|
+
migratedFrom: [oldTarget],
|
|
512
|
+
});
|
|
513
|
+
assert.equal(await lookupDeployment(home, oldTarget), null);
|
|
514
|
+
const newEntry = await lookupDeployment(home, newTarget);
|
|
515
|
+
assert.ok(newEntry);
|
|
516
|
+
assert.equal(newEntry?.surfaceId, "frontmatter-emit:antigravity:my-mcp");
|
|
517
|
+
}
|
|
518
|
+
finally {
|
|
519
|
+
await rm(home, { recursive: true });
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
describe("loadRegistry - backward compatibility", () => {
|
|
524
|
+
it("reads old-style skill-dir entries without discriminated union", async () => {
|
|
525
|
+
const home = await makeTmpDir();
|
|
526
|
+
try {
|
|
527
|
+
// Write a registry.json as it would have been written by the old flat schema
|
|
528
|
+
const dir = path.join(home, ".inception-engine");
|
|
529
|
+
await mkdir(dir, { recursive: true });
|
|
530
|
+
const oldEntry = {
|
|
531
|
+
kind: "skill-dir",
|
|
532
|
+
source: "/old/source",
|
|
533
|
+
skill: "legacy-skill",
|
|
534
|
+
agent: "claude-code",
|
|
535
|
+
method: "symlink",
|
|
536
|
+
deployed: "2026-01-01T00:00:00.000Z",
|
|
537
|
+
};
|
|
538
|
+
const oldRegistry = {
|
|
539
|
+
version: 1,
|
|
540
|
+
deployments: { "/old/target": oldEntry },
|
|
541
|
+
};
|
|
542
|
+
await writeFile(registryPath(home), `${JSON.stringify(oldRegistry, null, 2)}\n`, "utf-8");
|
|
543
|
+
const entry = await lookupDeployment(home, "/old/target");
|
|
544
|
+
assert.ok(entry, "old skill-dir entry should be readable");
|
|
545
|
+
assert.equal(entry.kind, "skill-dir");
|
|
546
|
+
assert.equal(entry.skill, "legacy-skill");
|
|
547
|
+
assert.equal(entry.agent, "claude-code");
|
|
548
|
+
if (entry.kind === "skill-dir") {
|
|
549
|
+
assert.equal(entry.source, "/old/source");
|
|
550
|
+
assert.equal(entry.method, "symlink");
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
finally {
|
|
554
|
+
await rm(home, { recursive: true });
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
it("reads legacy frontmatter-emit entries without patch provenance", async () => {
|
|
558
|
+
const home = await makeTmpDir();
|
|
559
|
+
try {
|
|
560
|
+
const dir = path.join(home, ".inception-engine");
|
|
561
|
+
await mkdir(dir, { recursive: true });
|
|
562
|
+
const legacyRegistry = {
|
|
563
|
+
version: 1,
|
|
564
|
+
deployments: {
|
|
565
|
+
"/old/target": {
|
|
566
|
+
kind: "frontmatter-emit",
|
|
567
|
+
skill: "legacy-mcp",
|
|
568
|
+
agent: "antigravity",
|
|
569
|
+
deployed: "2026-01-01T00:00:00.000Z",
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
};
|
|
573
|
+
await writeFile(registryPath(home), `${JSON.stringify(legacyRegistry, null, 2)}\n`, "utf-8");
|
|
574
|
+
const entry = await lookupDeployment(home, "/old/target");
|
|
575
|
+
assert.ok(entry);
|
|
576
|
+
assert.equal(entry?.kind, "frontmatter-emit");
|
|
577
|
+
if (entry?.kind === "frontmatter-emit") {
|
|
578
|
+
assert.equal(entry.skill, "legacy-mcp");
|
|
579
|
+
assert.equal(entry.agent, "antigravity");
|
|
580
|
+
assert.equal(entry.patch, undefined);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
finally {
|
|
584
|
+
await rm(home, { recursive: true });
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|