code-finder 0.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.
Files changed (37) hide show
  1. claude_context/__init__.py +33 -0
  2. claude_context/agentic_integration.py +309 -0
  3. claude_context/ast_chunker.py +646 -0
  4. claude_context/config.py +239 -0
  5. claude_context/context_manager.py +627 -0
  6. claude_context/embeddings.py +307 -0
  7. claude_context/embeddings_interface.py +226 -0
  8. claude_context/enhanced_ast_chunker.py +1129 -0
  9. claude_context/explorer.py +951 -0
  10. claude_context/explorer_with_context.py +1008 -0
  11. claude_context/indexer.py +893 -0
  12. claude_context/markdown_chunker.py +421 -0
  13. claude_context/mode_handler.py +1774 -0
  14. claude_context/query_metrics.py +164 -0
  15. claude_context/question_generator.py +800 -0
  16. claude_context/readme_extractor.py +485 -0
  17. claude_context/repository_adapter.py +399 -0
  18. claude_context/search.py +493 -0
  19. claude_context/skills/__init__.py +11 -0
  20. claude_context/skills/_cli_common.py +74 -0
  21. claude_context/skills/_index_manager.py +98 -0
  22. claude_context/skills/api_surface.py +219 -0
  23. claude_context/skills/evidence_retrieval.py +151 -0
  24. claude_context/skills/grounded_review.py +212 -0
  25. claude_context/synthesis/__init__.py +8 -0
  26. claude_context/synthesis/editor_agent.py +391 -0
  27. claude_context/synthesis/llm_synthesizer.py +153 -0
  28. claude_context/synthesis/logic_explainer.py +235 -0
  29. claude_context/synthesis/multi_review_pipeline.py +717 -0
  30. claude_context/synthesis/prompt_builder.py +439 -0
  31. claude_context/synthesis/providers.py +115 -0
  32. claude_context/synthesis/validators.py +458 -0
  33. code_finder-0.1.0.dist-info/METADATA +823 -0
  34. code_finder-0.1.0.dist-info/RECORD +37 -0
  35. code_finder-0.1.0.dist-info/WHEEL +5 -0
  36. code_finder-0.1.0.dist-info/entry_points.txt +4 -0
  37. code_finder-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,235 @@
1
+ """LogicExplainer subagent
2
+
3
+ Produces rationale-rich summaries for code units so downstream documentation can
4
+ capture the _why_ along with the _what_. The explainer consumes semantic chunks
5
+ generated during indexing and, optionally, decision/context notes. Results feed
6
+ into the structured evidence pipeline (`rationale` tier).
7
+
8
+ Two-pass workflow note: the forthcoming editor agent will consume the
9
+ `LogicSummary` payloads directly to audit the synthesized sections. Treat the
10
+ outputs as canonical evidence blocks so the editor can compare its rewrites
11
+ against the underlying rationale rather than re-querying the repository.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import logging
18
+ from dataclasses import dataclass
19
+ from typing import Any, Dict, Iterable, List, Optional
20
+
21
+ from ..context_manager import ContextItem
22
+ from .providers import TextGenerator, create_generator
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ @dataclass
28
+ class LogicSummary:
29
+ """Structured output for a single code unit summary."""
30
+
31
+ unit_id: str
32
+ file_path: str
33
+ chunk_type: str
34
+ summary_text: str
35
+ rationale: str
36
+ trade_offs: List[str]
37
+ side_effects: List[str]
38
+ dependencies: List[str]
39
+ citations: List[str]
40
+ confidence: float
41
+
42
+ def to_dict(self) -> Dict[str, Any]:
43
+ return {
44
+ "unit_id": self.unit_id,
45
+ "file_path": self.file_path,
46
+ "chunk_type": self.chunk_type,
47
+ "summary": self.summary_text,
48
+ "rationale": self.rationale,
49
+ "trade_offs": self.trade_offs,
50
+ "side_effects": self.side_effects,
51
+ "dependencies": self.dependencies,
52
+ "citations": self.citations,
53
+ "confidence": self.confidence,
54
+ }
55
+
56
+
57
+ class LogicExplainer:
58
+ """LLM-based summarizer that captures design intent for code units.
59
+
60
+ Downstream editor passes should treat the returned dictionaries as
61
+ immutable evidence objects: each field provides the structured context the
62
+ editor needs to justify rewrites and preserve citations.
63
+ """
64
+
65
+ def __init__(
66
+ self,
67
+ generator: Optional[TextGenerator] = None,
68
+ *,
69
+ max_units: int = 12,
70
+ max_tokens: int = 600,
71
+ temperature: float = 0.15,
72
+ ) -> None:
73
+ self.generator = generator or create_generator()
74
+ self.max_units = max_units
75
+ self.max_tokens = max_tokens
76
+ self.temperature = temperature
77
+
78
+ def summarize_chunks(
79
+ self,
80
+ chunks: Iterable[Dict[str, Any]],
81
+ decision_notes: Optional[Iterable[ContextItem]] = None,
82
+ ) -> List[Dict[str, Any]]:
83
+ """Generate structured rationale summaries for selected code chunks."""
84
+
85
+ selected_chunks = [c for c in chunks if c.get("content")][: self.max_units]
86
+ if not selected_chunks:
87
+ return []
88
+
89
+ decision_context = self._format_decision_context(decision_notes)
90
+ summaries: List[Dict[str, Any]] = []
91
+
92
+ for chunk in selected_chunks:
93
+ try:
94
+ summary = self._summarize_chunk(chunk, decision_context)
95
+ if summary:
96
+ summaries.append(summary)
97
+ except Exception as exc: # pylint: disable=broad-except
98
+ logger.warning(
99
+ "LogicExplainer failed for %s:%s - %s",
100
+ chunk.get("file_path"),
101
+ chunk.get("chunk_name") or chunk.get("chunk_type"),
102
+ exc,
103
+ )
104
+
105
+ return summaries
106
+
107
+ def _summarize_chunk(
108
+ self,
109
+ chunk: Dict[str, Any],
110
+ decision_context: str,
111
+ ) -> Optional[Dict[str, Any]]:
112
+ system_prompt = (
113
+ "You are a senior software architect documenting code. "
114
+ "Explain what a unit does, why it exists, and notable trade-offs. "
115
+ "Respond strictly in JSON using the provided schema."
116
+ )
117
+
118
+ unit_label = self._make_unit_id(chunk)
119
+ user_prompt = self._build_user_prompt(chunk, decision_context, unit_label)
120
+
121
+ response = self.generator.generate(
122
+ system_prompt=system_prompt,
123
+ user_prompt=user_prompt,
124
+ temperature=self.temperature,
125
+ max_tokens=self.max_tokens,
126
+ )
127
+
128
+ parsed = self._parse_response(response)
129
+ if not parsed:
130
+ return None
131
+
132
+ logic_summary = LogicSummary(
133
+ unit_id=unit_label,
134
+ file_path=str(chunk.get("file_path", "")),
135
+ chunk_type=str(chunk.get("chunk_type", "unknown")),
136
+ summary_text=parsed.get("summary_text", ""),
137
+ rationale=parsed.get("why", ""),
138
+ trade_offs=self._ensure_list(parsed.get("trade_offs")),
139
+ side_effects=self._ensure_list(parsed.get("side_effects")),
140
+ dependencies=self._ensure_list(parsed.get("dependencies")),
141
+ citations=self._ensure_list(parsed.get("citations")),
142
+ confidence=self._safe_float(parsed.get("confidence", 0.6)),
143
+ )
144
+
145
+ return logic_summary.to_dict()
146
+
147
+ def _build_user_prompt(
148
+ self,
149
+ chunk: Dict[str, Any],
150
+ decision_context: str,
151
+ unit_label: str,
152
+ ) -> str:
153
+ snippet = (chunk.get("content") or "").strip()
154
+ header = f"### CODE UNIT\nLabel: {unit_label}\nType: {chunk.get('chunk_type', 'unknown')}"
155
+
156
+ if chunk.get("chunk_name"):
157
+ header += f"\nSymbol: {chunk['chunk_name']}"
158
+
159
+ header += f"\nFile: {chunk.get('file_path')} ({chunk.get('start_line')}-{chunk.get('end_line')})"
160
+
161
+ prompt = [
162
+ header,
163
+ "\n### SOURCE CODE\n```python\n" + snippet[:1500] + "\n```",
164
+ ]
165
+
166
+ if decision_context:
167
+ prompt.append("\n### KNOWN DECISIONS\n" + decision_context)
168
+
169
+ prompt.append(
170
+ "\n### REQUIRED JSON SCHEMA\n"
171
+ '{"summary_text": "str", "why": "str", '
172
+ '"trade_offs": ["str"], "side_effects": ["str"], '
173
+ '"dependencies": ["str"], "citations": ["str"], '
174
+ '"confidence": "float"}'
175
+ )
176
+
177
+ return "\n".join(prompt)
178
+
179
+ def _format_decision_context(
180
+ self, decision_notes: Optional[Iterable[ContextItem]]
181
+ ) -> str:
182
+ if not decision_notes:
183
+ return ""
184
+
185
+ lines: List[str] = []
186
+ for note in decision_notes:
187
+ try:
188
+ summary = note.get_summary()
189
+ except Exception: # fallback if summary fails
190
+ summary = (note.content or "").split("\n", maxsplit=1)[0][:200]
191
+ lines.append(f"- {note.type.upper()}: {summary} (source: {note.source or 'unknown'})")
192
+ return "\n".join(lines)
193
+
194
+ @staticmethod
195
+ def _parse_response(response: str) -> Optional[Dict[str, Any]]:
196
+ if not response:
197
+ return None
198
+
199
+ cleaned = response.strip()
200
+ if cleaned.startswith("```"):
201
+ cleaned = cleaned.split("\n", 1)[1]
202
+ if cleaned.endswith("```"):
203
+ cleaned = cleaned.rsplit("\n", 1)[0]
204
+
205
+ try:
206
+ parsed = json.loads(cleaned)
207
+ if isinstance(parsed, dict):
208
+ return parsed
209
+ except json.JSONDecodeError:
210
+ logger.debug("LogicExplainer JSON parse failed: %s", cleaned)
211
+ return None
212
+
213
+ @staticmethod
214
+ def _make_unit_id(chunk: Dict[str, Any]) -> str:
215
+ name = chunk.get("chunk_name") or chunk.get("chunk_type") or "unit"
216
+ path = chunk.get("file_path", "unknown")
217
+ start = chunk.get("start_line", 0)
218
+ return f"{path}:{start}:{name}"
219
+
220
+ @staticmethod
221
+ def _ensure_list(value: Any) -> List[str]:
222
+ if isinstance(value, list):
223
+ return [str(v) for v in value if v]
224
+ if isinstance(value, str) and value:
225
+ return [value]
226
+ return []
227
+
228
+ @staticmethod
229
+ def _safe_float(value: Any, default: float = 0.6) -> float:
230
+ try:
231
+ return float(value)
232
+ except (TypeError, ValueError):
233
+ return default
234
+
235
+