@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
package/.prettierignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dist
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
> @codemcp/ade-harnesses@0.0.2 test /home/runner/work/ade/ade/packages/harnesses
|
|
3
|
+
> vitest --run
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
[1m[46m RUN [49m[22m [36mv3.2.4 [39m[90m/home/runner/work/ade/ade/packages/harnesses[39m
|
|
7
|
+
|
|
8
|
+
[32m✓[39m src/writers/copilot.spec.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 55[2mms[22m[39m
|
|
9
|
+
[32m✓[39m src/writers/cursor.spec.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 82[2mms[22m[39m
|
|
10
|
+
[32m✓[39m src/writers/claude-code.spec.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 107[2mms[22m[39m
|
|
11
|
+
[32m✓[39m src/writers/roo-code.spec.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 31[2mms[22m[39m
|
|
12
|
+
[32m✓[39m src/writers/windsurf.spec.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 82[2mms[22m[39m
|
|
13
|
+
[32m✓[39m src/writers/cline.spec.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 82[2mms[22m[39m
|
|
14
|
+
[32m✓[39m src/index.spec.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 9[2mms[22m[39m
|
|
15
|
+
|
|
16
|
+
[2m Test Files [22m [1m[32m7 passed[39m[22m[90m (7)[39m
|
|
17
|
+
[2m Tests [22m [1m[32m28 passed[39m[22m[90m (28)[39m
|
|
18
|
+
[2m Start at [22m 06:53:29
|
|
19
|
+
[2m Duration [22m 3.26s[2m (transform 457ms, setup 0ms, collect 1.32s, tests 448ms, environment 4ms, prepare 2.22s)[22m
|
|
20
|
+
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Luke Baker
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type { HarnessWriter } from "./types.js";
|
|
2
|
+
export { installSkills } from "./skills-installer.js";
|
|
3
|
+
export { universalWriter } from "./writers/universal.js";
|
|
4
|
+
export { claudeCodeWriter } from "./writers/claude-code.js";
|
|
5
|
+
export { cursorWriter } from "./writers/cursor.js";
|
|
6
|
+
export { copilotWriter } from "./writers/copilot.js";
|
|
7
|
+
export { windsurfWriter } from "./writers/windsurf.js";
|
|
8
|
+
export { clineWriter } from "./writers/cline.js";
|
|
9
|
+
export { rooCodeWriter } from "./writers/roo-code.js";
|
|
10
|
+
export { kiroWriter } from "./writers/kiro.js";
|
|
11
|
+
export { opencodeWriter } from "./writers/opencode.js";
|
|
12
|
+
import type { HarnessWriter } from "./types.js";
|
|
13
|
+
/** All built-in harness writers, ordered for wizard display. */
|
|
14
|
+
export declare const allHarnessWriters: HarnessWriter[];
|
|
15
|
+
/** Look up a harness writer by id. */
|
|
16
|
+
export declare function getHarnessWriter(id: string): HarnessWriter | undefined;
|
|
17
|
+
/** All valid harness IDs. */
|
|
18
|
+
export declare function getHarnessIds(): string[];
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export { installSkills } from "./skills-installer.js";
|
|
2
|
+
export { universalWriter } from "./writers/universal.js";
|
|
3
|
+
export { claudeCodeWriter } from "./writers/claude-code.js";
|
|
4
|
+
export { cursorWriter } from "./writers/cursor.js";
|
|
5
|
+
export { copilotWriter } from "./writers/copilot.js";
|
|
6
|
+
export { windsurfWriter } from "./writers/windsurf.js";
|
|
7
|
+
export { clineWriter } from "./writers/cline.js";
|
|
8
|
+
export { rooCodeWriter } from "./writers/roo-code.js";
|
|
9
|
+
export { kiroWriter } from "./writers/kiro.js";
|
|
10
|
+
export { opencodeWriter } from "./writers/opencode.js";
|
|
11
|
+
import { universalWriter } from "./writers/universal.js";
|
|
12
|
+
import { claudeCodeWriter } from "./writers/claude-code.js";
|
|
13
|
+
import { cursorWriter } from "./writers/cursor.js";
|
|
14
|
+
import { copilotWriter } from "./writers/copilot.js";
|
|
15
|
+
import { windsurfWriter } from "./writers/windsurf.js";
|
|
16
|
+
import { clineWriter } from "./writers/cline.js";
|
|
17
|
+
import { rooCodeWriter } from "./writers/roo-code.js";
|
|
18
|
+
import { kiroWriter } from "./writers/kiro.js";
|
|
19
|
+
import { opencodeWriter } from "./writers/opencode.js";
|
|
20
|
+
/** All built-in harness writers, ordered for wizard display. */
|
|
21
|
+
export const allHarnessWriters = [
|
|
22
|
+
universalWriter,
|
|
23
|
+
claudeCodeWriter,
|
|
24
|
+
cursorWriter,
|
|
25
|
+
copilotWriter,
|
|
26
|
+
windsurfWriter,
|
|
27
|
+
clineWriter,
|
|
28
|
+
rooCodeWriter,
|
|
29
|
+
kiroWriter,
|
|
30
|
+
opencodeWriter
|
|
31
|
+
];
|
|
32
|
+
/** Look up a harness writer by id. */
|
|
33
|
+
export function getHarnessWriter(id) {
|
|
34
|
+
return allHarnessWriters.find((w) => w.id === id);
|
|
35
|
+
}
|
|
36
|
+
/** All valid harness IDs. */
|
|
37
|
+
export function getHarnessIds() {
|
|
38
|
+
return allHarnessWriters.map((w) => w.id);
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SkillDefinition } from "@codemcp/ade-core";
|
|
2
|
+
/**
|
|
3
|
+
* Install skills using the @codemcp/skills programmatic API.
|
|
4
|
+
*
|
|
5
|
+
* Inline skills are expected to already exist as SKILL.md files under
|
|
6
|
+
* `<projectRoot>/.ade/skills/<name>/` (written by the agent writer).
|
|
7
|
+
* This function calls `runAdd` with the local path for inline skills
|
|
8
|
+
* and the remote source for external skills.
|
|
9
|
+
*
|
|
10
|
+
* Note: `runAdd` uses `process.cwd()` to determine the install destination.
|
|
11
|
+
* This function changes cwd to `projectRoot` before calling `runAdd`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function installSkills(skills: SkillDefinition[], projectRoot: string): Promise<void>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { runAdd } from "@codemcp/skills/api";
|
|
3
|
+
function isInlineSkill(skill) {
|
|
4
|
+
return "body" in skill;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Install skills using the @codemcp/skills programmatic API.
|
|
8
|
+
*
|
|
9
|
+
* Inline skills are expected to already exist as SKILL.md files under
|
|
10
|
+
* `<projectRoot>/.ade/skills/<name>/` (written by the agent writer).
|
|
11
|
+
* This function calls `runAdd` with the local path for inline skills
|
|
12
|
+
* and the remote source for external skills.
|
|
13
|
+
*
|
|
14
|
+
* Note: `runAdd` uses `process.cwd()` to determine the install destination.
|
|
15
|
+
* This function changes cwd to `projectRoot` before calling `runAdd`.
|
|
16
|
+
*/
|
|
17
|
+
export async function installSkills(skills, projectRoot) {
|
|
18
|
+
if (skills.length === 0)
|
|
19
|
+
return;
|
|
20
|
+
const originalCwd = process.cwd();
|
|
21
|
+
process.chdir(projectRoot);
|
|
22
|
+
try {
|
|
23
|
+
for (const skill of skills) {
|
|
24
|
+
const source = isInlineSkill(skill)
|
|
25
|
+
? join(projectRoot, ".ade", "skills", skill.name)
|
|
26
|
+
: skill.source;
|
|
27
|
+
try {
|
|
28
|
+
await runAdd([source], { yes: true, all: true });
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
// runAdd may throw on network errors for external skills.
|
|
32
|
+
// Log and continue — inline skills should always succeed.
|
|
33
|
+
console.warn(`Warning: failed to install skill "${skill.name}" from ${source}:`, err instanceof Error ? err.message : err);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
// Restore cwd only if the original directory still exists
|
|
39
|
+
try {
|
|
40
|
+
process.chdir(originalCwd);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Original cwd may have been removed (e.g. in tests)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AgentWriterDef } from "@codemcp/ade-core";
|
|
2
|
+
/**
|
|
3
|
+
* A harness writer extends AgentWriterDef with metadata for display in the
|
|
4
|
+
* setup wizard and CLI help.
|
|
5
|
+
*/
|
|
6
|
+
export interface HarnessWriter extends AgentWriterDef {
|
|
7
|
+
/** Human-readable label for the wizard (e.g. "Claude Code") */
|
|
8
|
+
label: string;
|
|
9
|
+
/** Short description shown as hint in the wizard */
|
|
10
|
+
description: string;
|
|
11
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { GitHook, LogicalConfig, McpServerEntry } from "@codemcp/ade-core";
|
|
2
|
+
/** Read a JSON file, returning `{}` if missing or unparseable. */
|
|
3
|
+
export declare function readJsonOrEmpty(path: string): Promise<Record<string, unknown>>;
|
|
4
|
+
/** Write a JSON object with trailing newline. Creates parent dirs. */
|
|
5
|
+
export declare function writeJson(path: string, data: unknown): Promise<void>;
|
|
6
|
+
export type ServerTransform = (server: McpServerEntry) => Record<string, unknown>;
|
|
7
|
+
/** Standard mcpServers entry (cursor, universal, claude-code). */
|
|
8
|
+
export declare const standardEntry: ServerTransform;
|
|
9
|
+
/** Adds `type: "stdio"` (copilot). */
|
|
10
|
+
export declare const stdioEntry: ServerTransform;
|
|
11
|
+
/** Adds `alwaysAllow` (cline, roo-code, windsurf). */
|
|
12
|
+
export declare const alwaysAllowEntry: ServerTransform;
|
|
13
|
+
interface WriteMcpServersOpts {
|
|
14
|
+
/** Full path to the JSON file. */
|
|
15
|
+
path: string;
|
|
16
|
+
/** Key in the JSON that holds the server map. Default: `"mcpServers"`. */
|
|
17
|
+
key?: string;
|
|
18
|
+
/** Transform each McpServerEntry into the harness-specific shape. */
|
|
19
|
+
transform?: ServerTransform;
|
|
20
|
+
/** Extra top-level fields to merge (e.g. `$schema`). */
|
|
21
|
+
defaults?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Merge MCP server entries into an existing JSON config file.
|
|
25
|
+
* Creates the file (and parent dirs) if missing.
|
|
26
|
+
*/
|
|
27
|
+
export declare function writeMcpServers(servers: McpServerEntry[], opts: WriteMcpServersOpts): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Write instructions as a plain text rules file.
|
|
30
|
+
* Skips if no instructions.
|
|
31
|
+
*/
|
|
32
|
+
export declare function writeRulesFile(instructions: string[], path: string): Promise<void>;
|
|
33
|
+
interface AgentMdOpts {
|
|
34
|
+
/** Full path to the .md file. */
|
|
35
|
+
path: string;
|
|
36
|
+
/** Extra YAML frontmatter lines (after name/description, before `---`). */
|
|
37
|
+
extraFrontmatter?: string[];
|
|
38
|
+
/** Fallback body when instructions are empty. */
|
|
39
|
+
fallbackBody?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Write an agent markdown file with YAML frontmatter.
|
|
43
|
+
* Shared by claude-code, copilot, and opencode.
|
|
44
|
+
*/
|
|
45
|
+
export declare function writeAgentMd(config: LogicalConfig, opts: AgentMdOpts): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Write git hook scripts to `.git/hooks/<phase>`.
|
|
48
|
+
* Files are created with executable permissions (0o755).
|
|
49
|
+
* No-op when the hooks array is empty.
|
|
50
|
+
*/
|
|
51
|
+
export declare function writeGitHooks(hooks: GitHook[] | undefined, projectRoot: string): Promise<void>;
|
|
52
|
+
export declare function writeInlineSkills(config: LogicalConfig, projectRoot: string): Promise<void>;
|
|
53
|
+
export {};
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// JSON helpers
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
/** Read a JSON file, returning `{}` if missing or unparseable. */
|
|
7
|
+
export async function readJsonOrEmpty(path) {
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(await readFile(path, "utf-8"));
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Write a JSON object with trailing newline. Creates parent dirs. */
|
|
16
|
+
export async function writeJson(path, data) {
|
|
17
|
+
await mkdir(dirname(path), { recursive: true });
|
|
18
|
+
await writeFile(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
19
|
+
}
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Server entry transform — each harness overrides only what differs
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
/** Minimal MCP entry: command + args + optional env. */
|
|
24
|
+
function baseEntry(server) {
|
|
25
|
+
return {
|
|
26
|
+
command: server.command,
|
|
27
|
+
args: server.args,
|
|
28
|
+
...(Object.keys(server.env).length > 0 ? { env: server.env } : {})
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** Standard mcpServers entry (cursor, universal, claude-code). */
|
|
32
|
+
export const standardEntry = baseEntry;
|
|
33
|
+
/** Adds `type: "stdio"` (copilot). */
|
|
34
|
+
export const stdioEntry = (s) => ({
|
|
35
|
+
type: "stdio",
|
|
36
|
+
...baseEntry(s)
|
|
37
|
+
});
|
|
38
|
+
/** Adds `alwaysAllow` (cline, roo-code, windsurf). */
|
|
39
|
+
export const alwaysAllowEntry = (s) => ({
|
|
40
|
+
...baseEntry(s),
|
|
41
|
+
alwaysAllow: s.allowedTools ?? ["*"]
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Merge MCP server entries into an existing JSON config file.
|
|
45
|
+
* Creates the file (and parent dirs) if missing.
|
|
46
|
+
*/
|
|
47
|
+
export async function writeMcpServers(servers, opts) {
|
|
48
|
+
if (servers.length === 0)
|
|
49
|
+
return;
|
|
50
|
+
const key = opts.key ?? "mcpServers";
|
|
51
|
+
const transform = opts.transform ?? standardEntry;
|
|
52
|
+
const existing = await readJsonOrEmpty(opts.path);
|
|
53
|
+
const map = existing[key] ?? {};
|
|
54
|
+
for (const server of servers) {
|
|
55
|
+
map[server.ref] = transform(server);
|
|
56
|
+
}
|
|
57
|
+
const result = { ...(opts.defaults ?? {}), ...existing, [key]: map };
|
|
58
|
+
await writeJson(opts.path, result);
|
|
59
|
+
}
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Instructions → flat rules file (windsurf, cline, roo-code)
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
/**
|
|
64
|
+
* Write instructions as a plain text rules file.
|
|
65
|
+
* Skips if no instructions.
|
|
66
|
+
*/
|
|
67
|
+
export async function writeRulesFile(instructions, path) {
|
|
68
|
+
if (instructions.length === 0)
|
|
69
|
+
return;
|
|
70
|
+
const lines = instructions.flatMap((i) => [i, ""]);
|
|
71
|
+
await mkdir(dirname(path), { recursive: true });
|
|
72
|
+
await writeFile(path, lines.join("\n"), "utf-8");
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Write an agent markdown file with YAML frontmatter.
|
|
76
|
+
* Shared by claude-code, copilot, and opencode.
|
|
77
|
+
*/
|
|
78
|
+
export async function writeAgentMd(config, opts) {
|
|
79
|
+
if (config.instructions.length === 0 && config.mcp_servers.length === 0)
|
|
80
|
+
return;
|
|
81
|
+
const fm = [
|
|
82
|
+
"---",
|
|
83
|
+
"name: ade",
|
|
84
|
+
"description: ADE — Agentic Development Environment agent with project conventions and tools"
|
|
85
|
+
];
|
|
86
|
+
if (opts.extraFrontmatter) {
|
|
87
|
+
fm.push(...opts.extraFrontmatter);
|
|
88
|
+
}
|
|
89
|
+
fm.push("---");
|
|
90
|
+
const body = config.instructions.length > 0
|
|
91
|
+
? config.instructions.join("\n\n")
|
|
92
|
+
: (opts.fallbackBody ?? "");
|
|
93
|
+
const content = fm.join("\n") + "\n\n" + body + "\n";
|
|
94
|
+
await mkdir(dirname(opts.path), { recursive: true });
|
|
95
|
+
await writeFile(opts.path, content, "utf-8");
|
|
96
|
+
}
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Inline skill SKILL.md writer (used by claude-code)
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Git hook installer
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
/**
|
|
104
|
+
* Write git hook scripts to `.git/hooks/<phase>`.
|
|
105
|
+
* Files are created with executable permissions (0o755).
|
|
106
|
+
* No-op when the hooks array is empty.
|
|
107
|
+
*/
|
|
108
|
+
export async function writeGitHooks(hooks, projectRoot) {
|
|
109
|
+
if (!hooks)
|
|
110
|
+
return;
|
|
111
|
+
for (const hook of hooks) {
|
|
112
|
+
const hookPath = join(projectRoot, ".git", "hooks", hook.phase);
|
|
113
|
+
await writeFile(hookPath, hook.script, { mode: 0o755 });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export async function writeInlineSkills(config, projectRoot) {
|
|
117
|
+
for (const skill of config.skills) {
|
|
118
|
+
if (!("body" in skill))
|
|
119
|
+
continue;
|
|
120
|
+
const skillDir = join(projectRoot, ".ade", "skills", skill.name);
|
|
121
|
+
await mkdir(skillDir, { recursive: true });
|
|
122
|
+
const frontmatter = [
|
|
123
|
+
"---",
|
|
124
|
+
`name: ${skill.name}`,
|
|
125
|
+
`description: ${skill.description}`,
|
|
126
|
+
"---"
|
|
127
|
+
].join("\n");
|
|
128
|
+
await writeFile(join(skillDir, "SKILL.md"), `${frontmatter}\n\n${skill.body}\n`, "utf-8");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { readJsonOrEmpty, writeJson, writeMcpServers, writeAgentMd, writeInlineSkills, writeGitHooks } from "../util.js";
|
|
3
|
+
export const claudeCodeWriter = {
|
|
4
|
+
id: "claude-code",
|
|
5
|
+
label: "Claude Code",
|
|
6
|
+
description: "Anthropic's CLI agent — .claude/agents/ade.md + .mcp.json + .claude/settings.json",
|
|
7
|
+
async install(config, projectRoot) {
|
|
8
|
+
await writeAgentMd(config, {
|
|
9
|
+
path: join(projectRoot, ".claude", "agents", "ade.md"),
|
|
10
|
+
fallbackBody: "ADE — Agentic Development Environment agent."
|
|
11
|
+
});
|
|
12
|
+
await writeMcpServers(config.mcp_servers, {
|
|
13
|
+
path: join(projectRoot, ".mcp.json")
|
|
14
|
+
});
|
|
15
|
+
await writeClaudeSettings(config, projectRoot);
|
|
16
|
+
await writeInlineSkills(config, projectRoot);
|
|
17
|
+
await writeGitHooks(config.git_hooks, projectRoot);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
async function writeClaudeSettings(config, projectRoot) {
|
|
21
|
+
const servers = config.mcp_servers;
|
|
22
|
+
if (servers.length === 0)
|
|
23
|
+
return;
|
|
24
|
+
const settingsPath = join(projectRoot, ".claude", "settings.json");
|
|
25
|
+
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
|
+
const existingPerms = existing.permissions ?? {};
|
|
39
|
+
const existingAllow = existingPerms.allow ?? [];
|
|
40
|
+
const mergedAllow = [...new Set([...existingAllow, ...allowRules])];
|
|
41
|
+
await writeJson(settingsPath, {
|
|
42
|
+
...existing,
|
|
43
|
+
permissions: { ...existingPerms, allow: mergedAllow }
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { writeMcpServers, alwaysAllowEntry, writeRulesFile, writeGitHooks } from "../util.js";
|
|
3
|
+
export const clineWriter = {
|
|
4
|
+
id: "cline",
|
|
5
|
+
label: "Cline",
|
|
6
|
+
description: "VS Code AI agent — .cline/mcp.json + .clinerules",
|
|
7
|
+
async install(config, projectRoot) {
|
|
8
|
+
await writeMcpServers(config.mcp_servers, {
|
|
9
|
+
path: join(projectRoot, ".cline", "mcp.json"),
|
|
10
|
+
transform: alwaysAllowEntry
|
|
11
|
+
});
|
|
12
|
+
await writeRulesFile(config.instructions, join(projectRoot, ".clinerules"));
|
|
13
|
+
await writeGitHooks(config.git_hooks, projectRoot);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { writeMcpServers, stdioEntry, writeAgentMd, writeGitHooks } from "../util.js";
|
|
3
|
+
export const copilotWriter = {
|
|
4
|
+
id: "copilot",
|
|
5
|
+
label: "GitHub Copilot",
|
|
6
|
+
description: "VS Code + CLI — .vscode/mcp.json + .github/agents/ade.agent.md",
|
|
7
|
+
async install(config, projectRoot) {
|
|
8
|
+
await writeMcpServers(config.mcp_servers, {
|
|
9
|
+
path: join(projectRoot, ".vscode", "mcp.json"),
|
|
10
|
+
key: "servers",
|
|
11
|
+
transform: stdioEntry
|
|
12
|
+
});
|
|
13
|
+
const tools = [
|
|
14
|
+
"edit",
|
|
15
|
+
"search",
|
|
16
|
+
"runCommands",
|
|
17
|
+
"runTasks",
|
|
18
|
+
"fetch",
|
|
19
|
+
"githubRepo",
|
|
20
|
+
...config.mcp_servers.map((s) => `${s.ref}/*`)
|
|
21
|
+
];
|
|
22
|
+
await writeAgentMd(config, {
|
|
23
|
+
path: join(projectRoot, ".github", "agents", "ade.agent.md"),
|
|
24
|
+
extraFrontmatter: ["tools:", ...tools.map((t) => ` - ${t}`)]
|
|
25
|
+
});
|
|
26
|
+
await writeGitHooks(config.git_hooks, projectRoot);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { writeMcpServers, writeGitHooks } from "../util.js";
|
|
4
|
+
export const cursorWriter = {
|
|
5
|
+
id: "cursor",
|
|
6
|
+
label: "Cursor",
|
|
7
|
+
description: "AI code editor — .cursor/mcp.json + .cursor/rules/",
|
|
8
|
+
async install(config, projectRoot) {
|
|
9
|
+
await writeMcpServers(config.mcp_servers, {
|
|
10
|
+
path: join(projectRoot, ".cursor", "mcp.json")
|
|
11
|
+
});
|
|
12
|
+
if (config.instructions.length > 0) {
|
|
13
|
+
const rulesDir = join(projectRoot, ".cursor", "rules");
|
|
14
|
+
await mkdir(rulesDir, { recursive: true });
|
|
15
|
+
const content = [
|
|
16
|
+
"---",
|
|
17
|
+
"description: ADE project conventions",
|
|
18
|
+
"globs: *",
|
|
19
|
+
"---",
|
|
20
|
+
"",
|
|
21
|
+
...config.instructions.flatMap((i) => [i, ""])
|
|
22
|
+
].join("\n");
|
|
23
|
+
await writeFile(join(rulesDir, "ade.mdc"), content, "utf-8");
|
|
24
|
+
}
|
|
25
|
+
await writeGitHooks(config.git_hooks, projectRoot);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { standardEntry, writeJson, writeGitHooks } from "../util.js";
|
|
3
|
+
export const kiroWriter = {
|
|
4
|
+
id: "kiro",
|
|
5
|
+
label: "Kiro",
|
|
6
|
+
description: "AWS AI IDE — .kiro/agents/ade.json",
|
|
7
|
+
async install(config, projectRoot) {
|
|
8
|
+
const servers = config.mcp_servers;
|
|
9
|
+
if (servers.length > 0 || config.instructions.length > 0) {
|
|
10
|
+
const mcpServers = {};
|
|
11
|
+
for (const s of servers) {
|
|
12
|
+
mcpServers[s.ref] = standardEntry(s);
|
|
13
|
+
}
|
|
14
|
+
const tools = [
|
|
15
|
+
"execute_bash",
|
|
16
|
+
"fs_read",
|
|
17
|
+
"fs_write",
|
|
18
|
+
"knowledge",
|
|
19
|
+
"thinking",
|
|
20
|
+
...Object.keys(mcpServers).map((n) => `@${n}`)
|
|
21
|
+
];
|
|
22
|
+
const allowedTools = [];
|
|
23
|
+
for (const s of servers) {
|
|
24
|
+
const explicit = s.allowedTools;
|
|
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
|
+
}
|
|
44
|
+
await writeGitHooks(config.git_hooks, projectRoot);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { writeMcpServers, writeAgentMd, writeGitHooks } from "../util.js";
|
|
3
|
+
export const opencodeWriter = {
|
|
4
|
+
id: "opencode",
|
|
5
|
+
label: "OpenCode",
|
|
6
|
+
description: "Terminal AI agent — opencode.json + .opencode/agents/",
|
|
7
|
+
async install(config, projectRoot) {
|
|
8
|
+
await writeMcpServers(config.mcp_servers, {
|
|
9
|
+
path: join(projectRoot, "opencode.json"),
|
|
10
|
+
key: "mcp",
|
|
11
|
+
transform: (s) => ({
|
|
12
|
+
type: "local",
|
|
13
|
+
command: [s.command, ...s.args],
|
|
14
|
+
...(Object.keys(s.env).length > 0 ? { env: s.env } : {})
|
|
15
|
+
}),
|
|
16
|
+
defaults: { $schema: "https://opencode.ai/config.json" }
|
|
17
|
+
});
|
|
18
|
+
const servers = config.mcp_servers;
|
|
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
|
+
}
|
|
38
|
+
await writeAgentMd(config, {
|
|
39
|
+
path: join(projectRoot, ".opencode", "agents", "ade.md"),
|
|
40
|
+
extraFrontmatter: extraFm,
|
|
41
|
+
fallbackBody: "ADE — Agentic Development Environment agent with project conventions and tools."
|
|
42
|
+
});
|
|
43
|
+
await writeGitHooks(config.git_hooks, projectRoot);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { writeMcpServers, alwaysAllowEntry, writeRulesFile, writeGitHooks } from "../util.js";
|
|
3
|
+
export const rooCodeWriter = {
|
|
4
|
+
id: "roo-code",
|
|
5
|
+
label: "Roo Code",
|
|
6
|
+
description: "AI coding agent — .roo/mcp.json + .roorules",
|
|
7
|
+
async install(config, projectRoot) {
|
|
8
|
+
await writeMcpServers(config.mcp_servers, {
|
|
9
|
+
path: join(projectRoot, ".roo", "mcp.json"),
|
|
10
|
+
transform: alwaysAllowEntry
|
|
11
|
+
});
|
|
12
|
+
await writeRulesFile(config.instructions, join(projectRoot, ".roorules"));
|
|
13
|
+
await writeGitHooks(config.git_hooks, projectRoot);
|
|
14
|
+
}
|
|
15
|
+
};
|