mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat: Add phases for document consolidation, assembly, and compliance refinement
- Introduced Phase 2.5: Consolidation Agent to summarize analysis outputs and generate design overviews. - Added Phase 4: Document Assembly to create index-style documents linking chapter files. - Implemented Phase 5: Compliance Review & Iterative Refinement for CPCC compliance checks and updates. - Established CPCC Compliance Requirements document outlining mandatory sections and validation functions. - Created a base template for analysis agents to ensure consistency and efficiency in execution.
This commit is contained in:
@@ -51,6 +51,17 @@ interface CommandOperationResult {
|
||||
status?: number;
|
||||
}
|
||||
|
||||
interface GroupDefinition {
|
||||
name: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
interface CommandGroupsConfig {
|
||||
groups: Record<string, GroupDefinition>; // Custom group definitions
|
||||
assignments: Record<string, string>; // commandName -> groupId mapping
|
||||
}
|
||||
|
||||
// ========== Helper Functions ==========
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
@@ -125,19 +136,79 @@ function parseCommandFrontmatter(content: string): CommandMetadata {
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer group from command path if not specified in frontmatter
|
||||
* Get command groups config file path
|
||||
*/
|
||||
function inferGroupFromPath(relativePath: string, metadata: CommandMetadata): string {
|
||||
// If group is specified in frontmatter, use it
|
||||
if (metadata.group && metadata.group !== 'other') {
|
||||
return metadata.group;
|
||||
function getGroupsConfigPath(location: CommandLocation, projectPath: string): string {
|
||||
const baseDir = location === 'project'
|
||||
? join(projectPath, '.claude')
|
||||
: join(homedir(), '.claude');
|
||||
return join(baseDir, 'command-groups.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load command groups configuration
|
||||
*/
|
||||
function loadGroupsConfig(location: CommandLocation, projectPath: string): CommandGroupsConfig {
|
||||
const configPath = getGroupsConfigPath(location, projectPath);
|
||||
|
||||
const defaultConfig: CommandGroupsConfig = {
|
||||
groups: {},
|
||||
assignments: {}
|
||||
};
|
||||
|
||||
if (!existsSync(configPath)) {
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
// Infer from directory structure
|
||||
try {
|
||||
const content = readFileSync(configPath, 'utf8');
|
||||
const parsed = JSON.parse(content);
|
||||
|
||||
return {
|
||||
groups: isRecord(parsed.groups) ? parsed.groups as Record<string, GroupDefinition> : {},
|
||||
assignments: isRecord(parsed.assignments) ? parsed.assignments as Record<string, string> : {}
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(`[Commands] Failed to load groups config from ${configPath}:`, err);
|
||||
return defaultConfig;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save command groups configuration
|
||||
*/
|
||||
function saveGroupsConfig(location: CommandLocation, projectPath: string, config: CommandGroupsConfig): void {
|
||||
const configPath = getGroupsConfigPath(location, projectPath);
|
||||
const configDir = dirname(configPath);
|
||||
|
||||
if (!existsSync(configDir)) {
|
||||
mkdirSync(configDir, { recursive: true });
|
||||
}
|
||||
|
||||
try {
|
||||
const content = JSON.stringify(config, null, 2);
|
||||
require('fs').writeFileSync(configPath, content, 'utf8');
|
||||
} catch (err) {
|
||||
console.error(`[Commands] Failed to save groups config to ${configPath}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group for a command (from config or inferred from path)
|
||||
*/
|
||||
function getCommandGroup(commandName: string, relativePath: string, location: CommandLocation, projectPath: string): string {
|
||||
// First check custom assignments
|
||||
const config = loadGroupsConfig(location, projectPath);
|
||||
if (config.assignments[commandName]) {
|
||||
return config.assignments[commandName];
|
||||
}
|
||||
|
||||
// Fallback to path-based inference - use full directory path as group
|
||||
const parts = relativePath.split(/[/\\]/);
|
||||
if (parts.length > 1) {
|
||||
// Use first directory as group (e.g., 'workflow', 'issue', 'memory')
|
||||
return parts[0];
|
||||
// Use full directory path (excluding filename) as group
|
||||
// e.g., 'workflow/review/code-review.md' -> 'workflow/review'
|
||||
return parts.slice(0, -1).join('/');
|
||||
}
|
||||
|
||||
return 'other';
|
||||
@@ -150,7 +221,8 @@ function scanCommandsRecursive(
|
||||
baseDir: string,
|
||||
currentDir: string,
|
||||
location: CommandLocation,
|
||||
enabled: boolean
|
||||
enabled: boolean,
|
||||
projectPath: string
|
||||
): CommandInfo[] {
|
||||
const results: CommandInfo[] = [];
|
||||
|
||||
@@ -168,17 +240,20 @@ function scanCommandsRecursive(
|
||||
if (entry.isDirectory()) {
|
||||
// Skip _disabled directory when scanning enabled commands
|
||||
if (entry.name === '_disabled') continue;
|
||||
|
||||
|
||||
// Recursively scan subdirectories
|
||||
results.push(...scanCommandsRecursive(baseDir, fullPath, location, enabled));
|
||||
results.push(...scanCommandsRecursive(baseDir, fullPath, location, enabled, projectPath));
|
||||
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
||||
try {
|
||||
const content = readFileSync(fullPath, 'utf8');
|
||||
const metadata = parseCommandFrontmatter(content);
|
||||
const group = inferGroupFromPath(relativePath, metadata);
|
||||
const commandName = metadata.name || basename(entry.name, '.md');
|
||||
|
||||
// Get group from external config (not from frontmatter)
|
||||
const group = getCommandGroup(commandName, relativePath, location, projectPath);
|
||||
|
||||
results.push({
|
||||
name: metadata.name || basename(entry.name, '.md'),
|
||||
name: commandName,
|
||||
description: metadata.description,
|
||||
group,
|
||||
enabled,
|
||||
@@ -217,28 +292,28 @@ function getCommandsConfig(projectPath: string): CommandsConfig {
|
||||
// Scan project commands
|
||||
const projectDir = getCommandsDir('project', projectPath);
|
||||
const projectDisabledDir = getDisabledCommandsDir('project', projectPath);
|
||||
|
||||
|
||||
// Enabled project commands
|
||||
const enabledProject = scanCommandsRecursive(projectDir, projectDir, 'project', true);
|
||||
const enabledProject = scanCommandsRecursive(projectDir, projectDir, 'project', true, projectPath);
|
||||
result.projectCommands.push(...enabledProject);
|
||||
|
||||
|
||||
// Disabled project commands
|
||||
if (existsSync(projectDisabledDir)) {
|
||||
const disabledProject = scanCommandsRecursive(projectDisabledDir, projectDisabledDir, 'project', false);
|
||||
const disabledProject = scanCommandsRecursive(projectDisabledDir, projectDisabledDir, 'project', false, projectPath);
|
||||
result.projectCommands.push(...disabledProject);
|
||||
}
|
||||
|
||||
// Scan user commands
|
||||
const userDir = getCommandsDir('user', projectPath);
|
||||
const userDisabledDir = getDisabledCommandsDir('user', projectPath);
|
||||
|
||||
|
||||
// Enabled user commands
|
||||
const enabledUser = scanCommandsRecursive(userDir, userDir, 'user', true);
|
||||
const enabledUser = scanCommandsRecursive(userDir, userDir, 'user', true, projectPath);
|
||||
result.userCommands.push(...enabledUser);
|
||||
|
||||
|
||||
// Disabled user commands
|
||||
if (existsSync(userDisabledDir)) {
|
||||
const disabledUser = scanCommandsRecursive(userDisabledDir, userDisabledDir, 'user', false);
|
||||
const disabledUser = scanCommandsRecursive(userDisabledDir, userDisabledDir, 'user', false, projectPath);
|
||||
result.userCommands.push(...disabledUser);
|
||||
}
|
||||
|
||||
@@ -449,8 +524,17 @@ export async function handleCommandsRoutes(ctx: RouteContext): Promise<boolean>
|
||||
});
|
||||
|
||||
const config = getCommandsConfig(validatedProjectPath);
|
||||
|
||||
// Include groups config from both project and user
|
||||
const projectGroupsConfig = loadGroupsConfig('project', validatedProjectPath);
|
||||
const userGroupsConfig = loadGroupsConfig('user', validatedProjectPath);
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(config));
|
||||
res.end(JSON.stringify({
|
||||
...config,
|
||||
projectGroupsConfig,
|
||||
userGroupsConfig
|
||||
}));
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
const status = message.includes('Access denied') ? 403 : 400;
|
||||
@@ -513,5 +597,78 @@ export async function handleCommandsRoutes(ctx: RouteContext): Promise<boolean>
|
||||
return true;
|
||||
}
|
||||
|
||||
// GET /api/commands/groups - Get groups configuration
|
||||
if (pathname === '/api/commands/groups' && req.method === 'GET') {
|
||||
const projectPathParam = url.searchParams.get('path') || initialPath;
|
||||
const location = url.searchParams.get('location') || 'project';
|
||||
|
||||
try {
|
||||
const validatedProjectPath = await validateAllowedPath(projectPathParam, {
|
||||
mustExist: true,
|
||||
allowedDirectories: [initialPath]
|
||||
});
|
||||
|
||||
if (location !== 'project' && location !== 'user') {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Invalid location' }));
|
||||
return true;
|
||||
}
|
||||
|
||||
const groupsConfig = loadGroupsConfig(location as CommandLocation, validatedProjectPath);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(groupsConfig));
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
const status = message.includes('Access denied') ? 403 : 400;
|
||||
res.writeHead(status, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: message }));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// PUT /api/commands/groups - Update groups configuration
|
||||
if (pathname === '/api/commands/groups' && req.method === 'PUT') {
|
||||
const projectPathParam = url.searchParams.get('path') || initialPath;
|
||||
const location = url.searchParams.get('location') || 'project';
|
||||
|
||||
handlePostRequest(req, res, async (body) => {
|
||||
try {
|
||||
const validatedProjectPath = await validateAllowedPath(projectPathParam, {
|
||||
mustExist: true,
|
||||
allowedDirectories: [initialPath]
|
||||
});
|
||||
|
||||
if (location !== 'project' && location !== 'user') {
|
||||
return { error: 'Invalid location', status: 400 };
|
||||
}
|
||||
|
||||
if (!isRecord(body)) {
|
||||
return { error: 'Invalid request body', status: 400 };
|
||||
}
|
||||
|
||||
// Validate and save groups config
|
||||
const config: CommandGroupsConfig = {
|
||||
groups: isRecord(body.groups) ? body.groups as Record<string, GroupDefinition> : {},
|
||||
assignments: isRecord(body.assignments) ? body.assignments as Record<string, string> : {}
|
||||
};
|
||||
|
||||
saveGroupsConfig(location as CommandLocation, validatedProjectPath, config);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Groups configuration updated',
|
||||
data: config,
|
||||
status: 200
|
||||
};
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
const status = message.includes('Access denied') ? 403 : 400;
|
||||
console.error(`[Commands] Failed to update groups config: ${message}`);
|
||||
return { error: message, status };
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Skills Routes Module
|
||||
* Handles all Skills-related API endpoints
|
||||
*/
|
||||
import { readFileSync, existsSync, readdirSync, statSync, unlinkSync, renameSync, writeFileSync, mkdirSync, cpSync, rmSync, promises as fsPromises } from 'fs';
|
||||
import { readFileSync, existsSync, readdirSync, statSync, unlinkSync, renameSync, promises as fsPromises } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { executeCliTool } from '../../tools/cli-executor.js';
|
||||
@@ -16,8 +16,6 @@ import type {
|
||||
SkillsConfig,
|
||||
SkillInfo,
|
||||
SkillFolderValidation,
|
||||
DisabledSkillInfo,
|
||||
DisabledSkillsConfig,
|
||||
DisabledSkillSummary,
|
||||
ExtendedSkillsConfig,
|
||||
SkillOperationResult
|
||||
@@ -40,106 +38,15 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
|
||||
// ========== Skills Helper Functions ==========
|
||||
|
||||
// ========== Disabled Skills Helper Functions ==========
|
||||
|
||||
/**
|
||||
* Get disabled skills directory path
|
||||
*/
|
||||
function getDisabledSkillsDir(location: SkillLocation, projectPath: string): string {
|
||||
if (location === 'project') {
|
||||
return join(projectPath, '.claude', '.disabled-skills');
|
||||
}
|
||||
return join(homedir(), '.claude', '.disabled-skills');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disabled skills config file path
|
||||
*/
|
||||
function getDisabledSkillsConfigPath(location: SkillLocation, projectPath: string): string {
|
||||
if (location === 'project') {
|
||||
return join(projectPath, '.claude', 'disabled-skills.json');
|
||||
}
|
||||
return join(homedir(), '.claude', 'disabled-skills.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load disabled skills configuration
|
||||
* Throws on JSON parse errors to surface config corruption
|
||||
*/
|
||||
function loadDisabledSkillsConfig(location: SkillLocation, projectPath: string): DisabledSkillsConfig {
|
||||
const configPath = getDisabledSkillsConfigPath(location, projectPath);
|
||||
|
||||
if (!existsSync(configPath)) {
|
||||
return { skills: {} };
|
||||
}
|
||||
|
||||
try {
|
||||
const content = readFileSync(configPath, 'utf8');
|
||||
const config = JSON.parse(content);
|
||||
return { skills: config.skills || {} };
|
||||
} catch (error) {
|
||||
// Throw on JSON parse errors to surface config corruption
|
||||
if (error instanceof SyntaxError) {
|
||||
throw new Error(`Config file corrupted: ${configPath}`);
|
||||
}
|
||||
// Log and return empty for other errors (permission, etc.)
|
||||
console.error(`[Skills] Failed to load disabled skills config: ${error}`);
|
||||
return { skills: {} };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save disabled skills configuration
|
||||
*/
|
||||
function saveDisabledSkillsConfig(location: SkillLocation, projectPath: string, config: DisabledSkillsConfig): void {
|
||||
const configPath = getDisabledSkillsConfigPath(location, projectPath);
|
||||
const configDir = join(configPath, '..');
|
||||
|
||||
if (!existsSync(configDir)) {
|
||||
mkdirSync(configDir, { recursive: true });
|
||||
}
|
||||
|
||||
writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Move directory with fallback to copy-delete and rollback on failure
|
||||
*/
|
||||
function moveDirectory(source: string, target: string): void {
|
||||
try {
|
||||
// Try atomic rename first
|
||||
renameSync(source, target);
|
||||
} catch (error: unknown) {
|
||||
const err = error as NodeJS.ErrnoException;
|
||||
// If rename fails (cross-filesystem, permission issues), fallback to copy-delete
|
||||
if (err.code === 'EXDEV' || err.code === 'EPERM' || err.code === 'EBUSY') {
|
||||
cpSync(source, target, { recursive: true, force: true });
|
||||
try {
|
||||
rmSync(source, { recursive: true, force: true });
|
||||
} catch (rmError) {
|
||||
// Rollback: remove the copied target directory to avoid duplicates
|
||||
try {
|
||||
rmSync(target, { recursive: true, force: true });
|
||||
} catch {
|
||||
// Ignore rollback errors
|
||||
}
|
||||
throw new Error(`Failed to remove source directory after copy: ${(rmError as Error).message}`);
|
||||
}
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a skill by moving it to disabled directory
|
||||
* Disable a skill by renaming SKILL.md to SKILL.md.disabled
|
||||
*/
|
||||
async function disableSkill(
|
||||
skillName: string,
|
||||
location: SkillLocation,
|
||||
projectPath: string,
|
||||
initialPath: string,
|
||||
reason?: string
|
||||
reason?: string // Kept for API compatibility but no longer used
|
||||
): Promise<SkillOperationResult> {
|
||||
try {
|
||||
// Validate skill name
|
||||
@@ -147,7 +54,7 @@ async function disableSkill(
|
||||
return { success: false, message: 'Invalid skill name', status: 400 };
|
||||
}
|
||||
|
||||
// Get source directory
|
||||
// Get skill directory
|
||||
let skillsDir: string;
|
||||
if (location === 'project') {
|
||||
try {
|
||||
@@ -161,42 +68,23 @@ async function disableSkill(
|
||||
skillsDir = join(homedir(), '.claude', 'skills');
|
||||
}
|
||||
|
||||
const sourceDir = join(skillsDir, skillName);
|
||||
if (!existsSync(sourceDir)) {
|
||||
const skillDir = join(skillsDir, skillName);
|
||||
if (!existsSync(skillDir)) {
|
||||
return { success: false, message: 'Skill not found', status: 404 };
|
||||
}
|
||||
|
||||
// Get target directory
|
||||
const disabledDir = getDisabledSkillsDir(location, projectPath);
|
||||
if (!existsSync(disabledDir)) {
|
||||
mkdirSync(disabledDir, { recursive: true });
|
||||
const skillMdPath = join(skillDir, 'SKILL.md');
|
||||
if (!existsSync(skillMdPath)) {
|
||||
return { success: false, message: 'SKILL.md not found', status: 404 };
|
||||
}
|
||||
|
||||
const targetDir = join(disabledDir, skillName);
|
||||
if (existsSync(targetDir)) {
|
||||
return { success: false, message: 'Skill already exists in disabled directory', status: 409 };
|
||||
const disabledPath = join(skillDir, 'SKILL.md.disabled');
|
||||
if (existsSync(disabledPath)) {
|
||||
return { success: false, message: 'Skill already disabled', status: 409 };
|
||||
}
|
||||
|
||||
// Move skill to disabled directory
|
||||
moveDirectory(sourceDir, targetDir);
|
||||
|
||||
// Update config with rollback on failure
|
||||
try {
|
||||
const config = loadDisabledSkillsConfig(location, projectPath);
|
||||
config.skills[skillName] = {
|
||||
disabledAt: new Date().toISOString(),
|
||||
reason
|
||||
};
|
||||
saveDisabledSkillsConfig(location, projectPath, config);
|
||||
} catch (configError) {
|
||||
// Rollback: move the skill back to original location
|
||||
try {
|
||||
moveDirectory(targetDir, sourceDir);
|
||||
} catch {
|
||||
// Ignore rollback errors - skill is in disabled directory but not in config
|
||||
}
|
||||
throw new Error(`Failed to update config: ${(configError as Error).message}`);
|
||||
}
|
||||
// Rename: SKILL.md → SKILL.md.disabled
|
||||
renameSync(skillMdPath, disabledPath);
|
||||
|
||||
return { success: true, message: 'Skill disabled', skillName, location };
|
||||
} catch (error) {
|
||||
@@ -205,7 +93,7 @@ async function disableSkill(
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable a skill by moving it back from disabled directory
|
||||
* Enable a skill by renaming SKILL.md.disabled back to SKILL.md
|
||||
*/
|
||||
async function enableSkill(
|
||||
skillName: string,
|
||||
@@ -219,14 +107,7 @@ async function enableSkill(
|
||||
return { success: false, message: 'Invalid skill name', status: 400 };
|
||||
}
|
||||
|
||||
// Get source directory (disabled)
|
||||
const disabledDir = getDisabledSkillsDir(location, projectPath);
|
||||
const sourceDir = join(disabledDir, skillName);
|
||||
if (!existsSync(sourceDir)) {
|
||||
return { success: false, message: 'Disabled skill not found', status: 404 };
|
||||
}
|
||||
|
||||
// Get target directory (skills)
|
||||
// Get skill directory
|
||||
let skillsDir: string;
|
||||
if (location === 'project') {
|
||||
try {
|
||||
@@ -240,33 +121,24 @@ async function enableSkill(
|
||||
skillsDir = join(homedir(), '.claude', 'skills');
|
||||
}
|
||||
|
||||
if (!existsSync(skillsDir)) {
|
||||
mkdirSync(skillsDir, { recursive: true });
|
||||
const skillDir = join(skillsDir, skillName);
|
||||
if (!existsSync(skillDir)) {
|
||||
return { success: false, message: 'Skill not found', status: 404 };
|
||||
}
|
||||
|
||||
const targetDir = join(skillsDir, skillName);
|
||||
if (existsSync(targetDir)) {
|
||||
return { success: false, message: 'Skill already exists in skills directory', status: 409 };
|
||||
const disabledPath = join(skillDir, 'SKILL.md.disabled');
|
||||
if (!existsSync(disabledPath)) {
|
||||
return { success: false, message: 'Disabled skill not found', status: 404 };
|
||||
}
|
||||
|
||||
// Move skill back to skills directory
|
||||
moveDirectory(sourceDir, targetDir);
|
||||
|
||||
// Update config with rollback on failure
|
||||
try {
|
||||
const config = loadDisabledSkillsConfig(location, projectPath);
|
||||
delete config.skills[skillName];
|
||||
saveDisabledSkillsConfig(location, projectPath, config);
|
||||
} catch (configError) {
|
||||
// Rollback: move the skill back to disabled directory
|
||||
try {
|
||||
moveDirectory(targetDir, sourceDir);
|
||||
} catch {
|
||||
// Ignore rollback errors - skill is in skills directory but still in config
|
||||
}
|
||||
throw new Error(`Failed to update config: ${(configError as Error).message}`);
|
||||
const skillMdPath = join(skillDir, 'SKILL.md');
|
||||
if (existsSync(skillMdPath)) {
|
||||
return { success: false, message: 'Skill already enabled', status: 409 };
|
||||
}
|
||||
|
||||
// Rename: SKILL.md.disabled → SKILL.md
|
||||
renameSync(disabledPath, skillMdPath);
|
||||
|
||||
return { success: true, message: 'Skill enabled', skillName, location };
|
||||
} catch (error) {
|
||||
return { success: false, message: (error as Error).message, status: 500 };
|
||||
@@ -274,28 +146,33 @@ async function enableSkill(
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of disabled skills
|
||||
* Get list of disabled skills by checking for SKILL.md.disabled files
|
||||
*/
|
||||
function getDisabledSkillsList(location: SkillLocation, projectPath: string): DisabledSkillSummary[] {
|
||||
const disabledDir = getDisabledSkillsDir(location, projectPath);
|
||||
const config = loadDisabledSkillsConfig(location, projectPath);
|
||||
const result: DisabledSkillSummary[] = [];
|
||||
|
||||
if (!existsSync(disabledDir)) {
|
||||
// Get skills directory (not a separate disabled directory)
|
||||
let skillsDir: string;
|
||||
if (location === 'project') {
|
||||
skillsDir = join(projectPath, '.claude', 'skills');
|
||||
} else {
|
||||
skillsDir = join(homedir(), '.claude', 'skills');
|
||||
}
|
||||
|
||||
if (!existsSync(skillsDir)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
const skills = readdirSync(disabledDir, { withFileTypes: true });
|
||||
const skills = readdirSync(skillsDir, { withFileTypes: true });
|
||||
for (const skill of skills) {
|
||||
if (skill.isDirectory()) {
|
||||
const skillMdPath = join(disabledDir, skill.name, 'SKILL.md');
|
||||
if (existsSync(skillMdPath)) {
|
||||
const content = readFileSync(skillMdPath, 'utf8');
|
||||
const disabledPath = join(skillsDir, skill.name, 'SKILL.md.disabled');
|
||||
if (existsSync(disabledPath)) {
|
||||
const content = readFileSync(disabledPath, 'utf8');
|
||||
const parsed = parseSkillFrontmatter(content);
|
||||
const skillDir = join(disabledDir, skill.name);
|
||||
const skillDir = join(skillsDir, skill.name);
|
||||
const supportingFiles = getSupportingFiles(skillDir);
|
||||
const disabledInfo = config.skills[skill.name] || { disabledAt: new Date().toISOString() };
|
||||
|
||||
result.push({
|
||||
name: parsed.name || skill.name,
|
||||
@@ -306,8 +183,8 @@ function getDisabledSkillsList(location: SkillLocation, projectPath: string): Di
|
||||
location,
|
||||
path: skillDir,
|
||||
supportingFiles,
|
||||
disabledAt: disabledInfo.disabledAt,
|
||||
reason: disabledInfo.reason
|
||||
disabledAt: new Date().toISOString(), // Cannot get exact time without config file
|
||||
reason: undefined // No longer stored
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -396,7 +273,8 @@ function getSupportingFiles(skillDir: string): string[] {
|
||||
try {
|
||||
const entries = readdirSync(skillDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.name !== 'SKILL.md') {
|
||||
// Exclude SKILL.md and SKILL.md.disabled from supporting files
|
||||
if (entry.name !== 'SKILL.md' && entry.name !== 'SKILL.md.disabled') {
|
||||
if (entry.isFile()) {
|
||||
files.push(entry.name);
|
||||
} else if (entry.isDirectory()) {
|
||||
|
||||
Reference in New Issue
Block a user