@codemcp/ade-harnesses 0.0.2
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/.prettierignore +1 -0
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-format.log +6 -0
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +20 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/LICENSE +21 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +39 -0
- package/dist/skills-installer.d.ts +13 -0
- package/dist/skills-installer.js +46 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.js +1 -0
- package/dist/util.d.ts +53 -0
- package/dist/util.js +130 -0
- package/dist/writers/claude-code.d.ts +2 -0
- package/dist/writers/claude-code.js +45 -0
- package/dist/writers/cline.d.ts +2 -0
- package/dist/writers/cline.js +15 -0
- package/dist/writers/copilot.d.ts +2 -0
- package/dist/writers/copilot.js +28 -0
- package/dist/writers/cursor.d.ts +2 -0
- package/dist/writers/cursor.js +27 -0
- package/dist/writers/kiro.d.ts +2 -0
- package/dist/writers/kiro.js +46 -0
- package/dist/writers/opencode.d.ts +2 -0
- package/dist/writers/opencode.js +45 -0
- package/dist/writers/roo-code.d.ts +2 -0
- package/dist/writers/roo-code.js +15 -0
- package/dist/writers/universal.d.ts +2 -0
- package/dist/writers/universal.js +22 -0
- package/dist/writers/windsurf.d.ts +2 -0
- package/dist/writers/windsurf.js +15 -0
- package/eslint.config.mjs +40 -0
- package/package.json +35 -0
- package/src/index.spec.ts +45 -0
- package/src/index.ts +46 -0
- package/src/skills-installer.ts +54 -0
- package/src/types.ts +12 -0
- package/src/util.ts +208 -0
- package/src/writers/claude-code.spec.ts +162 -0
- package/src/writers/claude-code.ts +64 -0
- package/src/writers/cline.spec.ts +69 -0
- package/src/writers/cline.ts +24 -0
- package/src/writers/copilot.spec.ts +102 -0
- package/src/writers/copilot.ts +38 -0
- package/src/writers/cursor.spec.ts +116 -0
- package/src/writers/cursor.ts +33 -0
- package/src/writers/kiro.ts +52 -0
- package/src/writers/opencode.ts +54 -0
- package/src/writers/roo-code.spec.ts +69 -0
- package/src/writers/roo-code.ts +24 -0
- package/src/writers/universal.ts +31 -0
- package/src/writers/windsurf.spec.ts +70 -0
- package/src/writers/windsurf.ts +27 -0
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +7 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsconfig.vitest.json +7 -0
- package/vitest.config.ts +5 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import type { LogicalConfig } from "@codemcp/ade-core";
|
|
3
|
+
import type { HarnessWriter } from "../types.js";
|
|
4
|
+
import {
|
|
5
|
+
readJsonOrEmpty,
|
|
6
|
+
writeJson,
|
|
7
|
+
writeMcpServers,
|
|
8
|
+
writeAgentMd,
|
|
9
|
+
writeInlineSkills,
|
|
10
|
+
writeGitHooks
|
|
11
|
+
} from "../util.js";
|
|
12
|
+
|
|
13
|
+
export const claudeCodeWriter: HarnessWriter = {
|
|
14
|
+
id: "claude-code",
|
|
15
|
+
label: "Claude Code",
|
|
16
|
+
description:
|
|
17
|
+
"Anthropic's CLI agent — .claude/agents/ade.md + .mcp.json + .claude/settings.json",
|
|
18
|
+
async install(config: LogicalConfig, projectRoot: string) {
|
|
19
|
+
await writeAgentMd(config, {
|
|
20
|
+
path: join(projectRoot, ".claude", "agents", "ade.md"),
|
|
21
|
+
fallbackBody: "ADE — Agentic Development Environment agent."
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await writeMcpServers(config.mcp_servers, {
|
|
25
|
+
path: join(projectRoot, ".mcp.json")
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
await writeClaudeSettings(config, projectRoot);
|
|
29
|
+
await writeInlineSkills(config, projectRoot);
|
|
30
|
+
await writeGitHooks(config.git_hooks, projectRoot);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
async function writeClaudeSettings(
|
|
35
|
+
config: LogicalConfig,
|
|
36
|
+
projectRoot: string
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
const servers = config.mcp_servers;
|
|
39
|
+
if (servers.length === 0) return;
|
|
40
|
+
|
|
41
|
+
const settingsPath = join(projectRoot, ".claude", "settings.json");
|
|
42
|
+
const existing = await readJsonOrEmpty(settingsPath);
|
|
43
|
+
|
|
44
|
+
const allowRules: string[] = [];
|
|
45
|
+
for (const server of servers) {
|
|
46
|
+
const allowed = server.allowedTools ?? ["*"];
|
|
47
|
+
if (allowed.includes("*")) {
|
|
48
|
+
allowRules.push(`MCP(${server.ref}:*)`);
|
|
49
|
+
} else {
|
|
50
|
+
for (const tool of allowed) {
|
|
51
|
+
allowRules.push(`MCP(${server.ref}:${tool})`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const existingPerms = (existing.permissions as Record<string, unknown>) ?? {};
|
|
57
|
+
const existingAllow = (existingPerms.allow as string[]) ?? [];
|
|
58
|
+
const mergedAllow = [...new Set([...existingAllow, ...allowRules])];
|
|
59
|
+
|
|
60
|
+
await writeJson(settingsPath, {
|
|
61
|
+
...existing,
|
|
62
|
+
permissions: { ...existingPerms, allow: mergedAllow }
|
|
63
|
+
});
|
|
64
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
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 { LogicalConfig } from "@codemcp/ade-core";
|
|
6
|
+
import { clineWriter } from "./cline.js";
|
|
7
|
+
|
|
8
|
+
describe("clineWriter", () => {
|
|
9
|
+
let dir: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
dir = await mkdtemp(join(tmpdir(), "ade-harness-cline-"));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await rm(dir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("has correct metadata", () => {
|
|
20
|
+
expect(clineWriter.id).toBe("cline");
|
|
21
|
+
expect(clineWriter.label).toBe("Cline");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("writes .cline/mcp.json with MCP servers", async () => {
|
|
25
|
+
const config: LogicalConfig = {
|
|
26
|
+
mcp_servers: [
|
|
27
|
+
{
|
|
28
|
+
ref: "workflows",
|
|
29
|
+
command: "npx",
|
|
30
|
+
args: ["-y", "@codemcp/workflows"],
|
|
31
|
+
env: {}
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
instructions: [],
|
|
35
|
+
cli_actions: [],
|
|
36
|
+
knowledge_sources: [],
|
|
37
|
+
skills: [],
|
|
38
|
+
git_hooks: [],
|
|
39
|
+
setup_notes: []
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
await clineWriter.install(config, dir);
|
|
43
|
+
|
|
44
|
+
const raw = await readFile(join(dir, ".cline", "mcp.json"), "utf-8");
|
|
45
|
+
const parsed = JSON.parse(raw);
|
|
46
|
+
expect(parsed.mcpServers["workflows"]).toEqual({
|
|
47
|
+
command: "npx",
|
|
48
|
+
args: ["-y", "@codemcp/workflows"],
|
|
49
|
+
alwaysAllow: ["*"]
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("writes .clinerules with instructions", async () => {
|
|
54
|
+
const config: LogicalConfig = {
|
|
55
|
+
mcp_servers: [],
|
|
56
|
+
instructions: ["Follow TDD."],
|
|
57
|
+
cli_actions: [],
|
|
58
|
+
knowledge_sources: [],
|
|
59
|
+
skills: [],
|
|
60
|
+
git_hooks: [],
|
|
61
|
+
setup_notes: []
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
await clineWriter.install(config, dir);
|
|
65
|
+
|
|
66
|
+
const content = await readFile(join(dir, ".clinerules"), "utf-8");
|
|
67
|
+
expect(content).toContain("Follow TDD.");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -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.json + .clinerules",
|
|
15
|
+
async install(config: LogicalConfig, projectRoot: string) {
|
|
16
|
+
await writeMcpServers(config.mcp_servers, {
|
|
17
|
+
path: join(projectRoot, ".cline", "mcp.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,102 @@
|
|
|
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 { LogicalConfig } from "@codemcp/ade-core";
|
|
6
|
+
import { copilotWriter } from "./copilot.js";
|
|
7
|
+
|
|
8
|
+
describe("copilotWriter", () => {
|
|
9
|
+
let dir: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
dir = await mkdtemp(join(tmpdir(), "ade-harness-copilot-"));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await rm(dir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("has correct metadata", () => {
|
|
20
|
+
expect(copilotWriter.id).toBe("copilot");
|
|
21
|
+
expect(copilotWriter.label).toBe("GitHub Copilot");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("writes .vscode/mcp.json with 'servers' key and type field", async () => {
|
|
25
|
+
const config: LogicalConfig = {
|
|
26
|
+
mcp_servers: [
|
|
27
|
+
{
|
|
28
|
+
ref: "workflows",
|
|
29
|
+
command: "npx",
|
|
30
|
+
args: ["-y", "@codemcp/workflows"],
|
|
31
|
+
env: {}
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
instructions: [],
|
|
35
|
+
cli_actions: [],
|
|
36
|
+
knowledge_sources: [],
|
|
37
|
+
skills: [],
|
|
38
|
+
git_hooks: [],
|
|
39
|
+
setup_notes: []
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
await copilotWriter.install(config, dir);
|
|
43
|
+
|
|
44
|
+
const raw = await readFile(join(dir, ".vscode", "mcp.json"), "utf-8");
|
|
45
|
+
const parsed = JSON.parse(raw);
|
|
46
|
+
// Copilot uses "servers", not "mcpServers"
|
|
47
|
+
expect(parsed.servers["workflows"]).toEqual({
|
|
48
|
+
type: "stdio",
|
|
49
|
+
command: "npx",
|
|
50
|
+
args: ["-y", "@codemcp/workflows"]
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("does not write copilot-instructions.md (prefers agent definition)", async () => {
|
|
55
|
+
const config: LogicalConfig = {
|
|
56
|
+
mcp_servers: [],
|
|
57
|
+
instructions: ["Follow TDD."],
|
|
58
|
+
cli_actions: [],
|
|
59
|
+
knowledge_sources: [],
|
|
60
|
+
skills: [],
|
|
61
|
+
git_hooks: [],
|
|
62
|
+
setup_notes: []
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
await copilotWriter.install(config, dir);
|
|
66
|
+
|
|
67
|
+
await expect(
|
|
68
|
+
readFile(join(dir, ".github", "copilot-instructions.md"), "utf-8")
|
|
69
|
+
).rejects.toThrow();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("writes dedicated .github/agents/ade.agent.md with agent definition", 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: ["Follow TDD."],
|
|
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 content = await readFile(
|
|
93
|
+
join(dir, ".github", "agents", "ade.agent.md"),
|
|
94
|
+
"utf-8"
|
|
95
|
+
);
|
|
96
|
+
expect(content).toContain("name: ade");
|
|
97
|
+
expect(content).toContain("tools:");
|
|
98
|
+
expect(content).toContain(" - workflows/*");
|
|
99
|
+
expect(content).toContain(" - edit");
|
|
100
|
+
expect(content).toContain("Follow TDD.");
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
stdioEntry,
|
|
7
|
+
writeAgentMd,
|
|
8
|
+
writeGitHooks
|
|
9
|
+
} from "../util.js";
|
|
10
|
+
|
|
11
|
+
export const copilotWriter: HarnessWriter = {
|
|
12
|
+
id: "copilot",
|
|
13
|
+
label: "GitHub Copilot",
|
|
14
|
+
description: "VS Code + CLI — .vscode/mcp.json + .github/agents/ade.agent.md",
|
|
15
|
+
async install(config: LogicalConfig, projectRoot: string) {
|
|
16
|
+
await writeMcpServers(config.mcp_servers, {
|
|
17
|
+
path: join(projectRoot, ".vscode", "mcp.json"),
|
|
18
|
+
key: "servers",
|
|
19
|
+
transform: stdioEntry
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const tools = [
|
|
23
|
+
"edit",
|
|
24
|
+
"search",
|
|
25
|
+
"runCommands",
|
|
26
|
+
"runTasks",
|
|
27
|
+
"fetch",
|
|
28
|
+
"githubRepo",
|
|
29
|
+
...config.mcp_servers.map((s) => `${s.ref}/*`)
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
await writeAgentMd(config, {
|
|
33
|
+
path: join(projectRoot, ".github", "agents", "ade.agent.md"),
|
|
34
|
+
extraFrontmatter: ["tools:", ...tools.map((t) => ` - ${t}`)]
|
|
35
|
+
});
|
|
36
|
+
await writeGitHooks(config.git_hooks, projectRoot);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
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 { LogicalConfig } from "@codemcp/ade-core";
|
|
6
|
+
import { cursorWriter } from "./cursor.js";
|
|
7
|
+
|
|
8
|
+
describe("cursorWriter", () => {
|
|
9
|
+
let dir: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
dir = await mkdtemp(join(tmpdir(), "ade-harness-cursor-"));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await rm(dir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("has correct metadata", () => {
|
|
20
|
+
expect(cursorWriter.id).toBe("cursor");
|
|
21
|
+
expect(cursorWriter.label).toBe("Cursor");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("writes .cursor/mcp.json with MCP servers", async () => {
|
|
25
|
+
const config: LogicalConfig = {
|
|
26
|
+
mcp_servers: [
|
|
27
|
+
{
|
|
28
|
+
ref: "workflows",
|
|
29
|
+
command: "npx",
|
|
30
|
+
args: ["-y", "@codemcp/workflows"],
|
|
31
|
+
env: {}
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
instructions: [],
|
|
35
|
+
cli_actions: [],
|
|
36
|
+
knowledge_sources: [],
|
|
37
|
+
skills: [],
|
|
38
|
+
git_hooks: [],
|
|
39
|
+
setup_notes: []
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
await cursorWriter.install(config, dir);
|
|
43
|
+
|
|
44
|
+
const raw = await readFile(join(dir, ".cursor", "mcp.json"), "utf-8");
|
|
45
|
+
const parsed = JSON.parse(raw);
|
|
46
|
+
expect(parsed.mcpServers["workflows"]).toEqual({
|
|
47
|
+
command: "npx",
|
|
48
|
+
args: ["-y", "@codemcp/workflows"]
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("writes .cursor/rules/ade.mdc with instructions", async () => {
|
|
53
|
+
const config: LogicalConfig = {
|
|
54
|
+
mcp_servers: [],
|
|
55
|
+
instructions: ["Follow TDD.", "Use conventional commits."],
|
|
56
|
+
cli_actions: [],
|
|
57
|
+
knowledge_sources: [],
|
|
58
|
+
skills: [],
|
|
59
|
+
git_hooks: [],
|
|
60
|
+
setup_notes: []
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
await cursorWriter.install(config, dir);
|
|
64
|
+
|
|
65
|
+
const content = await readFile(
|
|
66
|
+
join(dir, ".cursor", "rules", "ade.mdc"),
|
|
67
|
+
"utf-8"
|
|
68
|
+
);
|
|
69
|
+
expect(content).toContain("description: ADE project conventions");
|
|
70
|
+
expect(content).toContain("Follow TDD.");
|
|
71
|
+
expect(content).toContain("Use conventional commits.");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("includes agentskills server from mcp_servers", async () => {
|
|
75
|
+
const config: LogicalConfig = {
|
|
76
|
+
mcp_servers: [
|
|
77
|
+
{
|
|
78
|
+
ref: "agentskills",
|
|
79
|
+
command: "npx",
|
|
80
|
+
args: ["-y", "@codemcp/skills-server"],
|
|
81
|
+
env: {}
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
instructions: [],
|
|
85
|
+
cli_actions: [],
|
|
86
|
+
knowledge_sources: [],
|
|
87
|
+
skills: [{ name: "my-skill", description: "A skill", body: "content" }],
|
|
88
|
+
git_hooks: [],
|
|
89
|
+
setup_notes: []
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
await cursorWriter.install(config, dir);
|
|
93
|
+
|
|
94
|
+
const raw = await readFile(join(dir, ".cursor", "mcp.json"), "utf-8");
|
|
95
|
+
const parsed = JSON.parse(raw);
|
|
96
|
+
expect(parsed.mcpServers["agentskills"]).toBeDefined();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("skips mcp.json when no servers and no skills", async () => {
|
|
100
|
+
const config: LogicalConfig = {
|
|
101
|
+
mcp_servers: [],
|
|
102
|
+
instructions: ["hello"],
|
|
103
|
+
cli_actions: [],
|
|
104
|
+
knowledge_sources: [],
|
|
105
|
+
skills: [],
|
|
106
|
+
git_hooks: [],
|
|
107
|
+
setup_notes: []
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
await cursorWriter.install(config, dir);
|
|
111
|
+
|
|
112
|
+
await expect(
|
|
113
|
+
readFile(join(dir, ".cursor", "mcp.json"), "utf-8")
|
|
114
|
+
).rejects.toThrow();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { LogicalConfig } from "@codemcp/ade-core";
|
|
4
|
+
import type { HarnessWriter } from "../types.js";
|
|
5
|
+
import { writeMcpServers, writeGitHooks } from "../util.js";
|
|
6
|
+
|
|
7
|
+
export const cursorWriter: HarnessWriter = {
|
|
8
|
+
id: "cursor",
|
|
9
|
+
label: "Cursor",
|
|
10
|
+
description: "AI code editor — .cursor/mcp.json + .cursor/rules/",
|
|
11
|
+
async install(config: LogicalConfig, projectRoot: string) {
|
|
12
|
+
await writeMcpServers(config.mcp_servers, {
|
|
13
|
+
path: join(projectRoot, ".cursor", "mcp.json")
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
if (config.instructions.length > 0) {
|
|
17
|
+
const rulesDir = join(projectRoot, ".cursor", "rules");
|
|
18
|
+
await mkdir(rulesDir, { recursive: true });
|
|
19
|
+
|
|
20
|
+
const content = [
|
|
21
|
+
"---",
|
|
22
|
+
"description: ADE project conventions",
|
|
23
|
+
"globs: *",
|
|
24
|
+
"---",
|
|
25
|
+
"",
|
|
26
|
+
...config.instructions.flatMap((i) => [i, ""])
|
|
27
|
+
].join("\n");
|
|
28
|
+
|
|
29
|
+
await writeFile(join(rulesDir, "ade.mdc"), content, "utf-8");
|
|
30
|
+
}
|
|
31
|
+
await writeGitHooks(config.git_hooks, projectRoot);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import type { LogicalConfig } from "@codemcp/ade-core";
|
|
3
|
+
import type { HarnessWriter } from "../types.js";
|
|
4
|
+
import { standardEntry, writeJson, writeGitHooks } from "../util.js";
|
|
5
|
+
|
|
6
|
+
export const kiroWriter: HarnessWriter = {
|
|
7
|
+
id: "kiro",
|
|
8
|
+
label: "Kiro",
|
|
9
|
+
description: "AWS AI IDE — .kiro/agents/ade.json",
|
|
10
|
+
async install(config: LogicalConfig, projectRoot: string) {
|
|
11
|
+
const servers = config.mcp_servers;
|
|
12
|
+
if (servers.length > 0 || config.instructions.length > 0) {
|
|
13
|
+
const mcpServers: Record<string, unknown> = {};
|
|
14
|
+
for (const s of servers) {
|
|
15
|
+
mcpServers[s.ref] = standardEntry(s);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const tools: string[] = [
|
|
19
|
+
"execute_bash",
|
|
20
|
+
"fs_read",
|
|
21
|
+
"fs_write",
|
|
22
|
+
"knowledge",
|
|
23
|
+
"thinking",
|
|
24
|
+
...Object.keys(mcpServers).map((n) => `@${n}`)
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const allowedTools: string[] = [];
|
|
28
|
+
for (const s of servers) {
|
|
29
|
+
const explicit = s.allowedTools;
|
|
30
|
+
if (explicit && !explicit.includes("*")) {
|
|
31
|
+
for (const tool of explicit) {
|
|
32
|
+
allowedTools.push(`@${s.ref}/${tool}`);
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
allowedTools.push(`@${s.ref}/*`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
await writeJson(join(projectRoot, ".kiro", "agents", "ade.json"), {
|
|
40
|
+
name: "ade",
|
|
41
|
+
prompt:
|
|
42
|
+
config.instructions.length > 0
|
|
43
|
+
? config.instructions.join("\n\n")
|
|
44
|
+
: "ADE — Agentic Development Environment agent",
|
|
45
|
+
mcpServers,
|
|
46
|
+
tools,
|
|
47
|
+
allowedTools
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
await writeGitHooks(config.git_hooks, projectRoot);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import type { LogicalConfig } from "@codemcp/ade-core";
|
|
3
|
+
import type { HarnessWriter } from "../types.js";
|
|
4
|
+
import { writeMcpServers, writeAgentMd, writeGitHooks } from "../util.js";
|
|
5
|
+
|
|
6
|
+
export const opencodeWriter: HarnessWriter = {
|
|
7
|
+
id: "opencode",
|
|
8
|
+
label: "OpenCode",
|
|
9
|
+
description: "Terminal AI agent — opencode.json + .opencode/agents/",
|
|
10
|
+
async install(config: LogicalConfig, projectRoot: string) {
|
|
11
|
+
await writeMcpServers(config.mcp_servers, {
|
|
12
|
+
path: join(projectRoot, "opencode.json"),
|
|
13
|
+
key: "mcp",
|
|
14
|
+
transform: (s) => ({
|
|
15
|
+
type: "local",
|
|
16
|
+
command: [s.command, ...s.args],
|
|
17
|
+
...(Object.keys(s.env).length > 0 ? { env: s.env } : {})
|
|
18
|
+
}),
|
|
19
|
+
defaults: { $schema: "https://opencode.ai/config.json" }
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const servers = config.mcp_servers;
|
|
23
|
+
const extraFm: string[] = [
|
|
24
|
+
"tools:",
|
|
25
|
+
" read: true",
|
|
26
|
+
" edit: approve",
|
|
27
|
+
" bash: approve"
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
if (servers.length > 0) {
|
|
31
|
+
extraFm.push("mcp_servers:");
|
|
32
|
+
for (const s of servers) {
|
|
33
|
+
extraFm.push(` ${s.ref}:`);
|
|
34
|
+
extraFm.push(
|
|
35
|
+
` command: [${[s.command, ...s.args].map((a) => `"${a}"`).join(", ")}]`
|
|
36
|
+
);
|
|
37
|
+
if (Object.keys(s.env).length > 0) {
|
|
38
|
+
extraFm.push(" env:");
|
|
39
|
+
for (const [k, v] of Object.entries(s.env)) {
|
|
40
|
+
extraFm.push(` ${k}: "${v}"`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await writeAgentMd(config, {
|
|
47
|
+
path: join(projectRoot, ".opencode", "agents", "ade.md"),
|
|
48
|
+
extraFrontmatter: extraFm,
|
|
49
|
+
fallbackBody:
|
|
50
|
+
"ADE — Agentic Development Environment agent with project conventions and tools."
|
|
51
|
+
});
|
|
52
|
+
await writeGitHooks(config.git_hooks, projectRoot);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
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 { LogicalConfig } from "@codemcp/ade-core";
|
|
6
|
+
import { rooCodeWriter } from "./roo-code.js";
|
|
7
|
+
|
|
8
|
+
describe("rooCodeWriter", () => {
|
|
9
|
+
let dir: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
dir = await mkdtemp(join(tmpdir(), "ade-harness-roo-"));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await rm(dir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("has correct metadata", () => {
|
|
20
|
+
expect(rooCodeWriter.id).toBe("roo-code");
|
|
21
|
+
expect(rooCodeWriter.label).toBe("Roo Code");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("writes .roo/mcp.json with MCP servers", async () => {
|
|
25
|
+
const config: LogicalConfig = {
|
|
26
|
+
mcp_servers: [
|
|
27
|
+
{
|
|
28
|
+
ref: "workflows",
|
|
29
|
+
command: "npx",
|
|
30
|
+
args: ["-y", "@codemcp/workflows"],
|
|
31
|
+
env: {}
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
instructions: [],
|
|
35
|
+
cli_actions: [],
|
|
36
|
+
knowledge_sources: [],
|
|
37
|
+
skills: [],
|
|
38
|
+
git_hooks: [],
|
|
39
|
+
setup_notes: []
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
await rooCodeWriter.install(config, dir);
|
|
43
|
+
|
|
44
|
+
const raw = await readFile(join(dir, ".roo", "mcp.json"), "utf-8");
|
|
45
|
+
const parsed = JSON.parse(raw);
|
|
46
|
+
expect(parsed.mcpServers["workflows"]).toEqual({
|
|
47
|
+
command: "npx",
|
|
48
|
+
args: ["-y", "@codemcp/workflows"],
|
|
49
|
+
alwaysAllow: ["*"]
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("writes .roorules with instructions", async () => {
|
|
54
|
+
const config: LogicalConfig = {
|
|
55
|
+
mcp_servers: [],
|
|
56
|
+
instructions: ["Follow TDD."],
|
|
57
|
+
cli_actions: [],
|
|
58
|
+
knowledge_sources: [],
|
|
59
|
+
skills: [],
|
|
60
|
+
git_hooks: [],
|
|
61
|
+
setup_notes: []
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
await rooCodeWriter.install(config, dir);
|
|
65
|
+
|
|
66
|
+
const content = await readFile(join(dir, ".roorules"), "utf-8");
|
|
67
|
+
expect(content).toContain("Follow TDD.");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -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 rooCodeWriter: HarnessWriter = {
|
|
12
|
+
id: "roo-code",
|
|
13
|
+
label: "Roo Code",
|
|
14
|
+
description: "AI coding agent — .roo/mcp.json + .roorules",
|
|
15
|
+
async install(config: LogicalConfig, projectRoot: string) {
|
|
16
|
+
await writeMcpServers(config.mcp_servers, {
|
|
17
|
+
path: join(projectRoot, ".roo", "mcp.json"),
|
|
18
|
+
transform: alwaysAllowEntry
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
await writeRulesFile(config.instructions, join(projectRoot, ".roorules"));
|
|
22
|
+
await writeGitHooks(config.git_hooks, projectRoot);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import type { LogicalConfig } from "@codemcp/ade-core";
|
|
4
|
+
import type { HarnessWriter } from "../types.js";
|
|
5
|
+
import { writeMcpServers, writeGitHooks } from "../util.js";
|
|
6
|
+
|
|
7
|
+
export const universalWriter: HarnessWriter = {
|
|
8
|
+
id: "universal",
|
|
9
|
+
label: "Universal (AGENTS.md + .mcp.json)",
|
|
10
|
+
description:
|
|
11
|
+
"Cross-tool standard — AGENTS.md + .mcp.json (works with any agent)",
|
|
12
|
+
async install(config: LogicalConfig, projectRoot: string) {
|
|
13
|
+
if (config.instructions.length > 0) {
|
|
14
|
+
const lines = [
|
|
15
|
+
"# AGENTS",
|
|
16
|
+
"",
|
|
17
|
+
...config.instructions.flatMap((i) => [i, ""])
|
|
18
|
+
];
|
|
19
|
+
await writeFile(
|
|
20
|
+
join(projectRoot, "AGENTS.md"),
|
|
21
|
+
lines.join("\n"),
|
|
22
|
+
"utf-8"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
await writeMcpServers(config.mcp_servers, {
|
|
27
|
+
path: join(projectRoot, ".mcp.json")
|
|
28
|
+
});
|
|
29
|
+
await writeGitHooks(config.git_hooks, projectRoot);
|
|
30
|
+
}
|
|
31
|
+
};
|