mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 08:00:59 +00:00
feat(coding-agent): add configurable skills directories (#269)
This commit is contained in:
parent
ace3563f0e
commit
70440f7591
9 changed files with 186 additions and 39 deletions
|
|
@ -145,6 +145,36 @@ Skills are discovered from these locations (later wins on name collision):
|
||||||
4. `~/.pi/agent/skills/**/SKILL.md` (Pi user, recursive)
|
4. `~/.pi/agent/skills/**/SKILL.md` (Pi user, recursive)
|
||||||
5. `<cwd>/.pi/skills/**/SKILL.md` (Pi project, recursive)
|
5. `<cwd>/.pi/skills/**/SKILL.md` (Pi project, recursive)
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Configure skill loading in `~/.pi/agent/settings.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"skills": {
|
||||||
|
"enabled": true,
|
||||||
|
"enableCodexUser": true,
|
||||||
|
"enableClaudeUser": true,
|
||||||
|
"enableClaudeProject": true,
|
||||||
|
"enablePiUser": true,
|
||||||
|
"enablePiProject": true,
|
||||||
|
"customDirectories": ["~/my-skills-repo"],
|
||||||
|
"ignoredSkills": ["deprecated-skill"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Setting | Default | Description |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| `enabled` | `true` | Master toggle for all skills |
|
||||||
|
| `enableCodexUser` | `true` | Load from `~/.codex/skills/` |
|
||||||
|
| `enableClaudeUser` | `true` | Load from `~/.claude/skills/` |
|
||||||
|
| `enableClaudeProject` | `true` | Load from `<cwd>/.claude/skills/` |
|
||||||
|
| `enablePiUser` | `true` | Load from `~/.pi/agent/skills/` |
|
||||||
|
| `enablePiProject` | `true` | Load from `<cwd>/.pi/skills/` |
|
||||||
|
| `customDirectories` | `[]` | Additional directories to scan (supports `~` expansion) |
|
||||||
|
| `ignoredSkills` | `[]` | Skill names to exclude |
|
||||||
|
|
||||||
## How Skills Work
|
## How Skills Work
|
||||||
|
|
||||||
1. At startup, pi scans skill locations and extracts names + descriptions
|
1. At startup, pi scans skill locations and extracts names + descriptions
|
||||||
|
|
@ -233,3 +263,5 @@ Settings (`~/.pi/agent/settings.json`):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Use the granular `enable*` flags to disable individual sources (e.g., `enableClaudeUser: false` to skip `~/.claude/skills`).
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import type { BranchEventResult, HookRunner, TurnEndEvent, TurnStartEvent } from
|
||||||
import type { BashExecutionMessage } from "./messages.js";
|
import type { BashExecutionMessage } from "./messages.js";
|
||||||
import { getApiKeyForModel, getAvailableModels } from "./model-config.js";
|
import { getApiKeyForModel, getAvailableModels } from "./model-config.js";
|
||||||
import { loadSessionFromEntries, type SessionManager } from "./session-manager.js";
|
import { loadSessionFromEntries, type SessionManager } from "./session-manager.js";
|
||||||
import type { SettingsManager } from "./settings-manager.js";
|
import type { SettingsManager, SkillsSettings } from "./settings-manager.js";
|
||||||
import { expandSlashCommand, type FileSlashCommand } from "./slash-commands.js";
|
import { expandSlashCommand, type FileSlashCommand } from "./slash-commands.js";
|
||||||
|
|
||||||
/** Session-specific events that extend the core AgentEvent */
|
/** Session-specific events that extend the core AgentEvent */
|
||||||
|
|
@ -55,6 +55,7 @@ export interface AgentSessionConfig {
|
||||||
hookRunner?: HookRunner | null;
|
hookRunner?: HookRunner | null;
|
||||||
/** Custom tools for session lifecycle events */
|
/** Custom tools for session lifecycle events */
|
||||||
customTools?: LoadedCustomTool[];
|
customTools?: LoadedCustomTool[];
|
||||||
|
skillsSettings?: Required<SkillsSettings>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Options for AgentSession.prompt() */
|
/** Options for AgentSession.prompt() */
|
||||||
|
|
@ -148,6 +149,8 @@ export class AgentSession {
|
||||||
// Custom tools for session lifecycle
|
// Custom tools for session lifecycle
|
||||||
private _customTools: LoadedCustomTool[] = [];
|
private _customTools: LoadedCustomTool[] = [];
|
||||||
|
|
||||||
|
private _skillsSettings: Required<SkillsSettings> | undefined;
|
||||||
|
|
||||||
constructor(config: AgentSessionConfig) {
|
constructor(config: AgentSessionConfig) {
|
||||||
this.agent = config.agent;
|
this.agent = config.agent;
|
||||||
this.sessionManager = config.sessionManager;
|
this.sessionManager = config.sessionManager;
|
||||||
|
|
@ -156,6 +159,7 @@ export class AgentSession {
|
||||||
this._fileCommands = config.fileCommands ?? [];
|
this._fileCommands = config.fileCommands ?? [];
|
||||||
this._hookRunner = config.hookRunner ?? null;
|
this._hookRunner = config.hookRunner ?? null;
|
||||||
this._customTools = config.customTools ?? [];
|
this._customTools = config.customTools ?? [];
|
||||||
|
this._skillsSettings = config.skillsSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
@ -485,6 +489,10 @@ export class AgentSession {
|
||||||
return this._queuedMessages;
|
return this._queuedMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get skillsSettings(): Required<SkillsSettings> | undefined {
|
||||||
|
return this._skillsSettings;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abort current operation and wait for agent to become idle.
|
* Abort current operation and wait for agent to become idle.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,13 @@ export interface RetrySettings {
|
||||||
|
|
||||||
export interface SkillsSettings {
|
export interface SkillsSettings {
|
||||||
enabled?: boolean; // default: true
|
enabled?: boolean; // default: true
|
||||||
|
enableCodexUser?: boolean; // default: true
|
||||||
|
enableClaudeUser?: boolean; // default: true
|
||||||
|
enableClaudeProject?: boolean; // default: true
|
||||||
|
enablePiUser?: boolean; // default: true
|
||||||
|
enablePiProject?: boolean; // default: true
|
||||||
|
customDirectories?: string[]; // default: []
|
||||||
|
ignoredSkills?: string[]; // default: []
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TerminalSettings {
|
export interface TerminalSettings {
|
||||||
|
|
@ -253,6 +260,19 @@ export class SettingsManager {
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSkillsSettings(): Required<SkillsSettings> {
|
||||||
|
return {
|
||||||
|
enabled: this.settings.skills?.enabled ?? true,
|
||||||
|
enableCodexUser: this.settings.skills?.enableCodexUser ?? true,
|
||||||
|
enableClaudeUser: this.settings.skills?.enableClaudeUser ?? true,
|
||||||
|
enableClaudeProject: this.settings.skills?.enableClaudeProject ?? true,
|
||||||
|
enablePiUser: this.settings.skills?.enablePiUser ?? true,
|
||||||
|
enablePiProject: this.settings.skills?.enablePiProject ?? true,
|
||||||
|
customDirectories: this.settings.skills?.customDirectories ?? [],
|
||||||
|
ignoredSkills: this.settings.skills?.ignoredSkills ?? [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
getShowImages(): boolean {
|
getShowImages(): boolean {
|
||||||
return this.settings.terminal?.showImages ?? true;
|
return this.settings.terminal?.showImages ?? true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { existsSync, readdirSync, readFileSync } from "fs";
|
||||||
import { homedir } from "os";
|
import { homedir } from "os";
|
||||||
import { basename, dirname, join, resolve } from "path";
|
import { basename, dirname, join, resolve } from "path";
|
||||||
import { CONFIG_DIR_NAME } from "../config.js";
|
import { CONFIG_DIR_NAME } from "../config.js";
|
||||||
|
import type { SkillsSettings } from "./settings-manager.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard frontmatter fields per Agent Skills spec.
|
* Standard frontmatter fields per Agent Skills spec.
|
||||||
|
|
@ -315,7 +316,17 @@ function escapeXml(str: string): string {
|
||||||
* Load skills from all configured locations.
|
* Load skills from all configured locations.
|
||||||
* Returns skills and any validation warnings.
|
* Returns skills and any validation warnings.
|
||||||
*/
|
*/
|
||||||
export function loadSkills(): LoadSkillsResult {
|
export function loadSkills(options: SkillsSettings = {}): LoadSkillsResult {
|
||||||
|
const {
|
||||||
|
enableCodexUser = true,
|
||||||
|
enableClaudeUser = true,
|
||||||
|
enableClaudeProject = true,
|
||||||
|
enablePiUser = true,
|
||||||
|
enablePiProject = true,
|
||||||
|
customDirectories = [],
|
||||||
|
ignoredSkills = [],
|
||||||
|
} = options;
|
||||||
|
|
||||||
const skillMap = new Map<string, Skill>();
|
const skillMap = new Map<string, Skill>();
|
||||||
const allWarnings: SkillWarning[] = [];
|
const allWarnings: SkillWarning[] = [];
|
||||||
const collisionWarnings: SkillWarning[] = [];
|
const collisionWarnings: SkillWarning[] = [];
|
||||||
|
|
@ -323,6 +334,9 @@ export function loadSkills(): LoadSkillsResult {
|
||||||
function addSkills(result: LoadSkillsResult) {
|
function addSkills(result: LoadSkillsResult) {
|
||||||
allWarnings.push(...result.warnings);
|
allWarnings.push(...result.warnings);
|
||||||
for (const skill of result.skills) {
|
for (const skill of result.skills) {
|
||||||
|
if (ignoredSkills.includes(skill.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const existing = skillMap.get(skill.name);
|
const existing = skillMap.get(skill.name);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
collisionWarnings.push({
|
collisionWarnings.push({
|
||||||
|
|
@ -335,23 +349,24 @@ export function loadSkills(): LoadSkillsResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Codex: recursive
|
if (enableCodexUser) {
|
||||||
const codexUserDir = join(homedir(), ".codex", "skills");
|
addSkills(loadSkillsFromDirInternal(join(homedir(), ".codex", "skills"), "codex-user", "recursive"));
|
||||||
addSkills(loadSkillsFromDirInternal(codexUserDir, "codex-user", "recursive"));
|
}
|
||||||
|
if (enableClaudeUser) {
|
||||||
// Claude: single level only
|
addSkills(loadSkillsFromDirInternal(join(homedir(), ".claude", "skills"), "claude-user", "claude"));
|
||||||
const claudeUserDir = join(homedir(), ".claude", "skills");
|
}
|
||||||
addSkills(loadSkillsFromDirInternal(claudeUserDir, "claude-user", "claude"));
|
if (enableClaudeProject) {
|
||||||
|
addSkills(loadSkillsFromDirInternal(resolve(process.cwd(), ".claude", "skills"), "claude-project", "claude"));
|
||||||
const claudeProjectDir = resolve(process.cwd(), ".claude", "skills");
|
}
|
||||||
addSkills(loadSkillsFromDirInternal(claudeProjectDir, "claude-project", "claude"));
|
if (enablePiUser) {
|
||||||
|
addSkills(loadSkillsFromDirInternal(join(homedir(), CONFIG_DIR_NAME, "agent", "skills"), "user", "recursive"));
|
||||||
// Pi: recursive
|
}
|
||||||
const globalSkillsDir = join(homedir(), CONFIG_DIR_NAME, "agent", "skills");
|
if (enablePiProject) {
|
||||||
addSkills(loadSkillsFromDirInternal(globalSkillsDir, "user", "recursive"));
|
addSkills(loadSkillsFromDirInternal(resolve(process.cwd(), CONFIG_DIR_NAME, "skills"), "project", "recursive"));
|
||||||
|
}
|
||||||
const projectSkillsDir = resolve(process.cwd(), CONFIG_DIR_NAME, "skills");
|
for (const customDir of customDirectories) {
|
||||||
addSkills(loadSkillsFromDirInternal(projectSkillsDir, "project", "recursive"));
|
addSkills(loadSkillsFromDirInternal(customDir.replace(/^~(?=$|[\\/])/, homedir()), "custom", "recursive"));
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
skills: Array.from(skillMap.values()),
|
skills: Array.from(skillMap.values()),
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import chalk from "chalk";
|
||||||
import { existsSync, readFileSync } from "fs";
|
import { existsSync, readFileSync } from "fs";
|
||||||
import { join, resolve } from "path";
|
import { join, resolve } from "path";
|
||||||
import { getAgentDir, getDocsPath, getReadmePath } from "../config.js";
|
import { getAgentDir, getDocsPath, getReadmePath } from "../config.js";
|
||||||
|
import type { SkillsSettings } from "./settings-manager.js";
|
||||||
import { formatSkillsForPrompt, loadSkills } from "./skills.js";
|
import { formatSkillsForPrompt, loadSkills } from "./skills.js";
|
||||||
import type { ToolName } from "./tools/index.js";
|
import type { ToolName } from "./tools/index.js";
|
||||||
|
|
||||||
|
|
@ -109,12 +110,12 @@ export interface BuildSystemPromptOptions {
|
||||||
customPrompt?: string;
|
customPrompt?: string;
|
||||||
selectedTools?: ToolName[];
|
selectedTools?: ToolName[];
|
||||||
appendSystemPrompt?: string;
|
appendSystemPrompt?: string;
|
||||||
skillsEnabled?: boolean;
|
skillsSettings?: SkillsSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Build the system prompt with tools, guidelines, and context */
|
/** Build the system prompt with tools, guidelines, and context */
|
||||||
export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): string {
|
export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): string {
|
||||||
const { customPrompt, selectedTools, appendSystemPrompt, skillsEnabled = true } = options;
|
const { customPrompt, selectedTools, appendSystemPrompt, skillsSettings } = options;
|
||||||
const resolvedCustomPrompt = resolvePromptInput(customPrompt, "system prompt");
|
const resolvedCustomPrompt = resolvePromptInput(customPrompt, "system prompt");
|
||||||
const resolvedAppendPrompt = resolvePromptInput(appendSystemPrompt, "append system prompt");
|
const resolvedAppendPrompt = resolvePromptInput(appendSystemPrompt, "append system prompt");
|
||||||
|
|
||||||
|
|
@ -151,8 +152,8 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
||||||
|
|
||||||
// Append skills section (only if read tool is available)
|
// Append skills section (only if read tool is available)
|
||||||
const customPromptHasRead = !selectedTools || selectedTools.includes("read");
|
const customPromptHasRead = !selectedTools || selectedTools.includes("read");
|
||||||
if (skillsEnabled && customPromptHasRead) {
|
if (skillsSettings?.enabled !== false && customPromptHasRead) {
|
||||||
const { skills } = loadSkills();
|
const { skills } = loadSkills(skillsSettings ?? {});
|
||||||
prompt += formatSkillsForPrompt(skills);
|
prompt += formatSkillsForPrompt(skills);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -257,8 +258,8 @@ Documentation:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append skills section (only if read tool is available)
|
// Append skills section (only if read tool is available)
|
||||||
if (skillsEnabled && hasRead) {
|
if (skillsSettings?.enabled !== false && hasRead) {
|
||||||
const { skills } = loadSkills();
|
const { skills } = loadSkills(skillsSettings ?? {});
|
||||||
prompt += formatSkillsForPrompt(skills);
|
prompt += formatSkillsForPrompt(skills);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,7 @@ export {
|
||||||
type RetrySettings,
|
type RetrySettings,
|
||||||
type Settings,
|
type Settings,
|
||||||
SettingsManager,
|
SettingsManager,
|
||||||
|
type SkillsSettings,
|
||||||
} from "./core/settings-manager.js";
|
} from "./core/settings-manager.js";
|
||||||
// Skills
|
// Skills
|
||||||
export {
|
export {
|
||||||
|
|
|
||||||
|
|
@ -285,12 +285,15 @@ export async function main(args: string[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build system prompt
|
// Build system prompt
|
||||||
const skillsEnabled = !parsed.noSkills && settingsManager.getSkillsEnabled();
|
const skillsSettings = settingsManager.getSkillsSettings();
|
||||||
|
if (parsed.noSkills) {
|
||||||
|
skillsSettings.enabled = false;
|
||||||
|
}
|
||||||
const systemPrompt = buildSystemPrompt({
|
const systemPrompt = buildSystemPrompt({
|
||||||
customPrompt: parsed.systemPrompt,
|
customPrompt: parsed.systemPrompt,
|
||||||
selectedTools: parsed.tools,
|
selectedTools: parsed.tools,
|
||||||
appendSystemPrompt: parsed.appendSystemPrompt,
|
appendSystemPrompt: parsed.appendSystemPrompt,
|
||||||
skillsEnabled,
|
skillsSettings,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle session restoration
|
// Handle session restoration
|
||||||
|
|
@ -440,6 +443,7 @@ export async function main(args: string[]) {
|
||||||
fileCommands,
|
fileCommands,
|
||||||
hookRunner,
|
hookRunner,
|
||||||
customTools: loadedCustomTools,
|
customTools: loadedCustomTools,
|
||||||
|
skillsSettings,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Route to appropriate mode
|
// Route to appropriate mode
|
||||||
|
|
|
||||||
|
|
@ -314,18 +314,23 @@ export class InteractiveMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show loaded skills
|
// Show loaded skills
|
||||||
const { skills, warnings: skillWarnings } = loadSkills();
|
const skillsSettings = this.session.skillsSettings;
|
||||||
if (skills.length > 0) {
|
if (skillsSettings?.enabled !== false) {
|
||||||
const skillList = skills.map((s) => theme.fg("dim", ` ${s.filePath}`)).join("\n");
|
const { skills, warnings: skillWarnings } = loadSkills(skillsSettings ?? {});
|
||||||
this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded skills:\n") + skillList, 0, 0));
|
if (skills.length > 0) {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
const skillList = skills.map((s) => theme.fg("dim", ` ${s.filePath}`)).join("\n");
|
||||||
}
|
this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded skills:\n") + skillList, 0, 0));
|
||||||
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
|
}
|
||||||
|
|
||||||
// Show skill warnings if any
|
// Show skill warnings if any
|
||||||
if (skillWarnings.length > 0) {
|
if (skillWarnings.length > 0) {
|
||||||
const warningList = skillWarnings.map((w) => theme.fg("warning", ` ${w.skillPath}: ${w.message}`)).join("\n");
|
const warningList = skillWarnings
|
||||||
this.chatContainer.addChild(new Text(theme.fg("warning", "Skill warnings:\n") + warningList, 0, 0));
|
.map((w) => theme.fg("warning", ` ${w.skillPath}: ${w.message}`))
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
.join("\n");
|
||||||
|
this.chatContainer.addChild(new Text(theme.fg("warning", "Skill warnings:\n") + warningList, 0, 0));
|
||||||
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show loaded custom tools
|
// Show loaded custom tools
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
import { homedir } from "os";
|
||||||
import { join, resolve } from "path";
|
import { join, resolve } from "path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { formatSkillsForPrompt, loadSkillsFromDir, type Skill } from "../src/core/skills.js";
|
import { formatSkillsForPrompt, loadSkills, loadSkillsFromDir, type Skill } from "../src/core/skills.js";
|
||||||
|
|
||||||
const fixturesDir = resolve(__dirname, "fixtures/skills");
|
const fixturesDir = resolve(__dirname, "fixtures/skills");
|
||||||
const collisionFixturesDir = resolve(__dirname, "fixtures/skills-collision");
|
const collisionFixturesDir = resolve(__dirname, "fixtures/skills-collision");
|
||||||
|
|
@ -230,6 +231,66 @@ describe("skills", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("loadSkills with options", () => {
|
||||||
|
it("should load from customDirectories only when built-ins disabled", () => {
|
||||||
|
const { skills } = loadSkills({
|
||||||
|
enableCodexUser: false,
|
||||||
|
enableClaudeUser: false,
|
||||||
|
enableClaudeProject: false,
|
||||||
|
enablePiUser: false,
|
||||||
|
enablePiProject: false,
|
||||||
|
customDirectories: [fixturesDir],
|
||||||
|
});
|
||||||
|
expect(skills.length).toBeGreaterThan(0);
|
||||||
|
expect(skills.every((s) => s.source === "custom")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter out ignoredSkills", () => {
|
||||||
|
const { skills } = loadSkills({
|
||||||
|
enableCodexUser: false,
|
||||||
|
enableClaudeUser: false,
|
||||||
|
enableClaudeProject: false,
|
||||||
|
enablePiUser: false,
|
||||||
|
enablePiProject: false,
|
||||||
|
customDirectories: [join(fixturesDir, "valid-skill")],
|
||||||
|
ignoredSkills: ["valid-skill"],
|
||||||
|
});
|
||||||
|
expect(skills).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should expand ~ in customDirectories", () => {
|
||||||
|
const homeSkillsDir = join(homedir(), ".pi/agent/skills");
|
||||||
|
const { skills: withTilde } = loadSkills({
|
||||||
|
enableCodexUser: false,
|
||||||
|
enableClaudeUser: false,
|
||||||
|
enableClaudeProject: false,
|
||||||
|
enablePiUser: false,
|
||||||
|
enablePiProject: false,
|
||||||
|
customDirectories: ["~/.pi/agent/skills"],
|
||||||
|
});
|
||||||
|
const { skills: withoutTilde } = loadSkills({
|
||||||
|
enableCodexUser: false,
|
||||||
|
enableClaudeUser: false,
|
||||||
|
enableClaudeProject: false,
|
||||||
|
enablePiUser: false,
|
||||||
|
enablePiProject: false,
|
||||||
|
customDirectories: [homeSkillsDir],
|
||||||
|
});
|
||||||
|
expect(withTilde.length).toBe(withoutTilde.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return empty when all sources disabled and no custom dirs", () => {
|
||||||
|
const { skills } = loadSkills({
|
||||||
|
enableCodexUser: false,
|
||||||
|
enableClaudeUser: false,
|
||||||
|
enableClaudeProject: false,
|
||||||
|
enablePiUser: false,
|
||||||
|
enablePiProject: false,
|
||||||
|
});
|
||||||
|
expect(skills).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("collision handling", () => {
|
describe("collision handling", () => {
|
||||||
it("should detect name collisions and keep first skill", () => {
|
it("should detect name collisions and keep first skill", () => {
|
||||||
// Load from first directory
|
// Load from first directory
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue