#!/usr/bin/env python3
"""Durable human decision context for CARBON verification workflows.

CARBON can reason over code and observed evidence, but it must not silently
invent intent, taste, consequence, accountability, or a release decision. This
runtime stores the smallest set of decision-changing questions for a project,
keeps them open until a person answers, and exposes an explicit summary for
confidence, readiness, risk, and planning workflows.
"""

from __future__ import annotations

import argparse
import datetime as dt
import hashlib
import json
import os
import re
import tempfile
from pathlib import Path
from typing import Any


SCHEMA = "carbon.human-decision-context/v1"
MAX_QUESTIONS = {"quick": 5, "medium": 8, "large": 12}
IGNORED = {".git", ".carbon", "node_modules", "vendor", "dist", "build", ".next", ".venv", "venv", "coverage", "__pycache__"}
TEXT_EXTENSIONS = {".md", ".txt", ".rst", ".json", ".yaml", ".yml", ".toml", ".js", ".jsx", ".ts", ".tsx", ".py", ".go", ".java", ".rb", ".php", ".html", ".vue", ".svelte"}


def now() -> str:
    return dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")


def read_json(path: Path, fallback: Any) -> Any:
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        return fallback


def atomic_json(path: Path, value: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
    with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=path.parent, delete=False) as handle:
        json.dump(value, handle, indent=2, ensure_ascii=False)
        handle.write("\n")
        temporary = Path(handle.name)
    os.chmod(temporary, 0o600)
    os.replace(temporary, path)
    os.chmod(path, 0o600)


def context_path(root: Path) -> Path:
    return root / ".carbon" / "human" / "decision-context.json"


def question_id(key: str) -> str:
    return "HUM-" + hashlib.sha256(key.encode("utf-8")).hexdigest()[:8].upper()


def quick_options(key: str) -> list[dict[str, str]]:
    """Offer fast, editable decision starters without putting words in a human's mouth."""
    options = {
        "release-context": [
            ("demo", "Demo", "A time-boxed demonstration; avoid production-level implications."),
            ("internal", "Internal trial", "Limited internal use with a named owner and reversible scope."),
            ("staged", "Staged rollout", "A controlled customer release with monitoring and rollback."),
            ("production", "Production release", "A customer-impacting production decision."),
        ],
        "customer-outcome": [
            ("define", "Define the outcome", "The accountable owner needs to state the desired customer result."),
            ("task", "Complete the key task", "The user can finish the primary job without unacceptable workarounds."),
            ("time", "Reduce effort or time", "The user reaches a useful result materially faster or more easily."),
            ("measure", "Use a named metric", "Success will be judged through an explicit product or business measure."),
        ],
        "value-oracle": [
            ("observe", "Observe a real user", "Use a representative journey and direct feedback."),
            ("pilot", "Pilot outcome", "Use a small, accountable pilot with a defined success threshold."),
            ("metric", "Product metric", "Use a named adoption, completion, or quality measure."),
            ("not-ready", "Not defined yet", "Do not imply value confidence until an owner defines the oracle."),
        ],
        "failure-consequence": [
            ("hold", "Hold on failure", "The failure is unacceptable even once; do not release through it."),
            ("canary", "Canary with safeguards", "A small, monitored, reversible exposure may be acceptable."),
            ("recover", "Recoverable incident", "A clear owner, recovery path, and customer remedy are sufficient."),
            ("unknown", "Need risk owner", "The consequence and acceptable tradeoff require accountable input."),
        ],
        "domain-invariants": [
            ("none", "No additional rules", "No known domain invariant beyond the documented behavior."),
            ("provide", "I can provide rules", "Record the exception, reconciliation, or boundary condition."),
            ("expert", "Ask a domain expert", "A knowledgeable operator should define this before release."),
            ("research", "Need discovery", "Use a pilot, support history, or workflow research to establish it."),
        ],
        "experience-credibility": [
            ("clear", "Clear enough", "The intended user can understand and trust the primary journey."),
            ("friction", "Reduce friction", "There are known confusing, surprising, or credibility-damaging moments."),
            ("research", "Validate with users", "Use representative feedback before relying on the experience."),
            ("unknown", "Not judged yet", "Do not claim the workflow is credible without accountable review."),
        ],
        "failure-ownership": [
            ("named", "Owner and recovery are named", "Detection, response, rollback, and communication are defined."),
            ("add", "Add an operational plan", "The release needs a named owner and safe fallback first."),
            ("canary", "Canary only", "Limit exposure while operational ownership is established."),
            ("unknown", "No owner yet", "Treat recovery accountability as an explicit readiness gap."),
        ],
        "ai-boundaries": [
            ("human", "Human approval required", "A person must approve consequential or sensitive outcomes."),
            ("restricted", "Restrict the behavior", "Constrain tools, populations, or actions until more evidence exists."),
            ("evaluate", "Add safety evaluation", "Define unacceptable slices and a decision threshold."),
            ("unknown", "Need accountable owner", "No silent AI tradeoff should be accepted without a named human."),
        ],
        "data-commitment": [
            ("document", "Document the commitment", "Publish or approve the applicable data-use and retention rules."),
            ("minimize", "Minimize data", "Collect and retain less until the purpose and policy are clear."),
            ("review", "Legal/privacy review", "A responsible owner should determine the required commitment."),
            ("unknown", "Not defined yet", "Keep the policy question open; technical storage alone is not a commitment."),
        ],
        "blind-spot": [
            ("none", "No additional blind spot", "No unrecorded assumption is known at this time."),
            ("add", "Record an assumption", "Add the domain knowledge or challenge CARBON should carry forward."),
            ("expert", "Consult an expert", "An external or domain expert should challenge the current model."),
            ("research", "Need more discovery", "Gather workflow, customer, or incident evidence before deciding."),
        ],
    }
    return [{"id": option_id, "label": label, "description": description} for option_id, label, description in options.get(key, [])]


