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
8 changes: 8 additions & 0 deletions src/constants/augmentcode-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
31 changes: 31 additions & 0 deletions src/features/skills/augmentcode-skill.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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);
Expand Down
11 changes: 10 additions & 1 deletion src/features/skills/augmentcode-skill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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],
};
}

Expand Down
25 changes: 24 additions & 1 deletion src/features/subagents/augmentcode-subagent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
});
});
});
Expand Down Expand Up @@ -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"),
Expand Down
18 changes: 14 additions & 4 deletions src/features/subagents/augmentcode-subagent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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],
};
}

Expand Down Expand Up @@ -166,12 +173,15 @@ export class AugmentcodeSubagent extends ToolSubagent {

static async fromFile({
outputRoot = process.cwd(),
relativeDirPath,
relativeFilePath,
validate = true,
global = false,
}: ToolSubagentFromFileParams): Promise<AugmentcodeSubagent> {
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);

Expand All @@ -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(),
Expand Down