šŸŽ© You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP →
Sign In

@silverassist/agents-toolkit

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@silverassist/agents-toolkit - npm Package Compare versions

Comparing version
2.6.0
to
2.7.0
+1
dist/cli.d.mts
#!/usr/bin/env node
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { VERSION } from './index.mjs';
import crypto from 'node:crypto';
const COLORS = {
reset: "\x1B[0m",
bright: "\x1B[1m",
green: "\x1B[32m",
yellow: "\x1B[33m",
blue: "\x1B[34m",
red: "\x1B[31m",
cyan: "\x1B[36m"
};
function log(message, color = "reset") {
console.log(`${COLORS[color]}${message}${COLORS.reset}`);
}
function success(message) {
log(`\u2705 ${message}`, "green");
}
function warn(message) {
log(`\u26A0\uFE0F ${message}`, "yellow");
}
function error(message) {
log(`\u274C ${message}`, "red");
}
function info(message) {
log(`\u2139\uFE0F ${message}`, "blue");
}
function getHomeDir() {
return process.env["HOME"] || process.env["USERPROFILE"] || "";
}
function getTargetDir(global = false) {
return global ? path.join(getHomeDir(), ".copilot") : path.join(process.cwd(), ".github");
}
function getClaudeTargetDir(global = false) {
return global ? path.join(getHomeDir(), ".claude") : path.join(process.cwd(), ".claude");
}
function getAgentsSkillsDir(global = false) {
const base = global ? getHomeDir() : process.cwd();
return path.join(base, ".agents", "skills");
}
const DEFAULT_CONFIG = {
stack: "all",
tracker: "all",
jira: {
projectKey: "PROJECT",
baseUrl: "https://your-org.atlassian.net"
},
git: {
defaultBranch: "dev",
branchPrefix: {
feature: "feature/",
bugfix: "bugfix/",
hotfix: "hotfix/"
}
},
pr: {
targetBranch: "dev",
template: "default"
}
};
const VALID_STACKS = /* @__PURE__ */ new Set(["react", "wordpress", "all"]);
const VALID_TRACKERS = /* @__PURE__ */ new Set(["jira", "github", "all"]);
function loadConfig(configPath) {
if (!fs.existsSync(configPath)) return null;
try {
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
if (typeof raw !== "object" || raw === null) return null;
const obj = raw;
const stack = obj["stack"];
if (stack !== void 0 && (typeof stack !== "string" || !VALID_STACKS.has(stack))) return null;
const tracker = obj["tracker"];
if (tracker !== void 0 && (typeof tracker !== "string" || !VALID_TRACKERS.has(tracker))) return null;
return raw;
} catch {
return null;
}
}
function resolveFilters(options) {
const validStacks = ["react", "wordpress", "all"];
const validTrackers = ["jira", "github", "all"];
let stack = "all";
let tracker = "all";
const globalConfig = loadConfig(path.join(getHomeDir(), ".agents-toolkit.json"));
if (globalConfig?.stack) stack = globalConfig.stack;
if (globalConfig?.tracker) tracker = globalConfig.tracker;
const projectConfig = loadConfig(path.join(process.cwd(), ".agents-toolkit.json"));
if (projectConfig?.stack) stack = projectConfig.stack;
if (projectConfig?.tracker) tracker = projectConfig.tracker;
if (options.stack !== null) {
const value = options.stack.trim().toLowerCase();
if (!value) {
error("Missing value for --stack. Use react, wordpress, or all.");
process.exit(1);
}
if (!validStacks.includes(value)) {
error(`Invalid --stack value: ${options.stack}. Use react, wordpress, or all.`);
process.exit(1);
}
stack = value;
}
if (options.tracker !== null) {
const value = options.tracker.trim().toLowerCase();
if (!value) {
error("Missing value for --tracker. Use jira, github, or all.");
process.exit(1);
}
if (!validTrackers.includes(value)) {
error(`Invalid --tracker value: ${options.tracker}. Use jira, github, or all.`);
process.exit(1);
}
tracker = value;
}
return { stack, tracker };
}
function getInstallScope(options) {
const {
promptsOnly = false,
partialsOnly = false,
skillsOnly = false,
instructionsOnly = false,
hooksOnly = false
} = options;
const hasSpecificFlag = promptsOnly || partialsOnly || skillsOnly || instructionsOnly || hooksOnly;
return {
shouldInstallPrompts: !hasSpecificFlag || promptsOnly || partialsOnly,
shouldInstallInstructions: !hasSpecificFlag || instructionsOnly,
shouldInstallSkills: !hasSpecificFlag || skillsOnly,
shouldInstallHooks: !hasSpecificFlag || hooksOnly
};
}
function getChangeCount(result, dryRun) {
return dryRun ? result.planned : result.written;
}
function ensureConfigFile(options = {}) {
const { dryRun = false, global: isGlobal = false } = options;
const configDir = isGlobal ? getHomeDir() : process.cwd();
const configPath = path.join(configDir, ".agents-toolkit.json");
if (fs.existsSync(configPath)) {
return { written: 0, planned: 0 };
}
if (dryRun) {
info(`Would create ${isGlobal ? "~" : "."}/.agents-toolkit.json`);
return { written: 0, planned: 1 };
}
fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
success(`Created ${isGlobal ? "~" : "."}/.agents-toolkit.json config file`);
return { written: 1, planned: 1 };
}
const TEMPLATES_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "templates");
function copyDir(src, dest, options = {}) {
const {
force = false,
dryRun = false,
renameFile = (name) => name,
transformContent,
filter,
dirFilter,
partialsFilter
} = options;
const totals = { written: 0, planned: 0 };
if (!fs.existsSync(src)) return totals;
if (!dryRun && !fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true });
}
const entries = fs.readdirSync(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
if (entry.isDirectory()) {
if (dirFilter !== void 0 && !dirFilter(entry.name)) continue;
const nestedOptions = { ...options };
if (entry.name === "_partials" && partialsFilter !== void 0) {
nestedOptions.filter = partialsFilter;
}
const nested = copyDir(srcPath, path.join(dest, entry.name), nestedOptions);
totals.written += nested.written;
totals.planned += nested.planned;
} else {
if (filter !== void 0 && !filter(entry.name)) continue;
const destName = renameFile(entry.name);
const destPath = path.join(dest, destName);
if (fs.existsSync(destPath) && !force) {
warn(`Skipping existing file: ${path.relative(process.cwd(), destPath)}`);
continue;
}
totals.planned++;
if (dryRun) {
info(`Would copy: ${path.relative(process.cwd(), destPath)}`);
} else {
if (transformContent !== void 0) {
const rawContent = fs.readFileSync(srcPath, "utf-8");
fs.writeFileSync(destPath, transformContent(rawContent));
} else {
fs.copyFileSync(srcPath, destPath);
}
totals.written++;
}
}
}
return totals;
}
function appendSkillsToGitignore(cwd) {
const gitignorePath = path.join(cwd, ".gitignore");
const block = [
"",
"# agents-toolkit managed \u2014 regenerate with: npx @silverassist/agents-toolkit restore",
".agents/skills/",
".github/skills/",
".claude/skills/"
].join("\n");
const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf-8") : "";
if (existing.includes(".agents/skills/") || existing.includes(".github/skills/") || existing.includes(".claude/skills/")) {
return;
}
fs.writeFileSync(gitignorePath, existing + block + "\n", "utf-8");
info("Updated .gitignore with agents-toolkit managed paths");
}
function linkSkill(canonicalSkillDir, agentSkillLinkPath, options = {}) {
const { dryRun = false, force = false, copy = false } = options;
const totals = { written: 0, planned: 0 };
const relTarget = path.relative(path.dirname(agentSkillLinkPath), canonicalSkillDir);
const rel = (p) => path.relative(process.cwd(), p);
let existing = null;
try {
existing = fs.lstatSync(agentSkillLinkPath);
} catch {
}
if (existing !== null) {
if (existing.isSymbolicLink() && !copy) {
const current = fs.readlinkSync(agentSkillLinkPath);
if (path.resolve(path.dirname(agentSkillLinkPath), current) === path.resolve(canonicalSkillDir)) {
return totals;
}
}
if (!force) {
warn(`Skipping existing skill: ${rel(agentSkillLinkPath)}`);
return totals;
}
if (!dryRun) fs.rmSync(agentSkillLinkPath, { recursive: true, force: true });
}
totals.planned++;
if (dryRun) {
info(
copy ? `Would copy skill: ${rel(agentSkillLinkPath)}` : `Would link: ${rel(agentSkillLinkPath)} -> ${relTarget}`
);
return totals;
}
fs.mkdirSync(path.dirname(agentSkillLinkPath), { recursive: true });
const doCopy = () => copyDir(canonicalSkillDir, agentSkillLinkPath, { force: true });
if (copy) {
doCopy();
} else {
try {
fs.symlinkSync(relTarget, agentSkillLinkPath, "dir");
} catch {
doCopy();
}
}
totals.written++;
return totals;
}
function installSkillsStandard({
isGlobal,
agentSkillsDir,
force = false,
dryRun = false,
copy = false,
dirFilter
}) {
const totals = { written: 0, planned: 0, installedSkills: {} };
const skillsSrc = path.join(TEMPLATES_DIR, "shared", "skills");
const canonicalDir = getAgentsSkillsDir(isGlobal);
if (!fs.existsSync(skillsSrc)) return totals;
const canonicalResult = copyDir(skillsSrc, canonicalDir, {
force,
dryRun,
...dirFilter !== void 0 ? { dirFilter } : {}
});
totals.written += canonicalResult.written;
totals.planned += canonicalResult.planned;
const entries = fs.readdirSync(skillsSrc, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
if (dirFilter !== void 0 && !dirFilter(entry.name)) continue;
const canonicalSkillDir = path.join(canonicalDir, entry.name);
const agentSkillLinkPath = path.join(agentSkillsDir, entry.name);
const linkResult = linkSkill(canonicalSkillDir, agentSkillLinkPath, { dryRun, force, copy });
totals.written += linkResult.written;
totals.planned += linkResult.planned;
totals.installedSkills[entry.name] = { canonicalDir: canonicalSkillDir };
}
return totals;
}
const LOCKFILE_NAME = "agents-toolkit-lock.json";
function computeSkillHash(skillDir) {
const skillMdPath = path.join(skillDir, "SKILL.md");
if (!fs.existsSync(skillMdPath)) return null;
const content = fs.readFileSync(skillMdPath, "utf-8");
return crypto.createHash("sha256").update(content).digest("hex");
}
function readLockfile(cwd = process.cwd()) {
const lockPath = path.join(cwd, LOCKFILE_NAME);
if (!fs.existsSync(lockPath)) return null;
try {
const raw = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
if (typeof raw !== "object" || raw === null) return null;
const obj = raw;
if (obj["version"] !== 1) return null;
if (typeof obj["packageVersion"] !== "string") return null;
const config = obj["config"];
if (typeof config !== "object" || config === null) return null;
const cfg = config;
if (typeof cfg["stack"] !== "string" || typeof cfg["tracker"] !== "string") return null;
const skills = obj["skills"];
if (typeof skills !== "object" || skills === null) return null;
for (const entry of Object.values(skills)) {
if (typeof entry !== "object" || entry === null) return null;
const e = entry;
const agents = e["agents"];
if (!Array.isArray(agents)) return null;
if (!agents.every((a) => typeof a === "string")) return null;
const hash = e["computedHash"];
if (hash !== null && typeof hash !== "string") return null;
}
return raw;
} catch {
return null;
}
}
function writeLockfile({ skills, config, packageVersion, cwd = process.cwd() }) {
const lockPath = path.join(cwd, LOCKFILE_NAME);
const existing = readLockfile(cwd);
const mergedSkills = Object.assign({}, existing?.skills ?? {});
for (const [name, meta] of Object.entries(skills)) {
const prev = mergedSkills[name];
const prevAgents = prev?.agents ?? [];
const allAgents = Array.from(/* @__PURE__ */ new Set([...prevAgents, ...meta.agents]));
mergedSkills[name] = {
source: "@silverassist/agents-toolkit",
packageVersion,
computedHash: meta.computedHash,
agents: allAgents
};
}
const lockfile = { version: 1, packageVersion, config, skills: mergedSkills };
fs.writeFileSync(lockPath, JSON.stringify(lockfile, null, 2) + "\n", "utf-8");
success(`Wrote ${LOCKFILE_NAME}`);
}
const FILE_CATEGORIES = {
instructions: {
react: [
"caching",
"css-styling",
"react-components",
"seo-ai-optimization",
"server-actions",
"tests",
"tsdoc-standards",
"typescript"
],
wordpress: ["php-standards", "wordpress-plugin-architecture", "testing-standards"],
universal: ["documentation-language", "github-workflow"]
},
prompts: {
react: [],
wordpress: ["new-wp-component", "new-wp-plugin", "quality-check"],
universal: [
"analyze-ticket",
"work-ticket",
"analyze-github-issue",
"work-github-issue",
"create-plan",
"create-pr",
"prepare-pr",
"finalize-pr",
"create-github-pr",
"finalize-github-pr",
"resolve-github-reviews",
"review-code",
"fix-issues",
"add-tests",
"prepare-github-release"
],
jira: ["analyze-ticket", "work-ticket", "create-pr", "finalize-pr"],
github: [
"analyze-github-issue",
"work-github-issue",
"create-github-pr",
"finalize-github-pr",
"resolve-github-reviews"
]
},
partials: {
react: ["release-node"],
wordpress: ["release-wordpress"],
jira: ["jira-integration"],
github: ["github-integration"],
universal: ["git-operations", "pr-template", "validations", "documentation"]
},
skills: {
react: ["component-architecture", "nextjs-caching", "testing-patterns", "tsdoc-standards"],
wordpress: ["create-component", "plugin-creation", "quality-checks", "testing"],
github: ["github-review-management", "core-review"],
universal: ["domain-driven-design", "release-management", "github-review-management", "core-review"]
}
};
function shouldIncludeFile(filename, category, filters) {
const cats = FILE_CATEGORIES[category];
if (cats.universal?.includes(filename)) {
if (filters.tracker !== "all" && cats.jira?.includes(filename) && filters.tracker !== "jira") return false;
if (filters.tracker !== "all" && cats.github?.includes(filename) && filters.tracker !== "github") return false;
return true;
}
if (filters.stack !== "all") {
if (cats.react?.includes(filename)) return filters.stack === "react";
if (cats.wordpress?.includes(filename)) return filters.stack === "wordpress";
}
if (filters.tracker !== "all") {
if (cats.jira?.includes(filename)) return filters.tracker === "jira";
if (cats.github?.includes(filename)) return filters.tracker === "github";
}
return true;
}
function extractClaudeAlias(frontmatterBody) {
const match = frontmatterBody.match(/^model:[ \t]+([^\n]+)$/m);
const value = match?.[1]?.trim();
if (!value) return null;
if (/haiku/i.test(value)) return "haiku";
if (/sonnet/i.test(value)) return "sonnet";
if (/opus/i.test(value)) return "opus";
if (/fable/i.test(value)) return "fable";
return null;
}
function transformFrontmatterForClaude(content) {
const match = content.match(/^---\n([\s\S]*?)\n---\n\n?/);
if (!match) return content;
const [fullMatch, frontmatterBody] = match;
if (fullMatch === void 0 || frontmatterBody === void 0) return content;
const body = content.slice(fullMatch.length);
const alias = extractClaudeAlias(frontmatterBody);
if (!alias) return body;
return `---
model: ${alias}
---
${body}`;
}
function adaptPathsForClaude(content) {
return content.replace(/\.github\/copilot-instructions\.md/g, "CLAUDE.md").replace(/\.github\/prompts\/_partials\//g, ".claude/commands/_partials/");
}
function finalizeHookConfigs(hooksDest, isGlobal) {
const cwd = isGlobal ? hooksDest : path.relative(process.cwd(), hooksDest).split(path.sep).join("/");
const jsonFiles = fs.readdirSync(hooksDest).filter((f) => f.endsWith(".json"));
for (const file of jsonFiles) {
const filePath = path.join(hooksDest, file);
const config = JSON.parse(fs.readFileSync(filePath, "utf-8"));
config.version = 1;
for (const events of Object.values(config.hooks ?? {})) {
for (const entry of events) {
entry.cwd = cwd;
}
}
fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n");
}
}
function installHooks({
targetDir,
force = false,
dryRun = false,
global: isGlobal = false
}) {
const hooksSrc = path.join(TEMPLATES_DIR, "shared", "hooks");
const hooksDest = path.join(targetDir, "hooks");
if (!fs.existsSync(hooksSrc)) {
warn("No hooks templates found \u2014 skipping");
return { written: 0, planned: 0 };
}
const result = copyDir(hooksSrc, hooksDest, { force, dryRun });
if (!dryRun) {
const scriptsDir = path.join(hooksDest, "scripts");
if (fs.existsSync(scriptsDir)) {
for (const script of fs.readdirSync(scriptsDir).filter((f) => f.endsWith(".sh"))) {
fs.chmodSync(path.join(scriptsDir, script), 493);
}
}
if (fs.existsSync(hooksDest)) {
finalizeHookConfigs(hooksDest, isGlobal);
}
}
if (!dryRun && result.written > 0) {
success(`Installed ${result.written} hook files`);
}
return result;
}
function installCopilotInstructions(options = { targetDir: "" }) {
const { targetDir, dryRun = false } = options;
const result = { written: 0, planned: 0 };
const copilotInstructionsPath = path.join(targetDir, "copilot-instructions.md");
const templatePath = path.join(TEMPLATES_DIR, "agents", "copilot-instructions.md");
if (!fs.existsSync(templatePath)) return result;
const templateContent = fs.readFileSync(templatePath, "utf-8");
if (fs.existsSync(copilotInstructionsPath)) {
const existingContent = fs.readFileSync(copilotInstructionsPath, "utf-8");
const marker = "## \u{1F504} Copilot Agent Workflow";
if (existingContent.includes(marker)) {
info("copilot-instructions.md already contains key sections");
return result;
}
result.planned++;
if (dryRun) {
info("Would append key sections to existing copilot-instructions.md");
return result;
}
const sectionsToAppend = templateContent.split("\n").slice(4).join("\n");
fs.writeFileSync(
copilotInstructionsPath,
`${existingContent}
<!-- Added by agents-toolkit -->
${sectionsToAppend}`
);
success("Appended key sections to existing copilot-instructions.md");
result.written++;
return result;
}
result.planned++;
if (dryRun) {
info("Would create copilot-instructions.md");
return result;
}
fs.writeFileSync(copilotInstructionsPath, templateContent);
success("Created copilot-instructions.md with key sections");
result.written++;
return result;
}
function getAgentsTemplateBody(templateContent) {
const lines = templateContent.split("\n");
const dividerIndex = lines.indexOf("---");
if (dividerIndex === -1) return templateContent;
return lines.slice(dividerIndex + 1).join("\n").trimStart();
}
function installAgentsFile({
templatePath,
force = false,
append = false,
dryRun = false
}) {
const result = { written: 0, planned: 0 };
const agentsPath = path.join(process.cwd(), "AGENTS.md");
if (!fs.existsSync(templatePath)) return result;
const agentsExists = fs.existsSync(agentsPath);
if (!agentsExists || force) {
result.planned++;
if (dryRun) {
info(agentsExists ? "Would update AGENTS.md in project root" : "Would create AGENTS.md in project root");
return result;
}
fs.copyFileSync(templatePath, agentsPath);
success(agentsExists ? "Updated AGENTS.md in project root" : "Created AGENTS.md in project root");
result.written++;
return result;
}
if (!append) {
info("AGENTS.md already exists in project root (use --force to overwrite or --append to merge)");
return result;
}
const existingContent = fs.readFileSync(agentsPath, "utf-8");
const mergeMarker = "## \u{1F504} Agent Workflow (Complex Tasks)";
if (existingContent.includes(mergeMarker)) {
info("AGENTS.md already contains workflow sections");
return result;
}
result.planned++;
if (dryRun) {
info("Would append missing sections to AGENTS.md");
return result;
}
const templateContent = fs.readFileSync(templatePath, "utf-8");
const templateBody = getAgentsTemplateBody(templateContent);
fs.writeFileSync(agentsPath, `${existingContent}
<!-- Added by agents-toolkit (--append) -->
${templateBody}`);
success("Appended missing sections to AGENTS.md");
result.written++;
return result;
}
function installGitBasedTarget(options = {}, target = "copilot") {
const {
force = false,
append = false,
dryRun = false,
copy = false,
global: isGlobal = false,
filters = { stack: "all", tracker: "all" },
noAgentOverrides = false
} = options;
const isCodex = target === "codex";
const targetDir = getTargetDir(isGlobal);
const scope = getInstallScope(options);
let totalChanges = 0;
const installedSkillsMap = {};
const makeFilter = (category) => (name) => {
const basename = name.replace(/\.(prompt\.md|instructions\.md|md)$/, "");
return shouldIncludeFile(basename, category, filters);
};
const promptsFilter = makeFilter("prompts");
const partialsFilter = makeFilter("partials");
log(
isCodex ? "\n\u26A1 Codex Installer\n" : isGlobal ? "\n\u{1F310} Agents Toolkit Global Installer\n" : "\n\u{1F4E6} Agents Toolkit Installer\n",
"bright"
);
if (isGlobal) info(`Target: ${targetDir}
`);
if (dryRun) info("Dry run mode - no files will be copied\n");
if (scope.shouldInstallPrompts) {
info("Installing prompts...");
const result = copyDir(path.join(TEMPLATES_DIR, "shared", "prompts"), path.join(targetDir, "prompts"), {
force,
dryRun,
filter: promptsFilter,
partialsFilter
});
totalChanges += getChangeCount(result, dryRun);
if (!dryRun && result.written > 0) success(`Installed ${result.written} prompt files`);
}
if (scope.shouldInstallInstructions) {
info("Installing instructions...");
const result = copyDir(path.join(TEMPLATES_DIR, "shared", "instructions"), path.join(targetDir, "instructions"), {
force,
dryRun,
filter: makeFilter("instructions")
});
totalChanges += getChangeCount(result, dryRun);
if (!dryRun && result.written > 0) success(`Installed ${result.written} instruction files`);
}
if (scope.shouldInstallSkills) {
info("Installing skills (npx skills standard)...");
const result = installSkillsStandard({
isGlobal,
agentSkillsDir: path.join(targetDir, "skills"),
force,
dryRun,
copy,
dirFilter: makeFilter("skills")
});
totalChanges += getChangeCount(result, dryRun);
for (const [name, meta] of Object.entries(result.installedSkills)) {
let entry = installedSkillsMap[name];
if (entry === void 0) {
entry = { canonicalDir: meta.canonicalDir, agents: [] };
installedSkillsMap[name] = entry;
}
entry.agents.push(path.relative(process.cwd(), path.join(targetDir, "skills")));
}
if (!dryRun && result.written > 0) success(`Installed ${result.written} skill files/links`);
}
if (scope.shouldInstallHooks) {
info("Installing hooks...");
const hooksResult = installHooks({ targetDir, force, dryRun, global: isGlobal });
totalChanges += getChangeCount(hooksResult, dryRun);
}
totalChanges += getChangeCount(ensureConfigFile({ dryRun, global: isGlobal }), dryRun);
if (scope.shouldInstallPrompts && !noAgentOverrides && !isCodex && !isGlobal) {
info("Installing Copilot subagent overrides...");
const result = copyDir(path.join(TEMPLATES_DIR, "shared", "agents"), path.join(targetDir, "agents"), {
force,
dryRun,
filter: (name) => name.endsWith(".agent.md")
});
totalChanges += getChangeCount(result, dryRun);
if (!dryRun && result.written > 0)
success(`Installed ${result.written} Copilot agent override(s) to .github/agents/`);
}
if (!isGlobal && scope.shouldInstallInstructions && !isCodex) {
totalChanges += getChangeCount(installCopilotInstructions({ targetDir, dryRun }), dryRun);
}
if (!isGlobal && scope.shouldInstallInstructions) {
const agentsTemplatePath = isCodex ? path.join(TEMPLATES_DIR, "agents", "AGENTS.codex.md") : path.join(TEMPLATES_DIR, "agents", "AGENTS.md");
totalChanges += getChangeCount(
installAgentsFile({ templatePath: agentsTemplatePath, force, append, dryRun }),
dryRun
);
}
if (!isGlobal && !dryRun && scope.shouldInstallSkills && Object.keys(installedSkillsMap).length > 0) {
const skillsForLock = {};
for (const [name, meta] of Object.entries(installedSkillsMap)) {
skillsForLock[name] = { computedHash: computeSkillHash(meta.canonicalDir), agents: meta.agents };
}
writeLockfile({ skills: skillsForLock, config: filters, packageVersion: VERSION });
appendSkillsToGitignore(process.cwd());
}
console.log("");
if (dryRun) {
info(`Dry run complete. ${totalChanges} files would be installed.`);
} else if (totalChanges > 0) {
success(`Installation complete! ${totalChanges} files installed.`);
console.log("");
if (isGlobal) {
info("Next steps:");
console.log(" 1. Update ~/.agents-toolkit.json with your defaults");
console.log(" 2. Instructions/prompts/skills are now available globally in VS Code");
} else {
info("Next steps:");
console.log(" 1. Update .agents-toolkit.json with your Jira project key");
if (isCodex) {
console.log(" 2. Review AGENTS.md in the project root");
console.log(" 3. Run Codex from this project root");
} else {
console.log(" 2. Configure Atlassian MCP in VS Code");
console.log(' 3. Run prompts via Command Palette > "GitHub Copilot: Run Prompt"');
}
}
} else {
warn("No new files installed. Use --force to overwrite existing files.");
}
console.log("");
}
function install(options = {}) {
installGitBasedTarget(options, "copilot");
}
function installCodex(options = {}) {
installGitBasedTarget(options, "codex");
}
function installClaude(options = {}) {
const {
force = false,
dryRun = false,
copy = false,
global: isGlobal = false,
filters = { stack: "all", tracker: "all" },
noAgentOverrides = false
} = options;
const scope = getInstallScope(options);
const claudeDir = getClaudeTargetDir(isGlobal);
const githubDir = getTargetDir(isGlobal);
let totalChanges = 0;
const installedSkillsMap = {};
const makeFilter = (category) => (name) => {
const basename = name.replace(/\.(prompt\.md|instructions\.md|md)$/, "");
return shouldIncludeFile(basename, category, filters);
};
const promptsFilter = makeFilter("prompts");
const partialsFilter = makeFilter("partials");
const claudeTransform = (content) => adaptPathsForClaude(transformFrontmatterForClaude(content));
log("\n\u{1F916} Claude Code Installer\n", "bright");
if (dryRun) info("Dry run mode - no files will be copied\n");
if (scope.shouldInstallPrompts) {
info("Installing slash commands...");
const result = copyDir(path.join(TEMPLATES_DIR, "shared", "prompts"), path.join(claudeDir, "commands"), {
force,
dryRun,
filter: promptsFilter,
partialsFilter,
renameFile: (name) => name.replace(/\.prompt\.md$/, ".md"),
transformContent: claudeTransform
});
totalChanges += getChangeCount(result, dryRun);
if (!dryRun && result.written > 0) success(`Installed ${result.written} command files to .claude/commands/`);
}
if (scope.shouldInstallPrompts && !noAgentOverrides) {
const agentsDir = path.join(TEMPLATES_DIR, "shared", "agents");
if (fs.existsSync(agentsDir)) {
info("Installing subagent overrides...");
const result = copyDir(agentsDir, path.join(claudeDir, "agents"), {
force,
dryRun,
filter: (name) => !name.endsWith(".agent.md")
});
totalChanges += getChangeCount(result, dryRun);
if (!dryRun && result.written > 0) success(`Installed ${result.written} agent override(s) to .claude/agents/`);
}
}
if (scope.shouldInstallInstructions) {
info("Installing instructions...");
const result = copyDir(path.join(TEMPLATES_DIR, "shared", "instructions"), path.join(githubDir, "instructions"), {
force,
dryRun,
filter: makeFilter("instructions")
});
totalChanges += getChangeCount(result, dryRun);
if (!dryRun && result.written > 0) success(`Installed ${result.written} instruction files`);
}
if (scope.shouldInstallSkills) {
info("Installing skills (npx skills standard)...");
const result = installSkillsStandard({
isGlobal,
agentSkillsDir: path.join(claudeDir, "skills"),
force,
dryRun,
copy,
dirFilter: makeFilter("skills")
});
totalChanges += getChangeCount(result, dryRun);
for (const [name, meta] of Object.entries(result.installedSkills)) {
let entry = installedSkillsMap[name];
if (entry === void 0) {
entry = { canonicalDir: meta.canonicalDir, agents: [] };
installedSkillsMap[name] = entry;
}
entry.agents.push(path.relative(process.cwd(), path.join(claudeDir, "skills")));
}
if (!dryRun && result.written > 0) success(`Installed ${result.written} skill files/links to .claude/skills/`);
}
if (!isGlobal && scope.shouldInstallInstructions) {
const claudeMdPath = path.join(process.cwd(), "CLAUDE.md");
const claudeMdTemplate = path.join(TEMPLATES_DIR, "agents", "CLAUDE.md");
if (fs.existsSync(claudeMdTemplate)) {
const exists = fs.existsSync(claudeMdPath);
if (exists && !force) {
info("CLAUDE.md already exists (use --force to overwrite)");
} else {
totalChanges++;
if (dryRun) {
info(exists ? "Would update CLAUDE.md" : "Would create CLAUDE.md");
} else {
fs.copyFileSync(claudeMdTemplate, claudeMdPath);
success(exists ? "Updated CLAUDE.md" : "Created CLAUDE.md");
}
}
}
}
totalChanges += getChangeCount(ensureConfigFile({ dryRun, global: isGlobal }), dryRun);
if (!isGlobal && !dryRun && scope.shouldInstallSkills && Object.keys(installedSkillsMap).length > 0) {
const skillsForLock = {};
for (const [name, meta] of Object.entries(installedSkillsMap)) {
skillsForLock[name] = { computedHash: computeSkillHash(meta.canonicalDir), agents: meta.agents };
}
writeLockfile({ skills: skillsForLock, config: filters, packageVersion: VERSION });
appendSkillsToGitignore(process.cwd());
}
console.log("");
if (dryRun) {
info(`Dry run complete. ${totalChanges} files would be installed.`);
} else if (totalChanges > 0) {
success(`Installation complete! ${totalChanges} files installed.`);
console.log("");
if (isGlobal) {
info("Next steps:");
console.log(" 1. Update ~/.agents-toolkit.json with your defaults");
console.log(" 2. Claude commands are now available globally");
} else {
info("Next steps:");
console.log(" 1. Update .agents-toolkit.json with your Jira project key");
console.log(" 2. Configure Atlassian MCP in Claude Code settings");
console.log(" 3. Run slash commands with /analyze-ticket, /work-ticket, etc.");
}
} else {
warn("No new files installed. Use --force to overwrite existing files.");
}
console.log("");
}
function restore(options = {}) {
const { dryRun = false, copy = false } = options;
log("\n\u{1F504} Agents Toolkit Restore\n", "bright");
const lockfile = readLockfile();
if (!lockfile) {
error(`No ${LOCKFILE_NAME} found. Run "install" first to generate it.`);
process.exit(1);
}
if (lockfile.packageVersion !== VERSION) {
warn(`Lockfile was created with v${lockfile.packageVersion}, current package is v${VERSION}.`);
warn('Run "update" to refresh the lockfile for the current version.');
}
if (dryRun) info("Dry run mode - no files will be restored\n");
const stack = lockfile.config.stack;
const tracker = lockfile.config.tracker;
const filters = { stack, tracker };
const makeFilter = (category) => (name) => {
const basename = name.replace(/\.(prompt\.md|instructions\.md|md)$/, "");
return shouldIncludeFile(basename, category, filters);
};
const agentDirs = /* @__PURE__ */ new Set();
for (const meta of Object.values(lockfile.skills)) {
for (const agentDir of meta.agents) {
agentDirs.add(agentDir);
}
}
let totalRestored = 0;
for (const agentDir of agentDirs) {
const agentSkillsDir = path.join(process.cwd(), agentDir);
const result = installSkillsStandard({
isGlobal: false,
agentSkillsDir,
force: true,
dryRun,
copy,
dirFilter: makeFilter("skills")
});
totalRestored += dryRun ? result.planned : result.written;
}
if (dryRun) {
info(`Dry run complete. ${totalRestored} files would be restored.`);
return;
}
const canonicalDir = getAgentsSkillsDir(false);
let allMatch = true;
for (const [name, meta] of Object.entries(lockfile.skills)) {
const canonicalSkillDir = path.join(canonicalDir, name);
const hash = computeSkillHash(canonicalSkillDir);
if (hash !== meta.computedHash) {
warn(
`Hash mismatch for skill "${name}" \u2014 expected ${meta.computedHash?.slice(0, 12)}\u2026 got ${hash?.slice(0, 12)}\u2026`
);
allMatch = false;
}
}
console.log("");
if (allMatch) {
success(`Restored ${Object.keys(lockfile.skills).length} skills successfully.`);
} else {
warn('Restored with hash mismatches. Run "update" to refresh the lockfile.');
}
console.log("");
}
function status() {
log("\n\u{1F4CA} Agents Toolkit Status\n", "bright");
const lockfile = readLockfile();
if (!lockfile) {
error(`No ${LOCKFILE_NAME} found. Run "install" first to generate it.`);
process.exit(1);
}
const canonicalDir = getAgentsSkillsDir(false);
const skills = lockfile.skills;
let hasIssues = false;
if (Object.keys(skills).length === 0) {
info("No skills recorded in lockfile.");
return;
}
if (lockfile.packageVersion !== VERSION) {
warn(`Lockfile package version: v${lockfile.packageVersion} \u2014 current: v${VERSION}`);
}
console.log("");
const COL_NAME = 28;
const COL_STATUS = 14;
const header = `${"Skill".padEnd(COL_NAME)} ${"Status".padEnd(COL_STATUS)} Hash`;
log(header, "cyan");
log("\u2500".repeat(header.length), "cyan");
for (const [name, meta] of Object.entries(skills)) {
const canonicalSkillDir = path.join(canonicalDir, name);
const hash = computeSkillHash(canonicalSkillDir);
let statusLabel;
let statusColor;
if (hash === null) {
statusLabel = "missing";
statusColor = "red";
hasIssues = true;
} else if (hash !== meta.computedHash) {
statusLabel = "modified";
statusColor = "yellow";
hasIssues = true;
} else {
statusLabel = "up-to-date";
statusColor = "green";
}
const hashDisplay = hash !== null ? `${hash.slice(0, 12)}\u2026` : "\u2014";
log(`${name.padEnd(COL_NAME)} ${statusLabel.padEnd(COL_STATUS)} ${hashDisplay}`, statusColor);
}
console.log("");
if (hasIssues) {
warn('Some skills are out of sync. Run "restore" or "update" to fix.');
process.exit(1);
} else {
success(`All ${Object.keys(skills).length} skills are up-to-date.`);
}
console.log("");
}
function list() {
log("\n\u{1F4CB} Available Prompts\n", "bright");
const promptsDir = path.join(TEMPLATES_DIR, "shared", "prompts");
if (!fs.existsSync(promptsDir)) {
error("Templates directory not found");
return;
}
const prompts = fs.readdirSync(promptsDir).filter((f) => f.endsWith(".prompt.md")).map((f) => f.replace(".prompt.md", ""));
const workflowPrompts = [
"analyze-ticket",
"create-plan",
"work-ticket",
"prepare-pr",
"create-pr",
"finalize-pr",
"analyze-github-issue",
"work-github-issue",
"create-github-pr",
"finalize-github-pr"
];
log("Workflow Prompts:", "cyan");
workflowPrompts.forEach((p, i) => {
if (prompts.includes(p)) console.log(` ${i + 1}. ${p}`);
});
console.log("");
log("Utility Prompts:", "cyan");
prompts.filter((p) => !workflowPrompts.includes(p)).forEach((p) => console.log(` \u2022 ${p}`));
console.log("");
log("Partials:", "cyan");
const partialsDir = path.join(promptsDir, "_partials");
if (fs.existsSync(partialsDir)) {
fs.readdirSync(partialsDir).filter((f) => f.endsWith(".md") && f !== "README.md").forEach((p) => console.log(` \u2022 ${p.replace(".md", "")}`));
}
console.log("");
log("Skills:", "cyan");
const skillsDir = path.join(TEMPLATES_DIR, "shared", "skills");
if (fs.existsSync(skillsDir)) {
fs.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).forEach((d) => console.log(` \u2022 ${d.name}`));
}
console.log("");
log("Hooks:", "cyan");
const hooksDir = path.join(TEMPLATES_DIR, "shared", "hooks");
if (fs.existsSync(hooksDir)) {
fs.readdirSync(hooksDir).filter((f) => f.endsWith(".json")).forEach((h) => console.log(` \u2022 ${h.replace(".json", "")}`));
}
console.log("");
}
function showHelp() {
log("\n\u{1F4E6} Agents Toolkit\n", "bright");
console.log("Usage: agents-toolkit <command> [options]\n");
log("Commands:", "cyan");
console.log(" install Install prompts (default target: copilot)");
console.log(" restore Restore skills from agents-toolkit-lock.json");
console.log(" status Check if installed skills match the lockfile");
console.log(" update Update existing prompts and refresh the lockfile");
console.log(" list List available prompts");
console.log(" help Show this help message");
console.log("");
log("Options:", "cyan");
console.log(" --force, -f Overwrite existing files");
console.log(" --global, -g Install to ~/.copilot/ (user-level, all projects)");
console.log(" --target <name> Target installer: copilot | claude | codex");
console.log(" --stack <name> Filter by stack: react | wordpress | all (default: all)");
console.log(" --tracker <name> Filter by tracker: jira | github | all (default: all)");
console.log(" --claude Install for Claude Code (.claude/commands/ + CLAUDE.md)");
console.log(" --codex Install for Codex (AGENTS.md + shared .github files)");
console.log(" --append Append missing AGENTS.md sections instead of overwriting");
console.log(" --prompts-only Only install prompts (no instructions/skills)");
console.log(" --instructions-only Only install instructions");
console.log(" --partials-only Only install partials");
console.log(" --skills-only Only install skills");
console.log(" --hooks-only Only install hooks (PostToolUse validation scripts)");
console.log(" --copy Copy skills instead of symlinking to .agents/skills/");
console.log(" --no-agent-overrides Skip installing agent overrides (.claude/agents/ and .github/agents/)");
console.log(" --dry-run Show what would be installed");
console.log("");
log("Examples:", "cyan");
console.log(" npx agents-toolkit install # All content to .github/");
console.log(" npx agents-toolkit install --global # All content to ~/.copilot/");
console.log(" npx agents-toolkit install --global --stack react # React only to ~/.copilot/");
console.log(" npx agents-toolkit install --stack react # React/TS only");
console.log(" npx agents-toolkit install --stack wordpress # PHP/WordPress only");
console.log(" npx agents-toolkit install --tracker github # GitHub Issues workflow");
console.log(" npx agents-toolkit install --tracker jira # Jira workflow");
console.log(" npx agents-toolkit install --target codex");
console.log(" npx agents-toolkit install --target=claude");
console.log(" npx agents-toolkit install --force");
console.log(" npx agents-toolkit install --append --instructions-only");
console.log(" npx agents-toolkit install --claude --force");
console.log(" npx agents-toolkit install --codex --force");
console.log(" npx agents-toolkit install --prompts-only");
console.log(" npx agents-toolkit list");
console.log("");
}
function parseArgs() {
const args = process.argv.slice(2);
const command = args[0] ?? "help";
const flags = args.slice(1);
let target = null;
let stack = null;
let tracker = null;
for (let i = 0; i < flags.length; i++) {
const arg = flags[i];
if (arg === void 0) break;
if (arg === "--target") {
const value = flags[i + 1];
if (value !== void 0 && !value.startsWith("-")) {
target = value;
i++;
} else {
target = "";
}
} else if (arg.startsWith("--target=")) {
target = arg.split("=").slice(1).join("=");
} else if (arg === "--stack") {
const value = flags[i + 1];
if (value !== void 0 && !value.startsWith("-")) {
stack = value;
i++;
} else {
stack = "";
}
} else if (arg.startsWith("--stack=")) {
stack = arg.split("=").slice(1).join("=");
} else if (arg === "--tracker") {
const value = flags[i + 1];
if (value !== void 0 && !value.startsWith("-")) {
tracker = value;
i++;
} else {
tracker = "";
}
} else if (arg.startsWith("--tracker=")) {
tracker = arg.split("=").slice(1).join("=");
}
}
const options = {
force: flags.includes("--force") || flags.includes("-f"),
global: flags.includes("--global") || flags.includes("-g"),
promptsOnly: flags.includes("--prompts-only"),
partialsOnly: flags.includes("--partials-only"),
skillsOnly: flags.includes("--skills-only"),
instructionsOnly: flags.includes("--instructions-only"),
hooksOnly: flags.includes("--hooks-only"),
dryRun: flags.includes("--dry-run"),
copy: flags.includes("--copy"),
claude: flags.includes("--claude"),
codex: flags.includes("--codex"),
append: flags.includes("--append"),
noAgentOverrides: flags.includes("--no-agent-overrides"),
target,
stack,
tracker
};
return { command, options };
}
function resolveInstallTarget(options) {
const legacyTargets = [];
if (options.claude) legacyTargets.push("claude");
if (options.codex) legacyTargets.push("codex");
let explicitTarget = null;
if (options.target !== null) {
explicitTarget = options.target.trim().toLowerCase();
if (!explicitTarget) {
error("Missing value for --target. Use copilot, claude, or codex.");
process.exit(1);
}
if (!["copilot", "claude", "codex"].includes(explicitTarget)) {
error(`Invalid --target value: ${options.target}. Use copilot, claude, or codex.`);
process.exit(1);
}
}
if (legacyTargets.length > 1) {
error("Use either --claude or --codex, not both.");
process.exit(1);
}
const legacyTarget = legacyTargets[0];
if (explicitTarget !== null && legacyTarget !== void 0 && legacyTarget !== explicitTarget) {
error(`Conflicting target flags: --target ${explicitTarget} and --${legacyTarget}.`);
process.exit(1);
}
if (explicitTarget !== null) return explicitTarget;
if (legacyTarget !== void 0) return legacyTarget;
return "copilot";
}
function main() {
const { command, options } = parseArgs();
const isInstallCommand = command === "install" || command === "update";
const target = isInstallCommand ? resolveInstallTarget(options) : null;
const filters = isInstallCommand ? resolveFilters(options) : { stack: "all", tracker: "all" };
const installOptions = { ...options, filters };
switch (command) {
case "install":
if (target === "claude") installClaude(installOptions);
else if (target === "codex") installCodex(installOptions);
else install(installOptions);
break;
case "restore":
restore(options);
break;
case "status":
status();
break;
case "update":
if (target === "claude") installClaude({ ...installOptions, force: true });
else if (target === "codex") installCodex({ ...installOptions, force: true });
else install({ ...installOptions, force: true });
break;
case "list":
list();
break;
case "help":
case "--help":
case "-h":
showHelp();
break;
default:
error(`Unknown command: ${command}`);
showHelp();
process.exit(1);
}
}
main();
/** Current package version — must match `package.json`. */
declare const VERSION = "2.7.0";
/** Prompt names grouped by `workflow` and `utility` categories. */
declare const PROMPTS: {
workflow: readonly string[];
utility: readonly string[];
};
/** Partial names installed into the `_partials/` subdirectory. */
declare const PARTIALS: readonly string[];
/** Instruction file names installed into `.github/instructions/`. */
declare const INSTRUCTIONS: readonly string[];
/** Skill names installed to the canonical `.agents/skills/` store. */
declare const SKILLS: readonly string[];
/** Hook config names installed to the hooks directory. */
declare const HOOKS: readonly string[];
/** Directory layout for the `npx skills` standard: canonical store and per-agent symlink targets. */
declare const SKILLS_LAYOUT: {
canonicalDir: string;
agentDirs: {
claude: string;
copilot: string;
};
};
/** All prompt names available as Claude Code slash commands during `--claude` install, sorted alphabetically. */
declare const CLAUDE_COMMANDS: readonly string[];
/** Install-target paths for Claude Code (instructions root file, commands dir, skills dir, agents dir). */
declare const CLAUDE_FILES: {
instructions: string;
commandsDir: string;
skillsDir: string;
agentsDir: string;
};
/** Subagent override names shipped by the toolkit (uses frontmatter `name:` field, not filename stem). */
declare const AGENTS: readonly ["Explore", "core-review"];
export { AGENTS, CLAUDE_COMMANDS, CLAUDE_FILES, HOOKS, INSTRUCTIONS, PARTIALS, PROMPTS, SKILLS, SKILLS_LAYOUT, VERSION };
/** Current package version — must match `package.json`. */
declare const VERSION = "2.7.0";
/** Prompt names grouped by `workflow` and `utility` categories. */
declare const PROMPTS: {
workflow: readonly string[];
utility: readonly string[];
};
/** Partial names installed into the `_partials/` subdirectory. */
declare const PARTIALS: readonly string[];
/** Instruction file names installed into `.github/instructions/`. */
declare const INSTRUCTIONS: readonly string[];
/** Skill names installed to the canonical `.agents/skills/` store. */
declare const SKILLS: readonly string[];
/** Hook config names installed to the hooks directory. */
declare const HOOKS: readonly string[];
/** Directory layout for the `npx skills` standard: canonical store and per-agent symlink targets. */
declare const SKILLS_LAYOUT: {
canonicalDir: string;
agentDirs: {
claude: string;
copilot: string;
};
};
/** All prompt names available as Claude Code slash commands during `--claude` install, sorted alphabetically. */
declare const CLAUDE_COMMANDS: readonly string[];
/** Install-target paths for Claude Code (instructions root file, commands dir, skills dir, agents dir). */
declare const CLAUDE_FILES: {
instructions: string;
commandsDir: string;
skillsDir: string;
agentsDir: string;
};
/** Subagent override names shipped by the toolkit (uses frontmatter `name:` field, not filename stem). */
declare const AGENTS: readonly ["Explore", "core-review"];
export { AGENTS, CLAUDE_COMMANDS, CLAUDE_FILES, HOOKS, INSTRUCTIONS, PARTIALS, PROMPTS, SKILLS, SKILLS_LAYOUT, VERSION };
const VERSION = "2.7.0";
const PROMPTS = {
workflow: [
"analyze-github-issue",
"analyze-ticket",
"create-github-pr",
"create-plan",
"create-pr",
"finalize-github-pr",
"finalize-pr",
"prepare-github-release",
"prepare-pr",
"work-github-issue",
"work-ticket"
],
utility: [
"add-tests",
"audit-ai-seo",
"fix-issues",
"new-wp-component",
"new-wp-plugin",
"quality-check",
"resolve-github-reviews",
"review-code"
]
};
const PARTIALS = [
"documentation",
"git-operations",
"github-integration",
"jira-integration",
"pr-template",
"release-node",
"release-wordpress",
"validations"
];
const INSTRUCTIONS = [
"caching",
"css-styling",
"documentation-language",
"github-workflow",
"php-standards",
"react-components",
"seo-ai-optimization",
"server-actions",
"testing-standards",
"tests",
"tsdoc-standards",
"typescript",
"wordpress-plugin-architecture"
];
const SKILLS = [
"ai-seo-optimization",
"component-architecture",
"core-review",
"create-component",
"domain-driven-design",
"github-review-management",
"nextjs-caching",
"plugin-creation",
"quality-checks",
"release-management",
"testing",
"testing-patterns",
"tsdoc-standards"
];
const HOOKS = ["lint-format", "validate-tsx"];
const SKILLS_LAYOUT = {
canonicalDir: ".agents/skills",
agentDirs: {
claude: ".claude/skills",
copilot: ".github/skills"
}
};
const CLAUDE_COMMANDS = [
"add-tests",
"analyze-github-issue",
"analyze-ticket",
"audit-ai-seo",
"create-github-pr",
"create-plan",
"create-pr",
"finalize-github-pr",
"finalize-pr",
"fix-issues",
"new-wp-component",
"new-wp-plugin",
"prepare-github-release",
"prepare-pr",
"quality-check",
"resolve-github-reviews",
"review-code",
"work-github-issue",
"work-ticket"
];
const CLAUDE_FILES = {
instructions: "CLAUDE.md",
commandsDir: ".claude/commands",
skillsDir: ".claude/skills",
agentsDir: ".claude/agents"
};
const AGENTS = ["Explore", "core-review"];
export { AGENTS, CLAUDE_COMMANDS, CLAUDE_FILES, HOOKS, INSTRUCTIONS, PARTIALS, PROMPTS, SKILLS, SKILLS_LAYOUT, VERSION };
---
name: core-review
description: "Run a consistency review before opening a PR or before pushing fixes — a dedicated, read-only pass that catches doc↔code drift, invalid examples, broken links, and stale indexes before a reviewer sees them. Scope: `--budget quick` (diff + directly-touched files), `--budget medium` (diff + one-hop neighbours, default), or `--budget thorough` (whole repo)."
model: Claude Haiku 4.5
tools: ['read', 'search']
user-invocable: true
---
# core-review (Copilot cheap-tier subagent)
A **pre-emptive consistency review** that runs before a reviewer (Copilot or
human) sees the branch. It catches the classes of issues that trigger multi-round
review loops — doc↔code drift, invalid code examples, broken links, stale
indexes — so they are fixed in the first push instead of round 5.
> **Model pin.** `Claude Haiku 4.5` is the cheap tier on Copilot. The VS Code
> model ceiling rule prevents cost escalation: a requested model can never
> exceed the cost tier of the parent conversation, so the agent cannot run more
> expensively than the caller's session. The pin is honoured unless an explicit
> model parameter overrides it at invocation time — which orchestrators do not
> do in normal use. Invoke this agent inline from any smart-tier orchestrator
> and it runs cheap.
>
> **Installed by:** `npx @silverassist/agents-toolkit install` → `.github/agents/core-review.agent.md`.
> Skipped when `--no-agent-overrides` is passed.
## Tools
`read` + `search` only. This agent never edits files. The caller applies findings
and re-runs until the pass reports zero issues.
## What to review (the checklist)
Apply this checklist to the file set the caller specifies. Report findings as:
```text
severity | file:line | problem | suggested fix
```
Severity levels: `critical` (compile/CI break, wrong behavior claim) Ā·
`warning` (stale doc, broken link, missing index entry) Ā· `nit` (wording, formatting).
### 1. Docs ↔ code consistency
- Docs claiming behavior the code does not have.
- A symbol categorized differently across files.
- An instruction contradicting the actual code convention.
- A table/tree entry for a file that does not exist (or a file missing from it).
### 2. Code-example validity
- Every "correct" snippet must compile and match the standard it illustrates.
- No JSDoc patterns in a TSDoc example; no syntactically invalid inline snippets.
### 3. Links and references
- Broken relative links — count the `../` hops from the file's real location.
- Outdated version/path references.
### 4. Markdown hygiene
- Every fenced code block has a language tag.
- No stray empty bullets or blank list items in templates.
### 5. Inventories / tables completeness
README tables, `AGENTS.md` indexes, and `N total` counts must list **all** shipped
assets or be explicitly marked truncated with a total.
### 6. Shell / script robustness
- Stage specific paths, not `git add -A`.
- A failed API call must fail fast, not be treated as an empty result.
- Paginate past the first 100 items.
### 7. Repo health
- `package-lock.json` in sync — after a dependency bump, `npm ci` must exit `0`
(a drifted lockfile fails with `Missing … from lock file`). Regenerate with
`npm install --package-lock-only` and verify.
- CI matrix / workflow config sanity: a `workflow_run` trigger names a workflow
whose `name:` actually exists; `on:` events match intent; least-privilege
`permissions:`.
## Output contract
Return **prioritized findings, most severe first**, one row each:
```text
severity | file:line | problem | suggested fix
```
Empty output ("no findings") is valid and good — say so explicitly.
## Budget table
| `--budget` | Scope |
|---|---|
| `quick` | Diff plus directly-touched files |
| `medium` | Diff + one-hop neighbours (importers, indexes, sibling files) — **default** |
| `thorough` | Whole repository |
The caller resolves the file list and passes it in the task brief. `--budget`
only names the scope; this agent never runs `git diff` itself.
---
name: Explore
description: Read-only codebase exploration and Q&A subagent. Use for information gathering across the workspace without writing any changes; report findings in the caller's requested format and never edit files. Cheap default (haiku) so autonomous cycles can dispatch this liberally without inheriting the parent's smart tier.
tools: Read, Grep, Glob, WebFetch
model: haiku
---
# Explore (cheap-tier override)
> **Filename rule — do not rename this file.** Claude Code loads subagents by
> filename stem (`.claude/agents/<name>.md`) and matches overrides against the
> built-in subagent's exact name. This file overrides Claude's built-in `Explore`
> subagent, so the stem **must** stay `Explore` (case-sensitive: `E` uppercase,
> rest lowercase). Renaming to `EXPLORE.md`, `explore.md`, or any kebab-case
> variant registers a *new* subagent instead of an override, leaving the built-in
> `Explore` on its default (smart) tier — defeating this file's purpose. This is
> a Claude Code protocol requirement, not a stylistic choice.
Override that pins the built-in `Explore` subagent to the cheap tier (`haiku`).
Installs to `.claude/agents/` in the project by default, or to `~/.claude/agents/`
with `--global`; the override applies wherever it lands. Rationale: `Explore` runs during nearly every planning /
review / PR cycle and does only **read-only** searches — the smart tier is
unnecessary and would drive up token cost when the parent conversation is
already on `sonnet`/`opus`.
## Behaviour
- Never writes, edits, or renames files. Never runs mutating shell commands.
- **No shell at all** — the `tools:` allowlist above grants only `Read`, `Grep`, `Glob` and
`WebFetch`. That is deliberate: `tools:` cannot restrict *which* shell commands run, so
granting `Bash` to reach one read-only command (`git diff`) would also grant every mutating
one, dissolving the guarantee above. The consequence for callers is concrete: this subagent
**cannot work out what a branch changed**. Any diff-scoped brief — such as `core-review`
at `--budget quick`/`medium` — must resolve the file list in the caller and paste it in.
Do not "fix" a brief that fails here by adding `Bash`; pass the list.
- Reports findings in the exact format the caller requests (bullet list,
table, code snippets, file+line citations).
- Prefers structural tools (grep/glob) over reading whole files, and reads
large slices in one call over many small reads.
- If a search is genuinely ambiguous or a deeper reasoning step is needed,
says so explicitly so the caller can re-dispatch on the smart tier.
## Override the override
If this project needs a stronger default for exploration, edit the `model:`
line above to a smarter alias, or delete `.claude/agents/Explore.md` entirely
to fall back to Claude's built-in `Explore`. Install with
`--no-agent-overrides` to skip shipping this file in the first place.
+36
-8
{
"name": "@silverassist/agents-toolkit",
"version": "2.6.0",
"version": "2.7.0",
"description": "Reusable AI agent prompts for development workflows with Jira integration — supports GitHub Copilot, Claude Code, and Codex",

@@ -31,9 +31,8 @@ "author": "Santiago Ramirez",

"type": "module",
"main": "src/index.js",
"main": "dist/index.mjs",
"bin": {
"agents-toolkit": "bin/cli.js"
"agents-toolkit": "dist/cli.mjs"
},
"files": [
"bin",
"src/index.js",
"dist",
"templates",

@@ -44,11 +43,40 @@ "README.md",

"scripts": {
"install-prompts": "node bin/cli.js install",
"test": "node --test"
"install-prompts": "node dist/cli.mjs install",
"preinstall-prompts": "npm run build",
"test": "node --test",
"pretest": "npm run build",
"prepack": "npm run build",
"typecheck": "tsc --noEmit",
"build": "unbuild",
"lint": "eslint .",
"lint:md": "markdownlint-cli2",
"format": "prettier --write .",
"format:check": "prettier --check .",
"validate:prompts": "node scripts/validate-prompts.mjs",
"check": "npm run format:check && npm run lint:md && npm run validate:prompts && npm run typecheck && npm run build && npm run lint && npm test",
"prepare": "node -e \"if(!process.env.CI&&process.env.NODE_ENV!=='production')require('child_process').execSync('husky',{stdio:'inherit'})\""
},
"engines": {
"node": ">=18.0.0"
"node": ">=22.0.0"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@microsoft/tsdoc": "^0.16.0",
"@microsoft/tsdoc-config": "^0.18.1",
"@types/node": "^22.20.1",
"eslint": "^10.8.0",
"eslint-plugin-tsdoc": "^0.5.2",
"eslint-plugin-tsdoc-require-2": "^1.2.4",
"husky": "^9.1.7",
"js-yaml": "5.2.3",
"lint-staged": "^16.4.0",
"markdownlint-cli2": "^0.23.2",
"prettier": "^3.9.6",
"typescript": "^5.9.3",
"typescript-eslint": "^8.65.0",
"unbuild": "^3.6.1"
}
}
+53
-17

@@ -18,2 +18,4 @@ # @silverassist/agents-toolkit

- āœ… **PostToolUse Hooks**: Automated validation and formatting after Copilot edits
- āœ… **Model-tier optimization**: All 19 prompts carry hardcoded `model:` pins — 13 on the cheap tier (Claude Haiku 4.5) for mechanical work, 6 on the smart tier (Claude Sonnet 5) for design tasks
- āœ… **Subagent overrides**: `core-review.agent.md` (Copilot) and `Explore.md` (Claude Code) install cheap-tier pinned agents
- āœ… **CLI Tool**: Quick installation in any project

@@ -59,3 +61,3 @@

```
```text
AGENTS.md # Copilot Coding Agent instructions (project root)

@@ -69,3 +71,3 @@ .github/

│ ā”œā”€ā”€ work-ticket.prompt.md
│ └── ... # 10 prompts total (depends on --tracker)
│ └── ... # 19 prompts total (filtered by --tracker and --stack)
ā”œā”€ā”€ instructions/

@@ -75,6 +77,12 @@ │ ā”œā”€ā”€ typescript.instructions.md

│ └── ... # filtered by --stack
└── skills/ # Symlinks → ../../.agents/skills/ (npx skills standard)
ā”œā”€ā”€ domain-driven-design -> ../../.agents/skills/domain-driven-design
ā”œā”€ā”€ testing-patterns -> ../../.agents/skills/testing-patterns
└── ... # filtered by --stack
ā”œā”€ā”€ skills/ # Symlinks → ../../.agents/skills/ (npx skills standard)
│ ā”œā”€ā”€ domain-driven-design -> ../../.agents/skills/domain-driven-design
│ ā”œā”€ā”€ testing-patterns -> ../../.agents/skills/testing-patterns
│ └── ... # filtered by --stack
ā”œā”€ā”€ hooks/ # PostToolUse validation hooks
│ ā”œā”€ā”€ validate-tsx.json
│ ā”œā”€ā”€ lint-format.json
│ └── scripts/
└── agents/ # Copilot custom agents (model-pinned overrides)
└── core-review.agent.md # cheap-tier inline reviewer (@core-review)
.agents/

@@ -106,3 +114,3 @@ └── skills/ # Canonical store (single source of truth)

```
```text
CLAUDE.md # Project instructions for Claude Code (project root)

@@ -115,2 +123,4 @@ .agents/

.claude/
ā”œā”€ā”€ agents/
│ └── Explore.md # cheap-tier Explore override (replaces built-in)
ā”œā”€ā”€ commands/

@@ -121,3 +131,3 @@ │ ā”œā”€ā”€ _partials/

│ ā”œā”€ā”€ work-ticket.md
│ └── ... # 10 commands total (depends on --tracker)
│ └── ... # 19 commands total (filtered by --tracker and --stack)
└── skills/ # Symlinks → ../../.agents/skills/ (read natively by Claude Code)

@@ -137,3 +147,3 @@ ā”œā”€ā”€ domain-driven-design -> ../../.agents/skills/domain-driven-design

```
```text
/analyze-github-issue

@@ -143,2 +153,3 @@ /work-github-issue

/finalize-github-pr
# … 19 total — type / in Claude Code chat to see the full list
```

@@ -156,3 +167,3 @@

```
```text
AGENTS.md # Project instructions for Codex (project root)

@@ -165,3 +176,3 @@ .github/

│ ā”œā”€ā”€ work-ticket.prompt.md
│ └── ... # 10 prompts total (depends on --tracker)
│ └── ... # 19 prompts total (filtered by --tracker and --stack)
ā”œā”€ā”€ instructions/

@@ -264,3 +275,3 @@ │ ā”œā”€ā”€ typescript.instructions.md

```
```text
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”

@@ -280,3 +291,3 @@ │ 1. Analyze │────▶│ 2. Plan │────▶│ 3. Work │

```
```text
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”

@@ -294,2 +305,26 @@ │ 1. Analyze │────▶│ 2. Plan │────▶│ 3. Work │

## Model Pins
All 19 shipped prompts carry a hardcoded `model:` pin. There is no configuration
block, no CLI flag, and nothing resolved at install time — to change a tier, edit
the `model:` line in the installed file.
| Tier | Model | Prompts |
|------|-------|---------|
| **Cheap** | `Claude Haiku 4.5` | `add-tests`, `analyze-github-issue`, `analyze-ticket`, `audit-ai-seo`, `finalize-github-pr`, `finalize-pr`, `fix-issues`, `new-wp-component`, `new-wp-plugin`, `prepare-github-release`, `prepare-pr`, `quality-check`, `review-code` |
| **Smart** | `Claude Sonnet 5` | `create-github-pr`, `create-plan`, `create-pr`, `resolve-github-reviews`, `work-github-issue`, `work-ticket` |
**Per-agent behavior:**
- **GitHub Copilot** — the `model:` pin wins over the Copilot picker. Skills inherit the invoking prompt’s model; use `@core-review` (the custom agent) or a standalone cheap-tier chat for the cheap-tier pass.
- **Claude Code** — Copilot model names are mapped to Claude aliases at install time: `Claude Haiku 4.5` → `haiku`, `Claude Sonnet 5` → `sonnet`. Each skill or slash-command establishes its own model boundary, so `@core-review` from a smart-tier orchestrator stays cheap.
- **Codex** — `model:` is ignored entirely. Control the session tier with `codex --model`.
**Subagent overrides (both cheap tier):**
- **Copilot**: `core-review.agent.md` installs to `.github/agents/`. @-mention it directly as `@core-review` in the chat picker — establishes its own model boundary so the cheap pin is honoured even when called from a smart-tier orchestrator.
- **Claude Code**: `Explore.md` installs to `.claude/agents/`, overriding Claude Code’s built-in `Explore` agent with a cheap-tier pin. `Explore` runs on nearly every autonomous cycle, so pinning it cheap stops the parent’s smart tier from being inherited.
Suppress both: `npx @silverassist/agents-toolkit@latest install --no-agent-overrides`
## CLI Reference

@@ -321,2 +356,3 @@

| `--copy` | Copy skills into each agent dir instead of symlinking to `.agents/skills/` |
| `--no-agent-overrides` | Skip installing agent overrides (`.github/agents/` for Copilot, `.claude/agents/` for Claude Code) |
| `--dry-run` | Show what would be installed without making changes |

@@ -471,3 +507,3 @@

| `component-architecture` | React component patterns, folder structure, naming conventions |
| `core-review` | Whole-repo pre-review (before a PR / before pushing review fixes) run as a read-only pass — inline on Copilot/Codex, optionally a subagent on Claude Code — to preempt Copilot iterations |
| `core-review` | Whole-repo pre-review run as a read-only pass — cheap tier (`model: haiku`). Inline or via `@core-review` custom agent on Copilot (establishes its own model boundary); optionally a subagent on Claude Code. Installs as both a skill and as `core-review.agent.md` in `.github/agents/`. |
| `create-component` | Scaffold a new component in a Silver Assist WordPress plugin (LoadableInterface) |

@@ -488,3 +524,3 @@ | `domain-driven-design` | DDD principles, domain organization, barrel exports |

```
```text
@workspace Use the component-architecture skill to create a new payment form

@@ -542,3 +578,3 @@ ```

```
```text
.github/hooks/ # or ~/.copilot/hooks/ for global

@@ -588,3 +624,3 @@ ā”œā”€ā”€ validate-tsx.json # Hook config (PostToolUse trigger)

- Node.js 18+
- Node.js 22+
- Git installed and configured

@@ -591,0 +627,0 @@ - **For Jira tracker:** Atlassian MCP configured (see below)

@@ -5,3 +5,3 @@ # Codex Agent Instructions

> Always read relevant instruction files from `.github/instructions/` before implementing changes.
>
> **CRITICAL**: This file contains mandatory instructions for Codex when working on this repository.

@@ -15,3 +15,3 @@ > The agent MUST follow these rules when working on issues in this repository.

```
```text
[Instructions]|root:.github/instructions

@@ -53,2 +53,17 @@ |css-styling.instructions.md → CSS/Tailwind patterns, cn() utility, responsive design

## šŸ’° Model-tier discipline
Every shipped prompt carries a hardcoded `model:` pin. **Codex ignores it** — the field exists for Copilot and Claude Code, and Codex reads the same files. Treat it as a recommendation and set the session model with `codex --model`. The rule of thumb:
- **Checklist / mechanical work → cheap tier** (pinned `Claude Haiku 4.5`). Examples: `quality-check`, `review-code`, `fix-issues`, `add-tests`, `prepare-pr`, `finalize-*`, `analyze-*`, `audit-ai-seo`, `prepare-github-release`, `new-wp-*`.
- **Design / reasoning → smart tier** (pinned `Claude Sonnet 5`). Examples: `create-plan`, `work-ticket`, `work-github-issue`, `create-pr`, `create-github-pr`, `resolve-github-reviews`.
**Codex is session-wide, so delegates cannot switch mid-session.** Codex has no per-prompt or per-skill `model:` field; the model is set once per session by `codex --model` (or `~/.codex/config.toml`) and every prompt in that session runs on it. To run a cheap-tier delegate (`quality-check`, `core-review`, `finalize-pr`, …) from a smart-tier session, open a **separate** `codex --model <cheap>` session for that step and relay its output back to the orchestrator's session.
Because Codex does not recognise `model:` as a prompt field, your editor's linter may flag it as an unknown key. That warning is **expected and non-blocking** — the field is inert on Codex, not invalid.
**The same applies to `.agent.md` files** that may be present in `.github/agents/` from a Copilot install — Codex does not recognise the custom-agent format, and the `model:` field there is also inert. Use `codex --model` to set the session tier. (Codex skips installing `.agent.md` files itself; these notes apply only when a repo already has them from a Copilot install.)
---
## āš™ļø Code Conventions (Quick Reference)

@@ -75,3 +90,3 @@

```
```text
āœ… components/user-card/index.tsx

@@ -170,3 +185,3 @@ āŒ components/UserCard.tsx

```
```text
Before ANY push to dev/staging/main:

@@ -173,0 +188,0 @@ ā–” All TypeScript errors resolved

@@ -5,3 +5,3 @@ # Copilot Coding Agent Instructions

> Always read relevant instruction files from `.github/instructions/` before implementing changes.
>
> **CRITICAL**: This file contains mandatory instructions for the GitHub Copilot Coding Agent.

@@ -15,3 +15,3 @@ > The agent MUST follow these rules when working on issues in this repository.

```
```text
[Instructions]|root:.github/instructions

@@ -32,3 +32,3 @@ |caching.instructions.md → Next.js caching: read-vs-mutation fetch, ISR tiers, CDN invalidation

|component-architecture/SKILL.md → Component design patterns
|core-review/SKILL.md → Whole-repo pre-review (read-only pass, inline on Copilot) to preempt Copilot iterations
|core-review/SKILL.md → Whole-repo pre-review (read-only pass; on Copilot use `@core-review` for cheap-tier inline) to preempt Copilot iterations
|domain-driven-design/SKILL.md → DDD principles and structure

@@ -58,2 +58,19 @@ |github-review-management/SKILL.md → Resolve GitHub PR review threads (gh + GraphQL)

## šŸ’° Model-tier discipline
Every shipped prompt carries a hardcoded `model:` pin. The rule of thumb:
- **Checklist / mechanical work → cheap tier** (`Claude Haiku 4.5`, `haiku` on Claude Code). Examples: `quality-check`, `review-code`, `fix-issues`, `add-tests`, `prepare-pr`, `finalize-*`, `analyze-*`, `audit-ai-seo`, `prepare-github-release`, `new-wp-*`.
- **Design / reasoning → smart tier** (`Claude Sonnet 5`, `sonnet` on Claude Code). Examples: `create-plan`, `work-ticket`, `work-github-issue`, `create-pr`, `create-github-pr`, `resolve-github-reviews`.
**Autonomous cycles — delegate model behaviour is platform-specific.**
- **Claude Code**: each delegate has its own `model:` boundary (skills via `SKILL.md`, slash-commands via `.md` frontmatter), so a cheap-tier delegate invoked from a smart-tier orchestrator (`create-github-pr` → `core-review`) runs on the delegate's own pin for that turn. Do **not** force the orchestrator's tier onto delegated steps.
- **Copilot**: `.prompt.md` files AND custom agents (`.agent.md`) establish their own `model:` boundary; skills inherit the invoking prompt's model. An inline `core-review` (or any skill) from a smart-tier orchestrator runs on the smart tier too — to keep it cheap, invoke it as a standalone chat (fresh prompt invocation) rather than inline. Alternatively, invoke `@core-review` (installed by the toolkit to `.github/agents/core-review.agent.md`) — custom agents establish their own model boundary on Copilot, so the cheap pin is honoured when no explicit invocation model is supplied, even inline from a smart-tier orchestrator.
- **Codex**: `model:` is ignored; the session runs one model set by `codex --model` (or `~/.codex/config.toml`). To run a cheap-tier delegate, open a separate `codex --model <cheap>` session for that step.
**To change a tier, edit the `model:` line in the installed file.** There is no tier config and no CLI flag — the pin is the whole mechanism. On Copilot and Claude Code the pin wins over the picker and `/model` respectively (both are consulted only when no pin is set); on Codex use `codex --model`.
---
## āš™ļø Code Conventions (Quick Reference)

@@ -80,3 +97,3 @@

```
```text
āœ… components/user-card/index.tsx

@@ -199,3 +216,3 @@ āŒ components/UserCard.tsx

```
```text
Before ANY push to dev/staging/main:

@@ -202,0 +219,0 @@ ā–” All TypeScript errors resolved

@@ -10,2 +10,3 @@ # Claude Code Instructions

### Phase 1: Initial Analysis
1. **Analyze the request** - Understand the full scope, dependencies, and potential impacts

@@ -17,2 +18,3 @@ 2. **Search existing code** - Use semantic search and grep to understand current implementation

### Phase 2: Planning Documentation
1. **Create planning document** - `docs/[feature-name]-plan.md` with:

@@ -29,3 +31,5 @@ - Problem statement and objectives

### Phase 3: Implementation by Phases
For each phase:
1. **Mark TODO as in-progress** - Update status before starting work

@@ -39,2 +43,3 @@ 2. **Implement changes** - Make code changes following the plan

### Phase 4: Final Documentation
1. **Create final documentation** - `docs/[feature-name].md`

@@ -46,2 +51,3 @@ 2. **Update related docs** - Update `project-overview.md`, `readme.md`, etc.

### Key Principles
- āœ… **One commit per phase** - Create clear checkpoint commits

@@ -69,2 +75,17 @@ - āœ… **Test everything** - Run full test suite after each phase

## Model-tier discipline
Every shipped slash command carries a hardcoded `model:` pin (the installer rewrites the Copilot model name to the matching Claude alias). Aliases rather than pinned version IDs, so they track the current generation without maintenance. The rule of thumb:
- **Checklist / mechanical work → `haiku`.** Commands: `/quality-check`, `/review-code`, `/fix-issues`, `/add-tests`, `/prepare-pr`, `/finalize-*`, `/analyze-*`, `/audit-ai-seo`, `/prepare-github-release`, `/new-wp-*`.
- **Design / reasoning → `sonnet` (explicit, not inherited).** Commands: `/create-plan`, `/work-ticket`, `/work-github-issue`, `/create-pr`, `/create-github-pr`, `/resolve-github-reviews`.
**Autonomous cycles honour each callee's own pin.** A smart-tier command that delegates to a cheap-tier skill or command lets the delegate's `model:` win for that turn — skills honour `model:` only while active, so the outer chat's model is preserved when the delegate finishes.
**To change a tier, edit the `model:` line in `.claude/commands/<command>.md`.** There is no tier config and no CLI flag. The pin **wins over `/model`** — Claude Code consults `/model` only when the invoked command has no `model:` frontmatter, so editing the file is the only way to change a pinned command's tier.
For subagent spawns specifically, `CLAUDE_CODE_SUBAGENT_MODEL` in your shell forces a specific model for every subagent regardless of the calling command's pin.
**When to escalate `sonnet` → `opus`.** `sonnet` is the default smart tier because the 6 orchestrator commands are checklist-driven — the prompt itself supplies the structure — so a heavier model buys little. Reach for `opus` only when the task requires **long, unstructured reasoning**: novel architecture spanning many layers, cross-repository renames whose blast radius is not knowable upfront, or research where the model is genuinely inventing the plan rather than following one. Edit the command's `model:` line to `opus` before invoking it and revert afterward; `/model opus` **cannot** override a `model: sonnet` pin.
## Key Technologies & Frameworks

@@ -83,2 +104,3 @@

**Core Principles**:
1. **Group by Domain, Not by Type** - Organize files by business domain rather than technical type

@@ -89,2 +111,3 @@ 2. **Clear Boundaries** - Each domain has well-defined responsibilities

**Quick Rules**:
- āœ… Create domain folders that match business concepts

@@ -119,3 +142,3 @@ - āœ… Keep domain-specific utilities inside domain folders

```
```text
āœ… CORRECT:

@@ -202,3 +225,3 @@ src/components/user-profile/index.tsx

```
```text
TYPE-XXX: Brief description

@@ -205,0 +228,0 @@

@@ -10,2 +10,3 @@ # Copilot Instructions

### Phase 1: Initial Analysis
1. **Analyze the request** - Understand the full scope, dependencies, and potential impacts

@@ -17,2 +18,3 @@ 2. **Search existing code** - Use semantic search and grep to understand current implementation

### Phase 2: Planning Documentation
1. **Create planning document** - `docs/[feature-name]-plan.md` with:

@@ -29,3 +31,5 @@ - Problem statement and objectives

### Phase 3: Implementation by Phases
For each phase:
1. **Mark TODO as in-progress** - Update status before starting work

@@ -39,2 +43,3 @@ 2. **Implement changes** - Make code changes following the plan

### Phase 4: Final Documentation
1. **Create final documentation** - `docs/[feature-name].md`

@@ -46,2 +51,3 @@ 2. **Update related docs** - Update `project-overview.md`, `readme.md`, etc.

### Key Principles
- āœ… **One commit per phase** - Create clear checkpoint commits

@@ -53,2 +59,15 @@ - āœ… **Test everything** - Run full test suite after each phase

## Model-tier discipline
Every shipped `.prompt.md` carries a hardcoded `model:` pin, written as a single value. The rule of thumb:
- **Checklist / mechanical work → cheap tier** (`Claude Haiku 4.5`). Prompts: `quality-check`, `review-code`, `fix-issues`, `add-tests`, `prepare-pr`, `finalize-*`, `analyze-*`, `audit-ai-seo`, `prepare-github-release`, `new-wp-*`.
- **Design / reasoning → smart tier** (`Claude Sonnet 5`). Prompts: `create-plan`, `work-ticket`, `work-github-issue`, `create-pr`, `create-github-pr`, `resolve-github-reviews`.
**Model boundaries on Copilot: prompt files and custom agents yes, skills no.** VS Code Copilot honours `model:` on `.prompt.md` files and `.agent.md` custom agents, but **not** on skills — skills inherit the invoking prompt's model. So a smart-tier orchestrator invoking `core-review` (or any other skill) inline runs the skill on the smart tier too. When a smart-tier orchestrator chains into another **prompt** that establishes a fresh `model:` boundary (an explicit new invocation of `quality-check.prompt.md`, `prepare-pr.prompt.md`, …), the invoked prompt's own pin wins. To force a cheap-tier delegate, invoke it as a **separate chat / fresh prompt invocation** rather than referencing it inline from the orchestrator. Alternatively, invoke `@core-review` — custom agents establish their own model boundary, so the cheap pin in `.github/agents/core-review.agent.md` is honoured when no explicit invocation model is supplied, even from within a smart-tier orchestrator.
**To change a tier, edit the `model:` line in `.github/prompts/<name>.prompt.md`.** There is no tier config and no CLI flag. The pin **wins over the picker** — VS Code Copilot consults the picker only when the invoked prompt has no `model:` frontmatter.
**Keep `model:` a single value, not a list.** A prioritized array is undocumented for prompt files and GitHub Copilot CLI rejects it outright (`model: Expected string, received array`). If the pinned model is unavailable, Copilot falls back to its own default — the toolkit does not ship fallback chains.
## Key Technologies & Frameworks

@@ -67,2 +86,3 @@

**Core Principles**:
1. **Group by Domain, Not by Type** - Organize files by business domain rather than technical type

@@ -73,2 +93,3 @@ 2. **Clear Boundaries** - Each domain has well-defined responsibilities

**Quick Rules**:
- āœ… Create domain folders that match business concepts

@@ -75,0 +96,0 @@ - āœ… Keep domain-specific utilities inside domain folders

@@ -36,2 +36,3 @@ ---

2b. **Make a POST-read page cacheable at the rendering/edge layer.** Pick one (lowest risk first):
- **CDN edge override (current/default):** `src/proxy.ts` matches city/community paths and sets

@@ -38,0 +39,0 @@ `Cache-Control: public, s-maxage=2592000, stale-while-revalidate=2592000` — the same header ISR

@@ -28,2 +28,3 @@ ---

**Exception — Spanish allowed:**
- User-facing content in WordPress admin (translation files `.pot`, `.po`)

@@ -45,3 +46,3 @@ - Content entered by end users

- One `# H1` per file (document title)
- Specify language in code blocks (```php, ```bash)
- Specify language in code blocks (`php`, `bash`)
- Use `**Bold**` for important terms, `` `code` `` for inline code

@@ -65,3 +66,3 @@ - Use numbered lists for sequential steps, bullet lists for non-sequential

```
```text
type(scope): brief description

@@ -92,3 +93,4 @@

**Good:**
```
```text
feat: Add unresolved errors filter with resolved badge

@@ -104,3 +106,4 @@

**Bad:**
```
```text
updates

@@ -119,3 +122,3 @@ Fixed bug

```
```text
WEB-XXX: Brief description

@@ -141,3 +144,3 @@ ```

```
```text
feature/WEB-XXX-description # New features

@@ -144,0 +147,0 @@ bugfix/WEB-XXX-description # Bug fixes

@@ -15,3 +15,3 @@ ---

```
```text
main → Production branch (default)

@@ -179,2 +179,3 @@ feature/description → New features

When creating jobs that only run for Dependabot PRs, **NEVER** use job-level `if: github.actor == 'dependabot[bot]'`. This causes the job to be "skipped", which can:
- Fail branch protection rules that require the job to pass

@@ -184,2 +185,3 @@ - Show confusing status in the PR checks

**āŒ WRONG — Job skipped for non-Dependabot PRs:**
```yaml

@@ -196,2 +198,3 @@ jobs:

**āœ… CORRECT — Job runs, steps conditionally execute:**
```yaml

@@ -198,0 +201,0 @@ jobs:

@@ -10,3 +10,3 @@ ---

```
```text
components/

@@ -13,0 +13,0 @@ └── domain/ # Domain folder (auth, checkout, etc.)

@@ -13,3 +13,3 @@ ---

```
```text
actions/

@@ -31,3 +31,3 @@ ā”œā”€ā”€ auth/

```
```text
actions/

@@ -183,2 +183,3 @@ ā”œā”€ā”€ auth-actions.ts

### 1. Always use "use server" directive
```typescript

@@ -189,2 +190,3 @@ "use server";

### 2. ALWAYS authenticate and authorize
```typescript

@@ -210,2 +212,3 @@ // āŒ INCORRECT: No auth check

### 3. NEVER trust client input
```typescript

@@ -229,2 +232,3 @@ // āŒ INCORRECT: Trusting client data

### 4. Return state, don't redirect
```typescript

@@ -239,2 +243,3 @@ // āŒ INCORRECT: Don't call redirect in actions

### 5. Validate all inputs with schema validation
```typescript

@@ -267,2 +272,3 @@ import { z } from "zod";

### 6. Handle errors gracefully (don't leak internals)
```typescript

@@ -284,2 +290,3 @@ try {

### 7. Revalidate after mutations
```typescript

@@ -292,2 +299,3 @@ revalidatePath("/affected-path");

### 8. Avoid mutations during rendering
```typescript

@@ -385,2 +393,3 @@ // āŒ INCORRECT: Side effect during render

1. **Self-hosting with multiple servers**: Configure a consistent encryption key:
```bash

@@ -390,2 +399,3 @@ # .env

```
The key must be AES-GCM encrypted and consistent across all servers.

@@ -399,2 +409,3 @@

4. **Corrupted cache**: Clear the Next.js cache:
```bash

@@ -408,2 +419,3 @@ rm -rf .next

Verify that:
- The function has `"use server"` at the top of the file OR inside the function

@@ -410,0 +422,0 @@ - The form uses `action={formAction}` (not `onSubmit`)

@@ -10,3 +10,3 @@ ---

```
```text
components/

@@ -63,2 +63,3 @@ └── button/

### Rendering
- Component renders without crashing

@@ -69,2 +70,3 @@ - All expected elements are present

### Props
- Default props work correctly

@@ -75,2 +77,3 @@ - Custom props are applied

### User Interactions
- Click handlers work

@@ -81,2 +84,3 @@ - Form inputs update

### Edge Cases
- Empty data handled

@@ -83,0 +87,0 @@ - Null/undefined handled

@@ -32,2 +32,3 @@ ---

## Avoid Nested Ternaries
**āŒ NEVER use nested ternary operators** - they reduce readability significantly.

@@ -34,0 +35,0 @@

@@ -45,2 +45,3 @@ ---

Priority values:
- **10**: Core components (Plugin, Activator, critical services)

@@ -116,3 +117,3 @@ - **20**: Services (business logic, API clients)

```
```text
User Request → Controller → Service → Repository/WordPress API

@@ -160,3 +161,3 @@ ↓

```
```text
plugin-name/

@@ -163,0 +164,0 @@ ā”œā”€ā”€ plugin-name.php # Main plugin file

@@ -78,2 +78,3 @@ # Documentation Partial

Template:
```markdown

@@ -119,3 +120,4 @@ # {Feature Name} Implementation Plan

Template:
```markdown
````markdown
# {Feature Name}

@@ -134,2 +136,3 @@

### Advanced Usage
```typescript

@@ -140,11 +143,15 @@ // More complex example

## Configuration
Any configuration options.
## API Reference
If applicable, API details.
## Troubleshooting
Common issues and solutions.
```
````
---

@@ -167,2 +174,3 @@

3. **Commit document**:
```bash

@@ -183,2 +191,3 @@ git add docs/{feature-name}-plan.md

2. **Commit documentation**:
```bash

@@ -194,2 +203,3 @@ git add docs/

### Code Documentation
- [ ] JSDoc on all public functions

@@ -201,2 +211,3 @@ - [ ] Props interfaces documented

### Project Documentation
- [ ] Feature documented in `docs/`

@@ -208,2 +219,3 @@ - [ ] README updated if needed

### PR Documentation
- [ ] Clear PR title with ticket ID

@@ -210,0 +222,0 @@ - [ ] Description explains changes

@@ -16,2 +16,3 @@ # Git Operations Partial

1. **Read base branch from config**:
```bash

@@ -47,2 +48,3 @@ BASE_BRANCH=$(node -e "try{const c=require('./.agents-toolkit.json');console.log(c.pr?.targetBranch||c.git?.defaultBranch||'main')}catch{console.log('main')}")

1. **Ensure on latest base branch**:
```bash

@@ -54,2 +56,3 @@ git checkout "$BASE_BRANCH"

2. **Create new branch**:
```bash

@@ -66,2 +69,3 @@ git checkout -b feature/[TICKET-ID]-short-description

1. **Push to remote**:
```bash

@@ -81,2 +85,3 @@ git push -u origin <branch-name>

1. **Fetch latest**:
```bash

@@ -87,2 +92,3 @@ git fetch origin

2. **Rebase on base branch**:
```bash

@@ -98,2 +104,3 @@ git rebase "origin/${BASE_BRANCH}"

4. **Push updated branch**:
```bash

@@ -110,2 +117,3 @@ git push --force-with-lease

1. **View recent commits**:
```bash

@@ -128,2 +136,3 @@ git log --oneline -5

1. **Summary of changes**:
```bash

@@ -134,2 +143,3 @@ git diff --stat

2. **List changed files**:
```bash

@@ -140,2 +150,3 @@ git diff "$BASE_BRANCH" --name-only

3. **Detailed diff**:
```bash

@@ -152,2 +163,3 @@ git diff "$BASE_BRANCH"

1. **Delete local branch**:
```bash

@@ -158,2 +170,3 @@ git branch -d <branch-name>

2. **Delete remote branch**:
```bash

@@ -164,2 +177,3 @@ git push origin --delete <branch-name>

3. **Prune stale branches**:
```bash

@@ -182,2 +196,3 @@ git remote prune origin

These branches require PRs and cannot receive direct commits:
- `main` - Production

@@ -184,0 +199,0 @@ - `dev` - Development

@@ -59,2 +59,3 @@ # GitHub Integration Partial

2. **Comment templates**:
```markdown

@@ -61,0 +62,0 @@ ## Development Started

@@ -66,2 +66,3 @@ # Jira Integration Partial

2. **Comment templates**:
```markdown

@@ -98,2 +99,3 @@ ## Development Started

1. **Add PR comment**:
```markdown

@@ -148,3 +150,3 @@ ## Pull Request

```
```text
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”

@@ -151,0 +153,0 @@ │ Open │───▶│ In Progress │───▶│ In Review │

@@ -15,3 +15,3 @@ # Pull Request Template Partial

```
```text
type: Short description of changes

@@ -21,2 +21,3 @@ ```

Examples:
- `feat: Add rate limiting middleware`

@@ -66,3 +67,3 @@ - `fix: Resolve CORS headers for image requests`

```
```text
{TICKET-ID}: Short description of changes

@@ -72,2 +73,3 @@ ```

Examples:
- `WEB-726: Add font size accessibility controls`

@@ -162,23 +164,4 @@ - `WEB-734: Fix responsive logo sizing in mobile header`

---
**Tips for large PRs**:
## PR Merge Checklist
Before merging:
- [ ] All review comments addressed
- [ ] CI/CD pipeline passes
- [ ] Branch is up-to-date with target
- [ ] No merge conflicts
- [ ] Documentation complete
- [ ] Issue linked and will auto-close
## PR Size Guidelines
| Size | Files Changed | Recommendation |
|------|---------------|----------------|
| Small | 1-5 files | āœ… Ideal |
| Medium | 6-15 files | āš ļø Acceptable |
| Large | 16+ files | āŒ Consider splitting |
**Tips for large PRs**:
- Split into multiple smaller PRs

@@ -193,2 +176,3 @@ - Create base branch for related changes

### Request Changes
```markdown

@@ -203,2 +187,3 @@ **Suggestion:** Consider using X instead of Y because...

### Approval
```markdown

@@ -218,2 +203,3 @@ āœ… LGTM!

Before merging:
- [ ] All review comments addressed

@@ -224,2 +210,3 @@ - [ ] CI/CD pipeline passes

- [ ] Documentation complete
- [ ] Jira ticket updated
- [ ] Issue linked and will auto-close *(Format A — GitHub Issues)*
- [ ] Jira ticket updated *(Format B — Jira)*

@@ -18,2 +18,3 @@ # Validations Partial

Use this order:
- Lint: `npm run lint` (if script exists)

@@ -20,0 +21,0 @@ - TypeScript: `npm run type-check` (if script exists), otherwise `npx tsc --noEmit` when `tsconfig.json` exists

---
agent: agent
description: Add tests for a component or function
model: Claude Haiku 4.5
tools:
- read_file
- grep_search
- create_file
- replace_string_in_file
- run_in_terminal
---

@@ -8,5 +15,8 @@

> **Model:** Cheap tier — `Claude Haiku 4.5` on Copilot, `haiku` on Claude Code (writing tests against an existing pattern is mechanical). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Add tests for **{target-file}** or **{component-name}**.
## Prerequisites
- Reference: `.github/instructions/tests.instructions.md`

@@ -19,2 +29,3 @@

Read the target file to understand:
- Exported functions/components

@@ -28,2 +39,3 @@ - Props and interfaces

Based on project structure:
- Components: `src/components/{name}/__tests__/{name}.test.tsx`

@@ -95,2 +107,3 @@ - Hooks: `src/hooks/__tests__/{hook-name}.test.ts`

#### Rendering
- [ ] Renders without crashing

@@ -101,2 +114,3 @@ - [ ] Renders all expected elements

#### Props
- [ ] Default props work

@@ -107,2 +121,3 @@ - [ ] Custom props applied correctly

#### User Interactions
- [ ] Click handlers work

@@ -113,2 +128,3 @@ - [ ] Form inputs update

#### State Changes
- [ ] Initial state correct

@@ -119,2 +135,3 @@ - [ ] State updates properly

#### Edge Cases
- [ ] Empty data handled

@@ -144,2 +161,3 @@ - [ ] Null/undefined handled

### Test Summary
- Tests created: N

@@ -150,2 +168,3 @@ - Passing: N

### Test Cases
1. āœ… Test case 1

@@ -156,2 +175,3 @@ 2. āœ… Test case 2

### Coverage Report
| Metric | Coverage |

@@ -158,0 +178,0 @@ |--------|----------|

---
agent: agent
description: Analyze a GitHub issue without creating branches or making changes
model: Claude Haiku 4.5
tools:
- read_file
- grep_search
- run_in_terminal
- github/*
---

@@ -8,2 +14,4 @@

> **Model:** Cheap tier — `Claude Haiku 4.5` on Copilot, `haiku` on Claude Code (read and summarize). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Analyze GitHub issue **#{issue-number}** in repository **{owner}/{repo}** and provide a comprehensive assessment without making any code changes.

@@ -42,5 +50,7 @@

#### Summary
Brief overview of what needs to be done.
#### Acceptance Criteria
- [ ] Criterion 1

@@ -53,2 +63,3 @@ - [ ] Criterion 2

#### Technical Impact
| Area | Files/Components | Impact Level |

@@ -61,2 +72,3 @@ |------|------------------|--------------|

#### Complexity Estimate
- **Level**: Simple / Medium / Complex

@@ -67,2 +79,3 @@ - **Estimated effort**: X hours/days

#### Risks & Blockers
- Risk 1: Description and mitigation

@@ -72,2 +85,3 @@ - Risk 2: Description and mitigation

#### Dependencies
- Related issues: #X, #Y

@@ -74,0 +88,0 @@ - External dependencies: packages, services

---
agent: agent
description: Analyze a Jira ticket without creating branches or making changes
model: Claude Haiku 4.5
tools:
- read_file
- grep_search
- atlassian/*
---

@@ -8,5 +13,8 @@

> **Model:** Cheap tier — `Claude Haiku 4.5` on Copilot, `haiku` on Claude Code (read and summarize). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Analyze Jira ticket **{ticket-id}** and provide a comprehensive assessment without making any code changes.
## Prerequisites
- Atlassian MCP connection is required

@@ -18,2 +26,3 @@ - Reference: `.github/prompts/_partials/jira-integration.md`

### 1. Verify Jira Access
- Use `getAccessibleAtlassianResources` to confirm connectivity

@@ -23,2 +32,3 @@ - Get the correct cloud ID for subsequent calls

### 2. Fetch Ticket Details
- Use `getJiraIssue` with ticket **{ticket-id}**

@@ -29,2 +39,3 @@ - Request fields: `summary`, `description`, `status`, `issuetype`, `priority`, `assignee`

### 3. Analyze Codebase Impact
- Search the codebase for related components

@@ -39,5 +50,7 @@ - Identify files likely to be modified

#### šŸ“‹ Summary
Brief overview of what needs to be done.
#### āœ… Acceptance Criteria
- [ ] Criterion 1

@@ -48,2 +61,3 @@ - [ ] Criterion 2

#### šŸ” Technical Impact
| Area | Files/Components | Impact Level |

@@ -56,2 +70,3 @@ |------|------------------|--------------|

#### šŸ“Š Complexity Estimate
- **Level**: Simple / Medium / Complex

@@ -62,2 +77,3 @@ - **Estimated time**: X hours/days

#### āš ļø Risks & Blockers
- Risk 1: Description and mitigation

@@ -67,2 +83,3 @@ - Risk 2: Description and mitigation

#### šŸ”— Related
- Related tickets or documentation

@@ -72,2 +89,3 @@ - Similar implementations in codebase

## Output Format
Present findings in a clear, structured format that can be referenced during implementation.
Present findings in a clear, structured format that can be referenced during implementation.
---
agent: agent
description: Run a comprehensive AI SEO optimization audit on the current project. Checks agent-friendly UX, E-E-A-T signals, content quality, technical SEO, and structured data.
model: Claude Haiku 4.5
tools:
- read_file
- grep_search
---

@@ -8,2 +12,4 @@

> **Model:** Cheap tier — `Claude Haiku 4.5` on Copilot, `haiku` on Claude Code (a deterministic checklist). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Run a comprehensive audit of this project for Google AI Search visibility and browser agent readiness.

@@ -10,0 +16,0 @@

---
agent: agent
description: Create a pull request for the current branch linked to a GitHub issue
model: Claude Sonnet 5
tools:
- read_file
- grep_search
- replace_string_in_file
- run_in_terminal
- github/*
---

@@ -8,5 +15,8 @@

> **Model:** Smart tier — `Claude Sonnet 5` on Copilot, `sonnet` on Claude Code (PR authoring and review orchestration). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Create a pull request for the current branch linked to GitHub issue **#{issue-number}**.
## Prerequisites
- Run `prepare-pr` first to ensure code is ready

@@ -28,2 +38,3 @@ - GitHub MCP connection or `gh` CLI required

Verify:
- Branch follows convention: `feature/{issue-number}-*` or `bugfix/{issue-number}-*`

@@ -47,2 +58,3 @@ - All changes are committed

Fetch issue **#{issue-number}** details:
- Get title for PR title

@@ -68,31 +80,77 @@ - Extract acceptance criteria

### 5. Pre-PR core review (whole repo)
### 5. Pre-PR core review
**First, remove the planning document** created by `work-github-issue` (e.g.
`docs/{feature-name}-plan.md`) — it has served its purpose. Deleting it now, **at PR creation
(not at finalization)**, keeps it out of the base branch instead of accumulating in `docs/` after
the merge. Do this **before** the review below, so the pass covers the *final* branch state and
catches any now-stale reference to the removed file (links, indexes, mentions):
**First, remove the planning document(s)** created by `work-github-issue` or `create-plan`
(shipped pattern: `docs/<feature-name>-plan.md`) — they have served their purpose. Deleting them
now, **at PR creation (not at finalization)**, keeps them out of the base branch instead of
accumulating in `docs/` after the merge. Do this **before** the review below, so the pass covers
the *final* branch state and catches any now-stale reference to the removed file (links,
indexes, mentions).
A file is removed only when it satisfies **both** conditions: it was **added on this branch**
vs `$BASE_BRANCH`, and it **carries the planning-doc marker** that `work-github-issue` /
`create-plan` write as the first line:
```markdown
<!-- agents-toolkit:planning-doc -->
```
The marker — not the filename — is what identifies a temporary plan. Filename patterns cannot:
`docs/*-plan.md` also matches a legitimate deliverable such as `docs/rollout-plan.md`, and
`docs/*plan*.md` additionally matches `explanation.md` (ex-**plan**-ation). A plan written
without the marker is simply **not deleted**. That bias is deliberate: leaving a plan doc behind
is a trivial cleanup, while deleting someone's deliverable is not recoverable from the PR.
Two portability details are load-bearing. The loop reads `git diff -z` output **NUL-delimited**,
because a path containing a space word-splits and makes `git rm` abort without removing
anything. And it deliberately avoids `grep -lZ | xargs -0`: BSD `grep` on macOS does **not**
NUL-terminate `-l` output, which silently breaks that chain on the platform many contributors
use. The `while read -d ''` form works on bash 3.2 (macOS's system bash) and needs no `mapfile`.
```bash
# Remove it only if it exists — a missing file is fine, but a real `git rm` error
# (permissions, path typo) must surface, so do not blanket-mask with `|| true`.
if [ -f docs/{feature-name}-plan.md ]; then
git rm docs/{feature-name}-plan.md
MARKER='agents-toolkit:planning-doc'
PLANS=()
while IFS= read -r -d '' f; do
# First line only, and the *complete* HTML comment — not the bare token.
# A whole-file grep would match a contributing guide that merely mentions the
# convention, and a substring match would still catch a heading like
# `# agents-toolkit:planning-doc notes`. `([[:space:]].*)?` allows optional
# metadata (e.g. `issue={issue-number}`) including any `-` in its value.
head -n 1 "$f" 2>/dev/null | grep -qE "^<!--[[:space:]]*${MARKER}([[:space:]].*)?-->[[:space:]]*$" && PLANS+=("$f")
done < <(git diff --name-only -z "$BASE_BRANCH" --diff-filter=A -- 'docs/*.md' 'docs/**/*.md')
if [ ${#PLANS[@]} -eq 0 ]; then
echo "No marked planning docs added on this branch — nothing to remove."
else
echo "Removing planning docs added on this branch:"
printf ' %s\n' "${PLANS[@]}"
# No `|| true` mask: a real git rm failure (permissions, unmerged path) must surface.
git rm -- "${PLANS[@]}"
fi
```
Now run a **whole-repo** consistency review to catch the doc↔code drift, invalid code examples,
broken links, and stale indexes that otherwise trigger multi-round Copilot reviews. Review the
whole repo — not just the diff — because Copilot re-reviews entire files.
Now run a **consistency review** to catch the doc↔code drift, invalid code examples,
broken links, and stale indexes that otherwise trigger multi-round Copilot reviews.
Run the **`core-review` skill** (`.agents/skills/core-review/SKILL.md`) as a dedicated,
read-only review pass. It works on every agent — only the mechanism differs (subagents are a
Claude-Code-only optimization, not a requirement):
Run the **`core-review` skill** (`.agents/skills/core-review/SKILL.md`) with **`--budget medium`**
(diff + one-hop neighbours: importers, indexes, sibling files). This is the pre-PR pass — the
diff is complete, so a `quick` pass would miss cross-file drift, but `thorough` (whole-repo) is
normally overkill at this stage unless the change touches architecture or renames symbols across
layers. Run it as a dedicated, read-only pass. The invocation mechanism varies by agent:
- **GitHub Copilot / Codex** — no subagents; run the checklist **inline as a distinct pass** over
the **whole repository** (not just the diff), producing the prioritized findings list.
- **Claude Code** — optionally delegate to a read-only subagent (`Explore` / `general-purpose`)
with the brief "review the whole repo against the core-review checklist; report
`severity | file:line | problem | suggested fix`; do not edit files."
- **GitHub Copilot** — run the checklist **inline as a distinct pass** over the scope defined by
`--budget medium`. Or, if `.github/agents/core-review.agent.md` is installed, @-mention
`@core-review` with **`--budget quick`** and **pass the file list from the `git diff` above in the brief** —
the agent has no shell and cannot run `git diff` itself.
- **Codex** — no subagents; run the checklist inline as a distinct pass over
the scope defined by `--budget medium` (diff + one-hop neighbours), producing the prioritized
findings list.
- **Claude Code** — optionally delegate to a read-only subagent (`Explore` / `general-purpose`).
**Resolve the file list here first and paste it into the brief** — the shipped `Explore`
override has no shell (`tools: Read, Grep, Glob, WebFetch`, so it stays read-only) and cannot
run `git diff` itself. Reuse the `git diff --name-only "$BASE_BRANCH"` output from Step 2 and
add its one-hop neighbours (importers/consumers, sibling files, docs/indexes that list the
changed symbol or asset). Then brief it: "review these files — `<list>` — against the
core-review checklist; report `severity | file:line | problem | suggested fix`; do not edit
files." Passing an explicit list is what scopes the pass to `--budget medium`.

@@ -105,8 +163,31 @@ Apply every `critical` and `warning` finding — including any stale reference exposed by removing

```bash
# No `|| true`: a failed commit (hooks, signing, identity) must stop the flow, not be masked —
# otherwise Step 6 would push without the planning-doc removal or the review fixes.
git commit -m "docs: Remove planning doc for #{issue-number} ahead of PR (+ review fixes)"
# Stage before committing: `git rm` staged the planning-doc deletion, but the review fixes
# you just applied are unstaged working-tree edits — and a fix that *creates* a file (a new
# test, a snapshot) is untracked, which no `git diff` variant reports. `git status
# --porcelain` covers modifications, additions, and deletions alike.
#
# Conditional because a clean branch reaches here legitimately: no planning doc to remove and
# a review pass with zero findings leaves nothing to commit, and an unconditional `git commit`
# would abort that flow with "nothing to commit".
if [ -n "$(git status --porcelain)" ]; then
git add -A
# This block also runs when there was no planning doc and the review produced fixes, so
# the message is derived from git state, not from PLANS (a shell-local variable that is
# not available if the removal block ran in a separate shell invocation).
if git diff --cached --name-only --diff-filter=D -- 'docs/*.md' 'docs/**/*.md' | grep -q .; then
MSG="docs: Remove planning doc for #{issue-number} ahead of PR (+ review fixes)"
else
MSG="chore: Apply pre-PR review fixes for #{issue-number}"
fi
# No `|| true`: a failed commit (hooks, signing, identity) must stop the flow, not be masked —
# otherwise Step 6 would push without the planning-doc removal or the review fixes.
git commit -m "$MSG"
fi
# The worktree must be clean before pushing — this must print nothing.
git status --porcelain
# Enforce, don't just report: the push must never carry uncommitted work.
if [ -n "$(git status --porcelain)" ]; then
echo "Worktree still dirty after commit — resolve before pushing:" >&2
git status --porcelain >&2
exit 1
fi
```

@@ -123,3 +204,4 @@

#### PR Title
```
```text
{Issue title}

@@ -182,2 +264,3 @@ ```

#### PR Settings
- **Source**: Current branch

@@ -184,0 +267,0 @@ - **Target**: `<base-branch>` resolved from `.agents-toolkit.json` (fallback: `main`)

---
agent: agent
description: Create a detailed implementation plan for a feature
model: Claude Sonnet 5
tools:
- read_file
- grep_search
- create_file
- run_in_terminal
---

@@ -8,5 +14,8 @@

> **Model:** Smart tier — `Claude Sonnet 5` on Copilot, `sonnet` on Claude Code (planning is real design reasoning). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Create a detailed implementation plan for: **{feature-description}**
## Prerequisites
- Reference: `.github/prompts/_partials/documentation.md` for plan template

@@ -19,2 +28,3 @@ - Reference: `AGENTS.md` for agent workflow conventions

### 1. Analyze Request
- Break down the feature into components

@@ -25,2 +35,3 @@ - Identify affected areas of the codebase

### 2. Research Current State
- Read relevant source files

@@ -34,6 +45,20 @@ - Understand current architecture

Include these sections:
**The first line must be the removal marker**, exactly:
---
```markdown
<!-- agents-toolkit:planning-doc -->
```
`create-pr` / `create-github-pr` delete the plan at PR time by grepping for this marker, and
they delete **nothing** without it — a plan written without the marker survives into the base
branch. The marker, not the filename, is what identifies the file as temporary, so a legitimate
deliverable like `docs/rollout-plan.md` is never at risk.
Write exactly this, starting at line 1 — the marker must be the **first** line of the file,
with no separator, blank line, or frontmatter above it. The removal step reads only `head -n 1`,
so a plan whose first line is anything else is never cleaned up:
```markdown
<!-- agents-toolkit:planning-doc -->
# {Feature Name} Implementation Plan

@@ -116,5 +141,4 @@

- Team coordination needed
```
---
### 4. Commit Plan

@@ -131,2 +155,3 @@

## Output
The planning document at `docs/{feature-name}-plan.md` ready for implementation.
---
agent: agent
description: Create a pull request for the current branch
model: Claude Sonnet 5
tools:
- read_file
- grep_search
- replace_string_in_file
- run_in_terminal
- atlassian/*
---

@@ -8,5 +15,8 @@

> **Model:** Smart tier — `Claude Sonnet 5` on Copilot, `sonnet` on Claude Code (PR authoring). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Create a pull request for the current branch linked to Jira ticket **{ticket-id}**.
## Prerequisites
- Run `prepare-pr` first to ensure code is ready

@@ -19,2 +29,6 @@ - Reference: `.github/prompts/_partials/pr-template.md`

### 0. Validate ticket ID
Verify that `{ticket-id}` has been replaced with a real ticket identifier (e.g. `WEB-1234`). If the literal string `{ticket-id}` is still present, stop and ask the user to provide the ticket ID.
### 1. Verify Current State

@@ -28,2 +42,3 @@

Verify:
- Branch follows convention: `feature/{ticket-id}-*` or `bugfix/{ticket-id}-*`

@@ -47,2 +62,3 @@ - All changes are committed

Fetch ticket **{ticket-id}** details:
- Get summary for PR title

@@ -62,14 +78,65 @@ - Extract acceptance criteria

Fix any issues before proceeding.
Fix any issues before proceeding. If a validation command fails and the fix is not straightforward (e.g. requires business logic decisions or takes more than one iteration), stop and report the failure to the user with the exact error output before attempting further changes.
### 5. Push Branch
### 5. Remove the planning document
The planning document created by `work-ticket` or `create-plan` has served its purpose. Delete it now so it stays out of the base branch after merge.
1. Find `docs/*.md` files **added on this branch** vs `$BASE_BRANCH`.
2. Keep only files whose **first line** matches `<!-- agents-toolkit:planning-doc … -->` (bare token or with optional metadata like `ticket={ticket-id}`).
3. `git rm` matching files and commit. If none match, skip.
4. Stage and commit any remaining working-tree changes, then verify the tree is clean.
```bash
MARKER='agents-toolkit:planning-doc'
PLANS=()
while IFS= read -r -d '' f; do
head -n 1 "$f" 2>/dev/null | grep -qE "^<!--[[:space:]]*${MARKER}([[:space:]].*)?-->[[:space:]]*$" && PLANS+=("$f")
done < <(git diff --name-only -z "$BASE_BRANCH" --diff-filter=A -- 'docs/*.md' 'docs/**/*.md')
if [ ${#PLANS[@]} -eq 0 ]; then
echo "No marked planning docs added on this branch — nothing to remove."
else
echo "Removing planning docs added on this branch:"
printf ' %s\n' "${PLANS[@]}"
git rm -- "${PLANS[@]}"
git commit -m "docs: Remove planning doc for {ticket-id} ahead of PR"
fi
```
```bash
if [ -n "$(git status --porcelain)" ]; then
git add -A
git commit -m "{ticket-id}: Apply pre-PR validation fixes"
fi
if [ -n "$(git status --porcelain)" ]; then
echo "Worktree still dirty after commit — resolve before pushing:" >&2
git status --porcelain >&2
exit 1
fi
```
<details>
<summary>Why these exact commands?</summary>
**Marker over filename**: `docs/*-plan.md` also matches legitimate deliverables like `docs/rollout-plan.md`, and `docs/*plan*.md` additionally matches `explanation.md` (ex-**plan**-ation). The marker is what `work-ticket`/`create-plan` write as line 1; a plan without it is simply not deleted. Leaving a plan behind is trivial to clean up; deleting a deliverable is not recoverable.
**NUL-delimited paths**: `git diff -z` NUL-delimits output so paths containing spaces don't word-split and break `git rm`. `grep -lZ | xargs -0` is avoided because BSD `grep` on macOS does not NUL-terminate `-l` output, silently breaking that chain. The `while read -d ''` form works on bash 3.2 (macOS's system bash) and needs no `mapfile`.
**`git status --porcelain` over `git diff --quiet`**: A fix that creates a new file (e.g. a new test or snapshot) leaves it untracked; no `git diff` variant reports untracked paths. `git status --porcelain` catches them all. No `|| true` on `git commit`: a failed commit (hooks, signing, identity) must stop the flow, not be masked.
</details>
### 6. Push Branch
```bash
git push -u origin $(git branch --show-current)
```
### 6. Create Pull Request
### 7. Create Pull Request
#### PR Title
```
```text
{ticket-id}: {Ticket Summary}

@@ -117,9 +184,11 @@ ```

#### PR Settings
- **Source**: Current branch
- **Target**: `<base-branch>` resolved from `.agents-toolkit.json` (fallback: `main`)
- **Reviewers**: Based on changed files
- **Reviewers**: Read `.github/CODEOWNERS` and map changed files to owners. If no CODEOWNERS file exists, leave the reviewers field empty and note it in the Output report.
### 7. Link PR to Jira
### 8. Link PR to Jira
Add comment to Jira ticket:
```markdown

@@ -138,2 +207,3 @@ ## Pull Request Created

Report:
1. āœ… PR URL

@@ -144,4 +214,5 @@ 2. āœ… Jira ticket linked

## Next Steps
- Wait for review
- Address feedback
- Use `finalize-pr` after approval
---
agent: agent
description: Finalize a pull request after approval and prepare for merge
model: Claude Haiku 4.5
tools:
- read_file
- run_in_terminal
- github/*
---

@@ -8,5 +13,8 @@

> **Model:** Cheap tier — `Claude Haiku 4.5` on Copilot, `haiku` on Claude Code (validation plus `git`/`gh` mechanics). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Finalize PR for GitHub issue **#{issue-number}** after approval and prepare for merge.
## Prerequisites
- PR has been approved

@@ -27,2 +35,3 @@ - GitHub MCP connection or `gh` CLI required

Check:
- All required approvals in place

@@ -51,6 +60,11 @@ - CI/CD pipeline passed

> **Before pushing any fix commit**, run a **whole-repo core review** (not just the changed
> files) using the **`core-review` skill** (`.agents/skills/core-review/SKILL.md`) as a dedicated
> read-only pass (inline on Copilot/Codex; optionally a subagent on Claude Code). Apply everything
> it flags first — pushing an adjacent, unfixed issue only starts a fresh Copilot round. For the
> **Before pushing any fix commit**, run a **core review** on the fix set using the
> **`core-review` skill** (`.agents/skills/core-review/SKILL.md`) with **`--budget quick`**
> (diff + directly-touched files — the fix set here is tight and self-contained; cross-file
> and one-hop adjacent drift was already covered by the pre-PR `medium` pass in
> `create-github-pr`, so `quick` at this stage only needs to catch self-consistency issues
> inside the fix commits themselves — a doc line the same fix made obsolete, a table row the
> commit forgot to update, a link a rename left behind). Run it as a dedicated read-only pass
> (inline on Copilot/Codex; optionally a subagent on Claude Code). Apply everything it flags
> first — pushing an unfixed self-inconsistency only starts a fresh Copilot round. For the
> full fetch → reply → resolve loop, use the `resolve-github-reviews` prompt.

@@ -67,2 +81,3 @@

If conflicts:
1. Resolve each conflict

@@ -73,2 +88,3 @@ 2. Stage resolved files: `git add <file>`

Push updated branch:
```bash

@@ -81,2 +97,3 @@ git push --force-with-lease

Run complete validation suite:
```bash

@@ -91,2 +108,3 @@ npm run lint --if-present

Verify:
- No regressions after rebase

@@ -105,3 +123,4 @@ - All tests still pass

**Final commit message format**:
```
```text
{Issue title} (#{pr-number})

@@ -108,0 +127,0 @@

---
agent: agent
description: Finalize a pull request after approval and prepare for merge
model: Claude Haiku 4.5
tools:
- read_file
- run_in_terminal
- atlassian/*
---

@@ -8,5 +13,8 @@

> **Model:** Cheap tier — `Claude Haiku 4.5` on Copilot, `haiku` on Claude Code (validation plus `git` mechanics). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Finalize PR for Jira ticket **{ticket-id}** after approval and prepare for merge.
## Prerequisites
- PR has been approved

@@ -22,2 +30,3 @@ - Reference: `.github/prompts/_partials/git-operations.md`

Check:
- All required approvals in place

@@ -30,2 +39,3 @@ - CI/CD pipeline passed

If there are unresolved comments:
- List each comment

@@ -45,2 +55,3 @@ - Address feedback

If conflicts:
1. Resolve each conflict

@@ -51,2 +62,3 @@ 2. Stage resolved files: `git add <file>`

Push updated branch:
```bash

@@ -59,2 +71,3 @@ git push --force-with-lease

Run complete validation suite:
```bash

@@ -69,2 +82,3 @@ npm run lint --if-present

Verify:
- No regressions after rebase

@@ -77,2 +91,3 @@ - All tests still pass

Add comment:
```markdown

@@ -91,2 +106,3 @@ ## Ready for Merge

Transition ticket to appropriate status:
- "In Review" → "Ready for QA" or

@@ -106,3 +122,4 @@ - "In Review" → "Done" (if no QA needed)

**Final commit message format**:
```
```text
{ticket-id}: {Summary of changes}

@@ -133,2 +150,3 @@

Update Jira:
- Transition to "Done" or "Ready for QA"

@@ -142,2 +160,3 @@ - Add deployment comment if applicable

āœ… **Pre-Merge Checklist**
- [ ] All approvals received

@@ -150,2 +169,3 @@ - [ ] CI/CD passed

āœ… **Merge Ready**
- Commit message prepared

@@ -155,2 +175,3 @@ - Merge strategy confirmed

āœ… **Post-Merge Tasks**
- [ ] Local branch deleted

@@ -162,4 +183,5 @@ - [ ] Remote branch deleted

## Notes
- If merge conflicts arise during squash, resolve and complete
- Notify team if deployment is needed
- Update related documentation if this was a major feature
---
agent: agent
description: Fix failing tests and lint errors
model: Claude Haiku 4.5
tools:
- read_file
- grep_search
- replace_string_in_file
- run_in_terminal
---

@@ -8,2 +14,4 @@

> **Model:** Cheap tier — `Claude Haiku 4.5` on Copilot, `haiku` on Claude Code (the findings are already identified). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Fix failing tests, lint errors, and type errors in the codebase.

@@ -25,2 +33,3 @@

Sort issues by type:
- **Lint errors**: ESLint violations

@@ -33,2 +42,3 @@ - **Type errors**: TypeScript compilation errors

#### Auto-fixable
```bash

@@ -39,3 +49,5 @@ npm run lint --if-present -- --fix

#### Manual fixes
For each remaining lint error:
1. Read the error message

@@ -49,2 +61,3 @@ 2. Locate the file and line

For each type error:
1. Read the TypeScript error message

@@ -61,2 +74,3 @@ 2. Understand the type mismatch

For each failing test:
1. Read the test output

@@ -90,10 +104,13 @@ 2. Understand what's expected vs actual

### Remaining Issues
List any issues that couldn't be auto-fixed.
### Changes Made
Summary of fixes applied.
## Notes
- Always commit after fixing each category
- Run full validation after all fixes
- Some issues may require architectural changes

@@ -9,2 +9,3 @@ ---

- create_file
model: Claude Haiku 4.5
---

@@ -14,2 +15,4 @@

> **Model:** Cheap tier — `Claude Haiku 4.5` on Copilot, `haiku` on Claude Code (scaffolding from a template). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Scaffold a new component in the current Silver Assist plugin following the LoadableInterface architecture.

@@ -20,2 +23,3 @@

Ask the user:
1. **Component type** — Service, Controller, View, Model, or Repository?

@@ -44,2 +48,3 @@ 2. **Component name** — e.g., `EmailNotification`, `ReportGenerator`

5. **Verify** — Run PHPCS and PHPStan on the new file:
```bash

@@ -46,0 +51,0 @@ vendor/bin/phpcs path/to/NewFile.php

@@ -9,2 +9,3 @@ ---

- create_file
model: Claude Haiku 4.5
---

@@ -14,2 +15,4 @@

> **Model:** Cheap tier — `Claude Haiku 4.5` on Copilot, `haiku` on Claude Code (scaffolding from a template). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Scaffold a new Silver Assist WordPress plugin with the standard architecture, quality tools, and CI/CD pipeline.

@@ -20,2 +23,3 @@

Ask the user:
1. **Plugin name** — Human-readable name (e.g., "Silver Assist Cache Manager")

@@ -40,2 +44,3 @@ 2. **Plugin slug** — Kebab-case slug (e.g., `silver-assist-cache-manager`)

After scaffolding, run quality checks to verify everything works:
```bash

@@ -42,0 +47,0 @@ composer install

@@ -9,2 +9,3 @@ ---

- create_file
model: Claude Haiku 4.5
---

@@ -14,2 +15,4 @@

> **Model:** Cheap tier — `Claude Haiku 4.5` on Copilot, `haiku` on Claude Code (validation plus release mechanics). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Prepare a new version release and drive it through the **correct GitHub flow** for the current

@@ -21,2 +24,3 @@ project. This prompt is **project-agnostic**: it detects the ecosystem (WordPress plugin vs Node/npm

## Prerequisites
- Reference: `.github/prompts/_partials/git-operations.md`

@@ -31,2 +35,3 @@ - Reference: `.github/prompts/_partials/release-wordpress.md` (WordPress projects)

Ask the user:
1. **Version type** — `patch`, `minor`, or `major`? (default: patch). Suggest one from the

@@ -33,0 +38,0 @@ `[Unreleased]` changelog content: new features → `minor`, fixes only → `patch`, breaking → `major`.

---
agent: agent
description: Prepare code for a pull request by running all validations
model: Claude Haiku 4.5
tools:
- read_file
- grep_search
- replace_string_in_file
- run_in_terminal
---

@@ -8,5 +14,8 @@

> **Model:** Cheap tier — `Claude Haiku 4.5` on Copilot, `haiku` on Claude Code (runs the checks and reports). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Prepare the current branch for a pull request by running all validations.
## Prerequisites
- Reference: `.github/prompts/_partials/validations.md`

@@ -27,2 +36,3 @@ - Reference: `.github/prompts/_partials/git-operations.md`

Verify:
- Not on protected branch (main, dev, stg, master, `${BASE_BRANCH}`)

@@ -35,5 +45,7 @@ - All changes are committed

#### Lint Check
```bash
npm run lint --if-present
```
- Fix auto-fixable: `npm run lint --if-present -- --fix`

@@ -43,2 +55,3 @@ - Report issues needing manual fix

#### Type Check
```bash

@@ -48,2 +61,3 @@ npm run type-check --if-present

```
- Fix any TypeScript errors

@@ -58,2 +72,3 @@ - Ensure no `any` types introduced

```
- Review test results

@@ -66,2 +81,3 @@ - Check coverage report

Verify:
- [ ] No `console.log` or debug statements

@@ -81,2 +97,3 @@ - [ ] No sensitive data exposed (API keys, secrets)

Check:
- Files changed align with ticket scope

@@ -89,2 +106,3 @@ - No unintended changes

Verify commit messages:
- Follow format: `TICKET-ID: Description`

@@ -95,2 +113,3 @@ - Use present tense, imperative mood

If merge commits are present, rebase non-interactively on base branch:
```bash

@@ -111,18 +130,25 @@ git fetch origin

### āœ… Passed Checks
- List all passed checks
### āš ļø Warnings
- Issues to address but not blockers
### āŒ Blockers
- Must fix before proceeding
### šŸ“ Changed Files
- List all modified files
### šŸ“ Summary
Brief summary for PR description
### šŸ‘„ Suggested Reviewers
Based on changed files:
- @reviewer1 (reason)

@@ -132,4 +158,5 @@ - @reviewer2 (reason)

## Next Steps
- Fix any blockers
- Address warnings
- Use `create-pr` to create the pull request

@@ -8,2 +8,3 @@ ---

- replace_string_in_file
model: Claude Haiku 4.5
---

@@ -13,2 +14,4 @@

> **Model:** Cheap tier — `Claude Haiku 4.5` on Copilot, `haiku` on Claude Code (runs the tools and reports). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Run the full quality check suite on the current Silver Assist plugin.

@@ -15,0 +18,0 @@

@@ -15,3 +15,3 @@ # Prompts / Commands

```
```text
prompts/

@@ -53,5 +53,46 @@ ā”œā”€ā”€ README.md # This documentation

## Model tiers
Every shipped prompt carries an explicit `model:` pin — on Copilot and Claude Code a fresh install therefore runs cost-optimally with no configuration at all. On Codex the field is ignored; select the session tier with `codex --model` instead. The pins are **hardcoded in the files** — there is no tier config, no CLI flag, and nothing to resolve at install time. **To change a tier on Copilot or Claude Code, edit the `model:` line in the installed file.** That is the whole mechanism, and it is deliberate: a model picker spanning three agents whose model catalogues move independently would cost more to maintain than it saves.
| Tier | Copilot / Codex | Claude Code | Used for |
| --- | --- | --- | --- |
| **Cheap** | `Claude Haiku 4.5` | `haiku` | Checklist / mechanical work — 13 prompts |
| **Smart** | `Claude Sonnet 5` | `sonnet` | Design / reasoning work — 6 prompts |
| Prompt | Tier | Rationale |
| --- | --- | --- |
| `analyze-ticket`, `analyze-github-issue` | Cheap | Read + summarize |
| `add-tests`, `audit-ai-seo`, `fix-issues`, `review-code` | Cheap | Deterministic checklists |
| `new-wp-component`, `new-wp-plugin`, `quality-check` | Cheap | Scaffolding / tool runs |
| `prepare-pr`, `prepare-github-release`, `finalize-pr`, `finalize-github-pr` | Cheap | Validation + git/gh mechanics |
| `create-plan` | Smart | Real design reasoning |
| `work-ticket`, `work-github-issue` | Smart | Implementation orchestration |
| `create-pr`, `create-github-pr` | Smart | PR authoring + review orchestration |
| `resolve-github-reviews` | Smart | The *fix* step needs reasoning |
### How each agent reads the pin
- **Claude Code** — the installer rewrites the Copilot model name to the matching alias, so `Claude Haiku 4.5` installs as `model: haiku`. Aliases track the current generation, so they do not go stale. The pin **wins over `/model`**, which is only consulted when no `model:` is set.
- **Copilot** — the pin is used as shipped. It **wins over the picker**, which is only consulted when no `model:` is set. Note that skills have no independent model boundary — they inherit the invoking prompt's model — while both `.prompt.md` files and custom agents (`.agent.md`) establish their own `model:` boundary. An inline `core-review` from a smart-tier orchestrator therefore runs smart. Invoke the skill as a standalone chat to keep it cheap, or invoke `@core-review` — custom agents establish their own model boundary and the cheap pin in `.github/agents/core-review.agent.md` is honoured when no explicit invocation model is supplied, even inline from a smart-tier orchestrator.
- **Codex** — `model:` is **ignored entirely**; the session runs whatever `codex --model` set. The field is left in place because the Codex installer copies these same shared templates into the same `.github/prompts/` directory Copilot uses, so the frontmatter Copilot needs is simply along for the ride; it produces a non-blocking lint warning and nothing else.
Because the pin is a single scalar, an unavailable model falls back to the agent's own default rather than to a second entry — the toolkit does not ship fallback chains. A prioritized `model:` array is undocumented for prompt files and is rejected outright by GitHub Copilot CLI.
## Tool scoping
Every prompt declares a `tools:` allowlist in its frontmatter. This is a **Copilot-only optimisation**: VS Code uses it to restrict which tools (and therefore which MCP server schemas) are sent to the model on each turn. Claude Code's `allowed-tools` field has opposite semantics — it is a permission pre-approval that does not reduce context — so the toolkit does not mirror `tools:` onto Claude commands.
Two groups of prompts include MCP wildcard entries:
| Wildcard | Prompts | What it covers |
| --- | --- | --- |
| `github/*` | `analyze-github-issue`, `work-github-issue`, `create-github-pr`, `finalize-github-pr`, `resolve-github-reviews` | All tools exposed by the GitHub MCP server |
| `atlassian/*` | `analyze-ticket`, `work-ticket`, `create-pr`, `finalize-pr` | All tools exposed by the Atlassian MCP server |
**Portability note**: `github/*` resolves only when the GitHub MCP server is registered under the name `github` in the user's `mcp.json`; `atlassian/*` similarly requires the name `atlassian`. These are the common default names used by the official MCP packages. If your installation uses a different server name (e.g. `github-mcp`, `jira`), edit the `tools:` list in the installed files to match.
## Workflow Stages
```
```text
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”

@@ -82,6 +123,7 @@ │ 1. Analyze │────▶│ 2. Plan │────▶│ 3. Work │

```
```text
/analyze-ticket
/work-ticket
/create-pr
# … 19 total — type / to open the full command palette
```

@@ -88,0 +130,0 @@

---
agent: agent
description: Fetch, respond to, resolve, and close GitHub PR review comments (Copilot or human)
model: Claude Sonnet 5
tools:
- read_file
- replace_string_in_file
- run_in_terminal
- github/*
---

@@ -8,6 +14,27 @@

> **Model:** Smart tier — `Claude Sonnet 5` on Copilot, `sonnet` on Claude Code (the fix step needs real reasoning). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Clear a pull request's review threads end-to-end: **fetch → address → reply → resolve → verify `0` unresolved**.
Works for both **Copilot** and **human** reviews.
> **Cost note.** The *fetch*, *reply-formatting*, and *resolve* steps are mechanical — they
> parse GraphQL / REST responses and post templated replies. The only step that may genuinely
> need the smart tier is the **code fix** in Step 3. But because `model:` **locks the model
> for the whole invocation** (see paragraph above), you cannot switch mid-run to make the
> mechanical phases cheap. To run those phases cheap, take one of the pre-invocation options:
> (a) invoke the `gh`/GraphQL commands from a separate cheap-tier chat/session and use this
> prompt only for the fix step; or (b) edit this file's `model:` line to the cheap-tier value
> before running and revert after.
>
> The `core-review` pass invoked from Step 3 pins itself cheap **only on Claude Code** (its
> `SKILL.md model: haiku` frontmatter is honoured per-turn there). On **Copilot** skills
> inherit the invoking prompt's model, so `core-review` runs on this prompt's smart tier when
> invoked inline. Two cheap alternatives: (a) invoke `core-review` from a separate cheap-tier
> chat instead of inline, or (b) if `.github/agents/core-review.agent.md` is installed,
> @-mention `@core-review` — custom agents establish their own model boundary, so the
> cheap pin is honoured when no explicit invocation model is supplied. On **Codex**
> the pass runs whatever session model is active (`codex --model`).
## Prerequisites
- `gh` CLI authenticated (`gh auth status`) with `repo` scope — the GraphQL thread-resolve mutation needs it

@@ -18,2 +45,3 @@ - Reference: `.github/prompts/_partials/github-integration.md`

## Inputs
- `{pr-number}` *(optional)* — target PR. Defaults to the PR for the current branch.

@@ -82,3 +110,3 @@ - `{repo}` *(optional)* — `owner/repo` for cross-repo review (adds `--repo` / fills the GraphQL vars).

while : ; do
PAGE=$(gh api graphql -F owner="$OWNER" -F repo="$REPO" -F pr="$PR" -F after="$CURSOR" -f query='
PAGE=$(GH_PAGER=cat gh api graphql -F owner="$OWNER" -F repo="$REPO" -F pr="$PR" -F after="$CURSOR" -f query='
query($owner:String!, $repo:String!, $pr:Int!, $after:String) {

@@ -146,10 +174,17 @@ repository(owner:$owner, name:$repo) {

**Then — once per batch, not per thread** — after **all** the per-thread fixes above are applied,
run a single **whole-repo** consistency pass (the *core review*) before committing the batch. Use
the **`core-review` skill** (`.agents/skills/core-review/SKILL.md`) as a dedicated read-only pass
(inline on Copilot/Codex; optionally a subagent on Claude Code). A fix often leaves or introduces
an adjacent issue (a now-stale doc line, a broken link, a table missing the new asset) that would
trigger yet another Copilot round. Apply everything the pass flags, re-run the checks above, and
only then proceed to Step 4. Running this once over the completed batch — rather than per thread —
keeps the (whole-repo) review cost bounded.
run a single consistency pass (the *core review*) before committing the batch. Use the
**`core-review` skill** (`.agents/skills/core-review/SKILL.md`) with **`--budget quick`**
(diff + directly-touched files — the batch is small and scoped, so `medium`/`thorough` would only
add unrelated noise). Run it as a dedicated read-only pass. Options by agent:
- **GitHub Copilot** — run inline as a distinct pass, or use `@core-review` for a cheap-tier pass — if using the agent, pass the current batch's changed-file list in the brief (the agent has no shell tool).
- **Codex** — run inline as a distinct pass.
- **Claude Code** — optionally delegate to a read-only subagent (`Explore` / `general-purpose`).
A fix often leaves or introduces an adjacent issue (a now-stale doc
line, a broken link, a table missing the new asset) that would trigger yet another Copilot round.
Apply everything the pass flags, re-run the checks above, and only then proceed to Step 4.
Running this once over the completed batch — rather than per thread — keeps the review cost
bounded.
### 4. Commit and push fixes (before replying)

@@ -160,3 +195,3 @@

> Run the whole-repo **core review** from Step 3 *before* this commit — pushing an adjacent,
> Run the **core review** from Step 3 *before* this commit — pushing an adjacent,
> unfixed issue starts a fresh Copilot round and defeats the purpose of resolving in batches.

@@ -220,3 +255,3 @@

```bash
gh api "repos/$OWNER/$REPO/pulls/$PR/comments/$COMMENT_ID/replies" \
GH_PAGER=cat gh api "repos/$OWNER/$REPO/pulls/$PR/comments/$COMMENT_ID/replies" \
-f body="Fixed in $SHA: <what changed>. Thanks!"

@@ -231,3 +266,3 @@ ```

```bash
gh api "repos/$OWNER/$REPO/pulls/$PR/comments" \
GH_PAGER=cat gh api "repos/$OWNER/$REPO/pulls/$PR/comments" \
-f body="Fixed in $SHA: <what changed>." \

@@ -246,3 +281,3 @@ -F in_reply_to="$COMMENT_ID"

```bash
gh api graphql -f id="$THREAD_ID" -f query='
GH_PAGER=cat gh api graphql -f id="$THREAD_ID" -f query='
mutation($id:ID!) {

@@ -264,3 +299,3 @@ resolveReviewThread(input:{threadId:$id}) {

jq -r 'select(.isResolved == false) | .id' /tmp/review-threads.jsonl | while read -r THREAD_ID; do
RESULT=$(gh api graphql -f id="$THREAD_ID" -f query='mutation($id:ID!){ resolveReviewThread(input:{threadId:$id}){ thread { isResolved } } }')
RESULT=$(GH_PAGER=cat gh api graphql -f id="$THREAD_ID" -f query='mutation($id:ID!){ resolveReviewThread(input:{threadId:$id}){ thread { isResolved } } }')
if [ "$(echo "$RESULT" | jq -r '.data.resolveReviewThread.thread.isResolved')" != "true" ]; then

@@ -285,3 +320,3 @@ echo "ERROR: failed to resolve $THREAD_ID: $RESULT" >&2

while : ; do
PAGE=$(gh api graphql -F owner="$OWNER" -F repo="$REPO" -F pr="$PR" -F after="$CURSOR" -f query='
PAGE=$(GH_PAGER=cat gh api graphql -F owner="$OWNER" -F repo="$REPO" -F pr="$PR" -F after="$CURSOR" -f query='
query($owner:String!, $repo:String!, $pr:Int!, $after:String) {

@@ -320,2 +355,3 @@ repository(owner:$owner, name:$repo) {

Then summarize:
- Threads addressed and how (fix commit SHA per finding).

@@ -322,0 +358,0 @@ - Any threads intentionally left with a reply explaining a false positive (resolve those too).

---
agent: agent
description: Quick code review of current changes
model: Claude Haiku 4.5
tools:
- read_file
- grep_search
- run_in_terminal
---

@@ -8,2 +13,4 @@

> **Model:** Cheap tier — `Claude Haiku 4.5` on Copilot, `haiku` on Claude Code (a deterministic checklist). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Perform a quick code review of the current changes.

@@ -26,2 +33,3 @@

#### Code Quality
- [ ] No `console.log` or debug statements

@@ -33,2 +41,3 @@ - [ ] No `any` types

#### Style & Conventions
- [ ] Follows project naming conventions

@@ -40,2 +49,3 @@ - [ ] Imports organized alphabetically

#### Logic
- [ ] No nested ternaries

@@ -47,2 +57,3 @@ - [ ] Early returns used appropriately

#### Documentation
- [ ] JSDoc on new functions

@@ -88,12 +99,16 @@ - [ ] Complex logic has comments

#### āŒ Critical (must fix)
- Issue 1: Description and fix
#### āš ļø Warnings (should fix)
- Warning 1: Description and suggestion
#### šŸ’” Suggestions (nice to have)
- Suggestion 1: Improvement idea
### Overall
- **Status**: Ready / Needs Work
- **Recommendation**: Summary
---
agent: agent
description: Start working on a GitHub issue with full workflow setup
model: Claude Sonnet 5
tools:
- read_file
- grep_search
- create_file
- run_in_terminal
- github/*
---

@@ -8,2 +15,4 @@

> **Model:** Smart tier — `Claude Sonnet 5` on Copilot, `sonnet` on Claude Code (implementation orchestration). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Start working on GitHub issue **#{issue-number}** in repository **{owner}/{repo}** with complete workflow setup.

@@ -22,2 +31,3 @@

Fetch issue **#{issue-number}** with all details:
- Title and description

@@ -32,2 +42,3 @@ - Labels and priority

Read project conventions:
- `AGENTS.md` — Agent instructions (Copilot/Codex)

@@ -42,2 +53,3 @@ - `CLAUDE.md` — Agent instructions (Claude Code)

Search codebase for:
- Related components

@@ -50,3 +62,17 @@ - Existing patterns

Create planning document at `docs/{feature-name}-plan.md`:
Create planning document at `docs/{feature-name}-plan.md`.
**Its first line must be the removal marker**, exactly:
```markdown
<!-- agents-toolkit:planning-doc issue={issue-number} -->
```
`create-github-pr` deletes the plan at PR time by grepping for this marker, and deletes
**nothing** without it — a plan written without the marker survives into the base branch. The
marker, not the filename, is what identifies the file as temporary, so a legitimate deliverable
like `docs/rollout-plan.md` is never at risk.
Then the body:
- Problem statement

@@ -61,2 +87,3 @@ - Current architecture

From latest `main`:
```bash

@@ -80,2 +107,3 @@ git checkout main

Add comment to the issue:
```markdown

@@ -94,2 +122,3 @@ ## Development Started

Report:
1. āœ… Issue summary

@@ -103,3 +132,4 @@ 2. āœ… Branch created

- Begin implementation following the plan
- Use `@core-review` with **`--budget quick`** (if installed) for cheap-tier consistency checks during development — pass the changed-file list in the brief (the agent has no shell and cannot run `git diff` itself)
- Use `prepare-pr` when ready for review
- Use `create-github-pr` to submit pull request
---
agent: agent
description: Start working on a Jira ticket with full workflow setup
model: Claude Sonnet 5
tools:
- read_file
- grep_search
- create_file
- run_in_terminal
- atlassian/*
---

@@ -8,5 +15,8 @@

> **Model:** Smart tier — `Claude Sonnet 5` on Copilot, `sonnet` on Claude Code (implementation orchestration). To change it, edit the `model:` line in this file's frontmatter; the pin wins over the Copilot picker and Claude `/model`. Codex ignores `model:` — set the session model with `codex --model`.
Start working on Jira ticket **{ticket-id}** with complete workflow setup.
## Prerequisites
- Atlassian MCP connection required

@@ -19,2 +29,3 @@ - Reference: `.github/prompts/_partials/jira-integration.md`

### 1. Verify Jira Access
- Use `getAccessibleAtlassianResources` to confirm connectivity

@@ -26,2 +37,3 @@ - Get the correct cloud ID

Fetch ticket **{ticket-id}** with all details:
- Summary and description

@@ -36,2 +48,3 @@ - Issue type and priority

Read project conventions:
- `AGENTS.md` - Main agent workflow guidelines

@@ -45,2 +58,3 @@ - `.github/copilot-instructions.md` - Additional project guidelines (if present)

Search codebase for:
- Related components

@@ -53,3 +67,17 @@ - Existing patterns

Create planning document at `docs/{feature-name}-plan.md`:
Create planning document at `docs/{feature-name}-plan.md`.
**Its first line must be the removal marker**, exactly:
```markdown
<!-- agents-toolkit:planning-doc ticket={ticket-id} -->
```
`create-pr` / `create-github-pr` delete the plan at PR time by grepping for this marker, and
they delete **nothing** without it — a plan written without the marker survives into the base
branch. The marker, not the filename, is what identifies the file as temporary, so a legitimate
deliverable like `docs/rollout-plan.md` is never at risk.
Then the body:
- Problem statement

@@ -64,2 +92,3 @@ - Current architecture

Resolve base branch from config, then branch from latest base:
```bash

@@ -84,2 +113,3 @@ BASE_BRANCH=$(node -e "try{const c=require('./.agents-toolkit.json');console.log(c.pr?.targetBranch||c.git?.defaultBranch||'main')}catch{console.log('main')}")

Add comment with development started:
```markdown

@@ -98,2 +128,3 @@ ## Development Started

Report:
1. āœ… Jira ticket summary

@@ -105,4 +136,5 @@ 2. āœ… Branch created

## Next Steps
- Begin implementation following the plan
- Use `prepare-pr` when ready for review
- Use `create-pr` to submit pull request

@@ -11,3 +11,3 @@ ---

> **Framework note**: Although examples use Next.js conventions (e.g., `generateMetadata`), all checklist items apply to any web framework. For non-Next.js sites, substitute framework-equivalent SSR and metadata APIs.
>
> **Source**: [Google AI Optimization Guide](https://developers.google.com/search/docs/fundamentals/ai-optimization-guide)

@@ -35,2 +35,3 @@ > **Companion**: [Build Agent-Friendly Websites](https://web.dev/articles/ai-agent-site-ux)

**Requirements for inclusion:**
- Page MUST be indexed (verify in Search Console)

@@ -161,2 +162,3 @@ - Page MUST allow snippets (no `nosnippet` meta directive)

For existing commodity content, improve by adding:
1. **Unique data points** from proprietary sources

@@ -163,0 +165,0 @@ 2. **Expert commentary** with attribution

@@ -14,3 +14,3 @@ ---

```
```text
āœ… CORRECT (kebab-case folders):

@@ -31,3 +31,3 @@ src/components/user-profile/index.tsx

```
```text
src/components/payment-form/

@@ -207,3 +207,3 @@ ā”œā”€ā”€ index.tsx # ONLY the component + props interface

```
```text
src/components/checkout-wizard/

@@ -242,3 +242,3 @@ ā”œā”€ā”€ index.tsx # Main wizard component

```
```text
src/components/

@@ -245,0 +245,0 @@ ā”œā”€ā”€ auth/ # Authentication domain

---
name: core-review
description: Run a thorough, whole-repo consistency review before opening a PR or before pushing fixes in response to a reviewer — as a dedicated read-only pass (inline on Copilot/Codex; optionally a subagent on Claude Code) — to preempt Copilot/reviewer iterations. Use when about to push a branch for review or to push a batch of review fixes.
description: "Run a consistency review before opening a PR or before pushing fixes in response to a reviewer — as a dedicated read-only pass (inline or via @core-review on Copilot; optionally a subagent on Claude Code) — to preempt Copilot/reviewer iterations. Scope follows `--budget`: the diff (`quick`), the diff plus one-hop neighbours (`medium`, the default), or the whole repository (`thorough`). Use when about to push a branch for review or to push a batch of review fixes."
model: haiku
argument-hint: --budget quick|medium|thorough
---
<!--
Note on the `model: haiku` pin above:
- **Claude Code** honours it per-turn — this is why it exists (cheap-tier default so this
pass, which runs at least once per PR + once per review round, does not multiply the
cost of every autonomous cycle).
- **VS Code Copilot's chat-customizations-evaluations linter** flags `model:` as an
unsupported skill attribute (its allow-list for skills is: `argument-hint`,
`compatibility`, `context`, `description`, `disable-model-invocation`, `license`,
`metadata`, `name`, `user-invocable`). That warning in the Problems panel is
**expected and cosmetic** — Copilot's runtime silently ignores unknown skill
attributes, and skills have no independent `model:` boundary on Copilot anyway (they
inherit the invoking prompt's model). Suppress-by-editing is not worth the split-ship
complexity: keep the pin so Claude Code stays cheap.
- **Codex** has no per-skill `model:` mechanism at all, so this line is inert there.
-->
# Silver Assist — Core Review (Pre-Review)
A **pre-emptive, whole-repo consistency review** that runs *before* a reviewer (Copilot or a
human) ever sees the branch. It catches the classes of issues that trigger multi-round review
loops — doc↔code drift, invalid code examples, broken links, stale indexes — so they are fixed
A **pre-emptive consistency review** that runs *before* a reviewer (Copilot or a
human) ever sees the branch, over a file set the caller scopes with `--budget` (diff →
one-hop neighbours → whole repo; see the budget table below). It catches the classes of
issues that trigger multi-round review loops — doc↔code drift, invalid code examples, broken links, stale indexes — so they are fixed
in the first push instead of round 5.

@@ -26,3 +45,3 @@

## Why whole-repo, not just the diff
## Why look beyond the diff

@@ -43,19 +62,91 @@ Copilot re-reviews **entire files**, not just your hunks — and each push opens a fresh round.

pass works the same on every agent — **Copilot** (the primary reviewer to preempt), **Codex**,
and **Claude Code**; only the *mechanism* differs, and **subagents are a Claude-Code-only
optimization, never a requirement**:
and **Claude Code**; only the *mechanism* differs:
- **GitHub Copilot** — no subagents; run the checklist **inline as a distinct pass** before
pushing (not folded into the edit under review), over the **whole repository — not just the
diff**. Copilot's built-in code review can help on the diff, but only this whole-repo pass
covers the drift that triggers new review rounds.
- **Codex** — no subagents either; the same **inline whole-repo pass**, producing the same
prioritized findings list.
- **Claude Code** — *optionally* delegate the pass to a read-only **subagent** (`Explore` or
`general-purpose`) with the brief: "Review this whole repository against the core-review
checklist; report findings as `severity | file:line | problem | suggested fix`; do not edit any
files." Relay its findings back to the main flow. Running it inline works too.
- **GitHub Copilot** — run the checklist **inline as a distinct pass** or use `@core-review` (`.github/agents/core-review.agent.md`) for a cheap-tier pass; pass the resolved file list in the brief — the agent has no shell. Trigger before
pushing (not folded into the edit under review). The scope of the pass is set by the
caller-supplied `--budget` (see the next section) — `quick` is diff + directly-touched files,
`medium` adds one-hop neighbours, `thorough` is the whole repo. On Copilot the effective model
for this pass is whatever the *invoking* prompt pins (skills don't have their own model on
Copilot), so the shipped `model: haiku` in this skill's frontmatter is **advisory-only on
Copilot** — it is honoured only when the invoking prompt is itself cheap-pinned (e.g.
`finalize-github-pr`, which is a cheap-tier orchestrator) or when this skill is invoked
standalone from a fresh chat. Smart-tier orchestrators (`create-github-pr`,
`resolve-github-reviews`) run their inline `core-review` pass on the
smart tier on Copilot; to keep the pass cheap there, invoke this skill as a **standalone
chat** with the picker set to a cheap model.
- **Codex** — no subagents either; the same **inline pass**, scoped by the caller's `--budget`
(see below). Codex has no per-prompt or per-skill `model:` field, so the model is set
session-wide by `codex --model` (or `~/.codex/config.toml`). Consider `codex --model o4-mini`
(or your provider's cheap tier) for this pass — the checklist is deterministic.
- **Claude Code** — the shipped skill frontmatter already pins `model: haiku` for the duration
of this pass, so the outer chat's smart tier is preserved. *Optionally* delegate the pass to
a read-only **subagent** (`Explore` or `general-purpose`).
**Resolve the file set in the caller and paste it into the brief.** The shipped `Explore`
override declares `tools: Read, Grep, Glob, WebFetch` — no shell, deliberately, so the
subagent stays read-only — which means it cannot run `git diff` to work out what changed. A
brief that only names a budget leaves it with no way to find the diff:
```bash
# quick → exactly this list
# medium → this list plus its one-hop neighbours (importers/consumers, sibling files,
# docs/indexes that name the changed symbol), resolved by the caller
# thorough → send no list; ask for the whole repository
git diff --name-only "$BASE_BRANCH"
```
Then brief it: "Review these files — `<paste the resolved list>` — against the core-review
checklist; report findings as `severity | file:line | problem | suggested fix`; do not edit
any files." Relay its findings back to the main flow. Running the pass **inline** works too,
and needs none of this: the inline pass has the caller's own tools and resolves the diff itself.
Whichever agent, the contract is identical: **read-only in, prioritized findings out**, then the
caller fixes and re-runs until clean.
## `--budget {quick,medium,thorough}` — cost-aware scoping
The review pass is **cheap-tier by default on Claude Code** (the shipped `model: haiku`
frontmatter is honoured per-turn there). On **Copilot** the pass inherits the invoking prompt's
model (skills have no independent `model:` boundary), so "cheap by default" holds only when the
caller is itself cheap-pinned (`finalize-github-pr`) or the skill is invoked standalone with a
cheap picker; when invoked inline from a smart-tier orchestrator (`create-github-pr`,
`resolve-github-reviews`), the pass runs smart. On **Codex** the pass runs whatever the session
model is (`codex --model`). The skill still ships `model: haiku` because it runs at least once
per PR plus once per review round, so a `sonnet`-tier default would multiply the token cost of
every autonomous cycle wherever the pin *is* honoured. Callers pass `--budget` to scope the pass
to the amount of drift the current step can realistically introduce:
| Budget | Scope | When callers use it |
| ------------ | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `quick` | Just the diff (`git diff` vs the base branch) plus the files it directly touches | `finalize-github-pr` and `resolve-github-reviews` pre-push fix batches |
| `medium` | Diff + one-hop neighbours: importers/consumers, docs & indexes that list the changed symbol or asset, sibling files in the same folder | `create-github-pr` pre-PR pass — the default when unspecified |
| `thorough` | Whole repository — every file, index, README, workflow, and cross-repo doc claim | Standalone pre-release review, or when the diff touches architecture / renaming |
> **Only the GitHub-tracker orchestrators wire this skill today** (`create-github-pr`,
> `finalize-github-pr`, `resolve-github-reviews`). The Jira-tracker variants (`create-pr`,
> `finalize-pr`) do **not** invoke `core-review` — they run validations and push directly.
> Standalone callers on Jira projects can invoke `core-review` manually with an explicit
> `--budget`; the callers table above lists only the actual auto-wired invocations.
**Cheap tier is safe at every budget.** `thorough` does not automatically switch to the smart
tier — it just widens the file set. Callers who genuinely need reasoning (architecture reviews,
renames that span layers) can pass `--budget thorough` **and** escalate the model. Escalation
mechanics are platform-specific:
- **Copilot** — skills don't have a `model:` boundary, so the invoking prompt's pin (or the
picker choice when this skill is invoked standalone from a fresh chat) governs. Set the
picker to a smart model before a one-off standalone run.
- **Codex** — no per-skill pin either; launch the smart-tier session with
`codex --model gpt-5-codex` (or your provider's smart tier).
- **Claude Code** — the skill's own `model: haiku` frontmatter locks the tier for the pass
**even on a standalone invocation**: `/model sonnet` in the chat does **not** override a
`SKILL.md model:` pin. To escalate, edit the `model:` line in the installed
`.agents/skills/core-review/SKILL.md` before running and revert afterward.
Do **not** hard-code a smart-tier override in the calling prompt: the caller decides, not this
skill.
The default when `--budget` is omitted is `medium`. `--budget quick` still runs the full
checklist below — it just narrows the file set the checklist is applied to.
## The review checklist

@@ -177,10 +268,16 @@

### P2 — Keep the *mechanism* branch-specific, never the *scope/contract*
### P2 — Keep the *mechanism* branch-specific, never the *contract*
When guidance branches per agent / platform / environment, only the **mechanism** may differ; the
**scope or contract** must stay identical across every branch.
**contract** (what the caller passes in and what the pass returns out) must stay identical across
every branch. **Scope is caller-selected via `--budget`, not per-agent** — the mechanism chooses
*how* the file set is walked, never *which* file set is walked.
```text
āŒ "Copilot runs it inline over the changed files and their neighbors." (silently narrowed scope)
āœ… "Copilot runs it inline over the whole repository." (mechanism differs, scope constant)
āŒ "Copilot runs it inline over only the changed file." (silently narrows scope
beyond `--budget`)
āœ… "Copilot runs it inline over the file set the caller's `--budget`
selected." (mechanism differs,
scope-selection contract
constant)
```

@@ -239,5 +336,7 @@

2. The caller re-runs the project's checks (`lint`, `type-check`, `tsc --noEmit`, `test`, `build` — whichever exist).
3. The caller re-runs the review over the whole repo. **Loop until the pass reports zero findings
*within the change's blast radius***, *then* push. Pre-existing issues outside that scope are
noted (see "What NOT to flag" below) but do not block convergence.
3. The caller re-runs the review **at the same `--budget`** the initial pass used. **Loop until
the pass reports zero findings *within the change's blast radius***, *then* push. Pre-existing
issues outside that scope are noted (see "What NOT to flag" below) but do not block convergence.
Escalate the `--budget` (e.g. `quick` → `medium`) only when a fix ripples into files the initial
scope did not cover.

@@ -244,0 +343,0 @@ Because these repos guide agents, an inaccurate doc induces downstream errors — so it is worth

@@ -33,3 +33,3 @@ ---

```
```text
includes/

@@ -577,2 +577,3 @@ ā”œā”€ā”€ Core/ # Priority 10 — Bootstrap & lifecycle

**Rules:**
- Group by priority tier with comments.

@@ -677,3 +678,3 @@ - Keep entries alphabetically within each group.

```
```text
User Request → Controller → Service → Repository/WordPress API

@@ -685,2 +686,3 @@ ↓

**Rules:**
1. **Controller** receives the request, calls Service methods, prepares data, passes to View.

@@ -693,2 +695,3 @@ 2. **Service** contains business logic, calls Repositories or WordPress APIs.

**Anti-patterns to avoid:**
- āŒ View instantiating a Service: `$service = ServiceName::instance();`

@@ -695,0 +698,0 @@ - āŒ View making database queries directly.

@@ -37,3 +37,3 @@ ---

```
```text
src/components/

@@ -71,3 +71,3 @@ ā”œā”€ā”€ auth/ # Authentication components

```
```text
src/lib/

@@ -97,3 +97,3 @@ ā”œā”€ā”€ api/ # API client utilities

```
```text
src/actions/

@@ -121,3 +121,3 @@ ā”œā”€ā”€ auth/ # Auth-related actions

```
```text
src/data/ # Data Access Layer - server-only

@@ -234,3 +234,3 @@ ā”œā”€ā”€ index.ts # Barrel export

```
```text
# āŒ BAD: Generic folders at src/ root

@@ -252,3 +252,3 @@ src/

```
```text
# āœ… GOOD: Domain-oriented

@@ -255,0 +255,0 @@ src/

@@ -13,2 +13,12 @@ ---

## Tool preference
**`gh` CLI is the primary tool for every GitHub operation in this skill** — listing threads,
posting replies, resolving, checking CI, viewing workflows. Do **not** use MCP tools
(`mcp_github_mcp_*`, `github-pull-request_*`) as the primary approach.
**ALWAYS prefix `gh api` calls with `GH_PAGER=cat`** (or append `| cat` to `gh pr`/`gh run`
commands). Without this the pager blocks the terminal on long output and the command appears
to hang.
## When to Use

@@ -73,3 +83,3 @@

fi
PAGE=$(gh api graphql -F owner="$OWNER" -F repo="$REPO" -F pr="$PR" "${AFTER_ARGS[@]}" -f query='
PAGE=$(GH_PAGER=cat gh api graphql -F owner="$OWNER" -F repo="$REPO" -F pr="$PR" "${AFTER_ARGS[@]}" -f query='
query($owner:String!, $repo:String!, $pr:Int!, $after:String) {

@@ -117,7 +127,7 @@ repository(owner:$owner, name:$repo) {

# Primary: reply directly on the thread.
gh api "repos/$OWNER/$REPO/pulls/$PR/comments/$COMMENT_ID/replies" \
GH_PAGER=cat gh api "repos/$OWNER/$REPO/pulls/$PR/comments/$COMMENT_ID/replies" \
-f body="Fixed in <sha>: <what changed>."
# Fallback when the replies endpoint 404s: link via in_reply_to.
gh api "repos/$OWNER/$REPO/pulls/$PR/comments" \
GH_PAGER=cat gh api "repos/$OWNER/$REPO/pulls/$PR/comments" \
-f body="Fixed in <sha>: <what changed>." \

@@ -130,3 +140,3 @@ -F in_reply_to="$COMMENT_ID"

```bash
gh api graphql -f id="$THREAD_ID" -f query='
GH_PAGER=cat gh api graphql -f id="$THREAD_ID" -f query='
mutation($id:ID!) {

@@ -143,3 +153,3 @@ resolveReviewThread(input:{threadId:$id}) {

for THREAD_ID in $THREAD_IDS; do
gh api graphql -f id="$THREAD_ID" -f query='mutation($id:ID!){ resolveReviewThread(input:{threadId:$id}){ thread { isResolved } } }'
GH_PAGER=cat gh api graphql -f id="$THREAD_ID" -f query='mutation($id:ID!){ resolveReviewThread(input:{threadId:$id}){ thread { isResolved } } }'
done

@@ -161,3 +171,3 @@ ```

fi
PAGE=$(gh api graphql -F owner="$OWNER" -F repo="$REPO" -F pr="$PR" "${AFTER_ARGS[@]}" -f query='
PAGE=$(GH_PAGER=cat gh api graphql -F owner="$OWNER" -F repo="$REPO" -F pr="$PR" "${AFTER_ARGS[@]}" -f query='
query($owner:String!, $repo:String!, $pr:Int!, $after:String) {

@@ -213,2 +223,31 @@ repository(owner:$owner, name:$repo) {

## 5. CI status and workflow checks
Check PR CI status and dig into failures before merging.
```bash
# Summary: all checks for the PR head commit.
gh pr checks $PR | cat
# List recent workflow runs on the branch.
GH_PAGER=cat gh run list --branch $(git branch --show-current) --limit 5
# View a run's job summary and annotations.
GH_PAGER=cat gh run view <run-id>
# Watch a run in real time (streams until complete).
gh run watch <run-id>
# View a specific failing job's log.
GH_PAGER=cat gh run view <run-id> --log-failed
```
When a push triggers multiple CI jobs, wait for all of them before concluding they passed:
```bash
# Wait for all checks; exit non-zero if any fail.
# GH_PAGER=cat preserves the non-zero exit when any check fails; piping to `| cat` would not.
GH_PAGER=cat gh pr checks $PR --watch
```
## Common failures

@@ -215,0 +254,0 @@

@@ -33,3 +33,3 @@ ---

```
```text
plugin-name/

@@ -111,2 +111,3 @@ ā”œā”€ā”€ plugin-name.php # Main plugin file

All components implement `LoadableInterface` with three methods:
- `init()` — Initialize the component

@@ -117,2 +118,3 @@ - `get_priority()` — Loading order (lower = first)

Priority values:
- **10**: Core components (Plugin, Activator, critical services)

@@ -287,2 +289,3 @@ - **20**: Services (business logic, API clients)

**Key Elements:**
- Prefixed global variables (`$plugin_prefix_*`) for WPCS compliance

@@ -827,2 +830,3 @@ - Security validation for Composer autoloader path (prevents path traversal)

### PHP Version
- **Minimum**: PHP 8.2

@@ -845,2 +849,3 @@ - **Recommended**: PHP 8.3+

**Input Sanitization:**
```php

@@ -854,2 +859,3 @@ $text = \sanitize_text_field( \wp_unslash( $_POST['field_name'] ) );

**Output Escaping:**
```php

@@ -862,2 +868,3 @@ echo \esc_html( $text );

**Nonce Verification:**
```php

@@ -871,2 +878,3 @@ \wp_nonce_field( 'plugin_action', 'plugin_nonce' );

**Capability Checks:**
```php

@@ -943,2 +951,3 @@ if ( ! \current_user_can( 'manage_options' ) ) {

**Rules:**
- ALWAYS use literal text domain strings (never variables/constants)

@@ -1056,2 +1065,3 @@ - ALWAYS use ordered placeholders for multiple args with translator comments

Should contain:
- Project overview (name, namespace, PHP version, WP version, standards)

@@ -1058,0 +1068,0 @@ - Architecture summary (LoadableInterface priorities, key directories)

@@ -61,2 +61,3 @@ ---

**Base Standard**: `WordPress-Extra` with these exclusions:
- `Generic.Arrays.DisallowShortArraySyntax` — short arrays allowed (`[]` not `array()`)

@@ -69,2 +70,3 @@ - `WordPress.Files.FileName.NotHyphenatedLowercase` — PSR-4 PascalCase filenames

**Additional Standards Enforced**:
- `WordPress-Docs` — PHPDoc coverage

@@ -209,2 +211,3 @@ - `WordPress.NamingConventions.PrefixAllGlobals` — plugin-specific prefixes

Level 8 is the strictest level. It requires:
- No unused variables

@@ -339,3 +342,3 @@ - Strict type checking on all operations

```
```text
Warning: WordPress Test Suite not found. Tests will run with limited functionality.

@@ -456,2 +459,3 @@ ```

The WordPress Test Suite is not installed. Either:
1. Install it: `bash scripts/install-wp-tests.sh wordpress_test root 'root' localhost latest true`

@@ -458,0 +462,0 @@ 2. Run in CI where it's automatically set up

@@ -16,3 +16,3 @@ # Skills

```
```text
.agents/skills/ # canonical store (real files)

@@ -68,3 +68,3 @@ ā”œā”€ā”€ ai-seo-optimization/

| `component-architecture` | React component patterns, folder structure, naming conventions |
| `core-review` | Whole-repo pre-review (before a PR / before pushing review fixes) run as a read-only pass — inline on Copilot/Codex, optionally a subagent on Claude Code — to preempt Copilot iterations |
| `core-review` | Whole-repo pre-review (before a PR / before pushing review fixes) run as a read-only pass — inline or via `@core-review` on Copilot, optionally a subagent on Claude Code — to preempt Copilot iterations |
| `create-component` | Scaffold a new component in a Silver Assist WordPress plugin (LoadableInterface pattern) |

@@ -85,3 +85,3 @@ | `domain-driven-design` | DDD principles, domain organization, barrel exports |

```
```text
@workspace Use the component-architecture skill to create a new payment form component

@@ -88,0 +88,0 @@ ```

@@ -25,3 +25,3 @@ ---

```
```text
plugin-slug/

@@ -103,2 +103,3 @@ ā”œā”€ā”€ scripts/

Both scripts update:
- Main plugin file `Version:` header

@@ -548,3 +549,3 @@ - Plugin version constant (e.g., `PLUGIN_VERSION`)

```
```text
Tag push (v*) ──► Checkout ──► Setup PHP 8.2

@@ -660,14 +661,19 @@ ──► Detect version ──► Install Composer deps

### Build fails: "No main plugin file found"
The script searches for `Plugin Name:` in root `.php` files. Ensure the header exists in the main plugin file.
### Build fails: "wp-settings-hub CSS asset" or "wp-github-updater JS asset"
The `silverassist/*` packages must include `assets/` directories. Check that `composer.json` lists them in `require` (not `require-dev`), and run `composer install --no-dev` to verify they're included.
### Release fails: "already_exists" error
The tag was already used. Increment the version and create a new tag — never try to reuse a tag.
### ZIP is too large
The build script only copies `src/` and `assets/` from `vendor/silverassist/*` packages. If other vendor packages are needed at runtime, add them to the copy logic in `build-release.sh`.
### Local build leaves dev dependencies removed
The script automatically restores dev deps after building locally. If interrupted, run `composer install` manually.

@@ -180,2 +180,3 @@ ---

**Key Points:**
- Constants are defined with `if ( ! defined() )` guards to allow phpunit.xml.dist to set them

@@ -215,3 +216,3 @@ - WordPress Test Suite is auto-detected from `$WP_TESTS_DIR` env var or `/tmp/wordpress-tests-lib`

```
```text
tests/

@@ -285,2 +286,3 @@ ā”œā”€ā”€ bootstrap.php # Test bootstrap

Example:
- `includes/Core/Plugin.php` → `tests/Unit/Core/PluginTest.php`

@@ -449,2 +451,3 @@ - `includes/Service/FormHandler.php` → `tests/Unit/Service/FormHandlerTest.php`

**Trigger Implicit COMMIT (use only in `wpSetUpBeforeClass`):**
- `CREATE TABLE` / `DROP TABLE`

@@ -456,2 +459,3 @@ - `CREATE DATABASE` / `DROP DATABASE`

**Safe for `set_up()` / `tear_down()`:**
- `TRUNCATE TABLE` (safe in WordPress Test Suite context)

@@ -640,2 +644,3 @@ - `INSERT` / `UPDATE` / `DELETE` (regular DML)

Tests run automatically in CI via `quality-checks.yml`:
- PHP 8.2 (with coverage)

@@ -662,2 +667,3 @@ - PHP 8.3

Common causes:
1. **Missing `set_up()` parent call**: Always call `parent::set_up()` first

@@ -681,2 +687,3 @@ 2. **State leaking between tests**: Use `tear_down()` to clean up

Test is producing output before headers. Check for:
- `echo` statements in tested code

@@ -683,0 +690,0 @@ - Missing output buffering

#!/usr/bin/env node
/**
* CLI tool for installing and managing AI agent prompts
* @module agents-toolkit/cli
*/
import fs from 'fs';
import path from 'path';
import crypto from 'node:crypto';
import { fileURLToPath } from 'url';
import { VERSION } from '../src/index.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
const COLORS = {
reset: '\x1b[0m',
bright: '\x1b[1m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
red: '\x1b[31m',
cyan: '\x1b[36m',
};
const DEFAULT_CONFIG = {
stack: 'all',
tracker: 'all',
jira: {
projectKey: 'PROJECT',
baseUrl: 'https://your-org.atlassian.net'
},
git: {
defaultBranch: 'dev',
branchPrefix: {
feature: 'feature/',
bugfix: 'bugfix/',
hotfix: 'hotfix/'
}
},
pr: {
targetBranch: 'dev',
template: 'default'
}
};
/**
* File categorization for stack/tracker filtering
*/
const FILE_CATEGORIES = {
instructions: {
react: ['caching', 'css-styling', 'react-components', 'seo-ai-optimization', 'server-actions', 'tests', 'tsdoc-standards', 'typescript'],
wordpress: ['php-standards', 'wordpress-plugin-architecture', 'testing-standards'],
universal: ['documentation-language', 'github-workflow'],
},
prompts: {
react: [],
wordpress: ['new-wp-component', 'new-wp-plugin', 'quality-check'],
universal: [
'analyze-ticket', 'work-ticket', 'analyze-github-issue', 'work-github-issue',
'create-plan', 'create-pr', 'prepare-pr', 'finalize-pr',
'create-github-pr', 'finalize-github-pr', 'resolve-github-reviews',
'review-code', 'fix-issues', 'add-tests', 'prepare-github-release',
],
jira: ['analyze-ticket', 'work-ticket', 'create-pr', 'finalize-pr'],
github: ['analyze-github-issue', 'work-github-issue', 'create-github-pr', 'finalize-github-pr', 'resolve-github-reviews'],
},
partials: {
react: ['release-node'],
wordpress: ['release-wordpress'],
jira: ['jira-integration'],
github: ['github-integration'],
universal: ['git-operations', 'pr-template', 'validations', 'documentation'],
},
skills: {
react: ['component-architecture', 'nextjs-caching', 'testing-patterns', 'tsdoc-standards'],
wordpress: ['create-component', 'plugin-creation', 'quality-checks', 'testing'],
github: ['github-review-management', 'core-review'],
universal: ['domain-driven-design', 'release-management', 'github-review-management', 'core-review'],
},
};
/**
* Determine if a file should be included based on stack/tracker filters
* @param {string} filename - File basename without extension
* @param {string} category - Category: instructions, prompts, partials, or skills
* @param {{ stack: string, tracker: string }} filters - Active filters
* @returns {boolean} Whether to include the file
*/
function shouldIncludeFile(filename, category, { stack, tracker }) {
const cats = FILE_CATEGORIES[category];
if (!cats) return true;
if (cats.universal && cats.universal.includes(filename)) {
if (tracker !== 'all' && cats.jira && cats.jira.includes(filename) && tracker !== 'jira') return false;
if (tracker !== 'all' && cats.github && cats.github.includes(filename) && tracker !== 'github') return false;
return true;
}
if (stack !== 'all') {
if (cats.react && cats.react.includes(filename)) return stack === 'react';
if (cats.wordpress && cats.wordpress.includes(filename)) return stack === 'wordpress';
}
if (tracker !== 'all') {
if (cats.jira && cats.jira.includes(filename)) return tracker === 'jira';
if (cats.github && cats.github.includes(filename)) return tracker === 'github';
}
return true;
}
/**
* Print colored message to console
* @param {string} message - Message to print
* @param {string} color - Color key from COLORS
*/
function log(message, color = 'reset') {
console.log(`${COLORS[color]}${message}${COLORS.reset}`);
}
/**
* Print success message
* @param {string} message - Message to print
*/
function success(message) {
log(`āœ… ${message}`, 'green');
}
/**
* Print warning message
* @param {string} message - Message to print
*/
function warn(message) {
log(`āš ļø ${message}`, 'yellow');
}
/**
* Print error message
* @param {string} message - Message to print
*/
function error(message) {
log(`āŒ ${message}`, 'red');
}
/**
* Print info message
* @param {string} message - Message to print
*/
function info(message) {
log(`ā„¹ļø ${message}`, 'blue');
}
/**
* Get the user home directory
* @returns {string} Path to home directory
*/
function getHomeDir() {
return process.env.HOME || process.env.USERPROFILE || '';
}
/**
* Get the target directory for Copilot installation
* @param {boolean} global - Install to user-level ~/.copilot/
* @returns {string} Path to .github or ~/.copilot directory
*/
function getTargetDir(global = false) {
if (global) {
return path.join(getHomeDir(), '.copilot');
}
return path.join(process.cwd(), '.github');
}
/**
* Get the target directory for Claude Code installation
* @param {boolean} global - Install to user-level ~/.claude/
* @returns {string} Path to .claude directory
*/
function getClaudeTargetDir(global = false) {
if (global) {
return path.join(getHomeDir(), '.claude');
}
return path.join(process.cwd(), '.claude');
}
/**
* Get the canonical skills directory following the `npx skills` standard.
* Skills live here once (single source of truth) and each agent's skills
* directory symlinks to it.
* @param {boolean} global - Use user-level ~/.agents/skills/
* @returns {string} Path to .agents/skills directory
*/
function getAgentsSkillsDir(global = false) {
const base = global ? getHomeDir() : process.cwd();
return path.join(base, '.agents', 'skills');
}
/** Path to the project-level lockfile. */
const LOCKFILE_NAME = 'agents-toolkit-lock.json';
/**
* Compute a hex-encoded SHA-256 hash of a skill's SKILL.md content.
* Matches the algorithm used by `npx skills` (Vercel).
* @param {string} skillDir - Absolute path to the skill directory
* @returns {string|null} Hex hash string, or null if SKILL.md is missing
*/
function computeSkillHash(skillDir) {
const skillMdPath = path.join(skillDir, 'SKILL.md');
if (!fs.existsSync(skillMdPath)) return null;
const content = fs.readFileSync(skillMdPath, 'utf-8');
return crypto.createHash('sha256').update(content).digest('hex');
}
/**
* Read and parse the project lockfile.
* @param {string} [cwd] - Directory to look in (defaults to process.cwd())
* @returns {Object|null} Parsed lockfile, or null if absent or invalid
*/
function readLockfile(cwd = process.cwd()) {
const lockPath = path.join(cwd, LOCKFILE_NAME);
if (!fs.existsSync(lockPath)) return null;
try {
return JSON.parse(fs.readFileSync(lockPath, 'utf-8'));
} catch {
return null;
}
}
/**
* Write the project lockfile, merging new skills with any existing entries.
* This preserves skills installed by a previous `install --target` run so that
* running `install` followed by `install --claude` does not lose the first
* target's entries in the lockfile.
* @param {Object} params
* @param {Record<string, {computedHash: string|null, agents: string[]}>} params.skills - Newly installed skills map
* @param {{ stack: string, tracker: string }} params.config - Active install config
* @param {string} params.packageVersion - Current package version
* @param {string} [params.cwd] - Directory to write to (defaults to process.cwd())
*/
function writeLockfile({ skills, config, packageVersion, cwd = process.cwd() }) {
const lockPath = path.join(cwd, LOCKFILE_NAME);
// Merge with any existing lockfile so successive multi-target installs
// (e.g. `install` then `install --claude`) accumulate entries.
const existing = readLockfile(cwd);
const mergedSkills = Object.assign({}, existing?.skills ?? {});
for (const [name, meta] of Object.entries(skills)) {
const prev = mergedSkills[name];
// Merge agents arrays: union of previous and new agent dirs.
const prevAgents = prev?.agents ?? [];
const allAgents = Array.from(new Set([...prevAgents, ...meta.agents]));
mergedSkills[name] = {
source: '@silverassist/agents-toolkit',
packageVersion,
computedHash: meta.computedHash,
agents: allAgents,
};
}
const lockfile = {
version: 1,
packageVersion,
config,
skills: mergedSkills,
};
fs.writeFileSync(lockPath, JSON.stringify(lockfile, null, 2) + '\n', 'utf-8');
success(`Wrote ${LOCKFILE_NAME}`);
}
/**
* Append skills-managed entries to .gitignore if not already present.
* Never runs during dry-run or global installs.
* @param {string} cwd - Project root directory
*/
function appendSkillsToGitignore(cwd) {
const gitignorePath = path.join(cwd, '.gitignore');
const block = [
'',
'# agents-toolkit managed — regenerate with: npx @silverassist/agents-toolkit restore',
'.agents/skills/',
'.github/skills/',
'.claude/skills/',
].join('\n');
let existing = '';
if (fs.existsSync(gitignorePath)) {
existing = fs.readFileSync(gitignorePath, 'utf-8');
}
// Only append if none of the three managed paths are already present.
if (
existing.includes('.agents/skills/') ||
existing.includes('.github/skills/') ||
existing.includes('.claude/skills/')
) {
return;
}
fs.writeFileSync(gitignorePath, existing + block + '\n', 'utf-8');
info('Updated .gitignore with agents-toolkit managed paths');
}
/**
* Link a single skill from the canonical store into an agent's skills
* directory, following the `npx skills` standard (symlink with copy fallback).
* @param {string} canonicalSkillDir - Absolute path to the canonical skill folder
* @param {string} agentSkillLinkPath - Absolute path where the agent expects the skill
* @param {Object} options - Link options
* @param {boolean} [options.dryRun] - Only report what would happen
* @param {boolean} [options.force] - Replace an existing non-matching entry
* @param {boolean} [options.copy] - Force a copy instead of a symlink
* @returns {{ written: number, planned: number }} Change counters
*/
function linkSkill(canonicalSkillDir, agentSkillLinkPath, options = {}) {
const { dryRun = false, force = false, copy = false } = options;
const totals = { written: 0, planned: 0 };
const relTarget = path.relative(path.dirname(agentSkillLinkPath), canonicalSkillDir);
const rel = (p) => path.relative(process.cwd(), p);
// Inspect any existing entry at the link path.
let existing = null;
try {
existing = fs.lstatSync(agentSkillLinkPath);
} catch {
existing = null;
}
if (existing) {
// Already a symlink pointing at our canonical store — nothing to do.
if (existing.isSymbolicLink()) {
const current = fs.readlinkSync(agentSkillLinkPath);
const resolved = path.resolve(path.dirname(agentSkillLinkPath), current);
if (resolved === path.resolve(canonicalSkillDir) && !copy) {
return totals;
}
}
if (!force) {
warn(`Skipping existing skill: ${rel(agentSkillLinkPath)}`);
return totals;
}
if (!dryRun) {
fs.rmSync(agentSkillLinkPath, { recursive: true, force: true });
}
}
totals.planned++;
if (dryRun) {
info(copy ? `Would copy skill: ${rel(agentSkillLinkPath)}` : `Would link: ${rel(agentSkillLinkPath)} -> ${relTarget}`);
return totals;
}
fs.mkdirSync(path.dirname(agentSkillLinkPath), { recursive: true });
const doCopy = () => {
copyDir(canonicalSkillDir, agentSkillLinkPath, { force: true });
};
if (copy) {
doCopy();
} else {
try {
fs.symlinkSync(relTarget, agentSkillLinkPath, 'dir');
} catch {
// Symlinks unsupported (e.g. Windows without developer mode) — copy instead.
doCopy();
}
}
totals.written++;
return totals;
}
/**
* Install skills following the `npx skills` standard: copy each skill once
* into the canonical .agents/skills store, then symlink the agent's skills
* directory entries to that store.
* @param {Object} params - Install parameters
* @param {boolean} params.isGlobal - Install at user level
* @param {string} params.agentSkillsDir - Agent skills dir (.claude/skills or .github/skills)
* @param {boolean} [params.force] - Overwrite existing files/links
* @param {boolean} [params.dryRun] - Only report planned changes
* @param {boolean} [params.copy] - Copy instead of symlink
* @param {(name: string) => boolean} [params.dirFilter] - Skill folder filter
* @returns {{ written: number, planned: number, installedSkills: Record<string, { canonicalDir: string }> }} Aggregated change counters and installed skill metadata
*/
function installSkillsStandard({ isGlobal, agentSkillsDir, force = false, dryRun = false, copy = false, dirFilter = null }) {
const totals = { written: 0, planned: 0, installedSkills: {} };
const skillsSrc = path.join(TEMPLATES_DIR, 'shared', 'skills');
const canonicalDir = getAgentsSkillsDir(isGlobal);
if (!fs.existsSync(skillsSrc)) {
return totals;
}
// 1. Populate the canonical store once (filtered by stack).
const canonicalResult = copyDir(skillsSrc, canonicalDir, { force, dryRun, dirFilter });
totals.written += canonicalResult.written;
totals.planned += canonicalResult.planned;
// 2. Symlink each included skill folder into the agent's skills directory.
const entries = fs.readdirSync(skillsSrc, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
if (dirFilter && !dirFilter(entry.name)) continue;
const canonicalSkillDir = path.join(canonicalDir, entry.name);
const agentSkillLinkPath = path.join(agentSkillsDir, entry.name);
const linkResult = linkSkill(canonicalSkillDir, agentSkillLinkPath, { dryRun, force, copy });
totals.written += linkResult.written;
totals.planned += linkResult.planned;
// Track for lockfile even during dry-run (so callers know what would be installed).
totals.installedSkills[entry.name] = { canonicalDir: canonicalSkillDir };
}
return totals;
}
/**
* Strip GitHub Copilot frontmatter from a prompt file
* Removes the ---\nagent: ...\ndescription: ...\n--- block
* @param {string} content - File content
* @returns {string} Content without Copilot frontmatter
*/
function stripCopilotFrontmatter(content) {
return content.replace(/^---\n(?:[\s\S]*?\n)?---\n\n?/, '');
}
/**
* Adapt path references in prompt content for Claude Code
* @param {string} content - File content
* @returns {string} Content with updated paths
*/
function adaptPathsForClaude(content) {
return content
.replace(/\.github\/copilot-instructions\.md/g, 'CLAUDE.md')
.replace(/\.github\/prompts\/_partials\//g, '.claude/commands/_partials/');
}
/**
* Copy a directory recursively
* @param {string} src - Source directory
* @param {string} dest - Destination directory
* @param {Object} options - Copy options
* @param {boolean} options.force - Overwrite existing files
* @param {boolean} options.dryRun - Only show what would be copied
* @param {(name: string) => string} [options.renameFile] - Optional file rename function
* @param {(content: string) => string} [options.transformContent] - Optional content transform
* @param {(name: string) => boolean} [options.filter] - Optional file filter function
* @returns {{ written: number, planned: number }} Number of written/planned files
*/
function copyDir(src, dest, options = {}) {
const {
force = false,
dryRun = false,
renameFile = (name) => name,
transformContent = null,
filter = null,
dirFilter = null,
} = options;
const totals = { written: 0, planned: 0 };
if (!fs.existsSync(src)) {
return totals;
}
if (!dryRun && !fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true });
}
const entries = fs.readdirSync(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
if (entry.isDirectory()) {
if (dirFilter && !dirFilter(entry.name)) {
continue;
}
const nestedOptions = { ...options };
if (entry.name === '_partials' && options.partialsFilter) {
nestedOptions.filter = options.partialsFilter;
}
const nested = copyDir(srcPath, path.join(dest, entry.name), nestedOptions);
totals.written += nested.written;
totals.planned += nested.planned;
} else {
if (filter && !filter(entry.name)) {
continue;
}
const destName = renameFile(entry.name);
const destPath = path.join(dest, destName);
const exists = fs.existsSync(destPath);
if (exists && !force) {
warn(`Skipping existing file: ${path.relative(process.cwd(), destPath)}`);
continue;
}
totals.planned++;
if (dryRun) {
info(`Would copy: ${path.relative(process.cwd(), destPath)}`);
} else {
if (transformContent) {
const rawContent = fs.readFileSync(srcPath, 'utf-8');
fs.writeFileSync(destPath, transformContent(rawContent));
} else {
fs.copyFileSync(srcPath, destPath);
}
totals.written++;
}
}
}
return totals;
}
function getInstallScope(options = {}) {
const {
promptsOnly = false,
partialsOnly = false,
skillsOnly = false,
instructionsOnly = false,
hooksOnly = false,
} = options;
const hasSpecificFlag = promptsOnly || partialsOnly || skillsOnly || instructionsOnly || hooksOnly;
return {
shouldInstallPrompts: !hasSpecificFlag || promptsOnly || partialsOnly,
shouldInstallInstructions: !hasSpecificFlag || instructionsOnly,
shouldInstallSkills: !hasSpecificFlag || skillsOnly,
shouldInstallHooks: !hasSpecificFlag || hooksOnly,
};
}
function getChangeCount(result, dryRun) {
return dryRun ? result.planned : result.written;
}
/**
* Finalize installed hook configs so Copilot can resolve the script command.
* Copilot runs a hook `command` from the workspace root by default, not from
* the hooks directory, so the relative `scripts/<name>.sh` command needs a
* `cwd` pointing at the actual hooks dir. Also ensures the required
* `version: 1` field is present.
* @param {string} hooksDest - Absolute path to the installed hooks directory
* @param {boolean} isGlobal - Whether this is a global (~/.copilot) install
*/
function finalizeHookConfigs(hooksDest, isGlobal) {
// Global installs have no workspace anchor → use the absolute hooks path.
// Project installs use a path relative to the workspace root so the config
// stays portable/committable across machines and teammates.
const cwd = isGlobal
? hooksDest
: path.relative(process.cwd(), hooksDest).split(path.sep).join('/');
const jsonFiles = fs.readdirSync(hooksDest).filter((f) => f.endsWith('.json'));
for (const file of jsonFiles) {
const filePath = path.join(hooksDest, file);
const config = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
config.version = 1;
for (const events of Object.values(config.hooks || {})) {
for (const entry of events) {
entry.cwd = cwd;
}
}
fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n');
}
}
function installHooks({ targetDir, force = false, dryRun = false, global: isGlobal = false }) {
const hooksSrc = path.join(TEMPLATES_DIR, 'shared', 'hooks');
const hooksDest = path.join(targetDir, 'hooks');
if (!fs.existsSync(hooksSrc)) {
warn('No hooks templates found — skipping');
return { written: 0, skipped: 0, planned: 0 };
}
// Copy hook JSON configs and scripts
const result = copyDir(hooksSrc, hooksDest, { force, dryRun });
// Make scripts executable and finalize configs (non-dry-run only)
if (!dryRun) {
const scriptsDir = path.join(hooksDest, 'scripts');
if (fs.existsSync(scriptsDir)) {
const scripts = fs.readdirSync(scriptsDir).filter(f => f.endsWith('.sh'));
for (const script of scripts) {
const scriptPath = path.join(scriptsDir, script);
fs.chmodSync(scriptPath, 0o755);
}
}
if (fs.existsSync(hooksDest)) {
finalizeHookConfigs(hooksDest, isGlobal);
}
}
if (!dryRun && result.written > 0) {
success(`Installed ${result.written} hook files`);
}
return result;
}
function ensureConfigFile({ dryRun = false, global = false } = {}) {
const configDir = global ? getHomeDir() : process.cwd();
const configPath = path.join(configDir, '.agents-toolkit.json');
if (fs.existsSync(configPath)) {
return { written: 0, planned: 0 };
}
if (dryRun) {
info(`Would create ${global ? '~' : '.'}/.agents-toolkit.json`);
return { written: 0, planned: 1 };
}
fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
success(`Created ${global ? '~' : '.'}/.agents-toolkit.json config file`);
return { written: 1, planned: 1 };
}
function installCopilotInstructions({ targetDir, dryRun = false } = {}) {
const result = { written: 0, planned: 0 };
const copilotInstructionsPath = path.join(targetDir, 'copilot-instructions.md');
const templatePath = path.join(TEMPLATES_DIR, 'agents', 'copilot-instructions.md');
if (!fs.existsSync(templatePath)) {
return result;
}
const templateContent = fs.readFileSync(templatePath, 'utf-8');
if (fs.existsSync(copilotInstructionsPath)) {
const existingContent = fs.readFileSync(copilotInstructionsPath, 'utf-8');
const marker = '## šŸ”„ Copilot Agent Workflow';
if (existingContent.includes(marker)) {
info('copilot-instructions.md already contains key sections');
return result;
}
result.planned++;
if (dryRun) {
info('Would append key sections to existing copilot-instructions.md');
return result;
}
const sectionsToAppend = templateContent.split('\n').slice(4).join('\n');
const newContent = `${existingContent}\n\n<!-- Added by agents-toolkit -->\n${sectionsToAppend}`;
fs.writeFileSync(copilotInstructionsPath, newContent);
success('Appended key sections to existing copilot-instructions.md');
result.written++;
return result;
}
result.planned++;
if (dryRun) {
info('Would create copilot-instructions.md');
return result;
}
fs.writeFileSync(copilotInstructionsPath, templateContent);
success('Created copilot-instructions.md with key sections');
result.written++;
return result;
}
function getAgentsTemplateBody(templateContent) {
const lines = templateContent.split('\n');
const dividerIndex = lines.indexOf('---');
if (dividerIndex === -1) {
return templateContent;
}
return lines.slice(dividerIndex + 1).join('\n').trimStart();
}
function installAgentsFile(options = {}) {
const {
templatePath,
force = false,
append = false,
dryRun = false,
} = options;
const result = { written: 0, planned: 0 };
const agentsPath = path.join(process.cwd(), 'AGENTS.md');
if (!fs.existsSync(templatePath)) {
return result;
}
const agentsExists = fs.existsSync(agentsPath);
if (!agentsExists || force) {
result.planned++;
if (dryRun) {
info(agentsExists ? 'Would update AGENTS.md in project root' : 'Would create AGENTS.md in project root');
return result;
}
fs.copyFileSync(templatePath, agentsPath);
success(agentsExists ? 'Updated AGENTS.md in project root' : 'Created AGENTS.md in project root');
result.written++;
return result;
}
if (!append) {
info('AGENTS.md already exists in project root (use --force to overwrite or --append to merge)');
return result;
}
const existingContent = fs.readFileSync(agentsPath, 'utf-8');
const mergeMarker = '## šŸ”„ Agent Workflow (Complex Tasks)';
if (existingContent.includes(mergeMarker)) {
info('AGENTS.md already contains workflow sections');
return result;
}
result.planned++;
if (dryRun) {
info('Would append missing sections to AGENTS.md');
return result;
}
const templateContent = fs.readFileSync(templatePath, 'utf-8');
const templateBody = getAgentsTemplateBody(templateContent);
const mergedContent = `${existingContent}\n\n<!-- Added by agents-toolkit (--append) -->\n\n${templateBody}`;
fs.writeFileSync(agentsPath, mergedContent);
success('Appended missing sections to AGENTS.md');
result.written++;
return result;
}
function installGitBasedTarget(options = {}, target = 'copilot') {
const {
force = false,
append = false,
dryRun = false,
copy = false,
global: isGlobal = false,
filters = { stack: 'all', tracker: 'all' },
} = options;
const isCodex = target === 'codex';
const targetDir = getTargetDir(isGlobal);
const scope = getInstallScope(options);
let totalChanges = 0;
/** @type {Record<string, { canonicalDir: string, agents: string[] }>} */
const installedSkillsMap = {};
const makeFilter = (category) => (name) => {
const basename = name.replace(/\.(prompt\.md|instructions\.md|md)$/, '');
return shouldIncludeFile(basename, category, filters);
};
const promptsFilter = makeFilter('prompts');
const partialsFilter = makeFilter('partials');
log(isCodex ? '\n⚔ Codex Installer\n' : isGlobal ? '\n🌐 Agents Toolkit Global Installer\n' : '\nšŸ“¦ Agents Toolkit Installer\n', 'bright');
if (isGlobal) {
info(`Target: ${targetDir}\n`);
}
if (dryRun) {
info('Dry run mode - no files will be copied\n');
}
if (scope.shouldInstallPrompts) {
info('Installing prompts...');
const result = copyDir(path.join(TEMPLATES_DIR, 'shared', 'prompts'), path.join(targetDir, 'prompts'), { force, dryRun, filter: promptsFilter, partialsFilter });
totalChanges += getChangeCount(result, dryRun);
if (!dryRun && result.written > 0) {
success(`Installed ${result.written} prompt files`);
}
}
if (scope.shouldInstallInstructions) {
info('Installing instructions...');
const result = copyDir(path.join(TEMPLATES_DIR, 'shared', 'instructions'), path.join(targetDir, 'instructions'), { force, dryRun, filter: makeFilter('instructions') });
totalChanges += getChangeCount(result, dryRun);
if (!dryRun && result.written > 0) {
success(`Installed ${result.written} instruction files`);
}
}
if (scope.shouldInstallSkills) {
info('Installing skills (npx skills standard)...');
const result = installSkillsStandard({
isGlobal,
agentSkillsDir: path.join(targetDir, 'skills'),
force,
dryRun,
copy,
dirFilter: makeFilter('skills'),
});
totalChanges += getChangeCount(result, dryRun);
// Merge installed skills for lockfile (agent dir = .github/skills or ~/.copilot/skills).
for (const [name, meta] of Object.entries(result.installedSkills)) {
if (!installedSkillsMap[name]) {
installedSkillsMap[name] = { canonicalDir: meta.canonicalDir, agents: [] };
}
installedSkillsMap[name].agents.push(path.relative(process.cwd(), path.join(targetDir, 'skills')));
}
if (!dryRun && result.written > 0) {
success(`Installed ${result.written} skill files/links`);
}
}
if (scope.shouldInstallHooks) {
info('Installing hooks...');
const hooksResult = installHooks({ targetDir, force, dryRun, global: isGlobal });
totalChanges += getChangeCount(hooksResult, dryRun);
}
const configResult = ensureConfigFile({ dryRun, global: isGlobal });
totalChanges += getChangeCount(configResult, dryRun);
if (!isGlobal && scope.shouldInstallInstructions && !isCodex) {
const copilotInstructionsResult = installCopilotInstructions({ targetDir, dryRun });
totalChanges += getChangeCount(copilotInstructionsResult, dryRun);
}
if (!isGlobal && scope.shouldInstallInstructions) {
const agentsTemplatePath = isCodex
? path.join(TEMPLATES_DIR, 'agents', 'AGENTS.codex.md')
: path.join(TEMPLATES_DIR, 'agents', 'AGENTS.md');
const agentsResult = installAgentsFile({ templatePath: agentsTemplatePath, force, append, dryRun });
totalChanges += getChangeCount(agentsResult, dryRun);
}
// Write lockfile and update .gitignore for project (non-global, non-dry-run) installs.
if (!isGlobal && !dryRun && scope.shouldInstallSkills && Object.keys(installedSkillsMap).length > 0) {
const skillsForLock = {};
for (const [name, meta] of Object.entries(installedSkillsMap)) {
skillsForLock[name] = {
computedHash: computeSkillHash(meta.canonicalDir),
agents: meta.agents,
};
}
writeLockfile({ skills: skillsForLock, config: filters, packageVersion: VERSION });
appendSkillsToGitignore(process.cwd());
}
console.log('');
if (dryRun) {
info(`Dry run complete. ${totalChanges} files would be installed.`);
} else if (totalChanges > 0) {
success(`Installation complete! ${totalChanges} files installed.`);
console.log('');
if (isGlobal) {
info('Next steps:');
console.log(` 1. Update ~/.agents-toolkit.json with your defaults`);
console.log(' 2. Instructions/prompts/skills are now available globally in VS Code');
} else {
info('Next steps:');
console.log(' 1. Update .agents-toolkit.json with your Jira project key');
if (isCodex) {
console.log(' 2. Review AGENTS.md in the project root');
console.log(' 3. Run Codex from this project root');
} else {
console.log(' 2. Configure Atlassian MCP in VS Code');
console.log(' 3. Run prompts via Command Palette > "GitHub Copilot: Run Prompt"');
}
}
} else {
warn('No new files installed. Use --force to overwrite existing files.');
}
console.log('');
}
/**
* Install prompts to target directory
* @param {Object} options - Install options
*/
function install(options = {}) {
installGitBasedTarget(options, 'copilot');
}
/**
* Install files for Codex
* @param {Object} options - Install options
*/
function installCodex(options = {}) {
installGitBasedTarget(options, 'codex');
}
/**
* Install Claude Code files (CLAUDE.md + .claude/commands/)
* @param {Object} options - Install options
*/
function installClaude(options = {}) {
const { force = false, dryRun = false, copy = false, global: isGlobal = false, filters = { stack: 'all', tracker: 'all' } } = options;
const scope = getInstallScope(options);
const claudeDir = getClaudeTargetDir(isGlobal);
const githubDir = getTargetDir(isGlobal);
let totalChanges = 0;
/** @type {Record<string, { canonicalDir: string, agents: string[] }>} */
const installedSkillsMap = {};
const makeFilter = (category) => (name) => {
const basename = name.replace(/\.(prompt\.md|instructions\.md|md)$/, '');
return shouldIncludeFile(basename, category, filters);
};
const promptsFilter = makeFilter('prompts');
const partialsFilter = makeFilter('partials');
log('\nšŸ¤– Claude Code Installer\n', 'bright');
if (dryRun) {
info('Dry run mode - no files will be copied\n');
}
if (scope.shouldInstallPrompts) {
info('Installing slash commands...');
const result = copyDir(path.join(TEMPLATES_DIR, 'shared', 'prompts'), path.join(claudeDir, 'commands'), {
force,
dryRun,
filter: promptsFilter,
partialsFilter,
renameFile: (name) => name.replace(/\.prompt\.md$/, '.md'),
transformContent: (content) => adaptPathsForClaude(stripCopilotFrontmatter(content)),
});
totalChanges += getChangeCount(result, dryRun);
if (!dryRun && result.written > 0) {
success(`Installed ${result.written} command files to .claude/commands/`);
}
}
if (scope.shouldInstallInstructions) {
info('Installing instructions...');
const result = copyDir(path.join(TEMPLATES_DIR, 'shared', 'instructions'), path.join(githubDir, 'instructions'), { force, dryRun, filter: makeFilter('instructions') });
totalChanges += getChangeCount(result, dryRun);
if (!dryRun && result.written > 0) {
success(`Installed ${result.written} instruction files`);
}
}
if (scope.shouldInstallSkills) {
info('Installing skills (npx skills standard)...');
const result = installSkillsStandard({
isGlobal,
agentSkillsDir: path.join(claudeDir, 'skills'),
force,
dryRun,
copy,
dirFilter: makeFilter('skills'),
});
totalChanges += getChangeCount(result, dryRun);
for (const [name, meta] of Object.entries(result.installedSkills)) {
if (!installedSkillsMap[name]) {
installedSkillsMap[name] = { canonicalDir: meta.canonicalDir, agents: [] };
}
installedSkillsMap[name].agents.push(path.relative(process.cwd(), path.join(claudeDir, 'skills')));
}
if (!dryRun && result.written > 0) {
success(`Installed ${result.written} skill files/links to .claude/skills/`);
}
}
if (!isGlobal && scope.shouldInstallInstructions) {
const claudeMdPath = path.join(process.cwd(), 'CLAUDE.md');
const claudeMdTemplate = path.join(TEMPLATES_DIR, 'agents', 'CLAUDE.md');
if (fs.existsSync(claudeMdTemplate)) {
const exists = fs.existsSync(claudeMdPath);
if (exists && !force) {
info('CLAUDE.md already exists (use --force to overwrite)');
} else {
totalChanges += 1;
if (dryRun) {
info(exists ? 'Would update CLAUDE.md' : 'Would create CLAUDE.md');
} else {
fs.copyFileSync(claudeMdTemplate, claudeMdPath);
success(exists ? 'Updated CLAUDE.md' : 'Created CLAUDE.md');
}
}
}
}
const configResult = ensureConfigFile({ dryRun, global: isGlobal });
totalChanges += getChangeCount(configResult, dryRun);
// Write lockfile and update .gitignore for project (non-global, non-dry-run) installs.
if (!isGlobal && !dryRun && scope.shouldInstallSkills && Object.keys(installedSkillsMap).length > 0) {
const skillsForLock = {};
for (const [name, meta] of Object.entries(installedSkillsMap)) {
skillsForLock[name] = {
computedHash: computeSkillHash(meta.canonicalDir),
agents: meta.agents,
};
}
writeLockfile({ skills: skillsForLock, config: filters, packageVersion: VERSION });
appendSkillsToGitignore(process.cwd());
}
console.log('');
if (dryRun) {
info(`Dry run complete. ${totalChanges} files would be installed.`);
} else if (totalChanges > 0) {
success(`Installation complete! ${totalChanges} files installed.`);
console.log('');
if (isGlobal) {
info('Next steps:');
console.log(' 1. Update ~/.agents-toolkit.json with your defaults');
console.log(' 2. Claude commands are now available globally');
} else {
info('Next steps:');
console.log(' 1. Update .agents-toolkit.json with your Jira project key');
console.log(' 2. Configure Atlassian MCP in Claude Code settings');
console.log(' 3. Run slash commands with /analyze-ticket, /work-ticket, etc.');
}
} else {
warn('No new files installed. Use --force to overwrite existing files.');
}
console.log('');
}
/**
* Restore skills from the lockfile.
* Reads agents-toolkit-lock.json, reinstalls all skills, and verifies hashes.
* Restore always overwrites existing skill files — that is its purpose.
* @param {Object} [options]
* @param {boolean} [options.dryRun] - Only report planned changes
* @param {boolean} [options.copy] - Copy instead of symlink
*/
function restore(options = {}) {
const { dryRun = false, copy = false } = options;
log('\nšŸ”„ Agents Toolkit Restore\n', 'bright');
const lockfile = readLockfile();
if (!lockfile) {
error(`No ${LOCKFILE_NAME} found. Run "install" first to generate it.`);
process.exit(1);
}
if (lockfile.packageVersion !== VERSION) {
warn(`Lockfile was created with v${lockfile.packageVersion}, current package is v${VERSION}.`);
warn('Run "update" to refresh the lockfile for the current version.');
}
if (dryRun) {
info('Dry run mode - no files will be restored\n');
}
const { stack = 'all', tracker = 'all' } = lockfile.config || {};
const filters = { stack, tracker };
const makeFilter = (category) => (name) => {
const basename = name.replace(/\.(prompt\.md|instructions\.md|md)$/, '');
return shouldIncludeFile(basename, category, filters);
};
// Determine which agent dirs were recorded in the lockfile.
const agentDirs = new Set();
for (const meta of Object.values(lockfile.skills || {})) {
for (const agentDir of (meta.agents || [])) {
agentDirs.add(agentDir);
}
}
// Reinstall into each unique agent skills dir.
let totalRestored = 0;
for (const agentDir of agentDirs) {
const agentSkillsDir = path.join(process.cwd(), agentDir);
const result = installSkillsStandard({
isGlobal: false,
agentSkillsDir,
force: true, // restore always overwrites — that is its purpose
dryRun,
copy,
dirFilter: makeFilter('skills'),
});
totalRestored += dryRun ? result.planned : result.written;
}
if (dryRun) {
info(`Dry run complete. ${totalRestored} files would be restored.`);
return;
}
// Verify hashes match the lockfile.
const canonicalDir = getAgentsSkillsDir(false);
let allMatch = true;
for (const [name, meta] of Object.entries(lockfile.skills || {})) {
const canonicalSkillDir = path.join(canonicalDir, name);
const hash = computeSkillHash(canonicalSkillDir);
if (hash !== meta.computedHash) {
warn(`Hash mismatch for skill "${name}" — expected ${meta.computedHash?.slice(0, 12)}… got ${hash?.slice(0, 12)}…`);
allMatch = false;
}
}
console.log('');
if (allMatch) {
success(`Restored ${Object.keys(lockfile.skills || {}).length} skills successfully.`);
} else {
warn(`Restored with hash mismatches. Run "update" to refresh the lockfile.`);
}
console.log('');
}
/**
* Show the status of installed skills relative to the lockfile.
* Exits with code 1 if any skill is missing or has a hash mismatch.
*/
function status() {
log('\nšŸ“Š Agents Toolkit Status\n', 'bright');
const lockfile = readLockfile();
if (!lockfile) {
error(`No ${LOCKFILE_NAME} found. Run "install" first to generate it.`);
process.exit(1);
}
const canonicalDir = getAgentsSkillsDir(false);
const skills = lockfile.skills || {};
let hasIssues = false;
if (Object.keys(skills).length === 0) {
info('No skills recorded in lockfile.');
return;
}
if (lockfile.packageVersion !== VERSION) {
warn(`Lockfile package version: v${lockfile.packageVersion} — current: v${VERSION}`);
}
console.log('');
const COL_NAME = 28;
const COL_STATUS = 14;
const header = `${'Skill'.padEnd(COL_NAME)} ${'Status'.padEnd(COL_STATUS)} Hash`;
log(header, 'cyan');
log('─'.repeat(header.length), 'cyan');
for (const [name, meta] of Object.entries(skills)) {
const canonicalSkillDir = path.join(canonicalDir, name);
const hash = computeSkillHash(canonicalSkillDir);
let statusLabel;
let statusColor;
if (hash === null) {
statusLabel = 'missing';
statusColor = 'red';
hasIssues = true;
} else if (hash !== meta.computedHash) {
statusLabel = 'modified';
statusColor = 'yellow';
hasIssues = true;
} else {
statusLabel = 'up-to-date';
statusColor = 'green';
}
const hashDisplay = hash ? hash.slice(0, 12) + '…' : '—';
const line = `${name.padEnd(COL_NAME)} ${statusLabel.padEnd(COL_STATUS)} ${hashDisplay}`;
log(line, statusColor);
}
console.log('');
if (hasIssues) {
warn('Some skills are out of sync. Run "restore" or "update" to fix.');
process.exit(1);
} else {
success(`All ${Object.keys(skills).length} skills are up-to-date.`);
}
console.log('');
}
/**
* List available prompts
*/
function list() {
log('\nšŸ“‹ Available Prompts\n', 'bright');
const promptsDir = path.join(TEMPLATES_DIR, 'shared', 'prompts');
if (!fs.existsSync(promptsDir)) {
error('Templates directory not found');
return;
}
const prompts = fs.readdirSync(promptsDir)
.filter(f => f.endsWith('.prompt.md'))
.map(f => f.replace('.prompt.md', ''));
log('Workflow Prompts:', 'cyan');
const workflowPrompts = ['analyze-ticket', 'create-plan', 'work-ticket', 'prepare-pr', 'create-pr', 'finalize-pr', 'analyze-github-issue', 'work-github-issue', 'create-github-pr', 'finalize-github-pr'];
workflowPrompts.forEach((p, i) => {
if (prompts.includes(p)) {
console.log(` ${i + 1}. ${p}`);
}
});
console.log('');
log('Utility Prompts:', 'cyan');
const utilityPrompts = prompts.filter(p => !workflowPrompts.includes(p));
utilityPrompts.forEach(p => {
console.log(` • ${p}`);
});
console.log('');
log('Partials:', 'cyan');
const partialsDir = path.join(promptsDir, '_partials');
if (fs.existsSync(partialsDir)) {
const partials = fs.readdirSync(partialsDir)
.filter(f => f.endsWith('.md') && f !== 'README.md');
partials.forEach(p => {
console.log(` • ${p.replace('.md', '')}`);
});
}
console.log('');
log('Skills:', 'cyan');
const skillsDir = path.join(TEMPLATES_DIR, 'shared', 'skills');
if (fs.existsSync(skillsDir)) {
const skills = fs.readdirSync(skillsDir, { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => d.name);
skills.forEach(s => {
console.log(` • ${s}`);
});
}
console.log('');
log('Hooks:', 'cyan');
const hooksDir = path.join(TEMPLATES_DIR, 'shared', 'hooks');
if (fs.existsSync(hooksDir)) {
const hooks = fs.readdirSync(hooksDir)
.filter(f => f.endsWith('.json'));
hooks.forEach(h => {
console.log(` • ${h.replace('.json', '')}`);
});
}
console.log('');
}
/**
* Show help message
*/
function showHelp() {
log('\nšŸ“¦ Agents Toolkit\n', 'bright');
console.log('Usage: agents-toolkit <command> [options]\n');
log('Commands:', 'cyan');
console.log(' install Install prompts (default target: copilot)');
console.log(' restore Restore skills from agents-toolkit-lock.json');
console.log(' status Check if installed skills match the lockfile');
console.log(' update Update existing prompts and refresh the lockfile');
console.log(' list List available prompts');
console.log(' help Show this help message');
console.log('');
log('Options:', 'cyan');
console.log(' --force, -f Overwrite existing files');
console.log(' --global, -g Install to ~/.copilot/ (user-level, all projects)');
console.log(' --target <name> Target installer: copilot | claude | codex');
console.log(' --stack <name> Filter by stack: react | wordpress | all (default: all)');
console.log(' --tracker <name> Filter by tracker: jira | github | all (default: all)');
console.log(' --claude Install for Claude Code (.claude/commands/ + CLAUDE.md)');
console.log(' --codex Install for Codex (AGENTS.md + shared .github files)');
console.log(' --append Append missing AGENTS.md sections instead of overwriting');
console.log(' --prompts-only Only install prompts (no instructions/skills)');
console.log(' --instructions-only Only install instructions');
console.log(' --partials-only Only install partials');
console.log(' --skills-only Only install skills');
console.log(' --hooks-only Only install hooks (PostToolUse validation scripts)');
console.log(' --copy Copy skills instead of symlinking to .agents/skills/');
console.log(' --dry-run Show what would be installed');
console.log('');
log('Examples:', 'cyan');
console.log(' npx agents-toolkit install # All content to .github/');
console.log(' npx agents-toolkit install --global # All content to ~/.copilot/');
console.log(' npx agents-toolkit install --global --stack react # React only to ~/.copilot/');
console.log(' npx agents-toolkit install --stack react # React/TS only');
console.log(' npx agents-toolkit install --stack wordpress # PHP/WordPress only');
console.log(' npx agents-toolkit install --tracker github # GitHub Issues workflow');
console.log(' npx agents-toolkit install --tracker jira # Jira workflow');
console.log(' npx agents-toolkit install --target codex');
console.log(' npx agents-toolkit install --target=claude');
console.log(' npx agents-toolkit install --force');
console.log(' npx agents-toolkit install --append --instructions-only');
console.log(' npx agents-toolkit install --claude --force');
console.log(' npx agents-toolkit install --codex --force');
console.log(' npx agents-toolkit install --prompts-only');
console.log(' npx agents-toolkit list');
console.log('');
}
/**
* Parse command line arguments
* @returns {Object} Parsed arguments
*/
function parseArgs() {
const args = process.argv.slice(2);
const command = args[0] || 'help';
const flags = args.slice(1);
let target = null;
let stack = null;
let tracker = null;
for (let i = 0; i < flags.length; i++) {
const arg = flags[i];
if (arg === '--target') {
const value = flags[i + 1];
if (value && !value.startsWith('-')) {
target = value;
i++;
} else {
target = '';
}
} else if (arg.startsWith('--target=')) {
target = arg.split('=').slice(1).join('=');
} else if (arg === '--stack') {
const value = flags[i + 1];
if (value && !value.startsWith('-')) {
stack = value;
i++;
} else {
stack = '';
}
} else if (arg.startsWith('--stack=')) {
stack = arg.split('=').slice(1).join('=');
} else if (arg === '--tracker') {
const value = flags[i + 1];
if (value && !value.startsWith('-')) {
tracker = value;
i++;
} else {
tracker = '';
}
} else if (arg.startsWith('--tracker=')) {
tracker = arg.split('=').slice(1).join('=');
}
}
const options = {
force: flags.includes('--force') || flags.includes('-f'),
global: flags.includes('--global') || flags.includes('-g'),
promptsOnly: flags.includes('--prompts-only'),
partialsOnly: flags.includes('--partials-only'),
skillsOnly: flags.includes('--skills-only'),
instructionsOnly: flags.includes('--instructions-only'),
hooksOnly: flags.includes('--hooks-only'),
dryRun: flags.includes('--dry-run'),
copy: flags.includes('--copy'),
claude: flags.includes('--claude'),
codex: flags.includes('--codex'),
append: flags.includes('--append'),
target,
stack,
tracker,
};
return { command, options };
}
function resolveInstallTarget(options = {}) {
const legacyTargets = [];
if (options.claude) {
legacyTargets.push('claude');
}
if (options.codex) {
legacyTargets.push('codex');
}
let explicitTarget = null;
if (options.target !== null && options.target !== undefined) {
explicitTarget = options.target.trim().toLowerCase();
if (!explicitTarget) {
error('Missing value for --target. Use copilot, claude, or codex.');
process.exit(1);
}
if (!['copilot', 'claude', 'codex'].includes(explicitTarget)) {
error(`Invalid --target value: ${options.target}. Use copilot, claude, or codex.`);
process.exit(1);
}
}
if (legacyTargets.length > 1) {
error('Use either --claude or --codex, not both.');
process.exit(1);
}
if (explicitTarget && legacyTargets.length > 0 && legacyTargets[0] !== explicitTarget) {
error(`Conflicting target flags: --target ${explicitTarget} and --${legacyTargets[0]}.`);
process.exit(1);
}
if (explicitTarget) {
return explicitTarget;
}
if (legacyTargets.length === 1) {
return legacyTargets[0];
}
return 'copilot';
}
/**
* Resolve stack and tracker filters from flags or config file
* Resolution order: CLI flags > project config > global config > defaults
* @param {Object} options - Parsed CLI options
* @returns {{ stack: string, tracker: string }} Resolved filters
*/
function resolveFilters(options = {}) {
const validStacks = ['react', 'wordpress', 'all'];
const validTrackers = ['jira', 'github', 'all'];
let stack = 'all';
let tracker = 'all';
// Check global config first (~/.agents-toolkit.json)
const globalConfigPath = path.join(getHomeDir(), '.agents-toolkit.json');
if (fs.existsSync(globalConfigPath)) {
try {
const config = JSON.parse(fs.readFileSync(globalConfigPath, 'utf-8'));
if (config.stack) stack = config.stack;
if (config.tracker) tracker = config.tracker;
} catch {
// Ignore invalid config
}
}
// Project config overrides global
const configPath = path.join(process.cwd(), '.agents-toolkit.json');
if (fs.existsSync(configPath)) {
try {
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
if (config.stack) stack = config.stack;
if (config.tracker) tracker = config.tracker;
} catch {
// Ignore invalid config
}
}
if (options.stack !== null && options.stack !== undefined) {
const value = options.stack.trim().toLowerCase();
if (!value) {
error('Missing value for --stack. Use react, wordpress, or all.');
process.exit(1);
}
if (!validStacks.includes(value)) {
error(`Invalid --stack value: ${options.stack}. Use react, wordpress, or all.`);
process.exit(1);
}
stack = value;
}
if (options.tracker !== null && options.tracker !== undefined) {
const value = options.tracker.trim().toLowerCase();
if (!value) {
error('Missing value for --tracker. Use jira, github, or all.');
process.exit(1);
}
if (!validTrackers.includes(value)) {
error(`Invalid --tracker value: ${options.tracker}. Use jira, github, or all.`);
process.exit(1);
}
tracker = value;
}
return { stack, tracker };
}
/**
* Main CLI entry point
*/
function main() {
const { command, options } = parseArgs();
const target = (command === 'install' || command === 'update')
? resolveInstallTarget(options)
: null;
const filters = (command === 'install' || command === 'update')
? resolveFilters(options)
: { stack: 'all', tracker: 'all' };
switch (command) {
case 'install':
if (target === 'claude') {
installClaude({ ...options, filters });
} else if (target === 'codex') {
installCodex({ ...options, filters });
} else {
install({ ...options, filters });
}
break;
case 'restore':
restore(options);
break;
case 'status':
status();
break;
case 'update':
if (target === 'claude') {
installClaude({ ...options, force: true, filters });
} else if (target === 'codex') {
installCodex({ ...options, force: true, filters });
} else {
install({ ...options, force: true, filters });
}
break;
case 'list':
list();
break;
case 'help':
case '--help':
case '-h':
showHelp();
break;
default:
error(`Unknown command: ${command}`);
showHelp();
process.exit(1);
}
}
main();
/**
* Agents Toolkit
* @module @silverassist/agents-toolkit
*/
export const VERSION = "2.6.0";
export const PROMPTS = {
workflow: [
"analyze-github-issue",
"analyze-ticket",
"create-github-pr",
"create-plan",
"create-pr",
"finalize-github-pr",
"finalize-pr",
"prepare-github-release",
"prepare-pr",
"work-github-issue",
"work-ticket",
],
utility: [
"add-tests",
"audit-ai-seo",
"fix-issues",
"new-wp-component",
"new-wp-plugin",
"quality-check",
"resolve-github-reviews",
"review-code",
],
};
export const PARTIALS = [
"documentation",
"git-operations",
"github-integration",
"jira-integration",
"pr-template",
"release-node",
"release-wordpress",
"validations",
];
export const INSTRUCTIONS = [
"caching",
"css-styling",
"documentation-language",
"github-workflow",
"php-standards",
"react-components",
"seo-ai-optimization",
"server-actions",
"testing-standards",
"tests",
"tsdoc-standards",
"typescript",
"wordpress-plugin-architecture",
];
export const SKILLS = [
"ai-seo-optimization",
"component-architecture",
"core-review",
"create-component",
"domain-driven-design",
"github-review-management",
"nextjs-caching",
"plugin-creation",
"quality-checks",
"release-management",
"testing",
"testing-patterns",
"tsdoc-standards",
];
export const HOOKS = ["validate-tsx", "lint-format"];
// Skills follow the `npx skills` standard: a single canonical copy lives in
// .agents/skills/ and each agent's skills directory symlinks to it.
export const SKILLS_LAYOUT = {
canonicalDir: ".agents/skills",
agentDirs: {
claude: ".claude/skills",
copilot: ".github/skills",
},
};
// Claude Code equivalents
export const CLAUDE_COMMANDS = [
"analyze-ticket",
"create-plan",
"work-ticket",
"prepare-pr",
"create-pr",
"finalize-pr",
"review-code",
"fix-issues",
"add-tests",
];
export const CLAUDE_FILES = {
instructions: "CLAUDE.md",
commandsDir: ".claude/commands",
skillsDir: ".claude/skills",
};