Skip to content

fix(init): opt-out for project-local editor files - #1836

Open
sloemo01 wants to merge 1 commit into
repowise-dev:mainfrom
sloemo01:fix/init-editor-files-optout
Open

fix(init): opt-out for project-local editor files#1836
sloemo01 wants to merge 1 commit into
repowise-dev:mainfrom
sloemo01:fix/init-editor-files-optout

Conversation

@sloemo01

Copy link
Copy Markdown
Contributor

Fixes #1603

What

When repowise init writes the repo-shared, tracked .mcp.json, it re-serialised the whole document from a dict. Pre-existing, unrelated server entries (e.g. a SonarQube entry with compact ["-e", "SONARQUBE_TOKEN"] args) therefore came back reformatted — one arg per line — in a file repowise had no business re-rendering end to end.

This PR makes the .mcp.json write a minimal edit. Only the mcpServers.repowise entry is touched; every other byte of the file stays identical.

Changes

  • json_merge.merge_json_object_member — a surgical, indentation-aware upsert that inserts/replaces only the owned member while preserving the rest of the document byte-for-byte. It also:
  • \claude_code.write_project_mcp_config` now uses the surgical writer.
  • Tests for the regression: unrelated entries preserved byte-identical, idempotency, env preservation, container creation, and missing-file creation.

The opt-out half of #1603 (project-local files behind --no-editor-setup) was already resolved in #1572; this closes the remaining reformatting bug the issue calls out as the one to fix first.

…e-identical

repowise init wrote the repo-shared, tracked .mcp.json by re-serialising
the whole document from a dict, so pre-existing unrelated server entries
(e.g. a SonarQube entry with compact ["-e", "VAR"] args) came back
reformatted on every run. Only the repowise entry is touched now: the new
merge_json_object_member performs a surgical, indentation-aware edit that
leaves every other byte of the file untouched, while still preserving
user-added keys (an env block) on the repowise entry and staying
idempotent across re-runs.

Closes repowise-dev#1603.
@repowise-bot

repowise-bot Bot commented Aug 22, 2026

Copy link
Copy Markdown

✅ Health of changed files: 5.5 (unchanged)
⚠️ Change risk: moderate, riskier than 62% of this repo's commits.

📋 At a glance
1 file changed health · 3 hotspots touched · 1 new finding introduced · 1 file with recent fix history.

Files & modules (2)
  • packages (2 files)
    • .../formats/json_merge.py
    • .../targets/claude_code.py
  • tests (1 file)
    • .../cli/test_agent_targets.py

✅ Health gate: passed

📌 Before you merge

  • Run .../cli/test_agent_drift_control.py, .../cli/test_agents_cmd.py: they import the changed files
🔎 More signals (2)

🗺️ Change map

flowchart LR
  subgraph PR ["Changed in this PR (3 with dependents)"]
    f_packages_cli_src_repowise_cli_agent_targets_formats_json_merge_py[".../formats/json_merge.py 🔥"]:::changed
    f_packages_cli_src_repowise_cli_agent_targets_targets_claude_code_py[".../targets/claude_code.py 🔥"]:::changed
    f_tests_unit_cli_test_agent_targets_py[".../cli/test_agent_targets.py"]:::changed
  end
  f_packages_cli_src_repowise_cli_agent_targets_formats_marker_block_py[".../formats/marker_block.py"]
  f_packages_cli_src_repowise_cli_agent_targets_formats_json_merge_py --> f_packages_cli_src_repowise_cli_agent_targets_formats_marker_block_py
  f_packages_cli_src_repowise_cli_agent_targets_formats_toml_merge_py[".../formats/toml_merge.py"]
  f_packages_cli_src_repowise_cli_agent_targets_formats_json_merge_py --> f_packages_cli_src_repowise_cli_agent_targets_formats_toml_merge_py
  f_packages_cli_src_repowise_cli_agent_targets_formats_yaml_merge_py[".../formats/yaml_merge.py"]
  f_packages_cli_src_repowise_cli_agent_targets_formats_json_merge_py --> f_packages_cli_src_repowise_cli_agent_targets_formats_yaml_merge_py
  f_packages_cli_src_repowise_cli_agent_targets_targets_codex_py[".../targets/codex.py"]
  f_packages_cli_src_repowise_cli_agent_targets_formats_json_merge_py --> f_packages_cli_src_repowise_cli_agent_targets_targets_codex_py
  f_packages_cli_src_repowise_cli_editor_integrations_claude_py[".../editor_integrations/claude.py"]
  f_packages_cli_src_repowise_cli_agent_targets_targets_claude_code_py --> f_packages_cli_src_repowise_cli_editor_integrations_claude_py
  f_packages_cli_src_repowise_cli_mcp_config_py[".../cli/mcp_config.py"]
  f_packages_cli_src_repowise_cli_agent_targets_targets_claude_code_py --> f_packages_cli_src_repowise_cli_mcp_config_py
  f_packages_cli_src_repowise_cli_uninstall_inventory_py[".../uninstall/inventory.py"]
  f_packages_cli_src_repowise_cli_agent_targets_targets_claude_code_py --> f_packages_cli_src_repowise_cli_uninstall_inventory_py
  f_tests_unit_cli_test_agent_targets_py --> f_packages_cli_src_repowise_cli_agent_targets_formats_toml_merge_py
  more(["+3 more dependents"])
  PR --> more
  t_tests_unit_cli_test_agent_drift_control_py(["✅ .../cli/test_agent_drift_control.py"]):::guard
  t_tests_unit_cli_test_agent_drift_control_py -.-> f_packages_cli_src_repowise_cli_agent_targets_targets_claude_code_py
  classDef changed fill:#dbeafe,stroke:#1d4ed8,color:#1e3a5f
  classDef warn fill:#fef3c7,stroke:#b45309,color:#78350f
  classDef guard fill:#dcfce7,stroke:#15803d,color:#14532d
Loading

Solid arrows: code that imports the changed files (10 direct dependents, from the last indexed snapshot). Dashed: history/tests.

🔥 Hotspots touched (3)

  • .../cli/test_agent_targets.py: 6 commits/90d, 1 dependents · primary owner: Raghav Chamadiya (100%)
  • .../formats/json_merge.py: 3 commits/90d, 10 dependents · primary owner: Raghav Chamadiya (100%)
  • .../targets/claude_code.py: 5 commits/90d, 6 dependents · primary owner: Raghav Chamadiya (100%)

👀 Suggested reviewers @RaghavChamadiya


📊 See the full report for this PR
Your repo map with this PR's blast radius lit up, every caller of the contracts it changes, and health before and after. No sign-in. · ⭐ Star Repowise · 📥 Install bot · Silence on a single PR with [skip repowise] in the title · Per-repo toggle on repowise.dev/settings?tab=bot · Updated 2026-08-22 11:29 UTC

@Ayush7614 Ayush7614 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verified: packages/cli/src/repowise/cli/agent_targets/formats/json_merge.py:221 adds surgical merge_json_object_member that upserts only mcpServers.repowise preserving unrelated entries byte-identical, with env preservation and idempotency, fixing #1603 reformatting bug. claude_code.py now uses it. Tests cover byte-identical, idempotent, env. Solid minimal-edit fix — LGTM.

@RaghavChamadiya

Copy link
Copy Markdown
Member

Thanks @sloemo01. The minimal-edit approach is the right answer to the reformat half of #1603. Re-serialising a tracked, repo-shared file end to end so that one contributor's repowise init shows up in everyone's diff as a whole-file rewrite is a real cost, and merge_json_object_member addresses it at the layer where it happens rather than papering over it downstream.

Careful detail worth calling out: returning UNCHANGED when edited == original, distinct from KEPT. Collapsing those two would have been the easy mistake and it is what makes the next point a bug rather than a catastrophe.

The caller can destroy a user's file. In claude_code.py:

action = merge_json_object_member(config_path, "mcpServers", "repowise", new_entry)
if action is FileAction.KEPT:
    action = write_json_config(
        config_path, {"mcpServers": {"repowise": new_entry}}

KEPT is the sentinel for "I cannot edit this safely": the root is not an object, mcpServers is present but not an object, or _insert_member could not find a place. In every one of those cases the fallback writes a document containing only the repowise entry. Every other MCP server the user configured and every other top-level key in .mcp.json is gone, in a file that is normally committed.

The helper's own comment says the opposite of what the caller does:

# container_key is present but not an object; leave the file alone
# rather than guessing what a rewrite would mean.
return FileAction.KEPT

It leaves the file alone, and then the caller guesses. The old code did not have this hole: it did existing["mcpServers"] = servers on the parsed document, so unrelated root keys survived even on the rewrite path.

Reaching it needs a .mcp.json with "mcpServers": null or "mcpServers": [], which is not exotic for a hand-edited or partially-migrated file. The comment says the fallback is there "rather than silently dropping the registration", but the trade as written is one missing registration against an overwritten shared config, and the second is much worse. Either keep the parsed document and merge into it as the old path did, or treat KEPT as "leave it, warn, move on".

Two smaller things:

The PR title and body say "opt-out for project-local editor files", and there is no opt-out anywhere in the diff. It is entirely the no-reformat half. #1603 has both halves and this closes it on merge, which would mark the opt-out as done when it is not. Worth retitling and changing Fixes #1603 to Refs #1603, especially since @rohitsalla has had #1133 open since 27 July doing the opt-out half in editor_integrations/. The two are complementary, not competing, and I would rather both land than have one silently close the other's issue.

Non-blocking: 270 lines of hand-written JSON position scanning is a lot of new surface in a write path. _find_object_member and _insert_member look right to me, but a fuzz-style test that round-trips a handful of awkward documents (one-line objects, trailing whitespace, nested mcpServers, a value containing }) would buy more confidence than the same number of hand-written cases.

Fix the fallback and I will take this.

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.

[Bug] init writes project-local .mcp.json / .vscode files with no opt-out, and reformats unrelated entries in a tracked file

3 participants