From 3f197db192ba4633c9e69a49573b0b06fa91672a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Mar 2026 10:01:47 +0000 Subject: [PATCH 1/2] feat: preserve user-modified local skills during setup/install Before writing SKILL.md files to .ade/skills/, check if an existing file differs from the default content. If it does, skip overwriting and warn the user that their modified version will be used, with instructions to remove .ade/skills/ to restore defaults. Also moves writeInlineSkills() to be called at the top level in setup/install commands (like installSkills) so the return value can drive user-facing clack warnings. https://claude.ai/code/session_01Sf3uGYRodvenEj8NWyzVcM --- packages/cli/src/commands/install.ts | 12 ++++++++- packages/cli/src/commands/setup.ts | 12 ++++++++- packages/harnesses/src/index.ts | 1 + packages/harnesses/src/util.ts | 27 ++++++++++++++----- packages/harnesses/src/writers/claude-code.ts | 2 -- 5 files changed, 43 insertions(+), 11 deletions(-) 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.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.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); } }; From a273a48a04176db95ac1e9bdae14683ed736309c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Mar 2026 12:19:57 +0000 Subject: [PATCH 2/2] test: update mocks and test for writeInlineSkills move to CLI level - Add writeInlineSkills to @codemcp/ade-harnesses mock in setup/install specs - Update claude-code.spec.ts to test writeInlineSkills directly rather than via claudeCodeWriter.install(), reflecting the move of skill writing responsibility to the CLI commands https://claude.ai/code/session_01Sf3uGYRodvenEj8NWyzVcM --- packages/cli/src/commands/install.spec.ts | 3 ++- packages/cli/src/commands/setup.spec.ts | 3 ++- packages/harnesses/src/writers/claude-code.spec.ts | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) 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/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/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"),