@codemcp/ade 0.2.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 (150) hide show
  1. package/.ade/skills/adr-nygard/SKILL.md +45 -0
  2. package/.ade/skills/conventional-commits/SKILL.md +36 -0
  3. package/.ade/skills/tanstack-architecture/SKILL.md +25 -0
  4. package/.ade/skills/tanstack-code/SKILL.md +25 -0
  5. package/.ade/skills/tanstack-design/SKILL.md +24 -0
  6. package/.ade/skills/tanstack-testing/SKILL.md +24 -0
  7. package/.agentskills/skills/adr-nygard/SKILL.md +45 -0
  8. package/.agentskills/skills/commit/SKILL.md +20 -0
  9. package/.agentskills/skills/tdd/SKILL.md +10 -0
  10. package/.beads/README.md +85 -0
  11. package/.beads/config.yaml +63 -0
  12. package/.beads/interactions.jsonl +0 -0
  13. package/.beads/issues.jsonl +46 -0
  14. package/.beads/last-touched +1 -0
  15. package/.beads/metadata.json +4 -0
  16. package/.claude/settings.json +16 -0
  17. package/.claude/skills/extending-catalog/SKILL.md +41 -0
  18. package/.cursor/mcp.json +16 -0
  19. package/.cursor/rules/ade.mdc +10 -0
  20. package/.github/agents/ade.agent.md +28 -0
  21. package/.github/copilot-instructions.md +11 -0
  22. package/.github/workflows/pr.yml +38 -0
  23. package/.github/workflows/release.yml +124 -0
  24. package/.husky/post-checkout +2 -0
  25. package/.husky/post-merge +2 -0
  26. package/.husky/pre-commit +2 -0
  27. package/.husky/pre-push +8 -0
  28. package/.kiro/agents/ade.json +20 -0
  29. package/.kiro/settings/mcp.json +14 -0
  30. package/.knowledge/.prettierignore +1 -0
  31. package/.knowledge/config.yaml +9 -0
  32. package/.lintstagedrc.js +4 -0
  33. package/.mcp.json +20 -0
  34. package/.opencode/agents/ade.md +118 -0
  35. package/.prettierignore +2 -0
  36. package/.prettierrc.yaml +3 -0
  37. package/.vibe/beads-state-ade-autonomy-facet-46zodk.json +29 -0
  38. package/.vibe/beads-state-ade-fix-no-arch-selected-hvfiio.json +34 -0
  39. package/.vibe/development-plan-autonomy-facet.md +214 -0
  40. package/.vibe/development-plan-fix-no-arch-selected.md +103 -0
  41. package/.vscode/mcp.json +24 -0
  42. package/LICENSE +21 -0
  43. package/README.md +293 -0
  44. package/config.lock.yaml +118 -0
  45. package/config.yaml +10 -0
  46. package/docs/CLI-PRD.md +251 -0
  47. package/docs/CLI-design.md +646 -0
  48. package/docs/adrs/0001-tui-framework-selection.md +77 -0
  49. package/eslint.config.mjs +38 -0
  50. package/opencode.json +17 -0
  51. package/package.json +79 -0
  52. package/packages/cli/.prettierignore +1 -0
  53. package/packages/cli/dist/commands/install.js +39 -0
  54. package/packages/cli/dist/commands/setup.js +177 -0
  55. package/packages/cli/dist/index.js +43 -0
  56. package/packages/cli/dist/knowledge-installer.js +38 -0
  57. package/packages/cli/dist/version.js +1 -0
  58. package/packages/cli/eslint.config.mjs +40 -0
  59. package/packages/cli/nodemon.json +7 -0
  60. package/packages/cli/package.json +40 -0
  61. package/packages/cli/src/commands/conventions.integration.spec.ts +267 -0
  62. package/packages/cli/src/commands/install.integration.spec.ts +123 -0
  63. package/packages/cli/src/commands/install.spec.ts +169 -0
  64. package/packages/cli/src/commands/install.ts +63 -0
  65. package/packages/cli/src/commands/knowledge.integration.spec.ts +129 -0
  66. package/packages/cli/src/commands/setup.integration.spec.ts +148 -0
  67. package/packages/cli/src/commands/setup.spec.ts +442 -0
  68. package/packages/cli/src/commands/setup.ts +252 -0
  69. package/packages/cli/src/index.ts +52 -0
  70. package/packages/cli/src/knowledge-installer.spec.ts +111 -0
  71. package/packages/cli/src/knowledge-installer.ts +54 -0
  72. package/packages/cli/src/version.ts +1 -0
  73. package/packages/cli/tsconfig.build.json +8 -0
  74. package/packages/cli/tsconfig.json +10 -0
  75. package/packages/cli/tsconfig.vitest.json +7 -0
  76. package/packages/cli/vitest.config.ts +5 -0
  77. package/packages/core/.prettierignore +1 -0
  78. package/packages/core/eslint.config.mjs +40 -0
  79. package/packages/core/nodemon.json +7 -0
  80. package/packages/core/package.json +34 -0
  81. package/packages/core/src/catalog/catalog.spec.ts +570 -0
  82. package/packages/core/src/catalog/facets/architecture.ts +438 -0
  83. package/packages/core/src/catalog/facets/autonomy.ts +106 -0
  84. package/packages/core/src/catalog/facets/backpressure.ts +143 -0
  85. package/packages/core/src/catalog/facets/practices.ts +173 -0
  86. package/packages/core/src/catalog/facets/process.ts +50 -0
  87. package/packages/core/src/catalog/index.ts +93 -0
  88. package/packages/core/src/config.spec.ts +165 -0
  89. package/packages/core/src/config.ts +39 -0
  90. package/packages/core/src/index.ts +55 -0
  91. package/packages/core/src/registry.spec.ts +145 -0
  92. package/packages/core/src/registry.ts +70 -0
  93. package/packages/core/src/resolver.spec.ts +626 -0
  94. package/packages/core/src/resolver.ts +214 -0
  95. package/packages/core/src/types.ts +179 -0
  96. package/packages/core/src/writers/git-hooks.ts +9 -0
  97. package/packages/core/src/writers/instruction.spec.ts +42 -0
  98. package/packages/core/src/writers/instruction.ts +8 -0
  99. package/packages/core/src/writers/knowledge.spec.ts +26 -0
  100. package/packages/core/src/writers/knowledge.ts +15 -0
  101. package/packages/core/src/writers/permission-policy.ts +8 -0
  102. package/packages/core/src/writers/setup-note.ts +9 -0
  103. package/packages/core/src/writers/skills.spec.ts +109 -0
  104. package/packages/core/src/writers/skills.ts +9 -0
  105. package/packages/core/src/writers/workflows.spec.ts +72 -0
  106. package/packages/core/src/writers/workflows.ts +26 -0
  107. package/packages/core/tsconfig.build.json +8 -0
  108. package/packages/core/tsconfig.json +7 -0
  109. package/packages/core/tsconfig.vitest.json +7 -0
  110. package/packages/core/vitest.config.ts +5 -0
  111. package/packages/harnesses/.prettierignore +1 -0
  112. package/packages/harnesses/eslint.config.mjs +40 -0
  113. package/packages/harnesses/package.json +35 -0
  114. package/packages/harnesses/src/index.spec.ts +45 -0
  115. package/packages/harnesses/src/index.ts +47 -0
  116. package/packages/harnesses/src/permission-policy.ts +173 -0
  117. package/packages/harnesses/src/skills-installer.ts +54 -0
  118. package/packages/harnesses/src/types.ts +12 -0
  119. package/packages/harnesses/src/util.ts +221 -0
  120. package/packages/harnesses/src/writers/claude-code.spec.ts +320 -0
  121. package/packages/harnesses/src/writers/claude-code.ts +107 -0
  122. package/packages/harnesses/src/writers/cline.spec.ts +212 -0
  123. package/packages/harnesses/src/writers/cline.ts +24 -0
  124. package/packages/harnesses/src/writers/copilot.spec.ts +258 -0
  125. package/packages/harnesses/src/writers/copilot.ts +105 -0
  126. package/packages/harnesses/src/writers/cursor.spec.ts +219 -0
  127. package/packages/harnesses/src/writers/cursor.ts +95 -0
  128. package/packages/harnesses/src/writers/kiro.spec.ts +228 -0
  129. package/packages/harnesses/src/writers/kiro.ts +89 -0
  130. package/packages/harnesses/src/writers/opencode.spec.ts +258 -0
  131. package/packages/harnesses/src/writers/opencode.ts +67 -0
  132. package/packages/harnesses/src/writers/roo-code.spec.ts +197 -0
  133. package/packages/harnesses/src/writers/roo-code.ts +71 -0
  134. package/packages/harnesses/src/writers/universal.spec.ts +134 -0
  135. package/packages/harnesses/src/writers/universal.ts +84 -0
  136. package/packages/harnesses/src/writers/windsurf.spec.ts +178 -0
  137. package/packages/harnesses/src/writers/windsurf.ts +89 -0
  138. package/packages/harnesses/tsconfig.build.json +8 -0
  139. package/packages/harnesses/tsconfig.json +7 -0
  140. package/packages/harnesses/tsconfig.vitest.json +7 -0
  141. package/packages/harnesses/vitest.config.ts +5 -0
  142. package/pnpm-workspace.yaml +2 -0
  143. package/scripts/rename-packages.sh +23 -0
  144. package/skills-lock.json +20 -0
  145. package/tsconfig.base.json +25 -0
  146. package/tsconfig.build.json +7 -0
  147. package/tsconfig.json +13 -0
  148. package/turbo.json +47 -0
  149. package/vitest.config.ts +22 -0
  150. package/vitest.setup.ts +0 -0
