274 lines
8.7 KiB
Python
274 lines
8.7 KiB
Python
|
|
#!/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()
|