Spaces:
Sleeping
Sleeping
File size: 8,983 Bytes
a847ac0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | #!/usr/bin/env python3
"""
Context & Codebase Dumper for VHectorLab 3D.
Generates vhectorlab-context.txt containing full context, architecture specifications,
developer protocol lessons, configuration, and source code for AI assistant analysis.
Optimized to produce a compact context payload (~100k-180k tokens) suitable for LLM context windows.
"""
import os
import sys
import argparse
from datetime import datetime
from pathlib import Path
# Included file extensions for general source code scanning
SOURCE_EXTENSIONS = {
".py", ".js", ".ts", ".jsx", ".tsx", ".css", ".html", ".glsl",
".json", ".toml", ".yml", ".yaml", ".md", ".sh", ".mjs"
}
# Binary file extensions to strictly exclude
BINARY_EXTENSIONS = {
".png", ".jpg", ".jpeg", ".gif", ".ico", ".pdf", ".zip", ".tar", ".gz",
".npz", ".pt", ".mp4", ".webm", ".mov", ".ttf", ".woff", ".woff2", ".eot"
}
# Directories to strictly exclude from code scan
EXCLUDE_DIRS = {
".git", "node_modules", ".venv", "venv", "dist", ".pnpm-store",
".pytest_cache", "__pycache__", ".vite", "htmlcov", ".DS_Store", "demo", "archivo",
".agents", "skills", ".claude", ".gemini"
}
# Files to explicitly exclude from code scan
EXCLUDE_FILES = {
"vhectorlab-context.txt", "vhectorlab_dev.log", "vectorlab_dev.log",
"public/vocab.txt", "vocab.txt", "vocab_embeddings.npz", "pnpm-lock.yaml", "package-lock.json",
"CONTEXT_HAB.MD", "scripts/capture-galaxy-demo.mjs", "capture-galaxy-demo.mjs",
"diagnose_group_separation.py"
}
# Explicitly ordered root context & specification files (placed at top of dump)
PRIORITY_DOCS = [
"manifest.json",
"README.md",
"CONTEXT.md",
"architecture_spec.md",
"CHANGELOG.md",
"roadmap/README.md",
"roadmap/gui-art.md",
"roadmap/sae-denoise.md",
]
def is_binary_file(filepath: Path) -> bool:
"""Check if file is binary by checking extensions or initial bytes."""
if filepath.suffix.lower() in BINARY_EXTENSIONS:
return True
try:
with open(filepath, "rb") as f:
chunk = f.read(1024)
if b"\x00" in chunk:
return True
except Exception:
return True
return False
def collect_project_files(root_dir: Path, include_tests: bool = False):
"""Collect relevant project files grouped by relative path."""
collected = []
for dirpath, dirnames, filenames in os.walk(root_dir):
# Filter directories in-place
dirnames[:] = [
d for d in dirnames
if d not in EXCLUDE_DIRS and not d.startswith(".venv") and (include_tests or d != "tests")
]
rel_dir = Path(dirpath).relative_to(root_dir)
# Skip tests directory unless explicitly included
if not include_tests:
if "tests" in rel_dir.parts or "backend/tests" in str(rel_dir):
continue
for filename in sorted(filenames):
rel_file = rel_dir / filename if str(rel_dir) != "." else Path(filename)
rel_path_str = str(rel_file)
# Check exclusions
if filename in EXCLUDE_FILES or rel_path_str in EXCLUDE_FILES:
continue
if filename.startswith(".") and filename != ".gitignore" and filename != ".env.example":
continue
if filename.startswith("PROMPT-"):
continue
if filename.endswith(".log") or filename.endswith(".npz") or filename.endswith(".pt"):
continue
if not include_tests and (filename.endswith(".test.js") or filename.startswith("test_")):
continue
filepath = root_dir / rel_file
if filepath.suffix.lower() not in SOURCE_EXTENSIONS and filename not in {"Dockerfile", "LICENSE", "NOTICE", "setup.sh"}:
continue
if is_binary_file(filepath):
continue
collected.append(rel_file)
# Sort files logically: priority docs first, then alphabetical by relative path
def file_sort_key(p: Path):
p_str = str(p)
if p_str in PRIORITY_DOCS:
return (0, PRIORITY_DOCS.index(p_str))
return (1, p_str)
collected.sort(key=file_sort_key)
return collected
def generate_context_dump(root_dir: Path, output_path: Path, include_tests: bool = False, max_changelog_lines: int = 150):
"""Generate the complete context and codebase dump txt file."""
if output_path.exists():
print(f"Removing existing dump file: {output_path}")
output_path.unlink()
project_files = collect_project_files(root_dir, include_tests=include_tests)
timestamp = datetime.now().isoformat()
print(f"Gathering {len(project_files)} project files into {output_path.name} (include_tests={include_tests})...")
total_lines = 0
file_count = 0
with open(output_path, "w", encoding="utf-8") as out:
# Header block
out.write("================================================================================\n")
out.write("VHECTORLAB 3D - COMPLETE CODEBASE & CONTEXT DUMP FOR AI AGENT ANALYSIS\n")
out.write(f"Generated: {timestamp}\n")
out.write(f"Repository Root: {root_dir.resolve()}\n")
out.write(f"Total Source Files Included: {len(project_files)}\n")
out.write(f"Include Tests: {include_tests}\n")
out.write("================================================================================\n\n")
# Project Summary / Description
out.write("--- 1. PROJECT OVERVIEW & ARCHITECTURE SUMMARY ---\n")
out.write("VHectorLab 3D is a WebGL vector space visualizer & LLM embedding analytics workspace.\n")
out.write("- Backend: FastAPI (Python 3.11+, `uv`), SentenceTransformers, PyTorch, SAE (Sparse Autoencoders).\n")
out.write("- Frontend: Vanilla JS + Three.js WebGL (Vite, npm).\n")
out.write("- Modes: ARITHMETIC (vector algebra) & COMPARE (cosine similarity & multi-token comparison).\n")
out.write("- Views: ANALYSIS (vertical stack), NAVIGATION (3D corridor), GALAXY (2D/3D UMAP manifold).\n")
out.write("- Render Modes: POINTS & RIBBONS (wide quad strips).\n")
out.write("- Quality & Protocol: Managed via `.agents/skills/dev-protocol/` (TDD, deep modules, lessons-learned).\n\n")
# Directory Tree Section
out.write("--- 2. REPOSITORY FILE TREE ---\n")
for pf in project_files:
out.write(f"- {pf}\n")
out.write("\n")
# Detailed Files Content Section
out.write("--- 3. DETAILED SOURCE CODE & CONTEXT FILES ---\n\n")
for pf in project_files:
full_path = root_dir / pf
try:
content = full_path.read_text(encoding="utf-8", errors="replace")
lines = content.splitlines()
# Compact long CHANGELOG.md if needed
if pf.name == "CHANGELOG.md" and len(lines) > max_changelog_lines:
lines = lines[:max_changelog_lines] + [f"\n[... Truncated older changelog history ({len(lines) - max_changelog_lines} lines omitted) ...]"]
content = "\n".join(lines)
num_lines = len(lines)
total_lines += num_lines
file_count += 1
out.write("================================================================================\n")
out.write(f"FILE: {pf} ({num_lines} lines)\n")
out.write("================================================================================\n")
out.write(content)
if not content.endswith("\n"):
out.write("\n")
out.write("\n")
except Exception as e:
out.write(f"ERROR: Could not read file {pf}: {e}\n\n")
file_size_mb = output_path.stat().st_size / (1024 * 1024)
estimated_tokens = total_lines * 8 # approximate ~8-9 tokens per line of code/markdown
print(f"✓ Context dump successfully created at {output_path}")
print(f" - Files: {file_count}")
print(f" - Total Lines: {total_lines}")
print(f" - File Size: {file_size_mb:.2f} MB (~{estimated_tokens // 1000}k tokens)")
return output_path
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Dump codebase context to txt for AI analysis")
parser.add_argument("--output", type=str, default="vhectorlab-context.txt", help="Output file path")
parser.add_argument("--include-tests", action="store_true", help="Include test files in the context dump")
args = parser.parse_args()
root_path = Path(__file__).resolve().parent.parent
out_file = Path(args.output)
if not out_file.is_absolute():
out_file = root_path / out_file
generate_context_dump(root_path, out_file, include_tests=args.include_tests)
|