claude-code-workflow 6.0.4 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/action-planning-agent.md +1 -1
- package/.claude/agents/cli-execution-agent.md +269 -269
- package/.claude/agents/cli-explore-agent.md +182 -182
- package/.claude/agents/context-search-agent.md +582 -582
- package/.claude/agents/memory-bridge.md +93 -93
- package/.claude/commands/cli/cli-init.md +1 -1
- package/.claude/commands/memory/docs-full-cli.md +471 -471
- package/.claude/commands/memory/docs-related-cli.md +386 -386
- package/.claude/commands/memory/docs.md +615 -615
- package/.claude/commands/memory/load.md +1 -1
- package/.claude/commands/memory/update-full.md +332 -332
- package/.claude/commands/memory/update-related.md +5 -5
- package/.claude/commands/workflow/init.md +1 -1
- package/.claude/commands/workflow/lite-fix.md +621 -621
- package/.claude/commands/workflow/lite-plan.md +592 -592
- package/.claude/commands/workflow/tools/context-gather.md +434 -434
- package/.claude/commands/workflow/ui-design/generate.md +504 -504
- package/.claude/commands/workflow/ui-design/import-from-code.md +537 -537
- package/.claude/scripts/classify-folders.sh +4 -0
- package/.claude/scripts/convert_tokens_to_css.sh +4 -0
- package/.claude/scripts/detect_changed_modules.sh +5 -1
- package/.claude/scripts/discover-design-files.sh +87 -83
- package/.claude/scripts/generate_module_docs.sh +717 -713
- package/.claude/scripts/get_modules_by_depth.sh +5 -1
- package/.claude/scripts/ui-generate-preview.sh +4 -0
- package/.claude/scripts/ui-instantiate-prototypes.sh +4 -0
- package/.claude/scripts/update_module_claude.sh +4 -0
- package/.claude/skills/command-guide/index/all-commands.json +1 -12
- package/.claude/skills/command-guide/index/by-category.json +1 -12
- package/.claude/skills/command-guide/index/by-use-case.json +1 -12
- package/.claude/skills/command-guide/index/essential-commands.json +1 -12
- package/.claude/skills/command-guide/reference/agents/action-planning-agent.md +127 -71
- package/.claude/skills/command-guide/reference/agents/cli-execution-agent.md +269 -269
- package/.claude/skills/command-guide/reference/agents/cli-explore-agent.md +182 -182
- package/.claude/skills/command-guide/reference/agents/conceptual-planning-agent.md +18 -38
- package/.claude/skills/command-guide/reference/agents/context-search-agent.md +582 -577
- package/.claude/skills/command-guide/reference/agents/memory-bridge.md +93 -93
- package/.claude/skills/command-guide/reference/commands/cli/cli-init.md +1 -1
- package/.claude/skills/command-guide/reference/commands/memory/docs-full-cli.md +471 -471
- package/.claude/skills/command-guide/reference/commands/memory/docs-related-cli.md +386 -386
- package/.claude/skills/command-guide/reference/commands/memory/docs.md +615 -610
- package/.claude/skills/command-guide/reference/commands/memory/load.md +1 -1
- package/.claude/skills/command-guide/reference/commands/memory/update-full.md +332 -332
- package/.claude/skills/command-guide/reference/commands/memory/update-related.md +5 -5
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/artifacts.md +299 -451
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/auto-parallel.md +14 -37
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/synthesis.md +252 -350
- package/.claude/skills/command-guide/reference/commands/workflow/init.md +2 -2
- package/.claude/skills/command-guide/reference/commands/workflow/lite-execute.md +52 -0
- package/.claude/skills/command-guide/reference/commands/workflow/lite-fix.md +621 -602
- package/.claude/skills/command-guide/reference/commands/workflow/lite-plan.md +46 -36
- package/.claude/skills/command-guide/reference/commands/workflow/review-fix.md +18 -58
- package/.claude/skills/command-guide/reference/commands/workflow/review-module-cycle.md +22 -52
- package/.claude/skills/command-guide/reference/commands/workflow/review-session-cycle.md +19 -48
- package/.claude/skills/command-guide/reference/commands/workflow/session/start.md +25 -5
- package/.claude/skills/command-guide/reference/commands/workflow/tdd-plan.md +1 -1
- package/.claude/skills/command-guide/reference/commands/workflow/test-fix-gen.md +7 -7
- package/.claude/skills/command-guide/reference/commands/workflow/tools/context-gather.md +434 -434
- package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-agent.md +151 -11
- package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-tdd.md +4 -4
- package/.claude/skills/command-guide/reference/commands/workflow/tools/test-task-generate.md +1 -1
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/generate.md +504 -504
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/import-from-code.md +537 -537
- package/.claude/workflows/context-search-strategy.md +77 -77
- package/.claude/workflows/tool-strategy.md +90 -71
- package/.claude/workflows/workflow-architecture.md +1 -1
- package/ccw/package.json +6 -6
- package/ccw/src/cli.js +16 -0
- package/ccw/src/commands/stop.js +101 -0
- package/ccw/src/commands/tool.js +181 -0
- package/ccw/src/core/dashboard-generator.js +18 -3
- package/ccw/src/core/lite-scanner.js +35 -11
- package/ccw/src/core/server.js +583 -17
- package/ccw/src/templates/dashboard-css/01-base.css +161 -0
- package/ccw/src/templates/dashboard-css/02-session.css +726 -0
- package/ccw/src/templates/dashboard-css/03-tasks.css +512 -0
- package/ccw/src/templates/dashboard-css/04-lite-tasks.css +843 -0
- package/ccw/src/templates/dashboard-css/05-context.css +2206 -0
- package/ccw/src/templates/dashboard-css/06-cards.css +1570 -0
- package/ccw/src/templates/dashboard-css/07-managers.css +936 -0
- package/ccw/src/templates/dashboard-css/08-review.css +1266 -0
- package/ccw/src/templates/dashboard-css/09-explorer.css +1397 -0
- package/ccw/src/templates/dashboard-js/components/global-notifications.js +219 -0
- package/ccw/src/templates/dashboard-js/components/hook-manager.js +10 -0
- package/ccw/src/templates/dashboard-js/components/mcp-manager.js +24 -2
- package/ccw/src/templates/dashboard-js/components/navigation.js +11 -5
- package/ccw/src/templates/dashboard-js/components/tabs-context.js +20 -20
- package/ccw/src/templates/dashboard-js/components/tabs-other.js +11 -11
- package/ccw/src/templates/dashboard-js/components/theme.js +29 -1
- package/ccw/src/templates/dashboard-js/main.js +4 -0
- package/ccw/src/templates/dashboard-js/state.js +5 -0
- package/ccw/src/templates/dashboard-js/views/explorer.js +852 -0
- package/ccw/src/templates/dashboard-js/views/home.js +13 -9
- package/ccw/src/templates/dashboard-js/views/hook-manager.js +8 -5
- package/ccw/src/templates/dashboard-js/views/lite-tasks.js +21 -16
- package/ccw/src/templates/dashboard-js/views/mcp-manager.js +148 -8
- package/ccw/src/templates/dashboard-js/views/project-overview.js +15 -11
- package/ccw/src/templates/dashboard-js/views/review-session.js +3 -3
- package/ccw/src/templates/dashboard-js/views/session-detail.js +38 -28
- package/ccw/src/templates/dashboard.html +129 -28
- package/ccw/src/tools/classify-folders.js +204 -0
- package/ccw/src/tools/convert-tokens-to-css.js +250 -0
- package/ccw/src/tools/detect-changed-modules.js +288 -0
- package/ccw/src/tools/discover-design-files.js +134 -0
- package/ccw/src/tools/edit-file.js +266 -0
- package/ccw/src/tools/generate-module-docs.js +416 -0
- package/ccw/src/tools/get-modules-by-depth.js +308 -0
- package/ccw/src/tools/index.js +176 -0
- package/ccw/src/tools/ui-generate-preview.js +327 -0
- package/ccw/src/tools/ui-instantiate-prototypes.js +301 -0
- package/ccw/src/tools/update-module-claude.js +380 -0
- package/ccw/src/utils/browser-launcher.js +15 -4
- package/package.json +1 -1
- package/.claude/skills/command-guide/reference/commands/workflow/status.md +0 -352
- package/ccw/src/core/server.js.bak +0 -385
- package/ccw/src/core/server_original.bak +0 -385
- package/ccw/src/templates/dashboard.css +0 -8114
- package/ccw/src/templates/dashboard_tailwind.html +0 -42
- package/ccw/src/templates/dashboard_test.html +0 -37
- package/ccw/src/templates/tailwind-base.css +0 -212
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect Changed Modules Tool
|
|
3
|
+
* Find modules affected by git changes or recent modifications
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readdirSync, statSync, existsSync, readFileSync } from 'fs';
|
|
7
|
+
import { join, resolve, dirname, extname, relative } from 'path';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
|
|
10
|
+
// Source file extensions to track
|
|
11
|
+
const SOURCE_EXTENSIONS = [
|
|
12
|
+
'.md', '.js', '.ts', '.jsx', '.tsx',
|
|
13
|
+
'.py', '.go', '.rs', '.java', '.cpp', '.c', '.h',
|
|
14
|
+
'.sh', '.ps1', '.json', '.yaml', '.yml'
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
// Directories to exclude
|
|
18
|
+
const EXCLUDE_DIRS = [
|
|
19
|
+
'.git', '__pycache__', 'node_modules', '.venv', 'venv', 'env',
|
|
20
|
+
'dist', 'build', '.cache', '.pytest_cache', '.mypy_cache',
|
|
21
|
+
'coverage', '.nyc_output', 'logs', 'tmp', 'temp'
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if git is available and we're in a repo
|
|
26
|
+
*/
|
|
27
|
+
function isGitRepo(basePath) {
|
|
28
|
+
try {
|
|
29
|
+
execSync('git rev-parse --git-dir', { cwd: basePath, stdio: 'pipe' });
|
|
30
|
+
return true;
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get changed files from git
|
|
38
|
+
*/
|
|
39
|
+
function getGitChangedFiles(basePath) {
|
|
40
|
+
try {
|
|
41
|
+
// Get staged + unstaged changes
|
|
42
|
+
let output = execSync('git diff --name-only HEAD 2>/dev/null', {
|
|
43
|
+
cwd: basePath,
|
|
44
|
+
encoding: 'utf8',
|
|
45
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
46
|
+
}).trim();
|
|
47
|
+
|
|
48
|
+
const cachedOutput = execSync('git diff --name-only --cached 2>/dev/null', {
|
|
49
|
+
cwd: basePath,
|
|
50
|
+
encoding: 'utf8',
|
|
51
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
52
|
+
}).trim();
|
|
53
|
+
|
|
54
|
+
if (cachedOutput) {
|
|
55
|
+
output = output ? `${output}\n${cachedOutput}` : cachedOutput;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// If no working changes, check last commit
|
|
59
|
+
if (!output) {
|
|
60
|
+
output = execSync('git diff --name-only HEAD~1 HEAD 2>/dev/null', {
|
|
61
|
+
cwd: basePath,
|
|
62
|
+
encoding: 'utf8',
|
|
63
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
64
|
+
}).trim();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return output ? output.split('\n').filter(f => f.trim()) : [];
|
|
68
|
+
} catch (e) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Find recently modified files (fallback when no git changes)
|
|
75
|
+
*/
|
|
76
|
+
function findRecentlyModified(basePath, hoursAgo = 24) {
|
|
77
|
+
const results = [];
|
|
78
|
+
const cutoffTime = Date.now() - (hoursAgo * 60 * 60 * 1000);
|
|
79
|
+
|
|
80
|
+
function scan(dirPath) {
|
|
81
|
+
try {
|
|
82
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
83
|
+
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
if (entry.isDirectory()) {
|
|
86
|
+
if (EXCLUDE_DIRS.includes(entry.name)) continue;
|
|
87
|
+
scan(join(dirPath, entry.name));
|
|
88
|
+
} else if (entry.isFile()) {
|
|
89
|
+
const ext = extname(entry.name).toLowerCase();
|
|
90
|
+
if (!SOURCE_EXTENSIONS.includes(ext)) continue;
|
|
91
|
+
|
|
92
|
+
const fullPath = join(dirPath, entry.name);
|
|
93
|
+
try {
|
|
94
|
+
const stat = statSync(fullPath);
|
|
95
|
+
if (stat.mtimeMs > cutoffTime) {
|
|
96
|
+
results.push(relative(basePath, fullPath));
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
// Skip files we can't stat
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} catch (e) {
|
|
104
|
+
// Ignore permission errors
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
scan(basePath);
|
|
109
|
+
return results;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Extract unique parent directories from file list
|
|
114
|
+
*/
|
|
115
|
+
function extractDirectories(files, basePath) {
|
|
116
|
+
const dirs = new Set();
|
|
117
|
+
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
const dir = dirname(file);
|
|
120
|
+
if (dir === '.' || dir === '') {
|
|
121
|
+
dirs.add('.');
|
|
122
|
+
} else {
|
|
123
|
+
dirs.add('./' + dir.replace(/\\/g, '/'));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return Array.from(dirs).sort();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Count files in directory
|
|
132
|
+
*/
|
|
133
|
+
function countFiles(dirPath) {
|
|
134
|
+
try {
|
|
135
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
136
|
+
return entries.filter(e => e.isFile()).length;
|
|
137
|
+
} catch (e) {
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get file types in directory
|
|
144
|
+
*/
|
|
145
|
+
function getFileTypes(dirPath) {
|
|
146
|
+
const types = new Set();
|
|
147
|
+
try {
|
|
148
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
149
|
+
entries.forEach(entry => {
|
|
150
|
+
if (entry.isFile()) {
|
|
151
|
+
const ext = extname(entry.name).slice(1);
|
|
152
|
+
if (ext) types.add(ext);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
} catch (e) {
|
|
156
|
+
// Ignore
|
|
157
|
+
}
|
|
158
|
+
return Array.from(types);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Main execute function
|
|
163
|
+
*/
|
|
164
|
+
async function execute(params) {
|
|
165
|
+
const { format = 'paths', path: targetPath = '.' } = params;
|
|
166
|
+
|
|
167
|
+
const basePath = resolve(process.cwd(), targetPath);
|
|
168
|
+
|
|
169
|
+
if (!existsSync(basePath)) {
|
|
170
|
+
throw new Error(`Directory not found: ${basePath}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Get changed files
|
|
174
|
+
let changedFiles = [];
|
|
175
|
+
let changeSource = 'none';
|
|
176
|
+
|
|
177
|
+
if (isGitRepo(basePath)) {
|
|
178
|
+
changedFiles = getGitChangedFiles(basePath);
|
|
179
|
+
changeSource = changedFiles.length > 0 ? 'git' : 'none';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Fallback to recently modified files
|
|
183
|
+
if (changedFiles.length === 0) {
|
|
184
|
+
changedFiles = findRecentlyModified(basePath);
|
|
185
|
+
changeSource = changedFiles.length > 0 ? 'mtime' : 'none';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Extract affected directories
|
|
189
|
+
const affectedDirs = extractDirectories(changedFiles, basePath);
|
|
190
|
+
|
|
191
|
+
// Format output
|
|
192
|
+
let output;
|
|
193
|
+
const results = [];
|
|
194
|
+
|
|
195
|
+
for (const dir of affectedDirs) {
|
|
196
|
+
const fullPath = dir === '.' ? basePath : resolve(basePath, dir);
|
|
197
|
+
if (!existsSync(fullPath) || !statSync(fullPath).isDirectory()) continue;
|
|
198
|
+
|
|
199
|
+
const fileCount = countFiles(fullPath);
|
|
200
|
+
const types = getFileTypes(fullPath);
|
|
201
|
+
const depth = dir === '.' ? 0 : (dir.match(/\//g) || []).length;
|
|
202
|
+
const hasClaude = existsSync(join(fullPath, 'CLAUDE.md'));
|
|
203
|
+
|
|
204
|
+
results.push({
|
|
205
|
+
depth,
|
|
206
|
+
path: dir,
|
|
207
|
+
files: fileCount,
|
|
208
|
+
types,
|
|
209
|
+
has_claude: hasClaude
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
switch (format) {
|
|
214
|
+
case 'list':
|
|
215
|
+
output = results.map(r =>
|
|
216
|
+
`depth:${r.depth}|path:${r.path}|files:${r.files}|types:[${r.types.join(',')}]|has_claude:${r.has_claude ? 'yes' : 'no'}|status:changed`
|
|
217
|
+
).join('\n');
|
|
218
|
+
break;
|
|
219
|
+
|
|
220
|
+
case 'grouped':
|
|
221
|
+
const maxDepth = results.length > 0 ? Math.max(...results.map(r => r.depth)) : 0;
|
|
222
|
+
const lines = ['Affected modules by changes:'];
|
|
223
|
+
|
|
224
|
+
for (let d = 0; d <= maxDepth; d++) {
|
|
225
|
+
const atDepth = results.filter(r => r.depth === d);
|
|
226
|
+
if (atDepth.length > 0) {
|
|
227
|
+
lines.push(` Depth ${d}:`);
|
|
228
|
+
atDepth.forEach(r => {
|
|
229
|
+
const claudeIndicator = r.has_claude ? ' [OK]' : '';
|
|
230
|
+
lines.push(` - ${r.path}${claudeIndicator} (changed)`);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (results.length === 0) {
|
|
236
|
+
lines.push(' No recent changes detected');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
output = lines.join('\n');
|
|
240
|
+
break;
|
|
241
|
+
|
|
242
|
+
case 'paths':
|
|
243
|
+
default:
|
|
244
|
+
output = affectedDirs.join('\n');
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
format,
|
|
250
|
+
change_source: changeSource,
|
|
251
|
+
changed_files_count: changedFiles.length,
|
|
252
|
+
affected_modules_count: results.length,
|
|
253
|
+
results,
|
|
254
|
+
output
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Tool Definition
|
|
260
|
+
*/
|
|
261
|
+
export const detectChangedModulesTool = {
|
|
262
|
+
name: 'detect_changed_modules',
|
|
263
|
+
description: `Detect modules affected by git changes or recent file modifications.
|
|
264
|
+
Features:
|
|
265
|
+
- Git-aware: detects staged, unstaged, or last commit changes
|
|
266
|
+
- Fallback: finds files modified in last 24 hours
|
|
267
|
+
- Respects .gitignore patterns
|
|
268
|
+
|
|
269
|
+
Output formats: list, grouped, paths (default)`,
|
|
270
|
+
parameters: {
|
|
271
|
+
type: 'object',
|
|
272
|
+
properties: {
|
|
273
|
+
format: {
|
|
274
|
+
type: 'string',
|
|
275
|
+
enum: ['list', 'grouped', 'paths'],
|
|
276
|
+
description: 'Output format (default: paths)',
|
|
277
|
+
default: 'paths'
|
|
278
|
+
},
|
|
279
|
+
path: {
|
|
280
|
+
type: 'string',
|
|
281
|
+
description: 'Target directory path (default: current directory)',
|
|
282
|
+
default: '.'
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
required: []
|
|
286
|
+
},
|
|
287
|
+
execute
|
|
288
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discover Design Files Tool
|
|
3
|
+
* Find CSS/JS/HTML design-related files and output JSON
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readdirSync, statSync, existsSync, writeFileSync } from 'fs';
|
|
7
|
+
import { join, resolve, relative, extname } from 'path';
|
|
8
|
+
|
|
9
|
+
// Directories to exclude
|
|
10
|
+
const EXCLUDE_DIRS = [
|
|
11
|
+
'node_modules', 'dist', '.git', 'build', 'coverage',
|
|
12
|
+
'.cache', '.next', '.nuxt', '__pycache__', '.venv'
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
// File type patterns
|
|
16
|
+
const FILE_PATTERNS = {
|
|
17
|
+
css: ['.css', '.scss', '.sass', '.less', '.styl'],
|
|
18
|
+
js: ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs', '.vue', '.svelte'],
|
|
19
|
+
html: ['.html', '.htm']
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Find files matching extensions recursively
|
|
24
|
+
*/
|
|
25
|
+
function findFiles(basePath, extensions) {
|
|
26
|
+
const results = [];
|
|
27
|
+
|
|
28
|
+
function scan(dirPath) {
|
|
29
|
+
try {
|
|
30
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
31
|
+
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
if (EXCLUDE_DIRS.includes(entry.name)) continue;
|
|
35
|
+
scan(join(dirPath, entry.name));
|
|
36
|
+
} else if (entry.isFile()) {
|
|
37
|
+
const ext = extname(entry.name).toLowerCase();
|
|
38
|
+
if (extensions.includes(ext)) {
|
|
39
|
+
results.push(relative(basePath, join(dirPath, entry.name)).replace(/\\/g, '/'));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// Ignore permission errors
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
scan(basePath);
|
|
49
|
+
return results.sort();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Main execute function
|
|
54
|
+
*/
|
|
55
|
+
async function execute(params) {
|
|
56
|
+
const { sourceDir = '.', outputPath } = params;
|
|
57
|
+
|
|
58
|
+
const basePath = resolve(process.cwd(), sourceDir);
|
|
59
|
+
|
|
60
|
+
if (!existsSync(basePath)) {
|
|
61
|
+
throw new Error(`Directory not found: ${basePath}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!statSync(basePath).isDirectory()) {
|
|
65
|
+
throw new Error(`Not a directory: ${basePath}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Find files by type
|
|
69
|
+
const cssFiles = findFiles(basePath, FILE_PATTERNS.css);
|
|
70
|
+
const jsFiles = findFiles(basePath, FILE_PATTERNS.js);
|
|
71
|
+
const htmlFiles = findFiles(basePath, FILE_PATTERNS.html);
|
|
72
|
+
|
|
73
|
+
// Build result
|
|
74
|
+
const result = {
|
|
75
|
+
discovery_time: new Date().toISOString(),
|
|
76
|
+
source_directory: basePath,
|
|
77
|
+
file_types: {
|
|
78
|
+
css: {
|
|
79
|
+
count: cssFiles.length,
|
|
80
|
+
files: cssFiles
|
|
81
|
+
},
|
|
82
|
+
js: {
|
|
83
|
+
count: jsFiles.length,
|
|
84
|
+
files: jsFiles
|
|
85
|
+
},
|
|
86
|
+
html: {
|
|
87
|
+
count: htmlFiles.length,
|
|
88
|
+
files: htmlFiles
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
total_files: cssFiles.length + jsFiles.length + htmlFiles.length
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Write to file if outputPath specified
|
|
95
|
+
if (outputPath) {
|
|
96
|
+
const outPath = resolve(process.cwd(), outputPath);
|
|
97
|
+
writeFileSync(outPath, JSON.stringify(result, null, 2), 'utf8');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
css_count: cssFiles.length,
|
|
102
|
+
js_count: jsFiles.length,
|
|
103
|
+
html_count: htmlFiles.length,
|
|
104
|
+
total_files: result.total_files,
|
|
105
|
+
output_path: outputPath || null,
|
|
106
|
+
result
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Tool Definition
|
|
112
|
+
*/
|
|
113
|
+
export const discoverDesignFilesTool = {
|
|
114
|
+
name: 'discover_design_files',
|
|
115
|
+
description: `Discover CSS/JS/HTML design-related files in a directory.
|
|
116
|
+
Scans recursively and excludes common build/cache directories.
|
|
117
|
+
Returns JSON with file discovery results.`,
|
|
118
|
+
parameters: {
|
|
119
|
+
type: 'object',
|
|
120
|
+
properties: {
|
|
121
|
+
sourceDir: {
|
|
122
|
+
type: 'string',
|
|
123
|
+
description: 'Source directory to scan (default: current directory)',
|
|
124
|
+
default: '.'
|
|
125
|
+
},
|
|
126
|
+
outputPath: {
|
|
127
|
+
type: 'string',
|
|
128
|
+
description: 'Optional path to write JSON output file'
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
required: []
|
|
132
|
+
},
|
|
133
|
+
execute
|
|
134
|
+
};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit File Tool - AI-focused file editing
|
|
3
|
+
* Two complementary modes:
|
|
4
|
+
* - update: Content-driven text replacement (AI primary use)
|
|
5
|
+
* - line: Position-driven line operations (precise control)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
9
|
+
import { resolve, isAbsolute } from 'path';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolve file path and read content
|
|
13
|
+
* @param {string} filePath - Path to file
|
|
14
|
+
* @returns {{resolvedPath: string, content: string}}
|
|
15
|
+
*/
|
|
16
|
+
function readFile(filePath) {
|
|
17
|
+
const resolvedPath = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath);
|
|
18
|
+
|
|
19
|
+
if (!existsSync(resolvedPath)) {
|
|
20
|
+
throw new Error(`File not found: ${resolvedPath}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const content = readFileSync(resolvedPath, 'utf8');
|
|
25
|
+
return { resolvedPath, content };
|
|
26
|
+
} catch (error) {
|
|
27
|
+
throw new Error(`Failed to read file: ${error.message}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Write content to file
|
|
33
|
+
* @param {string} filePath - Path to file
|
|
34
|
+
* @param {string} content - Content to write
|
|
35
|
+
*/
|
|
36
|
+
function writeFile(filePath, content) {
|
|
37
|
+
try {
|
|
38
|
+
writeFileSync(filePath, content, 'utf8');
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new Error(`Failed to write file: ${error.message}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Mode: update - Simple text replacement
|
|
46
|
+
* Auto-adapts line endings (CRLF/LF)
|
|
47
|
+
*/
|
|
48
|
+
function executeUpdateMode(content, params) {
|
|
49
|
+
const { oldText, newText, replaceAll } = params;
|
|
50
|
+
|
|
51
|
+
if (!oldText) throw new Error('Parameter "oldText" is required for update mode');
|
|
52
|
+
if (newText === undefined) throw new Error('Parameter "newText" is required for update mode');
|
|
53
|
+
|
|
54
|
+
// Detect original line ending
|
|
55
|
+
const hasCRLF = content.includes('\r\n');
|
|
56
|
+
|
|
57
|
+
// Normalize to LF for matching
|
|
58
|
+
const normalize = (str) => str.replace(/\r\n/g, '\n');
|
|
59
|
+
const normalizedContent = normalize(content);
|
|
60
|
+
const normalizedOld = normalize(oldText);
|
|
61
|
+
const normalizedNew = normalize(newText);
|
|
62
|
+
|
|
63
|
+
let newContent = normalizedContent;
|
|
64
|
+
let status = 'not found';
|
|
65
|
+
let replacements = 0;
|
|
66
|
+
|
|
67
|
+
if (newContent.includes(normalizedOld)) {
|
|
68
|
+
if (replaceAll) {
|
|
69
|
+
const parts = newContent.split(normalizedOld);
|
|
70
|
+
replacements = parts.length - 1;
|
|
71
|
+
newContent = parts.join(normalizedNew);
|
|
72
|
+
status = 'replaced_all';
|
|
73
|
+
} else {
|
|
74
|
+
newContent = newContent.replace(normalizedOld, normalizedNew);
|
|
75
|
+
status = 'replaced';
|
|
76
|
+
replacements = 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Restore original line ending
|
|
81
|
+
if (hasCRLF) {
|
|
82
|
+
newContent = newContent.replace(/\n/g, '\r\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
content: newContent,
|
|
87
|
+
modified: content !== newContent,
|
|
88
|
+
status,
|
|
89
|
+
replacements,
|
|
90
|
+
message:
|
|
91
|
+
status === 'replaced_all'
|
|
92
|
+
? `Text replaced successfully (${replacements} occurrences)`
|
|
93
|
+
: status === 'replaced'
|
|
94
|
+
? 'Text replaced successfully'
|
|
95
|
+
: 'oldText not found in file'
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Mode: line - Line-based operations
|
|
101
|
+
* Operations: insert_before, insert_after, replace, delete
|
|
102
|
+
*/
|
|
103
|
+
function executeLineMode(content, params) {
|
|
104
|
+
const { operation, line, text, end_line } = params;
|
|
105
|
+
|
|
106
|
+
if (!operation) throw new Error('Parameter "operation" is required for line mode');
|
|
107
|
+
if (line === undefined) throw new Error('Parameter "line" is required for line mode');
|
|
108
|
+
|
|
109
|
+
// Detect original line ending and normalize for processing
|
|
110
|
+
const hasCRLF = content.includes('\r\n');
|
|
111
|
+
const normalizedContent = hasCRLF ? content.replace(/\r\n/g, '\n') : content;
|
|
112
|
+
|
|
113
|
+
const lines = normalizedContent.split('\n');
|
|
114
|
+
const lineIndex = line - 1; // Convert to 0-based
|
|
115
|
+
|
|
116
|
+
if (lineIndex < 0 || lineIndex >= lines.length) {
|
|
117
|
+
throw new Error(`Line ${line} out of range (1-${lines.length})`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let newLines = [...lines];
|
|
121
|
+
let message = '';
|
|
122
|
+
|
|
123
|
+
switch (operation) {
|
|
124
|
+
case 'insert_before':
|
|
125
|
+
if (text === undefined) throw new Error('Parameter "text" is required for insert_before');
|
|
126
|
+
newLines.splice(lineIndex, 0, text);
|
|
127
|
+
message = `Inserted before line ${line}`;
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case 'insert_after':
|
|
131
|
+
if (text === undefined) throw new Error('Parameter "text" is required for insert_after');
|
|
132
|
+
newLines.splice(lineIndex + 1, 0, text);
|
|
133
|
+
message = `Inserted after line ${line}`;
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
case 'replace':
|
|
137
|
+
if (text === undefined) throw new Error('Parameter "text" is required for replace');
|
|
138
|
+
const endIdx = end_line ? end_line - 1 : lineIndex;
|
|
139
|
+
if (endIdx < lineIndex || endIdx >= lines.length) {
|
|
140
|
+
throw new Error(`end_line ${end_line} is invalid`);
|
|
141
|
+
}
|
|
142
|
+
const deleteCount = endIdx - lineIndex + 1;
|
|
143
|
+
newLines.splice(lineIndex, deleteCount, text);
|
|
144
|
+
message = end_line ? `Replaced lines ${line}-${end_line}` : `Replaced line ${line}`;
|
|
145
|
+
break;
|
|
146
|
+
|
|
147
|
+
case 'delete':
|
|
148
|
+
const endDelete = end_line ? end_line - 1 : lineIndex;
|
|
149
|
+
if (endDelete < lineIndex || endDelete >= lines.length) {
|
|
150
|
+
throw new Error(`end_line ${end_line} is invalid`);
|
|
151
|
+
}
|
|
152
|
+
const count = endDelete - lineIndex + 1;
|
|
153
|
+
newLines.splice(lineIndex, count);
|
|
154
|
+
message = end_line ? `Deleted lines ${line}-${end_line}` : `Deleted line ${line}`;
|
|
155
|
+
break;
|
|
156
|
+
|
|
157
|
+
default:
|
|
158
|
+
throw new Error(`Unknown operation: ${operation}. Valid: insert_before, insert_after, replace, delete`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let newContent = newLines.join('\n');
|
|
162
|
+
|
|
163
|
+
// Restore original line endings
|
|
164
|
+
if (hasCRLF) {
|
|
165
|
+
newContent = newContent.replace(/\n/g, '\r\n');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
content: newContent,
|
|
170
|
+
modified: content !== newContent,
|
|
171
|
+
operation,
|
|
172
|
+
line,
|
|
173
|
+
end_line,
|
|
174
|
+
message
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Main execute function - routes to appropriate mode
|
|
180
|
+
*/
|
|
181
|
+
async function execute(params) {
|
|
182
|
+
const { path: filePath, mode = 'update' } = params;
|
|
183
|
+
|
|
184
|
+
if (!filePath) throw new Error('Parameter "path" is required');
|
|
185
|
+
|
|
186
|
+
const { resolvedPath, content } = readFile(filePath);
|
|
187
|
+
|
|
188
|
+
let result;
|
|
189
|
+
switch (mode) {
|
|
190
|
+
case 'update':
|
|
191
|
+
result = executeUpdateMode(content, params);
|
|
192
|
+
break;
|
|
193
|
+
case 'line':
|
|
194
|
+
result = executeLineMode(content, params);
|
|
195
|
+
break;
|
|
196
|
+
default:
|
|
197
|
+
throw new Error(`Unknown mode: ${mode}. Valid modes: update, line`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Write if modified
|
|
201
|
+
if (result.modified) {
|
|
202
|
+
writeFile(resolvedPath, result.content);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Remove content from result (don't return file content)
|
|
206
|
+
const { content: _, ...output } = result;
|
|
207
|
+
return output;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Edit File Tool Definition
|
|
212
|
+
*/
|
|
213
|
+
export const editFileTool = {
|
|
214
|
+
name: 'edit_file',
|
|
215
|
+
description: `Update file with two modes:
|
|
216
|
+
- update: Replace oldText with newText (default)
|
|
217
|
+
- line: Position-driven line operations`,
|
|
218
|
+
parameters: {
|
|
219
|
+
type: 'object',
|
|
220
|
+
properties: {
|
|
221
|
+
path: {
|
|
222
|
+
type: 'string',
|
|
223
|
+
description: 'Path to the file to modify'
|
|
224
|
+
},
|
|
225
|
+
mode: {
|
|
226
|
+
type: 'string',
|
|
227
|
+
enum: ['update', 'line'],
|
|
228
|
+
description: 'Edit mode (default: update)',
|
|
229
|
+
default: 'update'
|
|
230
|
+
},
|
|
231
|
+
// Update mode params
|
|
232
|
+
oldText: {
|
|
233
|
+
type: 'string',
|
|
234
|
+
description: '[update mode] Text to find and replace'
|
|
235
|
+
},
|
|
236
|
+
newText: {
|
|
237
|
+
type: 'string',
|
|
238
|
+
description: '[update mode] Replacement text'
|
|
239
|
+
},
|
|
240
|
+
replaceAll: {
|
|
241
|
+
type: 'boolean',
|
|
242
|
+
description: '[update mode] Replace all occurrences of oldText (default: false)'
|
|
243
|
+
},
|
|
244
|
+
// Line mode params
|
|
245
|
+
operation: {
|
|
246
|
+
type: 'string',
|
|
247
|
+
enum: ['insert_before', 'insert_after', 'replace', 'delete'],
|
|
248
|
+
description: '[line mode] Line operation type'
|
|
249
|
+
},
|
|
250
|
+
line: {
|
|
251
|
+
type: 'number',
|
|
252
|
+
description: '[line mode] Line number (1-based)'
|
|
253
|
+
},
|
|
254
|
+
end_line: {
|
|
255
|
+
type: 'number',
|
|
256
|
+
description: '[line mode] End line for range operations'
|
|
257
|
+
},
|
|
258
|
+
text: {
|
|
259
|
+
type: 'string',
|
|
260
|
+
description: '[line mode] Text for insert/replace operations'
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
required: ['path']
|
|
264
|
+
},
|
|
265
|
+
execute
|
|
266
|
+
};
|