def text_from_project(root: Path, limit: int = 180_000) -> tuple[str, list[str]]:
    chunks: list[str] = []
    files: list[str] = []
    total = 0
    for current, dirs, names in os.walk(root):
        dirs[:] = sorted(name for name in dirs if name not in IGNORED and not name.startswith(".cache"))
        for name in sorted(names):
            path = Path(current) / name
            if path.suffix.lower() not in TEXT_EXTENSIONS or path.is_symlink():
                continue
            try:
                raw = path.read_text(encoding="utf-8", errors="ignore")
            except OSError:
                continue
            relative = path.relative_to(root).as_posix()
            excerpt = raw[:8_000]
            chunks.append(f"\n--- {relative} ---\n{excerpt}")
            files.append(relative)
            total += len(excerpt)
            if total >= limit:
                return "".join(chunks), files
    return "".join(chunks), files


def latest_run(root: Path) -> tuple[dict[str, Any], Path | None]:
    candidates: list[tuple[str, dict[str, Any], Path]] = []
    runs = root / ".carbon" / "runs"
    if not runs.is_dir():
        return {}, None
    for path in runs.glob("*/run.json"):
        value = read_json(path, {})
        if isinstance(value, dict):
            candidates.append((str(value.get("updatedAt") or value.get("createdAt") or ""), value, path))
    if not candidates:
        return {}, None
    _, run, path = max(candidates, key=lambda item: item[0])
    return run, path


