crucible-mcp 1.0.0__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crucible/enforcement/compliance.py +9 -5
- crucible/server.py +72 -1
- {crucible_mcp-1.0.0.dist-info → crucible_mcp-1.1.0.dist-info}/METADATA +6 -3
- {crucible_mcp-1.0.0.dist-info → crucible_mcp-1.1.0.dist-info}/RECORD +7 -7
- {crucible_mcp-1.0.0.dist-info → crucible_mcp-1.1.0.dist-info}/WHEEL +0 -0
- {crucible_mcp-1.0.0.dist-info → crucible_mcp-1.1.0.dist-info}/entry_points.txt +0 -0
- {crucible_mcp-1.0.0.dist-info → crucible_mcp-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -6,6 +6,7 @@ Supports Sonnet (default) and Opus (for high-stakes assertions).
|
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
8
|
import os
|
|
9
|
+
import sys
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
11
12
|
from crucible.enforcement.budget import (
|
|
@@ -79,8 +80,8 @@ def _load_api_key_from_config() -> str | None:
|
|
|
79
80
|
key = data.get("anthropic_api_key") or data.get("ANTHROPIC_API_KEY")
|
|
80
81
|
if key:
|
|
81
82
|
return key
|
|
82
|
-
except Exception:
|
|
83
|
-
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(f"Warning: failed to read {config_path}: {e}", file=sys.stderr)
|
|
84
85
|
|
|
85
86
|
return None
|
|
86
87
|
|
|
@@ -294,27 +295,30 @@ def run_single_assertion(
|
|
|
294
295
|
)
|
|
295
296
|
|
|
296
297
|
except ImportError as e:
|
|
298
|
+
print(f"Warning: LLM assertion '{assertion.id}' skipped: {e}", file=sys.stderr)
|
|
297
299
|
return LLMAssertionResult(
|
|
298
300
|
assertion_id=assertion.id,
|
|
299
|
-
passed=True, #
|
|
301
|
+
passed=True, # Graceful degradation - don't fail on missing dependency
|
|
300
302
|
findings=(),
|
|
301
303
|
tokens_used=0,
|
|
302
304
|
model_used=model_name,
|
|
303
305
|
error=str(e),
|
|
304
306
|
)
|
|
305
307
|
except ValueError as e:
|
|
308
|
+
print(f"Warning: LLM assertion '{assertion.id}' skipped: {e}", file=sys.stderr)
|
|
306
309
|
return LLMAssertionResult(
|
|
307
310
|
assertion_id=assertion.id,
|
|
308
|
-
passed=True, #
|
|
311
|
+
passed=True, # Graceful degradation - don't fail on missing API key
|
|
309
312
|
findings=(),
|
|
310
313
|
tokens_used=0,
|
|
311
314
|
model_used=model_name,
|
|
312
315
|
error=str(e),
|
|
313
316
|
)
|
|
314
317
|
except Exception as e:
|
|
318
|
+
print(f"Warning: LLM assertion '{assertion.id}' failed: {e}", file=sys.stderr)
|
|
315
319
|
return LLMAssertionResult(
|
|
316
320
|
assertion_id=assertion.id,
|
|
317
|
-
passed=True, #
|
|
321
|
+
passed=True, # Graceful degradation - don't fail on API errors
|
|
318
322
|
findings=(),
|
|
319
323
|
tokens_used=0,
|
|
320
324
|
model_used=model_name,
|
crucible/server.py
CHANGED
|
@@ -7,6 +7,7 @@ from mcp.server import Server
|
|
|
7
7
|
from mcp.server.stdio import stdio_server
|
|
8
8
|
from mcp.types import TextContent, Tool
|
|
9
9
|
|
|
10
|
+
from crucible.enforcement.assertions import load_assertions
|
|
10
11
|
from crucible.knowledge.loader import (
|
|
11
12
|
get_custom_knowledge_files,
|
|
12
13
|
load_all_knowledge,
|
|
@@ -314,6 +315,20 @@ async def list_tools() -> list[Tool]:
|
|
|
314
315
|
},
|
|
315
316
|
},
|
|
316
317
|
),
|
|
318
|
+
Tool(
|
|
319
|
+
name="get_assertions",
|
|
320
|
+
description="Load active enforcement assertions for this project. Call at session start to understand what code patterns are enforced. Returns all pattern and LLM assertions that will be checked during reviews.",
|
|
321
|
+
inputSchema={
|
|
322
|
+
"type": "object",
|
|
323
|
+
"properties": {
|
|
324
|
+
"include_compliance": {
|
|
325
|
+
"type": "boolean",
|
|
326
|
+
"description": "Include LLM compliance assertion details (default: true)",
|
|
327
|
+
"default": True,
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
),
|
|
317
332
|
]
|
|
318
333
|
|
|
319
334
|
|
|
@@ -715,6 +730,61 @@ def _handle_load_knowledge(arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
715
730
|
return [TextContent(type="text", text="\n".join(output_parts))]
|
|
716
731
|
|
|
717
732
|
|
|
733
|
+
def _handle_get_assertions(arguments: dict[str, Any]) -> list[TextContent]:
|
|
734
|
+
"""Handle get_assertions tool - load active enforcement rules."""
|
|
735
|
+
include_compliance = arguments.get("include_compliance", True)
|
|
736
|
+
|
|
737
|
+
assertions, load_errors = load_assertions()
|
|
738
|
+
|
|
739
|
+
if not assertions and not load_errors:
|
|
740
|
+
return [TextContent(type="text", text="No assertions found. Add assertion files to .crucible/assertions/ or use bundled assertions.")]
|
|
741
|
+
|
|
742
|
+
parts: list[str] = ["# Active Enforcement Assertions\n"]
|
|
743
|
+
parts.append("These patterns are enforced during code review. Avoid these in your code.\n")
|
|
744
|
+
|
|
745
|
+
if load_errors:
|
|
746
|
+
parts.append("## Load Errors\n")
|
|
747
|
+
for error in load_errors:
|
|
748
|
+
parts.append(f"- {error}")
|
|
749
|
+
parts.append("")
|
|
750
|
+
|
|
751
|
+
# Group by source file / category
|
|
752
|
+
pattern_assertions = [a for a in assertions if a.type.value == "pattern"]
|
|
753
|
+
llm_assertions = [a for a in assertions if a.type.value == "llm"]
|
|
754
|
+
|
|
755
|
+
if pattern_assertions:
|
|
756
|
+
parts.append("## Pattern Assertions (fast, always run)\n")
|
|
757
|
+
parts.append("| ID | Message | Severity | Languages |")
|
|
758
|
+
parts.append("|---|---|---|---|")
|
|
759
|
+
for a in pattern_assertions:
|
|
760
|
+
langs = ", ".join(a.languages) if a.languages else "all"
|
|
761
|
+
parts.append(f"| `{a.id}` | {a.message} | {a.severity} | {langs} |")
|
|
762
|
+
parts.append("")
|
|
763
|
+
|
|
764
|
+
if llm_assertions and include_compliance:
|
|
765
|
+
parts.append("## LLM Compliance Assertions (semantic, budget-controlled)\n")
|
|
766
|
+
parts.append("| ID | Message | Severity | Model |")
|
|
767
|
+
parts.append("|---|---|---|---|")
|
|
768
|
+
for a in llm_assertions:
|
|
769
|
+
model = a.model or "sonnet"
|
|
770
|
+
parts.append(f"| `{a.id}` | {a.message} | {a.severity} | {model} |")
|
|
771
|
+
parts.append("")
|
|
772
|
+
|
|
773
|
+
# Show compliance requirements for LLM assertions
|
|
774
|
+
parts.append("### Compliance Requirements\n")
|
|
775
|
+
for a in llm_assertions:
|
|
776
|
+
parts.append(f"**{a.id}:**")
|
|
777
|
+
if a.compliance:
|
|
778
|
+
parts.append(f"```\n{a.compliance.strip()}\n```")
|
|
779
|
+
parts.append("")
|
|
780
|
+
|
|
781
|
+
# Summary
|
|
782
|
+
parts.append("---\n")
|
|
783
|
+
parts.append(f"**Total:** {len(pattern_assertions)} pattern + {len(llm_assertions)} LLM assertions")
|
|
784
|
+
|
|
785
|
+
return [TextContent(type="text", text="\n".join(parts))]
|
|
786
|
+
|
|
787
|
+
|
|
718
788
|
def _handle_delegate_semgrep(arguments: dict[str, Any]) -> list[TextContent]:
|
|
719
789
|
"""Handle delegate_semgrep tool."""
|
|
720
790
|
path = arguments.get("path", "")
|
|
@@ -1241,7 +1311,8 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
1241
1311
|
"quick_review": _handle_quick_review,
|
|
1242
1312
|
"full_review": _handle_full_review,
|
|
1243
1313
|
"review_changes": _handle_review_changes,
|
|
1244
|
-
#
|
|
1314
|
+
# Context injection tools (call at session start)
|
|
1315
|
+
"get_assertions": _handle_get_assertions,
|
|
1245
1316
|
"get_principles": _handle_get_principles,
|
|
1246
1317
|
"load_knowledge": _handle_load_knowledge,
|
|
1247
1318
|
"delegate_semgrep": _handle_delegate_semgrep,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crucible-mcp
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Code review MCP server for Claude. Not affiliated with Atlassian.
|
|
5
5
|
Author: be.nvy
|
|
6
6
|
License-Expression: MIT
|
|
@@ -109,13 +109,16 @@ Add to Claude Code (`.mcp.json`):
|
|
|
109
109
|
|
|
110
110
|
| Tool | Purpose |
|
|
111
111
|
|------|---------|
|
|
112
|
+
| `get_assertions()` | **Session start:** Load enforced patterns into context |
|
|
113
|
+
| `get_principles(topic)` | **Session start:** Load engineering knowledge by topic |
|
|
114
|
+
| `load_knowledge(files)` | **Session start:** Load specific knowledge files |
|
|
112
115
|
| `review(path)` | Full review: analysis + skills + knowledge + assertions |
|
|
113
116
|
| `review(mode='staged')` | Review git changes with enforcement |
|
|
114
|
-
| `load_knowledge(files)` | Load specific knowledge files |
|
|
115
|
-
| `get_principles(topic)` | Load engineering knowledge by topic |
|
|
116
117
|
| `delegate_*` | Direct tool access (semgrep, ruff, slither, bandit) |
|
|
117
118
|
| `check_tools()` | Show installed analysis tools |
|
|
118
119
|
|
|
120
|
+
**Tip:** Call `get_assertions()` at the start of a session so Claude knows what patterns to avoid *before* writing code.
|
|
121
|
+
|
|
119
122
|
## CLI
|
|
120
123
|
|
|
121
124
|
```bash
|
|
@@ -2,13 +2,13 @@ crucible/__init__.py,sha256=M4v_CsJVOdiAAPgmd54mxkkbnes8e5ifMznDuOJhzzY,77
|
|
|
2
2
|
crucible/cli.py,sha256=DTYt-V5W-DJSkQV3qXumsxImd2Ib3rn2ZgeL-m-dEvA,79233
|
|
3
3
|
crucible/errors.py,sha256=HrX_yvJEhXJoKodXGo_iY9wqx2J3ONYy0a_LbrVC5As,819
|
|
4
4
|
crucible/models.py,sha256=jaxbiPc1E7bJxKPLadZe1dbSJdq-WINsxjveeSNNqeg,2066
|
|
5
|
-
crucible/server.py,sha256=
|
|
5
|
+
crucible/server.py,sha256=TtvLWYSJsd8Szi0ZmjYQO4cPmqDlH_J6VxQ8xQY46Bo,49864
|
|
6
6
|
crucible/domain/__init__.py,sha256=2fsoB5wH2Pl3vtGRt4voYOSZ04-zLoW8pNq6nvzVMgU,118
|
|
7
7
|
crucible/domain/detection.py,sha256=TNeLB_VQgS1AsT5BKDf_tIpGa47THrFoRXwU4u54VB0,1797
|
|
8
8
|
crucible/enforcement/__init__.py,sha256=FOaGSrE1SWFPxBJ1L5VoDhQDmlJgRXXs_iiI20wHf2Q,867
|
|
9
9
|
crucible/enforcement/assertions.py,sha256=ay5QvJIr_YaqWYbrJNhbouafJOiy4ZhwiX7E9VAY3s4,8166
|
|
10
10
|
crucible/enforcement/budget.py,sha256=-wFTlVY80c3-eJhvmlWrdlS8LB8E25aMnhtYpwR38sQ,4706
|
|
11
|
-
crucible/enforcement/compliance.py,sha256=
|
|
11
|
+
crucible/enforcement/compliance.py,sha256=skod0sa381Jwrg-N1FmjJBw4o0BeiBiDEqQ1osKGDQk,15269
|
|
12
12
|
crucible/enforcement/models.py,sha256=dEcPiUL6JEOBtxWOgKd_PZnsW_nUIaFsx18L70fM59M,4574
|
|
13
13
|
crucible/enforcement/patterns.py,sha256=hE4Z-JJ9OBruSFPBDxw_aNaSJbyUPD2SWCEwA1KzDmI,9720
|
|
14
14
|
crucible/enforcement/bundled/error-handling.yaml,sha256=2OSRhZwUGkF18bNfpARrVRsvgPgpyWr3pC0OUhtLgl0,2741
|
|
@@ -59,8 +59,8 @@ crucible/synthesis/__init__.py,sha256=CYrkZG4bdAjp8XdOh1smfKscd3YU5lZlaDLGwLE9c-
|
|
|
59
59
|
crucible/tools/__init__.py,sha256=gFRThTk1E-fHzpe8bB5rtBG6Z6G-ysPzjVEHfKGbEYU,400
|
|
60
60
|
crucible/tools/delegation.py,sha256=_x1y76No3qkmGjjROVvMx1pSKKwU59aRu5R-r07lVFU,12871
|
|
61
61
|
crucible/tools/git.py,sha256=7-aJCesoQe3ZEBFcRxHBhY8RpZrBlNtHSns__RqiG04,10406
|
|
62
|
-
crucible_mcp-1.
|
|
63
|
-
crucible_mcp-1.
|
|
64
|
-
crucible_mcp-1.
|
|
65
|
-
crucible_mcp-1.
|
|
66
|
-
crucible_mcp-1.
|
|
62
|
+
crucible_mcp-1.1.0.dist-info/METADATA,sha256=3mJv-xrzkQHnjHaBFQvQsX8DeoCAVyAEu-jl2GhJ6uA,6471
|
|
63
|
+
crucible_mcp-1.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
64
|
+
crucible_mcp-1.1.0.dist-info/entry_points.txt,sha256=18BZaH1OlFSFYtKuHq0Z8yYX8Wmx7Ikfqay-P00ZX3Q,83
|
|
65
|
+
crucible_mcp-1.1.0.dist-info/top_level.txt,sha256=4hzuFgqbFPOO-WiU_DYxTm8VYIxTXh7Wlp0gRcWR0Cs,9
|
|
66
|
+
crucible_mcp-1.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|