agentopology 0.9.1 → 0.10.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 (178) hide show
  1. package/README.md +17 -19
  2. package/dist/__tests__/e2e/action-guard.test.d.ts +13 -0
  3. package/dist/__tests__/e2e/action-guard.test.d.ts.map +1 -0
  4. package/dist/__tests__/e2e/action-guard.test.js +299 -0
  5. package/dist/__tests__/e2e/action-guard.test.js.map +1 -0
  6. package/dist/__tests__/e2e/compile.test.js +1 -1
  7. package/dist/__tests__/e2e/compile.test.js.map +1 -1
  8. package/dist/__tests__/e2e/conformance.test.d.ts +13 -0
  9. package/dist/__tests__/e2e/conformance.test.d.ts.map +1 -0
  10. package/dist/__tests__/e2e/conformance.test.js +109 -0
  11. package/dist/__tests__/e2e/conformance.test.js.map +1 -0
  12. package/dist/__tests__/e2e/contract-drift.test.d.ts +11 -0
  13. package/dist/__tests__/e2e/contract-drift.test.d.ts.map +1 -0
  14. package/dist/__tests__/e2e/contract-drift.test.js +142 -0
  15. package/dist/__tests__/e2e/contract-drift.test.js.map +1 -0
  16. package/dist/__tests__/e2e/fail-closed.test.d.ts +11 -0
  17. package/dist/__tests__/e2e/fail-closed.test.d.ts.map +1 -0
  18. package/dist/__tests__/e2e/fail-closed.test.js +111 -0
  19. package/dist/__tests__/e2e/fail-closed.test.js.map +1 -0
  20. package/dist/__tests__/e2e/guard-check.test.d.ts +9 -0
  21. package/dist/__tests__/e2e/guard-check.test.d.ts.map +1 -0
  22. package/dist/__tests__/e2e/guard-check.test.js +316 -0
  23. package/dist/__tests__/e2e/guard-check.test.js.map +1 -0
  24. package/dist/__tests__/e2e/guard-target.test.d.ts +11 -0
  25. package/dist/__tests__/e2e/guard-target.test.d.ts.map +1 -0
  26. package/dist/__tests__/e2e/guard-target.test.js +198 -0
  27. package/dist/__tests__/e2e/guard-target.test.js.map +1 -0
  28. package/dist/__tests__/e2e/inventory.test.d.ts +18 -0
  29. package/dist/__tests__/e2e/inventory.test.d.ts.map +1 -0
  30. package/dist/__tests__/e2e/inventory.test.js +187 -0
  31. package/dist/__tests__/e2e/inventory.test.js.map +1 -0
  32. package/dist/__tests__/e2e/matcher.test.d.ts +14 -0
  33. package/dist/__tests__/e2e/matcher.test.d.ts.map +1 -0
  34. package/dist/__tests__/e2e/matcher.test.js +145 -0
  35. package/dist/__tests__/e2e/matcher.test.js.map +1 -0
  36. package/dist/__tests__/e2e/step-check.test.d.ts +9 -0
  37. package/dist/__tests__/e2e/step-check.test.d.ts.map +1 -0
  38. package/dist/__tests__/e2e/step-check.test.js +158 -0
  39. package/dist/__tests__/e2e/step-check.test.js.map +1 -0
  40. package/dist/__tests__/e2e/strength.test.d.ts +17 -0
  41. package/dist/__tests__/e2e/strength.test.d.ts.map +1 -0
  42. package/dist/__tests__/e2e/strength.test.js +130 -0
  43. package/dist/__tests__/e2e/strength.test.js.map +1 -0
  44. package/dist/__tests__/e2e/verified-on.test.d.ts +2 -0
  45. package/dist/__tests__/e2e/verified-on.test.d.ts.map +1 -0
  46. package/dist/__tests__/e2e/verified-on.test.js +57 -0
  47. package/dist/__tests__/e2e/verified-on.test.js.map +1 -0
  48. package/dist/__tests__/open-findings.test.js +1 -0
  49. package/dist/__tests__/open-findings.test.js.map +1 -1
  50. package/dist/bindings/__tests__/bindings.test.js +6 -686
  51. package/dist/bindings/__tests__/bindings.test.js.map +1 -1
  52. package/dist/bindings/__tests__/defaults-agreement.test.js +0 -12
  53. package/dist/bindings/__tests__/defaults-agreement.test.js.map +1 -1
  54. package/dist/bindings/__tests__/hook-command.test.js +2 -2
  55. package/dist/bindings/__tests__/hook-command.test.js.map +1 -1
  56. package/dist/bindings/__tests__/mod-plugin.test.js +2 -2
  57. package/dist/bindings/__tests__/mod-plugin.test.js.map +1 -1
  58. package/dist/bindings/claude-code.d.ts +45 -1
  59. package/dist/bindings/claude-code.d.ts.map +1 -1
  60. package/dist/bindings/claude-code.js +191 -19
  61. package/dist/bindings/claude-code.js.map +1 -1
  62. package/dist/bindings/index.d.ts +5 -5
  63. package/dist/bindings/index.d.ts.map +1 -1
  64. package/dist/bindings/index.js +9 -16
  65. package/dist/bindings/index.js.map +1 -1
  66. package/dist/bindings/lib/stub.d.ts +15 -1
  67. package/dist/bindings/lib/stub.d.ts.map +1 -1
  68. package/dist/bindings/lib/stub.js +27 -2
  69. package/dist/bindings/lib/stub.js.map +1 -1
  70. package/dist/bindings/types.d.ts +6 -1
  71. package/dist/bindings/types.d.ts.map +1 -1
  72. package/dist/cli/__tests__/cli.test.js +25 -0
  73. package/dist/cli/__tests__/cli.test.js.map +1 -1
  74. package/dist/cli/index.js +222 -8
  75. package/dist/cli/index.js.map +1 -1
  76. package/dist/conformance/claude-code.d.ts +99 -0
  77. package/dist/conformance/claude-code.d.ts.map +1 -0
  78. package/dist/conformance/claude-code.js +242 -0
  79. package/dist/conformance/claude-code.js.map +1 -0
  80. package/dist/conformance/extract.d.ts +70 -0
  81. package/dist/conformance/extract.d.ts.map +1 -0
  82. package/dist/conformance/extract.js +185 -0
  83. package/dist/conformance/extract.js.map +1 -0
  84. package/dist/docs/content.d.ts.map +1 -1
  85. package/dist/docs/content.js +57 -32
  86. package/dist/docs/content.js.map +1 -1
  87. package/dist/guard/check.d.ts +53 -0
  88. package/dist/guard/check.d.ts.map +1 -0
  89. package/dist/guard/check.js +366 -0
  90. package/dist/guard/check.js.map +1 -0
  91. package/dist/guard/target.d.ts +45 -0
  92. package/dist/guard/target.d.ts.map +1 -0
  93. package/dist/guard/target.js +168 -0
  94. package/dist/guard/target.js.map +1 -0
  95. package/dist/import/__tests__/import.test.js +0 -554
  96. package/dist/import/__tests__/import.test.js.map +1 -1
  97. package/dist/import/index.d.ts +0 -1
  98. package/dist/import/index.d.ts.map +1 -1
  99. package/dist/import/index.js +1 -7
  100. package/dist/import/index.js.map +1 -1
  101. package/dist/import/serializer.d.ts.map +1 -1
  102. package/dist/import/serializer.js +41 -0
  103. package/dist/import/serializer.js.map +1 -1
  104. package/dist/index.d.ts +0 -4
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +0 -4
  107. package/dist/index.js.map +1 -1
  108. package/dist/inventory/index.d.ts +65 -0
  109. package/dist/inventory/index.d.ts.map +1 -0
  110. package/dist/inventory/index.js +272 -0
  111. package/dist/inventory/index.js.map +1 -0
  112. package/dist/parser/ast.d.ts +46 -0
  113. package/dist/parser/ast.d.ts.map +1 -1
  114. package/dist/parser/ast.js.map +1 -1
  115. package/dist/parser/index.d.ts +6 -1
  116. package/dist/parser/index.d.ts.map +1 -1
  117. package/dist/parser/index.js +66 -4
  118. package/dist/parser/index.js.map +1 -1
  119. package/dist/parser/known-fields.d.ts +3 -0
  120. package/dist/parser/known-fields.d.ts.map +1 -1
  121. package/dist/parser/known-fields.js +5 -3
  122. package/dist/parser/known-fields.js.map +1 -1
  123. package/dist/parser/ledger.d.ts +36 -0
  124. package/dist/parser/ledger.d.ts.map +1 -0
  125. package/dist/parser/ledger.js +72 -0
  126. package/dist/parser/ledger.js.map +1 -0
  127. package/dist/parser/run.d.ts +6 -0
  128. package/dist/parser/run.d.ts.map +1 -1
  129. package/dist/parser/run.js +10 -0
  130. package/dist/parser/run.js.map +1 -1
  131. package/dist/parser/validator.d.ts.map +1 -1
  132. package/dist/parser/validator.js +255 -1
  133. package/dist/parser/validator.js.map +1 -1
  134. package/dist/scaffold/manifest.d.ts.map +1 -1
  135. package/dist/scaffold/manifest.js +0 -8
  136. package/dist/scaffold/manifest.js.map +1 -1
  137. package/dist/scaffold/merge.d.ts.map +1 -1
  138. package/dist/scaffold/merge.js +1 -3
  139. package/dist/scaffold/merge.js.map +1 -1
  140. package/dist/step/check.d.ts +56 -0
  141. package/dist/step/check.d.ts.map +1 -0
  142. package/dist/step/check.js +125 -0
  143. package/dist/step/check.js.map +1 -0
  144. package/dist/sync/index.d.ts +0 -2
  145. package/dist/sync/index.d.ts.map +1 -1
  146. package/dist/sync/index.js +0 -109
  147. package/dist/sync/index.js.map +1 -1
  148. package/package.json +3 -1
  149. package/spec/claude-code/README.md +19 -0
  150. package/spec/claude-code/hooks-contract.2.1.295.json +143 -0
  151. package/spec/claude-code/hooks-contract.json +142 -0
  152. package/spec/grammar.md +45 -7
  153. package/spec/testing.md +16 -0
  154. package/spec/validation.md +11 -2
  155. package/dist/__tests__/live/zz-debug.test.d.ts +0 -2
  156. package/dist/__tests__/live/zz-debug.test.d.ts.map +0 -1
  157. package/dist/__tests__/live/zz-debug.test.js +0 -4
  158. package/dist/__tests__/live/zz-debug.test.js.map +0 -1
  159. package/dist/bindings/copilot-cli.d.ts +0 -20
  160. package/dist/bindings/copilot-cli.d.ts.map +0 -1
  161. package/dist/bindings/copilot-cli.js +0 -1516
  162. package/dist/bindings/copilot-cli.js.map +0 -1
  163. package/dist/bindings/gemini-cli.d.ts +0 -18
  164. package/dist/bindings/gemini-cli.d.ts.map +0 -1
  165. package/dist/bindings/gemini-cli.js +0 -1318
  166. package/dist/bindings/gemini-cli.js.map +0 -1
  167. package/dist/bindings/kiro.d.ts +0 -17
  168. package/dist/bindings/kiro.d.ts.map +0 -1
  169. package/dist/bindings/kiro.js +0 -1615
  170. package/dist/bindings/kiro.js.map +0 -1
  171. package/dist/bindings/openclaw.d.ts +0 -18
  172. package/dist/bindings/openclaw.d.ts.map +0 -1
  173. package/dist/bindings/openclaw.js +0 -1947
  174. package/dist/bindings/openclaw.js.map +0 -1
  175. package/dist/import/openclaw.d.ts +0 -70
  176. package/dist/import/openclaw.d.ts.map +0 -1
  177. package/dist/import/openclaw.js +0 -1060
  178. package/dist/import/openclaw.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import { parse } from "../../parser/index.js";
3
- import { bindings, claudeCodeBinding, kiroBinding, cursorBinding, } from "../index.js";
3
+ import { bindings, claudeCodeBinding, cursorBinding, } from "../index.js";
4
4
  import { isStubContent, STUB_MARKER } from "../lib/stub.js";
5
5
  // ---------------------------------------------------------------------------
6
6
  // Minimal but valid .at topology exercising most features
@@ -156,8 +156,8 @@ function assertStructuralInvariants(files) {
156
156
  // Registry tests
157
157
  // ---------------------------------------------------------------------------
158
158
  describe("Binding registry", () => {
159
- it("contains all 8 bindings", () => {
160
- expect(Object.keys(bindings)).toHaveLength(8);
159
+ it("contains the 4 bindings: claude-code, and experimental codex, cursor, claude-workflow", () => {
160
+ expect(Object.keys(bindings).sort()).toEqual(["claude-code", "claude-workflow", "codex", "cursor"]);
161
161
  });
162
162
  it("all bindings have a name and description", () => {
163
163
  for (const [key, binding] of Object.entries(bindings)) {
@@ -875,675 +875,6 @@ describe("codex binding", () => {
875
875
  expect(agentsMd.content).toContain("**Model:** opus");
876
876
  });
877
877
  });
878
- // ---------------------------------------------------------------------------
879
- // gemini-cli
880
- // ---------------------------------------------------------------------------
881
- describe("gemini-cli binding", () => {
882
- const files = scaffoldBinding("gemini-cli");
883
- assertStructuralInvariants(files);
884
- it("produces .gemini/CONTEXT.md context file (never root GEMINI.md)", () => {
885
- const geminiMd = files.find((f) => f.path === ".gemini/CONTEXT.md");
886
- expect(geminiMd).toBeDefined();
887
- // Must never generate root-level GEMINI.md
888
- const rootGeminiMd = files.find((f) => f.path === "GEMINI.md");
889
- expect(rootGeminiMd).toBeUndefined();
890
- });
891
- it("produces .gemini/settings.json", () => {
892
- const settings = files.find((f) => f.path === ".gemini/settings.json");
893
- expect(settings).toBeDefined();
894
- const parsed = JSON.parse(settings.content);
895
- expect(parsed).toBeDefined();
896
- });
897
- it("produces .gemini/instructions.md", () => {
898
- const instructions = files.find((f) => f.path === ".gemini/instructions.md");
899
- expect(instructions).toBeDefined();
900
- });
901
- it("produces command files from triggers as .toml", () => {
902
- const cmdFile = files.find((f) => f.path === ".gemini/commands/run.toml");
903
- expect(cmdFile).toBeDefined();
904
- expect(cmdFile.content).toContain('description =');
905
- expect(cmdFile.content).toContain('prompt = """');
906
- });
907
- it("settings.json uses real Gemini CLI structure", () => {
908
- const settings = files.find((f) => f.path === ".gemini/settings.json");
909
- expect(settings).toBeDefined();
910
- const parsed = JSON.parse(settings.content);
911
- // general.defaultApprovalMode instead of toolApproval
912
- expect(parsed.general).toBeDefined();
913
- expect(parsed.general.defaultApprovalMode).toBeDefined();
914
- expect(["default", "auto_edit", "plan"]).toContain(parsed.general.defaultApprovalMode);
915
- // model.name only set for Gemini-compatible model names
916
- // (non-Gemini models like haiku/opus are omitted to avoid crashing the CLI)
917
- if (parsed.model) {
918
- expect(parsed.model.name).toBeDefined();
919
- }
920
- // context.fileName points to the context file
921
- expect(parsed.context).toBeDefined();
922
- expect(parsed.context.fileName).toBe(".gemini/CONTEXT.md");
923
- // No fictional keys
924
- expect(parsed).not.toHaveProperty("toolApproval");
925
- expect(parsed).not.toHaveProperty("allowedTools");
926
- expect(parsed).not.toHaveProperty("deniedTools");
927
- expect(parsed).not.toHaveProperty("thinkingConfig");
928
- expect(parsed).not.toHaveProperty("modelConfig");
929
- expect(parsed).not.toHaveProperty("sandbox");
930
- });
931
- it("includes MCP servers in settings.json (not separate file)", () => {
932
- const settings = files.find((f) => f.path === ".gemini/settings.json");
933
- const parsed = JSON.parse(settings.content);
934
- expect(parsed.mcpServers).toBeDefined();
935
- expect(parsed.mcpServers.filesystem).toBeDefined();
936
- // No separate MCP file
937
- const mcpFile = files.find((f) => f.path === ".gemini/settings/mcp.json");
938
- expect(mcpFile).toBeUndefined();
939
- });
940
- it("includes hooks in settings.json with correct event mapping", () => {
941
- const settings = files.find((f) => f.path === ".gemini/settings.json");
942
- const parsed = JSON.parse(settings.content);
943
- // The test topology has a PostToolUse hook -> should map to AfterTool
944
- expect(parsed.hooks).toBeDefined();
945
- expect(parsed.hooks.AfterTool).toBeDefined();
946
- expect(parsed.hooksConfig).toEqual({ enabled: true });
947
- });
948
- });
949
- // ---------------------------------------------------------------------------
950
- // copilot-cli
951
- // ---------------------------------------------------------------------------
952
- describe("copilot-cli binding", () => {
953
- const files = scaffoldBinding("copilot-cli");
954
- assertStructuralInvariants(files);
955
- it("produces .github/copilot-instructions.md", () => {
956
- const instructions = files.find((f) => f.path === ".github/copilot-instructions.md");
957
- expect(instructions).toBeDefined();
958
- expect(instructions.content.length).toBeGreaterThan(0);
959
- });
960
- it("produces .github/instructions/*.instructions.md for each agent", () => {
961
- const agentFiles = files.filter((f) => f.path.match(/\.github\/instructions\/.*\.instructions\.md$/));
962
- expect(agentFiles.length).toBeGreaterThanOrEqual(2);
963
- const paths = agentFiles.map((f) => f.path);
964
- expect(paths).toContain(".github/instructions/planner.instructions.md");
965
- expect(paths).toContain(".github/instructions/builder.instructions.md");
966
- });
967
- it("uses applyTo frontmatter derived from reads/writes paths", () => {
968
- const planner = files.find((f) => f.path === ".github/instructions/planner.instructions.md");
969
- expect(planner).toBeDefined();
970
- expect(planner.content).toContain("applyTo:");
971
- expect(planner.content).toContain("workspace/input.md");
972
- expect(planner.content).toContain("workspace/plan.md");
973
- });
974
- it("does NOT include fictional frontmatter keys (tools, model, sandbox)", () => {
975
- const builder = files.find((f) => f.path === ".github/instructions/builder.instructions.md");
976
- expect(builder).toBeDefined();
977
- // Frontmatter should only contain applyTo
978
- const fmMatch = builder.content.match(/---\n([\s\S]*?)\n---/);
979
- expect(fmMatch).toBeDefined();
980
- const fmContent = fmMatch[1];
981
- expect(fmContent).not.toContain("tools:");
982
- expect(fmContent).not.toContain("model:");
983
- expect(fmContent).not.toContain("sandbox:");
984
- expect(fmContent).not.toContain("name:");
985
- expect(fmContent).not.toContain("permissions:");
986
- });
987
- it("generates gate script for enforced gate", () => {
988
- const gateScript = files.find((f) => f.path === "scripts/gate-quality-check.sh");
989
- expect(gateScript).toBeDefined();
990
- expect(gateScript.content).toContain("lint.sh");
991
- });
992
- it("does NOT generate gate script for advisory gate", () => {
993
- const advisoryScript = files.find((f) => f.path === "scripts/gate-soft-review.sh");
994
- expect(advisoryScript).toBeUndefined();
995
- });
996
- });
997
- // ---------------------------------------------------------------------------
998
- // openclaw
999
- // ---------------------------------------------------------------------------
1000
- describe("openclaw binding", () => {
1001
- const files = scaffoldBinding("openclaw");
1002
- assertStructuralInvariants(files);
1003
- it("produces openclaw.json with correct schema", () => {
1004
- const config = files.find((f) => f.path === "openclaw.json");
1005
- expect(config).toBeDefined();
1006
- const parsed = JSON.parse(config.content);
1007
- expect(parsed).toBeDefined();
1008
- // agents.defaults exists with workspace and model
1009
- expect(parsed.agents.defaults).toBeDefined();
1010
- expect(parsed.agents.defaults.workspace).toBe("~/.openclaw/workspace");
1011
- expect(parsed.agents.defaults.model.primary).toContain("anthropic/claude-");
1012
- // Agent models use provider/model-id format
1013
- const agentList = parsed.agents.list;
1014
- expect(agentList.length).toBeGreaterThan(0);
1015
- for (const agent of agentList) {
1016
- expect(agent.model.primary).toMatch(/^[a-z]+\/claude-/);
1017
- }
1018
- // Gateway auth uses mode: "token" format
1019
- expect(parsed.gateway.auth.mode).toBe("token");
1020
- expect(parsed.gateway.auth.token).toBeDefined();
1021
- expect(parsed.gateway.auth).not.toHaveProperty("requireToken");
1022
- // models.providers instead of top-level providers array
1023
- expect(parsed.models).toBeDefined();
1024
- expect(parsed.models.providers).toBeDefined();
1025
- expect(parsed.models.providers.anthropic).toBeDefined();
1026
- expect(parsed).not.toHaveProperty("providers");
1027
- // Tools have flat allow/deny, no "defaults" nesting
1028
- expect(parsed.tools.allow).toBeDefined();
1029
- expect(parsed.tools.deny).toBeDefined();
1030
- expect(parsed.tools.agentToAgent.enabled).toBe(true);
1031
- expect(parsed.tools).not.toHaveProperty("defaults");
1032
- // No unknown top-level keys
1033
- expect(parsed).not.toHaveProperty("modelFallbackChain");
1034
- expect(parsed).not.toHaveProperty("sandboxDefaults");
1035
- expect(parsed).not.toHaveProperty("defaults");
1036
- });
1037
- it("produces SOUL.md", () => {
1038
- const soul = files.find((f) => f.path === "SOUL.md");
1039
- expect(soul).toBeDefined();
1040
- });
1041
- it("produces AGENTS.md with tool restrictions and topology overview", () => {
1042
- const agents = files.find((f) => f.path === "AGENTS.md");
1043
- expect(agents).toBeDefined();
1044
- expect(agents.content).toContain("planner");
1045
- // Tool restrictions in AGENTS.md
1046
- expect(agents.content).toContain("Tools allowed:");
1047
- // Topology overview merged from TEAM.md
1048
- expect(agents.content).toContain("Topology Overview");
1049
- });
1050
- it("produces TOOLS.md", () => {
1051
- const tools = files.find((f) => f.path === "TOOLS.md");
1052
- expect(tools).toBeDefined();
1053
- });
1054
- it("produces MEMORY.md", () => {
1055
- const memory = files.find((f) => f.path === "MEMORY.md");
1056
- expect(memory).toBeDefined();
1057
- });
1058
- it("produces BOOTSTRAP.md", () => {
1059
- const bootstrap = files.find((f) => f.path === "BOOTSTRAP.md");
1060
- expect(bootstrap).toBeDefined();
1061
- });
1062
- it("does NOT produce TEAM.md", () => {
1063
- const team = files.find((f) => f.path === "TEAM.md");
1064
- expect(team).toBeUndefined();
1065
- });
1066
- it("produces USER.md", () => {
1067
- const user = files.find((f) => f.path === "USER.md");
1068
- expect(user).toBeDefined();
1069
- expect(user.content).toContain("User Profile");
1070
- });
1071
- it("produces IDENTITY.md", () => {
1072
- const identity = files.find((f) => f.path === "IDENTITY.md");
1073
- expect(identity).toBeDefined();
1074
- expect(identity.content).toContain("Identity");
1075
- expect(identity.content).toContain("Name:");
1076
- });
1077
- it("produces memory/ and skills/ directories", () => {
1078
- const memoryDir = files.find((f) => f.path === "memory/.gitkeep");
1079
- const skillsDir = files.find((f) => f.path === "skills/.gitkeep");
1080
- expect(memoryDir).toBeDefined();
1081
- expect(skillsDir).toBeDefined();
1082
- });
1083
- });
1084
- // ---------------------------------------------------------------------------
1085
- // kiro (deep)
1086
- // ---------------------------------------------------------------------------
1087
- describe("kiro binding (deep)", () => {
1088
- // A richer topology that exercises: multiple agents with tools/prompts,
1089
- // hooks, triggers, mcp-servers, gates, memory, and different tool mappings.
1090
- const RICH_TOPOLOGY = `
1091
- topology deep-kiro-test : [pipeline, fan-out] {
1092
-
1093
- meta {
1094
- version: "2.0.0"
1095
- description: "Rich topology for deep Kiro binding tests"
1096
- }
1097
-
1098
- orchestrator {
1099
- model: opus
1100
- generates: "plans/run.md"
1101
- handles: [ingest]
1102
- }
1103
-
1104
- roles {
1105
- analyzer: "Analyzes incoming data and produces structured reports"
1106
- coder: "Writes and modifies source code"
1107
- reviewer: "Reviews code changes for quality"
1108
- }
1109
-
1110
- action ingest {
1111
- kind: external
1112
- source: "webhook"
1113
- description: "Ingest data from webhook"
1114
- }
1115
-
1116
- agent analyzer {
1117
- role: analyzer
1118
- model: sonnet
1119
- phase: 1
1120
- tools: [Read, Grep]
1121
- reads: ["workspace/raw.md"]
1122
- writes: ["workspace/analysis.md"]
1123
- permissions: supervised
1124
- outputs: {
1125
- quality: good | bad | unknown
1126
- }
1127
- prompt {
1128
- Carefully analyze all incoming data.
1129
- Produce a structured report with findings.
1130
- }
1131
- }
1132
-
1133
- agent coder {
1134
- role: coder
1135
- model: opus
1136
- phase: 2
1137
- tools: [Read, Write, Bash, Edit]
1138
- reads: ["workspace/analysis.md"]
1139
- writes: ["workspace/code/"]
1140
- permissions: autonomous
1141
- mcp-servers: [filesystem, github]
1142
- prompt {
1143
- Implement code changes based on the analysis report.
1144
- Follow existing patterns in the codebase.
1145
- }
1146
- hooks {
1147
- hook coder-post-write {
1148
- on: PostToolUse
1149
- matcher: Write
1150
- run: "scripts/format.sh"
1151
- type: command
1152
- }
1153
- }
1154
- }
1155
-
1156
- agent reviewer {
1157
- role: reviewer
1158
- model: haiku
1159
- phase: 3
1160
- tools: [Read, Bash]
1161
- reads: ["workspace/code/"]
1162
- writes: ["workspace/review.md"]
1163
- permissions: supervised
1164
- }
1165
-
1166
- flow {
1167
- ingest -> analyzer
1168
- analyzer -> coder [when analyzer.quality == good]
1169
- coder -> reviewer
1170
- reviewer -> coder [when reviewer.verdict == revise] [max 3]
1171
- }
1172
-
1173
- gates {
1174
- gate lint-gate {
1175
- after: coder
1176
- run: "scripts/lint-check.sh"
1177
- checks: [lint, types, format]
1178
- on-fail: halt
1179
- }
1180
- gate security-gate {
1181
- before: reviewer
1182
- run: "scripts/security-scan.sh"
1183
- checks: [cve, secrets]
1184
- on-fail: halt
1185
- }
1186
- }
1187
-
1188
- triggers {
1189
- command analyze {
1190
- pattern: "/analyze <INPUT>"
1191
- argument: INPUT
1192
- }
1193
- command deploy {
1194
- pattern: "/deploy <ENV>"
1195
- argument: ENV
1196
- }
1197
- }
1198
-
1199
- hooks {
1200
- hook global-post-write {
1201
- on: PostToolUse
1202
- matcher: Write
1203
- run: "scripts/post-write.sh"
1204
- type: command
1205
- }
1206
- hook pre-bash {
1207
- on: PreToolUse
1208
- matcher: Bash
1209
- run: "scripts/pre-bash.sh"
1210
- type: command
1211
- }
1212
- }
1213
-
1214
- mcp-servers {
1215
- filesystem {
1216
- command: "npx"
1217
- args: ["-y", "@modelcontextprotocol/server-filesystem"]
1218
- }
1219
- github {
1220
- command: "npx"
1221
- args: ["-y", "@modelcontextprotocol/server-github"]
1222
- env: {
1223
- GITHUB_TOKEN: "\${GITHUB_TOKEN}"
1224
- }
1225
- }
1226
- }
1227
-
1228
- memory {
1229
- domains: true
1230
- references: true
1231
- metrics: true
1232
- workspace {
1233
- path: "workspace/"
1234
- structure: [raw, analysis, code, review]
1235
- }
1236
- }
1237
-
1238
- settings {
1239
- allow: ["Read", "Grep", "Glob"]
1240
- deny: ["Bash"]
1241
- }
1242
- }
1243
- `;
1244
- const richAst = parse(RICH_TOPOLOGY);
1245
- const files = kiroBinding.scaffold(richAst);
1246
- // 1. Agent JSON validity
1247
- describe("agent JSON validity", () => {
1248
- const agentFiles = files.filter((f) => f.path.match(/\.kiro\/agents\/.*\.json$/));
1249
- it("produces a JSON file for each agent", () => {
1250
- expect(agentFiles.length).toBe(3);
1251
- const paths = agentFiles.map((f) => f.path);
1252
- expect(paths).toContain(".kiro/agents/analyzer.json");
1253
- expect(paths).toContain(".kiro/agents/coder.json");
1254
- expect(paths).toContain(".kiro/agents/reviewer.json");
1255
- });
1256
- it("each agent JSON has id (name), description, and instructions (prompt) fields", () => {
1257
- for (const f of agentFiles) {
1258
- const json = JSON.parse(f.content);
1259
- expect(json.name, `${f.path} should have a name field`).toBeDefined();
1260
- expect(typeof json.name).toBe("string");
1261
- expect(json.name.length).toBeGreaterThan(0);
1262
- expect(json.description, `${f.path} should have a description field`).toBeDefined();
1263
- expect(typeof json.description).toBe("string");
1264
- expect(json.description.length).toBeGreaterThan(0);
1265
- expect(json.prompt, `${f.path} should have a prompt (instructions) field`).toBeDefined();
1266
- expect(typeof json.prompt).toBe("string");
1267
- expect(json.prompt.length).toBeGreaterThan(0);
1268
- }
1269
- });
1270
- });
1271
- // 2. Steering docs have YAML frontmatter
1272
- describe("steering docs", () => {
1273
- const steeringFiles = files.filter((f) => f.path.startsWith(".kiro/steering/"));
1274
- it("produces at least 3 steering files", () => {
1275
- expect(steeringFiles.length).toBeGreaterThanOrEqual(3);
1276
- });
1277
- it("each steering file starts with YAML frontmatter (---)", () => {
1278
- for (const f of steeringFiles) {
1279
- expect(f.content.startsWith("---"), `${f.path} should start with YAML frontmatter (---)`).toBe(true);
1280
- // Should have closing frontmatter delimiter too
1281
- const secondDash = f.content.indexOf("---", 3);
1282
- expect(secondDash, `${f.path} should have closing --- in frontmatter`).toBeGreaterThan(3);
1283
- }
1284
- });
1285
- });
1286
- // 3. MCP config matches topology's mcp-servers
1287
- describe("MCP config", () => {
1288
- const mcpFile = files.find((f) => f.path === ".kiro/settings/mcp.json");
1289
- it("produces valid JSON for mcp.json", () => {
1290
- expect(mcpFile).toBeDefined();
1291
- expect(() => JSON.parse(mcpFile.content)).not.toThrow();
1292
- });
1293
- it("contains all declared MCP servers", () => {
1294
- const mcp = JSON.parse(mcpFile.content);
1295
- expect(mcp.mcpServers).toBeDefined();
1296
- expect(mcp.mcpServers.filesystem).toBeDefined();
1297
- expect(mcp.mcpServers.github).toBeDefined();
1298
- });
1299
- it("server entries have command and args fields", () => {
1300
- const mcp = JSON.parse(mcpFile.content);
1301
- for (const [name, config] of Object.entries(mcp.mcpServers)) {
1302
- expect(config.command, `MCP server "${name}" should have a command`).toBeDefined();
1303
- expect(config.args, `MCP server "${name}" should have args`).toBeDefined();
1304
- }
1305
- });
1306
- it("preserves env field from topology", () => {
1307
- const mcp = JSON.parse(mcpFile.content);
1308
- // The parser stores env as-is from the topology; verify it exists on each server
1309
- for (const [name, config] of Object.entries(mcp.mcpServers)) {
1310
- expect(config.env, `MCP server "${name}" should have an env field`).toBeDefined();
1311
- }
1312
- });
1313
- });
1314
- // 4. Hook mapping — verify hook/gate scripts are generated
1315
- describe("hook mapping", () => {
1316
- it("generates script stubs for global hooks", () => {
1317
- const postWriteScript = files.find((f) => f.path === ".kiro/scripts/post-write.sh");
1318
- expect(postWriteScript, "should generate post-write.sh for global-post-write hook").toBeDefined();
1319
- expect(postWriteScript.content).toContain("postToolUse");
1320
- const preBashScript = files.find((f) => f.path === ".kiro/scripts/pre-bash.sh");
1321
- expect(preBashScript, "should generate pre-bash.sh for pre-bash hook").toBeDefined();
1322
- expect(preBashScript.content).toContain("preToolUse");
1323
- });
1324
- it("generates script stubs for gate scripts", () => {
1325
- const lintScript = files.find((f) => f.path === ".kiro/scripts/lint-check.sh");
1326
- expect(lintScript, "should generate lint-check.sh for lint-gate").toBeDefined();
1327
- expect(lintScript.content).toContain("lint-gate");
1328
- const securityScript = files.find((f) => f.path === ".kiro/scripts/security-scan.sh");
1329
- expect(securityScript, "should generate security-scan.sh for security-gate").toBeDefined();
1330
- expect(securityScript.content).toContain("security-gate");
1331
- });
1332
- });
1333
- // 5. Flow as prose — verify AGENTS.md contains agent names and flow description
1334
- describe("flow as prose in AGENTS.md", () => {
1335
- const agentsMd = files.find((f) => f.path === "AGENTS.md");
1336
- it("AGENTS.md is generated", () => {
1337
- expect(agentsMd).toBeDefined();
1338
- });
1339
- it("contains all agent names", () => {
1340
- const content = agentsMd.content;
1341
- expect(content).toContain("analyzer");
1342
- expect(content).toContain("coder");
1343
- expect(content).toContain("reviewer");
1344
- });
1345
- it("contains a Flow section with edge descriptions", () => {
1346
- const content = agentsMd.content;
1347
- expect(content).toContain("## Flow");
1348
- expect(content).toContain("analyzer -> coder");
1349
- expect(content).toContain("coder -> reviewer");
1350
- expect(content).toContain("reviewer -> coder");
1351
- });
1352
- it("contains gate descriptions", () => {
1353
- const content = agentsMd.content;
1354
- // Gate ids are title-cased in AGENTS.md (lint-gate -> Lint Gate)
1355
- expect(content).toContain("Lint Gate");
1356
- expect(content).toContain("Security Gate");
1357
- });
1358
- it("contains trigger descriptions", () => {
1359
- const content = agentsMd.content;
1360
- expect(content).toContain("/analyze");
1361
- expect(content).toContain("/deploy");
1362
- });
1363
- });
1364
- // 6. Tool mapping — verify mapped tool names in agent instructions
1365
- describe("tool mapping", () => {
1366
- it("maps Read to 'read' in agent tools", () => {
1367
- const analyzerFile = files.find((f) => f.path === ".kiro/agents/analyzer.json");
1368
- const json = JSON.parse(analyzerFile.content);
1369
- expect(json.tools).toContain("read");
1370
- });
1371
- it("maps Write to 'write' in agent tools", () => {
1372
- const coderFile = files.find((f) => f.path === ".kiro/agents/coder.json");
1373
- const json = JSON.parse(coderFile.content);
1374
- expect(json.tools).toContain("write");
1375
- });
1376
- it("maps Bash to 'shell' in agent tools", () => {
1377
- const coderFile = files.find((f) => f.path === ".kiro/agents/coder.json");
1378
- const json = JSON.parse(coderFile.content);
1379
- expect(json.tools).toContain("shell");
1380
- });
1381
- it("maps Edit to 'write' in agent tools", () => {
1382
- const coderFile = files.find((f) => f.path === ".kiro/agents/coder.json");
1383
- const json = JSON.parse(coderFile.content);
1384
- // Edit maps to "write", so coder should have "write" (from both Write and Edit)
1385
- expect(json.tools).toContain("write");
1386
- });
1387
- it("maps Grep to lowercase 'grep' in agent tools", () => {
1388
- const analyzerFile = files.find((f) => f.path === ".kiro/agents/analyzer.json");
1389
- const json = JSON.parse(analyzerFile.content);
1390
- expect(json.tools).toContain("grep");
1391
- });
1392
- });
1393
- // 7. Prompt content — verify prompt text appears in agent JSON instructions
1394
- describe("prompt content", () => {
1395
- it("analyzer agent JSON contains its prompt text", () => {
1396
- const analyzerFile = files.find((f) => f.path === ".kiro/agents/analyzer.json");
1397
- const json = JSON.parse(analyzerFile.content);
1398
- expect(json.prompt).toContain("Carefully analyze all incoming data");
1399
- expect(json.prompt).toContain("structured report with findings");
1400
- });
1401
- it("coder agent JSON contains its prompt text", () => {
1402
- const coderFile = files.find((f) => f.path === ".kiro/agents/coder.json");
1403
- const json = JSON.parse(coderFile.content);
1404
- expect(json.prompt).toContain("Implement code changes based on the analysis report");
1405
- expect(json.prompt).toContain("Follow existing patterns in the codebase");
1406
- });
1407
- it("reviewer agent JSON has role text but no inline prompt", () => {
1408
- const reviewerFile = files.find((f) => f.path === ".kiro/agents/reviewer.json");
1409
- const json = JSON.parse(reviewerFile.content);
1410
- // reviewer has no prompt block, so only role text should be present
1411
- expect(json.prompt).toContain("Reviews code changes for quality");
1412
- expect(json.prompt).not.toContain("## Instructions");
1413
- });
1414
- });
1415
- // 8. Trigger prompts — verify .kiro/prompts/ files are generated
1416
- describe("trigger prompts", () => {
1417
- it("generates a prompt file for each trigger", () => {
1418
- const analyzePrompt = files.find((f) => f.path === ".kiro/prompts/analyze.md");
1419
- expect(analyzePrompt, "should generate analyze.md prompt").toBeDefined();
1420
- const deployPrompt = files.find((f) => f.path === ".kiro/prompts/deploy.md");
1421
- expect(deployPrompt, "should generate deploy.md prompt").toBeDefined();
1422
- });
1423
- it("prompt files contain the trigger pattern", () => {
1424
- const analyzePrompt = files.find((f) => f.path === ".kiro/prompts/analyze.md");
1425
- expect(analyzePrompt.content).toContain("/analyze <INPUT>");
1426
- const deployPrompt = files.find((f) => f.path === ".kiro/prompts/deploy.md");
1427
- expect(deployPrompt.content).toContain("/deploy <ENV>");
1428
- });
1429
- it("prompt files contain argument info", () => {
1430
- const analyzePrompt = files.find((f) => f.path === ".kiro/prompts/analyze.md");
1431
- expect(analyzePrompt.content).toContain("INPUT");
1432
- const deployPrompt = files.find((f) => f.path === ".kiro/prompts/deploy.md");
1433
- expect(deployPrompt.content).toContain("ENV");
1434
- });
1435
- it("prompt files contain agent summary table", () => {
1436
- const analyzePrompt = files.find((f) => f.path === ".kiro/prompts/analyze.md");
1437
- expect(analyzePrompt.content).toContain("analyzer");
1438
- expect(analyzePrompt.content).toContain("coder");
1439
- expect(analyzePrompt.content).toContain("reviewer");
1440
- });
1441
- });
1442
- // Additional: memory directories
1443
- describe("memory directories", () => {
1444
- it("generates .kiro/runs/.gitkeep for workspace memory", () => {
1445
- // The parser resolves the workspace sub-block from memory {}
1446
- expect(files.find((f) => f.path === ".kiro/runs/.gitkeep")).toBeDefined();
1447
- });
1448
- it("structure steering mentions workspace layout", () => {
1449
- const structureSteering = files.find((f) => f.path === ".kiro/steering/structure.md");
1450
- expect(structureSteering).toBeDefined();
1451
- // The workspace structure items appear in the steering doc
1452
- expect(structureSteering.content).toContain("Workspace");
1453
- });
1454
- });
1455
- // Additional: per-agent hooks and MCP resources
1456
- describe("per-agent hooks and MCP resources", () => {
1457
- it("coder agent JSON includes per-agent hooks as object keyed by event", () => {
1458
- const coderFile = files.find((f) => f.path === ".kiro/agents/coder.json");
1459
- const json = JSON.parse(coderFile.content);
1460
- expect(json.hooks).toBeDefined();
1461
- expect(json.hooks.postToolUse).toBeDefined();
1462
- expect(json.hooks.postToolUse.length).toBeGreaterThanOrEqual(1);
1463
- expect(json.hooks.postToolUse[0].command).toBe("scripts/format.sh");
1464
- expect(json.hooks.postToolUse[0].matcher).toBe("fs_write");
1465
- });
1466
- it("coder agent JSON includes inline mcpServers config", () => {
1467
- const coderFile = files.find((f) => f.path === ".kiro/agents/coder.json");
1468
- const json = JSON.parse(coderFile.content);
1469
- expect(json.mcpServers).toBeDefined();
1470
- expect(json.mcpServers.filesystem).toBeDefined();
1471
- expect(json.mcpServers.filesystem.command).toBe("npx");
1472
- expect(json.mcpServers.github).toBeDefined();
1473
- expect(json.mcpServers.github.command).toBe("npx");
1474
- // type field should be stripped for stdio servers
1475
- expect(json.mcpServers.filesystem.type).toBeUndefined();
1476
- expect(json.mcpServers.github.type).toBeUndefined();
1477
- });
1478
- });
1479
- });
1480
- // ---------------------------------------------------------------------------
1481
- // kiro
1482
- // ---------------------------------------------------------------------------
1483
- describe("kiro binding", () => {
1484
- const files = scaffoldBinding("kiro");
1485
- assertStructuralInvariants(files);
1486
- it("produces .kiro/agents/*.json for each agent", () => {
1487
- const agentFiles = files.filter((f) => f.path.match(/\.kiro\/agents\/.*\.json$/));
1488
- expect(agentFiles.length).toBeGreaterThanOrEqual(2);
1489
- const paths = agentFiles.map((f) => f.path);
1490
- expect(paths).toContain(".kiro/agents/planner.json");
1491
- expect(paths).toContain(".kiro/agents/builder.json");
1492
- // Verify JSON is valid
1493
- for (const f of agentFiles) {
1494
- expect(() => JSON.parse(f.content)).not.toThrow();
1495
- }
1496
- });
1497
- it("produces steering documents", () => {
1498
- const steeringFiles = files.filter((f) => f.path.startsWith(".kiro/steering/"));
1499
- expect(steeringFiles.length).toBeGreaterThanOrEqual(3);
1500
- const paths = steeringFiles.map((f) => f.path);
1501
- expect(paths).toContain(".kiro/steering/product.md");
1502
- expect(paths).toContain(".kiro/steering/tech.md");
1503
- expect(paths).toContain(".kiro/steering/structure.md");
1504
- });
1505
- it("produces .kiro/settings/mcp.json for MCP servers", () => {
1506
- const mcpFile = files.find((f) => f.path === ".kiro/settings/mcp.json");
1507
- expect(mcpFile).toBeDefined();
1508
- const mcp = JSON.parse(mcpFile.content);
1509
- expect(mcp.mcpServers.filesystem).toBeDefined();
1510
- });
1511
- it("produces AGENTS.md top-level flow narrative", () => {
1512
- const agentsMd = files.find((f) => f.path === "AGENTS.md");
1513
- expect(agentsMd).toBeDefined();
1514
- expect(agentsMd.content).toContain("Flow");
1515
- });
1516
- it("produces prompt files from triggers", () => {
1517
- const promptFile = files.find((f) => f.path === ".kiro/prompts/run.md");
1518
- expect(promptFile).toBeDefined();
1519
- });
1520
- it("produces memory directories", () => {
1521
- const runsKeep = files.find((f) => f.path === ".kiro/runs/.gitkeep");
1522
- expect(runsKeep).toBeDefined();
1523
- });
1524
- it("generates .kiro/hooks/ JSON file for enforced gate", () => {
1525
- const hookFile = files.find((f) => f.path === ".kiro/hooks/gate-quality-check.kiro.hook");
1526
- expect(hookFile).toBeDefined();
1527
- const hookJson = JSON.parse(hookFile.content);
1528
- expect(hookJson.enabled).toBe(true);
1529
- expect(hookJson.when.type).toBe("fileEdited");
1530
- expect(hookJson.then.type).toBe("runCommand");
1531
- expect(hookJson.then.command).toContain("lint.sh");
1532
- });
1533
- it("does NOT generate hook file for advisory gate", () => {
1534
- const advisoryHook = files.find((f) => f.path === ".kiro/hooks/gate-soft-review.kiro.hook");
1535
- expect(advisoryHook).toBeUndefined();
1536
- });
1537
- });
1538
- // cursor — Ground-truth validation against real-world Cursor IDE configs
1539
- //
1540
- // Reference configs sourced from GitHub:
1541
- // - maccman/ai-monorepo-scaffold (16 .mdc rules)
1542
- // - marcoemrich/mad-tdd-mob-ai-driven (TDD rules)
1543
- // - OthmanAdi/planning-with-files (hooks.json)
1544
- // - mpaiva/project-starter-template-cursor (mcp.json)
1545
- // - sanjeed5/awesome-cursor-rules-mdc (3,377 stars, 230+ rules)
1546
- // ---------------------------------------------------------------------------
1547
878
  describe("cursor binding — ground-truth validation", () => {
1548
879
  const files = scaffoldBinding("cursor");
1549
880
  assertStructuralInvariants(files);
@@ -2499,29 +1830,18 @@ topology subagent-test : [pipeline] {
2499
1830
  describe("Stub marker uniformity across bindings (#4)", () => {
2500
1831
  // The baseline TOPOLOGY_SOURCE at the top of this file has gate
2501
1832
  // `quality-check` whose `run: "scripts/lint.sh"` becomes a stub. Bindings
2502
- // that scaffold gates (5 of 7) must produce a stub for it. The two that
2503
- // don't (copilot-cli, cursor) have no gate-script generation and are
2504
- // excluded.
2505
- const stubProducers = ["claude-code", "codex", "gemini-cli", "kiro", "openclaw"];
1833
+ // that scaffold gates must produce a stub for it; cursor has no gate-script
1834
+ // generation.
1835
+ const stubProducers = ["claude-code", "codex"];
2506
1836
  for (const bindingName of stubProducers) {
2507
1837
  it(`${bindingName} emits at least one stub with the AGENTOPOLOGY_STUB marker`, () => {
2508
1838
  const files = scaffoldBinding(bindingName);
2509
1839
  const stubs = files.filter((f) => isStubContent(f.content));
2510
1840
  expect(stubs.length, `${bindingName} should emit at least one stub`).toBeGreaterThan(0);
2511
- // Every stub must contain the literal marker string (pinned by the
2512
- // STUB_MARKER constant — see lib/stub.ts).
2513
1841
  for (const s of stubs) {
2514
1842
  expect(s.content).toContain(STUB_MARKER);
2515
1843
  }
2516
1844
  });
2517
1845
  }
2518
- it("copilot-cli produces NO stubs (no shell-script scaffolds)", () => {
2519
- // Sanity check — confirms the two non-stub-producing bindings stay
2520
- // in their respective lanes. If this fails, copilot-cli probably
2521
- // grew a gate-script path and needs the same marker treatment.
2522
- const files = scaffoldBinding("copilot-cli");
2523
- const stubs = files.filter((f) => isStubContent(f.content));
2524
- expect(stubs).toEqual([]);
2525
- });
2526
1846
  });
2527
1847
  //# sourceMappingURL=bindings.test.js.map