thermogram 0.5.2

Plastic memory capsule with 4-temperature tensor states (hot/warm/cool/cold), bidirectional transitions, and hash-chained auditability
Documentation
#!/usr/bin/env python3
"""
Engineering Log Generator for Astromind

Generates timestamped markdown files documenting engineering decisions,
achievements, and rationale for the TRM/ANT project.

Usage:
    python scripts/eng_log.py --subject "Subject Title" --achievement "What was achieved" --rationale "Why this matters"
    python scripts/eng_log.py --from-stdin  # Read JSON from stdin
    python scripts/eng_log.py --template    # Print template for Claude to fill

Example:
    python scripts/eng_log.py \

        --subject "mHC Integration" \

        --achievement "Added orthogonal weight projection to TRM residual connections" \

        --rationale "Stabilizes training for deeper recursive loops per DeepSeek mHC paper"
"""

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

ENGINEERING_DIR = Path(__file__).parent.parent / "engineering"

TEMPLATE = """
{
    "subject": "<Brief title of the engineering work>",
    "achievement": "<What was accomplished - be specific about code changes, metrics, etc.>",
    "rationale": "<Why this change matters - link to research, performance gains, architectural benefits>",
    "tags": ["<optional>", "<tags>", "<for-categorization>"],
    "related_files": ["<optional>", "<list of modified files>"],
    "metrics": {
        "before": "<optional: metric before change>",
        "after": "<optional: metric after change>"
    },
    "references": ["<optional: papers, docs, or prior logs>"],
    "next_steps": ["<optional: follow-up work identified>"]
}
"""

def generate_log(data: dict) -> str:
    """Generate markdown content from log data."""
    now = datetime.now()
    timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
    date_str = now.strftime("%Y-%m-%d")

    subject = data.get("subject", "Untitled")
    achievement = data.get("achievement", "")
    rationale = data.get("rationale", "")
    tags = data.get("tags", [])
    related_files = data.get("related_files", [])
    metrics = data.get("metrics", {})
    references = data.get("references", [])
    next_steps = data.get("next_steps", [])
    conclusions = data.get("conclusions", [])
    commits = data.get("commits", [])

    md = f"""# {subject}

**Date:** {date_str}
**Timestamp:** {timestamp}
"""

    if tags:
        md += f"**Tags:** {', '.join(tags)}\n"
    if commits:
        md += f"**Commits:** {', '.join(f'`{c}`' for c in commits)}\n"

    md += f"""
---

## Achievement

{achievement}

## Rationale

{rationale}
"""

    # Training metrics table
    if metrics:
        md += "\n## Metrics\n\n"

        # Top-level metrics
        top_metrics = ["params", "epochs", "wall_clock", "accuracy", "failure_mode", "delta"]
        has_top = any(metrics.get(m) for m in top_metrics)
        if has_top:
            md += "| Metric | Value |\n|--------|-------|\n"
            if metrics.get("params"):
                md += f"| Parameters | {metrics['params']} |\n"
            if metrics.get("epochs"):
                md += f"| Epochs | {metrics['epochs']} |\n"
            if metrics.get("wall_clock"):
                md += f"| Wall Clock | {metrics['wall_clock']} |\n"
            if metrics.get("accuracy"):
                md += f"| Accuracy | {metrics['accuracy']} |\n"
            if metrics.get("failure_mode"):
                md += f"| Failure Mode | {metrics['failure_mode']} |\n"
            if metrics.get("delta"):
                md += f"| **Delta** | **{metrics['delta']}** |\n"
            md += "\n"

        # Before/After comparison
        before = metrics.get("before", {})
        after = metrics.get("after", {})
        if before or after:
            md += "### Before/After Comparison\n\n"
            md += "| Metric | Before | After |\n|--------|--------|-------|\n"
            if isinstance(before, dict) and isinstance(after, dict):
                all_keys = set(before.keys()) | set(after.keys())
                for key in sorted(all_keys):
                    b_val = before.get(key, "N/A")
                    a_val = after.get(key, "N/A")
                    md += f"| {key} | {b_val} | {a_val} |\n"
            else:
                md += f"| Value | {before} | {after} |\n"

    # Conclusions - THE IMPORTANT PART
    if conclusions:
        md += "\n## Conclusions\n\n"
        for i, conclusion in enumerate(conclusions, 1):
            md += f"{i}. {conclusion}\n"

    if related_files:
        md += "\n## Related Files\n\n"
        for f in related_files:
            md += f"- `{f}`\n"

    if references:
        md += "\n## References\n\n"
        for ref in references:
            md += f"- {ref}\n"

    if next_steps:
        md += "\n## Next Steps\n\n"
        for step in next_steps:
            md += f"- [ ] {step}\n"

    md += "\n---\n*Generated by eng_log.py*\n"

    return md

def save_log(content: str, subject: str) -> Path:
    """Save log to engineering directory with timestamp filename."""
    ENGINEERING_DIR.mkdir(exist_ok=True)

    now = datetime.now()
    date_prefix = now.strftime("%Y%m%d")
    time_suffix = now.strftime("%H%M%S")

    # Sanitize subject for filename
    safe_subject = "".join(c if c.isalnum() or c in "-_ " else "" for c in subject)
    safe_subject = safe_subject.replace(" ", "-").lower()[:50]

    filename = f"{date_prefix}-{time_suffix}-{safe_subject}.md"
    filepath = ENGINEERING_DIR / filename

    filepath.write_text(content, encoding="utf-8")
    return filepath

def main():
    parser = argparse.ArgumentParser(description="Generate engineering log entries")
    parser.add_argument("--subject", "-s", help="Subject/title of the log entry")
    parser.add_argument("--achievement", "-a", help="What was achieved")
    parser.add_argument("--rationale", "-r", help="Why this matters")
    parser.add_argument("--tags", "-t", nargs="*", help="Optional tags")
    parser.add_argument("--from-stdin", action="store_true", help="Read JSON from stdin")
    parser.add_argument("--template", action="store_true", help="Print template JSON")
    parser.add_argument("--dry-run", action="store_true", help="Print markdown without saving")

    args = parser.parse_args()

    if args.template:
        print("Engineering Log Template (JSON):")
        print(TEMPLATE)
        return

    if args.from_stdin:
        try:
            data = json.load(sys.stdin)
        except json.JSONDecodeError as e:
            print(f"Error parsing JSON: {e}", file=sys.stderr)
            sys.exit(1)
    elif args.subject and args.achievement:
        data = {
            "subject": args.subject,
            "achievement": args.achievement,
            "rationale": args.rationale or "",
            "tags": args.tags or []
        }
    else:
        parser.print_help()
        print("\nError: Provide --subject and --achievement, or use --from-stdin", file=sys.stderr)
        sys.exit(1)

    content = generate_log(data)

    if args.dry_run:
        print(content)
    else:
        filepath = save_log(content, data.get("subject", "log"))
        print(f"Log saved to: {filepath}")

if __name__ == "__main__":
    main()