claude-code-workflow 6.3.11 → 6.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/.claude/CLAUDE.md +33 -33
  2. package/.claude/agents/issue-plan-agent.md +77 -5
  3. package/.claude/agents/issue-queue-agent.md +122 -18
  4. package/.claude/commands/issue/execute.md +53 -40
  5. package/.claude/commands/issue/new.md +113 -11
  6. package/.claude/commands/issue/plan.md +112 -37
  7. package/.claude/commands/issue/queue.md +28 -18
  8. package/.claude/skills/software-manual/scripts/assemble_docsify.py +584 -0
  9. package/.claude/skills/software-manual/templates/css/docsify-base.css +984 -0
  10. package/.claude/skills/software-manual/templates/docsify-shell.html +466 -0
  11. package/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json +141 -168
  12. package/.claude/workflows/cli-templates/schemas/solution-schema.json +3 -2
  13. package/.codex/prompts/issue-execute.md +3 -3
  14. package/.codex/prompts/issue-queue.md +3 -3
  15. package/ccw/dist/commands/issue.d.ts.map +1 -1
  16. package/ccw/dist/commands/issue.js +2 -1
  17. package/ccw/dist/commands/issue.js.map +1 -1
  18. package/ccw/src/commands/issue.ts +2 -1
  19. package/ccw/src/templates/dashboard-css/33-cli-stream-viewer.css +580 -467
  20. package/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js +532 -461
  21. package/ccw/src/templates/dashboard-js/components/notifications.js +774 -774
  22. package/ccw/src/templates/dashboard-js/i18n.js +4 -0
  23. package/ccw/src/templates/dashboard.html +10 -0
  24. package/ccw/src/tools/claude-cli-tools.ts +388 -388
  25. package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
  26. package/codex-lens/src/codexlens/config.py +19 -3
  27. package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
  28. package/codex-lens/src/codexlens/search/ranking.py +15 -4
  29. package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
  30. package/codex-lens/src/codexlens/semantic/vector_store.py +57 -47
  31. package/codex-lens/src/codexlens/storage/__pycache__/registry.cpython-313.pyc +0 -0
  32. package/codex-lens/src/codexlens/storage/registry.py +114 -101
  33. package/package.json +83 -83