@@ -0,0 +1,212 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdtemp, rm, readFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import type {
6
+ AutonomyProfile,
7
+ LogicalConfig,
8
+ PermissionPolicy
9
+ } from "@codemcp/ade-core";
10
+ import { clineWriter } from "./cline.js";
11
+
12
+ function autonomyPolicy(profile: AutonomyProfile): PermissionPolicy {
13
+ switch (profile) {
14
+ case "rigid":
15
+ return {
16
+ profile,
17
+ capabilities: {
18
+ read: "ask",
19
+ edit_write: "ask",
20
+ search_list: "ask",
21
+ bash_safe: "ask",
22
+ bash_unsafe: "ask",
23
+ web: "ask",
24
+ task_agent: "ask"
25
+ }
26
+ };
27
+ case "sensible-defaults":
28
+ return {
29
+ profile,
30
+ capabilities: {
31
+ read: "allow",
32
+ edit_write: "allow",
33
+ search_list: "allow",
34
+ bash_safe: "allow",
35
+ bash_unsafe: "ask",
36
+ web: "ask",
37
+ task_agent: "allow"
38
+ }
39
+ };
40
+ case "max-autonomy":
41
+ return {
42
+ profile,
43
+ capabilities: {
44
+ read: "allow",
45
+ edit_write: "allow",
46
+ search_list: "allow",
47
+ bash_safe: "allow",
48
+ bash_unsafe: "allow",
49
+ web: "ask",
50
+ task_agent: "allow"
51
+ }
52
+ };
53
+ }
54
+ }
55
+
56
+ describe("clineWriter", () => {
57
+ let dir: string;
58
+
59
+ beforeEach(async () => {
60
+ dir = await mkdtemp(join(tmpdir(), "ade-harness-cline-"));
61
+ });
62
+
63
+ afterEach(async () => {
64
+ await rm(dir, { recursive: true, force: true });
65
+ });
66
+
67
+ it("has correct metadata", () => {
68
+ expect(clineWriter.id).toBe("cline");
69
+ expect(clineWriter.label).toBe("Cline");
70
+ });
71
+
72
+ it("writes cline_mcp_settings.json with MCP servers", async () => {
73
+ const config: LogicalConfig = {
74
+ mcp_servers: [
75
+ {
76
+ ref: "workflows",
77
+ command: "npx",
78
+ args: ["-y", "@codemcp/workflows"],
79
+ env: {}
80
+ }
81
+ ],
82
+ instructions: [],
83
+ cli_actions: [],
84
+ knowledge_sources: [],
85
+ skills: [],
86
+ git_hooks: [],
87
+ setup_notes: []
88
+ };
89
+
90
+ await clineWriter.install(config, dir);
91
+
92
+ const raw = await readFile(join(dir, "cline_mcp_settings.json"), "utf-8");
93
+ const parsed = JSON.parse(raw);
94
+ expect(parsed.mcpServers["workflows"]).toEqual({
95
+ command: "npx",
96
+ args: ["-y", "@codemcp/workflows"],
97
+ alwaysAllow: ["*"]
98
+ });
99
+ });
100
+
101
+ it("forwards explicit MCP approvals unchanged from provisioning", async () => {
102
+ const config: LogicalConfig = {
103
+ mcp_servers: [
104
+ {
105
+ ref: "workflows",
106
+ command: "npx",
107
+ args: ["-y", "@codemcp/workflows"],
108
+ env: {},
109
+ allowedTools: ["whats_next", "proceed_to_phase"]
110
+ }
111
+ ],
112
+ instructions: [],
113
+ cli_actions: [],
114
+ knowledge_sources: [],
115
+ skills: [],
116
+ git_hooks: [],
117
+ setup_notes: []
118
+ };
119
+
120
+ await clineWriter.install(config, dir);
121
+
122
+ const raw = await readFile(join(dir, "cline_mcp_settings.json"), "utf-8");
123
+ const parsed = JSON.parse(raw);
124
+ expect(parsed.mcpServers["workflows"]).toEqual({
125
+ command: "npx",
126
+ args: ["-y", "@codemcp/workflows"],
127
+ alwaysAllow: ["whats_next", "proceed_to_phase"]
128
+ });
129
+ });
130
+
131
+ it("writes .clinerules with instructions", async () => {
132
+ const config: LogicalConfig = {
133
+ mcp_servers: [],
134
+ instructions: ["Follow TDD."],
135
+ cli_actions: [],
136
+ knowledge_sources: [],
137
+ skills: [],
138
+ git_hooks: [],
139
+ setup_notes: []
140
+ };
141
+
142
+ await clineWriter.install(config, dir);
143
+
144
+ const content = await readFile(join(dir, ".clinerules"), "utf-8");
145
+ expect(content).toContain("Follow TDD.");
146
+ });
147
+
148
+ it("does not invent built-in auto-approval settings for autonomy profiles", async () => {
149
+ const rigidRoot = join(dir, "rigid");
150
+ const sensibleRoot = join(dir, "sensible");
151
+ const maxRoot = join(dir, "max");
152
+
153
+ const rigidConfig: LogicalConfig = {
154
+ mcp_servers: [
155
+ {
156
+ ref: "workflows",
157
+ command: "npx",
158
+ args: ["-y", "@codemcp/workflows"],
159
+ env: {}
160
+ }
161
+ ],
162
+ instructions: ["Use approvals for risky actions."],
163
+ cli_actions: [],
164
+ knowledge_sources: [],
165
+ skills: [],
166
+ git_hooks: [],
167
+ setup_notes: [],
168
+ permission_policy: autonomyPolicy("rigid")
169
+ };
170
+
171
+ const sensibleConfig: LogicalConfig = {
172
+ ...rigidConfig,
173
+ permission_policy: autonomyPolicy("sensible-defaults")
174
+ };
175
+
176
+ const maxConfig: LogicalConfig = {
177
+ ...rigidConfig,
178
+ permission_policy: autonomyPolicy("max-autonomy")
179
+ };
180
+
181
+ await clineWriter.install(rigidConfig, rigidRoot);
182
+ await clineWriter.install(sensibleConfig, sensibleRoot);
183
+ await clineWriter.install(maxConfig, maxRoot);
184
+
185
+ const rigidSettings = JSON.parse(
186
+ await readFile(join(rigidRoot, "cline_mcp_settings.json"), "utf-8")
187
+ );
188
+ const sensibleSettings = JSON.parse(
189
+ await readFile(join(sensibleRoot, "cline_mcp_settings.json"), "utf-8")
190
+ );
191
+ const maxSettings = JSON.parse(
192
+ await readFile(join(maxRoot, "cline_mcp_settings.json"), "utf-8")
193
+ );
194
+ const maxRules = await readFile(join(maxRoot, ".clinerules"), "utf-8");
195
+
196
+ expect(rigidSettings).toEqual(sensibleSettings);
197
+ expect(sensibleSettings).toEqual(maxSettings);
198
+ expect(maxSettings).toEqual({
199
+ mcpServers: {
200
+ workflows: {
201
+ command: "npx",
202
+ args: ["-y", "@codemcp/workflows"],
203
+ alwaysAllow: ["*"]
204
+ }
205
+ }
206
+ });
207
+ expect(maxRules).toContain("Use approvals for risky actions.");
208
+ expect(maxRules).not.toContain("browser_action");
209
+ expect(maxRules).not.toContain("execute_command");
210
+ expect(maxRules).not.toContain("web");
211
+ });
212
+ });
@@ -0,0 +1,24 @@
1
+ import { join } from "node:path";
2
+ import type { LogicalConfig } from "@codemcp/ade-core";
3
+ import type { HarnessWriter } from "../types.js";
4
+ import {
5
+ writeMcpServers,
6
+ alwaysAllowEntry,
7
+ writeRulesFile,
8
+ writeGitHooks
9
+ } from "../util.js";
10
+
11
+ export const clineWriter: HarnessWriter = {
12
+ id: "cline",
13
+ label: "Cline",
14
+ description: "VS Code AI agent — cline_mcp_settings.json + .clinerules",
15
+ async install(config: LogicalConfig, projectRoot: string) {
16
+ await writeMcpServers(config.mcp_servers, {
17
+ path: join(projectRoot, "cline_mcp_settings.json"),
18
+ transform: alwaysAllowEntry
19
+ });
20
+
21
+ await writeRulesFile(config.instructions, join(projectRoot, ".clinerules"));
22
+ await writeGitHooks(config.git_hooks, projectRoot);
23
+ }
24
+ };
@@ -0,0 +1,258 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdtemp, rm, readFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import type {
6
+ AutonomyProfile,
7
+ LogicalConfig,
8
+ PermissionPolicy
9
+ } from "@codemcp/ade-core";
10
+ import { copilotWriter } from "./copilot.js";
11
+
12
+ function autonomyPolicy(profile: AutonomyProfile): PermissionPolicy {
13
+ switch (profile) {
14
+ case "rigid":
15
+ return {
16
+ profile,
17
+ capabilities: {
18
+ read: "ask",
19
+ edit_write: "ask",
20
+ search_list: "ask",
21
+ bash_safe: "ask",
22
+ bash_unsafe: "ask",
23
+ web: "ask",
24
+ task_agent: "ask"
25
+ }
26
+ };
27
+ case "sensible-defaults":
28
+ return {
29
+ profile,
30
+ capabilities: {
31
+ read: "allow",
32
+ edit_write: "allow",
33
+ search_list: "allow",
34
+ bash_safe: "allow",
35
+ bash_unsafe: "ask",
36
+ web: "ask",
37
+ task_agent: "allow"
38
+ }
39
+ };
40
+ case "max-autonomy":
41
+ return {
42
+ profile,
43
+ capabilities: {
44
+ read: "allow",
45
+ edit_write: "allow",
46
+ search_list: "allow",
47
+ bash_safe: "allow",
48
+ bash_unsafe: "allow",
49
+ web: "ask",
50
+ task_agent: "allow"
51
+ }
52
+ };
53
+ }
54
+ }
55
+
56
+ describe("copilotWriter", () => {
57
+ let dir: string;
58
+
59
+ beforeEach(async () => {
60
+ dir = await mkdtemp(join(tmpdir(), "ade-harness-copilot-"));
61
+ });
62
+
63
+ afterEach(async () => {
64
+ await rm(dir, { recursive: true, force: true });
65
+ });
66
+
67
+ it("has correct metadata", () => {
68
+ expect(copilotWriter.id).toBe("copilot");
69
+ expect(copilotWriter.label).toBe("GitHub Copilot");
70
+ });
71
+
72
+ it("writes .vscode/mcp.json with 'servers' key and type field", async () => {
73
+ const config: LogicalConfig = {
74
+ mcp_servers: [
75
+ {
76
+ ref: "workflows",
77
+ command: "npx",
78
+ args: ["-y", "@codemcp/workflows"],
79
+ env: {}
80
+ }
81
+ ],
82
+ instructions: [],
83
+ cli_actions: [],
84
+ knowledge_sources: [],
85
+ skills: [],
86
+ git_hooks: [],
87
+ setup_notes: []
88
+ };
89
+
90
+ await copilotWriter.install(config, dir);
91
+
92
+ const raw = await readFile(join(dir, ".vscode", "mcp.json"), "utf-8");
93
+ const parsed = JSON.parse(raw);
94
+ // Copilot uses "servers", not "mcpServers"
95
+ expect(parsed.servers["workflows"]).toEqual({
96
+ type: "stdio",
97
+ command: "npx",
98
+ args: ["-y", "@codemcp/workflows"]
99
+ });
100
+ });
101
+
102
+ it("does not write copilot-instructions.md (prefers agent definition)", async () => {
103
+ const config: LogicalConfig = {
104
+ mcp_servers: [],
105
+ instructions: ["Follow TDD."],
106
+ cli_actions: [],
107
+ knowledge_sources: [],
108
+ skills: [],
109
+ git_hooks: [],
110
+ setup_notes: []
111
+ };
112
+
113
+ await copilotWriter.install(config, dir);
114
+
115
+ await expect(
116
+ readFile(join(dir, ".github", "copilot-instructions.md"), "utf-8")
117
+ ).rejects.toThrow();
118
+ });
119
+
120
+ it("writes dedicated .github/agents/ade.agent.md with agent definition", async () => {
121
+ const config: LogicalConfig = {
122
+ mcp_servers: [
123
+ {
124
+ ref: "workflows",
125
+ command: "npx",
126
+ args: ["-y", "@codemcp/workflows"],
127
+ env: {}
128
+ }
129
+ ],
130
+ instructions: ["Follow TDD."],
131
+ cli_actions: [],
132
+ knowledge_sources: [],
133
+ skills: [],
134
+ git_hooks: [],
135
+ setup_notes: []
136
+ };
137
+
138
+ await copilotWriter.install(config, dir);
139
+
140
+ const content = await readFile(
141
+ join(dir, ".github", "agents", "ade.agent.md"),
142
+ "utf-8"
143
+ );
144
+ expect(content).toContain("name: ade");
145
+ expect(content).toContain("tools:");
146
+ expect(content).toContain(" - workflows/*");
147
+ expect(content).toContain("mcp-servers:");
148
+ expect(content).toContain(" workflows:");
149
+ expect(content).toContain(" type: stdio");
150
+ expect(content).toContain(' command: "npx"');
151
+ expect(content).toContain(' args: ["-y","@codemcp/workflows"]');
152
+ expect(content).toContain(' tools: ["*"]');
153
+ expect(content).toContain(" - read");
154
+ expect(content).toContain(" - edit");
155
+ expect(content).toContain(" - search");
156
+ expect(content).toContain(" - execute");
157
+ expect(content).toContain(" - agent");
158
+ expect(content).toContain(" - web");
159
+ expect(content).not.toContain("runCommands");
160
+ expect(content).not.toContain("runTasks");
161
+ expect(content).not.toContain("fetch");
162
+ expect(content).not.toContain("githubRepo");
163
+ expect(content).toContain("Follow TDD.");
164
+ });
165
+
166
+ it("derives the tools allowlist from autonomy while keeping web access approval-gated", async () => {
167
+ const rigidRoot = join(dir, "rigid");
168
+ const sensibleRoot = join(dir, "sensible");
169
+ const maxRoot = join(dir, "max");
170
+
171
+ const rigidConfig: LogicalConfig = {
172
+ mcp_servers: [
173
+ {
174
+ ref: "workflows",
175
+ command: "npx",
176
+ args: ["-y", "@codemcp/workflows"],
177
+ env: {}
178
+ }
179
+ ],
180
+ instructions: [],
181
+ cli_actions: [],
182
+ knowledge_sources: [],
183
+ skills: [],
184
+ git_hooks: [],
185
+ setup_notes: [],
186
+ permission_policy: autonomyPolicy("rigid")
187
+ };
188
+
189
+ const sensibleConfig: LogicalConfig = {
190
+ ...rigidConfig,
191
+ mcp_servers: [
192
+ {
193
+ ref: "workflows",
194
+ command: "npx",
195
+ args: ["-y", "@codemcp/workflows"],
196
+ env: {},
197
+ allowedTools: ["whats_next", "proceed_to_phase"]
198
+ }
199
+ ],
200
+ permission_policy: autonomyPolicy("sensible-defaults")
201
+ };
202
+
203
+ const maxConfig: LogicalConfig = {
204
+ ...rigidConfig,
205
+ permission_policy: autonomyPolicy("max-autonomy")
206
+ };
207
+
208
+ await copilotWriter.install(rigidConfig, rigidRoot);
209
+ await copilotWriter.install(sensibleConfig, sensibleRoot);
210
+ await copilotWriter.install(maxConfig, maxRoot);
211
+
212
+ const rigidAgent = await readFile(
213
+ join(rigidRoot, ".github", "agents", "ade.agent.md"),
214
+ "utf-8"
215
+ );
216
+ const sensibleAgent = await readFile(
217
+ join(sensibleRoot, ".github", "agents", "ade.agent.md"),
218
+ "utf-8"
219
+ );
220
+ const maxAgent = await readFile(
221
+ join(maxRoot, ".github", "agents", "ade.agent.md"),
222
+ "utf-8"
223
+ );
224
+
225
+ expect(rigidAgent).not.toContain(" - server/workflows/*");
226
+ expect(rigidAgent).toContain(" - workflows/*");
227
+ expect(rigidAgent).not.toContain(" - read");
228
+ expect(rigidAgent).not.toContain(" - edit");
229
+ expect(rigidAgent).not.toContain(" - search");
230
+ expect(rigidAgent).not.toContain(" - execute");
231
+ expect(rigidAgent).not.toContain(" - agent");
232
+ expect(rigidAgent).not.toContain(" - web");
233
+
234
+ expect(sensibleAgent).toContain(" - read");
235
+ expect(sensibleAgent).toContain(" - edit");
236
+ expect(sensibleAgent).toContain(" - search");
237
+ expect(sensibleAgent).toContain(" - agent");
238
+ expect(sensibleAgent).not.toContain(" - execute");
239
+ expect(sensibleAgent).not.toContain(" - todo");
240
+ expect(sensibleAgent).not.toContain(" - web");
241
+ expect(sensibleAgent).toContain(" - workflows/whats_next");
242
+ expect(sensibleAgent).toContain(" - workflows/proceed_to_phase");
243
+ expect(sensibleAgent).not.toContain(" - workflows/*");
244
+ expect(sensibleAgent).toContain(
245
+ ' tools: ["whats_next","proceed_to_phase"]'
246
+ );
247
+
248
+ expect(maxAgent).toContain(" - read");
249
+ expect(maxAgent).toContain(" - edit");
250
+ expect(maxAgent).toContain(" - search");
251
+ expect(maxAgent).toContain(" - execute");
252
+ expect(maxAgent).toContain(" - agent");
253
+ expect(maxAgent).toContain(" - todo");
254
+ expect(maxAgent).not.toContain(" - web");
255
+ expect(maxAgent).toContain(" - workflows/*");
256
+ expect(maxAgent).toContain("mcp-servers:");
257
+ });
258
+ });
@@ -0,0 +1,105 @@
1
+ import { join } from "node:path";
2
+ import type { LogicalConfig, McpServerEntry } from "@codemcp/ade-core";
3
+ import type { HarnessWriter } from "../types.js";
4
+ import {
5
+ writeMcpServers,
6
+ stdioEntry,
7
+ writeAgentMd,
8
+ writeGitHooks
9
+ } from "../util.js";
10
+ import {
11
+ allowsCapability,
12
+ hasPermissionPolicy,
13
+ keepsWebOnAsk
14
+ } from "../permission-policy.js";
15
+
16
+ export const copilotWriter: HarnessWriter = {
17
+ id: "copilot",
18
+ label: "GitHub Copilot",
19
+ description: "VS Code + CLI — .vscode/mcp.json + .github/agents/ade.agent.md",
20
+ async install(config: LogicalConfig, projectRoot: string) {
21
+ await writeMcpServers(config.mcp_servers, {
22
+ path: join(projectRoot, ".vscode", "mcp.json"),
23
+ key: "servers",
24
+ transform: stdioEntry
25
+ });
26
+
27
+ const tools = [
28
+ ...getBuiltInTools(config),
29
+ ...getForwardedMcpTools(config.mcp_servers)
30
+ ];
31
+
32
+ await writeAgentMd(config, {
33
+ path: join(projectRoot, ".github", "agents", "ade.agent.md"),
34
+ extraFrontmatter: [
35
+ "tools:",
36
+ ...tools.map((t) => ` - ${t}`),
37
+ ...renderCopilotAgentMcpServers(config.mcp_servers)
38
+ ]
39
+ });
40
+ await writeGitHooks(config.git_hooks, projectRoot);
41
+ }
42
+ };
43
+
44
+ function getBuiltInTools(config: LogicalConfig): string[] {
45
+ if (!hasPermissionPolicy(config)) {
46
+ return ["read", "edit", "search", "execute", "agent", "web"];
47
+ }
48
+
49
+ return [
50
+ ...(allowsCapability(config, "read") ? ["read"] : []),
51
+ ...(allowsCapability(config, "edit_write") ? ["edit"] : []),
52
+ ...(allowsCapability(config, "search_list") ? ["search"] : []),
53
+ ...(allowsCapability(config, "bash_unsafe") ? ["execute"] : []),
54
+ ...(allowsCapability(config, "task_agent") ? ["agent"] : []),
55
+ ...(allowsCapability(config, "task_agent") &&
56
+ allowsCapability(config, "bash_unsafe")
57
+ ? ["todo"]
58
+ : []),
59
+ ...(!keepsWebOnAsk(config) && allowsCapability(config, "web")
60
+ ? ["web"]
61
+ : [])
62
+ ];
63
+ }
64
+
65
+ function getForwardedMcpTools(servers: McpServerEntry[]): string[] {
66
+ return servers.flatMap((server) => {
67
+ const allowedTools = server.allowedTools ?? ["*"];
68
+ if (allowedTools.includes("*")) {
69
+ return [`${server.ref}/*`];
70
+ }
71
+
72
+ return allowedTools.map((tool) => `${server.ref}/${tool}`);
73
+ });
74
+ }
75
+
76
+ function renderCopilotAgentMcpServers(servers: McpServerEntry[]): string[] {
77
+ if (servers.length === 0) {
78
+ return [];
79
+ }
80
+
81
+ const lines = ["mcp-servers:"];
82
+
83
+ for (const server of servers) {
84
+ lines.push(` ${formatYamlKey(server.ref)}:`);
85
+ lines.push(" type: stdio");
86
+ lines.push(` command: ${JSON.stringify(server.command)}`);
87
+ lines.push(` args: ${JSON.stringify(server.args)}`);
88
+ lines.push(` tools: ${JSON.stringify(server.allowedTools ?? ["*"])}`);
89
+
90
+ if (Object.keys(server.env).length > 0) {
91
+ lines.push(" env:");
92
+ for (const [key, value] of Object.entries(server.env)) {
93
+ lines.push(` ${formatYamlKey(key)}: ${JSON.stringify(value)}`);
94
+ }
95
+ }
96
+ }
97
+
98
+ return lines;
99
+ }
100
+
101
+ function formatYamlKey(value: string): string {
102
+ return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(value)
103
+ ? value
104
+ : JSON.stringify(value);
105
+ }