|
|
|
|
|
""" |
|
|
Commitment Conservation Harness CLI |
|
|
|
|
|
Runs the operational harness via a single command so users don't need to |
|
|
navigate internal modules. Outputs JSON/CSV receipts. |
|
|
""" |
|
|
|
|
|
import os |
|
|
import json |
|
|
import argparse |
|
|
from datetime import datetime |
|
|
|
|
|
|
|
|
os.environ.setdefault("MPLBACKEND", "Agg") |
|
|
|
|
|
def _now_iso() -> str: |
|
|
return datetime.utcnow().replace(microsecond=0).isoformat() + "Z" |
|
|
|
|
|
def simple_extraction(text: str, quiet: bool = False, as_json: bool = False) -> int: |
|
|
"""Simple commitment extraction (default mode).""" |
|
|
try: |
|
|
from src.extraction import extract_hard_commitments |
|
|
import spacy |
|
|
except ImportError as e: |
|
|
print(f"Import error: {e}", file=__import__('sys').stderr) |
|
|
return 1 |
|
|
|
|
|
try: |
|
|
nlp = spacy.load("en_core_web_sm") |
|
|
except OSError: |
|
|
print("Error: spaCy model 'en_core_web_sm' not found.", file=__import__('sys').stderr) |
|
|
print("Install with: python -m spacy download en_core_web_sm", file=__import__('sys').stderr) |
|
|
return 1 |
|
|
|
|
|
commitments = extract_hard_commitments(text, nlp) |
|
|
|
|
|
if as_json: |
|
|
import json |
|
|
print(json.dumps({"input": text, "commitments": sorted(list(commitments))}, indent=2)) |
|
|
elif quiet: |
|
|
for c in sorted(commitments): |
|
|
print(c) |
|
|
else: |
|
|
print(f"Extracted {len(commitments)} commitment(s) from: \"{text[:60]}{'...' if len(text) > 60 else ''}\"") |
|
|
if commitments: |
|
|
for i, c in enumerate(sorted(commitments), 1): |
|
|
print(f" {i}. {c}") |
|
|
else: |
|
|
print(" (none)") |
|
|
|
|
|
return 0 |
|
|
|
|
|
def main() -> int: |
|
|
import sys |
|
|
|
|
|
|
|
|
if len(sys.argv) > 1 and sys.argv[1] == "run": |
|
|
|
|
|
return run_experiment() |
|
|
else: |
|
|
|
|
|
return run_simple_extraction() |
|
|
|
|
|
def run_simple_extraction() -> int: |
|
|
"""Simple extraction CLI.""" |
|
|
import sys |
|
|
p = argparse.ArgumentParser( |
|
|
prog="commitment-harness", |
|
|
description="Extract commitments from text.", |
|
|
epilog="For full experiments, use: python analyze.py run {compression|recursion|full}" |
|
|
) |
|
|
p.add_argument("text", help="Text to analyze") |
|
|
p.add_argument("--quiet", "-q", action="store_true", help="Output only commitments (no headers)") |
|
|
p.add_argument("--json", action="store_true", help="Output as JSON") |
|
|
|
|
|
args = p.parse_args() |
|
|
return simple_extraction(args.text, quiet=args.quiet, as_json=args.json) |
|
|
|
|
|
def run_experiment() -> int: |
|
|
"""Experimental harness CLI.""" |
|
|
import sys |
|
|
p = argparse.ArgumentParser( |
|
|
prog="commitment-harness run", |
|
|
description="Run commitment conservation experiments and export receipts." |
|
|
) |
|
|
|
|
|
sub = p.add_subparsers(dest="experiment", required=True) |
|
|
|
|
|
|
|
|
pc = sub.add_parser("compression", help="Run compression sweep on a signal.") |
|
|
pc.add_argument("--signal", required=True, help="Input signal text.") |
|
|
pc.add_argument("--out", default="outputs/compression_receipt.json", help="Output receipt path (json).") |
|
|
|
|
|
|
|
|
pr = sub.add_parser("recursion", help="Run recursion test on a signal.") |
|
|
pr.add_argument("--signal", required=True, help="Input signal text.") |
|
|
pr.add_argument("--depth", type=int, default=8, help="Recursion depth.") |
|
|
pr.add_argument("--enforced", action="store_true", help="Use enforcement mode.") |
|
|
pr.add_argument("--out", default="outputs/recursion_receipt.json", help="Output receipt path (json).") |
|
|
|
|
|
|
|
|
pf = sub.add_parser("full", help="Run the deterministic pipeline (if available).") |
|
|
pf.add_argument("--out", default="outputs/full_receipt.json", help="Output receipt path (json).") |
|
|
|
|
|
|
|
|
sys.argv.pop(1) |
|
|
args = p.parse_args() |
|
|
|
|
|
os.makedirs(os.path.dirname(args.out), exist_ok=True) |
|
|
|
|
|
|
|
|
try: |
|
|
from src.deterministic_pipeline import compression_sweep, recursion_test, deterministic_pipeline |
|
|
except ImportError: |
|
|
|
|
|
try: |
|
|
from src.test_harness import compression_sweep, recursion_test |
|
|
deterministic_pipeline = None |
|
|
except Exception as e: |
|
|
raise SystemExit(f"Import error: {e}\n(Verify you run this from the harness/ directory.)") |
|
|
|
|
|
receipt = { |
|
|
"timestamp_utc": _now_iso(), |
|
|
"experiment": args.experiment, |
|
|
"python": { |
|
|
"mpl_backend": os.environ.get("MPLBACKEND"), |
|
|
}, |
|
|
} |
|
|
|
|
|
if args.experiment == "compression": |
|
|
sigma_vals, fid_vals = compression_sweep(args.signal) |
|
|
receipt.update({ |
|
|
"input_signal": args.signal, |
|
|
"n": len(fid_vals), |
|
|
"sigma_values": sigma_vals, |
|
|
"fidelities": fid_vals, |
|
|
}) |
|
|
|
|
|
elif args.experiment == "recursion": |
|
|
deltas = recursion_test(args.signal, depth=args.depth, enforced=args.enforced) if hasattr(recursion_test, '__code__') and 'enforced' in recursion_test.__code__.co_varnames else recursion_test(args.signal, depth=args.depth) |
|
|
receipt.update({ |
|
|
"input_signal": args.signal, |
|
|
"depth": args.depth, |
|
|
"enforced": args.enforced if hasattr(recursion_test, '__code__') and 'enforced' in recursion_test.__code__.co_varnames else False, |
|
|
"deltas": deltas, |
|
|
}) |
|
|
|
|
|
elif args.experiment == "full": |
|
|
if deterministic_pipeline is None: |
|
|
raise SystemExit("deterministic_pipeline not available. (Missing src/deterministic_pipeline.py import.)") |
|
|
result = deterministic_pipeline() |
|
|
receipt.update({"result": result}) |
|
|
|
|
|
with open(args.out, "w", encoding="utf-8") as f: |
|
|
json.dump(receipt, f, indent=2, ensure_ascii=False) |
|
|
|
|
|
print(f"✓ Wrote receipt: {args.out}") |
|
|
return 0 |
|
|
|
|
|
if __name__ == "__main__": |
|
|
raise SystemExit(main()) |
|
|
|