Orchestrata Ultimate › Plans › refactor-convergence-design

Refactor lanes convergence — LSP + interpreter functions and our AST (design)

Source: plans/refactor-convergence-design.md at 8a2cb42ad · last changed 2026-10-20 · first committed 2026-10-20 · 139 lines.

Prose pending.

Plan text

Refactor lanes convergence — LSP + interpreter functions and our AST (design)

Status: PROPOSED, partially ruled (ADR-0117, Mohamed 2026-10-20 "A": the SPEC-109 engine MOVES to codetools — P-A rescoped to MOVE-109, §6 decision 1 ratified. Decisions 2-3 and the codemod/ctok location remain open. RUL-022: no dispatch executes the open parts until ruled; MOVE-109 carries ADR-0117 as its contract.) Author: coordinator/architect seat, 2026-10-20. All numbers measured this session; §8 is the reproduction ledger. Origin: Mohamed, 2026-10-20, verbatim: "consider the functions of an LSP and the python interpreter for refactoring, can we extract or use such functions with our ast?" — approved scope "a + b": deciding measurements + this design, folded into the queued packets.

1. The short answer

The LSP and interpreter functions are ALREADY extracted — across five surfaces in this repo, two of them real engines (SPEC-109's basedpyright JSON-RPC client, SPEC-110's transactional codemod workspace), one of them the interpreter's own compiler (symtable, inside the toolkit rename). The defect is not extraction; it is convergence and precision: three rename engines with no shared discovery vocabulary, inferred types that are consumed and discarded instead of resolving the KB's fail-closed cases, and two measured resolver-precision gaps (from-import asnames lost; same-bare-name classes silently zero-matching). This design assigns lanes, names the packets that close the gaps by REUSE, and lists what we deliberately do not build.

2. Measured inventory (2026-10-20)

