Skip to content

feat: non-destructive config generation (v2.37.0)#17

Merged
drknowhow merged 2 commits into
mainfrom
feat/non-destructive-config-v2.37.0
Jun 14, 2026
Merged

feat: non-destructive config generation (v2.37.0)#17
drknowhow merged 2 commits into
mainfrom
feat/non-destructive-config-v2.37.0

Conversation

@drknowhow

Copy link
Copy Markdown
Owner

Summary

Makes C3's config generation non-destructive. The rule everywhere is now "C3 owns only its own slice; anything you wrote by hand is preserved." Previously, regenerating instruction docs or applying a permission tier could silently clobber user content.

What changed

Instruction docs (CLAUDE.md / AGENTS.md / GEMINI.md)

  • C3 content is wrapped in <!-- C3:BEGIN … --> / <!-- C3:END --> sentinels with a visible # C3 — Managed Instructions heading.
  • New shared helpers in services/claude_md.py: wrap_c3_block, merge_c3_block, write_c3_instruction_doc.
  • All write paths route through the helper: SessionManager.save_claude_md (install/init), api_claudemd_save (Hub), cmd_claudemd (c3 claudemd save).
  • Merge modes: markers present → replace in place; legacy marker-less C3 file (## C3 Tools) → replace head, keep trailing # User Notes; genuine user file → append below, never destroy.
  • ClaudeMdManager.compact() now compacts only the inner block and re-wraps it, so the markers and surrounding user content survive (logic split into compact + _compact_sections).

settings.local.json

  • Permissions: new _merge_permission_tier + _c3_managed_permission_entries preserve user-added allow/deny and keys like ask/defaultMode, replacing only the entries a tier manages. Wired into all four appliers: _apply_permission_tier, cmd_install_mcp --permissions, cli/server.py:api_permissions_put, cli/hub_server.py:api_projects_permissions_put.
  • Hooks: install-mcp now identifies C3's own Stop hooks by their hook scripts and replaces only those — user-added stop hooks (including the common matcher-less shape) survive. Post/PreToolUse hooks were already merged by matcher.

.mcp.json

  • Already safe (_upsert_json_mcp_server only replaces the c3 server). Covered by a new test.

Tradeoff

This intentionally flips the previous "permissions replaced wholesale" contract. Tier deny rules still apply authoritatively (and deny wins over allow), but a user-custom allow now persists across tier switches. The authoritative tier is still stored in .c3/config.json and surfaced by c3 permissions show.

Version & docs

  • Bumped to 2.37.0 (pyproject.toml, cli/c3.py) + CHANGELOG entry.
  • Documented the behavior in README.md and the in-app guide (cli/guide/getting-started.html).

Tests

  • New tests/test_claude_md_merge.py (wrap / in-place replace / legacy migration / user-file append / compact preservation).
  • Updated tests/test_permissions.py (custom rules + ask preserved; stale tier entries dropped; tier-switch behavior).
  • New install-mcp preservation test in tests/test_install_mcp_entrypoint.py (.mcp.json servers + user permissions + custom PostToolUse hook + empty-matcher Stop hook all survive).
  • Local run: 39 passed across the touched suites; c3_validate clean on all edited Python files.

🤖 Generated with Claude Code

Make instruction-doc regeneration and permission-tier application
non-destructive: C3 now owns only its own slice and preserves
user-written content everywhere.

Instruction docs (CLAUDE.md / AGENTS.md / GEMINI.md): wrap C3 content
in C3:BEGIN / C3:END comment markers; merge instead of overwrite across
save_claude_md, the Hub save endpoint, and `c3 claudemd save`;
ClaudeMdManager.compact() now compacts only the inner block and re-wraps
it so the markers and surrounding user content survive.

settings.local.json: permission tiers now merge into existing
permissions (preserving user-added allow/deny plus keys like ask and
defaultMode) across all four appliers (_apply_permission_tier,
install-mcp --permissions, per-project server, and Hub). install-mcp
now preserves user-added Stop hooks, replacing only C3's own.
.mcp.json was already safe (only the c3 server entry is replaced).

Bump version to 2.37.0, add a CHANGELOG entry, document the behavior in
README and the in-app guide, and add tests (test_claude_md_merge.py,
permission-merge cases, and an install-mcp preservation test).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 14, 2026 10:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR makes C3’s project config generation non-destructive by ensuring regenerated instruction docs and permission-tier applications only replace C3-managed content while preserving user-authored content.

Changes:

  • Introduces C3-managed sentinel blocks for instruction docs and routes all write paths through shared merge/write helpers.
  • Adds permission-tier merging to preserve user-added allow/deny entries and non-list permission keys while replacing only C3-managed tier entries.
  • Expands test coverage for instruction-doc merging/compaction and install-mcp preservation behavior; bumps version to 2.37.0 and updates docs/changelog.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
services/claude_md.py Adds managed-block sentinels + merge/write helpers; updates compact() to operate on the managed inner block.
services/session_manager.py Switches instruction doc writes to the new non-destructive writer.
cli/commands/common.py Routes c3 claudemd save through the non-destructive writer.
cli/server.py Updates Hub save and permission-tier apply to merge rather than overwrite.
cli/hub_server.py Updates per-project permission-tier apply to merge rather than overwrite.
cli/c3.py Adds _merge_permission_tier and Stop-hook preservation logic; bumps CLI version.
tests/test_claude_md_merge.py New tests covering managed-block wrapping/merging and compaction preservation.
tests/test_permissions.py Updates tests to validate permission merging and tier switching behavior.
tests/test_install_mcp_entrypoint.py Adds install-mcp test ensuring user config (servers, permissions, hooks) is preserved.
README.md Documents managed-block behavior and new permission-tier merge semantics.
cli/guide/getting-started.html Updates guide to describe non-destructive merges for instruction docs, hooks, and permissions.
pyproject.toml Bumps package version to 2.37.0.
CHANGELOG.md Adds 2.37.0 entry describing non-destructive generation behavior.
CLAUDE.md Updates project structure listing content.

Comment thread services/claude_md.py
Comment on lines +109 to +116
# 1. Markers present → surgical in-place replacement.
if C3_BLOCK_BEGIN in existing and C3_BLOCK_END in existing:
start = existing.index(C3_BLOCK_BEGIN)
end = existing.index(C3_BLOCK_END) + len(C3_BLOCK_END)
before = existing[:start].rstrip()
after = existing[end:].lstrip()
parts = [p for p in (before, new_block, after) if p]
return "\n\n".join(parts) + "\n"
Comment thread services/claude_md.py
Comment on lines +396 to +404
has_block = C3_BLOCK_BEGIN in current and C3_BLOCK_END in current
if has_block:
start = current.index(C3_BLOCK_BEGIN)
end = current.index(C3_BLOCK_END) + len(C3_BLOCK_END)
before = current[:start]
after = current[end:]
inner = current[start + len(C3_BLOCK_BEGIN):end - len(C3_BLOCK_END)].strip()
if inner.startswith(C3_BLOCK_HEADING):
inner = inner[len(C3_BLOCK_HEADING):].lstrip("\n")
Comment thread services/claude_md.py
Comment on lines +409 to +414
pieces = []
if before.strip():
pieces.append(before.strip())
pieces.append(wrap_c3_block(compacted_inner))
if after.strip():
pieces.append(after.strip())
Comment on lines +8 to +12
import tempfile
import unittest
from pathlib import Path

from services.claude_md import (
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@drknowhow drknowhow merged commit 865ca8a into main Jun 14, 2026
11 checks passed
@drknowhow drknowhow deleted the feat/non-destructive-config-v2.37.0 branch June 14, 2026 10: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