@kuznai/inception-engine 0.21.0 → 0.23.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 +83 -8
- package/dist/src/config/agents.js +94 -11
- package/dist/src/core/adapters/execution-config.d.ts +14 -0
- package/dist/src/core/adapters/execution-config.js +60 -0
- package/dist/src/core/adapters/hooks.d.ts +8 -0
- package/dist/src/core/adapters/hooks.js +86 -0
- package/dist/src/core/adapters/index.d.ts +5 -3
- package/dist/src/core/adapters/index.js +9 -5
- package/dist/src/core/adapters/mcp.js +15 -1
- package/dist/src/core/adapters/rules.js +50 -19
- package/dist/src/core/capabilities.d.ts +1 -1
- package/dist/src/core/capabilities.js +33 -2
- package/dist/src/core/deploy.js +1 -1
- package/dist/src/core/init.js +87 -23
- package/dist/src/core/preflight.js +28 -0
- package/dist/src/core/revert.js +5 -1
- package/dist/src/core/validation.d.ts +1 -0
- package/dist/src/core/validation.js +57 -0
- package/dist/src/schemas/manifest.d.ts +58 -0
- package/dist/src/schemas/manifest.js +49 -3
- package/dist/src/types.d.ts +10 -2
- package/dist/test/unit/adapters.test.js +280 -3
- package/dist/test/unit/deploy.test.js +31 -3
- package/dist/test/unit/init-fixture.test.js +85 -1
- package/dist/test/unit/manifest.test.js +72 -0
- package/dist/test/unit/preflight.test.js +91 -7
- package/dist/test/unit/revert.test.js +73 -0
- package/package.json +1 -1
|
@@ -554,6 +554,78 @@ describe("loadManifest", () => {
|
|
|
554
554
|
await rm(dir, { recursive: true });
|
|
555
555
|
}
|
|
556
556
|
});
|
|
557
|
+
it("agentRules accepts targetDir with scope: 'repo'", async () => {
|
|
558
|
+
const dir = await makeTmpDir();
|
|
559
|
+
try {
|
|
560
|
+
await writeFile(path.join(dir, "inception.json"), JSON.stringify({
|
|
561
|
+
skills: [],
|
|
562
|
+
agentRules: [
|
|
563
|
+
{
|
|
564
|
+
name: "my-rule",
|
|
565
|
+
agents: ["claude-code"],
|
|
566
|
+
path: "rules/CLAUDE.md",
|
|
567
|
+
scope: "repo",
|
|
568
|
+
targetDir: "apps/frontend",
|
|
569
|
+
},
|
|
570
|
+
],
|
|
571
|
+
}));
|
|
572
|
+
const manifest = await loadManifest(dir);
|
|
573
|
+
assert.equal(manifest.agentRules[0]?.targetDir, "apps/frontend");
|
|
574
|
+
}
|
|
575
|
+
finally {
|
|
576
|
+
await rm(dir, { recursive: true });
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
it("agentRules rejects targetDir with scope: 'global'", async () => {
|
|
580
|
+
const dir = await makeTmpDir();
|
|
581
|
+
try {
|
|
582
|
+
await writeFile(path.join(dir, "inception.json"), JSON.stringify({
|
|
583
|
+
skills: [],
|
|
584
|
+
agentRules: [
|
|
585
|
+
{
|
|
586
|
+
name: "my-rule",
|
|
587
|
+
agents: ["claude-code"],
|
|
588
|
+
path: "rules/CLAUDE.md",
|
|
589
|
+
scope: "global",
|
|
590
|
+
targetDir: "apps/frontend",
|
|
591
|
+
},
|
|
592
|
+
],
|
|
593
|
+
}));
|
|
594
|
+
await assert.rejects(loadManifest(dir), (err) => {
|
|
595
|
+
assert.ok(err instanceof UserError);
|
|
596
|
+
assert.match(err.message, /targetDir is only supported for scope "repo" or "workspace"/);
|
|
597
|
+
return true;
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
finally {
|
|
601
|
+
await rm(dir, { recursive: true });
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
it("throws when agentRules targetDir is absolute", async () => {
|
|
605
|
+
const dir = await makeTmpDir();
|
|
606
|
+
try {
|
|
607
|
+
await writeFile(path.join(dir, "inception.json"), JSON.stringify({
|
|
608
|
+
skills: [],
|
|
609
|
+
agentRules: [
|
|
610
|
+
{
|
|
611
|
+
name: "my-rule",
|
|
612
|
+
agents: ["claude-code"],
|
|
613
|
+
path: "rules/CLAUDE.md",
|
|
614
|
+
scope: "repo",
|
|
615
|
+
targetDir: "/absolute/path",
|
|
616
|
+
},
|
|
617
|
+
],
|
|
618
|
+
}));
|
|
619
|
+
await assert.rejects(loadManifest(dir), (err) => {
|
|
620
|
+
assert.ok(err instanceof UserError);
|
|
621
|
+
assert.match(err.message, /targetDir must be a relative path/);
|
|
622
|
+
return true;
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
finally {
|
|
626
|
+
await rm(dir, { recursive: true });
|
|
627
|
+
}
|
|
628
|
+
});
|
|
557
629
|
it("defaults mcpServers and agentRules to [] when omitted", async () => {
|
|
558
630
|
const dir = await makeTmpDir();
|
|
559
631
|
try {
|
|
@@ -125,24 +125,22 @@ describe("runPreflight", () => {
|
|
|
125
125
|
assert.equal(implementationOnlyWarning, undefined, `expected no implementation-only warning, got: ${implementationOnlyWarning?.message}`);
|
|
126
126
|
});
|
|
127
127
|
});
|
|
128
|
-
describe("github-copilot
|
|
129
|
-
it("emits
|
|
128
|
+
describe("github-copilot devcontainer support", () => {
|
|
129
|
+
it("emits no capability warning for devcontainer MCP when Copilot is detected and devcontainer scope is targeted", async () => {
|
|
130
130
|
const manifestWithMcp = {
|
|
131
131
|
...emptyManifest,
|
|
132
132
|
mcpServers: [
|
|
133
133
|
{
|
|
134
134
|
name: "test-mcp",
|
|
135
|
-
scope: "
|
|
135
|
+
scope: "devcontainer",
|
|
136
136
|
agents: ["github-copilot"],
|
|
137
137
|
config: { command: "node", args: ["server.js"] },
|
|
138
138
|
},
|
|
139
139
|
],
|
|
140
140
|
};
|
|
141
141
|
const warnings = await runPreflight(baseOptions, manifestWithMcp, "/home/test", ["github-copilot"]);
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
assert.ok(plannedWarning, "expected a planned surface warning for Copilot devcontainer when MCP is targeted");
|
|
145
|
-
assert.match(plannedWarning.message, /planned/);
|
|
142
|
+
const capabilityWarning = warnings.find((w) => w.kind === "info" && /mcpServers/.test(w.message));
|
|
143
|
+
assert.equal(capabilityWarning, undefined, `expected no MCP capability warning for devcontainer scope, got: ${capabilityWarning?.message}`);
|
|
146
144
|
});
|
|
147
145
|
});
|
|
148
146
|
describe("instruction precedence warnings", () => {
|
|
@@ -281,6 +279,92 @@ describe("instruction precedence warnings", () => {
|
|
|
281
279
|
await rm(sourceDir, { recursive: true });
|
|
282
280
|
}
|
|
283
281
|
});
|
|
282
|
+
it("emits Copilot precedence warning when github-copilot has both shared-via (scope: repo) and native (scope: copilot-repo) entries", async () => {
|
|
283
|
+
const sourceDir = await makeTmpDir();
|
|
284
|
+
try {
|
|
285
|
+
await writeFile(path.join(sourceDir, "shared.md"), "# Shared rules");
|
|
286
|
+
await writeFile(path.join(sourceDir, "native.md"), "# Native rules");
|
|
287
|
+
const manifest = {
|
|
288
|
+
...emptyManifest,
|
|
289
|
+
agentRules: [
|
|
290
|
+
{
|
|
291
|
+
name: "shared-rules",
|
|
292
|
+
path: "shared.md",
|
|
293
|
+
agents: ["claude-code", "github-copilot"],
|
|
294
|
+
scope: "repo",
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: "native-rules",
|
|
298
|
+
path: "native.md",
|
|
299
|
+
agents: ["github-copilot"],
|
|
300
|
+
scope: "copilot-repo",
|
|
301
|
+
},
|
|
302
|
+
],
|
|
303
|
+
};
|
|
304
|
+
const warnings = await runPreflight({ ...baseOptions, directory: sourceDir }, manifest, "/home/test", ["github-copilot"]);
|
|
305
|
+
const precedenceWarnings = warnings.filter((w) => w.kind === "precedence");
|
|
306
|
+
assert.ok(precedenceWarnings.length > 0, "expected a precedence warning");
|
|
307
|
+
assert.ok(precedenceWarnings.some((w) => w.message.includes("github-copilot") &&
|
|
308
|
+
w.message.includes("CLAUDE.md-shared") &&
|
|
309
|
+
w.message.includes("native Copilot")), `expected Copilot precedence warning, got: ${JSON.stringify(precedenceWarnings)}`);
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
await rm(sourceDir, { recursive: true });
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
it("emits Copilot precedence warning when github-copilot has both global (scope: global) and copilot-scoped entries", async () => {
|
|
316
|
+
const sourceDir = await makeTmpDir();
|
|
317
|
+
try {
|
|
318
|
+
await writeFile(path.join(sourceDir, "shared.md"), "# Shared rules");
|
|
319
|
+
await writeFile(path.join(sourceDir, "scoped.md"), "# Scoped rules");
|
|
320
|
+
const manifest = {
|
|
321
|
+
...emptyManifest,
|
|
322
|
+
agentRules: [
|
|
323
|
+
{
|
|
324
|
+
name: "shared-rules",
|
|
325
|
+
path: "shared.md",
|
|
326
|
+
agents: ["claude-code", "github-copilot"],
|
|
327
|
+
scope: "global",
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: "typescript",
|
|
331
|
+
path: "scoped.md",
|
|
332
|
+
agents: ["github-copilot"],
|
|
333
|
+
scope: "copilot-scoped",
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
};
|
|
337
|
+
const warnings = await runPreflight({ ...baseOptions, directory: sourceDir }, manifest, "/home/test", ["github-copilot"]);
|
|
338
|
+
const precedenceWarnings = warnings.filter((w) => w.kind === "precedence");
|
|
339
|
+
assert.ok(precedenceWarnings.some((w) => w.message.includes("github-copilot") &&
|
|
340
|
+
w.message.includes("native Copilot")), `expected Copilot precedence warning, got: ${JSON.stringify(precedenceWarnings)}`);
|
|
341
|
+
}
|
|
342
|
+
finally {
|
|
343
|
+
await rm(sourceDir, { recursive: true });
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
it("emits no Copilot precedence warning when github-copilot only has native entries", async () => {
|
|
347
|
+
const sourceDir = await makeTmpDir();
|
|
348
|
+
try {
|
|
349
|
+
await writeFile(path.join(sourceDir, "native.md"), "# Native rules");
|
|
350
|
+
const manifest = {
|
|
351
|
+
...emptyManifest,
|
|
352
|
+
agentRules: [
|
|
353
|
+
{
|
|
354
|
+
name: "native-rules",
|
|
355
|
+
path: "native.md",
|
|
356
|
+
agents: ["github-copilot"],
|
|
357
|
+
scope: "copilot-repo",
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
};
|
|
361
|
+
const warnings = await runPreflight({ ...baseOptions, directory: sourceDir }, manifest, "/home/test", ["github-copilot"]);
|
|
362
|
+
assert.equal(warnings.filter((w) => w.kind === "precedence").length, 0);
|
|
363
|
+
}
|
|
364
|
+
finally {
|
|
365
|
+
await rm(sourceDir, { recursive: true });
|
|
366
|
+
}
|
|
367
|
+
});
|
|
284
368
|
});
|
|
285
369
|
describe("instruction budget warnings", () => {
|
|
286
370
|
it("emits budget warning for agentRules source file exceeding 50 KB", async () => {
|
|
@@ -29,6 +29,41 @@ describe("planRevert", () => {
|
|
|
29
29
|
const actions = planRevert(testManifest, ["codex"], "/home/test");
|
|
30
30
|
assert.equal(actions.length, 0);
|
|
31
31
|
});
|
|
32
|
+
it("includes hook and execution-config reverts for detected agents", () => {
|
|
33
|
+
const manifest = {
|
|
34
|
+
...testManifest,
|
|
35
|
+
hooks: [
|
|
36
|
+
{
|
|
37
|
+
name: "pre-exec-check",
|
|
38
|
+
agents: ["claude-code"],
|
|
39
|
+
config: {
|
|
40
|
+
hooks: {
|
|
41
|
+
PreToolUse: [
|
|
42
|
+
{
|
|
43
|
+
matcher: "Bash",
|
|
44
|
+
hooks: [{ type: "command", command: "scripts/check.sh" }],
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
executionConfigs: [
|
|
52
|
+
{
|
|
53
|
+
name: "gemini-safety",
|
|
54
|
+
agents: ["gemini-cli"],
|
|
55
|
+
config: { safeMode: true },
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
const actions = planRevert(manifest, ["claude-code", "gemini-cli"], "/home/test");
|
|
60
|
+
assert.ok(actions.some((action) => action.kind === "config-patch" &&
|
|
61
|
+
action.agent === "claude-code" &&
|
|
62
|
+
action.skill === "pre-exec-check"), "expected hook revert action for Claude Code");
|
|
63
|
+
assert.ok(actions.some((action) => action.kind === "config-patch" &&
|
|
64
|
+
action.agent === "gemini-cli" &&
|
|
65
|
+
action.skill === "gemini-safety"), "expected execution config revert action for Gemini CLI");
|
|
66
|
+
});
|
|
32
67
|
});
|
|
33
68
|
describe("planRevertAll", () => {
|
|
34
69
|
it("creates revert actions for all agents in manifest regardless of detection", () => {
|
|
@@ -46,6 +81,7 @@ describe("planRevertAll", () => {
|
|
|
46
81
|
agentRules: [],
|
|
47
82
|
permissions: [],
|
|
48
83
|
agentDefinitions: [],
|
|
84
|
+
hooks: [],
|
|
49
85
|
};
|
|
50
86
|
const actions = planRevertAll(multiAgentManifest, "/home/test");
|
|
51
87
|
assert.equal(actions.length, 3);
|
|
@@ -75,6 +111,7 @@ describe("planRevertAll", () => {
|
|
|
75
111
|
agentRules: [],
|
|
76
112
|
permissions: [],
|
|
77
113
|
agentDefinitions: [],
|
|
114
|
+
hooks: [],
|
|
78
115
|
};
|
|
79
116
|
const actions = planRevertAll(manifest, "/home/test");
|
|
80
117
|
assert.equal(actions.length, 5);
|
|
@@ -85,6 +122,36 @@ describe("planRevertAll", () => {
|
|
|
85
122
|
assert.match(targets.get("antigravity") ?? "", /\.gemini[\\/]antigravity[\\/]skills[\\/]test-skill$/);
|
|
86
123
|
assert.match(targets.get("opencode") ?? "", /opencode[\\/]skills[\\/]test-skill$/);
|
|
87
124
|
});
|
|
125
|
+
it("includes hook and execution-config reverts for all manifest agents", () => {
|
|
126
|
+
const manifest = {
|
|
127
|
+
...testManifest,
|
|
128
|
+
hooks: [
|
|
129
|
+
{
|
|
130
|
+
name: "pre-exec-check",
|
|
131
|
+
agents: ["claude-code"],
|
|
132
|
+
config: {
|
|
133
|
+
hooks: {
|
|
134
|
+
Stop: [{ hooks: [{ type: "command", command: "notify.sh" }] }],
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
executionConfigs: [
|
|
140
|
+
{
|
|
141
|
+
name: "gemini-safety",
|
|
142
|
+
agents: ["gemini-cli"],
|
|
143
|
+
config: { safeMode: true },
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
const actions = planRevertAll(manifest, "/home/test");
|
|
148
|
+
assert.ok(actions.some((action) => action.kind === "config-patch" &&
|
|
149
|
+
action.agent === "claude-code" &&
|
|
150
|
+
action.skill === "pre-exec-check"), "expected planRevertAll to include Claude hook revert action");
|
|
151
|
+
assert.ok(actions.some((action) => action.kind === "config-patch" &&
|
|
152
|
+
action.agent === "gemini-cli" &&
|
|
153
|
+
action.skill === "gemini-safety"), "expected planRevertAll to include Gemini execution config revert action");
|
|
154
|
+
});
|
|
88
155
|
});
|
|
89
156
|
describe("executeRevert", { skip: process.platform === "win32" }, () => {
|
|
90
157
|
it("removes a symlink registered in the deployment registry", async () => {
|
|
@@ -712,6 +779,7 @@ describe("planRevert — mcpServers and agentRules", () => {
|
|
|
712
779
|
agentRules: [],
|
|
713
780
|
permissions: [],
|
|
714
781
|
agentDefinitions: [],
|
|
782
|
+
hooks: [],
|
|
715
783
|
};
|
|
716
784
|
const actions = planRevert(manifest, ["claude-code"], home);
|
|
717
785
|
assert.equal(actions.length, 1);
|
|
@@ -735,6 +803,7 @@ describe("planRevert — mcpServers and agentRules", () => {
|
|
|
735
803
|
agentRules: [],
|
|
736
804
|
permissions: [],
|
|
737
805
|
agentDefinitions: [],
|
|
806
|
+
hooks: [],
|
|
738
807
|
};
|
|
739
808
|
const actions = planRevert(manifest, ["codex"], home);
|
|
740
809
|
assert.equal(actions.length, 0);
|
|
@@ -755,6 +824,7 @@ describe("planRevert — mcpServers and agentRules", () => {
|
|
|
755
824
|
],
|
|
756
825
|
permissions: [],
|
|
757
826
|
agentDefinitions: [],
|
|
827
|
+
hooks: [],
|
|
758
828
|
};
|
|
759
829
|
const actions = planRevert(manifest, ["claude-code"], home);
|
|
760
830
|
assert.equal(actions.length, 1);
|
|
@@ -778,6 +848,7 @@ describe("planRevert — mcpServers and agentRules", () => {
|
|
|
778
848
|
],
|
|
779
849
|
permissions: [],
|
|
780
850
|
agentDefinitions: [],
|
|
851
|
+
hooks: [],
|
|
781
852
|
};
|
|
782
853
|
const actions = planRevert(manifest, ["codex"], home);
|
|
783
854
|
assert.equal(actions.length, 0);
|
|
@@ -801,6 +872,7 @@ describe("planRevertAll — mcpServers and agentRules", () => {
|
|
|
801
872
|
agentRules: [],
|
|
802
873
|
permissions: [],
|
|
803
874
|
agentDefinitions: [],
|
|
875
|
+
hooks: [],
|
|
804
876
|
};
|
|
805
877
|
const actions = planRevertAll(manifest, home);
|
|
806
878
|
assert.equal(actions.length, 2);
|
|
@@ -824,6 +896,7 @@ describe("planRevertAll — mcpServers and agentRules", () => {
|
|
|
824
896
|
],
|
|
825
897
|
permissions: [],
|
|
826
898
|
agentDefinitions: [],
|
|
899
|
+
hooks: [],
|
|
827
900
|
};
|
|
828
901
|
const actions = planRevertAll(manifest, home);
|
|
829
902
|
assert.equal(actions.length, 2);
|