Surface Spec What it gives Engine Wiring status (measured)
tools/lsp_tool.py SPEC-065 definition / references / hover / document_symbols / diagnostics hand-rolled ast.walk name-matching for the first four (hover's own docstring: "no live type inference (no jedi/pyright-langserver session)"); pyright CLI for diagnostics (__init__(command="pyright") :610) WIRED: agents/tool_registry.py:2092 registers LSPTool
refactor/rename/ SPEC-109 prepareRename/rename/definition/open_document over stdio JSON-RPC; hard no-name-matching timeout policy (REQ-109-103); scanner→plan→executor→applier, module_move, string_refs, residue, report REAL LSP: basedpyright-langserver (MIT, bundled Node); design ratified by live spike 2026-07-27 (plans/rename-engine-design.md) CLI seam SHIPPED (scripts/rename_engine.py, 10.3KB); volk-tool registration DECLARED-DEFERRED (design §5: "~6-line follow-up"); refactor.rename.tool has 0 production importers
refactor/codemod/ SPEC-110 transactional workspace: TransformPass family (incl. RenameSymbolPass), daemon/server/RPC client, AnalysisPass protocol, Bolt + in-memory persistence, review own CIG (CodeIntelligenceGraph) + ast overlay CLI seam exists (scripts/codemod.py {load-bench,run,report,analyze}); codemod.daemon 0 importers; observability layer unwired (no CodemodEvent producer — backlog); AnalysisPass implementations ABSENT (REQ-110-031/032)
scripts/serialize_codebase_ast.py rename/move-symbol/extract-class — KB-native refactoring: token-precise, scope-aware via symtable (the interpreter's own compiler symbol table), type-scoped method rename, fail-closed on undeterminable receivers repo AST KB + symtable WIRED: the coordinator's daily lane; ast_api.py exposes read-only ops over HTTP
scripts/check_async_await.py — async-correctness gate, "resolve-by-type via Pyright" pyright as resolver library WIRED (gate)
code_intel.py / rts.py / KB — beyond-LSP: affected-symbols transitive closure (tabled Prolog, resolves 7 import cycles), literals, callers/callees type-resolved, body_hash/residue_hash change visibility, freshness Prolog KB over the serialized snapshot WIRED (RUL-028/029 doctrine; P-RUL040 rides it)
cst_lsp (pipx, user-level) — LibCST 1.8.5 + lsprotocol server (format-preserving CST) external NOT a repo dependency; codetools-adjacent; stays external

Dependency graph already carries: pyright>=1.1.411, basedpyright>=1.39.9 (pyproject dev group); pyright 1.1.411 + basedpyright + node all runnable in this worktree (measured).

3. Deciding measurements

4. Lane architecture (the convergence)

Lane Owner surface Function Rule
ANALYSIS — structure, closure, change-visibility AST KB: serialize_codebase_ast.py + code_intel.py + rts.py hashes (+ symtable for scopes) refs/who-imports/affected-symbols/unused/literals/branches/freshness the only lane that answers "what exists / what does this touch"; everything else consumes it
INFERENCE — types Pyright/basedpyright, consumed as (a) LSP server through SPEC-109's lsp_client — moving to codetools per ADR-0117, consumed as the deployed CLI, (b) resolver as in check_async_await receiver-type resolution, hover types, definition-by-type queried ON DEMAND at refactor time; NOT persisted into the KB by default (§6 decision 3)
EXECUTION — writes SPEC-110 codemod txn = the single transactional multi-pass write path; SPEC-109 = the type-resolved rename PLANNER producing workspace edits; toolkit rename = KB-native fast path applying changes atomically, reviewably, revertibly lane split for rename: name/func/class/module kinds → toolkit (KB-precise, symtable-scoped); method/type-scoped kinds → SPEC-109 (its founding class); a rename riding inside a larger codemod → RenameSymbolPass in the txn. All three consume ONE refs vocabulary (§6 decision 2)
GROUND TRUTH — runtime interpreter side: BDD Phase 3 EXECUTED grade (runtime provenance, designed not built), sanctioned-runner green proofs, per-file coverage "this code state actually executed" vs "should cover" no refactor packet is done without a sanctioned green proof + affected-set verdict (RUL-029/032)

5. Packets (fold into the queued lane — approval "b")

id Packet Folds Shape
P-A MOVE-109 (rescoped by ADR-0117, Mohamed ruling "A" 2026-10-20) SPEC-109 engine + CLI + 13 test modules + spec bookkeeping MOVE to codetools (its refactor lane — measured: import-level primitives only, zero pyright/LSP there); decouple the 5 external orchestrata imports (traces/tracer/tools.base/lsp_tool-helpers/registry); rule the module_move overlap in-packet; deploy /opt/codetools/bin; orchestrata consumes the deployed CLI — contracts seam ONLY on a concrete volk consumer (named trigger); two-copy parity: the M5 sanctioned green proof runs BEFORE the move (its oracle), byte-faithful move, codetools green AFTER; orchestrata-side RTM/spec disposition records the move (REQ-109 never dangles). Absorbs the standing "persistent LSP session backend (065)" item — the moved engine IS the persistent-session machinery. codemod/ctok location: open, own ruling (ADR-0117 decision 6). backlog 065 item; M1's refusal class; ADR-0117 cross-repo packet (orchestrata + codetools); contract = SPEC-109 design + ADR-0117
P-B CIG-PASSES Implement SPEC-110's absent AnalysisPasses (REQ-110-031/032) by DELEGATION to code_intel/KB: BlastRadius ← affected-symbols, DeadCode ← unused, Complexity ← complexity, Coupling/Cycles ← cycles+call-graph, BreakingChange ← refs+closure. No new analysis code; the pass = adapter + frozen dataclass per the protocol backlog SPEC-110 gaps item (REQ-110-031/032/033-035 split: 031/032 close here; 033-035 stay a spec-amendment decision) worker packet; contract = analysis/protocol.py
P-C ASNAME Capture from-import asnames in orchestrata_ast (standing task, M2 re-confirmed): names entry gains the alias; consumers (refs, rename lookup, sync) audited for the shape change existing backlog task worker packet; model-shape ripple = its design note
P-D DISAMBIG Rename/symbols lookup on an ambiguous bare class name must ERROR with the candidate list (file:line each), never silent 0-edit (M3); same for code_intel callers qname misses new, from M3 small worker packet, toolkit fence
P-E TYPEFACTS (deferred) Persist pyright-inferred type facts into the KB so refs/rename resolve receivers without a live server. DEFERRED behind §6 decision 3: staleness cost (every edit invalidates types) vs on-demand query; decide from P-A's measured refusal-residual, not now — decision gate, not a packet

Sequencing: P-C + P-D are precision prerequisites (small); P-A is the headline (turns measured refusals into completed renames); P-B closes a spec gap by reuse. None blocks the current queue (P-RUL039/040, build-order packets, INT-02+) — they slot into the debts/hardening lane.

6. Decisions owed to Mohamed (one recommendation each)

  1. Lane split vs single engine. Keep three rename surfaces with the §4 lane rule, or converge on SPEC-109 as the only rename engine (toolkit rename delegates everything)? RULED 2026-10-20 (ADR-0117): lane split stands — and the SPEC-109 lane lives in codetools (engine moves; toolkit KB-native rename stays; codemod txn stays pending its own location ruling).
  2. Shared refs vocabulary. The three engines each carry refs discovery; the standing "no shared finding/evidence vocabulary in core/" item is the same defect. Fold a common discovery/evidence protocol into that item's resolution, with ANALYSIS (KB) as the single source? Recommendation: yes, as the vocabulary item's design input — no third parallel model (Phase B unification of ast_models is the precedent).
  3. Type-fact persistence (P-E). Persist inferred types into the KB, or keep inference on-demand? Recommendation: defer — measure the refusal residual after P-A lands; persist only if on-demand latency or offline demand proves real. Stale type facts in a KB whose freshness discipline is file-hash-based would be a new lie class.

7. Explicitly NOT doing

8. Evidence ledger (reproduce)

# M1 refusal
.venv/bin/python scripts/serialize_codebase_ast.py rename --old begin --new begin_probe \
  --kind method --class SessionController --source src/orchestrata_ultimate   # dry-run
# M2 asname probe: /tmp/opencode/asname-probe/probe_mod.py + serialize + jq .imports
# M3 ambiguity
.venv/bin/python scripts/serialize_codebase_ast.py symbols --pattern "^PrologBridge$" --kind class
.venv/bin/python scripts/serialize_codebase_ast.py rename --old assertz --new assertz_probe \
  --kind method --class PrologBridge --source src/orchestrata_ultimate        # "0 edit(s)", silent
# M4 wiring: who-imports tools.lsp_tool | refactor.codemod.{server,daemon} | refactor.rename.tool
# M5: pytest-live tests/refactor/rename/ → RUL-038 guard names the sanctioned 13-file invocation
# M6: .venv/bin/pyright --version → 1.1.411; pyproject dev group

Cross-references: plans/rename-engine-design.md (SPEC-109, ratified+spiked), specs/109-rename-engine/, specs/110-codemod-engine/, specs/065-lsp-tool/, backlog items: asname capture, shared finding/evidence vocabulary, SPEC-110 gaps, tool-hardening batch (065), Neo4jSyncer name-index collision (M3's graph-side twin).