agentopology 0.9.2 → 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.
- package/README.md +17 -19
- package/dist/__tests__/e2e/action-guard.test.d.ts +13 -0
- package/dist/__tests__/e2e/action-guard.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/action-guard.test.js +299 -0
- package/dist/__tests__/e2e/action-guard.test.js.map +1 -0
- package/dist/__tests__/e2e/conformance.test.d.ts +13 -0
- package/dist/__tests__/e2e/conformance.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/conformance.test.js +109 -0
- package/dist/__tests__/e2e/conformance.test.js.map +1 -0
- package/dist/__tests__/e2e/contract-drift.test.d.ts +11 -0
- package/dist/__tests__/e2e/contract-drift.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/contract-drift.test.js +142 -0
- package/dist/__tests__/e2e/contract-drift.test.js.map +1 -0
- package/dist/__tests__/e2e/guard-check.test.d.ts +9 -0
- package/dist/__tests__/e2e/guard-check.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/guard-check.test.js +316 -0
- package/dist/__tests__/e2e/guard-check.test.js.map +1 -0
- package/dist/__tests__/e2e/guard-target.test.d.ts +11 -0
- package/dist/__tests__/e2e/guard-target.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/guard-target.test.js +198 -0
- package/dist/__tests__/e2e/guard-target.test.js.map +1 -0
- package/dist/__tests__/e2e/inventory.test.d.ts +18 -0
- package/dist/__tests__/e2e/inventory.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/inventory.test.js +187 -0
- package/dist/__tests__/e2e/inventory.test.js.map +1 -0
- package/dist/__tests__/e2e/matcher.test.d.ts +14 -0
- package/dist/__tests__/e2e/matcher.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/matcher.test.js +145 -0
- package/dist/__tests__/e2e/matcher.test.js.map +1 -0
- package/dist/__tests__/e2e/step-check.test.d.ts +9 -0
- package/dist/__tests__/e2e/step-check.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/step-check.test.js +158 -0
- package/dist/__tests__/e2e/step-check.test.js.map +1 -0
- package/dist/__tests__/e2e/strength.test.d.ts +17 -0
- package/dist/__tests__/e2e/strength.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/strength.test.js +130 -0
- package/dist/__tests__/e2e/strength.test.js.map +1 -0
- package/dist/__tests__/e2e/verified-on.test.d.ts +2 -0
- package/dist/__tests__/e2e/verified-on.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/verified-on.test.js +57 -0
- package/dist/__tests__/e2e/verified-on.test.js.map +1 -0
- package/dist/__tests__/open-findings.test.js +1 -0
- package/dist/__tests__/open-findings.test.js.map +1 -1
- package/dist/bindings/__tests__/bindings.test.js +6 -686
- package/dist/bindings/__tests__/bindings.test.js.map +1 -1
- package/dist/bindings/__tests__/defaults-agreement.test.js +0 -12
- package/dist/bindings/__tests__/defaults-agreement.test.js.map +1 -1
- package/dist/bindings/__tests__/hook-command.test.js +2 -2
- package/dist/bindings/__tests__/hook-command.test.js.map +1 -1
- package/dist/bindings/__tests__/mod-plugin.test.js +2 -2
- package/dist/bindings/__tests__/mod-plugin.test.js.map +1 -1
- package/dist/bindings/claude-code.d.ts +45 -1
- package/dist/bindings/claude-code.d.ts.map +1 -1
- package/dist/bindings/claude-code.js +181 -15
- package/dist/bindings/claude-code.js.map +1 -1
- package/dist/bindings/index.d.ts +5 -5
- package/dist/bindings/index.d.ts.map +1 -1
- package/dist/bindings/index.js +9 -16
- package/dist/bindings/index.js.map +1 -1
- package/dist/bindings/types.d.ts +6 -1
- package/dist/bindings/types.d.ts.map +1 -1
- package/dist/cli/__tests__/cli.test.js +25 -0
- package/dist/cli/__tests__/cli.test.js.map +1 -1
- package/dist/cli/index.js +222 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/conformance/claude-code.d.ts +99 -0
- package/dist/conformance/claude-code.d.ts.map +1 -0
- package/dist/conformance/claude-code.js +242 -0
- package/dist/conformance/claude-code.js.map +1 -0
- package/dist/conformance/extract.d.ts +70 -0
- package/dist/conformance/extract.d.ts.map +1 -0
- package/dist/conformance/extract.js +185 -0
- package/dist/conformance/extract.js.map +1 -0
- package/dist/docs/content.d.ts.map +1 -1
- package/dist/docs/content.js +57 -32
- package/dist/docs/content.js.map +1 -1
- package/dist/guard/check.d.ts +53 -0
- package/dist/guard/check.d.ts.map +1 -0
- package/dist/guard/check.js +366 -0
- package/dist/guard/check.js.map +1 -0
- package/dist/guard/target.d.ts +45 -0
- package/dist/guard/target.d.ts.map +1 -0
- package/dist/guard/target.js +168 -0
- package/dist/guard/target.js.map +1 -0
- package/dist/import/__tests__/import.test.js +0 -554
- package/dist/import/__tests__/import.test.js.map +1 -1
- package/dist/import/index.d.ts +0 -1
- package/dist/import/index.d.ts.map +1 -1
- package/dist/import/index.js +1 -7
- package/dist/import/index.js.map +1 -1
- package/dist/import/serializer.d.ts.map +1 -1
- package/dist/import/serializer.js +41 -0
- package/dist/import/serializer.js.map +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -4
- package/dist/index.js.map +1 -1
- package/dist/inventory/index.d.ts +65 -0
- package/dist/inventory/index.d.ts.map +1 -0
- package/dist/inventory/index.js +272 -0
- package/dist/inventory/index.js.map +1 -0
- package/dist/parser/ast.d.ts +46 -0
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/ast.js.map +1 -1
- package/dist/parser/index.d.ts +6 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +66 -4
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/known-fields.d.ts +3 -0
- package/dist/parser/known-fields.d.ts.map +1 -1
- package/dist/parser/known-fields.js +5 -3
- package/dist/parser/known-fields.js.map +1 -1
- package/dist/parser/ledger.d.ts +36 -0
- package/dist/parser/ledger.d.ts.map +1 -0
- package/dist/parser/ledger.js +72 -0
- package/dist/parser/ledger.js.map +1 -0
- package/dist/parser/run.d.ts +6 -0
- package/dist/parser/run.d.ts.map +1 -1
- package/dist/parser/run.js +10 -0
- package/dist/parser/run.js.map +1 -1
- package/dist/parser/validator.d.ts.map +1 -1
- package/dist/parser/validator.js +255 -1
- package/dist/parser/validator.js.map +1 -1
- package/dist/scaffold/manifest.d.ts.map +1 -1
- package/dist/scaffold/manifest.js +0 -8
- package/dist/scaffold/manifest.js.map +1 -1
- package/dist/scaffold/merge.d.ts.map +1 -1
- package/dist/scaffold/merge.js +1 -3
- package/dist/scaffold/merge.js.map +1 -1
- package/dist/step/check.d.ts +56 -0
- package/dist/step/check.d.ts.map +1 -0
- package/dist/step/check.js +125 -0
- package/dist/step/check.js.map +1 -0
- package/dist/sync/index.d.ts +0 -2
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +0 -109
- package/dist/sync/index.js.map +1 -1
- package/package.json +3 -1
- package/spec/claude-code/README.md +19 -0
- package/spec/claude-code/hooks-contract.2.1.295.json +143 -0
- package/spec/claude-code/hooks-contract.json +142 -0
- package/spec/grammar.md +45 -7
- package/spec/testing.md +15 -0
- package/spec/validation.md +11 -2
- package/dist/__tests__/live/zz-debug.test.d.ts +0 -2
- package/dist/__tests__/live/zz-debug.test.d.ts.map +0 -1
- package/dist/__tests__/live/zz-debug.test.js +0 -4
- package/dist/__tests__/live/zz-debug.test.js.map +0 -1
- package/dist/bindings/copilot-cli.d.ts +0 -20
- package/dist/bindings/copilot-cli.d.ts.map +0 -1
- package/dist/bindings/copilot-cli.js +0 -1516
- package/dist/bindings/copilot-cli.js.map +0 -1
- package/dist/bindings/gemini-cli.d.ts +0 -18
- package/dist/bindings/gemini-cli.d.ts.map +0 -1
- package/dist/bindings/gemini-cli.js +0 -1318
- package/dist/bindings/gemini-cli.js.map +0 -1
- package/dist/bindings/kiro.d.ts +0 -17
- package/dist/bindings/kiro.d.ts.map +0 -1
- package/dist/bindings/kiro.js +0 -1615
- package/dist/bindings/kiro.js.map +0 -1
- package/dist/bindings/openclaw.d.ts +0 -18
- package/dist/bindings/openclaw.d.ts.map +0 -1
- package/dist/bindings/openclaw.js +0 -1947
- package/dist/bindings/openclaw.js.map +0 -1
- package/dist/import/openclaw.d.ts +0 -70
- package/dist/import/openclaw.d.ts.map +0 -1
- package/dist/import/openclaw.js +0 -1060
- 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,
|
|
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
|
|
160
|
-
expect(Object.keys(bindings)).
|
|
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
|
|
2503
|
-
//
|
|
2504
|
-
|
|
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
|