@kuznai/inception-engine 0.22.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.
Files changed (43) hide show
  1. package/README.md +50 -9
  2. package/dist/src/config/agents.js +39 -0
  3. package/dist/src/core/adapters/execution-config.d.ts +14 -0
  4. package/dist/src/core/adapters/execution-config.js +60 -0
  5. package/dist/src/core/adapters/index.d.ts +4 -3
  6. package/dist/src/core/adapters/index.js +7 -5
  7. package/dist/src/core/adapters/rules.js +50 -19
  8. package/dist/src/core/capabilities.d.ts +1 -1
  9. package/dist/src/core/capabilities.js +18 -2
  10. package/dist/src/core/deploy.js +66 -2
  11. package/dist/src/core/init.js +69 -13
  12. package/dist/src/core/ownership.d.ts +1 -0
  13. package/dist/src/core/ownership.js +34 -4
  14. package/dist/src/core/preflight.js +28 -0
  15. package/dist/src/core/revert.js +86 -18
  16. package/dist/src/core/validation.d.ts +1 -1
  17. package/dist/src/core/validation.js +56 -3
  18. package/dist/src/schemas/manifest.d.ts +31 -0
  19. package/dist/src/schemas/manifest.js +39 -3
  20. package/dist/src/types.d.ts +6 -2
  21. package/dist/test/helpers/path.d.ts +2 -2
  22. package/dist/test/helpers/path.js +3 -3
  23. package/dist/test/os/posix/deploy.test.js +395 -0
  24. package/dist/test/os/posix/revert.test.js +188 -0
  25. package/dist/test/os/windows/deploy.test.d.ts +1 -0
  26. package/dist/test/os/windows/deploy.test.js +238 -0
  27. package/dist/test/unit/adapters.test.js +402 -2
  28. package/dist/test/unit/capabilities.test.js +44 -1
  29. package/dist/test/unit/cli.test.js +162 -0
  30. package/dist/test/unit/deploy.test.js +88 -647
  31. package/dist/test/unit/formatters.test.d.ts +1 -0
  32. package/dist/test/unit/formatters.test.js +74 -0
  33. package/dist/test/unit/init-fixture.test.js +85 -1
  34. package/dist/test/unit/manifest.test.js +72 -0
  35. package/dist/test/unit/ownership.test.js +61 -4
  36. package/dist/test/unit/preflight.test.js +145 -0
  37. package/dist/test/unit/resolve.test.js +67 -1
  38. package/dist/test/unit/revert.test.js +81 -182
  39. package/package.json +1 -1
  40. package/dist/test/os/windows/agentRules-integration.test.js +0 -245
  41. package/dist/test/os/windows/revert-integration.test.js +0 -72
  42. /package/dist/test/os/{windows/agentRules-integration.test.d.ts → posix/deploy.test.d.ts} +0 -0
  43. /package/dist/test/os/{windows/revert-integration.test.d.ts → posix/revert.test.d.ts} +0 -0
