Orchestrata Ultimate › Plans › spec-021-port-map

SPEC-021 Port Map — OpenCode Integration → orchestrata-ultimate

Source: plans/spec-021-port-map.md at 8a2cb42ad · last changed 2026-07-27 · first committed 2026-07-13 · 518 lines.

Prose pending.

Plan text

SPEC-021 Port Map — OpenCode Integration → orchestrata-ultimate

Read-only audit. Source: /home/mohamed/workspace/orchestrata/open-code/ (frozen). Deployed twin: ~/.opencode/ (running @opencode-ai/plugin@1.3.15, confirmed via ~/.opencode/node_modules/@opencode-ai/plugin/package.json). Oracle: specs/021-opencode-integration/spec.md + checklists/requirements.md (295 REQ-021 rows, not 299 — see drift note) + checklists/testing.md.

Classifications: PORT-CLEAN (works as-is) · RETARGET (needs concrete path/id/schema changes, named below) · BUILD-NEW (no backend exists in ultimate) · OBSOLETE/SUPERSEDED (ultimate already does this, differently or better).


0. CRITICAL — read this before planning any wave

Most of the plugin's advertised lifecycle behavior almost certainly never fires in the deployed app, independent of any porting work. orchestrata.ts registers 8 top-level hook keys on the object it returns:

session.status, session.created, session.idle, tool.execute.before,
tool.execute.after, message.updated, file.edited, experimental.session.compacting

The actual Hooks interface exported by @opencode-ai/plugin — checked in both the version pinned by the source config/package.json (1.3.3, reference copy at open-code/reference/node_modules/@opencode-ai/plugin/dist/index.d.ts:123-247) and the version physically installed at the live deployment target, ~/.opencode/node_modules/@opencode-ai/plugin (resolved to 1.3.15 per its package.json, type surface at ~/.opencode/node_modules/@opencode-ai/plugin/dist/index.d.ts:142-267) — contains only:

event, config, tool, auth, provider, chat.message, chat.params, chat.headers,
permission.ask, command.execute.before, tool.execute.before, shell.env,
tool.execute.after, experimental.chat.messages.transform,
experimental.chat.system.transform, experimental.session.compacting,
experimental.text.complete, tool.definition

Of the plugin's 8 registered keys, only 3 are real: tool.execute.before, tool.execute.after, experimental.session.compacting. The other 5 — session.status, session.created, session.idle, message.updated, file.edited — are not part of the Hooks type in either version. Session-lifecycle and file-edit notifications do exist in the SDK, but only as members of the discriminated Event union consumed through the single generic event hook (@opencode-ai/sdk types.gen.d.ts:602: EventSessionStatus | EventSessionIdle | ... | EventFileEdited | EventSessionCreated | ..., with EventSessionIdle at line 413 type: "session.idle", etc.) — i.e. the correct pattern is event: async ({ event }) => { switch (event.type) { case "session.idle": ... } }, not a dedicated "session.idle" key on the returned object.

Because OrchestrataPlugin (open-code/plugins/orchestrata.ts:19) has no return-type annotation (Plugin is never applied to it), TypeScript performs no excess-property check and the file compiles cleanly despite exporting 5 dead keys. Whether the actual Go/Bun OpenCode host loosely dispatches by matching hooks[event.type] for any string (in which case these might work despite being unlisted) cannot be confirmed from static .d.ts files alone — this must be verified by runtime instrumentation (e.g. temporarily add console.error at the top of each of the 5 handlers and observe a live OpenCode session) before treating any of the following as "working code to port":

Only these are on solid ground: KG context injection + policy enforcement + git checkpoint (tool.execute.before), tool-call/operation logging (tool.execute.after), the compaction context injector (experimental.session.compacting), and all 28 custom tool entries (the tool field IS in the real Hooks interface, and each tool definition structurally matches ToolDefinition — tool.d.ts — even though none of them call the exported tool() helper; TS structural typing accepts the plain object literals). Recommendation: any rebuild should re-home the 5 dead-hook behaviors onto a single event handler with a switch(event.type), not copy the current key names verbatim.


