Skip to main content

scripts-add-last-reviewed-field

#!/usr/bin/env python3 """ H.24.1.3: Add last_reviewed field to CODITECT component frontmatter.

Scans all component files (agents, skills, commands, hooks) and adds a last_reviewed: field to YAML frontmatter where it's missing. Default value = the existing updated: value, or created: as fallback.

Usage: python3 scripts/add-last-reviewed-field.py # Dry run (default) python3 scripts/add-last-reviewed-field.py --apply # Apply changes python3 scripts/add-last-reviewed-field.py --type agents # Only agents python3 scripts/add-last-reviewed-field.py --stats # Show statistics only python3 scripts/add-last-reviewed-field.py --audit-updated # Audit updated: field consistency

Version: 1.0.0 Track: H.24.1 """

import argparse import os import re import sys from pathlib import Path from datetime import datetime

def find_coditect_core() -> Path: """Find coditect-core root directory.""" # Try relative to script location script_dir = Path(file).resolve().parent candidate = script_dir.parent if (candidate / "agents").is_dir() and (candidate / "commands").is_dir(): return candidate

# Try common paths
for path in [
Path.home() / "PROJECTS" / "coditect-rollout-master" / "submodules" / "core" / "coditect-core",
Path.home() / "Library" / "Application Support" / "CODITECT" / "core",
]:
if path.is_dir():
return path

print("ERROR: Could not find coditect-core directory", file=sys.stderr)
sys.exit(1)

def get_component_files(core_dir: Path, component_type: str = None) -> list: """Get all component files with YAML frontmatter.""" files = []

scan_dirs = {
"agents": core_dir / "agents",
"skills": core_dir / "skills",
"commands": core_dir / "commands",
"hooks": core_dir / "hooks",
}

if component_type:
if component_type not in scan_dirs:
print(f"ERROR: Unknown type '{component_type}'. Use: agents, skills, commands, hooks", file=sys.stderr)
sys.exit(1)
scan_dirs = {component_type: scan_dirs[component_type]}

for ctype, directory in scan_dirs.items():
if not directory.is_dir():
continue

if ctype == "skills":
# Skills use */SKILL.md pattern
for skill_dir in sorted(directory.iterdir()):
if skill_dir.is_dir():
skill_file = skill_dir / "SKILL.md"
if skill_file.is_file():
files.append((ctype, skill_file))
else:
# Agents, commands, hooks use *.md pattern
for md_file in sorted(directory.glob("*.md")):
if md_file.name.startswith("CLAUDE") or md_file.name.startswith("README"):
continue
files.append((ctype, md_file))

return files

def parse_frontmatter(content: str) -> tuple: """Parse YAML frontmatter from content.

Returns (frontmatter_text, body_text, has_frontmatter).
frontmatter_text does NOT include the --- delimiters.
"""
if not content.startswith("---"):
return "", content, False

# Find closing ---
end_match = re.search(r'\n---\s*\n', content[3:])
if not end_match:
return "", content, False

end_pos = end_match.start() + 3 # offset for initial ---
frontmatter = content[4:end_pos] # skip opening ---\n
body = content[end_pos + end_match.end() - end_match.start():]

return frontmatter, body, True

def has_field(frontmatter: str, field: str) -> bool: """Check if a YAML field exists in frontmatter.""" pattern = rf'^{re.escape(field)}:' return bool(re.search(pattern, frontmatter, re.MULTILINE))

def get_field_value(frontmatter: str, field: str) -> str: """Extract a field value from frontmatter.""" pattern = rf"^{re.escape(field)}:\s*['"]?([^'"\n]+)['"]?" match = re.search(pattern, frontmatter, re.MULTILINE) return match.group(1).strip() if match else ""

def add_last_reviewed(frontmatter: str, default_value: str) -> str: """Add last_reviewed field after updated: or created: field.""" # Insert after updated: line if it exists updated_match = re.search(r'^(updated:.*\n)', frontmatter, re.MULTILINE) if updated_match: insert_pos = updated_match.end() return ( frontmatter[:insert_pos] + f"last_reviewed: '{default_value}'\n" + frontmatter[insert_pos:] )

# Insert after created: line as fallback
created_match = re.search(r'^(created:.*\n)', frontmatter, re.MULTILINE)
if created_match:
insert_pos = created_match.end()
return (
frontmatter[:insert_pos]
+ f"last_reviewed: '{default_value}'\n"
+ frontmatter[insert_pos:]
)

# Append to end of frontmatter
return frontmatter.rstrip() + f"\nlast_reviewed: '{default_value}'\n"

def process_file(filepath: Path, apply: bool = False) -> dict: """Process a single component file.

Returns dict with: has_frontmatter, has_last_reviewed, has_updated,
has_created, updated_value, created_value, action, modified.
"""
result = {
"has_frontmatter": False,
"has_last_reviewed": False,
"has_updated": False,
"has_created": False,
"updated_value": "",
"created_value": "",
"action": "skip",
"modified": False,
}

content = filepath.read_text(encoding="utf-8")
frontmatter, body, has_fm = parse_frontmatter(content)

if not has_fm:
result["action"] = "no_frontmatter"
return result

result["has_frontmatter"] = True
result["has_last_reviewed"] = has_field(frontmatter, "last_reviewed")
result["has_updated"] = has_field(frontmatter, "updated")
result["has_created"] = has_field(frontmatter, "created")
result["updated_value"] = get_field_value(frontmatter, "updated")
result["created_value"] = get_field_value(frontmatter, "created")

if result["has_last_reviewed"]:
result["action"] = "already_has"
return result

# Determine default value: prefer updated:, fallback to created:
default_value = result["updated_value"] or result["created_value"] or datetime.utcnow().strftime("%Y-%m-%d")

if apply:
new_frontmatter = add_last_reviewed(frontmatter, default_value)
# Ensure frontmatter ends with \n before closing ---
if not new_frontmatter.endswith("\n"):
new_frontmatter += "\n"
new_content = f"---\n{new_frontmatter}---\n{body}"
filepath.write_text(new_content, encoding="utf-8")
result["modified"] = True
result["action"] = f"added (value={default_value})"
else:
result["action"] = f"would_add (value={default_value})"

return result

def run_stats(core_dir: Path, component_type: str = None): """Show statistics about last_reviewed field coverage.""" files = get_component_files(core_dir, component_type)

stats = {
"total": 0,
"has_frontmatter": 0,
"has_last_reviewed": 0,
"missing_last_reviewed": 0,
"no_frontmatter": 0,
"has_updated": 0,
"missing_updated": 0,
"by_type": {},
}

for ctype, filepath in files:
stats["total"] += 1
if ctype not in stats["by_type"]:
stats["by_type"][ctype] = {"total": 0, "has": 0, "missing": 0, "no_fm": 0}

stats["by_type"][ctype]["total"] += 1

content = filepath.read_text(encoding="utf-8")
frontmatter, _, has_fm = parse_frontmatter(content)

if not has_fm:
stats["no_frontmatter"] += 1
stats["by_type"][ctype]["no_fm"] += 1
continue

stats["has_frontmatter"] += 1

if has_field(frontmatter, "last_reviewed"):
stats["has_last_reviewed"] += 1
stats["by_type"][ctype]["has"] += 1
else:
stats["missing_last_reviewed"] += 1
stats["by_type"][ctype]["missing"] += 1

if has_field(frontmatter, "updated"):
stats["has_updated"] += 1
else:
stats["missing_updated"] += 1

print("\n=== Component Version Observability Stats (H.24) ===\n")
print(f" Total component files scanned: {stats['total']}")
print(f" With YAML frontmatter: {stats['has_frontmatter']}")
print(f" No frontmatter (skip): {stats['no_frontmatter']}")
print()
print(f" Has last_reviewed: {stats['has_last_reviewed']}")
print(f" Missing last_reviewed: {stats['missing_last_reviewed']}")
print()
print(f" Has updated: {stats['has_updated']}")
print(f" Missing updated: {stats['missing_updated']}")
print()
print(" By component type:")
for ctype in sorted(stats["by_type"]):
t = stats["by_type"][ctype]
print(f" {ctype:10s}: {t['total']:4d} total, {t['has']:4d} has last_reviewed, {t['missing']:4d} missing, {t['no_fm']:4d} no frontmatter")

def run_audit_updated(core_dir: Path, component_type: str = None): """Audit updated: field consistency — find missing or stale values.""" files = get_component_files(core_dir, component_type)

missing = []
stale = [] # updated: matches created: (never actually updated)

for ctype, filepath in files:
content = filepath.read_text(encoding="utf-8")
frontmatter, _, has_fm = parse_frontmatter(content)
if not has_fm:
continue

updated_val = get_field_value(frontmatter, "updated")
created_val = get_field_value(frontmatter, "created")

if not updated_val:
missing.append((ctype, filepath.relative_to(core_dir)))
elif updated_val == created_val:
stale.append((ctype, filepath.relative_to(core_dir), updated_val))

print("\n=== updated: Field Audit (H.24.1.2) ===\n")
print(f" Components missing updated: field: {len(missing)}")
for ctype, path in missing[:20]:
print(f" [{ctype:8s}] {path}")
if len(missing) > 20:
print(f" ... and {len(missing) - 20} more")

print(f"\n Components where updated == created (never updated): {len(stale)}")
for ctype, path, val in stale[:20]:
print(f" [{ctype:8s}] {path} ({val})")
if len(stale) > 20:
print(f" ... and {len(stale) - 20} more")

def run_repair(core_dir: Path, component_type: str = None, apply: bool = False): """Repair files where closing --- was lost during frontmatter modification.

