oc_chat
Loading...
Searching...
No Matches
plan.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# oc_chat/registry/cli/plan.py
4
5# OCT-LINT: disable=tests_exist reason="Covered by oc_chat/tests/registry/test_domain_plan.py"
6"""
7Registry-native plan commands.
8
9Purpose
10-------
11Expose plan lifecycle mutation and inspection commands beneath
12``python -m oc_chat.registry plan``.
13
14Responsibilities
15----------------
16- Parse plan domain command arguments.
17- Resolve the registry and actor slot.
18- Dispatch to domain API and reader helpers.
19
20Diagnostics
21-----------
22Domain: OC_CHAT.MAIN
23Levels:
24 L2 - lifecycle events
25 L3 - semantic details
26 L4 - deep tracing
27
28Contracts
29---------
30- Mutating commands emit one operation-log row through the seam.
31- ``show`` is read-only and returns the current projected plan row.
32
33Dependencies
34------------
35- oc_diagnostics (structured debug logging via _dbg)
36- oc_chat.registry.backends.sqlite (SQLite backend)
37- oc_chat.registry.discovery (registry path discovery)
38- oc_chat.registry.domain (plan mutation API)
39- oc_chat.registry.readers (plan readers)
40- oc_chat.registry (Registry facade for plan mutations and readers)
41"""
42from __future__ import annotations
43
44import argparse
45import json
46from typing import Callable
47
48from oc_diagnostics import _dbg
49
50from oc_chat.registry import Registry
51from oc_chat.registry.backends.sqlite import SQLiteBackend
52from oc_chat.registry.discovery import discover_registry_location
53from oc_chat.registry.domain import (
54 plan_assign,
55 plan_create,
56 plan_reimport,
57 plan_set_status,
58 plan_supersede,
59)
60from oc_chat.registry.readers import show_plan
61
62
63_dbg("OC_CHAT.MAIN", "module", "loaded", 4)
64
65
66def add_plan_subcommands(plan_parser: argparse.ArgumentParser) -> None:
67 """Attach registry-native plan subcommands."""
68
69 plan_parser.set_defaults(handler=_cmd_plan_group, plan_parser=plan_parser)
70 subcommands = plan_parser.add_subparsers(dest="plan_command")
71
72 create = subcommands.add_parser("create", help="Emit plan.create")
73 _add_common(create)
74 create.add_argument("plan_id")
75 create.add_argument("--plan-number", required=True, type=int)
76 create.add_argument("--title", required=True)
77 create.add_argument("--body-path", required=True)
78 create.add_argument("--body-sha256", required=True)
79 create.add_argument("--plan-type")
80 create.add_argument("--model-override")
81 create.add_argument("--thinking-override")
82 create.add_argument("--session-override")
83 create.set_defaults(handler=_cmd_create)
84
85 show = subcommands.add_parser("show", help="Show a projected plan row")
86 show.add_argument("plan_id")
87 show.add_argument("--registry", help="Registry SQLite path.")
88 show.add_argument("--json", action="store_true")
89 show.set_defaults(handler=_cmd_show)
90
91 set_status = subcommands.add_parser("set-status", help="Emit plan.set_status")
92 _add_common(set_status)
93 set_status.add_argument("plan_id")
94 set_status.add_argument("--from-status", required=True)
95 set_status.add_argument("--to-status", required=True)
96 set_status.add_argument(
97 "--source",
98 required=True,
99 choices=["worker_self", "orchestrator", "round_lifecycle"],
100 )
101 set_status.add_argument("--note")
102 set_status.set_defaults(handler=_cmd_set_status)
103
104 assign = subcommands.add_parser("assign", help="Emit plan.assign")
105 _add_common(assign)
106 assign.add_argument("plan_id")
107 assign.add_argument("--from-slot")
108 assign.add_argument("--to-slot", required=True)
109 assign.add_argument("--branch", required=True)
110 assign.add_argument("--worktree", required=True)
111 assign.add_argument("--queue-position", required=True, type=int)
112 assign.set_defaults(handler=_cmd_assign)
113
114 supersede = subcommands.add_parser("supersede", help="Emit plan.supersede")
115 _add_common(supersede)
116 supersede.add_argument("plan_id")
117 supersede.add_argument("--by", required=True, dest="superseded_by_plan_id")
118 supersede.add_argument("--reason", required=True)
119 supersede.set_defaults(handler=_cmd_supersede)
120
121 reimport = subcommands.add_parser("reimport", help="Emit plan.reimport")
122 _add_common(reimport)
123 reimport.add_argument("plan_id")
124 reimport.add_argument("--previous-body-sha256", required=True)
125 reimport.add_argument("--new-body-sha256", required=True)
126 reimport.add_argument("--previous-body-observed-at", required=True)
127 reimport.add_argument("--new-body-observed-at", required=True)
128 reimport.set_defaults(handler=_cmd_reimport)
129
130
131def _cmd_plan_group(args: argparse.Namespace) -> int:
132 parser = args.plan_parser
133 parser.print_help()
134 return 1
135
136
137def _cmd_create(args: argparse.Namespace) -> int:
138 return _emit(
139 args,
140 lambda registry, actor_slot: plan_create(
141 registry,
142 plan_id=args.plan_id,
143 plan_number=args.plan_number,
144 title=args.title,
145 body_path=args.body_path,
146 body_sha256=args.body_sha256,
147 plan_type=args.plan_type,
148 model_override=args.model_override,
149 thinking_override=args.thinking_override,
150 session_override=args.session_override,
151 actor_slot=actor_slot,
152 actor_type="human_operator",
153 command="plan create",
154 ),
155 )
156
157
158def _cmd_show(args: argparse.Namespace) -> int:
159 registry, _backend = _registry(args.registry)
160 view = show_plan(registry, args.plan_id)
161 if view is None:
162 print(f"plan not found: {args.plan_id}")
163 return 1
164 if args.json:
165 print(json.dumps(view.__dict__, indent=2, sort_keys=True))
166 else:
167 print(f"{view.plan_id} | {view.status} | {view.assigned_slot or '-'}")
168 return 0
169
170
171def _cmd_set_status(args: argparse.Namespace) -> int:
172 return _emit(
173 args,
174 lambda registry, actor_slot: plan_set_status(
175 registry,
176 plan_id=args.plan_id,
177 from_status=args.from_status,
178 to_status=args.to_status,
179 source=args.source,
180 note=args.note,
181 actor_slot=actor_slot,
182 actor_type="human_operator",
183 command="plan set-status",
184 ),
185 )
186
187
188def _cmd_assign(args: argparse.Namespace) -> int:
189 return _emit(
190 args,
191 lambda registry, actor_slot: plan_assign(
192 registry,
193 plan_id=args.plan_id,
194 from_slot=args.from_slot,
195 to_slot=args.to_slot,
196 branch=args.branch,
197 worktree=args.worktree,
198 queue_position=args.queue_position,
199 actor_slot=actor_slot,
200 actor_type="human_operator",
201 command="plan assign",
202 ),
203 )
204
205
206def _cmd_supersede(args: argparse.Namespace) -> int:
207 return _emit(
208 args,
209 lambda registry, actor_slot: plan_supersede(
210 registry,
211 plan_id=args.plan_id,
212 superseded_by_plan_id=args.superseded_by_plan_id,
213 reason=args.reason,
214 actor_slot=actor_slot,
215 actor_type="human_operator",
216 command="plan supersede",
217 ),
218 )
219
220
221def _cmd_reimport(args: argparse.Namespace) -> int:
222 return _emit(
223 args,
224 lambda registry, actor_slot: plan_reimport(
225 registry,
226 plan_id=args.plan_id,
227 previous_body_sha256=args.previous_body_sha256,
228 new_body_sha256=args.new_body_sha256,
229 previous_body_observed_at=args.previous_body_observed_at,
230 new_body_observed_at=args.new_body_observed_at,
231 actor_slot=actor_slot,
232 actor_type="human_operator",
233 command="plan reimport",
234 ),
235 )
236
237
239 args: argparse.Namespace,
240 emit: Callable[[Registry, str], object],
241) -> int:
242 registry, backend = _registry(args.registry)
243 actor_slot = args.actor_slot or backend.active_operator_slot()
244 if actor_slot is None:
245 print("no active operator slot found")
246 return 1
247 row = emit(registry, actor_slot)
248 print(f"operation {getattr(row, 'id')} state_version={getattr(row, 'state_version')}")
249 return 0
250
251
252def _registry(registry_path: str | None) -> tuple[Registry, SQLiteBackend]:
253 location = discover_registry_location(registry_path=registry_path)
254 backend = SQLiteBackend(location.registry_path)
255 return Registry(backend), backend
256
257
258def _add_common(parser: argparse.ArgumentParser) -> None:
259 parser.add_argument("--registry", help="Registry SQLite path.")
260 parser.add_argument("--actor-slot", help="Actor slot. Defaults to active operator.")
int _cmd_assign(argparse.Namespace args)
Definition plan.py:188
None add_plan_subcommands(argparse.ArgumentParser plan_parser)
Definition plan.py:66
int _cmd_set_status(argparse.Namespace args)
Definition plan.py:171
int _emit(argparse.Namespace args, Callable[[Registry, str], object] emit)
Definition plan.py:241
int _cmd_show(argparse.Namespace args)
Definition plan.py:158
tuple[Registry, SQLiteBackend] _registry(str|None registry_path)
Definition plan.py:252
int _cmd_reimport(argparse.Namespace args)
Definition plan.py:221
int _cmd_create(argparse.Namespace args)
Definition plan.py:137
int _cmd_supersede(argparse.Namespace args)
Definition plan.py:206
None _add_common(argparse.ArgumentParser parser)
Definition plan.py:258
int _cmd_plan_group(argparse.Namespace args)
Definition plan.py:131