1. Spec-vs-artifact drift (spec claims that don't survive contact with the code)

# Claim Reality Evidence
D1 FR-001: plugin hooks into "all 7 lifecycle events" Code registers 8 keys (session.status is an uncounted 8th), and per §0 only 3 of the 8 are real SDK hooks orchestrata.ts:254,287,568,673,798,856,1213,1728
D2 FR-009/US-006: "24 custom tools (9 session, 12 KG, 3 conversation)" Actual count is 28: 9 session + 14 KG + 3 conversation + 2 code-review (code-review, code-fix) tool grep at orchestrata.ts:1758-3231; KG tools include kg-trace-req (2546), kg-session-audit (2626), kg-daemon-start/stop/status (2204/2311/2338), kg-cxp-thread (2762) — none counted in the spec's "12"
D3 checklists/requirements.md REQ-021-207..217 enumerate the KG/conversation tools Checklist covers only 8 of 14 KG tools and 2 of 3 conversation tools — kg-daemon-* (3), kg-trace-req, kg-session-audit, kg-cxp-thread, conv-query, code-review, code-fix have zero REQ-021 rows requirements.md:373-413 vs orchestrata.ts tool list
D4 FR-003 / US-002: KG sync queue + conversation queue flush happen "on session.idle" Both flushes actually run inline inside the session.created handler (comments even say "before closing" / "on session close" while living in the create path) — requirements.md's own checklist correctly locates them under "Session Created" (lines 70-99), contradicting spec.md FR-003 orchestrata.ts:406-557 (inside the "session.created" block, lines 287-562)
D5 US-003 / FR-005: "5-Gate Code Review Pipeline" The runtime's own banner and summary text call it "3-GATE" (🔍 COMPREHENSIVE CODE REVIEW (3-GATE)) and the "ALL GATES PASSED" success message only lists Gates 1-3, even though Gates 4 (complexity/nesting) and 5 (naming/design) do execute and can add warnings/errors orchestrata.ts:1619,1624-1627
D6 Spec Appendix ("Visual Architecture > Class Diagram") and specs/021-opencode-integration/compliance-audit.md describe SPEC-021 Both contain a TodoistIntegration class diagram with create_project/create_task/sync_tasks methods — content from an unrelated spec/template, never replaced. compliance-audit.md (12,560 lines / 613 KB) is a stale auto-generated report comparing against TodoistIntegration, not the OpenCode plugin at all spec.md:1701-1713; compliance-audit.md:1-60
D7 Metadata: "Status: Complete" Given D1 (5/8 hooks likely dead) and the concrete bugs in §2, "Complete" overstates verified working behavior spec.md:8

2. Concrete bugs found in the source artifact (carry-forward risk if ported verbatim)

These are defects in the source TypeScript/Python, independent of porting — flagged so none of them get silently reproduced.

  1. kg-daemon-start/stop/status process-matching is broken. The daemon is launched via nohup python3 -c '...' (orchestrata.ts:2224) whose argv never contains the literal string "kg-sync-daemon", yet kg-daemon-start checks pgrep -f "kg-sync-daemon" (2215), kg-daemon-stop runs pkill -f "kg-sync-daemon" (2321), and kg-daemon-status re-checks the same pattern (2348). None of the three can ever find the process they started — start never detects "already running" (duplicate daemons possible), stop never kills it, status always reports NOT running even while it is.
  2. conv-sync's embedded Cypher is not valid Cypher. orchestrata.ts:2898-2906 contains WITH c \n OPTIONAL MATCH (os:OrchestrataSession {seq: $seq}) \n IF os IS NOT NULL \n MERGE (c)-[:FOR_SESSION]->(os) — bare IF is not Cypher grammar (no CASE/APOC). Every invocation of conv-sync with pendingCount > 0 throws a Neo4j syntax error, caught only generically as Sync failed: .... The equivalent flush inside session.created (orchestrata.ts:519-523) omits this broken clause and is fine.
  3. code-fix's mutable-default-arg regex has unbalanced parentheses, orchestrata.ts:3207: r'def \w+\s*\(([^)]*=)\s*\[)(,|\))' — contains a stray unmatched ) (and a literal \[ that looks like a typo for \)). re.sub with this pattern raises re.error in Python at runtime; the whole code-fix tool invocation fails every time (uncaught inside the python3 -c heredoc, surfaces as a generic Fix failed: ... in TS).
  4. Gate 3's bare-except:-with-pass check references an undefined variable. orchestrata.ts:1380: for i, line in enumerate(lines): — lines is defined only in the separate Gate 4 try-block (1461), not in the Gate 3 block (1352-1454) where this loop lives. Any file containing a bare except: triggers a NameError, which the wrapping except Exception as e at 1453 silently swallows into a generic CONTENT CHECK warning — meaning the bare-except detector (and everything textually after it in that same try-block: logging checks, mutable-default check, @traces traceability check) silently stops running for any file that has the very pattern it's supposed to flag.
  5. session_cli.py end --achievements "..." / --upcoming "..." loses all but the last value. session_cli.py's end subcommand defines --achievements with nargs="*" (~/.claude/tools/session_cli.py:503) — i.e. it expects --achievements "A" "B" "C". But the plugin builds achievements.join('" --achievements "') (orchestrata.ts:629,1115,1857), producing --achievements "A" --achievements "B" --achievements "C" — repeated occurrences of the same flag in argparse overwrite, they don't accumulate, so args.achievements ends up ["C"] only. Every session close with 2+ achievements silently drops all but the last one. Same shape of bug applies to --upcoming.
  6. Project-scope leakage via substring match. Every KG/Redis-gated tool checks directory.includes("orchestrata") (e.g. orchestrata.ts:1986, 2033, 2055...). This substring also matches /home/mohamed/workspace/orchestrata (frozen source), orchestrata-ultimate (an earlier scaffold), orchestrata-ultimate-architecture (this repo), and orchestrata-backup-* — meaning any of these sibling checkouts opened in OpenCode would fire the same production Neo4j/Redis writes. Not new in the port, but should be tightened (e.g. exact directory match or a project-id file check) rather than carried forward.
  7. packages/ path filter never matches ultimate's tree. file.edited's gate (orchestrata.ts:1217: filePath?.includes("packages/") && filePath?.endsWith(".py")) and the module-import derivation (1332-1337: splits on /packages/, builds orchestrata.{pkg}.{module}) both hard-assume the 26-package monorepo layout. Ultimate's source lives at src/orchestrata_ultimate/... with no packages/ segment and a single top-level import package (orchestrata_ultimate.*, not orchestrata.<pkg>.<module>) — see CLAUDE.md Quick Reference. Even if the hook fired (§0), the filter would never trigger and the import-check logic would build a nonexistent module path.

3. Functional-unit inventory & classification

3.1 Lifecycle hooks (orchestrata.ts top-level keys)

Hook Line Real SDK hook? Class Notes
session.status 254 No (not in Hooks; would need event+EventSessionStatus) BUILD-NEW (if wanted at all) Purely a toast/log side-effect; low value — candidate to drop rather than rebuild
session.created 287 No (→ EventSessionCreated via event) RETARGET Session banner logic is otherwise sound and maps onto ultimate's session_cli.py/context.json schema almost exactly (verified, §5)
session.idle 568 No (→ EventSessionIdle via event) RETARGET Deception detector logic (pure JS/regex, no external deps) is portable as-is once re-homed onto event
tool.execute.before 673 Yes RETARGET KG-context-injection subprocess call retargets to an already-existing class, near-free (§6.6); policy-enforcement + git-checkpoint + bash-safety blocks are logic-portable as-is
tool.execute.after 798 Yes PORT-CLEAN Pure tool-call bookkeeping + session_cli.py log-work/log-artifact calls, already using the shared session CLI
message.updated 856 No (→ EventMessageUpdated via event) RETARGET "remember when" search directly parses context.json's session_history[].{purpose,achievements,log} — schema matches ultimate's context.json (verified, §5); "close the session" sequence reuses session_cli.py end (inherits bug #5)
file.edited 1213 No (→ EventFileEdited via event) RETARGET / consider OBSOLETE Path filter is dead on arrival (bug #7); recommend replacing the entire embedded heredoc with scripts/code-review.py <file> --format json (accepts single-file paths, has --fix; gate-by-gate coverage compared in §6) rather than porting the heredoc
experimental.session.compacting 1728 Yes PORT-CLEAN Trivial session_cli.py status --json read + output.context.push

3.2 Helper functions (pure logic, no I/O — trivially portable)

Function Line Class
checkMcpHealth 63 RETARGET — MCP ports 3456/3457 (document-reader/writer) are OpenCode-era concepts; no equivalent MCP servers found registered in ultimate's tool registry (tools.registry, 13 volk tools — none are "document-reader/writer"). Either BUILD-NEW those MCP servers or drop this check.
removeQuotedContent 75 PORT-CLEAN (pure regex)
detectDeception 85 PORT-CLEAN (pure regex + closure over toolCallHistory) — contingent on tool.execute.after populating history, which is fine (§3.1)
enforceTestingPolicy 184 OBSOLETE/SUPERSEDED — ultimate already enforces "no mocking" server-side via .claude/hooks/no-doubles-py.sh (PreToolUse) + forbidden-doubles-stop.sh (Stop) + code-review.py/test-review.py T-rules, independent of which editor/IDE is used. Porting a second, weaker regex-only copy for OpenCode only adds drift risk.
enforceImportPolicy 208 OBSOLETE/SUPERSEDED — "no relative imports" is one of the CLAUDE.md Key Rules and is checked by the existing review tooling (code-review.py, architecture_checker.py); same reasoning as above
enforceDevelopmentPolicy 220 OBSOLETE/SUPERSEDED — .claude/hooks/no-banned-deps.sh (PreToolUse on pyproject.toml writes) + .claude/banned-libraries.txt + code-review.py's BANNED-DEP gate already cover pip/uv enforcement per CLAUDE.md

3.3 Custom tools (28 total — see D2)

Group Tools Class
Session (9) session-status, session-begin, session-end, session-log-work, session-log-achievement, session-add-work, session-complete-work, session-update-state, session-log-choice PORT-CLEAN — ~/.claude/tools/session_cli.py is the exact same binary already used project-wide (verified subcommand-by-subcommand in §5); no retargeting needed beyond fixing bug #5
KG query (kg-query, kg-find-file, kg-find-entity, kg-stats, kg-cxp-thread) 5 RETARGET — raw Cypher hardcodes old, still-basically-correct labels (Class/Function/File are the real canonical labels per §6.1 — good news), but needs (a) project-qualified id filtering per §6.2 instead of bare path/name matches, and (b) for kg-cxp-thread specifically, resolving whether the live graph uses CXPPacket (confirmed real, cxp_auditor.py:78,81,114,150-151, indexed on thread_id) or canonical Message (canonical_schema.py:134) — the writer and the canonical schema disagree, confirm which before writing Cypher
KG query (kg-trace-req) 1 RETARGET — uses yet a third, internally-inconsistent label set (CodeClass/CodeFunction/Requirement/Specification) that matches none of the real canonical labels (Class/Function/Requirement/Spec, §6.1) — needs a full rewrite, not just an id-scheme fix
KG query (kg-session-audit) 1 RETARGET (schema is real, just different names) — assumes OrchestrataSession label + IN_SESSION relationship; neither exists in ultimate (confirmed, zero grep hits). Real label is Session (canonical_schema.py:81, session/neo4j_store.py:36), keyed by session_id/id, with confirmed edges (:Session)-[:BELONGS_TO]->(:Portfolio), (:Fact)-[:ABOUT]->(:Session), (:Session)-[:NEXT_SESSION]->(:Session), (:WorkItem)-[:CARRIED_OVER_FROM/TO]->(:Session); Violation (:141) and Escalation (:143) labels are real but the exact edge linking them to Session was not traced end-to-end (see §4) — confirm before writing the retargeted query
KG lifecycle (kg-sync, kg-queue-status, kg-flush-queue, kg-daemon-start/stop/status) 6 RETARGET (smaller than expected) — FileSynchronizer.sync_file() exists at knowledge_graph/synchronizer.py:71-72, same class/method name, only the import path changes (§6.3); no standalone single-file sync script exists, so the queue-consumer code should call FileSynchronizer.sync_file() directly in Python exactly as the old plugin does now, just with the new import. kg-daemon-* additionally carries bug #1 verbatim if ported unchanged.
KG context (kg-file-context) 1 Near PORT-CLEAN — KnowledgeGraphContext.get_file_context() already exists at the identical method signature, is already traced to REQ-021-004 (session/kg_context.py:23,84,85, §6.6) — this piece of SPEC-021 is effectively already ported/wired; only the import package-prefix (orchestrata.session.kg_context → orchestrata_ultimate.session.kg_context) needs changing
Conversation (conv-sync, conv-status, conv-query) 3 BUILD-NEW, confirmed — no Conversation/ConversationTurn/HAS_TURN labels or consumer exist anywhere in src/orchestrata_ultimate/ (§6.7, grep-confirmed zero hits, including the supposedly-relevant REDIS_KG_SYNC_PREFIX constant which turns out to be dead code). This is a whole missing subsystem, not a retarget. conv-sync additionally carries bug #2 verbatim if ported unchanged.
Code review (code-review, code-fix) 2 OBSOLETE/SUPERSEDED, prefer replacing with scripts/code-review.py — but note §6.8: that script covers syntax/lint/mypy/bare-except/mutable-defaults/@traces/naming, but is missing a generic secret detector, feature-envy detection, and nesting-depth (those three exist only in verification/review/checks_structure.py or claude/hooks/error_gate/checks_structure.py, not in code-review.py itself) — full Gate 3/4/5 parity requires wiring more than just code-review.py alone. code-fix carries bug #3 verbatim if ported unchanged.

3.4 Agents, commands, config

Unit Class Notes
agents/auto-kg-context.md, agents/kg-context-agent.md RETARGET (docs only) Pure prompt/markdown guidance, no executable logic; just needs path/label examples updated to ultimate's tree + schema. Both files are byte-identical between source and deployed twin (diff confirmed) and between open-code/ and open-code/reference/ mirrors.
commands/query-kg.md RETARGET (docs only) Same — just usage examples to update once the underlying kg-query tool is retargeted
install.sh PORT-CLEAN (mechanically) Deploys agents/, commands/, config/*.json, tools/, plugins/ to fixed ~/.opencode / ~/.config/opencode/plugins paths — none of these paths are project-specific, so the script itself needs no change; only its payload (the plugin/tools) needs retargeting
config/opencode.json RETARGET Source (open-code/config/opencode.json) is a 2-line stub ({"$schema": ...}); the deployed ~/.opencode/opencode.json has since been hand-extended with model, small_model, a permission block (denying .claude/** reads), and "plugin": ["./plugins/orchestrata.ts"] — this drift between source-of-truth and deployed-reality should be reconciled (decide which is canonical) before any redeploy overwrites the hand-tuned permissions
config/package.json RETARGET Source pins @opencode-ai/plugin@1.3.3; live install resolved 1.3.15 (and an even newer 1.3.17 is requested by the deployed package.json's own dependency line) — pin should be bumped to match whatever is actually verified working, given §0's hook-surface change risk between versions
config/bun.lock RETARGET Regenerate after bumping the plugin version

3.5 Python KG query tools (tools/)

File Class Notes
tools/kg_query (bash wrapper) RETARGET cd /home/mohamed/workspace/orchestrata && uv run python3 /home/mohamed/.opencode/tools/kg_query_inner.py "$@" — hardcodes the old monorepo path as the uv run project root (line 5) while invoking the script from the deployed twin's path, not a source-relative one (line 6). Needs both paths updated: cwd → ultimate's repo root (for uv run to resolve the right venv/pyproject), script path → wherever it's redeployed.
tools/kg_query.py RETARGET Hardcodes sys.path insertions for /home/mohamed/workspace/orchestrata/packages/orchestrata-{knowledge,core}/src (lines 16-39) and imports orchestrata.knowledge.config.Neo4jConfig / orchestrata.knowledge.neo4j_client.Neo4jClient — old monorepo package structure. Ultimate's equivalent is confirmed real at orchestrata_ultimate.knowledge_graph.config.Neo4jConfig / orchestrata_ultimate.knowledge_graph.neo4j_client.Neo4jClient (§6.5) with a matching async connect()->bool/query(cypher,params)->list[dict]/close() surface — just repoint the imports and drop the sys.path hacking, or replace outright with ultimate's own scripts/neo4j_query.py (see OBSOLETE row below)
tools/kg_query_inner.py RETARGET Same old-monorepo import as above, plus its whole reason for existing — the "project scoping" rewrite (lines 94-232) — is built around a single global (p:Project)-[:HAS_FILE]->(f:File) reachability pattern with a bare Project.id match. Confirmed (§6.2): ultimate's Project/Class/Function/Module/File nodes are keyed with project-qualified ids (f"{project}:{qualified_name_or_path}", per the live knowledge_graph/sync.py writer — dated 2026-07-12, very recent), though the schema module's own code_entity_id() helper hasn't caught up and still returns bare ids. This means the entire 30+-relationship-type graph-traversal rewrite in this file is obsolete in favor of a plain {project}: prefix filter/match — much simpler than what this file does today.
tools/neo4j_query.py OBSOLETE/SUPERSEDED — ultimate already has scripts/neo4j_query.py ("CLI for querying the Neo4j knowledge graph", per codebase map) which is presumably schema-current; the old standalone tool (with its Neo4jClient/Neo4jConfig mock-class fallback) duplicates that with stale imports
tools/simple_kg_query.py OBSOLETE/SUPERSEDED — same reasoning; a "connectivity test" variant of the same stale pattern

3.6 Redis queue infra

Key Type Class
orchestrata:kg:sync:queue SET RETARGET, not BUILD-NEW — FileSynchronizer.sync_file() exists in ultimate at knowledge_graph/synchronizer.py:71-72 (§6.3), same method name/purpose; the SADD/SCARD/SPOP producer/consumer pattern is unchanged, only the import path (orchestrata.knowledge.synchronizer → orchestrata_ultimate.knowledge_graph.synchronizer) needs updating
orchestrata:conversation:queue LIST BUILD-NEW, entirely — no consumer, no Conversation/ConversationTurn graph schema exists in ultimate at all (grep-confirmed zero hits across src/orchestrata_ultimate/)

4. What I did NOT get to / could not fully verify


5. Verified PORT-CLEAN surface (cross-checked directly against ultimate)


6. Ultimate-side equivalents (KG schema, sync, review pipeline)

Confirmed by a dedicated research pass over the current src/orchestrata_ultimate/ tree (all citations below are that pass's, cross-checked, plus a few items I confirmed independently as noted).

6.1 Canonical KG labels / relationships

Labels are plain, undecorated names — not CodeClass/CodeFunction/CodeFile as kg-trace-req assumes (canonical_schema.py): Module (74), Class (75), Function (76 — methods are also Function nodes, carrying a class_id property, no separate Method label), Spec (77), Requirement (78), RequirementItem (79), Project (82), Package (94), File (95), Directory (96).

Canonical relationships settled by canonical_schema.py (lines 151-153) are only three: REL_DEFINES = "DEFINES" (the one containment edge, replacing writer-specific HAS_CLASS/HAS_FUNCTION/HAS_METHOD), REL_TESTED_BY = "TESTED_BY", REL_TRACES_TO = "TRACES_TO" (always targets RequirementItem, never Requirement directly — comment at lines 149-150). The module's own docstring (46-49) states these are the only canonical names; HAS_FILE/CONTAINS/DEFINES_REQUIREMENT/HAS_ITEM/HAS_METHOD still live in the older, uncoordinated knowledge_graph/schema.py (lines 63-83), which per that module's own comment remains "the live writer surface until a later wave migrates." There is no HAS_REQUIREMENT relationship in canonical_schema.py at all — it exists only as a literal string in semantic_graph/spec_trace_enricher.py:207, an unrelated writer. This means kg_query_inner.py's project-scoping rewrite (which anchors through HAS_FILE/HAS_SPEC/ HAS_REQUIREMENT) is targeting relationship names that are, at best, legacy/non-canonical and at worst don't exist under those exact names for Requirement.

6.2 Node-ID convention — project-qualified ids ARE real, but two co-existing schemes

Confirmed: canonical_schema.py's docstring (15-26) documents the move from bare <relpath>::<qualname> to project-qualified <project>:<relpath>::<qualname> (dated 2026-07-12 — i.e. very recent). However the module's own code_entity_id() helper (canonical_schema.py:371-396, still called from knowledge_graph/ontology_mapping.py:132) has not been updated and still returns the bare f"{relpath}::{qualname}" — while the actual writer (knowledge_graph/sync.py) builds ids inline, already project-qualified, bypassing that helper:

Net effect for the port: any retargeted Cypher should build ids as f"{project}:{qualified_name_or_path}" directly (matching the live sync.py writer), not call the stale code_entity_id() helper, and not assume a global Project reachability traversal the way kg_query_inner.py does — a direct {project}: prefix match/filter is now the correct (and cheaper) scoping mechanism.

6.3 FileSynchronizer — EXISTS, 1:1 rename target

src/orchestrata_ultimate/knowledge_graph/synchronizer.py:38, class FileSynchronizer (@traces(spec="008", story="US-001", reqs=["REQ-008-022","REQ-008-025"])), with sync_file(self, file_path: str) -> SyncResult (line 71-72, same name/purpose as the old plugin's usage) plus sync_project(...) (384) and sync_project_with_resolution(...) (434). Import path changes only the package prefix: orchestrata.knowledge.synchronizer → orchestrata_ultimate.knowledge_graph.synchronizer; class and method names are unchanged. This retargets the KG-sync-queue consumer tools (kg-sync, kg-flush-queue, kg-daemon-*) from BUILD-NEW to a much smaller RETARGET: the consumer logic itself (SPOP loop, error handling) is unchanged, only the import line and module path.

6.4 No standalone "sync one file" script exists — use FileSynchronizer directly, not a CLI

Checked all three candidate full-repo scripts; none accepts a single source-file path:

Conclusion: the old plugin's python3 scripts/sync_monorepo_to_neo4j.py (full-repo, background) and the ad-hoc from orchestrata.knowledge.synchronizer import FileSynchronizer; syncer.sync_file(f) (single-file, queue-driven) map onto two different existing ultimate tools: full/background sync → scripts/ingest_orchestrata_ultimate.py (if repointed at the Memgraph graph is acceptable) or scripts/bootstrap_graphs.py --neo4j; single-file incremental sync → call FileSynchronizer.sync_file() directly from Python (as the old plugin already does), no new CLI needed.

6.5 Neo4jClient/Neo4jConfig — EXISTS, stable import path, matching async surface

knowledge_graph/neo4j_client.py:42 — class Neo4jClient(Neo4jNodeCreationMixin, Neo4jFileQueryMixin) with async def connect(self) -> bool (64), async def close(self) -> None (94), async def query(self, cypher, parameters=None) -> list[dict] (273). knowledge_graph/config.py:27 — @dataclass class Neo4jConfig with uri, username, password, database, max_pool_size, max_connection_lifetime, connection_timeout fields. Import paths: orchestrata_ultimate.knowledge_graph.neo4j_client.Neo4jClient / orchestrata_ultimate.knowledge_graph.config.Neo4jConfig — same package-prefix rename as §6.3, method surface (connect()->bool, query(cypher,params)->list[dict], close()) matches the old plugin's expectations closely. (A separate sync Neo4jSyncRepository also exists for non-async call sites, e.g. session/kg_context.py, core/graph.)

6.6 KnowledgeGraphContext.get_file_context — ALREADY PORTED

This is not a retarget — it's done. src/orchestrata_ultimate/session/kg_context.py:23 carries @traces(spec="021", story="US-001", reqs=["REQ-021-004"]) on the class itself and again on the method (line 84) — confirmed by direct grep (grep -rn 'spec="021"' src/ → exactly these 2 lines, nothing else in the whole tree). Import path is identical save the package prefix to the old plugin's usage: orchestrata.session.kg_context.KnowledgeGraphContext → orchestrata_ultimate.session.kg_context.KnowledgeGraphContext. Signature: get_file_context(self, file_path: str) -> dict, returning {"file": ..., "lines": ..., "entities": [{"type", "name", "has_docstring"}, ...], "specs": [spec_id, ...]} (lines 85-106) — a reasonable superset of what the old plugin printed. The class also exposes connect()/close()/connected, get_spec_context(spec_id) (108), and get_class_context(class_name) (151) — more than the old plugin used. This reclassifies tool.execute.before's KG-context-injection call and the kg-file-context tool from RETARGET to near-PORT-CLEAN: swap the import path, done. (A different, broader knowledge_graph/context_injector.py:404 ContextInjector class also exists but has no get_file_context method — don't confuse the two.)

6.7 Redis KG-sync-queue / conversation-queue consumers — confirmed BUILD-NEW

No consumer or producer exists for either Redis key pattern anywhere in src/ or scripts/. A constant REDIS_KG_SYNC_PREFIX = "orchestrata:kg:sync" (knowledge_graph/constants.py:25) exists but is dead — referenced only by its own definition and a unit test asserting its literal value (tests/knowledge_graph/test_constants.py:38); nothing produces to or consumes from it. HistoryManager (knowledge_graph/history.py:31) optionally accepts a redis.Redis client but is an in-memory change-history log, unrelated. Conversation/ConversationTurn/ HAS_TURN do not exist anywhere in src/orchestrata_ultimate/ (zero grep hits) — they appear only in the old plugin file itself and in planning docs. This confirms §3.3/§3.6's BUILD-NEW classification for the whole conversation-tracking subsystem, with no shortcut available.

6.8 scripts/code-review.py — real gate-by-gate coverage (supersedes most of Gates 1-5)

CLI: paths (nargs="+", files or directories) + --output/-o, --format {text,json,markdown}, --min-severity {ERROR,WARNING,INFO}, --fix, --format-code, --max-files, --diagnostics, --stdout (code-review.py:1888-1932). Checkers wired in main() (1950-1962): RuffFormatChecker, RuffChecker, MyPyChecker, StyleChecker, ChecklistChecker, ConnectionStringChecker, TraceabilityChecker, plus a standalone check_banned_dependencies (1845). Confirmed check-by-check against the old plugin's Gate 3/4/5 items:

Old-plugin check In code-review.py? Citation
Bare except: detection Yes (BARE_EXCEPT, ERROR) code-review.py:722-735
Mutable default args Yes (MUTABLE_DEFAULT_ARGUMENT) code-review.py:737-751
God object (old plugin: >20 methods) Different threshold/location — >15 public methods (also >300 class lines, >500 file lines), lives in verification/review/checks_structure.py:328-388 (check_god_objects), not in code-review.py itself (also duplicated at claude/hooks/error_gate/checks_structure.py:421) see citation
Feature envy (>15 self-refs) NOT FOUND anywhere in the repo (grepped feature_envy/FEATURE_ENVY, zero hits). Closest relative is the opposite check, METHOD_NO_SELF_ACCESS (flags methods that don't use self) at code-review.py:925-962 —
Hardcoded secrets/passwords/API keys/tokens Partial, not generic. ConnectionStringChecker (code-review.py:1521-1720) only flags hardcoded connection URIs/IPs/localhost:port and a fixed ENV_VAR_KEYS set (NEO4J_PASSWORD, DB_PASSWORD, etc., 1528-1543) — no generic password/API-key/token literal-string regex exists anywhere in code-review.py code-review.py:1521-1720
@traces decorator presence Yes — TraceabilityChecker (class-level at 1358, method-level at 1383) code-review.py:1281-1383
snake_case/PascalCase naming Yes — StyleChecker._check_naming_conventions, CLASS_NAMING/FUNCTION_NAMING regexes code-review.py:573-609
Nesting depth NOT in code-review.py (grepped nesting/NESTING/max_depth, zero hits) — lives only in claude/hooks/error_gate/checks_structure.py —

Conclusion: scripts/code-review.py is a strong but incomplete drop-in replacement for the embedded 5-gate heredoc — it covers syntax/format/lint/mypy, bare-except, mutable defaults, @traces presence, and naming outright, but is missing a generic secret-detector, a feature-envy check, and (in this script specifically) nesting-depth — those three would need to be added to code-review.py (or pulled from checks_structure.py / claude/hooks/error_gate/) if full Gate 3/4 parity with the old plugin is actually wanted, rather than assumed available.


7. Counts

Class Count Units
PORT-CLEAN / ALREADY DONE 6 tool.execute.after hook; experimental.session.compacting hook; all 9 session tools; install.sh; session_cli.py/context.json integration surface; KnowledgeGraphContext.get_file_context() — already exists in ultimate, already traced to REQ-021-004 (§6.6)
RETARGET ~22 session.created, session.idle, tool.execute.before, message.updated, file.edited hooks (5, contingent on the §0 event-rewrite); 5 KG-query tools + kg-trace-req + kg-session-audit (7, schema now confirmed real — just wrong names/ids in the source); kg-sync/queue-status/flush-queue/daemon×3 (6, smaller lift than expected — FileSynchronizer exists, §6.3); 2 agents + 1 command (3, docs-only); config×3 (opencode.json/package.json/bun.lock — now 1 fewer item since kg-file-context moved to PORT-CLEAN)
BUILD-NEW ~6 conv-sync/status/query (3, confirmed no backend at all, §6.7); Redis conversation-queue consumer; possibly session.status hook if kept; MCP document-reader/writer servers (if checkMcpHealth is kept) — KG-sync-queue consumer is now RETARGET not BUILD-NEW since FileSynchronizer exists
OBSOLETE/SUPERSEDED 6 enforceTestingPolicy, enforceImportPolicy, enforceDevelopmentPolicy (already covered by .claude/hooks/* + code-review.py/test-review.py); tools/neo4j_query.py, tools/simple_kg_query.py (ultimate has scripts/neo4j_query.py); code-review/code-fix tools (prefer scripts/code-review.py, though note §6.8's 3 coverage gaps)

(Counts are unit-groupings, not the raw 295-REQ tally — see §4 for why a full row-level count wasn't produced.)


8. Suggested W1/W2 work packages, in dependency order

W1 — foundation (must land before anything else is meaningful)

  1. Verify §0 empirically. Instrument a live OpenCode session (temporary console.error at the top of session.created/session.idle/message.updated/file.edited) and confirm whether they fire at all. This single result determines whether W1.2-W1.4 below are a rewrite-onto-event job or a "these never ran, design fresh" job.
  2. Only one KG-schema question remains open per §4/§6: the exact relationship name linking Violation/Escalation to Session (needed for kg-session-audit). Everything else (canonical labels/ids, FileSynchronizer, KnowledgeGraphContext.get_file_context, which full-sync script to use) is now confirmed — see §6.
  3. Decide canonical config: reconcile source open-code/config/opencode.json (2-line stub) vs. the deployed, hand-extended ~/.opencode/opencode.json (model/permission/plugin block) before any redeploy can safely overwrite it.
  4. Bump @opencode-ai/plugin pin to the version actually verified in W1.1, regenerate bun.lock.

W2 — rebuild the hook surface onto event (depends on W1.1)

  1. Re-home session.created, session.idle, message.updated, file.edited, session.status logic into a single event: async ({event}) => switch(event.type){...} handler.
  2. Fix bug #5 (session_cli.py end --achievements) while doing so — pass a real space-separated multi-arg invocation, not the quote-splicing hack.
  3. Retarget file.edited's path filter from packages/*.py to src/orchestrata_ultimate/**/*.py (and tests/**/*.py if desired) and replace the entire embedded 5-gate heredoc with a call to scripts/code-review.py <file> --format json --min-severity ERROR (fixes bugs #3, #4 for free by deleting the code that had them). Per §6.8, code-review.py alone does not fully replace Gates 3/4: no generic secret detector, no feature-envy check, no nesting-depth check. Decide explicitly whether to (a) accept the gap, (b) also wire in verification/review/checks_structure.py for god-object/nesting, or (c) add the missing checks to code-review.py directly — don't assume "replace with code-review.py" is 100% parity by default.
  4. Retarget the KG-context-injection subprocess call in tool.execute.before to orchestrata_ultimate.session.kg_context.KnowledgeGraphContext (§6.6) — this one is nearly free, the class already exists with a matching signature and is already traced to REQ-021-004.

W3 — retarget the 28 tools (depends on W1.2)

  1. Session tools (9): near-zero work — already PORT-CLEAN, just fix bug #5 in session-end.
  2. KG query tools (kg-query, kg-find-file, kg-find-entity, kg-stats, kg-cxp-thread, kg-trace-req, kg-file-context): rewrite Cypher/labels per confirmed §6 schema.
  3. kg-sync/queue-status/flush-queue/daemon×3: rewrite around confirmed sync backend; fix bug #1 (pgrep/pkill pattern) while doing so, or drop the daemon tools if a background sync process isn't actually wanted going forward.
  4. kg-session-audit: needs a real query against Session+Violation/Escalation edges (§6), not OrchestrataSession/IN_SESSION.
  5. Drop code-review/code-fix tools in favor of exposing scripts/code-review.py (with --fix) as a tool wrapper instead of reimplementing checks inline.

W4 — build the conversation-tracking subsystem from scratch (US-007, independently schedulable, no dependency on W1-W3 other than the Redis queue key convention)

  1. Design Conversation/ConversationTurn/HAS_TURN additions to canonical_schema.py (per this project's own "ontology before graph import" rule — design the schema addition before writing any ingestion code).
  2. Build the queue consumer (fixing bug #2's invalid Cypher while doing so).
  3. Wire conv-sync/conv-status/conv-query tools against it.

Deprioritize / consider dropping rather than porting: