mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 07:04:25 +00:00
coding-agent, mom: add skills API export and mom skills auto-discovery
coding-agent: - Export loadSkillsFromDir, formatSkillsForPrompt, and related types - Refactor skills.ts to expose public API mom: - Add skills auto-discovery from workspace/skills and channel/skills - Fix skill loading to use host paths (not Docker container paths) - Update README and system prompt with SKILL.md format docs
This commit is contained in:
parent
439f55b0eb
commit
e707ac4cd0
8 changed files with 175 additions and 65 deletions
|
|
@ -8,14 +8,12 @@ export interface SkillFrontmatter {
|
|||
description: string;
|
||||
}
|
||||
|
||||
export type SkillSource = "user" | "project" | "claude-user" | "claude-project" | "codex-user";
|
||||
|
||||
export interface Skill {
|
||||
name: string;
|
||||
description: string;
|
||||
filePath: string;
|
||||
baseDir: string;
|
||||
source: SkillSource;
|
||||
source: string;
|
||||
}
|
||||
|
||||
type SkillFormat = "recursive" | "claude";
|
||||
|
|
@ -60,9 +58,27 @@ function parseFrontmatter(content: string): { frontmatter: SkillFrontmatter; bod
|
|||
return { frontmatter, body };
|
||||
}
|
||||
|
||||
function loadSkillsFromDir(
|
||||
export interface LoadSkillsFromDirOptions {
|
||||
/** Directory to scan for skills */
|
||||
dir: string;
|
||||
/** Source identifier for these skills */
|
||||
source: string;
|
||||
/** Use colon-separated path names (e.g., db:migrate) instead of simple directory name */
|
||||
useColonPath?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load skills from a directory recursively.
|
||||
* Skills are directories containing a SKILL.md file with frontmatter including a description.
|
||||
*/
|
||||
export function loadSkillsFromDir(options: LoadSkillsFromDirOptions, subdir: string = ""): Skill[] {
|
||||
const { dir, source, useColonPath = false } = options;
|
||||
return loadSkillsFromDirInternal(dir, source, "recursive", useColonPath, subdir);
|
||||
}
|
||||
|
||||
function loadSkillsFromDirInternal(
|
||||
dir: string,
|
||||
source: SkillSource,
|
||||
source: string,
|
||||
format: SkillFormat,
|
||||
useColonPath: boolean = false,
|
||||
subdir: string = "",
|
||||
|
|
@ -91,7 +107,7 @@ function loadSkillsFromDir(
|
|||
// Recursive format: scan directories, look for SKILL.md files
|
||||
if (entry.isDirectory()) {
|
||||
const newSubdir = subdir ? `${subdir}:${entry.name}` : entry.name;
|
||||
skills.push(...loadSkillsFromDir(fullPath, source, format, useColonPath, newSubdir));
|
||||
skills.push(...loadSkillsFromDirInternal(fullPath, source, format, useColonPath, newSubdir));
|
||||
} else if (entry.isFile() && entry.name === "SKILL.md") {
|
||||
try {
|
||||
const rawContent = readFileSync(fullPath, "utf-8");
|
||||
|
|
@ -153,34 +169,60 @@ function loadSkillsFromDir(
|
|||
return skills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format skills for inclusion in a system prompt.
|
||||
*/
|
||||
export function formatSkillsForPrompt(skills: Skill[]): string {
|
||||
if (skills.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const lines = [
|
||||
"\n\n<available_skills>",
|
||||
"The following skills provide specialized instructions for specific tasks.",
|
||||
"Use the read tool to load a skill's file when the task matches its description.",
|
||||
"Skills may contain {baseDir} placeholders - replace them with the skill's base directory path.\n",
|
||||
];
|
||||
|
||||
for (const skill of skills) {
|
||||
lines.push(`- ${skill.name}: ${skill.description}`);
|
||||
lines.push(` File: ${skill.filePath}`);
|
||||
lines.push(` Base directory: ${skill.baseDir}`);
|
||||
}
|
||||
|
||||
lines.push("</available_skills>");
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
export function loadSkills(): Skill[] {
|
||||
const skillMap = new Map<string, Skill>();
|
||||
|
||||
// Codex: recursive, simple directory name
|
||||
const codexUserDir = join(homedir(), ".codex", "skills");
|
||||
for (const skill of loadSkillsFromDir(codexUserDir, "codex-user", "recursive", false)) {
|
||||
for (const skill of loadSkillsFromDirInternal(codexUserDir, "codex-user", "recursive", false)) {
|
||||
skillMap.set(skill.name, skill);
|
||||
}
|
||||
|
||||
// Claude: single level only
|
||||
const claudeUserDir = join(homedir(), ".claude", "skills");
|
||||
for (const skill of loadSkillsFromDir(claudeUserDir, "claude-user", "claude", false)) {
|
||||
for (const skill of loadSkillsFromDirInternal(claudeUserDir, "claude-user", "claude", false)) {
|
||||
skillMap.set(skill.name, skill);
|
||||
}
|
||||
|
||||
const claudeProjectDir = resolve(process.cwd(), ".claude", "skills");
|
||||
for (const skill of loadSkillsFromDir(claudeProjectDir, "claude-project", "claude", false)) {
|
||||
for (const skill of loadSkillsFromDirInternal(claudeProjectDir, "claude-project", "claude", false)) {
|
||||
skillMap.set(skill.name, skill);
|
||||
}
|
||||
|
||||
// Pi: recursive, colon-separated path names
|
||||
const globalSkillsDir = join(homedir(), CONFIG_DIR_NAME, "agent", "skills");
|
||||
for (const skill of loadSkillsFromDir(globalSkillsDir, "user", "recursive", true)) {
|
||||
for (const skill of loadSkillsFromDirInternal(globalSkillsDir, "user", "recursive", true)) {
|
||||
skillMap.set(skill.name, skill);
|
||||
}
|
||||
|
||||
const projectSkillsDir = resolve(process.cwd(), CONFIG_DIR_NAME, "skills");
|
||||
for (const skill of loadSkillsFromDir(projectSkillsDir, "project", "recursive", true)) {
|
||||
for (const skill of loadSkillsFromDirInternal(projectSkillsDir, "project", "recursive", true)) {
|
||||
skillMap.set(skill.name, skill);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import chalk from "chalk";
|
|||
import { existsSync, readFileSync } from "fs";
|
||||
import { join, resolve } from "path";
|
||||
import { getAgentDir, getDocsPath, getReadmePath } from "../config.js";
|
||||
import { loadSkills, type Skill } from "./skills.js";
|
||||
import { formatSkillsForPrompt, loadSkills } from "./skills.js";
|
||||
import type { ToolName } from "./tools/index.js";
|
||||
|
||||
/** Tool descriptions for system prompt */
|
||||
|
|
@ -102,29 +102,6 @@ export function loadProjectContextFiles(): Array<{ path: string; content: string
|
|||
return contextFiles;
|
||||
}
|
||||
|
||||
function buildSkillsSection(skills: Skill[]): string {
|
||||
if (skills.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const lines = [
|
||||
"\n\n<available_skills>",
|
||||
"The following skills provide specialized instructions for specific tasks.",
|
||||
"Use the read tool to load a skill's file when the task matches its description.",
|
||||
"Skills may contain {baseDir} placeholders - replace them with the skill's base directory path.\n",
|
||||
];
|
||||
|
||||
for (const skill of skills) {
|
||||
lines.push(`- ${skill.name}: ${skill.description}`);
|
||||
lines.push(` File: ${skill.filePath}`);
|
||||
lines.push(` Base directory: ${skill.baseDir}`);
|
||||
}
|
||||
|
||||
lines.push("</available_skills>");
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
export interface BuildSystemPromptOptions {
|
||||
customPrompt?: string;
|
||||
selectedTools?: ToolName[];
|
||||
|
|
@ -173,7 +150,7 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
|||
const customPromptHasRead = !selectedTools || selectedTools.includes("read");
|
||||
if (skillsEnabled && customPromptHasRead) {
|
||||
const skills = loadSkills();
|
||||
prompt += buildSkillsSection(skills);
|
||||
prompt += formatSkillsForPrompt(skills);
|
||||
}
|
||||
|
||||
// Add date/time and working directory last
|
||||
|
|
@ -279,7 +256,7 @@ Documentation:
|
|||
// Append skills section (only if read tool is available)
|
||||
if (skillsEnabled && hasRead) {
|
||||
const skills = loadSkills();
|
||||
prompt += buildSkillsSection(skills);
|
||||
prompt += formatSkillsForPrompt(skills);
|
||||
}
|
||||
|
||||
// Add date/time and working directory last
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue