@codemcp/ade-harnesses 0.0.2 → 0.1.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-format.log +1 -1
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +15 -12
- package/.turbo/turbo-typecheck.log +1 -1
- package/dist/permission-policy.d.ts +7 -0
- package/dist/permission-policy.js +152 -0
- package/dist/writers/claude-code.js +50 -18
- package/dist/writers/cline.js +2 -2
- package/dist/writers/copilot.js +61 -8
- package/dist/writers/cursor.js +48 -2
- package/dist/writers/kiro.js +54 -38
- package/dist/writers/opencode.js +26 -23
- package/dist/writers/roo-code.js +38 -2
- package/dist/writers/universal.js +41 -3
- package/dist/writers/windsurf.js +43 -1
- package/package.json +2 -2
- package/src/permission-policy.ts +173 -0
- package/src/writers/claude-code.spec.ts +160 -3
- package/src/writers/claude-code.ts +63 -18
- package/src/writers/cline.spec.ts +146 -3
- package/src/writers/cline.ts +2 -2
- package/src/writers/copilot.spec.ts +157 -1
- package/src/writers/copilot.ts +76 -9
- package/src/writers/cursor.spec.ts +104 -1
- package/src/writers/cursor.ts +65 -3
- package/src/writers/kiro.spec.ts +228 -0
- package/src/writers/kiro.ts +77 -40
- package/src/writers/opencode.spec.ts +258 -0
- package/src/writers/opencode.ts +40 -27
- package/src/writers/roo-code.spec.ts +129 -1
- package/src/writers/roo-code.ts +49 -2
- package/src/writers/universal.spec.ts +134 -0
- package/src/writers/universal.ts +57 -4
- package/src/writers/windsurf.spec.ts +111 -3
- package/src/writers/windsurf.ts +64 -2
- package/tsconfig.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
package/.turbo/turbo-format.log
CHANGED
package/.turbo/turbo-lint.log
CHANGED
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
> @codemcp/ade-harnesses@0.0
|
|
2
|
+
> @codemcp/ade-harnesses@0.1.0 test /home/runner/work/ade/ade/packages/harnesses
|
|
3
3
|
> vitest --run
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
[1m[46m RUN [49m[22m [36mv3.2.4 [39m[90m/home/runner/work/ade/ade/packages/harnesses[39m
|
|
7
7
|
|
|
8
|
-
[32m✓[39m src/writers/copilot.spec.ts [2m([22m[
|
|
9
|
-
[32m✓[39m src/writers/
|
|
10
|
-
[32m✓[39m src/writers/
|
|
11
|
-
[32m✓[39m src/writers/
|
|
12
|
-
[32m✓[39m src/writers/
|
|
13
|
-
[32m✓[39m src/writers/cline.spec.ts [2m([22m[
|
|
14
|
-
[32m✓[39m src/
|
|
8
|
+
[32m✓[39m src/writers/copilot.spec.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 99[2mms[22m[39m
|
|
9
|
+
[32m✓[39m src/writers/claude-code.spec.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 129[2mms[22m[39m
|
|
10
|
+
[32m✓[39m src/writers/opencode.spec.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 68[2mms[22m[39m
|
|
11
|
+
[32m✓[39m src/writers/cursor.spec.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 68[2mms[22m[39m
|
|
12
|
+
[32m✓[39m src/writers/kiro.spec.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 84[2mms[22m[39m
|
|
13
|
+
[32m✓[39m src/writers/cline.spec.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 68[2mms[22m[39m
|
|
14
|
+
[32m✓[39m src/writers/windsurf.spec.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 79[2mms[22m[39m
|
|
15
|
+
[32m✓[39m src/writers/roo-code.spec.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 92[2mms[22m[39m
|
|
16
|
+
[32m✓[39m src/writers/universal.spec.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 89[2mms[22m[39m
|
|
17
|
+
[32m✓[39m src/index.spec.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
15
18
|
|
|
16
|
-
[2m Test Files [22m [1m[
|
|
17
|
-
[2m Tests [22m [1m[
|
|
18
|
-
[2m Start at [22m
|
|
19
|
-
[2m Duration [22m 3.
|
|
19
|
+
[2m Test Files [22m [1m[32m10 passed[39m[22m[90m (10)[39m
|
|
20
|
+
[2m Tests [22m [1m[32m47 passed[39m[22m[90m (47)[39m
|
|
21
|
+
[2m Start at [22m 12:11:20
|
|
22
|
+
[2m Duration [22m 3.69s[2m (transform 606ms, setup 0ms, collect 1.55s, tests 781ms, environment 2ms, prepare 2.86s)[22m
|
|
20
23
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AutonomyCapability, LogicalConfig, PermissionDecision, PermissionRule } from "@codemcp/ade-core";
|
|
2
|
+
export declare function getAutonomyProfile(config: LogicalConfig): import("@codemcp/ade-core").AutonomyProfile | undefined;
|
|
3
|
+
export declare function hasPermissionPolicy(config: LogicalConfig): boolean;
|
|
4
|
+
export declare function getCapabilityDecision(config: LogicalConfig, capability: AutonomyCapability): PermissionDecision | undefined;
|
|
5
|
+
export declare function allowsCapability(config: LogicalConfig, capability: AutonomyCapability): boolean;
|
|
6
|
+
export declare function keepsWebOnAsk(config: LogicalConfig): boolean;
|
|
7
|
+
export declare function getHarnessPermissionRules(config: LogicalConfig): Record<string, PermissionRule> | undefined;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
const SENSIBLE_DEFAULTS_RULES = {
|
|
2
|
+
read: {
|
|
3
|
+
"*": "allow",
|
|
4
|
+
"*.env": "deny",
|
|
5
|
+
"*.env.*": "deny",
|
|
6
|
+
"*.env.example": "allow"
|
|
7
|
+
},
|
|
8
|
+
edit: "allow",
|
|
9
|
+
glob: "allow",
|
|
10
|
+
grep: "allow",
|
|
11
|
+
list: "allow",
|
|
12
|
+
lsp: "allow",
|
|
13
|
+
task: "allow",
|
|
14
|
+
todoread: "deny",
|
|
15
|
+
todowrite: "deny",
|
|
16
|
+
skill: "deny",
|
|
17
|
+
webfetch: "ask",
|
|
18
|
+
websearch: "ask",
|
|
19
|
+
codesearch: "ask",
|
|
20
|
+
bash: {
|
|
21
|
+
"*": "deny",
|
|
22
|
+
"grep *": "allow",
|
|
23
|
+
"rg *": "allow",
|
|
24
|
+
"find *": "allow",
|
|
25
|
+
"fd *": "allow",
|
|
26
|
+
ls: "allow",
|
|
27
|
+
"ls *": "allow",
|
|
28
|
+
"cat *": "allow",
|
|
29
|
+
"head *": "allow",
|
|
30
|
+
"tail *": "allow",
|
|
31
|
+
"wc *": "allow",
|
|
32
|
+
"sort *": "allow",
|
|
33
|
+
"uniq *": "allow",
|
|
34
|
+
"diff *": "allow",
|
|
35
|
+
"echo *": "allow",
|
|
36
|
+
"printf *": "allow",
|
|
37
|
+
pwd: "allow",
|
|
38
|
+
"which *": "allow",
|
|
39
|
+
"type *": "allow",
|
|
40
|
+
whoami: "allow",
|
|
41
|
+
date: "allow",
|
|
42
|
+
"date *": "allow",
|
|
43
|
+
env: "allow",
|
|
44
|
+
"tree *": "allow",
|
|
45
|
+
"file *": "allow",
|
|
46
|
+
"stat *": "allow",
|
|
47
|
+
"readlink *": "allow",
|
|
48
|
+
"realpath *": "allow",
|
|
49
|
+
"dirname *": "allow",
|
|
50
|
+
"basename *": "allow",
|
|
51
|
+
"sed *": "allow",
|
|
52
|
+
"awk *": "allow",
|
|
53
|
+
"cut *": "allow",
|
|
54
|
+
"tr *": "allow",
|
|
55
|
+
"tee *": "allow",
|
|
56
|
+
"xargs *": "allow",
|
|
57
|
+
"jq *": "allow",
|
|
58
|
+
"yq *": "allow",
|
|
59
|
+
"mkdir *": "allow",
|
|
60
|
+
"touch *": "allow",
|
|
61
|
+
"cp *": "ask",
|
|
62
|
+
"mv *": "ask",
|
|
63
|
+
"ln *": "ask",
|
|
64
|
+
"npm *": "ask",
|
|
65
|
+
"node *": "ask",
|
|
66
|
+
"pip *": "ask",
|
|
67
|
+
"python *": "ask",
|
|
68
|
+
"python3 *": "ask",
|
|
69
|
+
"rm *": "deny",
|
|
70
|
+
"rmdir *": "deny",
|
|
71
|
+
"curl *": "deny",
|
|
72
|
+
"wget *": "deny",
|
|
73
|
+
"chmod *": "deny",
|
|
74
|
+
"chown *": "deny",
|
|
75
|
+
"sudo *": "deny",
|
|
76
|
+
"su *": "deny",
|
|
77
|
+
"sh *": "deny",
|
|
78
|
+
"bash *": "deny",
|
|
79
|
+
"zsh *": "deny",
|
|
80
|
+
"eval *": "deny",
|
|
81
|
+
"exec *": "deny",
|
|
82
|
+
"source *": "deny",
|
|
83
|
+
". *": "deny",
|
|
84
|
+
"nohup *": "deny",
|
|
85
|
+
"dd *": "deny",
|
|
86
|
+
"mkfs *": "deny",
|
|
87
|
+
"mount *": "deny",
|
|
88
|
+
"umount *": "deny",
|
|
89
|
+
"kill *": "deny",
|
|
90
|
+
"killall *": "deny",
|
|
91
|
+
"pkill *": "deny",
|
|
92
|
+
"nc *": "deny",
|
|
93
|
+
"ncat *": "deny",
|
|
94
|
+
"ssh *": "deny",
|
|
95
|
+
"scp *": "deny",
|
|
96
|
+
"rsync *": "deny",
|
|
97
|
+
"docker *": "deny",
|
|
98
|
+
"kubectl *": "deny",
|
|
99
|
+
"systemctl *": "deny",
|
|
100
|
+
"service *": "deny",
|
|
101
|
+
"crontab *": "deny",
|
|
102
|
+
reboot: "deny",
|
|
103
|
+
"shutdown *": "deny",
|
|
104
|
+
"passwd *": "deny",
|
|
105
|
+
"useradd *": "deny",
|
|
106
|
+
"userdel *": "deny",
|
|
107
|
+
"iptables *": "deny"
|
|
108
|
+
},
|
|
109
|
+
external_directory: "deny",
|
|
110
|
+
doom_loop: "deny"
|
|
111
|
+
};
|
|
112
|
+
export function getAutonomyProfile(config) {
|
|
113
|
+
return config.permission_policy?.profile;
|
|
114
|
+
}
|
|
115
|
+
export function hasPermissionPolicy(config) {
|
|
116
|
+
return config.permission_policy !== undefined;
|
|
117
|
+
}
|
|
118
|
+
export function getCapabilityDecision(config, capability) {
|
|
119
|
+
return config.permission_policy?.capabilities?.[capability];
|
|
120
|
+
}
|
|
121
|
+
export function allowsCapability(config, capability) {
|
|
122
|
+
return getCapabilityDecision(config, capability) === "allow";
|
|
123
|
+
}
|
|
124
|
+
export function keepsWebOnAsk(config) {
|
|
125
|
+
return getCapabilityDecision(config, "web") === "ask";
|
|
126
|
+
}
|
|
127
|
+
export function getHarnessPermissionRules(config) {
|
|
128
|
+
switch (config.permission_policy?.profile) {
|
|
129
|
+
case "rigid":
|
|
130
|
+
return {
|
|
131
|
+
"*": "ask",
|
|
132
|
+
webfetch: "ask",
|
|
133
|
+
websearch: "ask",
|
|
134
|
+
codesearch: "ask",
|
|
135
|
+
external_directory: "deny",
|
|
136
|
+
doom_loop: "deny"
|
|
137
|
+
};
|
|
138
|
+
case "sensible-defaults":
|
|
139
|
+
return SENSIBLE_DEFAULTS_RULES;
|
|
140
|
+
case "max-autonomy":
|
|
141
|
+
return {
|
|
142
|
+
"*": "allow",
|
|
143
|
+
webfetch: "ask",
|
|
144
|
+
websearch: "ask",
|
|
145
|
+
codesearch: "ask",
|
|
146
|
+
external_directory: "deny",
|
|
147
|
+
doom_loop: "deny"
|
|
148
|
+
};
|
|
149
|
+
default:
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { readJsonOrEmpty, writeJson, writeMcpServers, writeAgentMd, writeInlineSkills, writeGitHooks } from "../util.js";
|
|
3
|
+
import { allowsCapability, keepsWebOnAsk } from "../permission-policy.js";
|
|
3
4
|
export const claudeCodeWriter = {
|
|
4
5
|
id: "claude-code",
|
|
5
6
|
label: "Claude Code",
|
|
@@ -18,28 +19,59 @@ export const claudeCodeWriter = {
|
|
|
18
19
|
}
|
|
19
20
|
};
|
|
20
21
|
async function writeClaudeSettings(config, projectRoot) {
|
|
21
|
-
const servers = config.mcp_servers;
|
|
22
|
-
if (servers.length === 0)
|
|
23
|
-
return;
|
|
24
22
|
const settingsPath = join(projectRoot, ".claude", "settings.json");
|
|
25
23
|
const existing = await readJsonOrEmpty(settingsPath);
|
|
26
|
-
const allowRules = [];
|
|
27
|
-
for (const server of servers) {
|
|
28
|
-
const allowed = server.allowedTools ?? ["*"];
|
|
29
|
-
if (allowed.includes("*")) {
|
|
30
|
-
allowRules.push(`MCP(${server.ref}:*)`);
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
for (const tool of allowed) {
|
|
34
|
-
allowRules.push(`MCP(${server.ref}:${tool})`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
24
|
const existingPerms = existing.permissions ?? {};
|
|
39
|
-
const existingAllow = existingPerms.allow
|
|
40
|
-
const
|
|
25
|
+
const existingAllow = asStringArray(existingPerms.allow);
|
|
26
|
+
const existingAsk = asStringArray(existingPerms.ask);
|
|
27
|
+
const autonomyRules = getClaudeAutonomyRules(config);
|
|
28
|
+
const mcpRules = getClaudeMcpAllowRules(config);
|
|
29
|
+
const allowRules = [
|
|
30
|
+
...new Set([...existingAllow, ...autonomyRules.allow, ...mcpRules])
|
|
31
|
+
];
|
|
32
|
+
const askRules = [...new Set([...existingAsk, ...autonomyRules.ask])];
|
|
33
|
+
if (allowRules.length === 0 &&
|
|
34
|
+
askRules.length === 0 &&
|
|
35
|
+
config.mcp_servers.length === 0) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
41
38
|
await writeJson(settingsPath, {
|
|
42
39
|
...existing,
|
|
43
|
-
permissions: {
|
|
40
|
+
permissions: {
|
|
41
|
+
...existingPerms,
|
|
42
|
+
...(allowRules.length > 0 ? { allow: allowRules } : {}),
|
|
43
|
+
...(askRules.length > 0 ? { ask: askRules } : {})
|
|
44
|
+
}
|
|
44
45
|
});
|
|
45
46
|
}
|
|
47
|
+
function asStringArray(value) {
|
|
48
|
+
return Array.isArray(value)
|
|
49
|
+
? value.filter((entry) => typeof entry === "string")
|
|
50
|
+
: [];
|
|
51
|
+
}
|
|
52
|
+
function getClaudeMcpAllowRules(config) {
|
|
53
|
+
const allowRules = [];
|
|
54
|
+
for (const server of config.mcp_servers) {
|
|
55
|
+
const allowedTools = server.allowedTools;
|
|
56
|
+
if (!allowedTools || allowedTools.includes("*")) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
for (const tool of allowedTools) {
|
|
60
|
+
allowRules.push(`mcp__${server.ref}__${tool}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return allowRules;
|
|
64
|
+
}
|
|
65
|
+
function getClaudeAutonomyRules(config) {
|
|
66
|
+
const ask = keepsWebOnAsk(config) ? ["WebFetch", "WebSearch"] : [];
|
|
67
|
+
return {
|
|
68
|
+
allow: [
|
|
69
|
+
...(allowsCapability(config, "read") ? ["Read"] : []),
|
|
70
|
+
...(allowsCapability(config, "edit_write") ? ["Edit"] : []),
|
|
71
|
+
...(allowsCapability(config, "search_list") ? ["Glob", "Grep"] : []),
|
|
72
|
+
...(allowsCapability(config, "bash_unsafe") ? ["Bash"] : []),
|
|
73
|
+
...(allowsCapability(config, "task_agent") ? ["TodoWrite"] : [])
|
|
74
|
+
],
|
|
75
|
+
ask
|
|
76
|
+
};
|
|
77
|
+
}
|
package/dist/writers/cline.js
CHANGED
|
@@ -3,10 +3,10 @@ import { writeMcpServers, alwaysAllowEntry, writeRulesFile, writeGitHooks } from
|
|
|
3
3
|
export const clineWriter = {
|
|
4
4
|
id: "cline",
|
|
5
5
|
label: "Cline",
|
|
6
|
-
description: "VS Code AI agent — .
|
|
6
|
+
description: "VS Code AI agent — cline_mcp_settings.json + .clinerules",
|
|
7
7
|
async install(config, projectRoot) {
|
|
8
8
|
await writeMcpServers(config.mcp_servers, {
|
|
9
|
-
path: join(projectRoot, ".
|
|
9
|
+
path: join(projectRoot, "cline_mcp_settings.json"),
|
|
10
10
|
transform: alwaysAllowEntry
|
|
11
11
|
});
|
|
12
12
|
await writeRulesFile(config.instructions, join(projectRoot, ".clinerules"));
|
package/dist/writers/copilot.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { writeMcpServers, stdioEntry, writeAgentMd, writeGitHooks } from "../util.js";
|
|
3
|
+
import { allowsCapability, hasPermissionPolicy, keepsWebOnAsk } from "../permission-policy.js";
|
|
3
4
|
export const copilotWriter = {
|
|
4
5
|
id: "copilot",
|
|
5
6
|
label: "GitHub Copilot",
|
|
@@ -11,18 +12,70 @@ export const copilotWriter = {
|
|
|
11
12
|
transform: stdioEntry
|
|
12
13
|
});
|
|
13
14
|
const tools = [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"runCommands",
|
|
17
|
-
"runTasks",
|
|
18
|
-
"fetch",
|
|
19
|
-
"githubRepo",
|
|
20
|
-
...config.mcp_servers.map((s) => `${s.ref}/*`)
|
|
15
|
+
...getBuiltInTools(config),
|
|
16
|
+
...getForwardedMcpTools(config.mcp_servers)
|
|
21
17
|
];
|
|
22
18
|
await writeAgentMd(config, {
|
|
23
19
|
path: join(projectRoot, ".github", "agents", "ade.agent.md"),
|
|
24
|
-
extraFrontmatter: [
|
|
20
|
+
extraFrontmatter: [
|
|
21
|
+
"tools:",
|
|
22
|
+
...tools.map((t) => ` - ${t}`),
|
|
23
|
+
...renderCopilotAgentMcpServers(config.mcp_servers)
|
|
24
|
+
]
|
|
25
25
|
});
|
|
26
26
|
await writeGitHooks(config.git_hooks, projectRoot);
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
|
+
function getBuiltInTools(config) {
|
|
30
|
+
if (!hasPermissionPolicy(config)) {
|
|
31
|
+
return ["read", "edit", "search", "execute", "agent", "web"];
|
|
32
|
+
}
|
|
33
|
+
return [
|
|
34
|
+
...(allowsCapability(config, "read") ? ["read"] : []),
|
|
35
|
+
...(allowsCapability(config, "edit_write") ? ["edit"] : []),
|
|
36
|
+
...(allowsCapability(config, "search_list") ? ["search"] : []),
|
|
37
|
+
...(allowsCapability(config, "bash_unsafe") ? ["execute"] : []),
|
|
38
|
+
...(allowsCapability(config, "task_agent") ? ["agent"] : []),
|
|
39
|
+
...(allowsCapability(config, "task_agent") &&
|
|
40
|
+
allowsCapability(config, "bash_unsafe")
|
|
41
|
+
? ["todo"]
|
|
42
|
+
: []),
|
|
43
|
+
...(!keepsWebOnAsk(config) && allowsCapability(config, "web")
|
|
44
|
+
? ["web"]
|
|
45
|
+
: [])
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
function getForwardedMcpTools(servers) {
|
|
49
|
+
return servers.flatMap((server) => {
|
|
50
|
+
const allowedTools = server.allowedTools ?? ["*"];
|
|
51
|
+
if (allowedTools.includes("*")) {
|
|
52
|
+
return [`${server.ref}/*`];
|
|
53
|
+
}
|
|
54
|
+
return allowedTools.map((tool) => `${server.ref}/${tool}`);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function renderCopilotAgentMcpServers(servers) {
|
|
58
|
+
if (servers.length === 0) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
const lines = ["mcp-servers:"];
|
|
62
|
+
for (const server of servers) {
|
|
63
|
+
lines.push(` ${formatYamlKey(server.ref)}:`);
|
|
64
|
+
lines.push(" type: stdio");
|
|
65
|
+
lines.push(` command: ${JSON.stringify(server.command)}`);
|
|
66
|
+
lines.push(` args: ${JSON.stringify(server.args)}`);
|
|
67
|
+
lines.push(` tools: ${JSON.stringify(server.allowedTools ?? ["*"])}`);
|
|
68
|
+
if (Object.keys(server.env).length > 0) {
|
|
69
|
+
lines.push(" env:");
|
|
70
|
+
for (const [key, value] of Object.entries(server.env)) {
|
|
71
|
+
lines.push(` ${formatYamlKey(key)}: ${JSON.stringify(value)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return lines;
|
|
76
|
+
}
|
|
77
|
+
function formatYamlKey(value) {
|
|
78
|
+
return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(value)
|
|
79
|
+
? value
|
|
80
|
+
: JSON.stringify(value);
|
|
81
|
+
}
|
package/dist/writers/cursor.js
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { writeMcpServers, writeGitHooks } from "../util.js";
|
|
4
|
+
import { getAutonomyProfile, getCapabilityDecision, hasPermissionPolicy } from "../permission-policy.js";
|
|
5
|
+
const CURSOR_CAPABILITY_ORDER = [
|
|
6
|
+
"read",
|
|
7
|
+
"edit_write",
|
|
8
|
+
"search_list",
|
|
9
|
+
"bash_safe",
|
|
10
|
+
"bash_unsafe",
|
|
11
|
+
"web",
|
|
12
|
+
"task_agent"
|
|
13
|
+
];
|
|
14
|
+
const CURSOR_CAPABILITY_LABELS = {
|
|
15
|
+
read: "read project files",
|
|
16
|
+
edit_write: "edit and write project files",
|
|
17
|
+
search_list: "search and list project contents",
|
|
18
|
+
bash_safe: "run safe local shell commands",
|
|
19
|
+
bash_unsafe: "run high-impact shell commands",
|
|
20
|
+
web: "use web or network access",
|
|
21
|
+
task_agent: "delegate or decompose work into agent tasks"
|
|
22
|
+
};
|
|
4
23
|
export const cursorWriter = {
|
|
5
24
|
id: "cursor",
|
|
6
25
|
label: "Cursor",
|
|
@@ -9,7 +28,8 @@ export const cursorWriter = {
|
|
|
9
28
|
await writeMcpServers(config.mcp_servers, {
|
|
10
29
|
path: join(projectRoot, ".cursor", "mcp.json")
|
|
11
30
|
});
|
|
12
|
-
|
|
31
|
+
const rulesBody = getCursorRulesBody(config);
|
|
32
|
+
if (rulesBody.length > 0) {
|
|
13
33
|
const rulesDir = join(projectRoot, ".cursor", "rules");
|
|
14
34
|
await mkdir(rulesDir, { recursive: true });
|
|
15
35
|
const content = [
|
|
@@ -18,10 +38,36 @@ export const cursorWriter = {
|
|
|
18
38
|
"globs: *",
|
|
19
39
|
"---",
|
|
20
40
|
"",
|
|
21
|
-
...
|
|
41
|
+
...rulesBody.flatMap((line) => [line, ""])
|
|
22
42
|
].join("\n");
|
|
23
43
|
await writeFile(join(rulesDir, "ade.mdc"), content, "utf-8");
|
|
24
44
|
}
|
|
25
45
|
await writeGitHooks(config.git_hooks, projectRoot);
|
|
26
46
|
}
|
|
27
47
|
};
|
|
48
|
+
function getCursorRulesBody(config) {
|
|
49
|
+
return [...config.instructions, ...getCursorAutonomyNotes(config)];
|
|
50
|
+
}
|
|
51
|
+
function getCursorAutonomyNotes(config) {
|
|
52
|
+
if (!hasPermissionPolicy(config)) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
const allowedCapabilities = CURSOR_CAPABILITY_ORDER.filter((capability) => getCapabilityDecision(config, capability) === "allow").map((capability) => CURSOR_CAPABILITY_LABELS[capability]);
|
|
56
|
+
const approvalGatedCapabilities = CURSOR_CAPABILITY_ORDER.filter((capability) => getCapabilityDecision(config, capability) === "ask").map((capability) => CURSOR_CAPABILITY_LABELS[capability]);
|
|
57
|
+
return [
|
|
58
|
+
`Cursor autonomy note (documented, not enforced): ${getAutonomyProfile(config) ?? "custom"}.`,
|
|
59
|
+
"Cursor has no verified committed project-local built-in ask/allow/deny config surface, so ADE documents autonomy intent here instead of writing unsupported permission config.",
|
|
60
|
+
...(allowedCapabilities.length > 0
|
|
61
|
+
? [
|
|
62
|
+
`Prefer handling these built-in capabilities without extra approval when Cursor permits it: ${allowedCapabilities.join(", ")}.`
|
|
63
|
+
]
|
|
64
|
+
: []),
|
|
65
|
+
...(approvalGatedCapabilities.length > 0
|
|
66
|
+
? [
|
|
67
|
+
`Request approval before these capabilities: ${approvalGatedCapabilities.join(", ")}.`
|
|
68
|
+
]
|
|
69
|
+
: []),
|
|
70
|
+
"Web and network access must remain approval-gated.",
|
|
71
|
+
"MCP server registration stays in .cursor/mcp.json; MCP tool approvals remain owned by provisioning and are not enforced or re-modeled in this rules file."
|
|
72
|
+
];
|
|
73
|
+
}
|
package/dist/writers/kiro.js
CHANGED
|
@@ -1,46 +1,62 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { standardEntry, writeJson,
|
|
2
|
+
import { standardEntry, writeGitHooks, writeJson, writeMcpServers } from "../util.js";
|
|
3
|
+
import { allowsCapability, getCapabilityDecision, hasPermissionPolicy } from "../permission-policy.js";
|
|
3
4
|
export const kiroWriter = {
|
|
4
5
|
id: "kiro",
|
|
5
6
|
label: "Kiro",
|
|
6
|
-
description: "AWS AI IDE — .kiro/agents/ade.json",
|
|
7
|
+
description: "AWS AI IDE — .kiro/agents/ade.json + .kiro/settings/mcp.json",
|
|
7
8
|
async install(config, projectRoot) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (explicit && !explicit.includes("*")) {
|
|
26
|
-
for (const tool of explicit) {
|
|
27
|
-
allowedTools.push(`@${s.ref}/${tool}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
allowedTools.push(`@${s.ref}/*`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
await writeJson(join(projectRoot, ".kiro", "agents", "ade.json"), {
|
|
35
|
-
name: "ade",
|
|
36
|
-
prompt: config.instructions.length > 0
|
|
37
|
-
? config.instructions.join("\n\n")
|
|
38
|
-
: "ADE — Agentic Development Environment agent",
|
|
39
|
-
mcpServers,
|
|
40
|
-
tools,
|
|
41
|
-
allowedTools
|
|
42
|
-
});
|
|
43
|
-
}
|
|
9
|
+
await writeMcpServers(config.mcp_servers, {
|
|
10
|
+
path: join(projectRoot, ".kiro", "settings", "mcp.json"),
|
|
11
|
+
transform: (server) => ({
|
|
12
|
+
...standardEntry(server),
|
|
13
|
+
autoApprove: server.allowedTools ?? ["*"]
|
|
14
|
+
})
|
|
15
|
+
});
|
|
16
|
+
await writeJson(join(projectRoot, ".kiro", "agents", "ade.json"), {
|
|
17
|
+
name: "ade",
|
|
18
|
+
description: "ADE — Agentic Development Environment agent with project conventions and tools.",
|
|
19
|
+
prompt: config.instructions.join("\n\n") ||
|
|
20
|
+
"ADE — Agentic Development Environment agent.",
|
|
21
|
+
mcpServers: getKiroAgentMcpServers(config.mcp_servers),
|
|
22
|
+
tools: getKiroTools(config),
|
|
23
|
+
allowedTools: getKiroAllowedTools(config),
|
|
24
|
+
useLegacyMcpJson: true
|
|
25
|
+
});
|
|
44
26
|
await writeGitHooks(config.git_hooks, projectRoot);
|
|
45
27
|
}
|
|
46
28
|
};
|
|
29
|
+
function getKiroTools(config) {
|
|
30
|
+
const mcpTools = getKiroForwardedMcpTools(config.mcp_servers);
|
|
31
|
+
if (!hasPermissionPolicy(config)) {
|
|
32
|
+
return ["read", "write", "shell", "spec", ...mcpTools];
|
|
33
|
+
}
|
|
34
|
+
return [
|
|
35
|
+
...(getCapabilityDecision(config, "read") !== "deny" ? ["read"] : []),
|
|
36
|
+
...(allowsCapability(config, "edit_write") ? ["write"] : []),
|
|
37
|
+
...(allowsCapability(config, "bash_unsafe") ? ["shell"] : []),
|
|
38
|
+
"spec",
|
|
39
|
+
...mcpTools
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
function getKiroAllowedTools(config) {
|
|
43
|
+
return getKiroTools(config);
|
|
44
|
+
}
|
|
45
|
+
function getKiroForwardedMcpTools(servers) {
|
|
46
|
+
return servers.flatMap((server) => {
|
|
47
|
+
const allowedTools = server.allowedTools ?? ["*"];
|
|
48
|
+
if (allowedTools.includes("*")) {
|
|
49
|
+
return [`@${server.ref}/*`];
|
|
50
|
+
}
|
|
51
|
+
return allowedTools.map((tool) => `@${server.ref}/${tool}`);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function getKiroAgentMcpServers(servers) {
|
|
55
|
+
return Object.fromEntries(servers.map((server) => [
|
|
56
|
+
server.ref,
|
|
57
|
+
{
|
|
58
|
+
...standardEntry(server),
|
|
59
|
+
autoApprove: server.allowedTools ?? ["*"]
|
|
60
|
+
}
|
|
61
|
+
]));
|
|
62
|
+
}
|
package/dist/writers/opencode.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { writeAgentMd, writeGitHooks, writeMcpServers } from "../util.js";
|
|
3
|
+
import { getHarnessPermissionRules } from "../permission-policy.js";
|
|
3
4
|
export const opencodeWriter = {
|
|
4
5
|
id: "opencode",
|
|
5
6
|
label: "OpenCode",
|
|
@@ -11,35 +12,37 @@ export const opencodeWriter = {
|
|
|
11
12
|
transform: (s) => ({
|
|
12
13
|
type: "local",
|
|
13
14
|
command: [s.command, ...s.args],
|
|
14
|
-
...(Object.keys(s.env).length > 0 ? {
|
|
15
|
+
...(Object.keys(s.env).length > 0 ? { environment: s.env } : {})
|
|
15
16
|
}),
|
|
16
17
|
defaults: { $schema: "https://opencode.ai/config.json" }
|
|
17
18
|
});
|
|
18
|
-
const
|
|
19
|
-
const extraFm = [
|
|
20
|
-
"tools:",
|
|
21
|
-
" read: true",
|
|
22
|
-
" edit: approve",
|
|
23
|
-
" bash: approve"
|
|
24
|
-
];
|
|
25
|
-
if (servers.length > 0) {
|
|
26
|
-
extraFm.push("mcp_servers:");
|
|
27
|
-
for (const s of servers) {
|
|
28
|
-
extraFm.push(` ${s.ref}:`);
|
|
29
|
-
extraFm.push(` command: [${[s.command, ...s.args].map((a) => `"${a}"`).join(", ")}]`);
|
|
30
|
-
if (Object.keys(s.env).length > 0) {
|
|
31
|
-
extraFm.push(" env:");
|
|
32
|
-
for (const [k, v] of Object.entries(s.env)) {
|
|
33
|
-
extraFm.push(` ${k}: "${v}"`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
19
|
+
const permission = getHarnessPermissionRules(config);
|
|
38
20
|
await writeAgentMd(config, {
|
|
39
21
|
path: join(projectRoot, ".opencode", "agents", "ade.md"),
|
|
40
|
-
extraFrontmatter:
|
|
22
|
+
extraFrontmatter: permission
|
|
23
|
+
? renderYamlMapping("permission", permission)
|
|
24
|
+
: undefined,
|
|
41
25
|
fallbackBody: "ADE — Agentic Development Environment agent with project conventions and tools."
|
|
42
26
|
});
|
|
43
27
|
await writeGitHooks(config.git_hooks, projectRoot);
|
|
44
28
|
}
|
|
45
29
|
};
|
|
30
|
+
function renderYamlMapping(key, value, indent = 0) {
|
|
31
|
+
const prefix = " ".repeat(indent);
|
|
32
|
+
const lines = [`${prefix}${formatYamlKey(key)}:`];
|
|
33
|
+
for (const [childKey, childValue] of Object.entries(value)) {
|
|
34
|
+
if (typeof childValue === "object" &&
|
|
35
|
+
childValue !== null &&
|
|
36
|
+
!Array.isArray(childValue)) {
|
|
37
|
+
lines.push(...renderYamlMapping(childKey, childValue, indent + 2));
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
lines.push(`${" ".repeat(indent + 2)}${formatYamlKey(childKey)}: ${JSON.stringify(childValue)}`);
|
|
41
|
+
}
|
|
42
|
+
return lines;
|
|
43
|
+
}
|
|
44
|
+
function formatYamlKey(value) {
|
|
45
|
+
return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(value)
|
|
46
|
+
? value
|
|
47
|
+
: JSON.stringify(value);
|
|
48
|
+
}
|