@@ -2,9 +2,10 @@ import assert from "node:assert/strict";
2
2
  import { realpath, rm, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { describe, it } from "node:test";
5
+ import { compileHookActions } from "../../src/core/adapters/hooks.js";
5
6
  import { compileMcpServerActions } from "../../src/core/adapters/mcp.js";
6
- import { compilePermissionsActions } from "../../src/core/adapters/permissions.js";
7
- import { compileAgentRuleActions } from "../../src/core/adapters/rules.js";
7
+ import { compilePermissionsActions, compilePermissionsReverts, } from "../../src/core/adapters/permissions.js";
8
+ import { compileAgentRuleActions, compileAgentRuleReverts, } from "../../src/core/adapters/rules.js";
8
9
  import { makeTmpDir } from "../helpers/fs.js";
9
10
  import { assertPathEndsWith, normalizeSlashes } from "../helpers/path.js";
10
11
  describe("compileMcpServerActions", () => {
@@ -354,6 +355,115 @@ describe("compileAgentRuleActions", () => {
354
355
  await rm(dir, { recursive: true });
355
356
  }
356
357
  });
358
+ it("scope copilot-repo: returns file-write action targeting {repo}/.github/copilot-instructions.md", async () => {
359
+ const dir = await makeTmpDir();
360
+ try {
361
+ await writeFile(path.join(dir, "copilot.md"), "# Copilot rules");
362
+ const repo = "/repo/myproject";
363
+ const realRoot = await realpath(dir);
364
+ const { actions, warnings } = await compileAgentRuleActions({
365
+ name: "copilot-main",
366
+ agents: ["github-copilot"],
367
+ path: "copilot.md",
368
+ scope: "copilot-repo",
369
+ }, dir, dir, realRoot, ["github-copilot"], "/home/test", repo);
370
+ assert.equal(actions.length, 1);
371
+ assert.equal(warnings.length, 0);
372
+ const action = actions[0];
373
+ assert.equal(action.kind, "file-write");
374
+ assert.equal(action.agent, "github-copilot");
375
+ assert.equal(normalizeSlashes(action.target), `${repo}/.github/copilot-instructions.md`, `expected target at {repo}/.github/copilot-instructions.md, got: ${action.target}`);
376
+ assert.equal(action.confidence, "documented");
377
+ }
378
+ finally {
379
+ await rm(dir, { recursive: true });
380
+ }
381
+ });
382
+ it("scope copilot-scoped: returns file-write action with {name} substituted in path", async () => {
383
+ const dir = await makeTmpDir();
384
+ try {
385
+ await writeFile(path.join(dir, "typescript.md"), "# TypeScript rules");
386
+ const repo = "/repo/myproject";
387
+ const realRoot = await realpath(dir);
388
+ const { actions, warnings } = await compileAgentRuleActions({
389
+ name: "typescript",
390
+ agents: ["github-copilot"],
391
+ path: "typescript.md",
392
+ scope: "copilot-scoped",
393
+ }, dir, dir, realRoot, ["github-copilot"], "/home/test", repo);
394
+ assert.equal(actions.length, 1);
395
+ assert.equal(warnings.length, 0);
396
+ const action = actions[0];
397
+ assert.equal(action.kind, "file-write");
398
+ assert.equal(action.agent, "github-copilot");
399
+ assert.equal(normalizeSlashes(action.target), `${repo}/.github/instructions/typescript.instructions.md`, `expected target at {repo}/.github/instructions/typescript.instructions.md, got: ${action.target}`);
400
+ assert.equal(action.confidence, "documented");
401
+ }
402
+ finally {
403
+ await rm(dir, { recursive: true });
404
+ }
405
+ });
406
+ it("scope copilot-repo: returns warning when no repo path provided", async () => {
407
+ const dir = await makeTmpDir();
408
+ try {
409
+ await writeFile(path.join(dir, "copilot.md"), "# Copilot rules");
410
+ const realRoot = await realpath(dir);
411
+ const { actions, warnings } = await compileAgentRuleActions({
412
+ name: "copilot-main",
413
+ agents: ["github-copilot"],
414
+ path: "copilot.md",
415
+ scope: "copilot-repo",
416
+ }, dir, dir, realRoot, ["github-copilot"], "/home/test");
417
+ assert.equal(actions.length, 0);
418
+ assert.equal(warnings.length, 1);
419
+ assert.equal(warnings[0]?.kind, "confidence");
420
+ assert.match(warnings[0]?.message ?? "", /copilot-repo/);
421
+ assert.match(warnings[0]?.message ?? "", /repository path/);
422
+ }
423
+ finally {
424
+ await rm(dir, { recursive: true });
425
+ }
426
+ });
427
+ it("scope copilot-repo: non-github-copilot agents get unsupported warning", async () => {
428
+ const dir = await makeTmpDir();
429
+ try {
430
+ await writeFile(path.join(dir, "copilot.md"), "# Copilot rules");
431
+ const realRoot = await realpath(dir);
432
+ const { actions, warnings } = await compileAgentRuleActions({
433
+ name: "copilot-main",
434
+ agents: ["claude-code"],
435
+ path: "copilot.md",
436
+ scope: "copilot-repo",
437
+ }, dir, dir, realRoot, ["claude-code"], "/home/test", "/repo/test");
438
+ assert.equal(actions.length, 0);
439
+ assert.equal(warnings.length, 1);
440
+ assert.equal(warnings[0]?.kind, "confidence");
441
+ }
442
+ finally {
443
+ await rm(dir, { recursive: true });
444
+ }
445
+ });
446
+ it("scope copilot-repo: plain markdown without frontmatter succeeds (no instructionFrontmatterRequired check)", async () => {
447
+ const dir = await makeTmpDir();
448
+ try {
449
+ // Plain markdown with no frontmatter — should NOT throw despite github-copilot
450
+ // having instructionFrontmatterRequired: true (native agentRules scopes skip that check)
451
+ await writeFile(path.join(dir, "plain.md"), "# Plain rules\n\nNo frontmatter.");
452
+ const repo = "/repo/myproject";
453
+ const realRoot = await realpath(dir);
454
+ const { actions, warnings } = await compileAgentRuleActions({
455
+ name: "plain-rules",
456
+ agents: ["github-copilot"],
457
+ path: "plain.md",
458
+ scope: "copilot-repo",
459
+ }, dir, dir, realRoot, ["github-copilot"], "/home/test", repo);
460
+ assert.equal(actions.length, 1);
461
+ assert.equal(warnings.length, 0);
462
+ }
463
+ finally {
464
+ await rm(dir, { recursive: true });
465
+ }
466
+ });
357
467
  it("throws when rules source file does not exist", async () => {
358
468
  const dir = await makeTmpDir();
359
469
  try {
@@ -469,6 +579,29 @@ describe("compileAgentRuleActions", () => {
469
579
  await rm(dir, { recursive: true });
470
580
  }
471
581
  });
582
+ it("scope repo with targetDir: injects targetDir into path for claude-code", async () => {
583
+ const dir = await makeTmpDir();
584
+ try {
585
+ const rulesFile = path.join(dir, "CLAUDE.md");
586
+ await writeFile(rulesFile, "# Rules");
587
+ const repo = "/repo/myproject";
588
+ const realRoot = await realpath(dir);
589
+ const { actions, warnings } = await compileAgentRuleActions({
590
+ name: "my-rule",
591
+ agents: ["claude-code"],
592
+ path: "CLAUDE.md",
593
+ scope: "repo",
594
+ targetDir: "apps/frontend",
595
+ }, dir, dir, realRoot, ["claude-code"], "/home/test", repo);
596
+ assert.equal(actions.length, 1);
597
+ assert.equal(warnings.length, 0);
598
+ const action = actions[0];
599
+ assert.equal(normalizeSlashes(action.target), `${repo}/apps/frontend/CLAUDE.md`, `expected target at {repo}/apps/frontend/CLAUDE.md, got: ${action.target}`);
600
+ }
601
+ finally {
602
+ await rm(dir, { recursive: true });
603
+ }
604
+ });
472
605
  it("scope repo: emits a warning and skips when repo path is not provided", async () => {
473
606
  const dir = await makeTmpDir();
474
607
  try {
@@ -552,6 +685,21 @@ describe("compileAgentRuleActions", () => {
552
685
  }
553
686
  });
554
687
  });
688
+ describe("compileAgentRuleReverts", () => {
689
+ it("injects targetDir into revert path", () => {
690
+ const home = "/home/test";
691
+ const repo = "/repo/test";
692
+ const actions = compileAgentRuleReverts({
693
+ name: "my-rule",
694
+ agents: ["claude-code"],
695
+ path: "CLAUDE.md",
696
+ scope: "repo",
697
+ targetDir: "apps/frontend",
698
+ }, ["claude-code"], home, repo);
699
+ assert.equal(actions.length, 1);
700
+ assert.equal(normalizeSlashes(actions[0]?.target ?? ""), `${repo}/apps/frontend/CLAUDE.md`);
701
+ });
702
+ });
555
703
  describe("compilePermissionsActions", () => {
556
704
  it("returns zero actions and warnings when no detected agents overlap", () => {
557
705
  const { actions, warnings } = compilePermissionsActions({
@@ -683,6 +831,149 @@ describe("compilePermissionsActions", () => {
683
831
  assert.ok(agents.includes("codex"));
684
832
  });
685
833
  });
834
+ describe("compilePermissionsReverts", () => {
835
+ it("returns config and toml revert actions for supported agents", () => {
836
+ const actions = compilePermissionsReverts({
837
+ name: "multi",
838
+ agents: ["claude-code", "codex", "opencode"],
839
+ config: {},
840
+ }, null, "/home/test");
841
+ assert.equal(actions.length, 3);
842
+ assert.deepEqual(actions.map((action) => [action.agent, action.kind]), [
843
+ ["claude-code", "config-patch"],
844
+ ["codex", "toml-patch"],
845
+ ["opencode", "config-patch"],
846
+ ]);
847
+ assertPathEndsWith(actions[0]?.target ?? "", ".claude/settings.json", `expected claude-code target under .claude/settings.json, got: ${actions[0]?.target}`);
848
+ assertPathEndsWith(actions[1]?.target ?? "", ".codex/config.toml", `expected codex target under .codex/config.toml, got: ${actions[1]?.target}`);
849
+ assertPathEndsWith(actions[2]?.target ?? "", "opencode/opencode.json", `expected opencode target under opencode/opencode.json, got: ${actions[2]?.target}`);
850
+ });
851
+ it("skips unsupported agents and respects agentFilter", () => {
852
+ const actions = compilePermissionsReverts({
853
+ name: "filtered",
854
+ agents: ["claude-code", "gemini-cli", "codex"],
855
+ config: {},
856
+ }, ["codex"], "/home/test");
857
+ assert.equal(actions.length, 1);
858
+ assert.equal(actions[0]?.agent, "codex");
859
+ assert.equal(actions[0]?.kind, "toml-patch");
860
+ });
861
+ });
862
+ describe("compileHookActions", () => {
863
+ const validClaudeHookConfig = {
864
+ hooks: {
865
+ PreToolUse: [
866
+ {
867
+ matcher: "Bash",
868
+ hooks: [{ type: "command", command: "scripts/check.sh" }],
869
+ },
870
+ ],
871
+ },
872
+ };
873
+ it("returns zero actions and warnings when no detected agents overlap", () => {
874
+ const { actions, warnings } = compileHookActions({ name: "check", agents: ["claude-code"], config: validClaudeHookConfig }, ["codex"], "/home/test");
875
+ assert.equal(actions.length, 0);
876
+ assert.equal(warnings.length, 0);
877
+ });
878
+ it("returns a config-patch action for claude-code targeting settings.json", () => {
879
+ const home = "/home/test";
880
+ const { actions, warnings } = compileHookActions({ name: "check", agents: ["claude-code"], config: validClaudeHookConfig }, ["claude-code"], home);
881
+ assert.equal(warnings.length, 0);
882
+ assert.equal(actions.length, 1);
883
+ const action = actions[0];
884
+ assert.equal(action.kind, "config-patch");
885
+ assert.ok(action.target.endsWith(".claude/settings.json".replace("/", path.sep)));
886
+ assert.equal(action.agent, "claude-code");
887
+ assert.equal(action.confidence, "documented");
888
+ });
889
+ it("accepts a hooks entry with no matcher property on the matcher object", () => {
890
+ const { actions, warnings } = compileHookActions({
891
+ name: "check",
892
+ agents: ["claude-code"],
893
+ config: {
894
+ hooks: {
895
+ Stop: [{ hooks: [{ type: "command", command: "notify.sh" }] }],
896
+ },
897
+ },
898
+ }, ["claude-code"], "/home/test");
899
+ assert.equal(warnings.length, 0);
900
+ assert.equal(actions.length, 1);
901
+ });
902
+ it("emits a warning and skips for agents without a hooks surface", () => {
903
+ const { actions, warnings } = compileHookActions({ name: "check", agents: ["gemini-cli"], config: {} }, ["gemini-cli"], "/home/test");
904
+ assert.equal(actions.length, 0);
905
+ assert.equal(warnings.length, 1);
906
+ });
907
+ it("throws on unknown top-level key in config for claude-code", () => {
908
+ assert.throws(() => compileHookActions({ name: "bad", agents: ["claude-code"], config: { bad_key: {} } }, ["claude-code"], "/home/test"), /unrecognized keys/);
909
+ });
910
+ it("throws when hooks value is not an object for claude-code", () => {
911
+ assert.throws(() => compileHookActions({ name: "bad", agents: ["claude-code"], config: { hooks: "string" } }, ["claude-code"], "/home/test"), /must define "hooks" as an object/);
912
+ });
913
+ it("throws when an event value is not an array", () => {
914
+ assert.throws(() => compileHookActions({
915
+ name: "bad",
916
+ agents: ["claude-code"],
917
+ config: { hooks: { PreToolUse: "not-array" } },
918
+ }, ["claude-code"], "/home/test"), /must be an array/);
919
+ });
920
+ it("throws when a matcher element is not an object", () => {
921
+ assert.throws(() => compileHookActions({
922
+ name: "bad",
923
+ agents: ["claude-code"],
924
+ config: { hooks: { PreToolUse: ["string"] } },
925
+ }, ["claude-code"], "/home/test"), /must be an object/);
926
+ });
927
+ it("throws when matcher.hooks is missing", () => {
928
+ assert.throws(() => compileHookActions({
929
+ name: "bad",
930
+ agents: ["claude-code"],
931
+ config: { hooks: { PreToolUse: [{ matcher: "Bash" }] } },
932
+ }, ["claude-code"], "/home/test"), /must be an array/);
933
+ });
934
+ it("throws when a hook command type is not 'command'", () => {
935
+ assert.throws(() => compileHookActions({
936
+ name: "bad",
937
+ agents: ["claude-code"],
938
+ config: {
939
+ hooks: {
940
+ PreToolUse: [
941
+ {
942
+ hooks: [{ type: "script", command: "check.sh" }],
943
+ },
944
+ ],
945
+ },
946
+ },
947
+ }, ["claude-code"], "/home/test"), /must be "command"/);
948
+ });
949
+ it("throws when a hook command is not a string", () => {
950
+ assert.throws(() => compileHookActions({
951
+ name: "bad",
952
+ agents: ["claude-code"],
953
+ config: {
954
+ hooks: {
955
+ PreToolUse: [{ hooks: [{ type: "command", command: 123 }] }],
956
+ },
957
+ },
958
+ }, ["claude-code"], "/home/test"), /must be a string/);
959
+ });
960
+ it("throws when matcher.matcher is not a string", () => {
961
+ assert.throws(() => compileHookActions({
962
+ name: "bad",
963
+ agents: ["claude-code"],
964
+ config: {
965
+ hooks: {
966
+ PreToolUse: [
967
+ {
968
+ matcher: 42,
969
+ hooks: [{ type: "command", command: "check.sh" }],
970
+ },
971
+ ],
972
+ },
973
+ },
974
+ }, ["claude-code"], "/home/test"), /must be a string when present/);
975
+ });
976
+ });
686
977
  describe("compileAgentDefinitionActions", () => {
687
978
  // Import is added inline to avoid modifying the top-level imports block
688
979
  // (the function is async so we do a dynamic import once and reuse).
@@ -752,6 +1043,68 @@ describe("compileAgentDefinitionActions", () => {
752
1043
  await rm(dir, { recursive: true });
753
1044
  }
754
1045
  });
1046
+ it("emits a warning and skips repo-scoped definitions when repo is missing", async () => {
1047
+ const compile = await getAdapter();
1048
+ const dir = await makeTmpDir();
1049
+ try {
1050
+ await writeFile(path.join(dir, "my-agent.md"), "---\nname: test-agent\ndescription: test\ntools: []\n---\n# Agent");
1051
+ const realRoot = await realpath(dir);
1052
+ const { actions, warnings } = await compile({
1053
+ name: "my-agent",
1054
+ agents: ["claude-code"],
1055
+ path: "my-agent.md",
1056
+ scope: "repo",
1057
+ }, dir, dir, realRoot, ["claude-code"], "/home/test");
1058
+ assert.equal(actions.length, 0);
1059
+ assert.equal(warnings.length, 1);
1060
+ assert.equal(warnings[0]?.kind, "confidence");
1061
+ assert.match(warnings[0]?.message ?? "", /scope "repo" requires a repository path/);
1062
+ }
1063
+ finally {
1064
+ await rm(dir, { recursive: true });
1065
+ }
1066
+ });
1067
+ it("uses the repo path as a workspace fallback for workspace-scoped definitions", async () => {
1068
+ const compile = await getAdapter();
1069
+ const dir = await makeTmpDir();
1070
+ try {
1071
+ await writeFile(path.join(dir, "my-agent.md"), "---\nname: test-agent\ndescription: test\ntools: []\n---\n# Agent");
1072
+ const realRoot = await realpath(dir);
1073
+ const { actions, warnings } = await compile({
1074
+ name: "my-agent",
1075
+ agents: ["gemini-cli"],
1076
+ path: "my-agent.md",
1077
+ scope: "workspace",
1078
+ }, dir, dir, realRoot, ["gemini-cli"], "/home/test", "/repo/test");
1079
+ assert.equal(warnings.length, 0);
1080
+ assert.equal(actions.length, 1);
1081
+ assert.equal(normalizeSlashes(actions[0]?.target ?? ""), "/repo/test/.gemini/agents/my-agent.md");
1082
+ }
1083
+ finally {
1084
+ await rm(dir, { recursive: true });
1085
+ }
1086
+ });
1087
+ it("emits a warning and skips workspace-scoped definitions when repo and workspace are missing", async () => {
1088
+ const compile = await getAdapter();
1089
+ const dir = await makeTmpDir();
1090
+ try {
1091
+ await writeFile(path.join(dir, "my-agent.md"), "---\nname: test-agent\ndescription: test\ntools: []\n---\n# Agent");
1092
+ const realRoot = await realpath(dir);
1093
+ const { actions, warnings } = await compile({
1094
+ name: "my-agent",
1095
+ agents: ["gemini-cli"],
1096
+ path: "my-agent.md",
1097
+ scope: "workspace",
1098
+ }, dir, dir, realRoot, ["gemini-cli"], "/home/test");
1099
+ assert.equal(actions.length, 0);
1100
+ assert.equal(warnings.length, 1);
1101
+ assert.equal(warnings[0]?.kind, "confidence");
1102
+ assert.match(warnings[0]?.message ?? "", /scope "workspace" requires a workspace or repository path/);
1103
+ }
1104
+ finally {
1105
+ await rm(dir, { recursive: true });
1106
+ }
1107
+ });
755
1108
  it("returns a file-write action for opencode with correct target", async () => {
756
1109
  const compile = await getAdapter();
757
1110
  const dir = await makeTmpDir();
@@ -1029,3 +1382,50 @@ describe("compileAgentDefinitionActions", () => {
1029
1382
  }
1030
1383
  });
1031
1384
  });
1385
+ describe("compileAgentDefinitionReverts", () => {
1386
+ async function getAdapter() {
1387
+ const { compileAgentDefinitionReverts } = await import("../../src/core/adapters/agent-definitions.js");
1388
+ return compileAgentDefinitionReverts;
1389
+ }
1390
+ it("returns revert actions for supported markdown and toml targets", async () => {
1391
+ const compile = await getAdapter();
1392
+ const actions = compile({
1393
+ name: "my-agent",
1394
+ agents: ["claude-code", "gemini-cli", "github-copilot"],
1395
+ path: "my-agent.md",
1396
+ scope: "repo",
1397
+ }, null, "/home/test", "/repo/test");
1398
+ assert.equal(actions.length, 3);
1399
+ assert.deepEqual(actions.map((action) => action.agent), ["claude-code", "gemini-cli", "github-copilot"]);
1400
+ assert.equal(normalizeSlashes(actions[0]?.target ?? ""), "/repo/test/.claude/agents/my-agent.md");
1401
+ assert.equal(normalizeSlashes(actions[1]?.target ?? ""), "/repo/test/.gemini/agents/my-agent.md");
1402
+ assert.equal(normalizeSlashes(actions[2]?.target ?? ""), "/repo/test/.github/copilot/agents/my-agent.md");
1403
+ });
1404
+ it("returns a global toml revert for gemini-cli", async () => {
1405
+ const compile = await getAdapter();
1406
+ const actions = compile({
1407
+ name: "my-agent",
1408
+ agents: ["gemini-cli"],
1409
+ path: "my-agent.toml",
1410
+ scope: "global",
1411
+ }, null, "/home/test");
1412
+ assert.equal(actions.length, 1);
1413
+ assert.equal(actions[0]?.agent, "gemini-cli");
1414
+ assert.equal(normalizeSlashes(actions[0]?.target ?? ""), "/home/test/.gemini/agents/my-agent.toml");
1415
+ });
1416
+ it("skips unsupported targets and missing workspace or repo requirements", async () => {
1417
+ const compile = await getAdapter();
1418
+ assert.deepEqual(compile({
1419
+ name: "my-agent",
1420
+ agents: ["codex"],
1421
+ path: "my-agent.md",
1422
+ scope: "repo",
1423
+ }, null, "/home/test", "/repo/test"), []);
1424
+ assert.deepEqual(compile({
1425
+ name: "my-agent",
1426
+ agents: ["gemini-cli"],
1427
+ path: "my-agent.md",
1428
+ scope: "workspace",
1429
+ }, null, "/home/test"), []);
1430
+ });
1431
+ });
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { describe, it } from "node:test";
3
- import { describeCapabilityConfidence, planCapabilityForDeploy, shouldInitIncludeAgent, } from "../../src/core/capabilities.js";
3
+ import { describeCapabilityConfidence, planCapabilityForDeploy, resolveCapabilitySurface, shouldInitIncludeAgent, } from "../../src/core/capabilities.js";
4
4
  describe("capabilities planner", () => {
5
5
  it("treats antigravity agentRules as deployable but not init-default", () => {
6
6
  const plan = planCapabilityForDeploy({
@@ -68,4 +68,47 @@ describe("capabilities planner", () => {
68
68
  assert.equal(confidence.confidence, "documented");
69
69
  assert.equal(confidence.message, null);
70
70
  });
71
+ it("treats antigravity agentRules as shared-via gemini-cli but directly deployable when the primary is absent", () => {
72
+ const plan = planCapabilityForDeploy({
73
+ agentId: "antigravity",
74
+ capability: "agentRules",
75
+ entryName: "gemini-compatible-rules",
76
+ targetAgentIds: ["antigravity"],
77
+ scope: "repo",
78
+ });
79
+ assert.equal(plan.outcome, "action");
80
+ const confidence = describeCapabilityConfidence("antigravity", "agentRules", "repo");
81
+ assert.match(confidence.message ?? "", /shared through "gemini-cli"/);
82
+ assert.doesNotMatch(confidence.message ?? "", /requires the primary target to deploy/);
83
+ });
84
+ it("marks gemini-cli hooks as planned and excludes them from init defaults", () => {
85
+ const surface = resolveCapabilitySurface("gemini-cli", "hooks");
86
+ assert.equal(surface.supportStatus, "planned");
87
+ assert.equal(surface.plannedSurface, "settings.json hooks field");
88
+ const plan = planCapabilityForDeploy({
89
+ agentId: "gemini-cli",
90
+ capability: "hooks",
91
+ entryName: "future-hooks",
92
+ targetAgentIds: ["gemini-cli"],
93
+ });
94
+ assert.equal(plan.outcome, "warn");
95
+ if (plan.outcome !== "warn")
96
+ return;
97
+ assert.match(plan.warning.message, /planned via settings\.json hooks field/);
98
+ assert.equal(shouldInitIncludeAgent("gemini-cli", "hooks"), false);
99
+ });
100
+ it("reports unsupported confidence for codex hooks and opencode workspace rules", () => {
101
+ const codexHooks = describeCapabilityConfidence("codex", "hooks");
102
+ assert.match(codexHooks.message ?? "", /hooks surface is unsupported/);
103
+ const workspaceRules = describeCapabilityConfidence("opencode", "agentRules", "workspace");
104
+ assert.match(workspaceRules.message ?? "", /workspace-local instruction surface distinct from repo-local AGENTS\.md/);
105
+ });
106
+ it("resolves executionConfigs support records for supported and unsupported agents", () => {
107
+ const gemini = resolveCapabilitySurface("gemini-cli", "executionConfigs");
108
+ assert.equal(gemini.supportStatus, "supported");
109
+ assert.equal(gemini.schemaLabel, "Settings execution config");
110
+ const claude = resolveCapabilitySurface("claude-code", "executionConfigs");
111
+ assert.equal(claude.supportStatus, "unsupported");
112
+ assert.equal(claude.schemaLabel, "an unsupported surface");
113
+ });
71
114
  });