#!/usr/bin/env python3 """Consolidate Nexus docs: move batches to docs/archive/ and generate merged volumes.""" from __future__ import annotations import shutil from datetime import date from pathlib import Path REPO = Path(__file__).resolve().parents[1] DOCS = REPO / "docs" ARCHIVE = DOCS / "archive" TODAY = date.today().isoformat() def ensure_dirs() -> None: for sub in ( "audits/2026-06-04-line-walk", "audits/2026-06-04-delta", "audits/2026-06-04-phase2", "audits/2026-06-04-frontend-14p", "memory/raw", "handoff", "project", "reports/2026-05-reviews", "reports/2026-05-steps", ): (ARCHIVE / sub).mkdir(parents=True, exist_ok=True) def extract_summary(text: str, max_lines: int = 12) -> str: lines: list[str] = [] for line in text.splitlines(): s = line.strip() if not s: continue if s.startswith("#"): lines.append(s) elif any(k in s for k in ("Closure", "DoD", "P0", "P1", "FINDING", "结论", "摘要")): lines.append(s[:200]) if len(lines) >= max_lines: break return "\n".join(lines) if lines else (text.splitlines()[0][:200] if text.splitlines() else "(empty)") def extract_closure_rows(text: str) -> list[str]: rows: list[str] = [] in_table = False for line in text.splitlines(): if "|" in line and ("Closure" in line or "H " in line or "FINDING" in line): in_table = True if in_table and line.strip().startswith("|") and "---" not in line: rows.append(line.strip()) if in_table and not line.strip() and rows: break return rows[:80] def merge_glob( pattern: str, out_name: str, title: str, archive_subdir: str, ) -> None: base = DOCS / "reports" files = sorted(base.glob(pattern)) if not files: print(f" skip merge (no files): {pattern}") return out_path = ARCHIVE / "audits" / out_name parts = [ f"# {title}", "", f"> 生成日期: {TODAY} · 源文件 {len(files)} 篇 · 原文 `docs/archive/{archive_subdir}/`", "", "## 批次索引", "", ] archive_dir = ARCHIVE / archive_subdir archive_dir.mkdir(parents=True, exist_ok=True) all_closure: list[str] = [] for f in files: dest = archive_dir / f.name if f.resolve() != dest.resolve(): if dest.exists(): dest.unlink() shutil.move(str(f), str(dest)) text = dest.read_text(encoding="utf-8", errors="replace") parts.append(f"### `{f.name}`") parts.append("") parts.append(extract_summary(text)) parts.append("") all_closure.extend(extract_closure_rows(text)) if all_closure: parts.extend(["## Closure 摘录(合并)", ""]) seen: set[str] = set() for row in all_closure: if row not in seen: seen.add(row) parts.append(row) parts.append("") out_path.write_text("\n".join(parts), encoding="utf-8") print(f" merged {len(files)} → {out_path.relative_to(REPO)}") def merge_memory() -> None: mem_dir = DOCS / "memory" if not mem_dir.is_dir(): return files = sorted(p for p in mem_dir.glob("*.md") if p.name != "MEMORY.md") if not files: return raw_dir = ARCHIVE / "memory/raw" raw_dir.mkdir(parents=True, exist_ok=True) parts = [ "# Agent Memory 合并卷(2026-05 迁移)", "", f"> 生成: {TODAY} · 原 docs/memory/ {len(files)} 篇", "", "请以 [nexus-functional-development-guide.md](../project/nexus-functional-development-guide.md) 为准。", "", ] for f in files: text = f.read_text(encoding="utf-8", errors="replace") dest = raw_dir / f.name if f.resolve() != dest.resolve(): shutil.move(str(f), str(dest)) parts.extend([f"## {f.stem}", "", extract_summary(text, 8), ""]) out = ARCHIVE / "memory/agentmemory-2026-05-merged.md" out.write_text("\n".join(parts), encoding="utf-8") (mem_dir / "MEMORY.md").write_text( "# 项目记忆(已归档)\n\n" "见 [agentmemory-2026-05-merged.md](../archive/memory/agentmemory-2026-05-merged.md)\n" "与 [功能开发指南](../project/nexus-functional-development-guide.md)。\n", encoding="utf-8", ) print(f" memory merged → {out.relative_to(REPO)}") def archive_handoffs() -> None: for name in ("handoff-2026-05-31.md", "handoff-2026-06-01.md"): src = DOCS / name if src.is_file(): shutil.move(str(src), str(ARCHIVE / "handoff" / name)) for f in sorted((DOCS / "project").glob("AI-HANDOFF-2026-05*.md")): shutil.move(str(f), str(ARCHIVE / "handoff" / f.name)) old = DOCS / "project" / "AI-HANDOFF-2026-06-01-files.md" if old.is_file(): shutil.move(str(old), str(ARCHIVE / "handoff" / old.name)) print(" handoffs archived") def archive_nexus_full_site_features() -> None: src = DOCS / "project" / "nexus-full-site-features.md" if not src.is_file(): return shutil.move(str(src), str(ARCHIVE / "project" / src.name)) (DOCS / "project" / "nexus-full-site-features.md").write_text( "# 全站功能清单(已 supersede)\n\n" "请以 **[nexus-functional-development-guide.md](nexus-functional-development-guide.md)** 为准。\n\n" "历史 HTML 版: [archive](../archive/project/nexus-full-site-features.md)\n", encoding="utf-8", ) print(" nexus-full-site-features → archive + stub") def archive_old_line_walk_standard() -> None: v1 = DOCS / "project" / "line-walk-audit-standard.md" if v1.is_file(): shutil.move(str(v1), str(ARCHIVE / "project" / v1.name)) print(" line-walk v1 archived") def archive_phase2_reviews() -> None: dest = ARCHIVE / "reports/2026-05-reviews" patterns = ( "review-*-2026-05-21.md", "signoff-2026-05-21.md", "session-log-2026-05-21.md", "2026-05-23-full-audit-report.md", "audit-full-vs-phase2-reconciliation.md", "audit-phase-2*.md", "audit-phase-3*.md", "implementation-progress-2026-05-21.md", ) n = 0 reports = DOCS / "reports" for pat in patterns: for g in reports.glob(pat): if g.is_file(): t = dest / g.name if t.exists(): t.unlink() shutil.move(str(g), str(t)) n += 1 step_dir = ARCHIVE / "reports/2026-05-steps" for f in reports.glob("step-*"): if f.is_dir(): target = step_dir / f.name if target.exists(): shutil.rmtree(target) shutil.move(str(f), str(target)) n += 1 if n: print(f" archived {n} legacy review/step items") def write_archive_readme() -> None: (ARCHIVE / "README.md").write_text( f"""# Nexus 文档归档区 > 更新: {TODAY} 历史审计、旧 handoff、agentmemory。**日常请先读** [docs/README.md](../README.md)。 ## 合并卷 | 文件 | 内容 | |------|------| | [audits/2026-06-04-line-walk-merged.md](audits/2026-06-04-line-walk-merged.md) | Line-walk batch | | [audits/2026-06-04-delta-merged.md](audits/2026-06-04-delta-merged.md) | Delta D1–D8 | | [audits/2026-06-04-phase2-merged.md](audits/2026-06-04-phase2-merged.md) | Phase2 P2 | | [audits/2026-06-04-frontend-14p-merged.md](audits/2026-06-04-frontend-14p-merged.md) | 前端 14p | | [memory/agentmemory-2026-05-merged.md](memory/agentmemory-2026-05-merged.md) | 旧 memory | 原文在对应子目录;changelog 不归档。 """, encoding="utf-8", ) def main() -> None: print("=== Nexus docs consolidation ===") ensure_dirs() merge_glob( "audit-bug-line-walk-2026-06-04-batch*.md", "2026-06-04-line-walk-merged.md", "Bug Scan Line-Walk 合并卷", "audits/2026-06-04-line-walk", ) merge_glob( "audit-delta-2026-06-04-waveD*.md", "2026-06-04-delta-merged.md", "Delta D1–D8 合并卷", "audits/2026-06-04-delta", ) merge_glob( "audit-phase2-2026-06-04-waveP2-*.md", "2026-06-04-phase2-merged.md", "Phase2 P2 合并卷", "audits/2026-06-04-phase2", ) merge_glob( "audit-frontend-14p-2026-06-04-batch*.md", "2026-06-04-frontend-14p-merged.md", "前端 14p batch 合并卷", "audits/2026-06-04-frontend-14p", ) merge_memory() archive_handoffs() archive_nexus_full_site_features() archive_old_line_walk_standard() archive_phase2_reviews() write_archive_readme() print("=== done ===") if __name__ == "__main__": main()