Files
Claude-Code-Workflow/ccw/src/tools/classify-folders.js
catlog22 91e4792aa9 feat(ccw): 添加 ccw tool exec 工具系统
新增工具:
- edit_file: AI辅助文件编辑 (update/line模式)
- get_modules_by_depth: 项目结构分析
- update_module_claude: CLAUDE.md文档生成
- generate_module_docs: 模块文档生成
- detect_changed_modules: Git变更检测
- classify_folders: 文件夹分类
- discover_design_files: 设计文件发现
- convert_tokens_to_css: 设计token转CSS
- ui_generate_preview: UI预览生成
- ui_instantiate_prototypes: 原型实例化

使用方式:
  ccw tool list              # 列出所有工具
  ccw tool exec <name> '{}'  # 执行工具

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 21:10:31 +08:00

205 lines
5.2 KiB
JavaScript

/**
* Classify Folders Tool
* Categorize folders by type for documentation generation
* Types: code (API.md + README.md), navigation (README.md only), skip (empty)
*/
import { readdirSync, statSync, existsSync } from 'fs';
import { join, resolve, extname } from 'path';
// Code file extensions
const CODE_EXTENSIONS = [
'.ts', '.tsx', '.js', '.jsx',
'.py', '.go', '.java', '.rs',
'.c', '.cpp', '.cs', '.rb',
'.php', '.swift', '.kt'
];
/**
* Count code files in a directory (non-recursive)
*/
function countCodeFiles(dirPath) {
try {
const entries = readdirSync(dirPath, { withFileTypes: true });
return entries.filter(e => {
if (!e.isFile()) return false;
const ext = extname(e.name).toLowerCase();
return CODE_EXTENSIONS.includes(ext);
}).length;
} catch (e) {
return 0;
}
}
/**
* Count subdirectories in a directory
*/
function countSubdirs(dirPath) {
try {
const entries = readdirSync(dirPath, { withFileTypes: true });
return entries.filter(e => e.isDirectory() && !e.name.startsWith('.')).length;
} catch (e) {
return 0;
}
}
/**
* Determine folder type
*/
function classifyFolder(dirPath) {
const codeFiles = countCodeFiles(dirPath);
const subdirs = countSubdirs(dirPath);
if (codeFiles > 0) {
return { type: 'code', codeFiles, subdirs }; // Generates API.md + README.md
} else if (subdirs > 0) {
return { type: 'navigation', codeFiles, subdirs }; // README.md only
} else {
return { type: 'skip', codeFiles, subdirs }; // Empty or no relevant content
}
}
/**
* Parse input from get_modules_by_depth format
* Format: depth:N|path:./path|files:N|types:[ext,ext]|has_claude:yes/no
*/
function parseModuleInput(line) {
const parts = {};
line.split('|').forEach(part => {
const [key, value] = part.split(':');
if (key && value !== undefined) {
parts[key] = value;
}
});
return parts;
}
/**
* Main execute function
*/
async function execute(params) {
const { input, path: targetPath } = params;
const results = [];
// Mode 1: Process piped input from get_modules_by_depth
if (input) {
let lines;
// Check if input is JSON (from ccw tool exec output)
if (typeof input === 'string' && input.trim().startsWith('{')) {
try {
const jsonInput = JSON.parse(input);
// Handle output from get_modules_by_depth tool (wrapped in result)
const output = jsonInput.result?.output || jsonInput.output;
if (output) {
lines = output.split('\n');
} else {
lines = [input];
}
} catch {
// Not JSON, treat as line-delimited text
lines = input.split('\n');
}
} else if (Array.isArray(input)) {
lines = input;
} else {
lines = input.split('\n');
}
for (const line of lines) {
if (!line.trim()) continue;
const parsed = parseModuleInput(line);
const folderPath = parsed.path;
if (!folderPath) continue;
const basePath = targetPath ? resolve(process.cwd(), targetPath) : process.cwd();
const fullPath = resolve(basePath, folderPath);
if (!existsSync(fullPath) || !statSync(fullPath).isDirectory()) {
continue;
}
const classification = classifyFolder(fullPath);
results.push({
path: folderPath,
type: classification.type,
code_files: classification.codeFiles,
subdirs: classification.subdirs
});
}
}
// Mode 2: Classify a single directory
else if (targetPath) {
const fullPath = resolve(process.cwd(), targetPath);
if (!existsSync(fullPath)) {
throw new Error(`Directory not found: ${fullPath}`);
}
if (!statSync(fullPath).isDirectory()) {
throw new Error(`Not a directory: ${fullPath}`);
}
const classification = classifyFolder(fullPath);
results.push({
path: targetPath,
type: classification.type,
code_files: classification.codeFiles,
subdirs: classification.subdirs
});
}
else {
throw new Error('Either "input" or "path" parameter is required');
}
// Format output
const output = results.map(r =>
`${r.path}|${r.type}|code:${r.code_files}|dirs:${r.subdirs}`
).join('\n');
return {
total: results.length,
by_type: {
code: results.filter(r => r.type === 'code').length,
navigation: results.filter(r => r.type === 'navigation').length,
skip: results.filter(r => r.type === 'skip').length
},
results,
output
};
}
/**
* Tool Definition
*/
export const classifyFoldersTool = {
name: 'classify_folders',
description: `Classify folders by type for documentation generation.
Types:
- code: Contains code files (generates API.md + README.md)
- navigation: Contains subdirectories only (generates README.md only)
- skip: Empty or no relevant content
Input: Either piped output from get_modules_by_depth or a single directory path.`,
parameters: {
type: 'object',
properties: {
input: {
type: 'string',
description: 'Piped input from get_modules_by_depth (one module per line)'
},
path: {
type: 'string',
description: 'Single directory path to classify'
}
},
required: []
},
execute
};