diff --git a/packages/extension/skills/amico-lab/SKILL.md b/packages/extension/skills/amico-lab/SKILL.md index ac29ca04..b9ccda7c 100644 --- a/packages/extension/skills/amico-lab/SKILL.md +++ b/packages/extension/skills/amico-lab/SKILL.md @@ -46,7 +46,7 @@ Before dispatching an experiment, acquire a lock to prevent concurrent writes to ### Acquiring a lock -Create `~/.amico/ops/locks/devices/{name}.lock` with contents: +Create `~/.amico/ops/locks/devices/{name}.lock` (mkdir the parent first: `mkdir -p ~/.amico/ops/locks/devices`) with contents: ``` session_id: {uuid} diff --git a/packages/extension/skills/amico-slack/SKILL.md b/packages/extension/skills/amico-slack/SKILL.md index 4cab1624..36333610 100644 --- a/packages/extension/skills/amico-slack/SKILL.md +++ b/packages/extension/skills/amico-slack/SKILL.md @@ -21,7 +21,7 @@ Use this skill when reading channel discussions, sending updates to Harmoniqs Sl 2. **Hyperlinks in Slack mrkdwn (MANDATORY)**: - Standard markdown links `[text](url)` **DO NOT WORK** in Slack! - **Always use Slack mrkdwn link format:** `` - - Example: `` + - Example: `` - Example: `` - For raw URLs without labels: `` diff --git a/packages/extension/skills/improve-codebase-architecture/SKILL.md b/packages/extension/skills/improve-codebase-architecture/SKILL.md index 352ac3b2..74732928 100644 --- a/packages/extension/skills/improve-codebase-architecture/SKILL.md +++ b/packages/extension/skills/improve-codebase-architecture/SKILL.md @@ -36,7 +36,7 @@ This skill is _informed_ by the project's domain model. The domain language give Read the project's domain glossary and any ADRs in the area you're touching first. -Then use the Agent tool with `subagent_type=Explore` to walk the codebase. Don't follow rigid heuristics — explore organically and note where you experience friction: +Then dispatch an exploration subagent to walk the codebase (on opencode: the Task tool with `subagent_type=explore`; on Claude Code: the Agent tool with `subagent_type=Explore`). Don't follow rigid heuristics — explore organically and note where you experience friction: - Where does understanding one concept require bouncing between many small modules? - Where are modules **shallow** — interface nearly as complex as the implementation? diff --git a/packages/extension/src/scores/package_skills.ts b/packages/extension/src/scores/package_skills.ts index 9a77cee3..dced0173 100644 --- a/packages/extension/src/scores/package_skills.ts +++ b/packages/extension/src/scores/package_skills.ts @@ -178,22 +178,26 @@ export function resolveLibrarySkills(roots: LibraryRootSpec[]): SkillIndexEntry[ } /** Stage the resolved (guarded) skill set as opencode-native skills for this - * session: copy each SKILL.md to `//SKILL.md` so opencode's + * session: copy each skill's WHOLE dir to `//` so opencode's * loader — pointed HERE via config `skills.paths` (an absolute dir) — registers * exactly this set and no more. We must NOT point `skills.paths` at a library * root: opencode scans it recursively for `**​/SKILL.md`, which would leak the * ~50 process skills (the exact guard from spec §3). Folder name = frontmatter * `name`, satisfying opencode's name-matches-folder rule; content is copied * verbatim (opencode ignores the extra `agents:` field — verified 2026-07-04). - * Returns the stage root, or "" if nothing was staged (→ no `skills.paths`). */ + * Companions (tdd's craft docs, the physics references/ dirs) ride along: + * SKILL.md relative links must resolve inside the stage copy, and the source + * dir is already fully granted to the agent (skillGrants), so staging them + * exposes nothing new (amicode#393). Still ONLY the resolved set — one dir + * per entry, nothing else. Returns the stage root, or "" if nothing was + * staged (→ no `skills.paths`). */ export function stageOpencodeSkills(stageRoot: string, entries: SkillIndexEntry[]): string { if (entries.length === 0) return ""; let staged = 0; for (const e of entries) { try { const dir = path.join(stageRoot, e.name); - fs.mkdirSync(dir, { recursive: true }); - fs.copyFileSync(e.path, path.join(dir, "SKILL.md")); + fs.cpSync(path.dirname(e.path), dir, { recursive: true }); staged++; } catch (err) { console.warn(`amicode: could not stage skill ${e.name} for opencode: ${err}`); // never dead-end (spec §9) diff --git a/packages/extension/test/scores/package_skills.test.ts b/packages/extension/test/scores/package_skills.test.ts index b5841784..0e8f226d 100644 --- a/packages/extension/test/scores/package_skills.test.ts +++ b/packages/extension/test/scores/package_skills.test.ts @@ -332,7 +332,7 @@ describe("buildSkillIndexSection", () => { }); describe("stageOpencodeSkills", () => { - it("copies ONLY the resolved set into //SKILL.md and returns the root", () => { + it("copies ONLY the resolved set into // and returns the root", () => { const src = fs.mkdtempSync(path.join(os.tmpdir(), "skillsrc-")); fs.mkdirSync(path.join(src, "atoms")); fs.writeFileSync(path.join(src, "atoms", "SKILL.md"), "---\nname: atoms\ndescription: d\nagents: [x]\n---\nbody\n"); @@ -348,6 +348,22 @@ describe("stageOpencodeSkills", () => { // nothing else staged — only the one resolved entry's dir exists expect(fs.readdirSync(stageRoot).sort()).toEqual(["atoms"]); }); + it("stages companion files so SKILL.md relative links resolve (amicode#393)", () => { + const src = fs.mkdtempSync(path.join(os.tmpdir(), "skillsrc-")); + fs.mkdirSync(path.join(src, "tdd")); + fs.mkdirSync(path.join(src, "tdd", "references")); + fs.writeFileSync(path.join(src, "tdd", "SKILL.md"), "---\nname: tdd\ndescription: d\n---\nSee [tests.md](tests.md).\n"); + fs.writeFileSync(path.join(src, "tdd", "tests.md"), "# tests\n"); + fs.writeFileSync(path.join(src, "tdd", "references", "r.md"), "# r\n"); + const stageRoot = fs.mkdtempSync(path.join(os.tmpdir(), "stage-")); + stageOpencodeSkills(stageRoot, [ + { source: "library", name: "tdd", description: "d", path: path.join(src, "tdd", "SKILL.md") }, + ]); + expect(fs.existsSync(path.join(stageRoot, "tdd", "tests.md"))).toBe(true); + expect(fs.existsSync(path.join(stageRoot, "tdd", "references", "r.md"))).toBe(true); + // still only the resolved set at the stage root + expect(fs.readdirSync(stageRoot)).toEqual(["tdd"]); + }); it("empty set → '' (no skills.paths registered)", () => { const stageRoot = fs.mkdtempSync(path.join(os.tmpdir(), "stage-")); expect(stageOpencodeSkills(stageRoot, [])).toBe("");