Skip to content

Validate component IDs in save_module_tree to catch orphaned components - #83

Open
LiberiFatali wants to merge 2 commits into
FSoft-AI4Code:mainfrom
LiberiFatali:fix/save_module_tree-orphan-silently
Open

Validate component IDs in save_module_tree to catch orphaned components#83
LiberiFatali wants to merge 2 commits into
FSoft-AI4Code:mainfrom
LiberiFatali:fix/save_module_tree-orphan-silently

Conversation

@LiberiFatali

Copy link
Copy Markdown

Address issue #82

Surface unmatched (orphaned) tree ids and unassigned (leftover) index components in the save_module_tree response and a workspace validation file, so silent documentation gaps are caught before docs are generated.

surface unmatched (orphaned) tree ids and unassigned (leftover) index
components in the save_module_tree response and a workspace validation
file, so silent documentation gaps are caught before docs are generated.
@LiberiFatali

Copy link
Copy Markdown
Author

@anhnh2002 could you take a look?

@anhnh2002 anhnh2002 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tackling #82

What's good

  • Validation never blocks the save — tree files, session cache, and processing order behave exactly as before; validation is a pure addition to the response, so existing consumers are unaffected.
  • Correct, minimal core logic: deterministic sorted() output, or [] guarding missing/None components, nested children handled.
  • Solid test coverage (happy path, orphan, leftover, nested, combined, missing session) and docs kept in sync (server.py tool description, IDE_DRIVEN_GUIDE.md table, .gitignore convention).

Requesting changes on two points (see inline comments):

  1. leftover_component_ids is computed against the full component index, but clustering intentionally excludes non-essential components (the cluster prompt says so explicitly, and the original pipeline clusters only leaf_nodes). As written, the warning fires on every normal run and will likely push agents to over-assign components to silence it. Compare against session.leaf_nodes and downgrade leftovers from warning to informational. The unmatched_ids check is correct as-is.
  2. The unbounded ID lists are embedded in the response twice (plus the log). Cap what goes over stdio and keep the full lists in module_tree_validation.json.

Minor: module_tree_validation.json could be added to the workspace-layout listings in the workspace.py docstring and IDE_DRIVEN_GUIDE.md; the and children in _collect_component_ids is redundant since walking an empty dict is a no-op.


module_tree = arguments["module_tree"]
output_dir = session.output_dir
known_ids = set(session.components.keys())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

known_ids = set(session.components.keys()) is the full component index, but clustering by design operates on a subset and intentionally excludes components. The cluster prompt (prompt_template.py:132-137) explicitly says "It's normal that some components are not essential to the repository" and "DO NOT include components that are not essential." The original pipeline also clusters only leaf_nodes (format_potential_core_components(leaf_nodes, ...) in cluster_modules.py).

So on any real repo, every non-leaf and every deliberately-excluded component lands in leftover_component_ids, and the "will receive no documentation" warning fires on every normal save. Since this response is consumed by an LLM agent, a persistent warning is likely to push agents to cram all components into modules just to silence it — degrading clustering quality, the opposite of this PR's intent.

Suggestion:

  • Keep the unmatched_ids check against the full index as-is — that's the real bug-catcher and is correct.
  • Compute leftovers against set(session.leaf_nodes) (the actual clustering candidate set) instead, and present it as informational (e.g. a note field) rather than folding it into warning.

Note the tests don't catch this because _make_session sets leaf_nodes identical to components — worth adding a case where components ⊃ leaf_nodes once the baseline is fixed.

Comment thread codewiki/mcp/tools/module_tree.py Outdated
Comment on lines +136 to +156
if unmatched_ids or leftover_ids:
warnings: List[str] = []
if unmatched_ids:
warnings.append(
f"{len(unmatched_ids)} component id(s) in the module tree do not "
f"exist in the analysis index and will be omitted from docs: "
f"{unmatched_ids}"
)
if leftover_ids:
warnings.append(
f"{len(leftover_ids)} indexed component(s) are assigned to no "
f"module and will receive no documentation: {leftover_ids}"
)
warning = " ".join(warnings)
logger.warning(
"save_module_tree for session %s: %s",
session_id,
warning,
)
else:
warning = ""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The full ID lists are transmitted three times per call: in the validation field, again inside the warning string, and again in the log line. Combined with the leftover-baseline issue above, a repo with a few thousand components could push 100KB+ through the MCP stdio channel on every save_module_tree — exactly the bloat the workspace design exists to avoid (see the workspace.py docstring: "Instead of transmitting bulky data through the MCP stdio channel...").

Suggestion: keep the counts in the response, truncate the embedded lists (e.g. first 20 + "see module_tree_validation.json for the full list"), and let the workspace JSON file carry the complete lists.

…ponse

- Compute leftover_component_ids against the clustering candidate set
  (session.leaf_nodes) instead of the full component index, since the
  cluster prompt deliberately excludes non-essential components; surface
  leftovers as an informational 'note' rather than a 'warning'.
- Keep the unmatched_ids check against the full index as the real warning.
- Cap embedded ID lists in the response at 20 with *_truncated flags and
  keep full lists in module_tree_validation.json to avoid stdio bloat.
- Simplify _collect_component_ids (empty-dict walk is a no-op).
- Add module_tree_validation.json to workspace layout listings.
- Tests: separate leaf_nodes from components, add non-leaf not-flagged,
  leftover-vs-candidates, list truncation, and no-warning-on-note cases.
@LiberiFatali

Copy link
Copy Markdown
Author

Thanks for the careful review — both points are valid and fixed in 1786bfc.

1. Leftover baseline now uses session.leaf_nodes, not the full index

  • _validate_module_tree takes a candidate_ids set; leftovers = leaf_nodes − assigned, so excluded/non-essential components (which the cluster prompt deliberately omits) no longer trigger a warning on every save.
  • Leftovers are downgraded from warning to an informational note; only unmatched_ids (checked against the full index) still produce a warning. The log call is split accordingly (warning vs info).
  • Added the suggested components ⊃ leaf_nodes test case plus a leftover-vs-candidates case.

2. Response ID lists are capped

  • Response lists capped at 20 with unmatched_truncated / leftover_truncated flags; counts are always exact; full lists stay in module_tree_validation.json.
  • warning / note strings embed only the capped list and point to the workspace file when truncated.

Minors: module_tree_validation.json added to the workspace-layout docs (workspace.py docstring + IDE_DRIVEN_GUIDE.md); dropped the redundant and children in _collect_component_ids.

Tests: 9/9 passing in tests/test_module_tree_validation.py, including truncation and no-warning-on-note-only.

@LiberiFatali
LiberiFatali requested a review from anhnh2002 August 6, 2026 08:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants