feat(skills): use ~/.agents/skills/ as primary skills directory with curated catalog
Stop force-syncing bundled skills to ~/.gsd/agent/skills/ on every launch. Instead, use ~/.agents/skills/ (the industry-standard skills.sh directory) as the primary global skills location, and .agents/skills/ for project-local skills. Changes: - loadSkills() now scans ~/.agents/skills/ (global) and .agents/skills/ (project) instead of ~/.gsd/agent/skills/ and .gsd/skills/ - initResources() no longer syncs src/resources/skills/ → ~/.gsd/agent/skills/ - skill-discovery, skill-telemetry, skill-health, preferences-skills all updated to use the ecosystem directory - New skill-catalog.ts: curated skill packs mapped to tech stacks, with brownfield auto-detection and greenfield tech stack selection - Init wizard gains a skill installation step that presents relevant packs and installs via `npx skills add` - Export ECOSYSTEM_SKILLS_DIR and ECOSYSTEM_PROJECT_SKILLS_DIR from pi-coding-agent Fixes #2004
This commit is contained in:
parent
3c9c6817dc
commit
aaed0ab796
10 changed files with 407 additions and 25 deletions
|
|
@ -2,11 +2,22 @@ import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "f
|
|||
import ignore from "ignore";
|
||||
import { homedir } from "os";
|
||||
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
||||
import { CONFIG_DIR_NAME, getAgentDir } from "../config.js";
|
||||
import { parseFrontmatter } from "../utils/frontmatter.js";
|
||||
import { toPosixPath } from "../utils/path-display.js";
|
||||
import type { ResourceDiagnostic } from "./diagnostics.js";
|
||||
|
||||
/**
|
||||
* The standard ecosystem skills directory used by skills.sh and the
|
||||
* Agent Skills standard. All agents share this location for globally
|
||||
* installed skills.
|
||||
*/
|
||||
export const ECOSYSTEM_SKILLS_DIR = join(homedir(), ".agents", "skills");
|
||||
|
||||
/**
|
||||
* The standard project-level skills directory (`.agents/skills/` relative to cwd).
|
||||
*/
|
||||
export const ECOSYSTEM_PROJECT_SKILLS_DIR = ".agents";
|
||||
|
||||
/** Max name length per spec */
|
||||
const MAX_NAME_LENGTH = 64;
|
||||
|
||||
|
|
@ -331,7 +342,7 @@ function escapeXml(str: string): string {
|
|||
export interface LoadSkillsOptions {
|
||||
/** Working directory for project-local skills. Default: process.cwd() */
|
||||
cwd?: string;
|
||||
/** Agent config directory for global skills. Default: ~/.pi/agent */
|
||||
/** @deprecated Skills now use ~/.agents/skills/ exclusively. This option is ignored. */
|
||||
agentDir?: string;
|
||||
/** Explicit skill paths (files or directories) */
|
||||
skillPaths?: string[];
|
||||
|
|
@ -357,10 +368,7 @@ function resolveSkillPath(p: string, cwd: string): string {
|
|||
* Returns skills and any validation diagnostics.
|
||||
*/
|
||||
export function loadSkills(options: LoadSkillsOptions = {}): LoadSkillsResult {
|
||||
const { cwd = process.cwd(), agentDir, skillPaths = [], includeDefaults = true } = options;
|
||||
|
||||
// Resolve agentDir - if not provided, use default from config
|
||||
const resolvedAgentDir = agentDir ?? getAgentDir();
|
||||
const { cwd = process.cwd(), skillPaths = [], includeDefaults = true } = options;
|
||||
|
||||
const skillMap = new Map<string, Skill>();
|
||||
const realPathSet = new Set<string>();
|
||||
|
|
@ -404,12 +412,14 @@ export function loadSkills(options: LoadSkillsOptions = {}): LoadSkillsResult {
|
|||
}
|
||||
|
||||
if (includeDefaults) {
|
||||
addSkills(loadSkillsFromDirInternal(join(resolvedAgentDir, "skills"), "user", true));
|
||||
addSkills(loadSkillsFromDirInternal(resolve(cwd, CONFIG_DIR_NAME, "skills"), "project", true));
|
||||
// Primary: ~/.agents/skills/ — the industry-standard skills.sh location
|
||||
addSkills(loadSkillsFromDirInternal(ECOSYSTEM_SKILLS_DIR, "user", true));
|
||||
// Primary project: .agents/skills/ — standard project-level location
|
||||
addSkills(loadSkillsFromDirInternal(resolve(cwd, ECOSYSTEM_PROJECT_SKILLS_DIR, "skills"), "project", true));
|
||||
}
|
||||
|
||||
const userSkillsDir = join(resolvedAgentDir, "skills");
|
||||
const projectSkillsDir = resolve(cwd, CONFIG_DIR_NAME, "skills");
|
||||
const userSkillsDir = ECOSYSTEM_SKILLS_DIR;
|
||||
const projectSkillsDir = resolve(cwd, ECOSYSTEM_PROJECT_SKILLS_DIR, "skills");
|
||||
|
||||
const isUnderPath = (target: string, root: string): boolean => {
|
||||
const normalizedRoot = resolve(root);
|
||||
|
|
|
|||
|
|
@ -212,6 +212,8 @@ export {
|
|||
} from "./core/settings-manager.js";
|
||||
// Skills
|
||||
export {
|
||||
ECOSYSTEM_SKILLS_DIR,
|
||||
ECOSYSTEM_PROJECT_SKILLS_DIR,
|
||||
formatSkillsForPrompt,
|
||||
getLoadedSkills,
|
||||
type LoadSkillsFromDirOptions,
|
||||
|
|
|
|||
|
|
@ -390,7 +390,9 @@ export function initResources(agentDir: string): void {
|
|||
|
||||
syncResourceDir(bundledExtensionsDir, join(agentDir, 'extensions'))
|
||||
syncResourceDir(join(resourcesDir, 'agents'), join(agentDir, 'agents'))
|
||||
syncResourceDir(join(resourcesDir, 'skills'), join(agentDir, 'skills'))
|
||||
// Skills are no longer force-synced here. Users install skills via the
|
||||
// skills.sh CLI (`npx skills add <repo>`) into ~/.agents/skills/ which
|
||||
// is the industry-standard Agent Skills ecosystem directory.
|
||||
|
||||
// Sync GSD-WORKFLOW.md to agentDir as a fallback for when GSD_WORKFLOW_PATH
|
||||
// env var is not set (e.g. fork/dev builds, alternative entry points).
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|||
import { gsdRoot } from "./paths.js";
|
||||
import { assertSafeDirectory } from "./validate-directory.js";
|
||||
import type { ProjectDetection, ProjectSignals } from "./detection.js";
|
||||
import { runSkillInstallStep } from "./skill-catalog.js";
|
||||
|
||||
// ─── Types ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -223,7 +224,14 @@ export async function showProjectInit(
|
|||
await customizeAdvancedPrefs(ctx, prefs);
|
||||
}
|
||||
|
||||
// ── Step 8: Bootstrap .gsd/ ────────────────────────────────────────────────
|
||||
// ── Step 8: Skill Installation ─────────────────────────────────────────────
|
||||
try {
|
||||
await runSkillInstallStep(ctx, signals);
|
||||
} catch {
|
||||
// Non-fatal — skill installation failure should never block project init
|
||||
}
|
||||
|
||||
// ── Step 9: Bootstrap .gsd/ ────────────────────────────────────────────────
|
||||
bootstrapGsdDirectory(basePath, prefs, signals);
|
||||
|
||||
// Ensure .gitignore
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
import { existsSync, readdirSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { isAbsolute, join } from "node:path";
|
||||
import { getAgentDir } from "@gsd/pi-coding-agent";
|
||||
import { statSync } from "node:fs";
|
||||
|
||||
import type {
|
||||
|
|
@ -25,12 +24,12 @@ export type { GSDSkillRule, SkillDiscoveryMode, SkillResolution, SkillResolution
|
|||
|
||||
/**
|
||||
* Known skill directories, in priority order.
|
||||
* User skills (~/.gsd/agent/skills/) take precedence over project skills.
|
||||
* Global skills (~/.agents/skills/) take precedence over project skills.
|
||||
*/
|
||||
export function getSkillSearchDirs(cwd: string): Array<{ dir: string; method: SkillResolution["method"] }> {
|
||||
return [
|
||||
{ dir: join(getAgentDir(), "skills"), method: "user-skill" },
|
||||
{ dir: join(cwd, ".pi", "agent", "skills"), method: "project-skill" },
|
||||
{ dir: join(homedir(), ".agents", "skills"), method: "user-skill" },
|
||||
{ dir: join(cwd, ".agents", "skills"), method: "project-skill" },
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
361
src/resources/extensions/gsd/skill-catalog.ts
Normal file
361
src/resources/extensions/gsd/skill-catalog.ts
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
/**
|
||||
* GSD Skill Catalog — Curated skill packs mapped to tech stacks.
|
||||
*
|
||||
* Each pack maps a detected (or user-chosen) tech stack to a skills.sh
|
||||
* repo + specific skill names. The init wizard uses this catalog to
|
||||
* install relevant skills during project onboarding.
|
||||
*
|
||||
* Installation is delegated entirely to the skills.sh CLI:
|
||||
* npx skills add <repo> --skill <name> --skill <name> -y
|
||||
*
|
||||
* Skills are installed into ~/.agents/skills/ (the industry-standard
|
||||
* ecosystem directory shared across all agents).
|
||||
*/
|
||||
|
||||
import { execFile } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { homedir } from "node:os";
|
||||
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
||||
import { showNextAction } from "../shared/tui.js";
|
||||
import type { ProjectSignals } from "./detection.js";
|
||||
|
||||
// ─── Catalog Types ────────────────────────────────────────────────────────────
|
||||
|
||||
export interface SkillPack {
|
||||
/** Human-readable name shown in the wizard */
|
||||
label: string;
|
||||
/** Short description */
|
||||
description: string;
|
||||
/** skills.sh repo identifier (owner/repo) */
|
||||
repo: string;
|
||||
/** Specific skill names to install from the repo */
|
||||
skills: string[];
|
||||
/** Which detected primaryLanguage values trigger this pack */
|
||||
matchLanguages?: string[];
|
||||
/** Which detected project files trigger this pack */
|
||||
matchFiles?: string[];
|
||||
}
|
||||
|
||||
// ─── Curated Catalog ──────────────────────────────────────────────────────────
|
||||
|
||||
export const SKILL_CATALOG: SkillPack[] = [
|
||||
// ── iOS / Swift ───────────────────────────────────────────────────────────
|
||||
{
|
||||
label: "Swift / iOS",
|
||||
description: "SwiftUI, Swift concurrency, SwiftData, iOS frameworks",
|
||||
repo: "dpearson2699/swift-ios-skills",
|
||||
skills: ["*"],
|
||||
matchLanguages: ["swift"],
|
||||
matchFiles: ["Package.swift"],
|
||||
},
|
||||
// ── React / Next.js ───────────────────────────────────────────────────────
|
||||
{
|
||||
label: "React & Web Frontend",
|
||||
description: "React best practices, web design, accessibility, core web vitals",
|
||||
repo: "vercel-labs/agent-skills",
|
||||
skills: [
|
||||
"vercel-react-best-practices",
|
||||
"web-design-guidelines",
|
||||
"vercel-composition-patterns",
|
||||
],
|
||||
matchLanguages: ["javascript/typescript"],
|
||||
},
|
||||
// ── React Native ──────────────────────────────────────────────────────────
|
||||
{
|
||||
label: "React Native",
|
||||
description: "React Native patterns and cross-platform mobile development",
|
||||
repo: "vercel-labs/agent-skills",
|
||||
skills: ["vercel-react-native-skills"],
|
||||
matchLanguages: ["javascript/typescript"],
|
||||
},
|
||||
// ── General Frontend ──────────────────────────────────────────────────────
|
||||
{
|
||||
label: "Frontend Design & UX",
|
||||
description: "Frontend design, accessibility, and browser automation",
|
||||
repo: "anthropics/skills",
|
||||
skills: ["frontend-design"],
|
||||
matchLanguages: ["javascript/typescript"],
|
||||
},
|
||||
// ── Rust ──────────────────────────────────────────────────────────────────
|
||||
{
|
||||
label: "Rust",
|
||||
description: "Rust language patterns and best practices",
|
||||
repo: "anthropics/skills",
|
||||
skills: ["rust-best-practices"],
|
||||
matchLanguages: ["rust"],
|
||||
matchFiles: ["Cargo.toml"],
|
||||
},
|
||||
// ── Python ────────────────────────────────────────────────────────────────
|
||||
{
|
||||
label: "Python",
|
||||
description: "Python patterns and best practices",
|
||||
repo: "anthropics/skills",
|
||||
skills: ["python-best-practices"],
|
||||
matchLanguages: ["python"],
|
||||
matchFiles: ["pyproject.toml", "setup.py"],
|
||||
},
|
||||
// ── Go ────────────────────────────────────────────────────────────────────
|
||||
{
|
||||
label: "Go",
|
||||
description: "Go language patterns and best practices",
|
||||
repo: "anthropics/skills",
|
||||
skills: ["go-best-practices"],
|
||||
matchLanguages: ["go"],
|
||||
matchFiles: ["go.mod"],
|
||||
},
|
||||
// ── General Tooling ───────────────────────────────────────────────────────
|
||||
{
|
||||
label: "Document Handling",
|
||||
description: "PDF, DOCX, XLSX, PPTX creation and manipulation",
|
||||
repo: "anthropics/skills",
|
||||
skills: ["pdf", "docx", "xlsx", "pptx"],
|
||||
},
|
||||
];
|
||||
|
||||
// ─── Greenfield Tech Stack Choices ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Choices shown to users when no tech stack can be auto-detected
|
||||
* (greenfield repos or empty directories).
|
||||
*/
|
||||
export const GREENFIELD_STACKS: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
description: string;
|
||||
packs: string[];
|
||||
}> = [
|
||||
{
|
||||
id: "ios",
|
||||
label: "iOS / Swift",
|
||||
description: "SwiftUI, Swift, iOS frameworks",
|
||||
packs: ["Swift / iOS"],
|
||||
},
|
||||
{
|
||||
id: "react-web",
|
||||
label: "React Web",
|
||||
description: "React, Next.js, web frontend",
|
||||
packs: ["React & Web Frontend", "Frontend Design & UX"],
|
||||
},
|
||||
{
|
||||
id: "react-native",
|
||||
label: "React Native",
|
||||
description: "Cross-platform mobile with React Native",
|
||||
packs: ["React Native", "React & Web Frontend"],
|
||||
},
|
||||
{
|
||||
id: "fullstack-js",
|
||||
label: "Full-Stack JavaScript/TypeScript",
|
||||
description: "Node.js backend + React frontend",
|
||||
packs: ["React & Web Frontend", "Frontend Design & UX"],
|
||||
},
|
||||
{
|
||||
id: "rust",
|
||||
label: "Rust",
|
||||
description: "Systems programming with Rust",
|
||||
packs: ["Rust"],
|
||||
},
|
||||
{
|
||||
id: "python",
|
||||
label: "Python",
|
||||
description: "Python applications, scripts, or ML",
|
||||
packs: ["Python"],
|
||||
},
|
||||
{
|
||||
id: "go",
|
||||
label: "Go",
|
||||
description: "Go services and CLIs",
|
||||
packs: ["Go"],
|
||||
},
|
||||
{
|
||||
id: "other",
|
||||
label: "Other / Skip",
|
||||
description: "Install skills later with npx skills add",
|
||||
packs: [],
|
||||
},
|
||||
];
|
||||
|
||||
// ─── Detection → Pack Matching ────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Match project signals to relevant skill packs.
|
||||
* Returns packs ordered by relevance (language match first, then file match).
|
||||
*/
|
||||
export function matchPacksForProject(signals: ProjectSignals): SkillPack[] {
|
||||
const matched = new Set<SkillPack>();
|
||||
|
||||
for (const pack of SKILL_CATALOG) {
|
||||
// Language match
|
||||
if (pack.matchLanguages && signals.primaryLanguage) {
|
||||
if (pack.matchLanguages.includes(signals.primaryLanguage)) {
|
||||
matched.add(pack);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// File match
|
||||
if (pack.matchFiles) {
|
||||
for (const file of pack.matchFiles) {
|
||||
if (signals.detectedFiles.includes(file)) {
|
||||
matched.add(pack);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...matched];
|
||||
}
|
||||
|
||||
// ─── Installation ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Install a skill pack via the skills.sh CLI.
|
||||
* Runs: npx skills add <repo> --skill <name> ... -y
|
||||
*
|
||||
* Returns true if installation succeeded.
|
||||
*/
|
||||
export function installSkillPack(pack: SkillPack): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const args = ["--yes", "skills", "add", pack.repo];
|
||||
|
||||
if (pack.skills.length === 1 && pack.skills[0] === "*") {
|
||||
args.push("--all");
|
||||
} else {
|
||||
for (const skill of pack.skills) {
|
||||
args.push("--skill", skill);
|
||||
}
|
||||
args.push("-y");
|
||||
}
|
||||
|
||||
execFile("npx", args, { timeout: 120_000 }, (error) => {
|
||||
resolve(!error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any skills from a pack are already installed.
|
||||
*/
|
||||
export function isPackInstalled(pack: SkillPack): boolean {
|
||||
const skillsDir = join(homedir(), ".agents", "skills");
|
||||
if (!existsSync(skillsDir)) return false;
|
||||
|
||||
if (pack.skills.length === 1 && pack.skills[0] === "*") {
|
||||
// For wildcard packs, check if the repo name appears as a skill dir prefix
|
||||
// This is a heuristic — can't know all skill names without querying the repo
|
||||
return false;
|
||||
}
|
||||
|
||||
return pack.skills.every((name) =>
|
||||
existsSync(join(skillsDir, name, "SKILL.md")),
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Init Wizard Integration ──────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Run skill installation step during project init.
|
||||
*
|
||||
* Brownfield (signals.detectedFiles.length > 0):
|
||||
* Auto-detects tech stack → shows matched packs → installs accepted ones.
|
||||
*
|
||||
* Greenfield (no files detected):
|
||||
* Asks user what tech stack they're using → maps to packs → installs.
|
||||
*
|
||||
* Returns the list of installed pack labels.
|
||||
*/
|
||||
export async function runSkillInstallStep(
|
||||
ctx: ExtensionCommandContext,
|
||||
signals: ProjectSignals,
|
||||
): Promise<string[]> {
|
||||
const installed: string[] = [];
|
||||
const isBrownfield = signals.detectedFiles.length > 0;
|
||||
|
||||
if (isBrownfield) {
|
||||
// ── Brownfield: auto-detect and confirm ─────────────────────────────────
|
||||
const matched = matchPacksForProject(signals);
|
||||
if (matched.length === 0) return installed;
|
||||
|
||||
// Filter out already-installed packs
|
||||
const toInstall = matched.filter((p) => !isPackInstalled(p));
|
||||
if (toInstall.length === 0) return installed;
|
||||
|
||||
const packNames = toInstall.map((p) => `${p.label}: ${p.description}`);
|
||||
const choice = await showNextAction(ctx, {
|
||||
title: "GSD — Install Skills",
|
||||
summary: [
|
||||
`Detected: ${signals.primaryLanguage ?? "unknown"} project`,
|
||||
"",
|
||||
"Recommended skill packs:",
|
||||
...packNames.map((n) => ` • ${n}`),
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
id: "install",
|
||||
label: "Install recommended skills",
|
||||
description: `Install ${toInstall.length} skill pack${toInstall.length > 1 ? "s" : ""} via skills.sh`,
|
||||
recommended: true,
|
||||
},
|
||||
{
|
||||
id: "skip",
|
||||
label: "Skip",
|
||||
description: "Install skills later with npx skills add",
|
||||
},
|
||||
],
|
||||
notYetMessage: "Run /gsd init when ready.",
|
||||
});
|
||||
|
||||
if (choice === "install") {
|
||||
for (const pack of toInstall) {
|
||||
ctx.ui.notify(`Installing ${pack.label} skills...`, "info");
|
||||
const ok = await installSkillPack(pack);
|
||||
if (ok) {
|
||||
installed.push(pack.label);
|
||||
} else {
|
||||
ctx.ui.notify(`Failed to install ${pack.label} — try manually: npx skills add ${pack.repo}`, "info");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ── Greenfield: ask user what they're building ──────────────────────────
|
||||
const stackChoice = await showNextAction(ctx, {
|
||||
title: "GSD — Project Skills",
|
||||
summary: [
|
||||
"What are you building? GSD will install relevant agent skills.",
|
||||
"Skills are installed globally via skills.sh and shared across agents.",
|
||||
],
|
||||
actions: GREENFIELD_STACKS.map((s) => ({
|
||||
id: s.id,
|
||||
label: s.label,
|
||||
description: s.description,
|
||||
})),
|
||||
notYetMessage: "Run /gsd init when ready.",
|
||||
});
|
||||
|
||||
if (stackChoice === "not_yet" || stackChoice === "other") return installed;
|
||||
|
||||
const stack = GREENFIELD_STACKS.find((s) => s.id === stackChoice);
|
||||
if (!stack) return installed;
|
||||
|
||||
const packsToInstall = SKILL_CATALOG.filter((p) =>
|
||||
stack.packs.includes(p.label),
|
||||
).filter((p) => !isPackInstalled(p));
|
||||
|
||||
for (const pack of packsToInstall) {
|
||||
ctx.ui.notify(`Installing ${pack.label} skills...`, "info");
|
||||
const ok = await installSkillPack(pack);
|
||||
if (ok) {
|
||||
installed.push(pack.label);
|
||||
} else {
|
||||
ctx.ui.notify(`Failed to install ${pack.label} — try manually: npx skills add ${pack.repo}`, "info");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (installed.length > 0) {
|
||||
ctx.ui.notify(`Installed: ${installed.join(", ")}`, "info");
|
||||
}
|
||||
|
||||
return installed;
|
||||
}
|
||||
|
|
@ -10,9 +10,10 @@
|
|||
|
||||
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { getAgentDir } from "@gsd/pi-coding-agent";
|
||||
import { homedir } from "node:os";
|
||||
|
||||
const SKILLS_DIR = join(getAgentDir(), "skills");
|
||||
/** Industry-standard skills.sh global skills directory */
|
||||
const SKILLS_DIR = join(homedir(), ".agents", "skills");
|
||||
|
||||
export interface DiscoveredSkill {
|
||||
name: string;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { getAgentDir } from "@gsd/pi-coding-agent";
|
||||
import { homedir } from "node:os";
|
||||
import type { UnitMetrics, MetricsLedger } from "./metrics.js";
|
||||
import { formatCost, formatTokenCount, loadLedgerFromDisk } from "./metrics.js";
|
||||
import { getSkillLastUsed, detectStaleSkills } from "./skill-telemetry.js";
|
||||
|
|
@ -208,7 +208,7 @@ export function formatSkillDetail(basePath: string, skillName: string): string {
|
|||
}
|
||||
|
||||
// Check for SKILL.md existence
|
||||
const skillPath = join(getAgentDir(), "skills", skillName, "SKILL.md");
|
||||
const skillPath = join(homedir(), ".agents", "skills", skillName, "SKILL.md");
|
||||
if (existsSync(skillPath)) {
|
||||
const stat = require("node:fs").statSync(skillPath);
|
||||
lines.push("");
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { getAgentDir } from "@gsd/pi-coding-agent";
|
||||
import { homedir } from "node:os";
|
||||
|
||||
// ─── In-memory state ──────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ const activelyLoadedSkills = new Set<string>();
|
|||
* Called before each unit starts.
|
||||
*/
|
||||
export function captureAvailableSkills(): void {
|
||||
const skillsDir = join(getAgentDir(), "skills");
|
||||
const skillsDir = join(homedir(), ".agents", "skills");
|
||||
availableSkills = listSkillNames(skillsDir);
|
||||
activelyLoadedSkills.clear();
|
||||
}
|
||||
|
|
@ -99,7 +99,7 @@ export function detectStaleSkills(
|
|||
const stale: string[] = [];
|
||||
|
||||
// Check all installed skills, not just those with usage data
|
||||
const skillsDir = join(getAgentDir(), "skills");
|
||||
const skillsDir = join(homedir(), ".agents", "skills");
|
||||
const installed = listSkillNames(skillsDir);
|
||||
|
||||
for (const skill of installed) {
|
||||
|
|
|
|||
|
|
@ -150,8 +150,7 @@ test("initResources syncs extensions, agents, and skills to target dir", async (
|
|||
// Agents synced
|
||||
assert.ok(existsSync(join(fakeAgentDir, "agents", "scout.md")), "scout agent synced");
|
||||
|
||||
// Skills synced
|
||||
assert.ok(existsSync(join(fakeAgentDir, "skills")), "skills directory synced");
|
||||
// Skills are NOT synced here — they use ~/.agents/skills/ via skills.sh
|
||||
|
||||
// Version manifest synced
|
||||
const managedVersion = readManagedResourceVersion(fakeAgentDir);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue