ada-pc: 2026-06-16 20:13:30 | 8
Affected files: 99 System/Export/agent-tools/AGENT_TOOLS.md 99 System/Export/agent-tools/vault_audit.py 99 System/Export/agent-tools/vault_common.py 99 System/Export/agent-tools/vault_frontmatter.py 99 System/Export/agent-tools/vault_index.py 99 System/Export/agent-tools/vault_ingest.py 99 System/INDEX.md AGENTS.md
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from vault_common import ROOT, all_records, json_print, rel, search_records, split_frontmatter, tokenize
|
||||
|
||||
INBOX_DIRS = [ROOT / "00 Inbox", ROOT / "01 Ideas"]
|
||||
LIBRARY_HINTS = {
|
||||
"ansible": "90 Library/DevOps/Ansible",
|
||||
"ci": "90 Library/DevOps",
|
||||
"cd": "90 Library/DevOps",
|
||||
"cicd": "90 Library/DevOps",
|
||||
"docker": "90 Library/Containers/Docker",
|
||||
"git": "90 Library/DevOps",
|
||||
"gitlab": "90 Library/DevOps",
|
||||
"jenkins": "90 Library/DevOps",
|
||||
"kubernetes": "90 Library/Containers/Kubernetes",
|
||||
"linux": "90 Library/Linux",
|
||||
"nginx": "90 Library/HomeLab/Proxy",
|
||||
"rails": "90 Library/Programming/Ruby On Rails",
|
||||
"ruby": "90 Library/Programming/Ruby",
|
||||
"terraform": "90 Library/DevOps",
|
||||
}
|
||||
|
||||
|
||||
def read_path(path_arg: str) -> Path:
|
||||
path = Path(path_arg)
|
||||
if not path.is_absolute():
|
||||
path = ROOT / path
|
||||
return path
|
||||
|
||||
|
||||
def scan() -> list[dict[str, object]]:
|
||||
items = []
|
||||
for directory in INBOX_DIRS:
|
||||
if not directory.exists():
|
||||
continue
|
||||
for path in sorted(directory.iterdir(), key=lambda item: item.name.lower()):
|
||||
if path.name == ".gitkeep" or not path.is_file():
|
||||
continue
|
||||
text = path.read_text(encoding="utf-8", errors="replace") if path.suffix == ".md" else ""
|
||||
fm, body, _ = split_frontmatter(text)
|
||||
items.append(
|
||||
{
|
||||
"path": rel(path),
|
||||
"folder": rel(directory),
|
||||
"suffix": path.suffix,
|
||||
"title": fm.get("title") or path.stem,
|
||||
"tags": fm.get("tags") or [],
|
||||
"bytes": path.stat().st_size,
|
||||
"classification": classify_text(path, body, fm),
|
||||
}
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
def classify_text(path: Path, body: str, frontmatter: dict[str, object]) -> dict[str, object]:
|
||||
tags = frontmatter.get("tags") or []
|
||||
if not isinstance(tags, list):
|
||||
tags = [tags]
|
||||
content = f"{path.stem}\n{' '.join(map(str, tags))}\n{body[:4000]}".lower()
|
||||
tokens = tokenize(content)
|
||||
|
||||
idea_markers = {"идея", "idea", "business", "product", "стартап", "концепция", "задумка"}
|
||||
if "01 Ideas" in rel(path) or tokens & idea_markers:
|
||||
kind = "idea"
|
||||
destination = "01 Ideas"
|
||||
else:
|
||||
kind = "library"
|
||||
destination = "90 Library/Other"
|
||||
for marker, folder in LIBRARY_HINTS.items():
|
||||
if marker in content:
|
||||
destination = folder
|
||||
break
|
||||
|
||||
return {
|
||||
"kind": kind,
|
||||
"destination": destination,
|
||||
"confidence": "heuristic",
|
||||
}
|
||||
|
||||
|
||||
def plan(path: Path) -> dict[str, object]:
|
||||
text = path.read_text(encoding="utf-8", errors="replace")
|
||||
fm, body, _ = split_frontmatter(text)
|
||||
classification = classify_text(path, body, fm)
|
||||
records = all_records()
|
||||
terms = " ".join([path.stem, str(fm.get("title") or ""), " ".join(map(str, fm.get("tags") or []))])
|
||||
similar = []
|
||||
for item in search_records(records, terms, limit=30):
|
||||
if item["path"] == rel(path):
|
||||
continue
|
||||
if item["path"].startswith(("03 Journal/", "99 System/Export/", "99 System/Cache/", "99 System/Template/")):
|
||||
continue
|
||||
similar.append(item)
|
||||
if len(similar) == 8:
|
||||
break
|
||||
wikilinks = [item["name"] for item in similar[:5]]
|
||||
|
||||
return {
|
||||
"path": rel(path),
|
||||
"classification": classification,
|
||||
"similar_notes": similar,
|
||||
"suggested_wikilinks": wikilinks,
|
||||
"write_policy": "read-only plan; agent must ask before workflow changes",
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Agent-facing ingest helper")
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
sub.add_parser("scan", help="List files in 00 Inbox and 01 Ideas")
|
||||
classify = sub.add_parser("classify", help="Classify one source file")
|
||||
classify.add_argument("path")
|
||||
plan_cmd = sub.add_parser("plan", help="Build JSON ingest plan for one source file")
|
||||
plan_cmd.add_argument("path")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "scan":
|
||||
json_print({"ok": True, "items": scan()})
|
||||
elif args.command == "classify":
|
||||
path = read_path(args.path)
|
||||
text = path.read_text(encoding="utf-8", errors="replace")
|
||||
fm, body, _ = split_frontmatter(text)
|
||||
json_print({"ok": True, "path": rel(path), "classification": classify_text(path, body, fm)})
|
||||
elif args.command == "plan":
|
||||
json_print({"ok": True, "plan": plan(read_path(args.path))})
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user