Claude Code Memory Compiler
This commit is contained in:
commit
f83d38d787
15 changed files with 2819 additions and 0 deletions
133
scripts/utils.py
Normal file
133
scripts/utils.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
"""Shared utilities for the personal knowledge base."""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from config import (
|
||||
CONCEPTS_DIR,
|
||||
CONNECTIONS_DIR,
|
||||
DAILY_DIR,
|
||||
INDEX_FILE,
|
||||
KNOWLEDGE_DIR,
|
||||
LOG_FILE,
|
||||
QA_DIR,
|
||||
STATE_FILE,
|
||||
)
|
||||
|
||||
|
||||
# ── State management ──────────────────────────────────────────────────
|
||||
|
||||
def load_state() -> dict:
|
||||
"""Load persistent state from state.json."""
|
||||
if STATE_FILE.exists():
|
||||
return json.loads(STATE_FILE.read_text(encoding="utf-8"))
|
||||
return {"ingested": {}, "query_count": 0, "last_lint": None, "total_cost": 0.0}
|
||||
|
||||
|
||||
def save_state(state: dict) -> None:
|
||||
"""Save state to state.json."""
|
||||
STATE_FILE.write_text(json.dumps(state, indent=2), encoding="utf-8")
|
||||
|
||||
|
||||
# ── File hashing ──────────────────────────────────────────────────────
|
||||
|
||||
def file_hash(path: Path) -> str:
|
||||
"""SHA-256 hash of a file (first 16 hex chars)."""
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()[:16]
|
||||
|
||||
|
||||
# ── Slug / naming ─────────────────────────────────────────────────────
|
||||
|
||||
def slugify(text: str) -> str:
|
||||
"""Convert text to a filename-safe slug."""
|
||||
text = text.lower().strip()
|
||||
text = re.sub(r"[^\w\s-]", "", text)
|
||||
text = re.sub(r"[\s_]+", "-", text)
|
||||
text = re.sub(r"-+", "-", text)
|
||||
return text.strip("-")
|
||||
|
||||
|
||||
# ── Wikilink helpers ──────────────────────────────────────────────────
|
||||
|
||||
def extract_wikilinks(content: str) -> list[str]:
|
||||
"""Extract all [[wikilinks]] from markdown content."""
|
||||
return re.findall(r"\[\[([^\]]+)\]\]", content)
|
||||
|
||||
|
||||
def wiki_article_exists(link: str) -> bool:
|
||||
"""Check if a wikilinked article exists on disk."""
|
||||
path = KNOWLEDGE_DIR / f"{link}.md"
|
||||
return path.exists()
|
||||
|
||||
|
||||
# ── Wiki content helpers ──────────────────────────────────────────────
|
||||
|
||||
def read_wiki_index() -> str:
|
||||
"""Read the knowledge base index file."""
|
||||
if INDEX_FILE.exists():
|
||||
return INDEX_FILE.read_text(encoding="utf-8")
|
||||
return "# Knowledge Base Index\n\n| Article | Summary | Compiled From | Updated |\n|---------|---------|---------------|---------|"
|
||||
|
||||
|
||||
def read_all_wiki_content() -> str:
|
||||
"""Read index + all wiki articles into a single string for context."""
|
||||
parts = [f"## INDEX\n\n{read_wiki_index()}"]
|
||||
|
||||
for subdir in [CONCEPTS_DIR, CONNECTIONS_DIR, QA_DIR]:
|
||||
if not subdir.exists():
|
||||
continue
|
||||
for md_file in sorted(subdir.glob("*.md")):
|
||||
rel = md_file.relative_to(KNOWLEDGE_DIR)
|
||||
content = md_file.read_text(encoding="utf-8")
|
||||
parts.append(f"## {rel}\n\n{content}")
|
||||
|
||||
return "\n\n---\n\n".join(parts)
|
||||
|
||||
|
||||
def list_wiki_articles() -> list[Path]:
|
||||
"""List all wiki article files."""
|
||||
articles = []
|
||||
for subdir in [CONCEPTS_DIR, CONNECTIONS_DIR, QA_DIR]:
|
||||
if subdir.exists():
|
||||
articles.extend(sorted(subdir.glob("*.md")))
|
||||
return articles
|
||||
|
||||
|
||||
def list_raw_files() -> list[Path]:
|
||||
"""List all daily log files."""
|
||||
if not DAILY_DIR.exists():
|
||||
return []
|
||||
return sorted(DAILY_DIR.glob("*.md"))
|
||||
|
||||
|
||||
# ── Index helpers ─────────────────────────────────────────────────────
|
||||
|
||||
def count_inbound_links(target: str, exclude_file: Path | None = None) -> int:
|
||||
"""Count how many wiki articles link to a given target."""
|
||||
count = 0
|
||||
for article in list_wiki_articles():
|
||||
if article == exclude_file:
|
||||
continue
|
||||
content = article.read_text(encoding="utf-8")
|
||||
if f"[[{target}]]" in content:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def get_article_word_count(path: Path) -> int:
|
||||
"""Count words in an article, excluding YAML frontmatter."""
|
||||
content = path.read_text(encoding="utf-8")
|
||||
# Strip frontmatter
|
||||
if content.startswith("---"):
|
||||
end = content.find("---", 3)
|
||||
if end != -1:
|
||||
content = content[end + 3:]
|
||||
return len(content.split())
|
||||
|
||||
|
||||
def build_index_entry(rel_path: str, summary: str, sources: str, updated: str) -> str:
|
||||
"""Build a single index table row."""
|
||||
link = rel_path.replace(".md", "")
|
||||
return f"| [[{link}]] | {summary} | {sources} | {updated} |"
|
||||
Loading…
Add table
Add a link
Reference in a new issue