claude-code-workflow 6.3.9 → 6.3.11
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/CLAUDE.md +1 -1
- package/.claude/agents/issue-plan-agent.md +21 -15
- package/.claude/agents/issue-queue-agent.md +114 -87
- package/.claude/commands/issue/discover.md +427 -0
- package/.claude/commands/issue/execute.md +195 -363
- package/.claude/commands/issue/new.md +13 -1
- package/.claude/commands/issue/plan.md +55 -32
- package/.claude/commands/issue/queue.md +145 -71
- package/.claude/commands/workflow/init.md +75 -29
- package/.claude/commands/workflow/lite-fix.md +8 -0
- package/.claude/commands/workflow/lite-plan.md +8 -0
- package/.claude/commands/workflow/review-module-cycle.md +4 -0
- package/.claude/commands/workflow/review-session-cycle.md +4 -0
- package/.claude/commands/workflow/review.md +4 -4
- package/.claude/commands/workflow/session/solidify.md +299 -0
- package/.claude/commands/workflow/session/start.md +10 -7
- package/.claude/commands/workflow/tools/context-gather.md +17 -10
- package/.claude/skills/software-manual/SKILL.md +184 -0
- package/.claude/skills/software-manual/phases/01-requirements-discovery.md +162 -0
- package/.claude/skills/software-manual/phases/02-project-exploration.md +101 -0
- package/.claude/skills/software-manual/phases/02.5-api-extraction.md +161 -0
- package/.claude/skills/software-manual/phases/03-parallel-analysis.md +183 -0
- package/.claude/skills/software-manual/phases/03.5-consolidation.md +82 -0
- package/.claude/skills/software-manual/phases/04-screenshot-capture.md +89 -0
- package/.claude/skills/software-manual/phases/05-html-assembly.md +132 -0
- package/.claude/skills/software-manual/phases/06-iterative-refinement.md +259 -0
- package/.claude/skills/software-manual/scripts/api-extractor.md +245 -0
- package/.claude/skills/software-manual/scripts/bundle-libraries.md +85 -0
- package/.claude/skills/software-manual/scripts/extract_apis.py +270 -0
- package/.claude/skills/software-manual/scripts/screenshot-helper.md +447 -0
- package/.claude/skills/software-manual/scripts/swagger-runner.md +419 -0
- package/.claude/skills/software-manual/scripts/typedoc-runner.md +357 -0
- package/.claude/skills/software-manual/specs/html-template.md +325 -0
- package/.claude/skills/software-manual/specs/quality-standards.md +253 -0
- package/.claude/skills/software-manual/specs/writing-style.md +298 -0
- package/.claude/skills/software-manual/templates/css/wiki-base.css +788 -0
- package/.claude/skills/software-manual/templates/css/wiki-dark.css +278 -0
- package/.claude/skills/software-manual/templates/tiddlywiki-shell.html +327 -0
- package/.claude/workflows/cli-templates/schemas/discovery-finding-schema.json +219 -0
- package/.claude/workflows/cli-templates/schemas/discovery-state-schema.json +125 -0
- package/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json +168 -74
- package/.claude/workflows/cli-templates/schemas/queue-schema.json +225 -108
- package/.claude/workflows/cli-templates/schemas/solution-schema.json +6 -28
- package/.claude/workflows/context-tools.md +17 -25
- package/.codex/AGENTS.md +10 -5
- package/.codex/prompts/issue-execute.md +174 -84
- package/.codex/prompts/issue-plan.md +106 -0
- package/.codex/prompts/issue-queue.md +225 -0
- package/ccw/dist/cli.d.ts.map +1 -1
- package/ccw/dist/cli.js +1 -0
- package/ccw/dist/cli.js.map +1 -1
- package/ccw/dist/commands/issue.d.ts.map +1 -1
- package/ccw/dist/commands/issue.js +443 -123
- package/ccw/dist/commands/issue.js.map +1 -1
- package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
- package/ccw/dist/core/dashboard-generator.js +4 -1
- package/ccw/dist/core/dashboard-generator.js.map +1 -1
- package/ccw/dist/core/data-aggregator.d.ts +32 -0
- package/ccw/dist/core/data-aggregator.d.ts.map +1 -1
- package/ccw/dist/core/data-aggregator.js +55 -11
- package/ccw/dist/core/data-aggregator.js.map +1 -1
- package/ccw/dist/core/routes/discovery-routes.d.ts +37 -0
- package/ccw/dist/core/routes/discovery-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/discovery-routes.js +514 -0
- package/ccw/dist/core/routes/discovery-routes.js.map +1 -0
- package/ccw/dist/core/server.d.ts.map +1 -1
- package/ccw/dist/core/server.js +9 -1
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/dist/tools/codex-lens.d.ts +12 -1
- package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
- package/ccw/dist/tools/codex-lens.js +56 -7
- package/ccw/dist/tools/codex-lens.js.map +1 -1
- package/ccw/src/cli.ts +1 -0
- package/ccw/src/commands/issue.ts +498 -158
- package/ccw/src/core/dashboard-generator.ts +4 -1
- package/ccw/src/core/data-aggregator.ts +94 -11
- package/ccw/src/core/routes/discovery-routes.ts +607 -0
- package/ccw/src/core/server.ts +9 -1
- package/ccw/src/templates/dashboard-css/34-discovery.css +783 -0
- package/ccw/src/templates/dashboard-js/components/cli-status.js +1 -78
- package/ccw/src/templates/dashboard-js/components/navigation.js +8 -0
- package/ccw/src/templates/dashboard-js/i18n.js +140 -4
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +0 -18
- package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +13 -3
- package/ccw/src/templates/dashboard-js/views/issue-discovery.js +730 -0
- package/ccw/src/templates/dashboard-js/views/issue-manager.js +57 -26
- package/ccw/src/templates/dashboard-js/views/project-overview.js +153 -0
- package/ccw/src/templates/dashboard.html +5 -0
- package/ccw/src/tools/codex-lens.ts +75 -9
- package/package.json +1 -1
- package/.claude/workflows/context-tools-ace.md +0 -105
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
API 文档提取脚本
|
|
4
|
+
支持 FastAPI、TypeScript、Python 模块
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, Any, Optional
|
|
12
|
+
|
|
13
|
+
# 项目配置
|
|
14
|
+
PROJECTS = {
|
|
15
|
+
'backend': {
|
|
16
|
+
'path': Path('D:/dongdiankaifa9/backend'),
|
|
17
|
+
'type': 'fastapi',
|
|
18
|
+
'entry': 'app.main:app',
|
|
19
|
+
'output': 'api-docs/backend'
|
|
20
|
+
},
|
|
21
|
+
'frontend': {
|
|
22
|
+
'path': Path('D:/dongdiankaifa9/frontend'),
|
|
23
|
+
'type': 'typescript',
|
|
24
|
+
'entries': ['lib', 'hooks', 'components'],
|
|
25
|
+
'output': 'api-docs/frontend'
|
|
26
|
+
},
|
|
27
|
+
'hydro_generator_module': {
|
|
28
|
+
'path': Path('D:/dongdiankaifa9/hydro_generator_module'),
|
|
29
|
+
'type': 'python',
|
|
30
|
+
'output': 'api-docs/hydro_generator'
|
|
31
|
+
},
|
|
32
|
+
'multiphysics_network': {
|
|
33
|
+
'path': Path('D:/dongdiankaifa9/multiphysics_network'),
|
|
34
|
+
'type': 'python',
|
|
35
|
+
'output': 'api-docs/multiphysics'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def extract_fastapi(name: str, config: Dict[str, Any], output_base: Path) -> bool:
|
|
41
|
+
"""提取 FastAPI OpenAPI 文档"""
|
|
42
|
+
path = config['path']
|
|
43
|
+
output_dir = output_base / config['output']
|
|
44
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
|
|
46
|
+
# 添加路径到 sys.path
|
|
47
|
+
if str(path) not in sys.path:
|
|
48
|
+
sys.path.insert(0, str(path))
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
# 动态导入 app
|
|
52
|
+
from app.main import app
|
|
53
|
+
|
|
54
|
+
# 获取 OpenAPI schema
|
|
55
|
+
openapi_schema = app.openapi()
|
|
56
|
+
|
|
57
|
+
# 保存 JSON
|
|
58
|
+
json_path = output_dir / 'openapi.json'
|
|
59
|
+
with open(json_path, 'w', encoding='utf-8') as f:
|
|
60
|
+
json.dump(openapi_schema, f, indent=2, ensure_ascii=False)
|
|
61
|
+
|
|
62
|
+
# 生成 Markdown 摘要
|
|
63
|
+
md_path = output_dir / 'API_SUMMARY.md'
|
|
64
|
+
generate_api_markdown(openapi_schema, md_path)
|
|
65
|
+
|
|
66
|
+
endpoints = len(openapi_schema.get('paths', {}))
|
|
67
|
+
print(f" ✓ Extracted {endpoints} endpoints → {output_dir}")
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
except ImportError as e:
|
|
71
|
+
print(f" ✗ Import error: {e}")
|
|
72
|
+
return False
|
|
73
|
+
except Exception as e:
|
|
74
|
+
print(f" ✗ Error: {e}")
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def generate_api_markdown(schema: Dict, output_path: Path):
|
|
79
|
+
"""从 OpenAPI schema 生成 Markdown"""
|
|
80
|
+
lines = [
|
|
81
|
+
f"# {schema.get('info', {}).get('title', 'API Reference')}",
|
|
82
|
+
"",
|
|
83
|
+
f"Version: {schema.get('info', {}).get('version', '1.0.0')}",
|
|
84
|
+
"",
|
|
85
|
+
"## Endpoints",
|
|
86
|
+
"",
|
|
87
|
+
"| Method | Path | Summary |",
|
|
88
|
+
"|--------|------|---------|"
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
for path, methods in schema.get('paths', {}).items():
|
|
92
|
+
for method, details in methods.items():
|
|
93
|
+
if method in ('get', 'post', 'put', 'delete', 'patch'):
|
|
94
|
+
summary = details.get('summary', details.get('operationId', '-'))
|
|
95
|
+
lines.append(f"| `{method.upper()}` | `{path}` | {summary} |")
|
|
96
|
+
|
|
97
|
+
lines.extend([
|
|
98
|
+
"",
|
|
99
|
+
"## Schemas",
|
|
100
|
+
""
|
|
101
|
+
])
|
|
102
|
+
|
|
103
|
+
for name, schema_def in schema.get('components', {}).get('schemas', {}).items():
|
|
104
|
+
lines.append(f"### {name}")
|
|
105
|
+
lines.append("")
|
|
106
|
+
if 'properties' in schema_def:
|
|
107
|
+
lines.append("| Property | Type | Required |")
|
|
108
|
+
lines.append("|----------|------|----------|")
|
|
109
|
+
required = schema_def.get('required', [])
|
|
110
|
+
for prop, prop_def in schema_def['properties'].items():
|
|
111
|
+
prop_type = prop_def.get('type', prop_def.get('$ref', 'any'))
|
|
112
|
+
is_required = '✓' if prop in required else ''
|
|
113
|
+
lines.append(f"| `{prop}` | {prop_type} | {is_required} |")
|
|
114
|
+
lines.append("")
|
|
115
|
+
|
|
116
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
117
|
+
f.write('\n'.join(lines))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def extract_typescript(name: str, config: Dict[str, Any], output_base: Path) -> bool:
|
|
121
|
+
"""提取 TypeScript 文档 (TypeDoc)"""
|
|
122
|
+
path = config['path']
|
|
123
|
+
output_dir = output_base / config['output']
|
|
124
|
+
|
|
125
|
+
# 检查 TypeDoc 是否已安装
|
|
126
|
+
try:
|
|
127
|
+
result = subprocess.run(
|
|
128
|
+
['npx', 'typedoc', '--version'],
|
|
129
|
+
cwd=path,
|
|
130
|
+
capture_output=True,
|
|
131
|
+
text=True
|
|
132
|
+
)
|
|
133
|
+
if result.returncode != 0:
|
|
134
|
+
print(f" ⚠ TypeDoc not installed, installing...")
|
|
135
|
+
subprocess.run(
|
|
136
|
+
['npm', 'install', '--save-dev', 'typedoc', 'typedoc-plugin-markdown'],
|
|
137
|
+
cwd=path,
|
|
138
|
+
check=True
|
|
139
|
+
)
|
|
140
|
+
except FileNotFoundError:
|
|
141
|
+
print(f" ✗ npm/npx not found")
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
# 运行 TypeDoc
|
|
145
|
+
try:
|
|
146
|
+
entries = config.get('entries', ['lib'])
|
|
147
|
+
cmd = [
|
|
148
|
+
'npx', 'typedoc',
|
|
149
|
+
'--plugin', 'typedoc-plugin-markdown',
|
|
150
|
+
'--out', str(output_dir),
|
|
151
|
+
'--entryPointStrategy', 'expand',
|
|
152
|
+
'--exclude', '**/node_modules/**',
|
|
153
|
+
'--exclude', '**/*.test.*',
|
|
154
|
+
'--readme', 'none'
|
|
155
|
+
]
|
|
156
|
+
for entry in entries:
|
|
157
|
+
entry_path = path / entry
|
|
158
|
+
if entry_path.exists():
|
|
159
|
+
cmd.extend(['--entryPoints', str(entry_path)])
|
|
160
|
+
|
|
161
|
+
result = subprocess.run(cmd, cwd=path, capture_output=True, text=True)
|
|
162
|
+
|
|
163
|
+
if result.returncode == 0:
|
|
164
|
+
print(f" ✓ TypeDoc generated → {output_dir}")
|
|
165
|
+
return True
|
|
166
|
+
else:
|
|
167
|
+
print(f" ✗ TypeDoc error: {result.stderr[:200]}")
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
except Exception as e:
|
|
171
|
+
print(f" ✗ Error: {e}")
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def extract_python_module(name: str, config: Dict[str, Any], output_base: Path) -> bool:
|
|
176
|
+
"""提取 Python 模块文档 (pdoc)"""
|
|
177
|
+
path = config['path']
|
|
178
|
+
output_dir = output_base / config['output']
|
|
179
|
+
module_name = path.name
|
|
180
|
+
|
|
181
|
+
# 检查 pdoc
|
|
182
|
+
try:
|
|
183
|
+
subprocess.run(['pdoc', '--version'], capture_output=True, check=True)
|
|
184
|
+
except (FileNotFoundError, subprocess.CalledProcessError):
|
|
185
|
+
print(f" ⚠ pdoc not installed, installing...")
|
|
186
|
+
subprocess.run([sys.executable, '-m', 'pip', 'install', 'pdoc'], check=True)
|
|
187
|
+
|
|
188
|
+
# 运行 pdoc
|
|
189
|
+
try:
|
|
190
|
+
result = subprocess.run(
|
|
191
|
+
[
|
|
192
|
+
'pdoc', module_name,
|
|
193
|
+
'--output-dir', str(output_dir),
|
|
194
|
+
'--format', 'markdown'
|
|
195
|
+
],
|
|
196
|
+
cwd=path.parent,
|
|
197
|
+
capture_output=True,
|
|
198
|
+
text=True
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if result.returncode == 0:
|
|
202
|
+
# 统计生成的文件
|
|
203
|
+
md_files = list(output_dir.glob('**/*.md'))
|
|
204
|
+
print(f" ✓ pdoc generated {len(md_files)} files → {output_dir}")
|
|
205
|
+
return True
|
|
206
|
+
else:
|
|
207
|
+
print(f" ✗ pdoc error: {result.stderr[:200]}")
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
except Exception as e:
|
|
211
|
+
print(f" ✗ Error: {e}")
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
EXTRACTORS = {
|
|
216
|
+
'fastapi': extract_fastapi,
|
|
217
|
+
'typescript': extract_typescript,
|
|
218
|
+
'python': extract_python_module
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def main(output_base: Optional[str] = None, projects: Optional[list] = None):
|
|
223
|
+
"""主入口"""
|
|
224
|
+
base = Path(output_base) if output_base else Path.cwd()
|
|
225
|
+
|
|
226
|
+
print("=" * 50)
|
|
227
|
+
print("API Documentation Extraction")
|
|
228
|
+
print("=" * 50)
|
|
229
|
+
|
|
230
|
+
results = {}
|
|
231
|
+
|
|
232
|
+
for name, config in PROJECTS.items():
|
|
233
|
+
if projects and name not in projects:
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
print(f"\n[{name}] ({config['type']})")
|
|
237
|
+
|
|
238
|
+
if not config['path'].exists():
|
|
239
|
+
print(f" ✗ Path not found: {config['path']}")
|
|
240
|
+
results[name] = False
|
|
241
|
+
continue
|
|
242
|
+
|
|
243
|
+
extractor = EXTRACTORS.get(config['type'])
|
|
244
|
+
if extractor:
|
|
245
|
+
results[name] = extractor(name, config, base)
|
|
246
|
+
else:
|
|
247
|
+
print(f" ✗ Unknown type: {config['type']}")
|
|
248
|
+
results[name] = False
|
|
249
|
+
|
|
250
|
+
# 汇总
|
|
251
|
+
print("\n" + "=" * 50)
|
|
252
|
+
print("Summary")
|
|
253
|
+
print("=" * 50)
|
|
254
|
+
success = sum(1 for v in results.values() if v)
|
|
255
|
+
print(f"Success: {success}/{len(results)}")
|
|
256
|
+
|
|
257
|
+
return all(results.values())
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
if __name__ == '__main__':
|
|
261
|
+
import argparse
|
|
262
|
+
|
|
263
|
+
parser = argparse.ArgumentParser(description='Extract API documentation')
|
|
264
|
+
parser.add_argument('--output', '-o', default='.', help='Output base directory')
|
|
265
|
+
parser.add_argument('--projects', '-p', nargs='+', help='Specific projects to extract')
|
|
266
|
+
|
|
267
|
+
args = parser.parse_args()
|
|
268
|
+
|
|
269
|
+
success = main(args.output, args.projects)
|
|
270
|
+
sys.exit(0 if success else 1)
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
# Screenshot Helper
|
|
2
|
+
|
|
3
|
+
Guide for capturing screenshots using Chrome MCP.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This script helps capture screenshots of web interfaces for the software manual using Chrome MCP or fallback methods.
|
|
8
|
+
|
|
9
|
+
## Chrome MCP Prerequisites
|
|
10
|
+
|
|
11
|
+
### Check MCP Availability
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
async function checkChromeMCPAvailability() {
|
|
15
|
+
try {
|
|
16
|
+
// Attempt to get Chrome version via MCP
|
|
17
|
+
const version = await mcp__chrome__getVersion();
|
|
18
|
+
return {
|
|
19
|
+
available: true,
|
|
20
|
+
browser: version.browser,
|
|
21
|
+
version: version.version
|
|
22
|
+
};
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return {
|
|
25
|
+
available: false,
|
|
26
|
+
error: error.message
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### MCP Configuration
|
|
33
|
+
|
|
34
|
+
Expected Claude configuration for Chrome MCP:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"chrome": {
|
|
40
|
+
"command": "npx",
|
|
41
|
+
"args": ["@anthropic-ai/mcp-chrome"],
|
|
42
|
+
"env": {
|
|
43
|
+
"CHROME_PATH": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Screenshot Workflow
|
|
51
|
+
|
|
52
|
+
### Step 1: Prepare Environment
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
async function prepareScreenshotEnvironment(workDir, config) {
|
|
56
|
+
const screenshotDir = `${workDir}/screenshots`;
|
|
57
|
+
|
|
58
|
+
// Create directory
|
|
59
|
+
Bash({ command: `mkdir -p "${screenshotDir}"` });
|
|
60
|
+
|
|
61
|
+
// Check Chrome MCP
|
|
62
|
+
const chromeMCP = await checkChromeMCPAvailability();
|
|
63
|
+
|
|
64
|
+
if (!chromeMCP.available) {
|
|
65
|
+
console.log('Chrome MCP not available. Will generate manual guide.');
|
|
66
|
+
return { mode: 'manual' };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Start development server if needed
|
|
70
|
+
if (config.screenshot_config?.dev_command) {
|
|
71
|
+
const server = await startDevServer(config);
|
|
72
|
+
return { mode: 'auto', server, screenshotDir };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { mode: 'auto', screenshotDir };
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Step 2: Start Development Server
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
async function startDevServer(config) {
|
|
83
|
+
const devCommand = config.screenshot_config.dev_command;
|
|
84
|
+
const devUrl = config.screenshot_config.dev_url;
|
|
85
|
+
|
|
86
|
+
// Start server in background
|
|
87
|
+
const server = Bash({
|
|
88
|
+
command: devCommand,
|
|
89
|
+
run_in_background: true
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
console.log(`Starting dev server: ${devCommand}`);
|
|
93
|
+
|
|
94
|
+
// Wait for server to be ready
|
|
95
|
+
const ready = await waitForServer(devUrl, 30000);
|
|
96
|
+
|
|
97
|
+
if (!ready) {
|
|
98
|
+
throw new Error(`Server at ${devUrl} did not start in time`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log(`Dev server ready at ${devUrl}`);
|
|
102
|
+
|
|
103
|
+
return server;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function waitForServer(url, timeout = 30000) {
|
|
107
|
+
const start = Date.now();
|
|
108
|
+
|
|
109
|
+
while (Date.now() - start < timeout) {
|
|
110
|
+
try {
|
|
111
|
+
const response = await fetch(url, { method: 'HEAD' });
|
|
112
|
+
if (response.ok) return true;
|
|
113
|
+
} catch (e) {
|
|
114
|
+
// Server not ready
|
|
115
|
+
}
|
|
116
|
+
await sleep(1000);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Step 3: Capture Screenshots
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
async function captureScreenshots(screenshots, config, workDir) {
|
|
127
|
+
const results = {
|
|
128
|
+
captured: [],
|
|
129
|
+
failed: []
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const devUrl = config.screenshot_config.dev_url;
|
|
133
|
+
const screenshotDir = `${workDir}/screenshots`;
|
|
134
|
+
|
|
135
|
+
for (const ss of screenshots) {
|
|
136
|
+
try {
|
|
137
|
+
// Build full URL
|
|
138
|
+
const fullUrl = new URL(ss.url, devUrl).href;
|
|
139
|
+
|
|
140
|
+
console.log(`Capturing: ${ss.id} (${fullUrl})`);
|
|
141
|
+
|
|
142
|
+
// Configure capture options
|
|
143
|
+
const options = {
|
|
144
|
+
url: fullUrl,
|
|
145
|
+
viewport: { width: 1280, height: 800 },
|
|
146
|
+
fullPage: ss.fullPage || false
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Wait for specific element if specified
|
|
150
|
+
if (ss.wait_for) {
|
|
151
|
+
options.waitFor = ss.wait_for;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Capture specific element if selector provided
|
|
155
|
+
if (ss.selector) {
|
|
156
|
+
options.selector = ss.selector;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Add delay for animations
|
|
160
|
+
await sleep(500);
|
|
161
|
+
|
|
162
|
+
// Capture via Chrome MCP
|
|
163
|
+
const result = await mcp__chrome__screenshot(options);
|
|
164
|
+
|
|
165
|
+
// Save as PNG
|
|
166
|
+
const filename = `${ss.id}.png`;
|
|
167
|
+
Write(`${screenshotDir}/${filename}`, result.data, { encoding: 'base64' });
|
|
168
|
+
|
|
169
|
+
results.captured.push({
|
|
170
|
+
id: ss.id,
|
|
171
|
+
file: filename,
|
|
172
|
+
url: ss.url,
|
|
173
|
+
description: ss.description,
|
|
174
|
+
size: result.data.length
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error(`Failed to capture ${ss.id}:`, error.message);
|
|
179
|
+
results.failed.push({
|
|
180
|
+
id: ss.id,
|
|
181
|
+
url: ss.url,
|
|
182
|
+
error: error.message
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return results;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Step 4: Generate Manifest
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
function generateScreenshotManifest(results, workDir) {
|
|
195
|
+
const manifest = {
|
|
196
|
+
generated: new Date().toISOString(),
|
|
197
|
+
total: results.captured.length + results.failed.length,
|
|
198
|
+
captured: results.captured.length,
|
|
199
|
+
failed: results.failed.length,
|
|
200
|
+
screenshots: results.captured,
|
|
201
|
+
failures: results.failed
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
Write(`${workDir}/screenshots/screenshots-manifest.json`,
|
|
205
|
+
JSON.stringify(manifest, null, 2));
|
|
206
|
+
|
|
207
|
+
return manifest;
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Step 5: Cleanup
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
async function cleanupScreenshotEnvironment(env) {
|
|
215
|
+
if (env.server) {
|
|
216
|
+
console.log('Stopping dev server...');
|
|
217
|
+
KillShell({ shell_id: env.server.task_id });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Main Runner
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
async function runScreenshotCapture(workDir, screenshots) {
|
|
226
|
+
const config = JSON.parse(Read(`${workDir}/manual-config.json`));
|
|
227
|
+
|
|
228
|
+
// Prepare environment
|
|
229
|
+
const env = await prepareScreenshotEnvironment(workDir, config);
|
|
230
|
+
|
|
231
|
+
if (env.mode === 'manual') {
|
|
232
|
+
// Generate manual capture guide
|
|
233
|
+
generateManualCaptureGuide(screenshots, workDir);
|
|
234
|
+
return { success: false, mode: 'manual' };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
// Capture screenshots
|
|
239
|
+
const results = await captureScreenshots(screenshots, config, workDir);
|
|
240
|
+
|
|
241
|
+
// Generate manifest
|
|
242
|
+
const manifest = generateScreenshotManifest(results, workDir);
|
|
243
|
+
|
|
244
|
+
// Generate manual guide for failed captures
|
|
245
|
+
if (results.failed.length > 0) {
|
|
246
|
+
generateManualCaptureGuide(results.failed, workDir);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
success: true,
|
|
251
|
+
captured: results.captured.length,
|
|
252
|
+
failed: results.failed.length,
|
|
253
|
+
manifest
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
} finally {
|
|
257
|
+
// Cleanup
|
|
258
|
+
await cleanupScreenshotEnvironment(env);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Manual Capture Fallback
|
|
264
|
+
|
|
265
|
+
When Chrome MCP is unavailable:
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
function generateManualCaptureGuide(screenshots, workDir) {
|
|
269
|
+
const guide = `
|
|
270
|
+
# Manual Screenshot Capture Guide
|
|
271
|
+
|
|
272
|
+
Chrome MCP is not available. Please capture screenshots manually.
|
|
273
|
+
|
|
274
|
+
## Prerequisites
|
|
275
|
+
|
|
276
|
+
1. Start your development server
|
|
277
|
+
2. Open a browser
|
|
278
|
+
3. Use a screenshot tool (Snipping Tool, Screenshot, etc.)
|
|
279
|
+
|
|
280
|
+
## Screenshots Required
|
|
281
|
+
|
|
282
|
+
${screenshots.map((ss, i) => `
|
|
283
|
+
### ${i + 1}. ${ss.id}
|
|
284
|
+
|
|
285
|
+
- **URL**: ${ss.url}
|
|
286
|
+
- **Description**: ${ss.description}
|
|
287
|
+
- **Save as**: \`screenshots/${ss.id}.png\`
|
|
288
|
+
${ss.selector ? `- **Capture area**: \`${ss.selector}\` element only` : '- **Type**: Full page or viewport'}
|
|
289
|
+
${ss.wait_for ? `- **Wait for**: \`${ss.wait_for}\` to be visible` : ''}
|
|
290
|
+
|
|
291
|
+
**Steps:**
|
|
292
|
+
1. Navigate to ${ss.url}
|
|
293
|
+
${ss.wait_for ? `2. Wait for ${ss.wait_for} to appear` : ''}
|
|
294
|
+
${ss.selector ? `2. Capture only the ${ss.selector} area` : '2. Capture the full viewport'}
|
|
295
|
+
3. Save as \`${ss.id}.png\`
|
|
296
|
+
`).join('\n')}
|
|
297
|
+
|
|
298
|
+
## After Capturing
|
|
299
|
+
|
|
300
|
+
1. Place all PNG files in the \`screenshots/\` directory
|
|
301
|
+
2. Ensure filenames match exactly (case-sensitive)
|
|
302
|
+
3. Run Phase 5 (HTML Assembly) to continue
|
|
303
|
+
|
|
304
|
+
## Screenshot Specifications
|
|
305
|
+
|
|
306
|
+
- **Format**: PNG
|
|
307
|
+
- **Width**: 1280px recommended
|
|
308
|
+
- **Quality**: High
|
|
309
|
+
- **Annotations**: None (add in post-processing if needed)
|
|
310
|
+
`;
|
|
311
|
+
|
|
312
|
+
Write(`${workDir}/screenshots/MANUAL_CAPTURE.md`, guide);
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Advanced Options
|
|
317
|
+
|
|
318
|
+
### Viewport Sizes
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
const viewportPresets = {
|
|
322
|
+
desktop: { width: 1280, height: 800 },
|
|
323
|
+
tablet: { width: 768, height: 1024 },
|
|
324
|
+
mobile: { width: 375, height: 667 },
|
|
325
|
+
wide: { width: 1920, height: 1080 }
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
async function captureResponsive(ss, config, workDir) {
|
|
329
|
+
const results = [];
|
|
330
|
+
|
|
331
|
+
for (const [name, viewport] of Object.entries(viewportPresets)) {
|
|
332
|
+
const result = await mcp__chrome__screenshot({
|
|
333
|
+
url: ss.url,
|
|
334
|
+
viewport
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const filename = `${ss.id}-${name}.png`;
|
|
338
|
+
Write(`${workDir}/screenshots/${filename}`, result.data, { encoding: 'base64' });
|
|
339
|
+
|
|
340
|
+
results.push({ viewport: name, file: filename });
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return results;
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Before/After Comparisons
|
|
348
|
+
|
|
349
|
+
```javascript
|
|
350
|
+
async function captureInteraction(ss, config, workDir) {
|
|
351
|
+
const baseUrl = config.screenshot_config.dev_url;
|
|
352
|
+
const fullUrl = new URL(ss.url, baseUrl).href;
|
|
353
|
+
|
|
354
|
+
// Capture before state
|
|
355
|
+
const before = await mcp__chrome__screenshot({
|
|
356
|
+
url: fullUrl,
|
|
357
|
+
viewport: { width: 1280, height: 800 }
|
|
358
|
+
});
|
|
359
|
+
Write(`${workDir}/screenshots/${ss.id}-before.png`, before.data, { encoding: 'base64' });
|
|
360
|
+
|
|
361
|
+
// Perform interaction (click, type, etc.)
|
|
362
|
+
if (ss.interaction) {
|
|
363
|
+
await mcp__chrome__click({ selector: ss.interaction.click });
|
|
364
|
+
await sleep(500);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Capture after state
|
|
368
|
+
const after = await mcp__chrome__screenshot({
|
|
369
|
+
url: fullUrl,
|
|
370
|
+
viewport: { width: 1280, height: 800 }
|
|
371
|
+
});
|
|
372
|
+
Write(`${workDir}/screenshots/${ss.id}-after.png`, after.data, { encoding: 'base64' });
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
before: `${ss.id}-before.png`,
|
|
376
|
+
after: `${ss.id}-after.png`
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Screenshot Annotation
|
|
382
|
+
|
|
383
|
+
```javascript
|
|
384
|
+
function generateAnnotationGuide(screenshots, workDir) {
|
|
385
|
+
const guide = `
|
|
386
|
+
# Screenshot Annotation Guide
|
|
387
|
+
|
|
388
|
+
For screenshots requiring callouts or highlights:
|
|
389
|
+
|
|
390
|
+
## Tools
|
|
391
|
+
- macOS: Preview, Skitch
|
|
392
|
+
- Windows: Snipping Tool, ShareX
|
|
393
|
+
- Cross-platform: Greenshot, Lightshot
|
|
394
|
+
|
|
395
|
+
## Annotation Guidelines
|
|
396
|
+
|
|
397
|
+
1. **Callouts**: Use numbered circles (1, 2, 3)
|
|
398
|
+
2. **Highlights**: Use semi-transparent rectangles
|
|
399
|
+
3. **Arrows**: Point from text to element
|
|
400
|
+
4. **Text**: Use sans-serif font, 12-14pt
|
|
401
|
+
|
|
402
|
+
## Color Scheme
|
|
403
|
+
|
|
404
|
+
- Primary: #0d6efd (blue)
|
|
405
|
+
- Secondary: #6c757d (gray)
|
|
406
|
+
- Success: #198754 (green)
|
|
407
|
+
- Warning: #ffc107 (yellow)
|
|
408
|
+
- Danger: #dc3545 (red)
|
|
409
|
+
|
|
410
|
+
## Screenshots Needing Annotation
|
|
411
|
+
|
|
412
|
+
${screenshots.filter(s => s.annotate).map(ss => `
|
|
413
|
+
- **${ss.id}**: ${ss.description}
|
|
414
|
+
- Highlight: ${ss.annotate.highlight || 'N/A'}
|
|
415
|
+
- Callouts: ${ss.annotate.callouts?.join(', ') || 'N/A'}
|
|
416
|
+
`).join('\n')}
|
|
417
|
+
`;
|
|
418
|
+
|
|
419
|
+
Write(`${workDir}/screenshots/ANNOTATION_GUIDE.md`, guide);
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## Troubleshooting
|
|
424
|
+
|
|
425
|
+
### Chrome MCP Not Found
|
|
426
|
+
|
|
427
|
+
1. Check Claude MCP configuration
|
|
428
|
+
2. Verify Chrome is installed
|
|
429
|
+
3. Check CHROME_PATH environment variable
|
|
430
|
+
|
|
431
|
+
### Screenshots Are Blank
|
|
432
|
+
|
|
433
|
+
1. Increase wait time before capture
|
|
434
|
+
2. Check if page requires authentication
|
|
435
|
+
3. Verify URL is correct
|
|
436
|
+
|
|
437
|
+
### Elements Not Visible
|
|
438
|
+
|
|
439
|
+
1. Scroll element into view
|
|
440
|
+
2. Expand collapsed sections
|
|
441
|
+
3. Wait for animations to complete
|
|
442
|
+
|
|
443
|
+
### Server Not Starting
|
|
444
|
+
|
|
445
|
+
1. Check if port is already in use
|
|
446
|
+
2. Verify dev command is correct
|
|
447
|
+
3. Check for startup errors in logs
|