def project_profile(root: Path) -> dict[str, Any]:
    text, files = text_from_project(root)
    lower = text.lower()
    package = read_json(root / "package.json", {})
    run, run_path = latest_run(root)
    findings = [item for item in run.get("findings", []) if isinstance(item, dict)] if isinstance(run, dict) else []
    planned = [item for item in run.get("tests", []) if isinstance(item, dict)] if isinstance(run, dict) else []
    signals = {
        "ui": bool(re.search(r"<(?:form|button|input|main|nav)\b|react|vue|svelte|html", lower)),
        "api": bool(re.search(r"\b(?:openapi|swagger|express|fastapi|flask|route|endpoint|graphql|api/)\b", lower)),
        "ai": bool(re.search(r"\b(?:llm|model|prompt|openai|anthropic|gemini|rag|agent)\b", lower)),
        "data": bool(re.search(r"\b(?:database|postgres|mysql|sqlite|prisma|migration|localstorage|redis)\b", lower)),
        "auth": bool(re.search(r"\b(?:login|sign[ -]?in|password|oauth|token|session|role|permission)\b", lower)),
        "payment": bool(re.search(r"\b(?:stripe|payment|checkout|invoice|billing|subscription)\b", lower)),
        "privacy": bool(re.search(r"\b(?:privacy|personal data|pii|gdpr|cookie|consent|tracking)\b", lower)),
        "operations": bool(re.search(r"\b(?:deploy|production|rollback|monitoring|alert|on-call|incident|slo)\b", lower)),
        "release": bool(re.search(r"\b(?:release|production|staging|demo|beta|launch|ship)\b", lower)),
        "value": bool(re.search(r"\b(?:customer|user|outcome|problem|value|workflow|job to be done|success)\b", lower)),
    }
    critical_findings = [row for row in findings if str(row.get("severity", "")).lower() in {"critical", "high"}]
    return {
        "filesScanned": len(files),
        "fileSamples": files[:20],
        "fingerprint": hashlib.sha256("\n".join(files).encode("utf-8") + text[:100_000].encode("utf-8")).hexdigest(),
        "signals": signals,
        "packageName": package.get("name") if isinstance(package, dict) else "",
        "latestRunId": run.get("runId") if isinstance(run, dict) else "",
        "plannedTests": len(planned),
        "findings": len(findings),
        "criticalFindings": len(critical_findings),
        "findingTitles": [str(row.get("title") or row.get("id") or "finding")[:180] for row in critical_findings[:4]],
    }


def candidate_questions(profile: dict[str, Any], scope: str) -> list[dict[str, str]]:
    s = profile["signals"]
    candidates: list[dict[str, str]] = []

    def add(key: str, category: str, priority: str, question: str, why: str, impact: str) -> None:
        candidates.append({"key": key, "category": category, "priority": priority, "question": question, "why": why, "decisionImpact": impact, "options": quick_options(key)})

    product = profile.get("packageName") or "this product"
    scoped = f" for {scope}" if scope else ""
    add("release-context", "decision context", "P0",
        f"What decision should CARBON help make{scoped}: a demo readiness call, an internal trial, a staged rollout, or production release—and when?",
        "The project evidence does not reliably establish the release stage, deadline, or decision owner.",
        "Sets the consequence threshold, test depth, and what a credible confidence conclusion can mean.")
    if not s["value"]:
        add("customer-outcome", "business value", "P0",
            f"Which customer problem and observable outcome must {product} deliver for this work to count as successful—not merely technically complete?",
            "Code and tests can establish behavior, but not whether that behavior solves the customer’s actual problem.",
            "Defines the primary value oracle, useful journey, and acceptance evidence.")
    else:
        add("value-oracle", "business value", "P0",
            "What evidence would convince the accountable product owner that the intended user achieved the promised outcome with acceptable effort?",
            "A stated feature or workflow is not yet a decision-quality value oracle.",
            "Guides business-value checks, persona charters, and release interpretation.")
    if s["auth"] or s["payment"] or s["privacy"] or profile["criticalFindings"]:
        sensitive = "authentication, payment, privacy, or a currently severe finding" if (s["auth"] or s["payment"] or s["privacy"]) else "the current high-severity findings"
        add("failure-consequence", "risk appetite", "P0",
            f"If {sensitive} fails in the real workflow, who absorbs the harm, what is the worst credible outcome, and which failures are unacceptable even once?",
            "The code cannot determine business consequence, compensating controls, or the organization’s risk appetite.",
            "Determines P0/P1 boundaries, safe test limits, and whether a hold, canary, or broader evidence is warranted.")
    if s["data"] or s["api"]:
        add("domain-invariants", "domain expertise", "P1",
            "Which domain invariants, exceptional cases, or reconciliation rules would an experienced operator spot immediately but are not written in the code or tests?",
            "Data and API behavior often have business rules that look valid syntactically while being wrong operationally.",
            "Creates high-value boundary and state-integrity oracles that automated discovery may miss.")
    if s["ui"]:
        add("experience-credibility", "experience judgment", "P1",
            "For the intended user, what would make this experience feel credible, clear, and worth trusting—and what confusion or surprise would make it unacceptable?",
            "Visual and interaction checks can detect defects, but product taste, trust, and workflow fit need accountable human judgment.",
            "Steers persona exploration toward moments that change adoption, support burden, and confidence.")
    if s["operations"] or s["api"]:
        add("failure-ownership", "operational accountability", "P1",
            "When this fails or degrades, who detects it, who owns recovery, what is the safe fallback, and how quickly must users be made whole?",
            "Recovery obligations and operational ownership are rarely complete in source artifacts.",
            "Prioritizes observability, resilience, rollback, and failure-injection evidence.")
    if s["ai"]:
        add("ai-boundaries", "AI behavior", "P0",
            "Which AI behaviors, populations, or mistakes would be unacceptable despite an apparently good aggregate result, and who has authority to approve those tradeoffs?",
            "Model and evaluator outputs do not define acceptable risk, representation, or human accountability.",
            "Sets evaluation slices, safety thresholds, escalation paths, and approval requirements.")
    if s["privacy"]:
        add("data-commitment", "privacy and trust", "P1",
            "What data-use, retention, consent, deletion, and customer-communication commitments must this product keep in practice?",
            "Storage behavior reveals technical facts, not the policy and customer commitments that determine acceptability.",
            "Guides privacy oracles, legal review, and scope for safe evidence capture.")
    add("blind-spot", "evaluation blind spot", "P1",
        "What assumption would be obvious to a domain expert but invisible in this repository—and what question are the coding agent and its automated evaluators not asking?",
        "This deliberately searches for missing oracles instead of asking humans to repeat information CARBON can inspect.",
        "Adds a documented challenge to future test selection, confidence, readiness, and risk reviews.")
    return candidates


