oc_chat
Loading...
Searching...
No Matches
regenerate.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# oc_chat/registry/cli/regenerate.py
4
5# OCT-LINT: disable=tests_exist reason="Covered by oc_chat/tests/registry/regenerate/test_pipeline.py"
6"""
7The ``regenerate`` CLI command (Plan 142 P1.4, §13).
8
9Purpose
10-------
11Expose ``oct agent regenerate`` / ``python -m oc_chat.registry regenerate`` — the
12deterministic source -> worktree-local authored-projection store rebuild. Emits
13the §10 JSON envelope and the §5.3 exit codes.
14
15Responsibilities
16----------------
17- Resolve the source tree + the worktree-local authored-projection DB path
18 (NEVER the shared live registry).
19- Invoke the pipeline and map the result to the envelope + exit code.
20
21Diagnostics
22-----------
23Domain: OC_CHAT.MAIN
24Levels:
25 L2 - CLI lifecycle
26 L3 - argument resolution
27 L4 - envelope emission
28
29Contracts
30---------
31- The default ``--db`` is a separate authored-projection file, never the live
32 ``registry.sqlite`` — regenerate never reads/writes the shared registry.
33
34Dependencies
35------------
36- argparse (CLI parsing)
37- oc_chat.registry.regenerate.pipeline (regenerate runs the source to authored-projection store rebuild)
38- oc_chat.registry.regenerate.envelope (exit_code_for maps a failed result to its exit code)
39- oc_chat.registry.discovery (workspace resolution for path defaults)
40- oc_diagnostics (structured debug logging via _dbg)
41"""
42from __future__ import annotations
43
44import argparse
45import json
46from pathlib import Path
47
48from oc_diagnostics import _dbg
49
50from oc_chat.registry.discovery import discover_registry_location
51from oc_chat.registry.regenerate.envelope import exit_code_for
52from oc_chat.registry.regenerate.pipeline import regenerate
53
54_dbg("OC_CHAT.MAIN", "module", "loaded", 4)
55
56
57def add_regenerate_subcommand(subcommands: argparse._SubParsersAction) -> None:
58 """Attach the top-level ``regenerate`` command (§13)."""
59
60 parser = subcommands.add_parser(
61 "regenerate",
62 help="Rebuild the worktree-local authored-projection store from source/.",
63 )
64 parser.add_argument("registry", nargs="?")
65 parser.add_argument("--source", dest="source")
66 parser.add_argument("--db", dest="db")
67 parser.add_argument("--backend", default="sqlite")
68 parser.add_argument("--scope", default="authored", choices=["authored", "runtime", "all"])
69 parser.add_argument("--dry-run", action="store_true")
70 parser.add_argument(
71 "--force",
72 action="store_true",
73 help="Reclaim a stale regenerate lock (logged; never skips validation).",
74 )
75 parser.add_argument("--json", action="store_true")
76 parser.set_defaults(handler=_cmd_regenerate)
77
78
79def _cmd_regenerate(args: argparse.Namespace) -> int:
80 func = "_cmd_regenerate"
81 location = discover_registry_location(registry_path=args.registry)
82 workspace = Path(location.workspace_root)
83 source_dir = Path(args.source) if args.source else workspace / "oc_chat" / "roadmap" / "source"
84 db_path = (
85 Path(args.db)
86 if args.db
87 else workspace / ".option_c" / "orchestration" / "registry_authored.sqlite"
88 )
89 db_path.parent.mkdir(parents=True, exist_ok=True)
90 if args.dry_run:
91 db_path = db_path.with_name("regen_dryrun.sqlite")
92 # R5: the CLI always locks at the workspace lock path (independent of --db);
93 # a --dry-run serializes with real runs by design (N-a). lock.py mkdirs the
94 # lock parent (H5), so a non-default --db in a fresh workspace still works.
95 lock_path = workspace / ".option_c" / "orchestration" / "regenerate.lock"
96 result = regenerate(
97 source_dir, db_path, backend=args.backend, scope=args.scope,
98 lock_path=lock_path, force=args.force,
99 )
100 _dbg("OC_CHAT.MAIN", func, f"regenerate ok={result.ok}", 3)
101 if args.json:
102 print(json.dumps(result.envelope, indent=2, sort_keys=True))
103 elif result.ok:
104 print(
105 f"regenerate ok ({result.records} records, "
106 f"logical_state_hash={result.logical_state_hash})"
107 )
108 else:
109 print(f"regenerate failed [{result.envelope.get('code')}]: {result.envelope.get('message')}")
110 return 0 if result.ok else exit_code_for(result.error)
None add_regenerate_subcommand(argparse._SubParsersAction subcommands)
Definition regenerate.py:57
int _cmd_regenerate(argparse.Namespace args)
Definition regenerate.py:79