Detects files that start with --- but have no closing --- delimiter,
then re-inserts the closing --- after the YAML frontmatter block.
"""
files = get_component_files(core_dir, component_type)

broken = []
for ctype, filepath in files:
content = filepath.read_text(encoding="utf-8")
if not content.startswith("---"):
continue
# Check if closing --- exists as its own line
end_match = re.search(r'\n---\s*\n', content[3:])
if not end_match:
broken.append((ctype, filepath))

mode = "REPAIR" if apply else "DRY RUN"
print(f"\n=== Repair Missing Frontmatter Delimiters ({mode}) ===\n")
print(f" Files with broken frontmatter: {len(broken)}")

if not broken:
print(" No broken files found.")
return

repaired = 0
for ctype, filepath in broken:
content = filepath.read_text(encoding="utf-8")

# The bug glued --- to the last frontmatter value, e.g.:
# moe_classified: 2026-01-07---
# Fix: find line containing "value---" and split it
glued_match = re.search(r'^(.+[^-])---$', content, re.MULTILINE)
if not glued_match:
print(f" SKIP (can't find glued ---): [{ctype}] {filepath.relative_to(core_dir)}")
continue

if apply:
# Replace "value---" with "value\n---"
new_content = content[:glued_match.start()] + glued_match.group(1) + "\n---" + content[glued_match.end():]
filepath.write_text(new_content, encoding="utf-8")
repaired += 1
print(f" REPAIRED: [{ctype}] {filepath.relative_to(core_dir)} (split: {glued_match.group(1)[:40]}...)")
else:
print(f" WOULD REPAIR: [{ctype}] {filepath.relative_to(core_dir)} (split: {glued_match.group(1)[:40]}...)")
repaired += 1

print(f"\n {'Repaired' if apply else 'Would repair'}: {repaired} files")
if not apply and repaired > 0:
print(f"\n To apply repairs, run with --repair --apply")

def main(): parser = argparse.ArgumentParser( description="Add last_reviewed field to CODITECT component frontmatter (H.24.1.3)" ) parser.add_argument("--apply", action="store_true", help="Actually modify files (default is dry run)") parser.add_argument("--type", choices=["agents", "skills", "commands", "hooks"], help="Only process specific component type") parser.add_argument("--stats", action="store_true", help="Show statistics only, don't process files") parser.add_argument("--audit-updated", action="store_true", help="Audit updated: field consistency (H.24.1.2)") parser.add_argument("--repair", action="store_true", help="Repair files with missing closing --- delimiter") parser.add_argument("--verbose", "-v", action="store_true", help="Show details for each file")

args = parser.parse_args()
core_dir = find_coditect_core()

if args.stats:
run_stats(core_dir, args.type)
return

if args.audit_updated:
run_audit_updated(core_dir, args.type)
return

if args.repair:
run_repair(core_dir, args.type, apply=args.apply)
return

# Main processing
files = get_component_files(core_dir, args.type)

mode = "APPLY" if args.apply else "DRY RUN"
print(f"\n=== Add last_reviewed Field ({mode}) ===\n")
print(f" Core directory: {core_dir}")
print(f" Component type: {args.type or 'all'}")
print(f" Files to scan: {len(files)}")
print()

added = 0
skipped_has = 0
skipped_no_fm = 0

for ctype, filepath in files:
result = process_file(filepath, apply=args.apply)

if result["action"] == "no_frontmatter":
skipped_no_fm += 1
if args.verbose:
print(f" SKIP (no frontmatter): [{ctype}] {filepath.relative_to(core_dir)}")
elif result["action"] == "already_has":
skipped_has += 1
if args.verbose:
print(f" SKIP (already has): [{ctype}] {filepath.relative_to(core_dir)}")
else:
added += 1
if args.verbose or not args.apply:
rel = filepath.relative_to(core_dir)
print(f" {'ADDED' if args.apply else 'WOULD ADD'}: [{ctype}] {rel} — {result['action']}")

print(f"\n Summary:")
print(f" {'Added' if args.apply else 'Would add'} last_reviewed: {added}")
print(f" Already had last_reviewed: {skipped_has}")
print(f" No frontmatter (skipped): {skipped_no_fm}")
print(f" Total scanned: {len(files)}")

if not args.apply and added > 0:
print(f"\n To apply changes, run with --apply flag:")
print(f" python3 scripts/add-last-reviewed-field.py --apply")

if name == "main": main()