refactor(#288): architecture debt - extract health, rename list, update C4#303
refactor(#288): architecture debt - extract health, rename list, update C4#303sshekhar563 wants to merge 2 commits into
Conversation
…, update C4 1. Extract shared health/cleanup logic to runtime/health.py (MCP+CLI) 2. Rename def list to list_cmd, remove 14 builtins.list workarounds 3. Regenerate C4 model with 9 missing subsystems and relationships 4. Add TODO(qbtrix#288) to soul.py documenting recommended class split
prakashUXtech
left a comment
There was a problem hiding this comment.
This is the good kind of extraction. I checked the thing I was most worried about, which is whether pulling health and cleanup into a shared module quietly flattened the CLI down to the weaker MCP version, and it did not: orphan-node removal, --low-importance, and the .soul.bak backup all survive, every --no-* toggle is still wired, and the thresholds (dedup 0.8, importance <= 2, the "Scored " staleness check) are unchanged. You also correctly left the backup in the CLI caller rather than dragging it into the shared module, which is the right call. The list rename is spot on too: keeping the explicit @cli.command("list") means soul list is unchanged for users, and that's the detail most people miss. All 14 builtins.list(...) workarounds and the import builtins are gone.
Deferring the Soul split with a concrete TODO(#288) is the right judgment. That one shouldn't be a drive-by.
Two blockers:
-
MCP
soul_cleanupgained a destructive capability without the CLI's guard.mcp/server.py:1574hardcodesorphan_nodes=True, so graph entities can now be permanently pruned through an LLM-invoked tool. On the CLI that same operation is deliberately paired withbackup_soul_file()before the export, but the MCP path just marks the soul modified and saves on shutdown. No backup, no opt-out, no dry-run. The drift you're fixing went the other way on purpose: the CLI author added the backup because orphan pruning is irreversible. Either addbackup_soul_fileto the MCP path or exposeorphan_nodes: bool = Falseso the caller opts in. Roughly five lines. -
This collides head-on with your own open #299. That PR touches the same files and the same lines, adding the public accessors and replacing the private-attribute access in the CLI and MCP. This PR moves those exact blocks into
runtime/health.pykeeping the private access verbatim, so if #299 lands first,health.pyquietly reintroduces the coupling #299 was written to delete, and whichever merges second will conflict on those hunks. Let's pick an order: I'd land #299 first, then rebase this onto its public API. Worth deciding before either merges.
Also worth fixing:
-
The new health filter can swallow real issues.
cli/main.py:3422-3425string-matches on"duplicate"and"orphan"to un-flatten the mergedissueslist. Skill ids are agent-learned and user-controllable, so a skill named something likeorphan-cleanupwith negative XP produces an issue string containing "orphan", gets filtered out, and isn't counted inissues_found. The panel can then print "No issues found, soul is healthy" while a real problem exists. Dev didn't have this hazard because it iterated separatebond_issuesandskill_issueslists. Keeping those as structured fields onHealthReport(withissuesas a derived property) fixes it and removes the fragile matching. -
Two of the new C4 relationships aren't real.
memory-tiers -> engineandcontext -> engine: nothing underruntime/memory/importssoul_protocol.engine(the actual importers arecli/journal.py,cli/org.py, andspike/memory_journal.py), andruntime/context/store.pyusessqlite3directly plusspec.context.models, neverengine/. This matters more than a doc nit because loom readsmodel.jsonfor blast-radius and boundary rules, so a wrong edge turns "returns nothing" into "returns something wrong", which is the worse failure. Suggestcli -> engineandcontext -> specinstead. The nine new components all check out. Minor: the note says "Regenerated" but the diff is clearly hand-authored, which is fine (and correctly keepsauthored: true), just word it that way. -
The MCP cleanup response renamed
removedtototal_removed. Any external MCP consumer readingdata["removed"]now gets aKeyError. Nothing in-repo depends on it, but MCP tools get called by arbitrary agents, so either keepremovedas an alias or call the rename out in the PR body. -
No tests import the new module. Coverage is entirely indirect through the CLI end-to-end tests (which is what gave me confidence the CLI didn't regress) and four key-presence MCP tests. That MCP shape-only assertion is exactly why the
removedrename went green. For a pure refactor this is the main risk, since a green suite proves very little about whether the extraction preserved semantics. Atests/test_runtime/test_health.pycoveringaudit_healthfield by field,plan_cleanupper flag, andexecute_cleanup's return count would close it. There's also no orphan-node test on either side.
Smaller: execute_cleanup does removed += 1 unconditionally while remove() returns a bool, and dedup plus low-importance can select the same id, so the count can overreport (copied from dev, so not a regression, but this is the natural place to fix it). The Updated: (#288) header lines are missing on cli/main.py and mcp/server.py. And lint is red only because the new file needs formatting: uv run ruff format src/soul_protocol/runtime/health.py.
The core of this refactor is sound. Everything above is at the edges.
|
Great response to the last round: five of the six findings are fixed, each with a regression test. The MCP The one remaining item isn't a code bug, it's sequencing with #299. The plan I'd suggest: land #299 first (it's approved and ready), then rebase this so |
Resolves #288.
This PR addresses four architectural debt and maintainability items identified in the dev audit:
1. Extract Duplicated Health & Cleanup Logic
src/soul_protocol/mcp/server.pyandsrc/soul_protocol/cli/main.py. The two implementations had already drifted (the CLI gained orphan-node removal and backup; MCP did not).src/soul_protocol/runtime/health.py.HealthReportandCleanupResultso callers can customize the formatting (JSON for MCP vs Rich panel for CLI).audit_health(),plan_cleanup(), andexecute_cleanup().2. Solve CLI Builtin Shadowing (
list)list()command shadowed Python's builtinlistinsidesrc/soul_protocol/cli/main.py, forcing 14 uglybuiltins.list(...)workarounds throughout the file.list_cmd(). Kept the CLI registration name as@cli.command("list")so there is zero change to the user-facing CLI command name (soul list). Removedbuiltinsimport and replaced all workarounds with standard Pythonlist(...).3. Regenerate Stale C4 Model
docs/c4/model.jsonwas missing 9 subsystems added since June 2026.engine,spec,eval,optimize,cognitive,context,dream,eternal, andhealth, along with 10 new relationships connecting them. Updated the CLI component description to reference the command module split.4. Document Soul Class Split Plan
Soulorchestrator is a 3,300+ line class that causes constant merge conflicts across feature branches. Splitting it requires a dedicated multi-PR effort.TODO(#288)at the top ofsrc/soul_protocol/runtime/soul.pyoutlining the recommended mixin split plan to keep future efforts aligned.Verification
python -m pytest tests/test_cli/test_health_cleanup_repair.py -v(All 21 health/cleanup tests passed).test_private_key_has_0600_permissionstest, which is a pre-existing failure on Windows).soul list,soul health, andsoul cleanupwork identically via the CLI.