def summary(context: dict[str, Any]) -> dict[str, Any]:
    questions = [row for row in context.get("questions", []) if isinstance(row, dict)]
    open_questions = [row for row in questions if row.get("status") == "open"]
    answered = [row for row in questions if row.get("status") == "answered"]
    p0 = [row for row in open_questions if row.get("priority") == "P0"]
    return {
        "status": "pending" if open_questions else ("recorded" if answered else "not-recorded"),
        "questions": len(questions),
        "open": len(open_questions),
        "answered": len(answered),
        "openP0": len(p0),
        "pendingQuestionIds": [str(row.get("id")) for row in open_questions[:8]],
        "answeredQuestionIds": [str(row.get("id")) for row in answered[:8]],
        "confidenceQualifier": (
            "Human decision context is pending; confidence covers observed technical evidence but not unrecorded intent, risk appetite, taste, consequence, or accountability."
            if open_questions or not questions else
            "Human decision context is recorded; answers guide CARBON planning and interpretation but do not turn unobserved behavior into a pass."
        ),
    }


def generate(root: Path, size: str, scope: str, refresh: bool = False) -> dict[str, Any]:
    root = root.expanduser().resolve()
    if not root.is_dir():
        raise ValueError("root must be an existing project directory")
    path = context_path(root)
    existing = read_json(path, {})
    if not isinstance(existing, dict) or existing.get("schema") != SCHEMA:
        existing = {"schema": SCHEMA, "projectRoot": str(root), "questions": [], "answers": [], "comments": [], "createdAt": now()}
    profile = project_profile(root)
    old_by_key = {str(row.get("key")): row for row in existing.get("questions", []) if isinstance(row, dict) and row.get("key")}
    questions: list[dict[str, Any]] = []
    for candidate in candidate_questions(profile, scope)[:MAX_QUESTIONS[size]]:
        old = old_by_key.get(candidate["key"])
        if old and old.get("status") == "answered":
            questions.append(old)
            continue
        if old and old.get("status") == "open":
            questions.append({**old, **candidate, "updatedAt": now()})
            continue
        questions.append({
            **candidate,
            "id": question_id(candidate["key"]),
            "status": "open",
            "askedAt": now(),
            "updatedAt": now(),
            "evidenceGap": candidate["why"],
            "sourceSignals": profile["signals"],
        })
    # Keep unresolved questions from an earlier run even when they do not rank in
    # the current size limit. Human decisions must not disappear because a later
    # command chose a smaller pass.
    selected = {row["key"] for row in questions}
    for row in existing.get("questions", []):
        if isinstance(row, dict) and row.get("status") == "open" and row.get("key") not in selected:
            questions.append(row)
    context = {
        "schema": SCHEMA,
        "projectRoot": str(root),
        "createdAt": existing.get("createdAt") or now(),
        "updatedAt": now(),
        "scope": scope,
        "size": size,
        "projectFingerprint": profile["fingerprint"],
        "profile": profile,
        "questions": questions,
        "answers": existing.get("answers", []) if isinstance(existing.get("answers"), list) else [],
        "comments": existing.get("comments", []) if isinstance(existing.get("comments"), list) else [],
        "principle": "CARBON asks humans only where intent, taste, experience, consequence, domain knowledge, or accountability changes a decision. Human input informs evaluation; it never fabricates observed evidence or removes human release authority.",
    }
    context["summary"] = summary(context)
    atomic_json(path, context)
    return {"path": str(path), "context": context, "summary": context["summary"], "generated": len([row for row in questions if row.get("status") == "open"])}