@@ -0,0 +1,584 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Docsify-Style HTML Manual Assembly Script Template
4
+ Generates interactive single-file documentation with hierarchical navigation
5
+
6
+ Usage:
7
+ 1. Copy this script to your manual output directory
8
+ 2. Customize MANUAL_META and NAV_STRUCTURE
9
+ 3. Run: python assemble_docsify.py
10
+ """
11
+
12
+ import json
13
+ import base64
14
+ import re
15
+ from pathlib import Path
16
+ from typing import Dict, List, Any
17
+
18
+ # Try to import markdown library
19
+ try:
20
+ import markdown
21
+ from markdown.extensions.codehilite import CodeHiliteExtension
22
+ from markdown.extensions.fenced_code import FencedCodeExtension
23
+ from markdown.extensions.tables import TableExtension
24
+ from markdown.extensions.toc import TocExtension
25
+ HAS_MARKDOWN = True
26
+ except ImportError:
27
+ HAS_MARKDOWN = False
28
+ print("Warning: markdown library not found. Install with: pip install markdown pygments")
29
+
30
+
31
+ # ============================================================
32
+ # CONFIGURATION - Customize these for your project
33
+ # ============================================================
34
+
35
+ # Paths - Update these paths for your environment
36
+ BASE_DIR = Path(__file__).parent
37
+ SECTIONS_DIR = BASE_DIR / "sections"
38
+ SCREENSHOTS_DIR = BASE_DIR / "screenshots"
39
+
40
+ # Template paths - Point to skill templates directory
41
+ SKILL_DIR = Path(__file__).parent.parent # Adjust based on where script is placed
42
+ TEMPLATE_FILE = SKILL_DIR / "templates" / "docsify-shell.html"
43
+ CSS_BASE_FILE = SKILL_DIR / "templates" / "css" / "docsify-base.css"
44
+
45
+ # Manual metadata - Customize for your software
46
+ MANUAL_META = {
47
+ "title": "Your Software",
48
+ "subtitle": "使用手册",
49
+ "version": "v1.0.0",
50
+ "timestamp": "2025-01-01",
51
+ "language": "zh-CN",
52
+ "logo_icon": "Y" # First letter or emoji
53
+ }
54
+
55
+ # Output file
56
+ OUTPUT_FILE = BASE_DIR / f"{MANUAL_META['title']}{MANUAL_META['subtitle']}.html"
57
+
58
+ # Hierarchical navigation structure
59
+ # Customize groups and items based on your sections
60
+ NAV_STRUCTURE = [
61
+ {
62
+ "type": "group",
63
+ "title": "入门指南",
64
+ "icon": "📚",
65
+ "expanded": True,
66
+ "items": [
67
+ {"id": "overview", "title": "产品概述", "file": "section-overview.md"},
68
+ ]
69
+ },
70
+ {
71
+ "type": "group",
72
+ "title": "使用教程",
73
+ "icon": "🎯",
74
+ "expanded": False,
75
+ "items": [
76
+ {"id": "ui-guide", "title": "UI操作指南", "file": "section-ui-guide.md"},
77
+ ]
78
+ },
79
+ {
80
+ "type": "group",
81
+ "title": "API参考",
82
+ "icon": "🔧",
83
+ "expanded": False,
84
+ "items": [
85
+ {"id": "api-reference", "title": "API文档", "file": "section-api-reference.md"},
86
+ ]
87
+ },
88
+ {
89
+ "type": "group",
90
+ "title": "配置与部署",
91
+ "icon": "⚙️",
92
+ "expanded": False,
93
+ "items": [
94
+ {"id": "configuration", "title": "配置指南", "file": "section-configuration.md"},
95
+ ]
96
+ },
97
+ {
98
+ "type": "group",
99
+ "title": "帮助与支持",
100
+ "icon": "💡",
101
+ "expanded": False,
102
+ "items": [
103
+ {"id": "troubleshooting", "title": "故障排除", "file": "section-troubleshooting.md"},
104
+ {"id": "examples", "title": "代码示例", "file": "section-examples.md"},
105
+ ]
106
+ }
107
+ ]
108
+
109
+ # Screenshot ID to filename mapping - Customize for your screenshots
110
+ SCREENSHOT_MAPPING = {
111
+ # "截图ID": "filename.png",
112
+ }
113
+
114
+
115
+ # ============================================================
116
+ # CORE FUNCTIONS - Generally don't need to modify
117
+ # ============================================================
118
+
119
+ # Global cache for embedded images
120
+ _embedded_images = {}
121
+
122
+
123
+ def read_file(filepath: Path) -> str:
124
+ """Read file content with UTF-8 encoding"""
125
+ return filepath.read_text(encoding='utf-8')
126
+
127
+
128
+ # ============================================================
129
+ # MERMAID VALIDATION
130
+ # ============================================================
131
+
132
+ # Valid Mermaid diagram types
133
+ MERMAID_DIAGRAM_TYPES = [
134
+ 'graph', 'flowchart', 'sequenceDiagram', 'classDiagram',
135
+ 'stateDiagram', 'stateDiagram-v2', 'erDiagram', 'journey',
136
+ 'gantt', 'pie', 'quadrantChart', 'requirementDiagram',
137
+ 'gitGraph', 'mindmap', 'timeline', 'zenuml', 'sankey-beta',
138
+ 'xychart-beta', 'block-beta'
139
+ ]
140
+
141
+ # Common Mermaid syntax patterns
142
+ MERMAID_PATTERNS = {
143
+ 'graph': r'^graph\s+(TB|BT|LR|RL|TD)\s*$',
144
+ 'flowchart': r'^flowchart\s+(TB|BT|LR|RL|TD)\s*$',
145
+ 'sequenceDiagram': r'^sequenceDiagram\s*$',
146
+ 'classDiagram': r'^classDiagram\s*$',
147
+ 'stateDiagram': r'^stateDiagram(-v2)?\s*$',
148
+ 'erDiagram': r'^erDiagram\s*$',
149
+ 'gantt': r'^gantt\s*$',
150
+ 'pie': r'^pie\s*(showData|title\s+.*)?\s*$',
151
+ 'journey': r'^journey\s*$',
152
+ }
153
+
154
+
155
+ class MermaidBlock:
156
+ """Represents a mermaid code block found in markdown"""
157
+ def __init__(self, content: str, file: str, line_num: int, indented: bool = False):
158
+ self.content = content
159
+ self.file = file
160
+ self.line_num = line_num
161
+ self.indented = indented
162
+ self.errors: List[str] = []
163
+ self.warnings: List[str] = []
164
+ self.diagram_type: str = None
165
+
166
+ def __repr__(self):
167
+ return f"MermaidBlock({self.diagram_type}, {self.file}:{self.line_num})"
168
+
169
+
170
+ def extract_mermaid_blocks(markdown_text: str, filename: str) -> List[MermaidBlock]:
171
+ """Extract all mermaid code blocks from markdown text"""
172
+ blocks = []
173
+
174
+ # More flexible pattern - matches opening fence with optional indent,
175
+ # then captures content until closing fence (with any indent)
176
+ pattern = r'^(\s*)(```|~~~)mermaid\s*\n(.*?)\n\s*\2\s*$'
177
+
178
+ for match in re.finditer(pattern, markdown_text, re.MULTILINE | re.DOTALL):
179
+ indent = match.group(1)
180
+ content = match.group(3)
181
+ # Calculate line number
182
+ line_num = markdown_text[:match.start()].count('\n') + 1
183
+ indented = len(indent) > 0
184
+
185
+ block = MermaidBlock(
186
+ content=content,
187
+ file=filename,
188
+ line_num=line_num,
189
+ indented=indented
190
+ )
191
+ blocks.append(block)
192
+
193
+ return blocks
194
+
195
+
196
+ def validate_mermaid_block(block: MermaidBlock) -> bool:
197
+ """Validate a mermaid block and populate errors/warnings"""
198
+ content = block.content.strip()
199
+ lines = content.split('\n')
200
+
201
+ if not lines:
202
+ block.errors.append("Empty mermaid block")
203
+ return False
204
+
205
+ first_line = lines[0].strip()
206
+
207
+ # Detect diagram type
208
+ for dtype in MERMAID_DIAGRAM_TYPES:
209
+ if first_line.startswith(dtype):
210
+ block.diagram_type = dtype
211
+ break
212
+
213
+ if not block.diagram_type:
214
+ block.errors.append(f"Unknown diagram type: '{first_line[:30]}...'")
215
+ block.errors.append(f"Valid types: {', '.join(MERMAID_DIAGRAM_TYPES[:8])}...")
216
+ return False
217
+
218
+ # Check for balanced brackets/braces
219
+ brackets = {'[': ']', '{': '}', '(': ')'}
220
+ stack = []
221
+ for i, char in enumerate(content):
222
+ if char in brackets:
223
+ stack.append((char, i))
224
+ elif char in brackets.values():
225
+ if not stack:
226
+ block.errors.append(f"Unmatched closing bracket '{char}' at position {i}")
227
+ else:
228
+ open_char, _ = stack.pop()
229
+ if brackets[open_char] != char:
230
+ block.errors.append(f"Mismatched brackets: '{open_char}' and '{char}'")
231
+
232
+ if stack:
233
+ for open_char, pos in stack:
234
+ block.warnings.append(f"Unclosed bracket '{open_char}' at position {pos}")
235
+
236
+ # Check for common graph/flowchart issues
237
+ if block.diagram_type in ['graph', 'flowchart']:
238
+ # Check direction specifier
239
+ if not re.match(r'^(graph|flowchart)\s+(TB|BT|LR|RL|TD)', first_line):
240
+ block.warnings.append("Missing or invalid direction (TB/BT/LR/RL/TD)")
241
+
242
+ # Check for arrow syntax
243
+ arrow_count = content.count('-->') + content.count('---') + content.count('-.->') + content.count('==>')
244
+ if arrow_count == 0 and len(lines) > 1:
245
+ block.warnings.append("No arrows found - graph may be incomplete")
246
+
247
+ # Check for sequenceDiagram issues
248
+ if block.diagram_type == 'sequenceDiagram':
249
+ if '->' not in content and '->>' not in content:
250
+ block.warnings.append("No message arrows found in sequence diagram")
251
+
252
+ # Indentation warning
253
+ if block.indented:
254
+ block.warnings.append("Indented code block - may not render in some markdown parsers")
255
+
256
+ return len(block.errors) == 0
257
+
258
+
259
+ def validate_all_mermaid(nav_structure: List[Dict], sections_dir: Path) -> Dict[str, Any]:
260
+ """Validate all mermaid blocks in all section files"""
261
+ report = {
262
+ 'total_blocks': 0,
263
+ 'valid_blocks': 0,
264
+ 'error_blocks': 0,
265
+ 'warning_blocks': 0,
266
+ 'blocks': [],
267
+ 'by_file': {},
268
+ 'by_type': {}
269
+ }
270
+
271
+ for group in nav_structure:
272
+ for item in group.get("items", []):
273
+ section_file = item.get("file")
274
+ if not section_file:
275
+ continue
276
+
277
+ filepath = sections_dir / section_file
278
+ if not filepath.exists():
279
+ continue
280
+
281
+ content = read_file(filepath)
282
+ blocks = extract_mermaid_blocks(content, section_file)
283
+
284
+ file_report = {'blocks': [], 'errors': 0, 'warnings': 0}
285
+
286
+ for block in blocks:
287
+ report['total_blocks'] += 1
288
+ is_valid = validate_mermaid_block(block)
289
+
290
+ if is_valid:
291
+ report['valid_blocks'] += 1
292
+ else:
293
+ report['error_blocks'] += 1
294
+ file_report['errors'] += 1
295
+
296
+ if block.warnings:
297
+ report['warning_blocks'] += 1
298
+ file_report['warnings'] += len(block.warnings)
299
+
300
+ # Track by diagram type
301
+ if block.diagram_type:
302
+ if block.diagram_type not in report['by_type']:
303
+ report['by_type'][block.diagram_type] = 0
304
+ report['by_type'][block.diagram_type] += 1
305
+
306
+ report['blocks'].append(block)
307
+ file_report['blocks'].append(block)
308
+
309
+ if blocks:
310
+ report['by_file'][section_file] = file_report
311
+
312
+ return report
313
+
314
+
315
+ def print_mermaid_report(report: Dict[str, Any]) -> None:
316
+ """Print mermaid validation report"""
317
+ print("\n" + "=" * 60)
318
+ print("MERMAID DIAGRAM VALIDATION REPORT")
319
+ print("=" * 60)
320
+
321
+ print(f"\nSummary:")
322
+ print(f" Total blocks: {report['total_blocks']}")
323
+ print(f" Valid: {report['valid_blocks']}")
324
+ print(f" With errors: {report['error_blocks']}")
325
+ print(f" With warnings: {report['warning_blocks']}")
326
+
327
+ if report['by_type']:
328
+ print(f"\nDiagram Types:")
329
+ for dtype, count in sorted(report['by_type'].items()):
330
+ print(f" {dtype}: {count}")
331
+
332
+ # Print errors and warnings
333
+ has_issues = False
334
+ for block in report['blocks']:
335
+ if block.errors or block.warnings:
336
+ if not has_issues:
337
+ print(f"\nIssues Found:")
338
+ has_issues = True
339
+
340
+ print(f"\n [{block.file}:{block.line_num}] {block.diagram_type or 'unknown'}")
341
+ for error in block.errors:
342
+ print(f" [ERROR] {error}")
343
+ for warning in block.warnings:
344
+ print(f" [WARN] {warning}")
345
+
346
+ if not has_issues:
347
+ print(f"\n No issues found!")
348
+
349
+ print("=" * 60 + "\n")
350
+
351
+
352
+ def convert_md_to_html(markdown_text: str) -> str:
353
+ """Convert Markdown to HTML with syntax highlighting"""
354
+ if not HAS_MARKDOWN:
355
+ # Fallback: just escape HTML and wrap in pre
356
+ escaped = markdown_text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
357
+ return f'<pre>{escaped}</pre>'
358
+
359
+ md = markdown.Markdown(
360
+ extensions=[
361
+ FencedCodeExtension(),
362
+ TableExtension(),
363
+ TocExtension(toc_depth=3),
364
+ CodeHiliteExtension(
365
+ css_class='highlight',
366
+ linenums=False,
367
+ guess_lang=True,
368
+ use_pygments=True
369
+ ),
370
+ ],
371
+ output_format='html5'
372
+ )
373
+ html = md.convert(markdown_text)
374
+ md.reset()
375
+ return html
376
+
377
+
378
+ def embed_screenshot_base64(screenshot_id: str) -> str:
379
+ """Embed screenshot as base64, using cache to avoid duplicates"""
380
+ global _embedded_images
381
+
382
+ filename = SCREENSHOT_MAPPING.get(screenshot_id)
383
+
384
+ if not filename:
385
+ return f'<div class="screenshot-placeholder">📷 {screenshot_id}</div>'
386
+
387
+ filepath = SCREENSHOTS_DIR / filename
388
+
389
+ if not filepath.exists():
390
+ return f'<div class="screenshot-placeholder">📷 {screenshot_id}</div>'
391
+
392
+ # Check cache
393
+ if filename not in _embedded_images:
394
+ try:
395
+ with open(filepath, 'rb') as f:
396
+ image_data = base64.b64encode(f.read()).decode('utf-8')
397
+ ext = filepath.suffix[1:].lower()
398
+ _embedded_images[filename] = f"data:image/{ext};base64,{image_data}"
399
+ except Exception as e:
400
+ return f'<div class="screenshot-placeholder">📷 {screenshot_id} (加载失败)</div>'
401
+
402
+ return f'''<figure class="screenshot">
403
+ <img src="{_embedded_images[filename]}" alt="{screenshot_id}" loading="lazy" />
404
+ <figcaption>{screenshot_id}</figcaption>
405
+ </figure>'''
406
+
407
+
408
+ def process_markdown_screenshots(markdown_text: str) -> str:
409
+ """Replace [[screenshot:xxx]] placeholders with embedded images"""
410
+ pattern = r'\[\[screenshot:(.*?)\]\]'
411
+
412
+ def replacer(match):
413
+ screenshot_id = match.group(1)
414
+ return embed_screenshot_base64(screenshot_id)
415
+
416
+ return re.sub(pattern, replacer, markdown_text)
417
+
418
+
419
+ def generate_sidebar_nav_html(nav_structure: List[Dict]) -> str:
420
+ """Generate hierarchical sidebar navigation HTML"""
421
+ html_parts = []
422
+
423
+ for group in nav_structure:
424
+ if group["type"] == "group":
425
+ expanded_class = "expanded" if group.get("expanded", False) else ""
426
+ html_parts.append(f'''
427
+ <div class="nav-group {expanded_class}">
428
+ <div class="nav-group-header">
429
+ <button class="nav-group-toggle" aria-expanded="{str(group.get('expanded', False)).lower()}">
430
+ <svg viewBox="0 0 24 24"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6z" fill="currentColor"/></svg>
431
+ </button>
432
+ <span class="nav-group-title">{group.get('icon', '')} {group['title']}</span>
433
+ </div>
434
+ <div class="nav-group-items">''')
435
+
436
+ for item in group.get("items", []):
437
+ html_parts.append(f'''
438
+ <a class="nav-item" href="#/{item['id']}" data-section="{item['id']}">{item['title']}</a>''')
439
+
440
+ html_parts.append('''
441
+ </div>
442
+ </div>''')
443
+
444
+ return '\n'.join(html_parts)
445
+
446
+
447
+ def generate_sections_html(nav_structure: List[Dict]) -> str:
448
+ """Generate content sections HTML"""
449
+ sections_html = []
450
+
451
+ for group in nav_structure:
452
+ for item in group.get("items", []):
453
+ section_id = item["id"]
454
+ section_title = item["title"]
455
+ section_file = item.get("file")
456
+
457
+ if not section_file:
458
+ continue
459
+
460
+ filepath = SECTIONS_DIR / section_file
461
+ if not filepath.exists():
462
+ print(f"Warning: Section file not found: {filepath}")
463
+ continue
464
+
465
+ # Read and convert markdown
466
+ markdown_content = read_file(filepath)
467
+ markdown_content = process_markdown_screenshots(markdown_content)
468
+ html_content = convert_md_to_html(markdown_content)
469
+
470
+ sections_html.append(f'''
471
+ <section class="content-section" id="section-{section_id}" data-title="{section_title}">
472
+ {html_content}
473
+ </section>''')
474
+
475
+ return '\n'.join(sections_html)
476
+
477
+
478
+ def generate_search_index(nav_structure: List[Dict]) -> str:
479
+ """Generate search index JSON"""
480
+ search_index = {}
481
+
482
+ for group in nav_structure:
483
+ for item in group.get("items", []):
484
+ section_id = item["id"]
485
+ section_file = item.get("file")
486
+
487
+ if not section_file:
488
+ continue
489
+
490
+ filepath = SECTIONS_DIR / section_file
491
+ if filepath.exists():
492
+ content = read_file(filepath)
493
+ clean_content = re.sub(r'[#*`\[\]()]', '', content)
494
+ clean_content = re.sub(r'\s+', ' ', clean_content)[:1500]
495
+
496
+ search_index[section_id] = {
497
+ "title": item["title"],
498
+ "body": clean_content,
499
+ "group": group["title"]
500
+ }
501
+
502
+ return json.dumps(search_index, ensure_ascii=False, indent=2)
503
+
504
+
505
+ def generate_nav_structure_json(nav_structure: List[Dict]) -> str:
506
+ """Generate navigation structure JSON for client-side"""
507
+ return json.dumps(nav_structure, ensure_ascii=False, indent=2)
508
+
509
+
510
+ def assemble_manual(validate_mermaid: bool = True):
511
+ """Main assembly function
512
+
513
+ Args:
514
+ validate_mermaid: Whether to validate mermaid diagrams (default: True)
515
+ """
516
+ global _embedded_images
517
+ _embedded_images = {}
518
+
519
+ full_title = f"{MANUAL_META['title']} {MANUAL_META['subtitle']}"
520
+ print(f"Assembling Docsify-style manual: {full_title}")
521
+
522
+ # Verify template exists
523
+ if not TEMPLATE_FILE.exists():
524
+ print(f"Error: Template not found at {TEMPLATE_FILE}")
525
+ print("Please update TEMPLATE_FILE path in this script.")
526
+ return None, 0
527
+
528
+ if not CSS_BASE_FILE.exists():
529
+ print(f"Error: CSS not found at {CSS_BASE_FILE}")
530
+ print("Please update CSS_BASE_FILE path in this script.")
531
+ return None, 0
532
+
533
+ # Validate Mermaid diagrams
534
+ mermaid_report = None
535
+ if validate_mermaid:
536
+ print("\nValidating Mermaid diagrams...")
537
+ mermaid_report = validate_all_mermaid(NAV_STRUCTURE, SECTIONS_DIR)
538
+ print_mermaid_report(mermaid_report)
539
+
540
+ # Warn if there are errors (but continue)
541
+ if mermaid_report['error_blocks'] > 0:
542
+ print(f"[WARN] {mermaid_report['error_blocks']} mermaid block(s) have errors!")
543
+ print(" These diagrams may not render correctly.")
544
+
545
+ # Read template and CSS
546
+ template_html = read_file(TEMPLATE_FILE)
547
+ css_content = read_file(CSS_BASE_FILE)
548
+
549
+ # Generate components
550
+ sidebar_nav_html = generate_sidebar_nav_html(NAV_STRUCTURE)
551
+ sections_html = generate_sections_html(NAV_STRUCTURE)
552
+ search_index_json = generate_search_index(NAV_STRUCTURE)
553
+ nav_structure_json = generate_nav_structure_json(NAV_STRUCTURE)
554
+
555
+ # Replace placeholders
556
+ output_html = template_html
557
+ output_html = output_html.replace('{{SOFTWARE_NAME}}', full_title)
558
+ output_html = output_html.replace('{{VERSION}}', MANUAL_META['version'])
559
+ output_html = output_html.replace('{{TIMESTAMP}}', MANUAL_META['timestamp'])
560
+ output_html = output_html.replace('{{LOGO_ICON}}', MANUAL_META['logo_icon'])
561
+ output_html = output_html.replace('{{EMBEDDED_CSS}}', css_content)
562
+ output_html = output_html.replace('{{SIDEBAR_NAV_HTML}}', sidebar_nav_html)
563
+ output_html = output_html.replace('{{SECTIONS_HTML}}', sections_html)
564
+ output_html = output_html.replace('{{SEARCH_INDEX_JSON}}', search_index_json)
565
+ output_html = output_html.replace('{{NAV_STRUCTURE_JSON}}', nav_structure_json)
566
+
567
+ # Write output file
568
+ OUTPUT_FILE.write_text(output_html, encoding='utf-8')
569
+
570
+ file_size = OUTPUT_FILE.stat().st_size
571
+ file_size_mb = file_size / (1024 * 1024)
572
+ section_count = sum(len(g.get("items", [])) for g in NAV_STRUCTURE)
573
+
574
+ print("[OK] Docsify-style manual generated successfully!")
575
+ print(f" Output: {OUTPUT_FILE}")
576
+ print(f" Size: {file_size_mb:.2f} MB ({file_size:,} bytes)")
577
+ print(f" Navigation Groups: {len(NAV_STRUCTURE)}")
578
+ print(f" Sections: {section_count}")
579
+
580
+ return str(OUTPUT_FILE), file_size
581
+
582
+
583
+ if __name__ == "__main__":
584
+ output_path, size = assemble_manual()