From e72fec50197979a458319d1ac49f3c1a5af156d9 Mon Sep 17 00:00:00 2001 From: dyoshikawa Date: Tue, 16 Jun 2026 11:59:22 -0700 Subject: [PATCH] feat(augmentcode): discover subagents and skills from the shared .agents/ tree on import Auggie CLI 0.16.0+ discovers subagents from .agents/ and skills from .agents/skills/ in addition to .augment/. Add these as import-only roots (importDirPaths for subagents, alternativeSkillRoots for skills), mirroring the junie and rovodev precedents. Generation still targets .augment/. Refs #1890 Co-Authored-By: Claude Opus 4.8 (1M context) --- src/constants/augmentcode-paths.ts | 8 +++++ src/features/skills/augmentcode-skill.test.ts | 31 +++++++++++++++++++ src/features/skills/augmentcode-skill.ts | 11 ++++++- .../subagents/augmentcode-subagent.test.ts | 25 ++++++++++++++- .../subagents/augmentcode-subagent.ts | 18 ++++++++--- 5 files changed, 87 insertions(+), 6 deletions(-) diff --git a/src/constants/augmentcode-paths.ts b/src/constants/augmentcode-paths.ts index 18a0c1c73..a86d45ee9 100644 --- a/src/constants/augmentcode-paths.ts +++ b/src/constants/augmentcode-paths.ts @@ -4,6 +4,14 @@ export const AUGMENTCODE_DIR = ".augment"; export const AUGMENTCODE_COMMANDS_DIR_PATH = join(AUGMENTCODE_DIR, "commands"); export const AUGMENTCODE_SKILLS_DIR_PATH = join(AUGMENTCODE_DIR, "skills"); export const AUGMENTCODE_AGENTS_DIR_PATH = join(AUGMENTCODE_DIR, "agents"); +// Auggie CLI 0.16.0+ also discovers subagents and skills from the cross-tool +// `.agents/` directory (project `.agents/` and user `~/.agents/`) in addition to +// `.augment/`. These are import-only discovery roots; generation still targets +// `.augment/agents/` and `.augment/skills/`. +// @see https://www.augmentcode.com/changelog/auggie-cli-0-16-0-release-notes +// @see https://docs.augmentcode.com/cli/skills +export const AUGMENTCODE_ALT_AGENTS_DIR_PATH = ".agents"; +export const AUGMENTCODE_AGENTS_SKILLS_DIR_PATH = join(".agents", "skills"); export const AUGMENTCODE_SETTINGS_FILE_NAME = "settings.json"; export const AUGMENTCODE_IGNORE_FILE_NAME = ".augmentignore"; export const AUGMENTCODE_LEGACY_RULE_FILE_NAME = ".augment-guidelines"; diff --git a/src/features/skills/augmentcode-skill.test.ts b/src/features/skills/augmentcode-skill.test.ts index f537771a5..da7bf9b69 100644 --- a/src/features/skills/augmentcode-skill.test.ts +++ b/src/features/skills/augmentcode-skill.test.ts @@ -33,6 +33,15 @@ describe("AugmentcodeSkill", () => { join(".augment", "skills"), ); }); + + it("should expose .agents/skills as an alternative discovery root", () => { + expect(AugmentcodeSkill.getSettablePaths().alternativeSkillRoots).toEqual([ + join(".agents", "skills"), + ]); + expect(AugmentcodeSkill.getSettablePaths({ global: true }).alternativeSkillRoots).toEqual([ + join(".agents", "skills"), + ]); + }); }); describe("fromRulesyncSkill", () => { @@ -128,6 +137,28 @@ This is the skill body.`; }); }); + it("should load a skill from the .agents/skills import root when relativeDirPath is set", async () => { + const skillDir = join(testDir, ".agents", "skills", "shared-skill"); + await ensureDir(skillDir); + const skillContent = `--- +name: shared-skill +description: Discovered from .agents/skills +--- + +Shared skill body.`; + await writeFileContent(join(skillDir, SKILL_FILE_NAME), skillContent); + + const skill = await AugmentcodeSkill.fromDir({ + outputRoot: testDir, + relativeDirPath: join(".agents", "skills"), + dirName: "shared-skill", + }); + + expect(skill.getRelativeDirPath()).toBe(join(".agents", "skills")); + expect(skill.getFrontmatter().name).toBe("shared-skill"); + expect(skill.getBody()).toBe("Shared skill body."); + }); + it("should throw when frontmatter is missing required fields", async () => { const skillDir = join(testDir, ".augment", "skills", "bad"); await ensureDir(skillDir); diff --git a/src/features/skills/augmentcode-skill.ts b/src/features/skills/augmentcode-skill.ts index 15559e290..88c89aa0e 100644 --- a/src/features/skills/augmentcode-skill.ts +++ b/src/features/skills/augmentcode-skill.ts @@ -2,7 +2,10 @@ import { join } from "node:path"; import { z } from "zod/mini"; -import { AUGMENTCODE_SKILLS_DIR_PATH } from "../../constants/augmentcode-paths.js"; +import { + AUGMENTCODE_AGENTS_SKILLS_DIR_PATH, + AUGMENTCODE_SKILLS_DIR_PATH, +} from "../../constants/augmentcode-paths.js"; import { SKILL_FILE_NAME } from "../../constants/general.js"; import { RULESYNC_SKILLS_RELATIVE_DIR_PATH } from "../../constants/rulesync-paths.js"; import { ValidationResult } from "../../types/ai-dir.js"; @@ -85,8 +88,14 @@ export class AugmentcodeSkill extends ToolSkill { // modes. The actual location differs based on outputRoot: // - Project mode: {process.cwd()}/.augment/skills/ // - Global mode: {getHomeDirectory()}/.augment/skills/ + // + // Auggie CLI 0.16.0+ additionally discovers skills from the cross-tool + // `.agents/skills/` directory (project `.agents/skills/` and user + // `~/.agents/skills/`). That is an import-only root: generation still writes + // to `.augment/skills/`. return { relativeDirPath: AUGMENTCODE_SKILLS_DIR_PATH, + alternativeSkillRoots: [AUGMENTCODE_AGENTS_SKILLS_DIR_PATH], }; } diff --git a/src/features/subagents/augmentcode-subagent.test.ts b/src/features/subagents/augmentcode-subagent.test.ts index 8a79af62c..bcfd94deb 100644 --- a/src/features/subagents/augmentcode-subagent.test.ts +++ b/src/features/subagents/augmentcode-subagent.test.ts @@ -26,12 +26,14 @@ describe("AugmentcodeSubagent", () => { }); describe("getSettablePaths", () => { - it("should return .augment/agents for both project and global mode", () => { + it("should return .augment/agents with .agents as an import-only root for both modes", () => { expect(AugmentcodeSubagent.getSettablePaths()).toEqual({ relativeDirPath: join(".augment", "agents"), + importDirPaths: [".agents"], }); expect(AugmentcodeSubagent.getSettablePaths({ global: true })).toEqual({ relativeDirPath: join(".augment", "agents"), + importDirPaths: [".agents"], }); }); }); @@ -179,6 +181,27 @@ Review carefully.`; expect(subagent.getBody()).toBe("Review carefully."); }); + it("should load a subagent from the .agents/ import root when relativeDirPath is set", async () => { + const fileContent = `--- +name: planner +description: Plans work +--- + +Plan ahead.`; + await writeFileContent(join(testDir, ".agents", "planner.md"), fileContent); + + const subagent = await AugmentcodeSubagent.fromFile({ + outputRoot: testDir, + relativeDirPath: ".agents", + relativeFilePath: "planner.md", + validate: true, + }); + + expect(subagent.getRelativeDirPath()).toBe(".agents"); + expect(subagent.getFrontmatter().name).toBe("planner"); + expect(subagent.getBody()).toBe("Plan ahead."); + }); + it("should throw for invalid frontmatter", async () => { await writeFileContent( join(testDir, ".augment", "agents", "bad.md"), diff --git a/src/features/subagents/augmentcode-subagent.ts b/src/features/subagents/augmentcode-subagent.ts index 1a0c449c1..26071a590 100644 --- a/src/features/subagents/augmentcode-subagent.ts +++ b/src/features/subagents/augmentcode-subagent.ts @@ -2,7 +2,10 @@ import { join } from "node:path"; import { z } from "zod/mini"; -import { AUGMENTCODE_AGENTS_DIR_PATH } from "../../constants/augmentcode-paths.js"; +import { + AUGMENTCODE_AGENTS_DIR_PATH, + AUGMENTCODE_ALT_AGENTS_DIR_PATH, +} from "../../constants/augmentcode-paths.js"; import { RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH } from "../../constants/rulesync-paths.js"; import { AiFileParams, ValidationResult } from "../../types/ai-file.js"; import { formatError } from "../../utils/error.js"; @@ -71,8 +74,12 @@ export class AugmentcodeSubagent extends ToolSubagent { } static getSettablePaths(_options: { global?: boolean } = {}): ToolSubagentSettablePaths { + // Auggie CLI additionally discovers subagents from the cross-tool `.agents/` + // directory (project `.agents/` and user `~/.agents/`). That is an + // import-only root: generation still writes to `.augment/agents/`. return { relativeDirPath: AUGMENTCODE_AGENTS_DIR_PATH, + importDirPaths: [AUGMENTCODE_ALT_AGENTS_DIR_PATH], }; } @@ -166,12 +173,15 @@ export class AugmentcodeSubagent extends ToolSubagent { static async fromFile({ outputRoot = process.cwd(), + relativeDirPath, relativeFilePath, validate = true, global = false, }: ToolSubagentFromFileParams): Promise { - const paths = this.getSettablePaths({ global }); - const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath); + // Honor an explicit discovery root (e.g. the `.agents/` import root) when + // provided; otherwise fall back to the canonical `.augment/agents/` location. + const dirPath = relativeDirPath ?? this.getSettablePaths({ global }).relativeDirPath; + const filePath = join(outputRoot, dirPath, relativeFilePath); const fileContent = await readFileContent(filePath); const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath); @@ -182,7 +192,7 @@ export class AugmentcodeSubagent extends ToolSubagent { return new AugmentcodeSubagent({ outputRoot, - relativeDirPath: paths.relativeDirPath, + relativeDirPath: dirPath, relativeFilePath, frontmatter: result.data, body: content.trim(),