def status(root: Path) -> dict[str, Any]:
    root = root.expanduser().resolve()
    context = read_json(context_path(root), {})
    if not isinstance(context, dict) or context.get("schema") != SCHEMA:
        return {"path": str(context_path(root)), "context": {}, "summary": {"status": "not-recorded", "questions": 0, "open": 0, "answered": 0, "openP0": 0, "pendingQuestionIds": [], "answeredQuestionIds": [], "confidenceQualifier": "Human decision context has not been requested; confidence remains technical-evidence qualified."}}
    context["summary"] = summary(context)
    return {"path": str(context_path(root)), "context": context, "summary": context["summary"]}


def answer(root: Path, question: str, value: str, answered_by: str = "human", option_id: str = "", comment: str = "") -> dict[str, Any]:
    root = root.expanduser().resolve()
    current = status(root)
    context = current["context"]
    if not context:
        raise ValueError("No human questions are recorded yet. Run generate first.")
    match = next((row for row in context.get("questions", []) if isinstance(row, dict) and str(row.get("id")) == question), None)
    if not match:
        raise ValueError(f"Unknown human question: {question}")
    clean = " ".join(value.split())
    selected_option: dict[str, Any] | None = None
    if option_id:
        selected_option = next((row for row in match.get("options", []) if isinstance(row, dict) and str(row.get("id")) == option_id), None)
        if not selected_option:
            raise ValueError(f"Unknown answer option for {question}: {option_id}")
        if not clean:
            clean = str(selected_option.get("label") or option_id)
    if not clean:
        raise ValueError("answer must not be empty unless a valid option is selected")
    if len(clean) > 8_000:
        raise ValueError("answer must be 8,000 characters or fewer")
    record = {"questionId": question, "answer": clean, "answeredAt": now(), "answeredBy": " ".join(answered_by.split())[:120] or "human"}
    if selected_option:
        record["selectedOptionId"] = str(selected_option.get("id"))
        record["selectedOptionLabel"] = str(selected_option.get("label"))
    match.update({"status": "answered", **record, "updatedAt": now()})
    answers = [row for row in context.get("answers", []) if isinstance(row, dict) and row.get("questionId") != question]
    answers.append(record)
    context["answers"] = answers[-100:]
    context["updatedAt"] = now()
    context["summary"] = summary(context)
    if comment.strip():
        append_comment(context, question, comment, answered_by)
    atomic_json(context_path(root), context)
    return {"path": str(context_path(root)), "question": match, "summary": context["summary"]}


def append_comment(context: dict[str, Any], question: str, value: str, author: str = "human") -> dict[str, Any]:
    clean = " ".join(value.split())
    if not clean:
        raise ValueError("comment must not be empty")
    if len(clean) > 8_000:
        raise ValueError("comment must be 8,000 characters or fewer")
    if question != "project" and not any(isinstance(row, dict) and str(row.get("id")) == question for row in context.get("questions", [])):
        raise ValueError(f"Unknown human question: {question}")
    record = {"id": "HCOM-" + secrets_token(clean + question), "questionId": question, "comment": clean, "createdAt": now(), "author": " ".join(author.split())[:120] or "human"}
    comments = [row for row in context.get("comments", []) if isinstance(row, dict)]
    comments.append(record)
    context["comments"] = comments[-200:]
    context["updatedAt"] = now()
    return record


def secrets_token(seed: str) -> str:
    return hashlib.sha256(f"{now()}:{seed}".encode("utf-8")).hexdigest()[:10].upper()


def comment(root: Path, question: str, value: str, author: str = "human") -> dict[str, Any]:
    root = root.expanduser().resolve()
    current = status(root)
    context = current["context"]
    if not context:
        raise ValueError("No human questions are recorded yet. Run generate first.")
    record = append_comment(context, question or "project", value, author)
    context["summary"] = summary(context)
    atomic_json(context_path(root), context)
    return {"path": str(context_path(root)), "comment": record, "summary": context["summary"]}


def render_report(root: Path) -> dict[str, Any]:
    """Publish a durable, evidence-safe snapshot beside the editable local workspace."""
    root = root.expanduser().resolve()
    current = status(root)
    context = current["context"]
    if not context:
        raise ValueError("No human questions are recorded yet. Run generate first.")
    questions = [row for row in context.get("questions", []) if isinstance(row, dict)]
    comments = [row for row in context.get("comments", []) if isinstance(row, dict)]
    open_questions = [row for row in questions if row.get("status") == "open"]
    answered_questions = [row for row in questions if row.get("status") == "answered"]
    question_items = []
    for row in questions:
        is_answered = row.get("status") == "answered"
        answer_text = str(row.get("answer") or "")
        selected = str(row.get("selectedOptionLabel") or "")
        detail = row.get("question") if not is_answered else f"Answer: {selected or answer_text}{f' — {answer_text}' if selected and answer_text and answer_text != selected else ''}"
        question_items.append({
            "title": f"{row.get('priority', 'P1')} · {row.get('category', 'human context')}",
            "detail": detail,
            "evidence": f"Why this matters: {row.get('decisionImpact') or row.get('why') or 'It can change the current decision.'}",
            "status": "completed" if is_answered else "planned",
        })
    comment_items = [{
        "title": "Project note" if row.get("questionId") == "project" else str(row.get("questionId") or "Question note"),
        "detail": str(row.get("comment") or ""),
        "evidence": f"Recorded by {row.get('author') or 'human'} at {row.get('createdAt') or 'an unspecified time'}.",
        "status": "completed",
    } for row in comments]
    metrics = [
        {"label": "Decision questions", "value": str(len(questions)), "detail": "Only questions that can change a decision are retained.", "tone": "neutral", "icon": "context"},
        {"label": "Open context", "value": str(len(open_questions)), "detail": "Open questions qualify interpretation; they are not failed technical tests.", "tone": "risk" if open_questions else "good", "icon": "unknown"},
        {"label": "Answered", "value": str(len(answered_questions)), "detail": "Recorded answers inform later plans and readiness work.", "tone": "good" if answered_questions else "neutral", "icon": "evidence"},
        {"label": "Comments", "value": str(len(comments)), "detail": "Human notes retained as project-local context.", "tone": "neutral", "icon": "observation"},
    ]
    payload = {
        "command": "carbon-human",
        "title": "Human decision context",
        "summary": "CARBON recorded the human context that can materially change the current confidence, readiness, risk, and test-selection interpretation.",
        "status": "partial" if open_questions else "completed",
        "scope": str(context.get("scope") or root.name),
        "metrics": metrics,
        "sections": [
            {"title": "Decision questions", "intro": "This is the durable human layer around the technical evidence. Choose fast options or add context in the interactive workspace; CARBON does not infer approval from silence.", "icon": "context", "tone": "warning" if open_questions else "good", "items": question_items},
            {"title": "Recorded comments", "intro": "Comments are kept with the project context and can be attached to an individual question or the overall decision.", "icon": "observation", "tone": "neutral", "items": comment_items},
            {"title": "How CARBON will use this", "intro": "Answers can change test selection, persona charters, release interpretation, recovery checks, and confidence qualifiers. They never fabricate an observed pass or replace human release authority.", "icon": "confidence", "tone": "neutral", "bullets": [str(context.get("principle") or "Human input remains explicit and decision-changing."), str(current["summary"].get("confidenceQualifier") or "") ]},
        ],
        "artifacts": [{"label": "Human decision context", "path": str(context_path(root)), "detail": "Local question, answer, and comment record. This report never stores credentials.", "icon": "artifact"}],
        "nextActions": [
            "Use the interactive Human workspace to select an answer or attach a comment to any question.",
            "Run /carbon-confidence after decision-changing answers are recorded to re-interpret the observed technical evidence.",
        ],
        "specialistIds": ["jason"],
        "evidenceBoundary": "This report records explicit human decision context. It does not prove product behavior, replace a release decision, or convert unobserved technical work into a pass.",
    }
    from carbon_command_report import render
    rendered = render(root, payload)
    context["latestReportPath"] = str(rendered.get("reportPath") or "")
    context["latestReportGeneratedAt"] = now()
    context["summary"] = summary(context)
    atomic_json(context_path(root), context)
    return {"path": str(context_path(root)), "context": context, "summary": current["summary"], "report": rendered}


