feat: add issue discovery view for managing discovery sessions and findings

- Implemented main render function for the issue discovery view.
- Added data loading functions to fetch discoveries, details, findings, and progress.
- Created rendering functions for discovery list and detail sections.
- Introduced filtering and searching capabilities for findings.
- Implemented actions for exporting and dismissing findings.
- Added polling mechanism to track discovery progress.
- Included utility functions for HTML escaping and cleanup.
This commit is contained in:
catlog22
2025-12-28 17:21:07 +08:00
parent a2c88ba885
commit 7aa1cda367
28 changed files with 7730 additions and 2 deletions

View File

@@ -0,0 +1,177 @@
---
name: software-manual
description: Generate interactive TiddlyWiki-style HTML software manuals with screenshots, API docs, and multi-level code examples. Use when creating user guides, software documentation, or API references. Triggers on "software manual", "user guide", "generate manual", "create docs".
allowed-tools: Task, AskUserQuestion, Read, Bash, Glob, Grep, Write, mcp__chrome__*
---
# Software Manual Skill
Generate comprehensive, interactive software manuals in TiddlyWiki-style single-file HTML format.
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────────┐
│ Context-Optimized Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Phase 1: Requirements → manual-config.json │
│ ↓ │
│ Phase 2: Exploration → exploration-*.json │
│ ↓ │
│ Phase 3: Parallel Agents → sections/section-*.md │
│ ↓ (6 Agents) │
│ Phase 3.5: Consolidation → consolidation-summary.md │
│ ↓ │
│ Phase 4: Screenshot → screenshots/*.png │
│ Capture (via Chrome MCP) │
│ ↓ │
│ Phase 5: HTML Assembly → {name}-使用手册.html │
│ ↓ │
│ Phase 6: Refinement → iterations/ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
## Key Design Principles
1. **主 Agent 编排,子 Agent 执行**: 所有繁重计算委托给 `universal-executor` 子 Agent
2. **Brief Returns**: Agents return path + summary, not full content (avoid context overflow)
3. **System Agents**: 使用 `cli-explore-agent` (探索) 和 `universal-executor` (执行)
4. **Chrome MCP Integration**: Batch screenshot capture with Base64 embedding
5. **Single-File HTML**: TiddlyWiki-style interactive document with embedded resources
6. **User-Friendly Writing**: Clear, step-by-step guides with difficulty levels
## Execution Flow
```
┌─────────────────────────────────────────────────────────────────┐
│ Phase 1: Requirements Discovery (主 Agent) │
│ → AskUserQuestion: 收集软件类型、目标用户、文档范围 │
│ → Output: manual-config.json │
├─────────────────────────────────────────────────────────────────┤
│ Phase 2: Project Exploration (cli-explore-agent × N) │
│ → 并行探索: architecture, ui-routes, api-endpoints, config │
│ → Output: exploration-*.json │
├─────────────────────────────────────────────────────────────────┤
│ Phase 3: Parallel Analysis (universal-executor × 6) │
│ → 6 个子 Agent 并行: overview, ui-guide, api-docs, config, │
│ troubleshooting, code-examples │
│ → Output: sections/section-*.md │
├─────────────────────────────────────────────────────────────────┤
│ Phase 3.5: Consolidation (universal-executor) │
│ → 质量检查: 一致性、交叉引用、截图标记 │
│ → Output: consolidation-summary.md, screenshots-list.json │
├─────────────────────────────────────────────────────────────────┤
│ Phase 4: Screenshot Capture (主 Agent + Chrome MCP) │
│ → 批量截图: 根据 screenshots-list.json │
│ → Output: screenshots/*.png + manifest.json │
├─────────────────────────────────────────────────────────────────┤
│ Phase 5: HTML Assembly (universal-executor) │
│ → 组装 HTML: MD→tiddlers, 嵌入 CSS/JS/图片 │
│ → Output: {name}-使用手册.html │
├─────────────────────────────────────────────────────────────────┤
│ Phase 6: Iterative Refinement (主 Agent) │
│ → 预览 + 用户反馈 + 迭代修复 │
│ → Output: iterations/v*.html │
└─────────────────────────────────────────────────────────────────┘
```
## Agent Configuration
| Agent | Role | Output File | Focus Areas |
|-------|------|-------------|-------------|
| overview | Product Manager | section-overview.md | Product intro, features, quick start |
| ui-guide | UX Expert | section-ui-guide.md | UI operations, step-by-step guides |
| api-docs | API Architect | section-api-reference.md | REST API, Frontend API |
| config | DevOps Engineer | section-configuration.md | Env vars, deployment, settings |
| troubleshooting | Support Engineer | section-troubleshooting.md | FAQs, error codes, solutions |
| code-examples | Developer Advocate | section-examples.md | Beginner/Intermediate/Advanced examples |
## Agent Return Format
```typescript
interface ManualAgentReturn {
status: "completed" | "partial" | "failed";
output_file: string;
summary: string; // Max 50 chars
screenshots_needed: Array<{
id: string; // e.g., "ss-login-form"
url: string; // Relative or absolute URL
description: string; // "Login form interface"
selector?: string; // CSS selector for partial screenshot
wait_for?: string; // Element to wait for
}>;
cross_references: string[]; // Other sections referenced
difficulty_level: "beginner" | "intermediate" | "advanced";
}
```
## HTML Features (TiddlyWiki-style)
1. **Search**: Full-text search with result highlighting
2. **Collapse/Expand**: Per-section collapsible content
3. **Tag Navigation**: Filter by category tags
4. **Theme Toggle**: Light/Dark mode with localStorage persistence
5. **Single File**: All CSS/JS/images embedded as Base64
6. **Offline**: Works without internet connection
7. **Print-friendly**: Optimized print stylesheet
## Directory Setup
```javascript
// Generate timestamp directory name
const timestamp = new Date().toISOString().slice(0,19).replace(/[-:T]/g, '');
const dir = `.workflow/.scratchpad/manual-${timestamp}`;
// Windows
Bash(`mkdir "${dir}\\sections" && mkdir "${dir}\\screenshots" && mkdir "${dir}\\api-docs" && mkdir "${dir}\\iterations"`);
```
## Output Structure
```
.workflow/.scratchpad/manual-{timestamp}/
├── manual-config.json # Phase 1
├── exploration/ # Phase 2
│ ├── exploration-architecture.json
│ ├── exploration-ui-routes.json
│ └── exploration-api-endpoints.json
├── sections/ # Phase 3
│ ├── section-overview.md
│ ├── section-ui-guide.md
│ ├── section-api-reference.md
│ ├── section-configuration.md
│ ├── section-troubleshooting.md
│ └── section-examples.md
├── consolidation-summary.md # Phase 3.5
├── api-docs/ # API documentation
│ ├── frontend/ # TypeDoc output
│ └── backend/ # Swagger/OpenAPI output
├── screenshots/ # Phase 4
│ ├── ss-*.png
│ └── screenshots-manifest.json
├── iterations/ # Phase 6
│ ├── v1.html
│ └── v2.html
└── {软件名}-使用手册.html # Final Output
```
## Reference Documents
| Document | Purpose |
|----------|---------|
| [phases/01-requirements-discovery.md](phases/01-requirements-discovery.md) | User config collection |
| [phases/02-project-exploration.md](phases/02-project-exploration.md) | Project type detection |
| [phases/03-parallel-analysis.md](phases/03-parallel-analysis.md) | 6 Agent orchestration |
| [phases/03.5-consolidation.md](phases/03.5-consolidation.md) | Cross-section synthesis |
| [phases/04-screenshot-capture.md](phases/04-screenshot-capture.md) | Chrome MCP integration |
| [phases/05-html-assembly.md](phases/05-html-assembly.md) | HTML generation |
| [phases/06-iterative-refinement.md](phases/06-iterative-refinement.md) | Quality iteration |
| [specs/quality-standards.md](specs/quality-standards.md) | Quality gates |
| [specs/writing-style.md](specs/writing-style.md) | User-friendly writing |
| [specs/html-template.md](specs/html-template.md) | HTML template spec |
| [templates/tiddlywiki-shell.html](templates/tiddlywiki-shell.html) | HTML template |
| [scripts/typedoc-runner.md](scripts/typedoc-runner.md) | TypeDoc execution |
| [scripts/swagger-runner.md](scripts/swagger-runner.md) | Swagger/OpenAPI |
| [scripts/screenshot-helper.md](scripts/screenshot-helper.md) | Chrome MCP guide |

View File

@@ -0,0 +1,162 @@
# Phase 1: Requirements Discovery
Collect user requirements and generate configuration for the manual generation process.
## Objective
Gather essential information about the software project to customize the manual generation:
- Software type and characteristics
- Target user audience
- Documentation scope and depth
- Special requirements
## Execution Steps
### Step 1: Software Information Collection
Use `AskUserQuestion` to collect:
```javascript
AskUserQuestion({
questions: [
{
question: "What type of software is this project?",
header: "Software Type",
options: [
{ label: "Web Application", description: "Frontend + Backend web app with UI" },
{ label: "CLI Tool", description: "Command-line interface tool" },
{ label: "SDK/Library", description: "Developer library or SDK" },
{ label: "Desktop App", description: "Desktop application (Electron, etc.)" }
],
multiSelect: false
},
{
question: "Who is the target audience for this manual?",
header: "Target Users",
options: [
{ label: "End Users", description: "Non-technical users who use the product" },
{ label: "Developers", description: "Developers integrating or extending the product" },
{ label: "Administrators", description: "System admins deploying and maintaining" },
{ label: "All Audiences", description: "Mixed audience with different sections" }
],
multiSelect: false
},
{
question: "What documentation scope do you need?",
header: "Doc Scope",
options: [
{ label: "Quick Start", description: "Essential getting started guide only" },
{ label: "User Guide", description: "Complete user-facing documentation" },
{ label: "API Reference", description: "Focus on API documentation" },
{ label: "Comprehensive", description: "Full documentation including all sections" }
],
multiSelect: false
},
{
question: "What difficulty levels should code examples cover?",
header: "Example Levels",
options: [
{ label: "Beginner Only", description: "Simple, basic examples" },
{ label: "Beginner + Intermediate", description: "Basic to moderate complexity" },
{ label: "All Levels", description: "Beginner, Intermediate, and Advanced" }
],
multiSelect: false
}
]
});
```
### Step 2: Auto-Detection (Supplement)
Automatically detect project characteristics:
```javascript
// Detect from package.json
const packageJson = Read('package.json');
const softwareName = packageJson.name;
const version = packageJson.version;
const description = packageJson.description;
// Detect tech stack
const hasReact = packageJson.dependencies?.react;
const hasVue = packageJson.dependencies?.vue;
const hasExpress = packageJson.dependencies?.express;
const hasNestJS = packageJson.dependencies?.['@nestjs/core'];
// Detect CLI
const hasBin = !!packageJson.bin;
// Detect UI
const hasPages = Glob('src/pages/**/*').length > 0 || Glob('pages/**/*').length > 0;
const hasRoutes = Glob('**/routes.*').length > 0;
```
### Step 3: Generate Configuration
Create `manual-config.json`:
```json
{
"software": {
"name": "{{detected or user input}}",
"version": "{{from package.json}}",
"description": "{{from package.json}}",
"type": "{{web|cli|sdk|desktop}}"
},
"target_audience": "{{end_users|developers|admins|all}}",
"doc_scope": "{{quick_start|user_guide|api_reference|comprehensive}}",
"example_levels": ["beginner", "intermediate", "advanced"],
"tech_stack": {
"frontend": "{{react|vue|angular|vanilla}}",
"backend": "{{express|nestjs|fastify|none}}",
"language": "{{typescript|javascript}}",
"ui_framework": "{{tailwind|mui|antd|none}}"
},
"features": {
"has_ui": true,
"has_api": true,
"has_cli": false,
"has_config": true
},
"agents_to_run": [
"overview",
"ui-guide",
"api-docs",
"config",
"troubleshooting",
"code-examples"
],
"screenshot_config": {
"enabled": true,
"dev_command": "npm run dev",
"dev_url": "http://localhost:3000",
"wait_timeout": 5000
},
"output": {
"filename": "{{name}}-使用手册.html",
"theme": "light",
"language": "zh-CN"
},
"timestamp": "{{ISO8601}}"
}
```
## Agent Selection Logic
Based on `doc_scope`, select agents to run:
| Scope | Agents |
|-------|--------|
| quick_start | overview |
| user_guide | overview, ui-guide, config, troubleshooting |
| api_reference | overview, api-docs, code-examples |
| comprehensive | ALL 6 agents |
## Output
- **File**: `manual-config.json`
- **Location**: `.workflow/.scratchpad/manual-{timestamp}/`
## Next Phase
Proceed to [Phase 2: Project Exploration](02-project-exploration.md) with the generated configuration.

View File

@@ -0,0 +1,101 @@
# Phase 2: Project Exploration
使用 `cli-explore-agent` 探索项目结构,生成文档所需的结构化数据。
## 探索角度
```javascript
const EXPLORATION_ANGLES = {
web: ['architecture', 'ui-routes', 'api-endpoints', 'config'],
cli: ['architecture', 'commands', 'config'],
sdk: ['architecture', 'public-api', 'types', 'config'],
desktop: ['architecture', 'ui-screens', 'config']
};
```
## 执行流程
```javascript
const config = JSON.parse(Read(`${workDir}/manual-config.json`));
const angles = EXPLORATION_ANGLES[config.software.type];
// 并行探索
const tasks = angles.map(angle => Task({
subagent_type: 'cli-explore-agent',
run_in_background: false,
prompt: buildExplorationPrompt(angle, config, workDir)
}));
const results = await Promise.all(tasks);
```
## 探索配置
```javascript
const EXPLORATION_CONFIGS = {
architecture: {
task: '分析项目模块结构、入口点、依赖关系',
patterns: ['src/*/', 'package.json', 'tsconfig.json'],
output: 'exploration-architecture.json'
},
'ui-routes': {
task: '提取 UI 路由、页面组件、导航结构',
patterns: ['src/pages/**', 'src/views/**', 'app/**/page.*', 'src/router/**'],
output: 'exploration-ui-routes.json'
},
'api-endpoints': {
task: '提取 REST API 端点、请求/响应类型',
patterns: ['src/**/*.controller.*', 'src/routes/**', 'openapi.*', 'swagger.*'],
output: 'exploration-api-endpoints.json'
},
config: {
task: '提取环境变量、配置文件选项',
patterns: ['.env.example', 'config/**', 'docker-compose.yml'],
output: 'exploration-config.json'
},
commands: {
task: '提取 CLI 命令、选项、示例',
patterns: ['src/cli*', 'bin/*', 'src/commands/**'],
output: 'exploration-commands.json'
}
};
```
## Prompt 构建
```javascript
function buildExplorationPrompt(angle, config, workDir) {
const cfg = EXPLORATION_CONFIGS[angle];
return `
[TASK]
${cfg.task}
[SCOPE]
项目类型: ${config.software.type}
扫描模式: deep-scan
文件模式: ${cfg.patterns.join(', ')}
[OUTPUT]
文件: ${workDir}/exploration/${cfg.output}
格式: JSON (schema-compliant)
[RETURN]
简要说明发现的内容数量和关键发现
`;
}
```
## 输出结构
```
exploration/
├── exploration-architecture.json # 模块结构
├── exploration-ui-routes.json # UI 路由
├── exploration-api-endpoints.json # API 端点
├── exploration-config.json # 配置选项
└── exploration-commands.json # CLI 命令 (if CLI)
```
## 下一阶段
→ [Phase 3: Parallel Analysis](03-parallel-analysis.md)

View File

@@ -0,0 +1,130 @@
# Phase 3: Parallel Analysis
使用 `universal-executor` 并行生成 6 个文档章节。
## Agent 配置
```javascript
const AGENT_CONFIGS = {
overview: {
role: 'Product Manager',
output: 'section-overview.md',
task: '撰写产品概览、核心功能、快速入门指南',
focus: '产品定位、目标用户、5步快速入门、系统要求',
input: ['exploration-architecture.json', 'README.md', 'package.json']
},
'ui-guide': {
role: 'UX Expert',
output: 'section-ui-guide.md',
task: '撰写界面操作指南,分步骤说明各功能使用方法',
focus: '界面布局、导航流程、功能操作、快捷键',
input: ['exploration-ui-routes.json', 'pages/**', 'views/**']
},
'api-docs': {
role: 'API Architect',
output: 'section-api-reference.md',
task: '撰写 REST API 和前端 API 参考文档',
focus: 'API 概览、端点分类、请求/响应示例、错误码',
input: ['exploration-api-endpoints.json', 'controllers/**', 'routes/**']
},
config: {
role: 'DevOps Engineer',
output: 'section-configuration.md',
task: '撰写配置指南,涵盖环境变量、配置文件、部署设置',
focus: '环境变量表格、配置文件格式、部署选项、安全设置',
input: ['exploration-config.json', '.env.example', 'config/**']
},
troubleshooting: {
role: 'Support Engineer',
output: 'section-troubleshooting.md',
task: '撰写故障排查指南涵盖常见问题、错误码、FAQ',
focus: '常见问题与解决方案、错误码参考、FAQ、获取帮助',
input: ['all exploration files', 'error handling code']
},
'code-examples': {
role: 'Developer Advocate',
output: 'section-examples.md',
task: '撰写多难度级别代码示例入门40%/进阶40%/高级20%',
focus: '完整可运行代码、分步解释、预期输出、最佳实践',
input: ['all exploration files', 'examples/**', 'tests/**']
}
};
```
## 执行流程
```javascript
const config = JSON.parse(Read(`${workDir}/manual-config.json`));
// 并行启动 6 个 universal-executor
const tasks = Object.entries(AGENT_CONFIGS).map(([name, cfg]) =>
Task({
subagent_type: 'universal-executor',
run_in_background: false,
prompt: buildAgentPrompt(name, cfg, config, workDir)
})
);
const results = await Promise.all(tasks);
```
## Prompt 构建
```javascript
function buildAgentPrompt(name, cfg, config, workDir) {
return `
[ROLE] ${cfg.role}
[TASK]
${cfg.task}
输出: ${workDir}/sections/${cfg.output}
[INPUT]
- Read: ${workDir}/manual-config.json
- Read: ${cfg.input.map(f => `${workDir}/exploration/${f}`).join(', ')}
[STYLE]
- 用户友好语言,避免技术术语
- 步骤编号清晰
- 代码块标注语言
- 截图标记: <!-- SCREENSHOT: id="ss-xxx" url="/path" description="xxx" -->
[FOCUS]
${cfg.focus}
[RETURN JSON]
{
"status": "completed",
"output_file": "sections/${cfg.output}",
"summary": "<50字>",
"screenshots_needed": [],
"cross_references": []
}
`;
}
```
## 结果收集
```javascript
const agentResults = results.map(r => JSON.parse(r));
const allScreenshots = agentResults.flatMap(r => r.screenshots_needed);
Write(`${workDir}/agent-results.json`, JSON.stringify({
results: agentResults,
screenshots_needed: allScreenshots,
timestamp: new Date().toISOString()
}, null, 2));
```
## 质量检查
- [ ] Markdown 语法有效
- [ ] 无占位符文本
- [ ] 代码块标注语言
- [ ] 截图标记格式正确
- [ ] 交叉引用有效
## 下一阶段
→ [Phase 3.5: Consolidation](03.5-consolidation.md)

View File

@@ -0,0 +1,82 @@
# Phase 3.5: Consolidation
使用 `universal-executor` 子 Agent 执行质量检查,避免主 Agent 内存溢出。
## 核心原则
**主 Agent 负责编排,子 Agent 负责繁重计算。**
## 执行流程
```javascript
const agentResults = JSON.parse(Read(`${workDir}/agent-results.json`));
// 委托给 universal-executor 执行整合检查
const result = Task({
subagent_type: 'universal-executor',
run_in_background: false,
prompt: buildConsolidationPrompt(workDir)
});
const consolidationResult = JSON.parse(result);
```
## Prompt 构建
```javascript
function buildConsolidationPrompt(workDir) {
return `
[ROLE] Quality Analyst
[TASK]
检查所有章节的一致性和完整性
[INPUT]
- 章节文件: ${workDir}/sections/section-*.md
- Agent 结果: ${workDir}/agent-results.json
[CHECKS]
1. Markdown 语法有效性
2. 截图标记格式 (<!-- SCREENSHOT: id="..." -->)
3. 交叉引用有效性
4. 术语一致性
5. 代码块语言标注
[OUTPUT]
1. 写入 ${workDir}/consolidation-summary.md
2. 写入 ${workDir}/screenshots-list.json (截图清单)
[RETURN JSON]
{
"status": "completed",
"sections_checked": <n>,
"screenshots_found": <n>,
"issues": { "errors": <n>, "warnings": <n> },
"quality_score": <0-100>
}
`;
}
```
## Agent 职责
1. **读取章节** → 逐个检查 section-*.md
2. **提取截图** → 收集所有截图标记
3. **验证引用** → 检查交叉引用有效性
4. **评估质量** → 计算综合分数
5. **输出报告** → consolidation-summary.md
## 输出
- `consolidation-summary.md` - 质量报告
- `screenshots-list.json` - 截图清单(供 Phase 4 使用)
## 质量门禁
- [ ] 无错误
- [ ] 总分 >= 60%
- [ ] 交叉引用有效
## 下一阶段
→ [Phase 4: Screenshot Capture](04-screenshot-capture.md)

View File

@@ -0,0 +1,271 @@
# Phase 4: Screenshot Capture
Capture screenshots using Chrome MCP for all identified UI elements.
## Objective
- Check Chrome MCP availability
- Start development server
- Capture all required screenshots
- Convert to Base64 for embedding
## Prerequisites
- Chrome MCP configured and available
- Development server can be started
- All screenshot URLs accessible
## Execution Steps
### Step 1: Load Screenshot List
```javascript
const consolidation = Read(`${workDir}/consolidation-summary.md`);
const config = JSON.parse(Read(`${workDir}/manual-config.json`));
// Parse screenshot table from consolidation
const screenshots = parseScreenshotTable(consolidation);
```
### Step 2: Check Chrome MCP Availability
```javascript
async function checkChromeMCP() {
try {
// Attempt to call Chrome MCP
const version = await mcp__chrome__getVersion();
return {
available: true,
version: version.version,
browser: version.browser
};
} catch (error) {
return {
available: false,
error: error.message
};
}
}
const chromeMCP = await checkChromeMCP();
if (!chromeMCP.available) {
// Fallback: generate manual screenshot instructions
generateManualScreenshotGuide(screenshots);
return {
status: 'skipped',
reason: 'Chrome MCP not available',
manual_guide: `${workDir}/screenshots/MANUAL_CAPTURE.md`
};
}
```
### Step 3: Start Development Server
```javascript
const devConfig = config.screenshot_config;
// Start dev server in background
const serverTask = Bash({
command: devConfig.dev_command,
run_in_background: true
});
// Wait for server to be ready
async function waitForServer(url, timeout = 30000) {
const start = Date.now();
while (Date.now() - start < timeout) {
try {
const response = await fetch(url);
if (response.ok) return true;
} catch (e) {
// Server not ready yet
}
await sleep(1000);
}
throw new Error(`Server at ${url} did not start within ${timeout}ms`);
}
await waitForServer(devConfig.dev_url, devConfig.wait_timeout);
```
### Step 4: Batch Screenshot Capture
```javascript
const capturedScreenshots = [];
const failedScreenshots = [];
for (const ss of screenshots) {
try {
const fullUrl = new URL(ss.url, devConfig.dev_url).href;
// Configure capture options
const captureOptions = {
url: fullUrl,
viewport: { width: 1280, height: 800 },
fullPage: ss.fullPage || false,
waitFor: ss.wait_for || null,
delay: 500 // Wait for animations
};
// Add selector for partial screenshot
if (ss.selector) {
captureOptions.selector = ss.selector;
}
// Capture screenshot
const result = await mcp__chrome__screenshot(captureOptions);
// Save screenshot
const filename = `${ss.id}.png`;
Write(`${workDir}/screenshots/${filename}`, result.data, { encoding: 'base64' });
capturedScreenshots.push({
...ss,
file: filename,
base64_size: result.data.length,
captured_at: new Date().toISOString()
});
} catch (error) {
failedScreenshots.push({
...ss,
error: error.message
});
}
}
```
### Step 5: Generate Screenshot Manifest
```javascript
const manifest = {
total: screenshots.length,
captured: capturedScreenshots.length,
failed: failedScreenshots.length,
screenshots: capturedScreenshots,
failures: failedScreenshots,
dev_server: {
url: devConfig.dev_url,
command: devConfig.dev_command
},
capture_config: {
viewport: { width: 1280, height: 800 },
format: 'png'
},
timestamp: new Date().toISOString()
};
Write(`${workDir}/screenshots/screenshots-manifest.json`, JSON.stringify(manifest, null, 2));
```
### Step 6: Stop Development Server
```javascript
// Kill the dev server process
KillShell({ shell_id: serverTask.task_id });
```
### Step 7: Handle Failures
If any screenshots failed:
```javascript
if (failedScreenshots.length > 0) {
// Generate manual capture instructions
const manualGuide = `
# Manual Screenshot Capture Required
The following screenshots could not be captured automatically:
${failedScreenshots.map(s => `
## ${s.id}
- **URL**: ${s.url}
- **Description**: ${s.description}
- **Error**: ${s.error}
**Instructions**:
1. Navigate to ${s.url}
2. Capture screenshot of ${s.selector || 'full page'}
3. Save as \`${s.id}.png\`
4. Place in \`screenshots/\` directory
`).join('\n')}
`;
Write(`${workDir}/screenshots/MANUAL_CAPTURE.md`, manualGuide);
}
```
## Fallback: Manual Screenshot Mode
When Chrome MCP is not available:
```javascript
function generateManualScreenshotGuide(screenshots) {
const guide = `
# Manual Screenshot Capture Guide
Chrome MCP is not available. Please capture screenshots manually.
## Setup
1. Start your development server:
\`\`\`bash
${config.screenshot_config.dev_command}
\`\`\`
2. Open browser to: ${config.screenshot_config.dev_url}
## Screenshots Required
${screenshots.map((s, i) => `
### ${i + 1}. ${s.id}
- **URL**: ${s.url}
- **Description**: ${s.description}
- **Save as**: \`screenshots/${s.id}.png\`
${s.selector ? `- **Element**: Capture only \`${s.selector}\`` : '- **Type**: Full page'}
`).join('\n')}
## After Capturing
Place all PNG files in the \`screenshots/\` directory, then run Phase 5 to continue.
`;
Write(`${workDir}/screenshots/MANUAL_CAPTURE.md`, guide);
}
```
## Chrome MCP Configuration Reference
Expected MCP configuration:
```json
{
"mcpServers": {
"chrome": {
"command": "npx",
"args": ["@anthropic-ai/mcp-chrome"],
"env": {
"CHROME_PATH": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
}
}
}
}
```
## Output
- **Files**: `screenshots/*.png`
- **Manifest**: `screenshots/screenshots-manifest.json`
- **Fallback**: `screenshots/MANUAL_CAPTURE.md` (if needed)
## Quality Checks
- [ ] All high-priority screenshots captured
- [ ] Screenshot dimensions consistent (1280x800)
- [ ] No broken/blank screenshots
- [ ] Manifest file complete
## Next Phase
Proceed to [Phase 5: HTML Assembly](05-html-assembly.md) with captured screenshots.

View File

@@ -0,0 +1,120 @@
# Phase 5: HTML Assembly
使用 `universal-executor` 子 Agent 生成最终 HTML避免主 Agent 内存溢出。
## 核心原则
**主 Agent 负责编排,子 Agent 负责繁重计算。**
## 执行流程
```javascript
const config = JSON.parse(Read(`${workDir}/manual-config.json`));
// 委托给 universal-executor 执行 HTML 组装
const result = Task({
subagent_type: 'universal-executor',
run_in_background: false,
prompt: buildAssemblyPrompt(config, workDir)
});
const buildResult = JSON.parse(result);
```
## Prompt 构建
```javascript
function buildAssemblyPrompt(config, workDir) {
return `
[ROLE] HTML Assembler
[TASK]
生成 TiddlyWiki 风格的交互式 HTML 手册
[INPUT]
- 模板: .claude/skills/software-manual/templates/tiddlywiki-shell.html
- CSS: .claude/skills/software-manual/templates/css/wiki-base.css, wiki-dark.css
- 配置: ${workDir}/manual-config.json
- 章节: ${workDir}/sections/section-*.md
- 截图: ${workDir}/screenshots/
[STEPS]
1. 读取 HTML 模板和 CSS
2. 逐个读取 section-*.md转换为 HTML tiddlers
3. 处理 <!-- SCREENSHOT: id="..." --> 标记,嵌入 Base64 图片
4. 生成目录、搜索索引
5. 组装最终 HTML写入 ${workDir}/${config.software.name}-使用手册.html
6. 生成构建报告 ${workDir}/build-report.json
[HTML FEATURES]
- 搜索: 全文检索 + 高亮
- 折叠: 章节可展开/收起
- 标签: 分类过滤
- 主题: 亮/暗模式切换
- 离线: 所有资源内嵌
[RETURN JSON]
{
"status": "completed",
"output_file": "${config.software.name}-使用手册.html",
"file_size": "<size>",
"sections_count": <n>,
"screenshots_embedded": <n>
}
`;
}
```
## Agent 职责
1. **读取模板** → HTML + CSS
2. **转换章节** → Markdown → HTML tiddlers
3. **嵌入截图** → Base64 编码
4. **生成索引** → 搜索数据
5. **组装输出** → 单文件 HTML
## Markdown 转换规则
Agent 内部实现:
```
# H1 → <h1>
## H2 → <h2>
### H3 → <h3>
```code``` → <pre><code>
**bold** → <strong>
*italic* → <em>
[text](url) → <a href>
- item → <li>
<!-- SCREENSHOT: id="xxx" --> → <figure><img src="data:..."></figure>
```
## Tiddler 结构
```html
<article class="tiddler" id="tiddler-{name}" data-tags="..." data-difficulty="...">
<header class="tiddler-header">
<h2><button class="collapse-toggle"></button> {title}</h2>
<div class="tiddler-meta">{badges}</div>
</header>
<div class="tiddler-content">{html}</div>
</article>
```
## 输出
- `{软件名}-使用手册.html` - 最终 HTML
- `build-report.json` - 构建报告
## 质量门禁
- [ ] HTML 渲染正确
- [ ] 搜索功能可用
- [ ] 折叠/展开正常
- [ ] 主题切换持久化
- [ ] 截图显示正确
- [ ] 文件大小 < 10MB
## 下一阶段
→ [Phase 6: Iterative Refinement](06-iterative-refinement.md)

View File

@@ -0,0 +1,259 @@
# Phase 6: Iterative Refinement
Preview, collect feedback, and iterate until quality meets standards.
## Objective
- Preview generated HTML in browser
- Collect user feedback
- Address issues iteratively
- Finalize documentation
## Execution Steps
### Step 1: Preview HTML
```javascript
const buildReport = JSON.parse(Read(`${workDir}/build-report.json`));
const outputFile = `${workDir}/${buildReport.output}`;
// Open in default browser for preview
Bash({ command: `start "${outputFile}"` }); // Windows
// Bash({ command: `open "${outputFile}"` }); // macOS
// Report to user
console.log(`
📖 Manual Preview
File: ${buildReport.output}
Size: ${buildReport.size_human}
Sections: ${buildReport.sections}
Screenshots: ${buildReport.screenshots}
Please review the manual in your browser.
`);
```
### Step 2: Collect Feedback
```javascript
const feedback = await AskUserQuestion({
questions: [
{
question: "How does the manual look overall?",
header: "Overall",
options: [
{ label: "Looks great!", description: "Ready to finalize" },
{ label: "Minor issues", description: "Small tweaks needed" },
{ label: "Major issues", description: "Significant changes required" },
{ label: "Missing content", description: "Need to add more sections" }
],
multiSelect: false
},
{
question: "Which aspects need improvement? (Select all that apply)",
header: "Improvements",
options: [
{ label: "Content accuracy", description: "Fix incorrect information" },
{ label: "More examples", description: "Add more code examples" },
{ label: "Better screenshots", description: "Retake or add screenshots" },
{ label: "Styling/Layout", description: "Improve visual appearance" }
],
multiSelect: true
}
]
});
```
### Step 3: Address Feedback
Based on feedback, take appropriate action:
#### Minor Issues
```javascript
if (feedback.overall === "Minor issues") {
// Prompt for specific changes
const details = await AskUserQuestion({
questions: [{
question: "What specific changes are needed?",
header: "Details",
options: [
{ label: "Typo fixes", description: "Fix spelling/grammar" },
{ label: "Reorder sections", description: "Change section order" },
{ label: "Update content", description: "Modify existing text" },
{ label: "Custom changes", description: "I'll describe the changes" }
],
multiSelect: true
}]
});
// Apply changes based on user input
applyMinorChanges(details);
}
```
#### Major Issues
```javascript
if (feedback.overall === "Major issues") {
// Return to relevant phase
console.log(`
Major issues require returning to an earlier phase:
- Content issues → Phase 3 (Parallel Analysis)
- Screenshot issues → Phase 4 (Screenshot Capture)
- Structure issues → Phase 2 (Project Exploration)
Which phase should we return to?
`);
const phase = await selectPhase();
return { action: 'restart', from_phase: phase };
}
```
#### Missing Content
```javascript
if (feedback.overall === "Missing content") {
// Identify missing sections
const missing = await AskUserQuestion({
questions: [{
question: "What content is missing?",
header: "Missing",
options: [
{ label: "API endpoints", description: "More API documentation" },
{ label: "UI features", description: "Additional UI guides" },
{ label: "Examples", description: "More code examples" },
{ label: "Troubleshooting", description: "More FAQ items" }
],
multiSelect: true
}]
});
// Run additional agent(s) for missing content
await runSupplementaryAgents(missing);
}
```
### Step 4: Save Iteration
```javascript
// Save current version before changes
const iterationNum = getNextIterationNumber(workDir);
const iterationDir = `${workDir}/iterations`;
// Copy current version
Bash({ command: `copy "${outputFile}" "${iterationDir}\\v${iterationNum}.html"` });
// Log iteration
const iterationLog = {
version: iterationNum,
timestamp: new Date().toISOString(),
feedback: feedback,
changes: appliedChanges
};
Write(`${iterationDir}/iteration-${iterationNum}.json`, JSON.stringify(iterationLog, null, 2));
```
### Step 5: Regenerate if Needed
```javascript
if (changesApplied) {
// Re-run HTML assembly with updated sections
await runPhase('05-html-assembly');
// Open updated preview
Bash({ command: `start "${outputFile}"` });
}
```
### Step 6: Finalize
When user approves:
```javascript
if (feedback.overall === "Looks great!") {
// Final quality check
const finalReport = {
...buildReport,
iterations: iterationNum,
finalized_at: new Date().toISOString(),
quality_score: calculateFinalQuality()
};
Write(`${workDir}/final-report.json`, JSON.stringify(finalReport, null, 2));
// Suggest final location
console.log(`
✅ Manual Finalized!
Output: ${buildReport.output}
Size: ${buildReport.size_human}
Quality: ${finalReport.quality_score}%
Iterations: ${iterationNum}
Suggested actions:
1. Copy to project root: copy "${outputFile}" "docs/"
2. Add to version control
3. Publish to documentation site
`);
return { status: 'completed', output: outputFile };
}
```
## Iteration History
Each iteration is logged:
```
iterations/
├── v1.html # First version
├── iteration-1.json # Feedback and changes
├── v2.html # After first iteration
├── iteration-2.json # Feedback and changes
└── ...
```
## Quality Metrics
Track improvement across iterations:
```javascript
const qualityMetrics = {
content_completeness: 0, // All sections present
screenshot_coverage: 0, // Screenshots for all UI
example_diversity: 0, // Different difficulty levels
search_accuracy: 0, // Search returns relevant results
user_satisfaction: 0 // Based on feedback
};
```
## Exit Conditions
The refinement phase ends when:
1. User explicitly approves ("Looks great!")
2. Maximum iterations reached (configurable, default: 5)
3. Quality score exceeds threshold (default: 90%)
## Output
- **Final HTML**: `{软件名}-使用手册.html`
- **Final Report**: `final-report.json`
- **Iteration History**: `iterations/`
## Completion
When finalized, the skill is complete. Final output location:
```
.workflow/.scratchpad/manual-{timestamp}/
├── {软件名}-使用手册.html ← Final deliverable
├── final-report.json
└── iterations/
```
Consider copying to a permanent location like `docs/` or project root.

View File

@@ -0,0 +1,447 @@
# Screenshot Helper
Guide for capturing screenshots using Chrome MCP.
## Overview
This script helps capture screenshots of web interfaces for the software manual using Chrome MCP or fallback methods.
## Chrome MCP Prerequisites
### Check MCP Availability
```javascript
async function checkChromeMCPAvailability() {
try {
// Attempt to get Chrome version via MCP
const version = await mcp__chrome__getVersion();
return {
available: true,
browser: version.browser,
version: version.version
};
} catch (error) {
return {
available: false,
error: error.message
};
}
}
```
### MCP Configuration
Expected Claude configuration for Chrome MCP:
```json
{
"mcpServers": {
"chrome": {
"command": "npx",
"args": ["@anthropic-ai/mcp-chrome"],
"env": {
"CHROME_PATH": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
}
}
}
}
```
## Screenshot Workflow
### Step 1: Prepare Environment
```javascript
async function prepareScreenshotEnvironment(workDir, config) {
const screenshotDir = `${workDir}/screenshots`;
// Create directory
Bash({ command: `mkdir -p "${screenshotDir}"` });
// Check Chrome MCP
const chromeMCP = await checkChromeMCPAvailability();
if (!chromeMCP.available) {
console.log('Chrome MCP not available. Will generate manual guide.');
return { mode: 'manual' };
}
// Start development server if needed
if (config.screenshot_config?.dev_command) {
const server = await startDevServer(config);
return { mode: 'auto', server, screenshotDir };
}
return { mode: 'auto', screenshotDir };
}
```
### Step 2: Start Development Server
```javascript
async function startDevServer(config) {
const devCommand = config.screenshot_config.dev_command;
const devUrl = config.screenshot_config.dev_url;
// Start server in background
const server = Bash({
command: devCommand,
run_in_background: true
});
console.log(`Starting dev server: ${devCommand}`);
// Wait for server to be ready
const ready = await waitForServer(devUrl, 30000);
if (!ready) {
throw new Error(`Server at ${devUrl} did not start in time`);
}
console.log(`Dev server ready at ${devUrl}`);
return server;
}
async function waitForServer(url, timeout = 30000) {
const start = Date.now();
while (Date.now() - start < timeout) {
try {
const response = await fetch(url, { method: 'HEAD' });
if (response.ok) return true;
} catch (e) {
// Server not ready
}
await sleep(1000);
}
return false;
}
```
### Step 3: Capture Screenshots
```javascript
async function captureScreenshots(screenshots, config, workDir) {
const results = {
captured: [],
failed: []
};
const devUrl = config.screenshot_config.dev_url;
const screenshotDir = `${workDir}/screenshots`;
for (const ss of screenshots) {
try {
// Build full URL
const fullUrl = new URL(ss.url, devUrl).href;
console.log(`Capturing: ${ss.id} (${fullUrl})`);
// Configure capture options
const options = {
url: fullUrl,
viewport: { width: 1280, height: 800 },
fullPage: ss.fullPage || false
};
// Wait for specific element if specified
if (ss.wait_for) {
options.waitFor = ss.wait_for;
}
// Capture specific element if selector provided
if (ss.selector) {
options.selector = ss.selector;
}
// Add delay for animations
await sleep(500);
// Capture via Chrome MCP
const result = await mcp__chrome__screenshot(options);
// Save as PNG
const filename = `${ss.id}.png`;
Write(`${screenshotDir}/${filename}`, result.data, { encoding: 'base64' });
results.captured.push({
id: ss.id,
file: filename,
url: ss.url,
description: ss.description,
size: result.data.length
});
} catch (error) {
console.error(`Failed to capture ${ss.id}:`, error.message);
results.failed.push({
id: ss.id,
url: ss.url,
error: error.message
});
}
}
return results;
}
```
### Step 4: Generate Manifest
```javascript
function generateScreenshotManifest(results, workDir) {
const manifest = {
generated: new Date().toISOString(),
total: results.captured.length + results.failed.length,
captured: results.captured.length,
failed: results.failed.length,
screenshots: results.captured,
failures: results.failed
};
Write(`${workDir}/screenshots/screenshots-manifest.json`,
JSON.stringify(manifest, null, 2));
return manifest;
}
```
### Step 5: Cleanup
```javascript
async function cleanupScreenshotEnvironment(env) {
if (env.server) {
console.log('Stopping dev server...');
KillShell({ shell_id: env.server.task_id });
}
}
```
## Main Runner
```javascript
async function runScreenshotCapture(workDir, screenshots) {
const config = JSON.parse(Read(`${workDir}/manual-config.json`));
// Prepare environment
const env = await prepareScreenshotEnvironment(workDir, config);
if (env.mode === 'manual') {
// Generate manual capture guide
generateManualCaptureGuide(screenshots, workDir);
return { success: false, mode: 'manual' };
}
try {
// Capture screenshots
const results = await captureScreenshots(screenshots, config, workDir);
// Generate manifest
const manifest = generateScreenshotManifest(results, workDir);
// Generate manual guide for failed captures
if (results.failed.length > 0) {
generateManualCaptureGuide(results.failed, workDir);
}
return {
success: true,
captured: results.captured.length,
failed: results.failed.length,
manifest
};
} finally {
// Cleanup
await cleanupScreenshotEnvironment(env);
}
}
```
## Manual Capture Fallback
When Chrome MCP is unavailable:
```javascript
function generateManualCaptureGuide(screenshots, workDir) {
const guide = `
# Manual Screenshot Capture Guide
Chrome MCP is not available. Please capture screenshots manually.
## Prerequisites
1. Start your development server
2. Open a browser
3. Use a screenshot tool (Snipping Tool, Screenshot, etc.)
## Screenshots Required
${screenshots.map((ss, i) => `
### ${i + 1}. ${ss.id}
- **URL**: ${ss.url}
- **Description**: ${ss.description}
- **Save as**: \`screenshots/${ss.id}.png\`
${ss.selector ? `- **Capture area**: \`${ss.selector}\` element only` : '- **Type**: Full page or viewport'}
${ss.wait_for ? `- **Wait for**: \`${ss.wait_for}\` to be visible` : ''}
**Steps:**
1. Navigate to ${ss.url}
${ss.wait_for ? `2. Wait for ${ss.wait_for} to appear` : ''}
${ss.selector ? `2. Capture only the ${ss.selector} area` : '2. Capture the full viewport'}
3. Save as \`${ss.id}.png\`
`).join('\n')}
## After Capturing
1. Place all PNG files in the \`screenshots/\` directory
2. Ensure filenames match exactly (case-sensitive)
3. Run Phase 5 (HTML Assembly) to continue
## Screenshot Specifications
- **Format**: PNG
- **Width**: 1280px recommended
- **Quality**: High
- **Annotations**: None (add in post-processing if needed)
`;
Write(`${workDir}/screenshots/MANUAL_CAPTURE.md`, guide);
}
```
## Advanced Options
### Viewport Sizes
```javascript
const viewportPresets = {
desktop: { width: 1280, height: 800 },
tablet: { width: 768, height: 1024 },
mobile: { width: 375, height: 667 },
wide: { width: 1920, height: 1080 }
};
async function captureResponsive(ss, config, workDir) {
const results = [];
for (const [name, viewport] of Object.entries(viewportPresets)) {
const result = await mcp__chrome__screenshot({
url: ss.url,
viewport
});
const filename = `${ss.id}-${name}.png`;
Write(`${workDir}/screenshots/${filename}`, result.data, { encoding: 'base64' });
results.push({ viewport: name, file: filename });
}
return results;
}
```
### Before/After Comparisons
```javascript
async function captureInteraction(ss, config, workDir) {
const baseUrl = config.screenshot_config.dev_url;
const fullUrl = new URL(ss.url, baseUrl).href;
// Capture before state
const before = await mcp__chrome__screenshot({
url: fullUrl,
viewport: { width: 1280, height: 800 }
});
Write(`${workDir}/screenshots/${ss.id}-before.png`, before.data, { encoding: 'base64' });
// Perform interaction (click, type, etc.)
if (ss.interaction) {
await mcp__chrome__click({ selector: ss.interaction.click });
await sleep(500);
}
// Capture after state
const after = await mcp__chrome__screenshot({
url: fullUrl,
viewport: { width: 1280, height: 800 }
});
Write(`${workDir}/screenshots/${ss.id}-after.png`, after.data, { encoding: 'base64' });
return {
before: `${ss.id}-before.png`,
after: `${ss.id}-after.png`
};
}
```
### Screenshot Annotation
```javascript
function generateAnnotationGuide(screenshots, workDir) {
const guide = `
# Screenshot Annotation Guide
For screenshots requiring callouts or highlights:
## Tools
- macOS: Preview, Skitch
- Windows: Snipping Tool, ShareX
- Cross-platform: Greenshot, Lightshot
## Annotation Guidelines
1. **Callouts**: Use numbered circles (1, 2, 3)
2. **Highlights**: Use semi-transparent rectangles
3. **Arrows**: Point from text to element
4. **Text**: Use sans-serif font, 12-14pt
## Color Scheme
- Primary: #0d6efd (blue)
- Secondary: #6c757d (gray)
- Success: #198754 (green)
- Warning: #ffc107 (yellow)
- Danger: #dc3545 (red)
## Screenshots Needing Annotation
${screenshots.filter(s => s.annotate).map(ss => `
- **${ss.id}**: ${ss.description}
- Highlight: ${ss.annotate.highlight || 'N/A'}
- Callouts: ${ss.annotate.callouts?.join(', ') || 'N/A'}
`).join('\n')}
`;
Write(`${workDir}/screenshots/ANNOTATION_GUIDE.md`, guide);
}
```
## Troubleshooting
### Chrome MCP Not Found
1. Check Claude MCP configuration
2. Verify Chrome is installed
3. Check CHROME_PATH environment variable
### Screenshots Are Blank
1. Increase wait time before capture
2. Check if page requires authentication
3. Verify URL is correct
### Elements Not Visible
1. Scroll element into view
2. Expand collapsed sections
3. Wait for animations to complete
### Server Not Starting
1. Check if port is already in use
2. Verify dev command is correct
3. Check for startup errors in logs

View File

@@ -0,0 +1,419 @@
# Swagger/OpenAPI Runner
Guide for generating backend API documentation from OpenAPI/Swagger specifications.
## Overview
This script extracts and converts OpenAPI/Swagger specifications to Markdown format for inclusion in the software manual.
## Detection Strategy
### Check for Existing Specification
```javascript
async function detectOpenAPISpec() {
// Check for existing spec files
const specPatterns = [
'openapi.json',
'openapi.yaml',
'openapi.yml',
'swagger.json',
'swagger.yaml',
'swagger.yml',
'**/openapi*.json',
'**/swagger*.json'
];
for (const pattern of specPatterns) {
const files = Glob(pattern);
if (files.length > 0) {
return { found: true, type: 'file', path: files[0] };
}
}
// Check for swagger-jsdoc in dependencies
const packageJson = JSON.parse(Read('package.json'));
if (packageJson.dependencies?.['swagger-jsdoc'] ||
packageJson.devDependencies?.['swagger-jsdoc']) {
return { found: true, type: 'jsdoc' };
}
// Check for NestJS Swagger
if (packageJson.dependencies?.['@nestjs/swagger']) {
return { found: true, type: 'nestjs' };
}
// Check for runtime endpoint
return { found: false, suggestion: 'runtime' };
}
```
## Extraction Methods
### Method A: From Existing Spec File
```javascript
async function extractFromFile(specPath, workDir) {
const outputDir = `${workDir}/api-docs/backend`;
Bash({ command: `mkdir -p "${outputDir}"` });
// Copy spec to output
Bash({ command: `cp "${specPath}" "${outputDir}/openapi.json"` });
// Convert to Markdown using widdershins
const result = Bash({
command: `npx widdershins "${specPath}" -o "${outputDir}/api-reference.md" --language_tabs 'javascript:JavaScript' 'python:Python' 'bash:cURL'`,
timeout: 60000
});
return { success: result.exitCode === 0, outputDir };
}
```
### Method B: From swagger-jsdoc
```javascript
async function extractFromJsDoc(workDir) {
const outputDir = `${workDir}/api-docs/backend`;
// Look for swagger definition file
const defFiles = Glob('**/swagger*.js').concat(Glob('**/openapi*.js'));
if (defFiles.length === 0) {
return { success: false, error: 'No swagger definition found' };
}
// Generate spec
const result = Bash({
command: `npx swagger-jsdoc -d "${defFiles[0]}" -o "${outputDir}/openapi.json"`,
timeout: 60000
});
if (result.exitCode !== 0) {
return { success: false, error: result.stderr };
}
// Convert to Markdown
Bash({
command: `npx widdershins "${outputDir}/openapi.json" -o "${outputDir}/api-reference.md" --language_tabs 'javascript:JavaScript' 'bash:cURL'`
});
return { success: true, outputDir };
}
```
### Method C: From NestJS Swagger
```javascript
async function extractFromNestJS(workDir) {
const outputDir = `${workDir}/api-docs/backend`;
// NestJS typically exposes /api-docs-json at runtime
// We need to start the server temporarily
// Start server in background
const server = Bash({
command: 'npm run start:dev',
run_in_background: true,
timeout: 30000
});
// Wait for server to be ready
await waitForServer('http://localhost:3000', 30000);
// Fetch OpenAPI spec
const spec = await fetch('http://localhost:3000/api-docs-json');
const specJson = await spec.json();
// Save spec
Write(`${outputDir}/openapi.json`, JSON.stringify(specJson, null, 2));
// Stop server
KillShell({ shell_id: server.task_id });
// Convert to Markdown
Bash({
command: `npx widdershins "${outputDir}/openapi.json" -o "${outputDir}/api-reference.md" --language_tabs 'javascript:JavaScript' 'bash:cURL'`
});
return { success: true, outputDir };
}
```
### Method D: From Runtime Endpoint
```javascript
async function extractFromRuntime(workDir, serverUrl = 'http://localhost:3000') {
const outputDir = `${workDir}/api-docs/backend`;
// Common OpenAPI endpoint paths
const endpointPaths = [
'/api-docs-json',
'/swagger.json',
'/openapi.json',
'/docs/json',
'/api/v1/docs.json'
];
let specJson = null;
for (const path of endpointPaths) {
try {
const response = await fetch(`${serverUrl}${path}`);
if (response.ok) {
specJson = await response.json();
break;
}
} catch (e) {
continue;
}
}
if (!specJson) {
return { success: false, error: 'Could not fetch OpenAPI spec from server' };
}
// Save and convert
Write(`${outputDir}/openapi.json`, JSON.stringify(specJson, null, 2));
Bash({
command: `npx widdershins "${outputDir}/openapi.json" -o "${outputDir}/api-reference.md"`
});
return { success: true, outputDir };
}
```
## Installation
### Required Tools
```bash
# For OpenAPI to Markdown conversion
npm install -g widdershins
# Or as dev dependency
npm install --save-dev widdershins
# For generating from JSDoc comments
npm install --save-dev swagger-jsdoc
```
## Configuration
### widdershins Options
```bash
npx widdershins openapi.json \
-o api-reference.md \
--language_tabs 'javascript:JavaScript' 'python:Python' 'bash:cURL' \
--summary \
--omitHeader \
--resolve \
--expandBody
```
| Option | Description |
|--------|-------------|
| `--language_tabs` | Code example languages |
| `--summary` | Use summary as operation heading |
| `--omitHeader` | Don't include title header |
| `--resolve` | Resolve $ref references |
| `--expandBody` | Show full request body |
### swagger-jsdoc Definition
Example `swagger-def.js`:
```javascript
module.exports = {
definition: {
openapi: '3.0.0',
info: {
title: 'MyApp API',
version: '1.0.0',
description: 'API documentation for MyApp'
},
servers: [
{ url: 'http://localhost:3000/api/v1' }
]
},
apis: ['./src/routes/*.js', './src/controllers/*.js']
};
```
## Output Format
### Generated Markdown Structure
```markdown
# MyApp API
## Overview
Base URL: `http://localhost:3000/api/v1`
## Authentication
This API uses Bearer token authentication.
---
## Projects
### List Projects
`GET /projects`
Returns a list of all projects.
**Parameters**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| status | query | string | false | Filter by status |
| page | query | integer | false | Page number |
**Responses**
| Status | Description |
|--------|-------------|
| 200 | Successful response |
| 401 | Unauthorized |
**Example Request**
```javascript
fetch('/api/v1/projects?status=active')
.then(res => res.json())
.then(data => console.log(data));
```
**Example Response**
```json
{
"data": [
{ "id": "1", "name": "Project 1" }
],
"pagination": {
"page": 1,
"total": 10
}
}
```
```
## Integration
### Main Runner
```javascript
async function runSwaggerExtraction(workDir) {
const detection = await detectOpenAPISpec();
if (!detection.found) {
console.log('No OpenAPI spec detected. Skipping backend API docs.');
return { success: false, skipped: true };
}
let result;
switch (detection.type) {
case 'file':
result = await extractFromFile(detection.path, workDir);
break;
case 'jsdoc':
result = await extractFromJsDoc(workDir);
break;
case 'nestjs':
result = await extractFromNestJS(workDir);
break;
default:
result = await extractFromRuntime(workDir);
}
if (result.success) {
// Post-process the Markdown
await postProcessApiDocs(result.outputDir);
}
return result;
}
async function postProcessApiDocs(outputDir) {
const mdFile = `${outputDir}/api-reference.md`;
let content = Read(mdFile);
// Remove widdershins header
content = content.replace(/^---[\s\S]*?---\n/, '');
// Add custom styling hints
content = content.replace(/^(#{1,3} .+)$/gm, '$1\n');
Write(mdFile, content);
}
```
## Troubleshooting
### Common Issues
#### "widdershins: command not found"
```bash
npm install -g widdershins
# Or use npx
npx widdershins openapi.json -o api.md
```
#### "Error parsing OpenAPI spec"
```bash
# Validate spec first
npx @redocly/cli lint openapi.json
# Fix common issues
npx @redocly/cli bundle openapi.json -o fixed.json
```
#### "Server not responding"
Ensure the development server is running and accessible:
```bash
# Check if server is running
curl http://localhost:3000/health
# Check OpenAPI endpoint
curl http://localhost:3000/api-docs-json
```
### Manual Fallback
If automatic extraction fails, document APIs manually:
1. List all route files: `Glob('**/routes/*.js')`
2. Extract route definitions using regex
3. Build documentation structure manually
```javascript
async function manualApiExtraction(workDir) {
const routeFiles = Glob('src/routes/*.js').concat(Glob('src/routes/*.ts'));
const endpoints = [];
for (const file of routeFiles) {
const content = Read(file);
const routes = content.matchAll(/router\.(get|post|put|delete|patch)\(['"]([^'"]+)['"]/g);
for (const match of routes) {
endpoints.push({
method: match[1].toUpperCase(),
path: match[2],
file: file
});
}
}
return endpoints;
}
```

View File

@@ -0,0 +1,357 @@
# TypeDoc Runner
Guide for generating frontend API documentation using TypeDoc.
## Overview
TypeDoc generates API documentation from TypeScript source code by analyzing type annotations and JSDoc comments.
## Prerequisites
### Check TypeScript Project
```javascript
// Verify TypeScript is used
const packageJson = JSON.parse(Read('package.json'));
const hasTypeScript = packageJson.devDependencies?.typescript ||
packageJson.dependencies?.typescript;
if (!hasTypeScript) {
console.log('Not a TypeScript project. Skipping TypeDoc.');
return;
}
// Check for tsconfig.json
const hasTsConfig = Glob('tsconfig.json').length > 0;
```
## Installation
### Install TypeDoc
```bash
npm install --save-dev typedoc typedoc-plugin-markdown
```
### Optional Plugins
```bash
# For better Markdown output
npm install --save-dev typedoc-plugin-markdown
# For README inclusion
npm install --save-dev typedoc-plugin-rename-defaults
```
## Configuration
### typedoc.json
Create `typedoc.json` in project root:
```json
{
"entryPoints": ["./src/index.ts"],
"entryPointStrategy": "expand",
"out": ".workflow/.scratchpad/manual-{timestamp}/api-docs/frontend",
"plugin": ["typedoc-plugin-markdown"],
"exclude": [
"**/node_modules/**",
"**/*.test.ts",
"**/*.spec.ts",
"**/tests/**"
],
"excludePrivate": true,
"excludeProtected": true,
"excludeInternal": true,
"hideGenerator": true,
"readme": "none",
"categorizeByGroup": true,
"navigation": {
"includeCategories": true,
"includeGroups": true
}
}
```
### Alternative: CLI Options
```bash
npx typedoc \
--entryPoints src/index.ts \
--entryPointStrategy expand \
--out api-docs/frontend \
--plugin typedoc-plugin-markdown \
--exclude "**/node_modules/**" \
--exclude "**/*.test.ts" \
--excludePrivate \
--excludeProtected \
--readme none
```
## Execution
### Basic Run
```javascript
async function runTypeDoc(workDir) {
const outputDir = `${workDir}/api-docs/frontend`;
// Create output directory
Bash({ command: `mkdir -p "${outputDir}"` });
// Run TypeDoc
const result = Bash({
command: `npx typedoc --out "${outputDir}" --plugin typedoc-plugin-markdown src/`,
timeout: 120000 // 2 minutes
});
if (result.exitCode !== 0) {
console.error('TypeDoc failed:', result.stderr);
return { success: false, error: result.stderr };
}
// List generated files
const files = Glob(`${outputDir}/**/*.md`);
console.log(`Generated ${files.length} documentation files`);
return { success: true, files };
}
```
### With Custom Entry Points
```javascript
async function runTypeDocCustom(workDir, entryPoints) {
const outputDir = `${workDir}/api-docs/frontend`;
// Build entry points string
const entries = entryPoints.map(e => `--entryPoints "${e}"`).join(' ');
const result = Bash({
command: `npx typedoc ${entries} --out "${outputDir}" --plugin typedoc-plugin-markdown`,
timeout: 120000
});
return { success: result.exitCode === 0 };
}
// Example: Document specific files
await runTypeDocCustom(workDir, [
'src/api/index.ts',
'src/hooks/index.ts',
'src/utils/index.ts'
]);
```
## Output Structure
```
api-docs/frontend/
├── README.md # Index
├── modules.md # Module list
├── modules/
│ ├── api.md # API module
│ ├── hooks.md # Hooks module
│ └── utils.md # Utils module
├── classes/
│ ├── ApiClient.md # Class documentation
│ └── ...
├── interfaces/
│ ├── Config.md # Interface documentation
│ └── ...
└── functions/
├── formatDate.md # Function documentation
└── ...
```
## Integration with Manual
### Reading TypeDoc Output
```javascript
async function integrateTypeDocOutput(workDir) {
const apiDocsDir = `${workDir}/api-docs/frontend`;
const files = Glob(`${apiDocsDir}/**/*.md`);
// Build API reference content
let content = '## Frontend API Reference\n\n';
// Add modules
const modules = Glob(`${apiDocsDir}/modules/*.md`);
for (const mod of modules) {
const modContent = Read(mod);
content += `### ${extractTitle(modContent)}\n\n`;
content += summarizeModule(modContent);
}
// Add functions
const functions = Glob(`${apiDocsDir}/functions/*.md`);
content += '\n### Functions\n\n';
for (const fn of functions) {
const fnContent = Read(fn);
content += formatFunctionDoc(fnContent);
}
// Add hooks
const hooks = Glob(`${apiDocsDir}/functions/*Hook*.md`);
if (hooks.length > 0) {
content += '\n### Hooks\n\n';
for (const hook of hooks) {
const hookContent = Read(hook);
content += formatHookDoc(hookContent);
}
}
return content;
}
```
### Example Output Format
```markdown
## Frontend API Reference
### API Module
Functions for interacting with the backend API.
#### fetchProjects
```typescript
function fetchProjects(options?: FetchOptions): Promise<Project[]>
```
Fetches all projects for the current user.
**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| options | FetchOptions | Optional fetch configuration |
**Returns:** Promise<Project[]>
### Hooks
#### useProjects
```typescript
function useProjects(options?: UseProjectsOptions): UseProjectsResult
```
React hook for managing project data.
**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| options.status | string | Filter by project status |
| options.limit | number | Max projects to fetch |
**Returns:**
| Property | Type | Description |
|----------|------|-------------|
| projects | Project[] | Array of projects |
| loading | boolean | Loading state |
| error | Error \| null | Error if failed |
| refetch | () => void | Refresh data |
```
## Troubleshooting
### Common Issues
#### "Cannot find module 'typescript'"
```bash
npm install --save-dev typescript
```
#### "No entry points found"
Ensure entry points exist:
```bash
# Check entry points
ls src/index.ts
# Or use glob pattern
npx typedoc --entryPoints "src/**/*.ts"
```
#### "Unsupported TypeScript version"
```bash
# Check TypeDoc compatibility
npm info typedoc peerDependencies
# Install compatible version
npm install --save-dev typedoc@0.25.x
```
### Debugging
```bash
# Verbose output
npx typedoc --logLevel Verbose src/
# Show warnings
npx typedoc --treatWarningsAsErrors src/
```
## Best Practices
### Document Exports Only
```typescript
// Good: Public API documented
/**
* Fetches projects from the API.
* @param options - Fetch options
* @returns Promise resolving to projects
*/
export function fetchProjects(options?: FetchOptions): Promise<Project[]> {
// ...
}
// Internal: Not documented
function internalHelper() {
// ...
}
```
### Use JSDoc Comments
```typescript
/**
* User hook for managing authentication state.
*
* @example
* ```tsx
* const { user, login, logout } = useAuth();
* ```
*
* @returns Authentication state and methods
*/
export function useAuth(): AuthResult {
// ...
}
```
### Define Types Properly
```typescript
/**
* Configuration for the API client.
*/
export interface ApiConfig {
/** API base URL */
baseUrl: string;
/** Request timeout in milliseconds */
timeout?: number;
/** Custom headers to include */
headers?: Record<string, string>;
}
```

View File

@@ -0,0 +1,325 @@
# HTML Template Specification
Technical specification for the TiddlyWiki-style HTML output.
## Overview
The output is a single, self-contained HTML file with:
- All CSS embedded inline
- All JavaScript embedded inline
- All images embedded as Base64
- Full offline functionality
## File Structure
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{SOFTWARE_NAME}} - User Manual</title>
<style>{{EMBEDDED_CSS}}</style>
</head>
<body class="wiki-container" data-theme="light">
<aside class="wiki-sidebar">...</aside>
<main class="wiki-content">...</main>
<button class="theme-toggle">...</button>
<script id="search-index" type="application/json">{{SEARCH_INDEX}}</script>
<script>{{EMBEDDED_JS}}</script>
</body>
</html>
```
## Placeholders
| Placeholder | Description | Source |
|-------------|-------------|--------|
| `{{SOFTWARE_NAME}}` | Software name | manual-config.json |
| `{{VERSION}}` | Version number | manual-config.json |
| `{{EMBEDDED_CSS}}` | All CSS content | wiki-base.css + wiki-dark.css |
| `{{TOC_HTML}}` | Table of contents | Generated from sections |
| `{{TIDDLERS_HTML}}` | All content blocks | Converted from Markdown |
| `{{SEARCH_INDEX_JSON}}` | Search data | Generated from content |
| `{{EMBEDDED_JS}}` | JavaScript code | Inline in template |
| `{{TIMESTAMP}}` | Generation timestamp | ISO 8601 format |
| `{{LOGO_BASE64}}` | Logo image | Project logo or generated |
## Component Specifications
### Sidebar (`.wiki-sidebar`)
```
Width: 280px (fixed)
Position: Fixed left
Height: 100vh
Components:
- Logo area (.wiki-logo)
- Search box (.wiki-search)
- Tag navigation (.wiki-tags)
- Table of contents (.wiki-toc)
```
### Main Content (`.wiki-content`)
```
Margin-left: 280px (sidebar width)
Max-width: 900px (content)
Components:
- Header bar (.content-header)
- Tiddler container (.tiddler-container)
- Footer (.wiki-footer)
```
### Tiddler (Content Block)
```html
<article class="tiddler"
id="tiddler-{{ID}}"
data-tags="{{TAGS}}"
data-difficulty="{{DIFFICULTY}}">
<header class="tiddler-header">
<h2 class="tiddler-title">
<button class="collapse-toggle"></button>
{{TITLE}}
</h2>
<div class="tiddler-meta">
<span class="difficulty-badge {{DIFFICULTY}}">{{DIFFICULTY_LABEL}}</span>
{{TAG_BADGES}}
</div>
</header>
<div class="tiddler-content">
{{CONTENT_HTML}}
</div>
</article>
```
### Search Index Format
```json
{
"tiddler-overview": {
"title": "Product Overview",
"body": "Plain text content for searching...",
"tags": ["getting-started", "overview"]
},
"tiddler-ui-guide": {
"title": "UI Guide",
"body": "Plain text content...",
"tags": ["ui-guide"]
}
}
```
## Interactive Features
### 1. Search
- Full-text search with result highlighting
- Searches title, body, and tags
- Shows up to 10 results
- Keyboard accessible (Enter to search, Esc to close)
### 2. Collapse/Expand
- Per-section toggle via button
- Expand All / Collapse All buttons
- State indicated by ▼ (expanded) or ▶ (collapsed)
- Smooth transition animation
### 3. Tag Filtering
- Tags: all, getting-started, ui-guide, api, config, troubleshooting, examples
- Single selection (radio behavior)
- "all" shows everything
- Hidden tiddlers via `display: none`
### 4. Theme Toggle
- Light/Dark mode switch
- Persists to localStorage (`wiki-theme`)
- Applies to entire document via `[data-theme="dark"]`
- Toggle button shows sun/moon icon
### 5. Responsive Design
```
Breakpoints:
- Desktop (> 1024px): Sidebar visible
- Tablet (768-1024px): Sidebar collapsible
- Mobile (< 768px): Sidebar hidden, hamburger menu
```
### 6. Print Support
- Hides sidebar, toggles, interactive elements
- Expands all collapsed sections
- Adjusts colors for print
- Page breaks between sections
## Accessibility
### Keyboard Navigation
- Tab through interactive elements
- Enter to activate buttons
- Escape to close search results
- Arrow keys in search results
### ARIA Attributes
```html
<input aria-label="Search">
<nav aria-label="Table of Contents">
<button aria-label="Toggle theme">
<div aria-live="polite"> <!-- For search results -->
```
### Color Contrast
- Text/background ratio ≥ 4.5:1
- Interactive elements clearly visible
- Focus indicators visible
## Performance
### Target Metrics
| Metric | Target |
|--------|--------|
| Total file size | < 10MB |
| Time to interactive | < 2s |
| Search latency | < 100ms |
### Optimization Strategies
1. **Lazy loading for images**: `loading="lazy"`
2. **Efficient search**: In-memory index, no external requests
3. **CSS containment**: Scope styles to components
4. **Minimal JavaScript**: Vanilla JS, no libraries
## CSS Variables
### Light Theme
```css
:root {
--bg-primary: #ffffff;
--bg-secondary: #f8f9fa;
--text-primary: #212529;
--text-secondary: #495057;
--accent-color: #0d6efd;
--border-color: #dee2e6;
}
```
### Dark Theme
```css
[data-theme="dark"] {
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--text-primary: #eaeaea;
--text-secondary: #b8b8b8;
--accent-color: #4dabf7;
--border-color: #2d3748;
}
```
## Markdown to HTML Mapping
| Markdown | HTML |
|----------|------|
| `# Heading` | `<h1>` |
| `## Heading` | `<h2>` |
| `**bold**` | `<strong>` |
| `*italic*` | `<em>` |
| `` `code` `` | `<code>` |
| `[link](url)` | `<a href="url">` |
| `- item` | `<ul><li>` |
| `1. item` | `<ol><li>` |
| ``` ```js ``` | `<pre><code class="language-js">` |
| `> quote` | `<blockquote>` |
| `---` | `<hr>` |
## Screenshot Embedding
### Marker Format
```markdown
<!-- SCREENSHOT: id="ss-login" url="/login" description="Login form" -->
```
### Embedded Format
```html
<figure class="screenshot">
<img src="data:image/png;base64,{{BASE64_DATA}}"
alt="Login form"
loading="lazy">
<figcaption>Login form</figcaption>
</figure>
```
### Placeholder (if missing)
```html
<div class="screenshot-placeholder">
[Screenshot: ss-login - Login form]
</div>
```
## File Size Optimization
### CSS
- Minify before embedding
- Remove unused styles
- Combine duplicate rules
### JavaScript
- Minify before embedding
- Remove console.log statements
- Use IIFE for scoping
### Images
- Compress before Base64 encoding
- Use appropriate dimensions (max 1280px width)
- Consider WebP format if browser support is acceptable
## Validation
### HTML Validation
- W3C HTML5 compliance
- Proper nesting
- Required attributes present
### CSS Validation
- Valid property values
- No deprecated properties
- Vendor prefixes where needed
### JavaScript
- No syntax errors
- All functions defined
- Error handling for edge cases
## Testing Checklist
- [ ] Opens in Chrome/Firefox/Safari/Edge
- [ ] Search works correctly
- [ ] Collapse/expand works
- [ ] Tag filtering works
- [ ] Theme toggle works
- [ ] Print preview correct
- [ ] Keyboard navigation works
- [ ] Mobile responsive
- [ ] Offline functionality
- [ ] All links valid
- [ ] All images display
- [ ] No console errors

View File

@@ -0,0 +1,253 @@
# Quality Standards
Quality gates and standards for software manual generation.
## Quality Dimensions
### 1. Completeness (25%)
All required sections present and adequately covered.
| Requirement | Weight | Criteria |
|-------------|--------|----------|
| Overview section | 5 | Product intro, features, quick start |
| UI Guide | 5 | All major screens documented |
| API Reference | 5 | All public APIs documented |
| Configuration | 4 | All config options explained |
| Troubleshooting | 3 | Common issues addressed |
| Examples | 3 | Multi-level examples provided |
**Scoring**:
- 100%: All sections present with adequate depth
- 80%: All sections present, some lacking depth
- 60%: Missing 1-2 sections
- 40%: Missing 3+ sections
- 0%: Critical sections missing (overview, UI guide)
### 2. Consistency (25%)
Terminology, style, and structure uniform across sections.
| Aspect | Check |
|--------|-------|
| Terminology | Same term for same concept throughout |
| Formatting | Consistent heading levels, code block styles |
| Tone | Consistent formality level |
| Cross-references | All internal links valid |
| Screenshot naming | Follow `ss-{feature}-{action}` pattern |
**Scoring**:
- 100%: Zero inconsistencies
- 80%: 1-3 minor inconsistencies
- 60%: 4-6 inconsistencies
- 40%: 7-10 inconsistencies
- 0%: Pervasive inconsistencies
### 3. Depth (25%)
Content provides sufficient detail for target audience.
| Level | Criteria |
|-------|----------|
| Shallow | Basic descriptions only |
| Standard | Descriptions + usage examples |
| Deep | Descriptions + examples + edge cases + best practices |
**Per-Section Depth Check**:
- [ ] Explains "what" (definition)
- [ ] Explains "why" (rationale)
- [ ] Explains "how" (procedure)
- [ ] Provides examples
- [ ] Covers edge cases
- [ ] Includes tips/best practices
**Scoring**:
- 100%: Deep coverage on all critical sections
- 80%: Standard coverage on all sections
- 60%: Shallow coverage on some sections
- 40%: Missing depth in critical areas
- 0%: Superficial throughout
### 4. Readability (25%)
Clear, user-friendly writing that's easy to follow.
| Metric | Target |
|--------|--------|
| Sentence length | Average < 20 words |
| Paragraph length | Average < 5 sentences |
| Heading hierarchy | Proper H1 > H2 > H3 nesting |
| Code blocks | Language specified |
| Lists | Used for 3+ items |
| Screenshots | Placed near relevant text |
**Structural Elements**:
- [ ] Clear section headers
- [ ] Numbered steps for procedures
- [ ] Bullet lists for options/features
- [ ] Tables for comparisons
- [ ] Code blocks with syntax highlighting
- [ ] Screenshots with captions
**Scoring**:
- 100%: All readability criteria met
- 80%: Minor structural issues
- 60%: Some sections hard to follow
- 40%: Significant readability problems
- 0%: Unclear, poorly structured
## Overall Quality Score
```
Overall = (Completeness × 0.25) + (Consistency × 0.25) +
(Depth × 0.25) + (Readability × 0.25)
```
**Quality Gates**:
| Gate | Threshold | Action |
|------|-----------|--------|
| Pass | ≥ 80% | Proceed to HTML generation |
| Review | 60-79% | Address warnings, proceed with caution |
| Fail | < 60% | Must address errors before continuing |
## Issue Classification
### Errors (Must Fix)
- Missing required sections
- Invalid cross-references
- Broken screenshot markers
- Code blocks without language
- Incomplete procedures (missing steps)
### Warnings (Should Fix)
- Terminology inconsistencies
- Sections lacking depth
- Missing examples
- Long paragraphs (> 7 sentences)
- Screenshots missing captions
### Info (Nice to Have)
- Optimization suggestions
- Additional example opportunities
- Alternative explanations
- Enhancement ideas
## Quality Checklist
### Pre-Generation
- [ ] All agents completed successfully
- [ ] No errors in consolidation report
- [ ] Overall score ≥ 60%
### Post-Generation
- [ ] HTML renders correctly
- [ ] Search returns relevant results
- [ ] All screenshots display
- [ ] Theme toggle works
- [ ] Print preview looks good
### Final Review
- [ ] User previewed and approved
- [ ] File size reasonable (< 10MB)
- [ ] No console errors in browser
- [ ] Accessible (keyboard navigation works)
## Automated Checks
```javascript
function runQualityChecks(workDir) {
const results = {
completeness: checkCompleteness(workDir),
consistency: checkConsistency(workDir),
depth: checkDepth(workDir),
readability: checkReadability(workDir)
};
results.overall = (
results.completeness * 0.25 +
results.consistency * 0.25 +
results.depth * 0.25 +
results.readability * 0.25
);
return results;
}
function checkCompleteness(workDir) {
const requiredSections = [
'section-overview.md',
'section-ui-guide.md',
'section-api-reference.md',
'section-configuration.md',
'section-troubleshooting.md',
'section-examples.md'
];
const existing = Glob(`${workDir}/sections/section-*.md`);
const found = requiredSections.filter(s =>
existing.some(e => e.endsWith(s))
);
return (found.length / requiredSections.length) * 100;
}
function checkConsistency(workDir) {
// Check terminology, cross-references, naming conventions
const issues = [];
// ... implementation ...
return Math.max(0, 100 - issues.length * 10);
}
function checkDepth(workDir) {
// Check content length, examples, edge cases
const sections = Glob(`${workDir}/sections/section-*.md`);
let totalScore = 0;
for (const section of sections) {
const content = Read(section);
let sectionScore = 0;
if (content.length > 500) sectionScore += 20;
if (content.includes('```')) sectionScore += 20;
if (content.includes('Example')) sectionScore += 20;
if (content.match(/\d+\./g)?.length > 3) sectionScore += 20;
if (content.includes('Note:') || content.includes('Tip:')) sectionScore += 20;
totalScore += sectionScore;
}
return totalScore / sections.length;
}
function checkReadability(workDir) {
// Check structure, formatting, organization
const sections = Glob(`${workDir}/sections/section-*.md`);
let issues = 0;
for (const section of sections) {
const content = Read(section);
// Check heading hierarchy
if (!content.startsWith('# ')) issues++;
// Check code block languages
const codeBlocks = content.match(/```\w*/g);
if (codeBlocks?.some(b => b === '```')) issues++;
// Check paragraph length
const paragraphs = content.split('\n\n');
if (paragraphs.some(p => p.split('. ').length > 7)) issues++;
}
return Math.max(0, 100 - issues * 10);
}
```

View File

@@ -0,0 +1,298 @@
# Writing Style Guide
User-friendly writing standards for software manuals.
## Core Principles
### 1. User-Centered
Write for the user, not the developer.
**Do**:
- "Click the **Save** button to save your changes"
- "Enter your email address in the login form"
**Don't**:
- "The onClick handler triggers the save mutation"
- "POST to /api/auth/login with email in body"
### 2. Action-Oriented
Focus on what users can **do**, not what the system does.
**Do**:
- "You can export your data as CSV"
- "To create a new project, click **New Project**"
**Don't**:
- "The system exports data in CSV format"
- "A new project is created when the button is clicked"
### 3. Clear and Direct
Use simple, straightforward language.
**Do**:
- "Select a file to upload"
- "The maximum file size is 10MB"
**Don't**:
- "Utilize the file selection interface to designate a file for uploading"
- "File size constraints limit uploads to 10 megabytes"
## Tone
### Friendly but Professional
- Conversational but not casual
- Helpful but not condescending
- Confident but not arrogant
**Examples**:
| Too Casual | Just Right | Too Formal |
|------------|------------|------------|
| "Yo, here's how..." | "Here's how to..." | "The following procedure describes..." |
| "Easy peasy!" | "That's all you need to do." | "The procedure has been completed." |
| "Don't worry about it" | "You don't need to change this" | "This parameter does not require modification" |
### Second Person
Address the user directly as "you".
**Do**: "You can customize your dashboard..."
**Don't**: "Users can customize their dashboards..."
## Structure
### Headings
Use clear, descriptive headings that tell users what they'll learn.
**Good Headings**:
- "Getting Started"
- "Creating Your First Project"
- "Configuring Email Notifications"
- "Troubleshooting Login Issues"
**Weak Headings**:
- "Overview"
- "Step 1"
- "Settings"
- "FAQ"
### Procedures
Number steps for sequential tasks.
```markdown
## Creating a New User
1. Navigate to **Settings** > **Users**.
2. Click the **Add User** button.
3. Enter the user's email address.
4. Select a role from the dropdown.
5. Click **Save**.
The new user will receive an invitation email.
```
### Features/Options
Use bullet lists for non-sequential items.
```markdown
## Export Options
You can export your data in several formats:
- **CSV**: Compatible with spreadsheets
- **JSON**: Best for developers
- **PDF**: Ideal for sharing reports
```
### Comparisons
Use tables for comparing options.
```markdown
## Plan Comparison
| Feature | Free | Pro | Enterprise |
|---------|------|-----|------------|
| Projects | 3 | Unlimited | Unlimited |
| Storage | 1GB | 10GB | 100GB |
| Support | Community | Email | Dedicated |
```
## Content Types
### Conceptual (What Is)
Explain what something is and why it matters.
```markdown
## What is a Workspace?
A workspace is a container for your projects and team members. Each workspace
has its own settings, billing, and permissions. You might create separate
workspaces for different clients or departments.
```
### Procedural (How To)
Step-by-step instructions for completing a task.
```markdown
## How to Create a Workspace
1. Click your profile icon in the top-right corner.
2. Select **Create Workspace**.
3. Enter a name for your workspace.
4. Choose a plan (you can upgrade later).
5. Click **Create**.
Your new workspace is ready to use.
```
### Reference (API/Config)
Detailed specifications and parameters.
```markdown
## Configuration Options
### `DATABASE_URL`
- **Type**: String (required)
- **Format**: `postgresql://user:password@host:port/database`
- **Example**: `postgresql://admin:secret@localhost:5432/myapp`
Database connection string for PostgreSQL.
```
## Formatting
### Bold
Use for:
- UI elements: Click **Save**
- First use of key terms: **Workspaces** contain projects
- Emphasis: **Never** share your API key
### Italic
Use for:
- Introducing new terms: A *workspace* is...
- Placeholders: Replace *your-api-key* with...
- Emphasis (sparingly): This is *really* important
### Code
Use for:
- Commands: Run `npm install`
- File paths: Edit `config/settings.json`
- Environment variables: Set `DATABASE_URL`
- API endpoints: POST `/api/users`
- Code references: The `handleSubmit` function
### Code Blocks
Always specify the language.
```javascript
// Example: Fetching user data
const response = await fetch('/api/user');
const user = await response.json();
```
### Notes and Warnings
Use for important callouts.
```markdown
> **Note**: This feature requires a Pro plan.
> **Warning**: Deleting a workspace cannot be undone.
> **Tip**: Use keyboard shortcuts to work faster.
```
## Screenshots
### When to Include
- First time showing a UI element
- Complex interfaces
- Before/after comparisons
- Error states
### Guidelines
- Capture just the relevant area
- Use consistent dimensions
- Highlight important elements
- Add descriptive captions
```markdown
<!-- SCREENSHOT: id="ss-dashboard" description="Main dashboard showing project list" -->
*The dashboard displays all your projects with their status.*
```
## Examples
### Good Section Example
```markdown
## Inviting Team Members
You can invite colleagues to collaborate on your projects.
### To invite a team member:
1. Open **Settings** > **Team**.
2. Click **Invite Member**.
3. Enter their email address.
4. Select their role:
- **Admin**: Full access to all settings
- **Editor**: Can edit projects
- **Viewer**: Read-only access
5. Click **Send Invite**.
The person will receive an email with a link to join your workspace.
> **Note**: You can have up to 5 team members on the Free plan.
<!-- SCREENSHOT: id="ss-invite-team" description="Team invitation dialog" -->
```
## Language Guidelines
### Avoid Jargon
| Technical | User-Friendly |
|-----------|---------------|
| Execute | Run |
| Terminate | Stop, End |
| Instantiate | Create |
| Invoke | Call, Use |
| Parameterize | Set, Configure |
| Persist | Save |
### Be Specific
| Vague | Specific |
|-------|----------|
| "Click the button" | "Click **Save**" |
| "Enter information" | "Enter your email address" |
| "An error occurred" | "Your password must be at least 8 characters" |
| "It takes a moment" | "This typically takes 2-3 seconds" |
### Use Active Voice
| Passive | Active |
|---------|--------|
| "The file is uploaded" | "Upload the file" |
| "Settings are saved" | "Click **Save** to keep your changes" |
| "Errors are displayed" | "The form shows any errors" |

View File

@@ -0,0 +1,695 @@
/* ========================================
TiddlyWiki-Style Base CSS
Software Manual Skill
======================================== */
/* ========== CSS Variables ========== */
:root {
/* Light Theme */
--bg-primary: #ffffff;
--bg-secondary: #f8f9fa;
--bg-tertiary: #e9ecef;
--text-primary: #212529;
--text-secondary: #495057;
--text-muted: #6c757d;
--border-color: #dee2e6;
--accent-color: #0d6efd;
--accent-hover: #0b5ed7;
--success-color: #198754;
--warning-color: #ffc107;
--danger-color: #dc3545;
--info-color: #0dcaf0;
/* Layout */
--sidebar-width: 280px;
--header-height: 60px;
--content-max-width: 900px;
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
/* Typography */
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--font-family-mono: 'SF Mono', Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--line-height: 1.6;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
/* Transitions */
--transition-fast: 150ms ease;
--transition-base: 300ms ease;
}
/* ========== Reset & Base ========== */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
}
body {
font-family: var(--font-family);
font-size: var(--font-size-base);
line-height: var(--line-height);
color: var(--text-primary);
background-color: var(--bg-secondary);
}
/* ========== Layout ========== */
.wiki-container {
display: flex;
min-height: 100vh;
}
/* ========== Sidebar ========== */
.wiki-sidebar {
position: fixed;
top: 0;
left: 0;
width: var(--sidebar-width);
height: 100vh;
background-color: var(--bg-primary);
border-right: 1px solid var(--border-color);
overflow-y: auto;
z-index: 100;
display: flex;
flex-direction: column;
transition: transform var(--transition-base);
}
/* Logo Area */
.wiki-logo {
padding: var(--spacing-lg);
text-align: center;
border-bottom: 1px solid var(--border-color);
}
.wiki-logo .logo-placeholder {
width: 60px;
height: 60px;
margin: 0 auto var(--spacing-sm);
background: linear-gradient(135deg, var(--accent-color), var(--info-color));
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: var(--font-size-xl);
}
.wiki-logo h1 {
font-size: var(--font-size-lg);
font-weight: 600;
margin-bottom: var(--spacing-xs);
}
.wiki-logo .version {
font-size: var(--font-size-sm);
color: var(--text-muted);
}
/* Search */
.wiki-search {
padding: var(--spacing-md);
position: relative;
}
.wiki-search input {
width: 100%;
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: var(--font-size-sm);
background-color: var(--bg-secondary);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.wiki-search input:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.15);
}
.search-results {
position: absolute;
top: 100%;
left: var(--spacing-md);
right: var(--spacing-md);
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 6px;
box-shadow: var(--shadow-lg);
max-height: 400px;
overflow-y: auto;
z-index: 200;
}
.search-result-item {
display: block;
padding: var(--spacing-sm) var(--spacing-md);
text-decoration: none;
color: var(--text-primary);
border-bottom: 1px solid var(--border-color);
transition: background-color var(--transition-fast);
}
.search-result-item:last-child {
border-bottom: none;
}
.search-result-item:hover {
background-color: var(--bg-secondary);
}
.result-title {
font-weight: 600;
margin-bottom: var(--spacing-xs);
}
.result-excerpt {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.result-excerpt mark {
background-color: var(--warning-color);
padding: 0 2px;
border-radius: 2px;
}
.no-results {
padding: var(--spacing-md);
text-align: center;
color: var(--text-muted);
}
/* Tags */
.wiki-tags {
padding: var(--spacing-md);
display: flex;
flex-wrap: wrap;
gap: var(--spacing-xs);
border-bottom: 1px solid var(--border-color);
}
.wiki-tags .tag {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: var(--font-size-sm);
border: 1px solid var(--border-color);
border-radius: 20px;
background: var(--bg-secondary);
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition-fast);
}
.wiki-tags .tag:hover {
border-color: var(--accent-color);
color: var(--accent-color);
}
.wiki-tags .tag.active {
background-color: var(--accent-color);
border-color: var(--accent-color);
color: white;
}
/* Table of Contents */
.wiki-toc {
flex: 1;
padding: var(--spacing-md);
overflow-y: auto;
}
.wiki-toc h3 {
font-size: var(--font-size-sm);
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
margin-bottom: var(--spacing-md);
}
.wiki-toc ul {
list-style: none;
}
.wiki-toc li {
margin-bottom: var(--spacing-xs);
}
.wiki-toc a {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-sm);
color: var(--text-secondary);
text-decoration: none;
border-radius: 6px;
font-size: var(--font-size-sm);
transition: all var(--transition-fast);
}
.wiki-toc a:hover {
background-color: var(--bg-secondary);
color: var(--accent-color);
}
/* ========== Main Content ========== */
.wiki-content {
flex: 1;
margin-left: var(--sidebar-width);
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* Header */
.content-header {
position: sticky;
top: 0;
background-color: var(--bg-primary);
border-bottom: 1px solid var(--border-color);
padding: var(--spacing-sm) var(--spacing-lg);
display: flex;
align-items: center;
justify-content: space-between;
z-index: 50;
}
.sidebar-toggle {
display: none;
flex-direction: column;
gap: 4px;
padding: var(--spacing-sm);
background: none;
border: none;
cursor: pointer;
}
.sidebar-toggle span {
display: block;
width: 20px;
height: 2px;
background-color: var(--text-primary);
transition: transform var(--transition-fast);
}
.header-actions {
display: flex;
gap: var(--spacing-sm);
}
.header-actions button {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: var(--font-size-sm);
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--bg-primary);
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition-fast);
}
.header-actions button:hover {
border-color: var(--accent-color);
color: var(--accent-color);
}
/* Tiddler Container */
.tiddler-container {
flex: 1;
max-width: var(--content-max-width);
margin: 0 auto;
padding: var(--spacing-lg);
width: 100%;
}
/* ========== Tiddler (Content Block) ========== */
.tiddler {
background-color: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
margin-bottom: var(--spacing-lg);
box-shadow: var(--shadow-sm);
transition: box-shadow var(--transition-fast);
}
.tiddler:hover {
box-shadow: var(--shadow-md);
}
.tiddler-header {
padding: var(--spacing-md) var(--spacing-lg);
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: var(--spacing-sm);
}
.tiddler-title {
display: flex;
align-items: center;
gap: var(--spacing-sm);
font-size: var(--font-size-xl);
font-weight: 600;
margin: 0;
}
.collapse-toggle {
background: none;
border: none;
font-size: var(--font-size-sm);
color: var(--text-muted);
cursor: pointer;
padding: var(--spacing-xs);
transition: transform var(--transition-fast);
}
.tiddler.collapsed .collapse-toggle {
transform: rotate(-90deg);
}
.tiddler-meta {
display: flex;
gap: var(--spacing-sm);
flex-wrap: wrap;
}
.difficulty-badge {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: 0.75rem;
font-weight: 500;
border-radius: 4px;
text-transform: uppercase;
}
.difficulty-badge.beginner {
background-color: #d1fae5;
color: #065f46;
}
.difficulty-badge.intermediate {
background-color: #fef3c7;
color: #92400e;
}
.difficulty-badge.advanced {
background-color: #fee2e2;
color: #991b1b;
}
.tag-badge {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: 0.75rem;
background-color: var(--bg-tertiary);
color: var(--text-secondary);
border-radius: 4px;
}
.tiddler-content {
padding: var(--spacing-lg);
}
.tiddler.collapsed .tiddler-content {
display: none;
}
/* ========== Content Typography ========== */
.tiddler-content h1,
.tiddler-content h2,
.tiddler-content h3,
.tiddler-content h4 {
margin-top: var(--spacing-lg);
margin-bottom: var(--spacing-md);
font-weight: 600;
}
.tiddler-content h1 { font-size: 1.75rem; }
.tiddler-content h2 { font-size: 1.5rem; }
.tiddler-content h3 { font-size: 1.25rem; }
.tiddler-content h4 { font-size: 1.125rem; }
.tiddler-content p {
margin-bottom: var(--spacing-md);
}
.tiddler-content ul,
.tiddler-content ol {
margin-bottom: var(--spacing-md);
padding-left: var(--spacing-lg);
}
.tiddler-content li {
margin-bottom: var(--spacing-xs);
}
.tiddler-content a {
color: var(--accent-color);
text-decoration: none;
}
.tiddler-content a:hover {
text-decoration: underline;
}
/* Code */
.tiddler-content code {
font-family: var(--font-family-mono);
font-size: 0.9em;
padding: 2px 6px;
background-color: var(--bg-tertiary);
border-radius: 4px;
}
.tiddler-content pre {
position: relative;
margin-bottom: var(--spacing-md);
padding: var(--spacing-md);
background-color: #1e1e1e;
border-radius: 8px;
overflow-x: auto;
}
.tiddler-content pre code {
padding: 0;
background: none;
color: #d4d4d4;
font-size: var(--font-size-sm);
}
.copy-code-btn {
position: absolute;
top: var(--spacing-sm);
right: var(--spacing-sm);
padding: var(--spacing-xs) var(--spacing-sm);
font-size: 0.75rem;
background-color: var(--bg-tertiary);
border: none;
border-radius: 4px;
cursor: pointer;
opacity: 0;
transition: opacity var(--transition-fast);
}
.tiddler-content pre:hover .copy-code-btn {
opacity: 1;
}
/* Tables */
.tiddler-content table {
width: 100%;
margin-bottom: var(--spacing-md);
border-collapse: collapse;
}
.tiddler-content th,
.tiddler-content td {
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--border-color);
text-align: left;
}
.tiddler-content th {
background-color: var(--bg-secondary);
font-weight: 600;
}
.tiddler-content tr:nth-child(even) {
background-color: var(--bg-secondary);
}
/* Screenshots */
.screenshot {
margin: var(--spacing-lg) 0;
text-align: center;
}
.screenshot img {
max-width: 100%;
border: 1px solid var(--border-color);
border-radius: 8px;
box-shadow: var(--shadow-md);
}
.screenshot figcaption {
margin-top: var(--spacing-sm);
font-size: var(--font-size-sm);
color: var(--text-muted);
font-style: italic;
}
.screenshot-placeholder {
padding: var(--spacing-xl);
background-color: var(--bg-tertiary);
border: 2px dashed var(--border-color);
border-radius: 8px;
color: var(--text-muted);
text-align: center;
}
/* ========== Footer ========== */
.wiki-footer {
padding: var(--spacing-lg);
text-align: center;
color: var(--text-muted);
font-size: var(--font-size-sm);
border-top: 1px solid var(--border-color);
background-color: var(--bg-primary);
}
/* ========== Theme Toggle ========== */
.theme-toggle {
position: fixed;
bottom: var(--spacing-lg);
right: var(--spacing-lg);
width: 48px;
height: 48px;
border-radius: 50%;
border: none;
background-color: var(--bg-primary);
box-shadow: var(--shadow-lg);
cursor: pointer;
font-size: 1.5rem;
z-index: 100;
transition: transform var(--transition-fast);
}
.theme-toggle:hover {
transform: scale(1.1);
}
[data-theme="light"] .moon-icon { display: inline; }
[data-theme="light"] .sun-icon { display: none; }
[data-theme="dark"] .moon-icon { display: none; }
[data-theme="dark"] .sun-icon { display: inline; }
/* ========== Back to Top ========== */
.back-to-top {
position: fixed;
bottom: calc(var(--spacing-lg) + 60px);
right: var(--spacing-lg);
width: 40px;
height: 40px;
border-radius: 50%;
border: none;
background-color: var(--accent-color);
color: white;
font-size: 1.25rem;
cursor: pointer;
opacity: 0;
visibility: hidden;
transition: all var(--transition-fast);
z-index: 100;
}
.back-to-top.visible {
opacity: 1;
visibility: visible;
}
.back-to-top:hover {
background-color: var(--accent-hover);
}
/* ========== Responsive ========== */
@media (max-width: 1024px) {
.wiki-sidebar {
transform: translateX(-100%);
}
.wiki-sidebar.open {
transform: translateX(0);
}
.wiki-content {
margin-left: 0;
}
.sidebar-toggle {
display: flex;
}
}
@media (max-width: 640px) {
.tiddler-header {
flex-direction: column;
align-items: flex-start;
}
.header-actions {
display: none;
}
.wiki-tags {
overflow-x: auto;
flex-wrap: nowrap;
padding-bottom: var(--spacing-md);
}
}
/* ========== Print Styles ========== */
@media print {
.wiki-sidebar,
.theme-toggle,
.back-to-top,
.content-header,
.collapse-toggle,
.copy-code-btn {
display: none !important;
}
.wiki-content {
margin-left: 0;
}
.tiddler {
break-inside: avoid;
box-shadow: none;
border: 1px solid #ccc;
}
.tiddler.collapsed .tiddler-content {
display: block;
}
.tiddler-content pre {
background-color: #f5f5f5 !important;
color: #333 !important;
}
}

View File

@@ -0,0 +1,278 @@
/* ========================================
TiddlyWiki-Style Dark Theme
Software Manual Skill
======================================== */
[data-theme="dark"] {
/* Dark Theme Colors */
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--bg-tertiary: #0f3460;
--text-primary: #eaeaea;
--text-secondary: #b8b8b8;
--text-muted: #888888;
--border-color: #2d3748;
--accent-color: #4dabf7;
--accent-hover: #339af0;
--success-color: #51cf66;
--warning-color: #ffd43b;
--danger-color: #ff6b6b;
--info-color: #22b8cf;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.5);
}
/* Dark theme specific overrides */
[data-theme="dark"] .wiki-logo .logo-placeholder {
background: linear-gradient(135deg, var(--accent-color), #6741d9);
}
[data-theme="dark"] .wiki-search input {
background-color: var(--bg-tertiary);
border-color: var(--border-color);
color: var(--text-primary);
}
[data-theme="dark"] .wiki-search input::placeholder {
color: var(--text-muted);
}
[data-theme="dark"] .search-results {
background-color: var(--bg-secondary);
border-color: var(--border-color);
}
[data-theme="dark"] .search-result-item {
border-color: var(--border-color);
}
[data-theme="dark"] .search-result-item:hover {
background-color: var(--bg-tertiary);
}
[data-theme="dark"] .result-excerpt mark {
background-color: rgba(255, 212, 59, 0.3);
color: var(--warning-color);
}
[data-theme="dark"] .wiki-tags .tag {
background-color: var(--bg-tertiary);
border-color: var(--border-color);
color: var(--text-secondary);
}
[data-theme="dark"] .wiki-tags .tag:hover {
border-color: var(--accent-color);
color: var(--accent-color);
}
[data-theme="dark"] .wiki-tags .tag.active {
background-color: var(--accent-color);
border-color: var(--accent-color);
color: #1a1a2e;
}
[data-theme="dark"] .wiki-toc a:hover {
background-color: var(--bg-tertiary);
}
[data-theme="dark"] .content-header {
background-color: var(--bg-primary);
border-color: var(--border-color);
}
[data-theme="dark"] .sidebar-toggle span {
background-color: var(--text-primary);
}
[data-theme="dark"] .header-actions button {
background-color: var(--bg-secondary);
border-color: var(--border-color);
color: var(--text-secondary);
}
[data-theme="dark"] .header-actions button:hover {
border-color: var(--accent-color);
color: var(--accent-color);
}
[data-theme="dark"] .tiddler {
background-color: var(--bg-primary);
border-color: var(--border-color);
}
[data-theme="dark"] .tiddler-header {
border-color: var(--border-color);
}
[data-theme="dark"] .difficulty-badge.beginner {
background-color: rgba(81, 207, 102, 0.2);
color: var(--success-color);
}
[data-theme="dark"] .difficulty-badge.intermediate {
background-color: rgba(255, 212, 59, 0.2);
color: var(--warning-color);
}
[data-theme="dark"] .difficulty-badge.advanced {
background-color: rgba(255, 107, 107, 0.2);
color: var(--danger-color);
}
[data-theme="dark"] .tag-badge {
background-color: var(--bg-tertiary);
color: var(--text-secondary);
}
[data-theme="dark"] .tiddler-content code {
background-color: var(--bg-tertiary);
color: var(--accent-color);
}
[data-theme="dark"] .tiddler-content pre {
background-color: #0d1117;
border: 1px solid var(--border-color);
}
[data-theme="dark"] .tiddler-content pre code {
color: #e6e6e6;
}
[data-theme="dark"] .copy-code-btn {
background-color: var(--bg-tertiary);
color: var(--text-secondary);
}
[data-theme="dark"] .tiddler-content th {
background-color: var(--bg-tertiary);
}
[data-theme="dark"] .tiddler-content tr:nth-child(even) {
background-color: var(--bg-secondary);
}
[data-theme="dark"] .tiddler-content th,
[data-theme="dark"] .tiddler-content td {
border-color: var(--border-color);
}
[data-theme="dark"] .screenshot img {
border-color: var(--border-color);
}
[data-theme="dark"] .screenshot-placeholder {
background-color: var(--bg-tertiary);
border-color: var(--border-color);
}
[data-theme="dark"] .wiki-footer {
background-color: var(--bg-primary);
border-color: var(--border-color);
}
[data-theme="dark"] .theme-toggle {
background-color: var(--bg-secondary);
color: var(--warning-color);
}
[data-theme="dark"] .back-to-top {
background-color: var(--accent-color);
}
[data-theme="dark"] .back-to-top:hover {
background-color: var(--accent-hover);
}
/* Scrollbar styling for dark theme */
[data-theme="dark"] ::-webkit-scrollbar {
width: 8px;
height: 8px;
}
[data-theme="dark"] ::-webkit-scrollbar-track {
background: var(--bg-secondary);
}
[data-theme="dark"] ::-webkit-scrollbar-thumb {
background: var(--bg-tertiary);
border-radius: 4px;
}
[data-theme="dark"] ::-webkit-scrollbar-thumb:hover {
background: var(--border-color);
}
/* Selection color */
[data-theme="dark"] ::selection {
background-color: rgba(77, 171, 247, 0.3);
color: var(--text-primary);
}
/* Focus styles for accessibility */
[data-theme="dark"] :focus {
outline-color: var(--accent-color);
}
[data-theme="dark"] .wiki-search input:focus {
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(77, 171, 247, 0.2);
}
/* Link colors */
[data-theme="dark"] .tiddler-content a {
color: var(--accent-color);
}
[data-theme="dark"] .tiddler-content a:hover {
color: var(--accent-hover);
}
/* Blockquote styling */
[data-theme="dark"] .tiddler-content blockquote {
border-left: 4px solid var(--accent-color);
background-color: var(--bg-tertiary);
padding: var(--spacing-md);
margin: var(--spacing-md) 0;
color: var(--text-secondary);
}
/* Horizontal rule */
[data-theme="dark"] .tiddler-content hr {
border: none;
border-top: 1px solid var(--border-color);
margin: var(--spacing-lg) 0;
}
/* Alert/Note boxes */
[data-theme="dark"] .note,
[data-theme="dark"] .warning,
[data-theme="dark"] .tip,
[data-theme="dark"] .danger {
padding: var(--spacing-md);
border-radius: 6px;
margin: var(--spacing-md) 0;
}
[data-theme="dark"] .note {
background-color: rgba(34, 184, 207, 0.1);
border-left: 4px solid var(--info-color);
}
[data-theme="dark"] .warning {
background-color: rgba(255, 212, 59, 0.1);
border-left: 4px solid var(--warning-color);
}
[data-theme="dark"] .tip {
background-color: rgba(81, 207, 102, 0.1);
border-left: 4px solid var(--success-color);
}
[data-theme="dark"] .danger {
background-color: rgba(255, 107, 107, 0.1);
border-left: 4px solid var(--danger-color);
}

View File

@@ -0,0 +1,332 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{{SOFTWARE_NAME}} - Interactive Software Manual">
<meta name="generator" content="software-manual-skill">
<title>{{SOFTWARE_NAME}} v{{VERSION}} - User Manual</title>
<style>
{{EMBEDDED_CSS}}
</style>
</head>
<body class="wiki-container" data-theme="light">
<!-- Sidebar Navigation -->
<aside class="wiki-sidebar">
<!-- Logo and Title -->
<div class="wiki-logo">
<div class="logo-placeholder">{{SOFTWARE_NAME}}</div>
<h1>{{SOFTWARE_NAME}}</h1>
<span class="version">v{{VERSION}}</span>
</div>
<!-- Search Box -->
<div class="wiki-search">
<input type="text" id="searchInput" placeholder="Search documentation..." aria-label="Search">
<div id="searchResults" class="search-results" aria-live="polite"></div>
</div>
<!-- Tag Navigation -->
<nav class="wiki-tags" aria-label="Filter by category">
<button class="tag active" data-tag="all">All</button>
<button class="tag" data-tag="getting-started">Getting Started</button>
<button class="tag" data-tag="ui-guide">UI Guide</button>
<button class="tag" data-tag="api">API</button>
<button class="tag" data-tag="config">Configuration</button>
<button class="tag" data-tag="troubleshooting">Troubleshooting</button>
<button class="tag" data-tag="examples">Examples</button>
</nav>
<!-- Table of Contents -->
{{TOC_HTML}}
</aside>
<!-- Main Content Area -->
<main class="wiki-content">
<!-- Header Bar -->
<header class="content-header">
<button class="sidebar-toggle" id="sidebarToggle" aria-label="Toggle sidebar">
<span></span>
<span></span>
<span></span>
</button>
<div class="header-actions">
<button class="expand-all" id="expandAll">Expand All</button>
<button class="collapse-all" id="collapseAll">Collapse All</button>
<button class="print-btn" id="printBtn">Print</button>
</div>
</header>
<!-- Tiddler Container -->
<div class="tiddler-container">
{{TIDDLERS_HTML}}
</div>
<!-- Footer -->
<footer class="wiki-footer">
<p>Generated by <strong>software-manual-skill</strong></p>
<p>Last updated: <time datetime="{{TIMESTAMP}}">{{TIMESTAMP}}</time></p>
</footer>
</main>
<!-- Theme Toggle Button -->
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
<span class="sun-icon">&#9728;</span>
<span class="moon-icon">&#9790;</span>
</button>
<!-- Back to Top Button -->
<button class="back-to-top" id="backToTop" aria-label="Back to top">&#8593;</button>
<!-- Search Index Data -->
<script id="search-index" type="application/json">
{{SEARCH_INDEX_JSON}}
</script>
<!-- Embedded JavaScript -->
<script>
(function() {
'use strict';
// ========== Search Functionality ==========
class WikiSearch {
constructor(indexData) {
this.index = indexData;
}
search(query) {
if (!query || query.length < 2) return [];
const results = [];
const lowerQuery = query.toLowerCase();
const queryWords = lowerQuery.split(/\s+/);
for (const [id, content] of Object.entries(this.index)) {
let score = 0;
// Title match (higher weight)
const titleLower = content.title.toLowerCase();
if (titleLower.includes(lowerQuery)) {
score += 10;
}
queryWords.forEach(word => {
if (titleLower.includes(word)) score += 3;
});
// Body match
const bodyLower = content.body.toLowerCase();
if (bodyLower.includes(lowerQuery)) {
score += 5;
}
queryWords.forEach(word => {
if (bodyLower.includes(word)) score += 1;
});
// Tag match
if (content.tags) {
content.tags.forEach(tag => {
if (tag.toLowerCase().includes(lowerQuery)) score += 4;
});
}
if (score > 0) {
results.push({
id,
title: content.title,
excerpt: this.highlight(content.body, query),
score
});
}
}
return results
.sort((a, b) => b.score - a.score)
.slice(0, 10);
}
highlight(text, query) {
const maxLength = 150;
const lowerText = text.toLowerCase();
const lowerQuery = query.toLowerCase();
const index = lowerText.indexOf(lowerQuery);
if (index === -1) {
return text.substring(0, maxLength) + (text.length > maxLength ? '...' : '');
}
const start = Math.max(0, index - 40);
const end = Math.min(text.length, index + query.length + 80);
let excerpt = text.substring(start, end);
if (start > 0) excerpt = '...' + excerpt;
if (end < text.length) excerpt += '...';
// Highlight matches
const regex = new RegExp('(' + query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ')', 'gi');
return excerpt.replace(regex, '<mark>$1</mark>');
}
}
// Initialize search
const indexData = JSON.parse(document.getElementById('search-index').textContent);
const search = new WikiSearch(indexData);
const searchInput = document.getElementById('searchInput');
const searchResults = document.getElementById('searchResults');
searchInput.addEventListener('input', function() {
const query = this.value.trim();
const results = search.search(query);
if (results.length === 0) {
searchResults.innerHTML = query.length >= 2
? '<div class="no-results">No results found</div>'
: '';
return;
}
searchResults.innerHTML = results.map(r => `
<a href="#${r.id}" class="search-result-item" data-tiddler="${r.id}">
<div class="result-title">${r.title}</div>
<div class="result-excerpt">${r.excerpt}</div>
</a>
`).join('');
});
// Clear search on result click
searchResults.addEventListener('click', function(e) {
const item = e.target.closest('.search-result-item');
if (item) {
searchInput.value = '';
searchResults.innerHTML = '';
// Expand target tiddler
const tiddlerId = item.dataset.tiddler;
const tiddler = document.getElementById(tiddlerId);
if (tiddler) {
tiddler.classList.remove('collapsed');
const toggle = tiddler.querySelector('.collapse-toggle');
if (toggle) toggle.textContent = '▼';
}
}
});
// ========== Collapse/Expand ==========
document.querySelectorAll('.collapse-toggle').forEach(btn => {
btn.addEventListener('click', function() {
const tiddler = this.closest('.tiddler');
tiddler.classList.toggle('collapsed');
this.textContent = tiddler.classList.contains('collapsed') ? '▶' : '▼';
});
});
// Expand/Collapse All
document.getElementById('expandAll').addEventListener('click', function() {
document.querySelectorAll('.tiddler').forEach(t => {
t.classList.remove('collapsed');
const toggle = t.querySelector('.collapse-toggle');
if (toggle) toggle.textContent = '▼';
});
});
document.getElementById('collapseAll').addEventListener('click', function() {
document.querySelectorAll('.tiddler').forEach(t => {
t.classList.add('collapsed');
const toggle = t.querySelector('.collapse-toggle');
if (toggle) toggle.textContent = '▶';
});
});
// ========== Tag Filtering ==========
document.querySelectorAll('.wiki-tags .tag').forEach(tag => {
tag.addEventListener('click', function() {
const filter = this.dataset.tag;
// Update active state
document.querySelectorAll('.wiki-tags .tag').forEach(t => t.classList.remove('active'));
this.classList.add('active');
// Filter tiddlers
document.querySelectorAll('.tiddler').forEach(tiddler => {
if (filter === 'all') {
tiddler.style.display = '';
} else {
const tags = tiddler.dataset.tags || '';
tiddler.style.display = tags.includes(filter) ? '' : 'none';
}
});
});
});
// ========== Theme Toggle ==========
const themeToggle = document.getElementById('themeToggle');
const savedTheme = localStorage.getItem('wiki-theme');
if (savedTheme) {
document.body.dataset.theme = savedTheme;
}
themeToggle.addEventListener('click', function() {
const isDark = document.body.dataset.theme === 'dark';
document.body.dataset.theme = isDark ? 'light' : 'dark';
localStorage.setItem('wiki-theme', document.body.dataset.theme);
});
// ========== Sidebar Toggle (Mobile) ==========
document.getElementById('sidebarToggle').addEventListener('click', function() {
document.querySelector('.wiki-sidebar').classList.toggle('open');
});
// ========== Back to Top ==========
const backToTop = document.getElementById('backToTop');
window.addEventListener('scroll', function() {
backToTop.classList.toggle('visible', window.scrollY > 300);
});
backToTop.addEventListener('click', function() {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
// ========== Print ==========
document.getElementById('printBtn').addEventListener('click', function() {
window.print();
});
// ========== TOC Navigation ==========
document.querySelectorAll('.wiki-toc a').forEach(link => {
link.addEventListener('click', function(e) {
const tiddlerId = this.getAttribute('href').substring(1);
const tiddler = document.getElementById(tiddlerId);
if (tiddler) {
// Expand if collapsed
tiddler.classList.remove('collapsed');
const toggle = tiddler.querySelector('.collapse-toggle');
if (toggle) toggle.textContent = '▼';
// Close sidebar on mobile
document.querySelector('.wiki-sidebar').classList.remove('open');
}
});
});
// ========== Code Block Copy ==========
document.querySelectorAll('pre').forEach(pre => {
const copyBtn = document.createElement('button');
copyBtn.className = 'copy-code-btn';
copyBtn.textContent = 'Copy';
copyBtn.addEventListener('click', function() {
const code = pre.querySelector('code');
navigator.clipboard.writeText(code.textContent).then(() => {
copyBtn.textContent = 'Copied!';
setTimeout(() => copyBtn.textContent = 'Copy', 2000);
});
});
pre.appendChild(copyBtn);
});
})();
</script>
</body>
</html>