crucible-mcp 0.4.0__py3-none-any.whl → 1.0.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.
Files changed (52) hide show
  1. crucible/cli.py +532 -12
  2. crucible/enforcement/budget.py +179 -0
  3. crucible/enforcement/bundled/error-handling.yaml +84 -0
  4. crucible/enforcement/bundled/security.yaml +123 -0
  5. crucible/enforcement/bundled/smart-contract.yaml +110 -0
  6. crucible/enforcement/compliance.py +486 -0
  7. crucible/enforcement/models.py +71 -1
  8. crucible/hooks/claudecode.py +388 -0
  9. crucible/hooks/precommit.py +117 -25
  10. crucible/knowledge/loader.py +186 -0
  11. crucible/knowledge/principles/API_DESIGN.md +176 -0
  12. crucible/knowledge/principles/COMMITS.md +127 -0
  13. crucible/knowledge/principles/DATABASE.md +138 -0
  14. crucible/knowledge/principles/DOCUMENTATION.md +201 -0
  15. crucible/knowledge/principles/ERROR_HANDLING.md +157 -0
  16. crucible/knowledge/principles/FP.md +162 -0
  17. crucible/knowledge/principles/GITIGNORE.md +218 -0
  18. crucible/knowledge/principles/OBSERVABILITY.md +147 -0
  19. crucible/knowledge/principles/PRECOMMIT.md +201 -0
  20. crucible/knowledge/principles/SECURITY.md +136 -0
  21. crucible/knowledge/principles/SMART_CONTRACT.md +153 -0
  22. crucible/knowledge/principles/SYSTEM_DESIGN.md +153 -0
  23. crucible/knowledge/principles/TESTING.md +129 -0
  24. crucible/knowledge/principles/TYPE_SAFETY.md +170 -0
  25. crucible/review/core.py +78 -7
  26. crucible/server.py +81 -14
  27. crucible/skills/accessibility-engineer/SKILL.md +71 -0
  28. crucible/skills/backend-engineer/SKILL.md +69 -0
  29. crucible/skills/customer-success/SKILL.md +69 -0
  30. crucible/skills/data-engineer/SKILL.md +70 -0
  31. crucible/skills/devops-engineer/SKILL.md +69 -0
  32. crucible/skills/fde-engineer/SKILL.md +69 -0
  33. crucible/skills/formal-verification/SKILL.md +86 -0
  34. crucible/skills/gas-optimizer/SKILL.md +89 -0
  35. crucible/skills/incident-responder/SKILL.md +91 -0
  36. crucible/skills/mev-researcher/SKILL.md +87 -0
  37. crucible/skills/mobile-engineer/SKILL.md +70 -0
  38. crucible/skills/performance-engineer/SKILL.md +68 -0
  39. crucible/skills/product-engineer/SKILL.md +68 -0
  40. crucible/skills/protocol-architect/SKILL.md +83 -0
  41. crucible/skills/security-engineer/SKILL.md +63 -0
  42. crucible/skills/tech-lead/SKILL.md +92 -0
  43. crucible/skills/uiux-engineer/SKILL.md +70 -0
  44. crucible/skills/web3-engineer/SKILL.md +79 -0
  45. crucible/tools/git.py +17 -4
  46. crucible_mcp-1.0.0.dist-info/METADATA +198 -0
  47. crucible_mcp-1.0.0.dist-info/RECORD +66 -0
  48. crucible_mcp-0.4.0.dist-info/METADATA +0 -160
  49. crucible_mcp-0.4.0.dist-info/RECORD +0 -28
  50. {crucible_mcp-0.4.0.dist-info → crucible_mcp-1.0.0.dist-info}/WHEEL +0 -0
  51. {crucible_mcp-0.4.0.dist-info → crucible_mcp-1.0.0.dist-info}/entry_points.txt +0 -0
  52. {crucible_mcp-0.4.0.dist-info → crucible_mcp-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,179 @@
1
+ """Token budget estimation and tracking for LLM compliance assertions."""
2
+
3
+ from crucible.enforcement.models import (
4
+ Assertion,
5
+ AssertionType,
6
+ BudgetState,
7
+ ComplianceConfig,
8
+ )
9
+
10
+ # Average tokens per character (rough estimate for code)
11
+ TOKENS_PER_CHAR = 0.25
12
+
13
+ # Base overhead for each LLM call (system prompt, response format, etc.)
14
+ BASE_OVERHEAD_TOKENS = 200
15
+
16
+ # Minimum tokens for compliance prompt
17
+ MIN_COMPLIANCE_TOKENS = 50
18
+
19
+
20
+ def estimate_assertion_tokens(assertion: Assertion, content_length: int) -> int:
21
+ """Estimate tokens needed to run an LLM assertion.
22
+
23
+ Args:
24
+ assertion: The assertion to estimate
25
+ content_length: Length of content to analyze in characters
26
+
27
+ Returns:
28
+ Estimated token count for input
29
+ """
30
+ if assertion.type != AssertionType.LLM:
31
+ return 0
32
+
33
+ # Content tokens
34
+ content_tokens = int(content_length * TOKENS_PER_CHAR)
35
+
36
+ # Compliance prompt tokens
37
+ compliance_tokens = 0
38
+ if assertion.compliance:
39
+ compliance_tokens = max(
40
+ MIN_COMPLIANCE_TOKENS,
41
+ int(len(assertion.compliance) * TOKENS_PER_CHAR),
42
+ )
43
+
44
+ return BASE_OVERHEAD_TOKENS + content_tokens + compliance_tokens
45
+
46
+
47
+ def estimate_total_budget(
48
+ assertions: list[Assertion],
49
+ content_length: int,
50
+ ) -> int:
51
+ """Estimate total tokens needed to run all LLM assertions.
52
+
53
+ Args:
54
+ assertions: List of assertions (filters to LLM only)
55
+ content_length: Length of content to analyze
56
+
57
+ Returns:
58
+ Estimated total token count
59
+ """
60
+ total = 0
61
+ for assertion in assertions:
62
+ if assertion.type == AssertionType.LLM:
63
+ total += estimate_assertion_tokens(assertion, content_length)
64
+ return total
65
+
66
+
67
+ def sort_by_priority(assertions: list[Assertion]) -> list[Assertion]:
68
+ """Sort assertions by priority (critical first).
69
+
70
+ Args:
71
+ assertions: Assertions to sort
72
+
73
+ Returns:
74
+ Sorted list (critical > high > medium > low)
75
+ """
76
+ return sorted(assertions, key=lambda a: a.priority.rank)
77
+
78
+
79
+ def select_within_budget(
80
+ assertions: list[Assertion],
81
+ content_length: int,
82
+ budget: int,
83
+ ) -> tuple[list[Assertion], list[Assertion]]:
84
+ """Select assertions that fit within token budget.
85
+
86
+ Args:
87
+ assertions: Assertions to select from (should be pre-sorted by priority)
88
+ content_length: Length of content to analyze
89
+ budget: Token budget (0 = unlimited)
90
+
91
+ Returns:
92
+ Tuple of (selected_assertions, skipped_assertions)
93
+ """
94
+ if budget == 0:
95
+ # Unlimited budget
96
+ return list(assertions), []
97
+
98
+ selected: list[Assertion] = []
99
+ skipped: list[Assertion] = []
100
+ tokens_used = 0
101
+
102
+ for assertion in assertions:
103
+ if assertion.type != AssertionType.LLM:
104
+ continue
105
+
106
+ estimated = estimate_assertion_tokens(assertion, content_length)
107
+
108
+ if tokens_used + estimated <= budget:
109
+ selected.append(assertion)
110
+ tokens_used += estimated
111
+ else:
112
+ skipped.append(assertion)
113
+
114
+ return selected, skipped
115
+
116
+
117
+ def filter_llm_assertions(assertions: list[Assertion]) -> list[Assertion]:
118
+ """Filter to only LLM-type assertions.
119
+
120
+ Args:
121
+ assertions: All assertions
122
+
123
+ Returns:
124
+ Only assertions with type=llm
125
+ """
126
+ return [a for a in assertions if a.type == AssertionType.LLM]
127
+
128
+
129
+ def create_budget_state(config: ComplianceConfig) -> BudgetState:
130
+ """Create initial budget state from config.
131
+
132
+ Args:
133
+ config: Compliance configuration
134
+
135
+ Returns:
136
+ Fresh BudgetState
137
+ """
138
+ return BudgetState(total_budget=config.token_budget)
139
+
140
+
141
+ def prepare_llm_assertions(
142
+ assertions: list[Assertion],
143
+ content_length: int,
144
+ config: ComplianceConfig,
145
+ ) -> tuple[list[Assertion], BudgetState]:
146
+ """Prepare LLM assertions for execution.
147
+
148
+ Filters to LLM assertions, sorts by priority, and selects within budget.
149
+
150
+ Args:
151
+ assertions: All loaded assertions
152
+ content_length: Length of content to analyze
153
+ config: Compliance configuration
154
+
155
+ Returns:
156
+ Tuple of (assertions_to_run, budget_state)
157
+ """
158
+ # Filter to LLM assertions only
159
+ llm_assertions = filter_llm_assertions(assertions)
160
+
161
+ if not llm_assertions:
162
+ return [], create_budget_state(config)
163
+
164
+ # Sort by priority
165
+ sorted_assertions = sort_by_priority(llm_assertions)
166
+
167
+ # Select within budget
168
+ selected, skipped = select_within_budget(
169
+ sorted_assertions,
170
+ content_length,
171
+ config.token_budget,
172
+ )
173
+
174
+ # Create budget state
175
+ state = create_budget_state(config)
176
+ for assertion in skipped:
177
+ state.skip(assertion.id)
178
+
179
+ return selected, state
@@ -0,0 +1,84 @@
1
+ version: "1.0"
2
+ name: error-handling
3
+ description: Error handling best practices to prevent silent failures
4
+ linked_knowledge: ERROR_HANDLING.md
5
+
6
+ assertions:
7
+ # High: Silent failures
8
+ - id: no-bare-except
9
+ type: pattern
10
+ pattern: "except\\s*:"
11
+ message: "Bare except catches everything including SystemExit/KeyboardInterrupt - catch specific exceptions"
12
+ severity: warning
13
+ priority: high
14
+ languages: [python]
15
+
16
+ - id: no-pass-in-except
17
+ type: pattern
18
+ pattern: "except[^:]*:\\s*\\n\\s*pass\\s*$"
19
+ message: "Empty except block silently swallows errors - log or re-raise"
20
+ severity: warning
21
+ priority: high
22
+ languages: [python]
23
+
24
+ - id: no-empty-catch
25
+ type: pattern
26
+ pattern: "catch\\s*\\([^)]*\\)\\s*\\{\\s*\\}"
27
+ message: "Empty catch block silently swallows errors - log or re-throw"
28
+ severity: warning
29
+ priority: high
30
+ languages: [javascript, typescript]
31
+
32
+ # Medium: Pokemon exception handling
33
+ - id: no-catch-exception
34
+ type: pattern
35
+ pattern: "except\\s+Exception\\s*:"
36
+ message: "Catching Exception is too broad - catch specific exception types"
37
+ severity: info
38
+ priority: medium
39
+ languages: [python]
40
+
41
+ - id: no-catch-base-exception
42
+ type: pattern
43
+ pattern: "except\\s+BaseException\\s*:"
44
+ message: "Catching BaseException includes SystemExit/KeyboardInterrupt - use Exception or specific types"
45
+ severity: warning
46
+ priority: medium
47
+ languages: [python]
48
+
49
+ # Medium: Error suppression
50
+ - id: no-ignore-errors-flag
51
+ type: pattern
52
+ pattern: "errors\\s*=\\s*[\"']ignore[\"']"
53
+ message: "errors='ignore' silently drops data - use 'replace' or handle explicitly"
54
+ severity: warning
55
+ priority: medium
56
+ languages: [python]
57
+
58
+ # Info: Best practices
59
+ - id: prefer-contextlib-suppress
60
+ type: pattern
61
+ pattern: "except\\s+\\w+\\s*:\\s*\\n\\s*pass\\s*$"
62
+ message: "Consider contextlib.suppress() for intentionally ignoring specific exceptions"
63
+ severity: info
64
+ priority: low
65
+ languages: [python]
66
+
67
+ # LLM assertion for complex error handling patterns
68
+ - id: error-handling-semantic
69
+ type: llm
70
+ compliance: |
71
+ Check that error handling follows best practices:
72
+ 1. Errors are not silently swallowed (no empty except/catch blocks that do nothing)
73
+ 2. Exceptions are logged or reported before being suppressed
74
+ 3. Error messages provide enough context to debug issues
75
+ 4. Errors at API/system boundaries are handled (not just propagated)
76
+ message: "Error handling may need improvement"
77
+ severity: warning
78
+ priority: medium
79
+ languages: [python, javascript, typescript]
80
+ applicability:
81
+ exclude:
82
+ - "**/test_*.py"
83
+ - "**/*_test.py"
84
+ - "**/tests/**"
@@ -0,0 +1,123 @@
1
+ version: "1.0"
2
+ name: security
3
+ description: Security assertions to catch common vulnerabilities
4
+ linked_knowledge: SECURITY.md
5
+
6
+ assertions:
7
+ # Critical: Code execution risks
8
+ - id: no-eval
9
+ type: pattern
10
+ pattern: "\\beval\\s*\\("
11
+ message: "eval() is dangerous - use ast.literal_eval() for data or safer alternatives"
12
+ severity: error
13
+ priority: critical
14
+ languages: [python, javascript, typescript]
15
+
16
+ - id: no-exec
17
+ type: pattern
18
+ pattern: "\\bexec\\s*\\("
19
+ message: "exec() allows arbitrary code execution - avoid or sandbox carefully"
20
+ severity: error
21
+ priority: critical
22
+ languages: [python]
23
+
24
+ # Critical: Shell injection
25
+ - id: no-shell-true
26
+ type: pattern
27
+ pattern: "shell\\s*=\\s*True"
28
+ message: "shell=True enables shell injection - use shell=False with argument list"
29
+ severity: error
30
+ priority: critical
31
+ languages: [python]
32
+
33
+ - id: no-os-system
34
+ type: pattern
35
+ pattern: "os\\.system\\s*\\("
36
+ message: "os.system() is vulnerable to shell injection - use subprocess with shell=False"
37
+ severity: error
38
+ priority: critical
39
+ languages: [python]
40
+
41
+ # Critical: Deserialization
42
+ - id: no-pickle-load
43
+ type: pattern
44
+ pattern: "pickle\\.load|pickle\\.loads|cPickle\\.load"
45
+ message: "pickle can execute arbitrary code - use json or msgpack for untrusted data"
46
+ severity: error
47
+ priority: critical
48
+ languages: [python]
49
+
50
+ - id: no-yaml-unsafe-load
51
+ type: pattern
52
+ pattern: "yaml\\.load\\s*\\([^)]*\\)(?!.*Loader)"
53
+ message: "yaml.load() without Loader is unsafe - use yaml.safe_load()"
54
+ severity: error
55
+ priority: critical
56
+ languages: [python]
57
+
58
+ # High: Hardcoded secrets
59
+ - id: no-hardcoded-password
60
+ type: pattern
61
+ pattern: "(?i)(password|passwd|pwd)\\s*=\\s*[\"'][^\"']{4,}[\"']"
62
+ message: "Possible hardcoded password - use environment variables or secrets manager"
63
+ severity: error
64
+ priority: high
65
+ applicability:
66
+ exclude:
67
+ - "**/test_*.py"
68
+ - "**/*_test.py"
69
+ - "**/tests/**"
70
+ - "**/*.md"
71
+
72
+ - id: no-hardcoded-api-key
73
+ type: pattern
74
+ pattern: "(?i)(api[_-]?key|apikey|secret[_-]?key)\\s*=\\s*[\"'][a-zA-Z0-9]{16,}[\"']"
75
+ message: "Possible hardcoded API key - use environment variables or secrets manager"
76
+ severity: error
77
+ priority: high
78
+ applicability:
79
+ exclude:
80
+ - "**/test_*.py"
81
+ - "**/*_test.py"
82
+ - "**/tests/**"
83
+ - "**/*.md"
84
+
85
+ # High: SQL injection
86
+ - id: no-string-sql
87
+ type: pattern
88
+ pattern: "execute\\s*\\(\\s*[\"']\\s*SELECT|execute\\s*\\(\\s*f[\"']|execute\\s*\\([^)]*%\\s*\\("
89
+ message: "Possible SQL injection - use parameterized queries"
90
+ severity: error
91
+ priority: high
92
+ languages: [python]
93
+
94
+ # Medium: Weak crypto
95
+ - id: no-md5-security
96
+ type: pattern
97
+ pattern: "hashlib\\.md5|MD5\\s*\\(|md5\\.new"
98
+ message: "MD5 is cryptographically broken - use SHA-256 or better for security"
99
+ severity: warning
100
+ priority: medium
101
+ languages: [python]
102
+
103
+ - id: no-sha1-security
104
+ type: pattern
105
+ pattern: "hashlib\\.sha1|SHA1\\s*\\(|sha1\\.new"
106
+ message: "SHA-1 is deprecated for security - use SHA-256 or better"
107
+ severity: warning
108
+ priority: medium
109
+ languages: [python]
110
+
111
+ # Medium: Insecure random
112
+ - id: no-random-for-security
113
+ type: pattern
114
+ pattern: "\\brandom\\.(choice|randint|random|uniform)\\s*\\("
115
+ message: "random module is not cryptographically secure - use secrets module"
116
+ severity: warning
117
+ priority: medium
118
+ languages: [python]
119
+ applicability:
120
+ exclude:
121
+ - "**/test_*.py"
122
+ - "**/*_test.py"
123
+ - "**/tests/**"
@@ -0,0 +1,110 @@
1
+ version: "1.0"
2
+ name: smart-contract
3
+ description: Smart contract security patterns for Solidity/Vyper
4
+ linked_knowledge: SMART_CONTRACT.md
5
+
6
+ assertions:
7
+ # Critical: Reentrancy
8
+ - id: state-after-external-call
9
+ type: pattern
10
+ pattern: "\\.call\\{[^}]*\\}\\([^)]*\\)[^;]*;[^}]*\\b(balances|_balances|userBalances|amounts)\\s*\\["
11
+ message: "State change after external call - potential reentrancy vulnerability (CEI violation)"
12
+ severity: error
13
+ priority: critical
14
+ languages: [solidity]
15
+
16
+ - id: transfer-without-reentrancy-guard
17
+ type: llm
18
+ compliance: |
19
+ Check for reentrancy vulnerabilities:
20
+ 1. External calls (.call, .transfer, .send) should come AFTER state changes (CEI pattern)
21
+ 2. Functions making external calls should have reentrancy guards (nonReentrant modifier)
22
+ 3. State variables should be updated before external calls, not after
23
+ 4. Check-effects-interactions pattern: checks first, then effects (state changes), then interactions (external calls)
24
+ message: "Potential reentrancy vulnerability - apply CEI pattern and/or nonReentrant modifier"
25
+ severity: error
26
+ priority: critical
27
+ model: opus
28
+ languages: [solidity]
29
+
30
+ # Critical: Access control
31
+ - id: missing-access-control
32
+ type: pattern
33
+ pattern: "function\\s+\\w+\\s*\\([^)]*\\)\\s+(external|public)(?![^{]*onlyOwner|[^{]*onlyRole|[^{]*require\\s*\\(\\s*msg\\.sender)"
34
+ message: "Public/external function may need access control"
35
+ severity: warning
36
+ priority: high
37
+ languages: [solidity]
38
+
39
+ - id: tx-origin-auth
40
+ type: pattern
41
+ pattern: "require\\s*\\(\\s*tx\\.origin\\s*==|tx\\.origin\\s*==\\s*msg\\.sender"
42
+ message: "tx.origin for auth is vulnerable to phishing - use msg.sender"
43
+ severity: error
44
+ priority: critical
45
+ languages: [solidity]
46
+
47
+ # High: Integer overflow (pre-0.8.0)
48
+ - id: unchecked-arithmetic
49
+ type: pattern
50
+ pattern: "unchecked\\s*\\{[^}]*[+\\-*/]"
51
+ message: "Unchecked arithmetic - ensure overflow/underflow is intentional and safe"
52
+ severity: warning
53
+ priority: high
54
+ languages: [solidity]
55
+
56
+ # High: Front-running
57
+ - id: block-timestamp-comparison
58
+ type: pattern
59
+ pattern: "block\\.timestamp\\s*[<>=]|[<>=]\\s*block\\.timestamp"
60
+ message: "block.timestamp can be manipulated by miners (~15 sec) - avoid for critical logic"
61
+ severity: warning
62
+ priority: medium
63
+ languages: [solidity]
64
+
65
+ # High: Denial of service
66
+ - id: unbounded-loop
67
+ type: pattern
68
+ pattern: "for\\s*\\([^;]*;[^;]*\\.length\\s*;"
69
+ message: "Loop bounded by dynamic array length - may cause out-of-gas DoS"
70
+ severity: warning
71
+ priority: high
72
+ languages: [solidity]
73
+
74
+ # Medium: Best practices
75
+ - id: missing-zero-address-check
76
+ type: pattern
77
+ pattern: "address\\s+\\w+\\s*=[^;]*;(?![^}]*require\\s*\\([^)]*!=\\s*address\\(0\\))"
78
+ message: "Consider checking for zero address on address parameters"
79
+ severity: info
80
+ priority: medium
81
+ languages: [solidity]
82
+
83
+ - id: hardcoded-gas
84
+ type: pattern
85
+ pattern: "\\.call\\{[^}]*gas:\\s*\\d+[^}]*\\}"
86
+ message: "Hardcoded gas values may break with EVM changes - use gasleft() or remove gas limit"
87
+ severity: warning
88
+ priority: medium
89
+ languages: [solidity]
90
+
91
+ # LLM: Complex security patterns
92
+ - id: smart-contract-security-review
93
+ type: llm
94
+ compliance: |
95
+ Perform a security review of this smart contract code:
96
+ 1. Check for reentrancy vulnerabilities (external calls before state changes)
97
+ 2. Verify access control on privileged functions
98
+ 3. Check for integer overflow/underflow risks
99
+ 4. Look for front-running vulnerabilities
100
+ 5. Check for DoS vectors (unbounded loops, block gas limit issues)
101
+ 6. Verify proper validation of external inputs
102
+ 7. Check for unsafe delegatecall usage
103
+ 8. Look for flash loan attack vectors
104
+ message: "Smart contract security issue detected"
105
+ severity: error
106
+ priority: critical
107
+ model: opus
108
+ languages: [solidity, vyper]
109
+ applicability:
110
+ glob: "**/*.sol"