Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/extension/skills/amico-lab/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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}
Expand Down
2 changes: 1 addition & 1 deletion packages/extension/skills/amico-slack/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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:** `<URL|display text>`
- Example: `<https://github.com/harmoniqs/amico-plugin/pull/49|PR #49>`
- Example: `<https://github.com/harmoniqs/amicode/pull/391|PR #391>`
- Example: `<https://news.fnal.gov/...|Fermilab press release>`
- For raw URLs without labels: `<https://example.com>`

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -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?
Expand Down
12 changes: 8 additions & 4 deletions packages/extension/src/scores/package_skills.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 `<stageRoot>/<name>/SKILL.md` so opencode's
* session: copy each skill's WHOLE dir to `<stageRoot>/<name>/` 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)
Expand Down
18 changes: 17 additions & 1 deletion packages/extension/test/scores/package_skills.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -332,7 +332,7 @@ describe("buildSkillIndexSection", () => {
});

describe("stageOpencodeSkills", () => {
it("copies ONLY the resolved set into <root>/<name>/SKILL.md and returns the root", () => {
it("copies ONLY the resolved set into <root>/<name>/ 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");
Expand All@@ -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("");
Expand Down
Loading