Files
SecondBrain/99 System/Tools/agent-tools/vault_frontmatter.py
T
Dmitry 22eda53d79 ada-pc: 2026-06-16 20:18:31 | 9
Affected files:
.obsidian/graph.json
99 System/INDEX.md
99 System/Tools/agent-tools/AGENT_TOOLS.md
99 System/Tools/agent-tools/vault_audit.py
99 System/Tools/agent-tools/vault_common.py
99 System/Tools/agent-tools/vault_frontmatter.py
99 System/Tools/agent-tools/vault_index.py
99 System/Tools/agent-tools/vault_ingest.py
AGENTS.md
2026-06-16 20:18:32 +03:00

136 lines
3.9 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import argparse
from datetime import date
from pathlib import Path
from vault_common import (
REQUIRED_FRONTMATTER,
ROOT,
VALID_STATUS,
VALID_TYPE,
all_records,
as_list,
json_print,
note_record,
note_files,
rel,
split_frontmatter,
)
def resolve_paths(paths: list[str]) -> list[Path]:
if not paths:
return note_files()
result = []
for item in paths:
path = Path(item)
if not path.is_absolute():
path = ROOT / path
result.append(path)
return result
def validate_record(record: dict[str, object]) -> list[str]:
fm = record["frontmatter"]
issues = []
if not record["has_frontmatter"]:
issues.append("missing_frontmatter")
for field in sorted(REQUIRED_FRONTMATTER):
if field not in fm or fm[field] in (None, ""):
issues.append(f"missing_{field}")
if fm.get("status") and str(fm["status"]) not in VALID_STATUS:
issues.append("invalid_status")
if fm.get("type") and str(fm["type"]) not in VALID_TYPE:
issues.append("invalid_type")
if "tags" in fm and not isinstance(fm["tags"], list):
issues.append("tags_not_list")
if "aliases" in fm and fm["aliases"] not in (None, "") and not isinstance(fm["aliases"], list):
issues.append("aliases_not_list")
return issues
def check(paths: list[Path]) -> list[dict[str, object]]:
results = []
for path in paths:
record = note_record(path)
issues = validate_record(record)
if issues:
results.append({"path": rel(path), "issues": issues})
return results
def yaml_list(values: list[str]) -> list[str]:
if not values:
return ["[]"]
return [""] + [f" - {value}" for value in values]
def normalize(path: Path) -> dict[str, object]:
text = path.read_text(encoding="utf-8")
fm, body, _ = split_frontmatter(text)
title = fm.get("title") or path.stem
status = fm.get("status") if fm.get("status") in VALID_STATUS else "seed"
note_type = fm.get("type") if fm.get("type") in VALID_TYPE else "concept"
tags = as_list(fm.get("tags"))
aliases = as_list(fm.get("aliases"))
created = fm.get("created") or date.today().isoformat()
updated = date.today().isoformat()
lines = [
"---",
f"title: {title}",
f"status: {status}",
f"type: {note_type}",
"tags:" + ("\n".join(yaml_list(tags)) if tags else " []"),
f"created: {created}",
f"updated: {updated}",
"aliases:" + ("\n".join(yaml_list(aliases)) if aliases else " []"),
]
for key, value in fm.items():
if key in {"title", "status", "type", "tags", "created", "updated", "aliases"}:
continue
if isinstance(value, list):
lines.append(f"{key}:")
lines.extend(f" - {item}" for item in value)
elif value in (None, ""):
lines.append(f"{key}:")
else:
lines.append(f"{key}: {value}")
lines.extend(["---", "", body.lstrip("\n")])
path.write_text("\n".join(lines), encoding="utf-8")
return {"path": rel(path), "written": True}
def main() -> int:
parser = argparse.ArgumentParser(description="Agent-facing frontmatter helper")
sub = parser.add_subparsers(dest="command", required=True)
check_cmd = sub.add_parser("check")
check_cmd.add_argument("paths", nargs="*")
normalize_cmd = sub.add_parser("normalize")
normalize_cmd.add_argument("paths", nargs="+")
args = parser.parse_args()
if args.command == "check":
issues = check(resolve_paths(args.paths))
json_print({"ok": True, "issues": issues, "total_with_issues": len(issues)})
elif args.command == "normalize":
written = [normalize(path) for path in resolve_paths(args.paths)]
json_print({"ok": True, "written": written})
return 0
if __name__ == "__main__":
raise SystemExit(main())