Skip to content

feat(git): add git_branch_delete tool + fix(lean): wire configured token limits into ResponseOffloader#182

Merged
MementoRC merged 5 commits into
developmentfrom
feat/issue-178-required-signatures
Jun 18, 2026
Merged

feat(git): add git_branch_delete tool + fix(lean): wire configured token limits into ResponseOffloader#182
MementoRC merged 5 commits into
developmentfrom
feat/issue-178-required-signatures

Conversation

@MementoRC

Copy link
Copy Markdown
Owner

Summary

Two related improvements to the lean git MCP interface, branched off the #178 required-signatures work.

1. git_branch_delete — discoverable local branch deletion

Local branch deletion already worked via git_branch_update(delete=True), but it was undiscoverable by name (callers scanning for a git_branch_delete tool concluded it didn't exist). Adds a thin git_branch_delete(repo, branch_name, force=False) that delegates to the existing git_branch_update delete path — no command reimplementation.

  • operations_extended.py: git_branch_delete delegating to git_branch_update(..., delete=True, force=force)
  • models.py: GitBranchDelete (repo_path, branch_name, force)
  • lean/registry_git.py: ToolDefinition registered after git_branch_update
  • tests/unit/git/test_git_branch_delete.py: 11 tests

2. Fix: wire configured token limits into ResponseOffloader

ResponseOffloader's offload-to-/tmp threshold was hard-wired to MCPTokenLimiter's 2000-token default (~7 KB). The MCP_GIT_LLM_TOKEN_LIMIT (20000) / MCP_GIT_UNKNOWN_TOKEN_LIMIT (25000) env vars were loaded by TokenLimitConfigManager but never consumed by the lean stack — dead config. As a result the server offloaded ~10x more aggressively than configured.

GitLeanInterface.__init__ now builds its default MCPTokenLimiter from config_manager.get_current_settings() (default_limit=settings.unknown_token_limit, operation_limits=settings.operation_limits) so the env-configured limits actually gate offloading.

  • lean/interface.py: settings-driven default MCPTokenLimiter
  • tests/unit/lean/test_offloader_token_limit.py: regression test (3 cases)

Test plan

  • pixi run -e quality test-unit — full unit suite passes (exit 0)
  • pixi run -e quality lint (ruff F,E9) — clean

⚠️ Both changes require an MCP server restart to take effect at runtime.

🤖 Generated with Claude Code

MementoRC and others added 3 commits May 21, 2026 21:54
GitHub exposes commit-signature enforcement on a separate endpoint
(.../protection/required_signatures), so it could not be set through
github_update_branch_protection. Closes that gap by adding a dedicated
enable/disable/get trio, mirroring the existing
github_*_vulnerability_alerts pattern.

New tools:
  - github_get_required_signatures(repo_owner, repo_name, branch)
  - github_enable_required_signatures(repo_owner, repo_name, branch)
  - github_disable_required_signatures(repo_owner, repo_name, branch)

Endpoint: PUT/DELETE/GET /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures

- security.py: three async functions using github_client_context().
  PUT returns 200+JSON (not 204 like vulnerability_alerts PUT);
  DELETE returns 204; GET returns 200 with {enabled, url} or 404
  when branch protection is absent.
- models.py: three Pydantic models with (repo_owner, repo_name, branch).
- registry_github.py: ToolDefinition trio added after vulnerability_alerts.
- api.py untouched (wildcard re-export of security.py covers new funcs).
- README.md tool counts: 52 -> 55 and 51/22 -> 54/25.
- tests/unit/github/test_github_required_signatures.py: 7 tests via
  AsyncMock+patch(github_client_context) covering get (enabled/disabled/
  404), enable (200/404), disable (204/404).

Closes #178
Local branch deletion already worked via git_branch_update(delete=True),
but it was undiscoverable by name. Add a thin git_branch_delete tool that
delegates to git_branch_update's delete path (no command reimplementation),
so callers scanning for branch-delete find it directly.

- operations_extended.py: git_branch_delete(repo, branch_name, force=False)
  delegating to git_branch_update(..., delete=True, force=force).
- models.py: GitBranchDelete (repo_path, branch_name, force).
- registry_git.py: ToolDefinition registered after git_branch_update.
- tests/unit/git/test_git_branch_delete.py: 11 tests mirroring
  test_git_branch_update delete cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ResponseOffloader's offload threshold was hard-wired to MCPTokenLimiter's
2000-token default; MCP_GIT_LLM_TOKEN_LIMIT/MCP_GIT_UNKNOWN_TOKEN_LIMIT were
loaded but never consumed by the lean stack. GitLeanInterface now builds its
default MCPTokenLimiter from TokenLimitSettings so the env-configured limits
actually gate offloading. Adds a regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown

Claude finished @MementoRC's task —— View job


PR Review

Tasks:

  • Gather context and understand the request
  • Read changed files
  • Review code quality and best practices
  • Check test coverage
  • Post final review

Summary

This PR is well-structured with two independent but related improvements. The code is clean, follows existing patterns, and includes good test coverage. A few observations below.


git_branch_delete (Change 1)

Positive:

  • The delegation pattern (git_branch_deletegit_branch_update(..., delete=True)) is clean and avoids code duplication. Discoverability is the sole goal and is achieved.
  • GitBranchDelete model (models.py:338-343) correctly mirrors the relevant subset of GitBranchUpdate fields.
  • Input validation is inherited for free via the delegate — dangerous-character rejection in git_branch_update (operations_extended.py:79-81) protects git_branch_delete without extra code.
  • The ToolDefinition for git_branch_delete (registry_git.py:479-485) is registered immediately after git_branch_update, which is good for discoverability ordering.

Minor observations:

  1. GitBranchDelete model is unused at the call site. The model exists for schema generation only (lean registry uses .model_json_schema()), and the underlying wrap_repo_op / git_branch_delete function accepts raw kwargs. This is consistent with how other models in the codebase are used — just worth confirming it's intentional.

  2. git_branch_delete docstring says "force=True uses -D" — accurate, but it doesn't mention that attempting to delete the currently-checked-out branch will always fail regardless of force. This is a git constraint and the error is surfaced cleanly, but it could save callers a round-trip if documented (e.g., in the ToolDefinition description or the model field).

  3. Missing () parenthesis group in DANGEROUS_CHARS (operations_extended.py:14): [;&| ` $()] — the ( and ) in the regex character class are matched as literals but the regex variable is named DANGEROUS_CHARS suggesting shell injection prevention. Opening/closing parens are indeed shell-relevant, so this is fine — just noting it's already covered.


ResponseOffloader token limit fix (Change 2)

Positive:

  • The fix (interface.py:133-138) is minimal, correct, and exactly targets the described bug: the config_manager.get_current_settings() call was already available (it's imported at line 23) but was never used to build the default limiter.
  • The local _settings variable is used and discarded in __init__, which avoids keeping a stale reference. Clean.
  • The regression test (test_offloader_token_limit.py) uses monkeypatch.setenv + the _reset_config_manager autouse fixture to avoid test pollution, which is correct.

Observations:

  1. _reset_config_manager fixture accesses a private attribute (_token_limits_mod.config_manager._settings = None). This works, but it couples the test to an implementation detail. If the singleton's internal attribute is renamed, the fixture silently stops resetting. A public reset() or invalidate() method on TokenLimitConfigManager would be more robust — but this is a low-priority nit for a test file.

  2. test_git_lean_interface_token_limiter_uses_configured_limit (test_offloader_token_limit.py:66-84) constructs GitLeanInterface(git_service=None, ...). This succeeds because the registry build and meta-tool setup happen after token_limiter initialization, but if those later steps require a non-None service, the test would silently degrade. Looking at the code it seems the service is accessed lazily, so this is fine — worth verifying if the test ever becomes flaky.

  3. The ResponseOffloader itself is not directly tested for the threshold change: the three tests verify MCPTokenLimiter.would_truncate behavior and GitLeanInterface.token_limiter.default_limit, but don't call ResponseOffloader.should_offload end-to-end. Since ResponseOffloader delegates to token_limiter, this is acceptable, but a single end-to-end test invoking offloader.should_offload(mid_payload, "git_diff") would provide stronger regression guarantees.


Test Coverage

  • test_git_branch_delete.py: 11 tests across success, input validation (parametrized over 5 dangerous chars), and error handling. Coverage is thorough for a thin delegating function.
  • test_offloader_token_limit.py: 3 targeted tests covering the old-behavior baseline, the fixed behavior, and the end-to-end GitLeanInterface default. Good regression anchoring.
  • No tests for the GitBranchDelete model's Pydantic schema shape (e.g., defaults, field descriptions) — minor gap.

Security

No concerns. git_branch_delete inherits the DANGEROUS_CHARS injection protection from git_branch_update. The new model introduces no new path parameters.


Performance

No concerns. The delegation adds one function call frame, which is negligible.


Overall

The changes are correct and well-implemented. The two main nits worth acting on before merge:

  • (4) Consider a public reset method on the config singleton to avoid private-attribute coupling in tests.
  • (6) Optional: add an offloader.should_offload assertion in the regression test for a stronger end-to-end guarantee.

Everything else is either correct as-is or a very minor documentation opportunity. Looks good to merge after optional nit consideration.

@MementoRC MementoRC merged commit 29f3932 into development Jun 18, 2026
16 checks passed
@MementoRC MementoRC deleted the feat/issue-178-required-signatures branch June 18, 2026 22:31
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.

1 participant