diff --git a/packages/cli/src/commands/install.spec.ts b/packages/cli/src/commands/install.spec.ts index db4abda..4c6f25e 100644 --- a/packages/cli/src/commands/install.spec.ts +++ b/packages/cli/src/commands/install.spec.ts @@ -49,7 +49,8 @@ vi.mock("@codemcp/ade-harnesses", () => ({ "kiro", "opencode" ]), - installSkills: vi.fn().mockResolvedValue(undefined) + installSkills: vi.fn().mockResolvedValue(undefined), + writeInlineSkills: vi.fn().mockResolvedValue([]) })); import * as clack from "@clack/prompts"; diff --git a/packages/cli/src/commands/install.ts b/packages/cli/src/commands/install.ts index 460d646..5d0831d 100644 --- a/packages/cli/src/commands/install.ts +++ b/packages/cli/src/commands/install.ts @@ -3,7 +3,8 @@ import { readLockFile } from "@codemcp/ade-core"; import { getHarnessWriter, getHarnessIds, - installSkills + installSkills, + writeInlineSkills } from "@codemcp/ade-harnesses"; export async function runInstall( @@ -41,6 +42,15 @@ export async function runInstall( } } + const modifiedSkills = await writeInlineSkills(logicalConfig, projectRoot); + if (modifiedSkills.length > 0) { + clack.log.warn( + `The following skills have been locally modified and will NOT be updated:\n` + + modifiedSkills.map((s) => ` - ${s}`).join("\n") + + `\n\nTo use the latest defaults, remove .ade/skills/ and re-run install.` + ); + } + await installSkills(logicalConfig.skills, projectRoot); if (logicalConfig.knowledge_sources.length > 0) { diff --git a/packages/cli/src/commands/setup.spec.ts b/packages/cli/src/commands/setup.spec.ts index 5e43589..97d0cae 100644 --- a/packages/cli/src/commands/setup.spec.ts +++ b/packages/cli/src/commands/setup.spec.ts @@ -51,7 +51,8 @@ vi.mock("@codemcp/ade-harnesses", () => ({ install: vi.fn().mockResolvedValue(undefined) }), getHarnessIds: vi.fn().mockReturnValue(["claude-code"]), - installSkills: vi.fn().mockResolvedValue(undefined) + installSkills: vi.fn().mockResolvedValue(undefined), + writeInlineSkills: vi.fn().mockResolvedValue([]) })); import * as clack from "@clack/prompts"; diff --git a/packages/cli/src/commands/setup.ts b/packages/cli/src/commands/setup.ts index 493a210..95930d2 100644 --- a/packages/cli/src/commands/setup.ts +++ b/packages/cli/src/commands/setup.ts @@ -18,7 +18,8 @@ import { import { allHarnessWriters, getHarnessWriter, - installSkills + installSkills, + writeInlineSkills } from "@codemcp/ade-harnesses"; export async function runSetup( @@ -162,6 +163,15 @@ export async function runSetup( } } + const modifiedSkills = await writeInlineSkills(logicalConfig, projectRoot); + if (modifiedSkills.length > 0) { + clack.log.warn( + `The following skills have been locally modified and will NOT be updated:\n` + + modifiedSkills.map((s) => ` - ${s}`).join("\n") + + `\n\nTo use the latest defaults, remove .ade/skills/ and re-run setup.` + ); + } + await installSkills(logicalConfig.skills, projectRoot); if (logicalConfig.knowledge_sources.length > 0) { diff --git a/packages/harnesses/src/index.ts b/packages/harnesses/src/index.ts index 9087d1e..f2d3643 100644 --- a/packages/harnesses/src/index.ts +++ b/packages/harnesses/src/index.ts @@ -1,5 +1,6 @@ export type { HarnessWriter } from "./types.js"; export { installSkills } from "./skills-installer.js"; +export { writeInlineSkills } from "./util.js"; export { universalWriter } from "./writers/universal.js"; export { claudeCodeWriter } from "./writers/claude-code.js"; diff --git a/packages/harnesses/src/util.ts b/packages/harnesses/src/util.ts index d5e3642..45b8a32 100644 --- a/packages/harnesses/src/util.ts +++ b/packages/harnesses/src/util.ts @@ -185,12 +185,14 @@ export async function writeGitHooks( export async function writeInlineSkills( config: LogicalConfig, projectRoot: string -): Promise { +): Promise { + const modified: string[] = []; + for (const skill of config.skills) { if (!("body" in skill)) continue; const skillDir = join(projectRoot, ".ade", "skills", skill.name); - await mkdir(skillDir, { recursive: true }); + const skillPath = join(skillDir, "SKILL.md"); const frontmatter = [ "---", @@ -199,10 +201,21 @@ export async function writeInlineSkills( "---" ].join("\n"); - await writeFile( - join(skillDir, "SKILL.md"), - `${frontmatter}\n\n${skill.body}\n`, - "utf-8" - ); + const expected = `${frontmatter}\n\n${skill.body}\n`; + + try { + const existing = await readFile(skillPath, "utf-8"); + if (existing !== expected) { + modified.push(skill.name); + continue; + } + } catch { + // File doesn't exist yet — fall through to write + } + + await mkdir(skillDir, { recursive: true }); + await writeFile(skillPath, expected, "utf-8"); } + + return modified; } diff --git a/packages/harnesses/src/writers/claude-code.spec.ts b/packages/harnesses/src/writers/claude-code.spec.ts index fc1efc2..656b17c 100644 --- a/packages/harnesses/src/writers/claude-code.spec.ts +++ b/packages/harnesses/src/writers/claude-code.spec.ts @@ -8,6 +8,7 @@ import type { PermissionPolicy } from "@codemcp/ade-core"; import { claudeCodeWriter } from "./claude-code.js"; +import { writeInlineSkills } from "../util.js"; function autonomyPolicy(profile: AutonomyProfile): PermissionPolicy { switch (profile) { @@ -290,7 +291,7 @@ describe("claudeCodeWriter", () => { }); }); - it("writes inline SKILL.md files", async () => { + it("writes inline SKILL.md files via writeInlineSkills", async () => { const config: LogicalConfig = { mcp_servers: [], instructions: [], @@ -307,7 +308,7 @@ describe("claudeCodeWriter", () => { setup_notes: [] }; - await claudeCodeWriter.install(config, dir); + await writeInlineSkills(config, dir); const skillMd = await readFile( join(dir, ".ade", "skills", "tanstack-architecture", "SKILL.md"), diff --git a/packages/harnesses/src/writers/claude-code.ts b/packages/harnesses/src/writers/claude-code.ts index e0b6609..9f584a7 100644 --- a/packages/harnesses/src/writers/claude-code.ts +++ b/packages/harnesses/src/writers/claude-code.ts @@ -6,7 +6,6 @@ import { writeJson, writeMcpServers, writeAgentMd, - writeInlineSkills, writeGitHooks } from "../util.js"; import { allowsCapability, keepsWebOnAsk } from "../permission-policy.js"; @@ -27,7 +26,6 @@ export const claudeCodeWriter: HarnessWriter = { }); await writeClaudeSettings(config, projectRoot); - await writeInlineSkills(config, projectRoot); await writeGitHooks(config.git_hooks, projectRoot); } };