oc_chat
Loading...
Searching...
No Matches
plan_manage.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# oc_chat/scripts/plan_manage.py
4
5# OCT-LINT: disable=tests_exist reason="Wiring shim; covered by oc_chat/tests/unit/test_plan_manage_manifest.py and test_259_dispatch_hooks.py"
6"""
7Plan-inventory and dispatch CLI — moved to :mod:`oc_orchestrator.plan_manage`.
8
9Purpose
10-------
11Plan 270 P4. The CLI now lives in the standalone ``oc_orchestrator``
12package. This file keeps ``oc_chat.scripts.plan_manage`` working — the
13path ``scripts/dispatch.ps1`` and every orchestrator command have always
14named — and supplies the two defaults that are oc_chat's and not the
15package's:
16
17- **the program name.** ``--help`` must print exactly what it printed
18 before the split, and ``prog`` is the only line in it that could differ.
19- **the dispatch hooks.** The package declares a ``DispatchHooks``
20 Protocol and announces to nobody; oc_chat has a hub and a work queue,
21 so this is where :func:`hooks_for_dispatch` is built — a dispatch still
22 records its ``dispatch`` message and, for a plan that really was
23 pending, its ``dequeue``.
24
25Unlike the eight ``core/`` shims this module is NOT a rebinding, because
26it has behaviour of its own: those two defaults. Every other name is
27delegated by :func:`__getattr__`.
28
29Responsibilities
30----------------
31- :func:`main` — the package's CLI with oc_chat's ``prog`` and hooks.
32- :func:`_dispatch_hooks` — the live hub listener, verbatim from the
33 pre-270 module, including its fail-soft refusal.
34- :func:`__getattr__` — every other name, delegated unchanged.
35
36Diagnostics
37-----------
38Domain: OC_CHAT.MAIN
39Levels:
40 L2 - lifecycle events (hub hooks unavailable)
41 L3 - semantic details
42 L4 - deep tracing (delegated name lookups)
43
44Contracts
45---------
46- Output is byte-identical to the pre-270 module for every sub-command,
47 and so are the hub entries and queue transitions a dispatch produces.
48- A hub that cannot be built is never allowed to block a dispatch: the
49 refusal is logged and ``NO_HOOKS`` is returned, exactly as before.
50- Importing this module registers oc_chat's ``oct`` timing hook, through
51 the ``core/worktree_env`` import below. Without it the engine timing
52 ledger would silently stop filling on the dispatch path, because the
53 moved package treats ``oct`` as absent by default.
54- Retirement rule (plan 270 P8): this path stays until an explicit
55 retirement plan retargets ``dispatch.ps1`` and the workflow doc. New
56 code names ``oc_orchestrator.plan_manage`` directly.
57
58Dependencies
59------------
60- oc_diagnostics (structured debug logging via _dbg)
61- oc_orchestrator.plan_manage (the moved module this path wraps)
62- oc_chat.core.worktree_env (imported for its side effect: the oct hook)
63- oc_orchestrator.hub_manage (lazy; hooks_for_dispatch, the hooks this path registers; moved there by plan 272, which is why this one names the package and not the shim)
64- oc_orchestrator.dispatch (lazy; NO_HOOKS, returned when the hub hooks cannot be built)
65"""
66
67from __future__ import annotations
68
69from pathlib import Path
70import sys
71from typing import Any
72
73from oc_diagnostics import _dbg
74
75from oc_orchestrator import plan_manage as _moved
76
77# Imported for its SIDE EFFECT and nothing else: the shim registers
78# oc_chat's oct timing hook on the moved worktree_env module. The
79# dispatch path reaches worktree_env through the package, never through
80# oc_chat, so without this import the engine.dispatch / engine.prepare /
81# engine.teardown rows would quietly stop being written.
82from oc_chat.core import worktree_env as _worktree_env # noqa: F401
83
84#: The program name this path has always printed in ``--help``.
85PROG = "python -m oc_chat.scripts.plan_manage"
86
87_dbg("OC_CHAT.MAIN", "module", "plan_manage shim -> oc_orchestrator.plan_manage", 4)
88
89
90def __getattr__(name: str) -> Any:
91 """Delegate every name this shim does not define to the moved module."""
92 return getattr(_moved, name)
93
94
95def _dispatch_hooks(repo_root: Path) -> Any:
96 """The live hub listener for one dispatch (plan 259 P6).
97
98 Registered by the CALLER, which is this function: the package
99 declares a Protocol and a ``NO_HOOKS`` default and names neither the
100 hub nor the queue, exactly as it declares nothing about the registry.
101 A dispatch becomes a ``dispatch`` message and, for an item that was
102 actually PENDING, one ``dequeue`` with ``reason="dispatched"``.
103
104 Returns ``NO_HOOKS`` when the hub cannot be built, so a dispatch is
105 never blocked by the thing that only watches it.
106 """
107 func = "plan_manage._dispatch_hooks"
108 from oc_orchestrator.dispatch import NO_HOOKS
109
110 try:
111 from oc_orchestrator.hub_manage import hooks_for_dispatch
112
113 return hooks_for_dispatch(
114 queue_dir=repo_root / "oc_chat" / ".option_c" / "orchestration",
115 comms_root=repo_root / "oc_chat" / ".option_c" / "comms",
116 )
117 except Exception as exc: # noqa: BLE001 - EXTERNAL_ERROR (L2); watching is optional
118 _dbg("OC_CHAT.MAIN", func,
119 f"EXTERNAL_ERROR: hub hooks unavailable ({type(exc).__name__}: "
120 f"{exc}); dispatching without them", 2)
121 return NO_HOOKS
122
123
124def main(argv: list[str] | None = None) -> int:
125 """Run one sub-command with oc_chat's program name and hub hooks."""
126 return _moved.main(argv, prog=PROG, dispatch_hooks=_dispatch_hooks)
127
128
129if __name__ == "__main__":
130 sys.exit(main())
Any _dispatch_hooks(Path repo_root)