crucible-mcp 0.1.0__py3-none-any.whl → 0.3.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.
@@ -16,6 +16,25 @@ KNOWLEDGE_USER = Path.home() / ".claude" / "crucible" / "knowledge"
16
16
  KNOWLEDGE_PROJECT = Path(".crucible") / "knowledge"
17
17
 
18
18
 
19
+ def load_knowledge_file(filename: str) -> Result[str, str]:
20
+ """Load a single knowledge file by name.
21
+
22
+ Args:
23
+ filename: Knowledge file name (e.g., "SECURITY.md")
24
+
25
+ Returns:
26
+ Result containing file content or error message
27
+ """
28
+ path, source = resolve_knowledge_file(filename)
29
+ if path is None:
30
+ return err(f"Knowledge file '{filename}' not found")
31
+
32
+ try:
33
+ return ok(path.read_text())
34
+ except OSError as e:
35
+ return err(f"Failed to read '{filename}': {e}")
36
+
37
+
19
38
  def resolve_knowledge_file(filename: str) -> tuple[Path | None, str]:
20
39
  """Find knowledge file with cascade priority.
21
40
 
@@ -52,6 +71,55 @@ def get_all_knowledge_files() -> set[str]:
52
71
  return files
53
72
 
54
73
 
74
+ def get_custom_knowledge_files() -> set[str]:
75
+ """Get knowledge files from project and user directories only.
76
+
77
+ These are custom/team knowledge files that should always be included
78
+ in full_review, regardless of skill references.
79
+
80
+ Returns:
81
+ Set of filenames from project and user knowledge directories
82
+ """
83
+ files: set[str] = set()
84
+
85
+ for source_dir in [KNOWLEDGE_USER, KNOWLEDGE_PROJECT]:
86
+ if source_dir.exists():
87
+ for file_path in source_dir.iterdir():
88
+ if file_path.is_file() and file_path.suffix == ".md":
89
+ files.add(file_path.name)
90
+
91
+ return files
92
+
93
+
94
+ def load_all_knowledge(
95
+ include_bundled: bool = False,
96
+ filenames: set[str] | None = None,
97
+ ) -> tuple[list[str], str]:
98
+ """Load multiple knowledge files.
99
+
100
+ Args:
101
+ include_bundled: If True, include bundled knowledge files
102
+ filenames: Specific files to load (if None, loads based on include_bundled)
103
+
104
+ Returns:
105
+ Tuple of (list of loaded filenames, combined content)
106
+ """
107
+ if filenames is None:
108
+ filenames = get_all_knowledge_files() if include_bundled else get_custom_knowledge_files()
109
+
110
+ loaded: list[str] = []
111
+ parts: list[str] = []
112
+
113
+ for filename in sorted(filenames):
114
+ result = load_knowledge_file(filename)
115
+ if result.is_ok:
116
+ loaded.append(filename)
117
+ parts.append(f"# {filename}\n\n{result.value}")
118
+
119
+ content = "\n\n---\n\n".join(parts) if parts else ""
120
+ return loaded, content
121
+
122
+
55
123
  def load_principles(topic: str | None = None) -> Result[str, str]:
56
124
  """
57
125
  Load engineering principles from markdown files.
@@ -66,9 +134,10 @@ def load_principles(topic: str | None = None) -> Result[str, str]:
66
134
  topic_files = {
67
135
  None: ["SECURITY.md", "TESTING.md"], # Default: security + testing basics
68
136
  "engineering": ["TESTING.md", "ERROR_HANDLING.md", "TYPE_SAFETY.md"],
69
- "security": ["SECURITY.md"],
137
+ "security": ["SECURITY.md", "GITIGNORE.md", "PRECOMMIT.md"],
70
138
  "smart_contract": ["SMART_CONTRACT.md"],
71
139
  "checklist": ["SECURITY.md", "TESTING.md", "ERROR_HANDLING.md"],
140
+ "repo_hygiene": ["GITIGNORE.md", "PRECOMMIT.md", "COMMITS.md"],
72
141
  }
73
142
 
74
143
  files_to_load = topic_files.get(topic, topic_files[None])
crucible/models.py CHANGED
@@ -59,3 +59,18 @@ DOMAIN_HEURISTICS: dict[Domain, dict[str, list[str]]] = {
59
59
  "markers": ["resource ", "provider ", "apiVersion:", "kind:"],
60
60
  },
61
61
  }
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class FullReviewResult:
66
+ """Result from full_review tool."""
67
+
68
+ domains_detected: tuple[str, ...]
69
+ severity_summary: dict[str, int]
70
+ findings: tuple[ToolFinding, ...]
71
+ applicable_skills: tuple[str, ...]
72
+ skill_triggers_matched: dict[str, tuple[str, ...]]
73
+ principles_loaded: tuple[str, ...]
74
+ principles_content: str
75
+ sage_knowledge: str | None = None
76
+ sage_query_used: str | None = None