def parser() -> argparse.ArgumentParser:
    root = argparse.ArgumentParser(description=__doc__)
    sub = root.add_subparsers(dest="action", required=True)
    generate_parser = sub.add_parser("generate")
    generate_parser.add_argument("--root", required=True)
    generate_parser.add_argument("--size", choices=tuple(MAX_QUESTIONS), default="quick")
    generate_parser.add_argument("--scope", default="")
    generate_parser.add_argument("--refresh", action="store_true")
    generate_parser.set_defaults(func=lambda args: generate(Path(args.root), args.size, args.scope, args.refresh))
    status_parser = sub.add_parser("status")
    status_parser.add_argument("--root", required=True)
    status_parser.set_defaults(func=lambda args: status(Path(args.root)))
    answer_parser = sub.add_parser("answer")
    answer_parser.add_argument("--root", required=True)
    answer_parser.add_argument("--question", required=True)
    answer_parser.add_argument("--answer", default="")
    answer_parser.add_argument("--answered-by", default="human")
    answer_parser.add_argument("--option-id", default="")
    answer_parser.add_argument("--comment", default="")
    answer_parser.set_defaults(func=lambda args: answer(Path(args.root), args.question, args.answer, args.answered_by, args.option_id, args.comment))
    comment_parser = sub.add_parser("comment")
    comment_parser.add_argument("--root", required=True)
    comment_parser.add_argument("--question", default="project")
    comment_parser.add_argument("--comment", required=True)
    comment_parser.add_argument("--author", default="human")
    comment_parser.set_defaults(func=lambda args: comment(Path(args.root), args.question, args.comment, args.author))
    report_parser = sub.add_parser("report")
    report_parser.add_argument("--root", required=True)
    report_parser.set_defaults(func=lambda args: render_report(Path(args.root)))
    return root


def main() -> int:
    try:
        args = parser().parse_args()
        print(json.dumps(args.func(args), ensure_ascii=False, indent=2))
        return 0
    except (OSError, ValueError) as error:
        print(f"carbon_human: {error}")
        return 2


if __name__ == "__main__":
    raise SystemExit(main())
