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.
- claude_context/__init__.py +33 -0
- claude_context/agentic_integration.py +309 -0
- claude_context/ast_chunker.py +646 -0
- claude_context/config.py +239 -0
- claude_context/context_manager.py +627 -0
- claude_context/embeddings.py +307 -0
- claude_context/embeddings_interface.py +226 -0
- claude_context/enhanced_ast_chunker.py +1129 -0
- claude_context/explorer.py +951 -0
- claude_context/explorer_with_context.py +1008 -0
- claude_context/indexer.py +893 -0
- claude_context/markdown_chunker.py +421 -0
- claude_context/mode_handler.py +1774 -0
- claude_context/query_metrics.py +164 -0
- claude_context/question_generator.py +800 -0
- claude_context/readme_extractor.py +485 -0
- claude_context/repository_adapter.py +399 -0
- claude_context/search.py +493 -0
- claude_context/skills/__init__.py +11 -0
- claude_context/skills/_cli_common.py +74 -0
- claude_context/skills/_index_manager.py +98 -0
- claude_context/skills/api_surface.py +219 -0
- claude_context/skills/evidence_retrieval.py +151 -0
- claude_context/skills/grounded_review.py +212 -0
- claude_context/synthesis/__init__.py +8 -0
- claude_context/synthesis/editor_agent.py +391 -0
- claude_context/synthesis/llm_synthesizer.py +153 -0
- claude_context/synthesis/logic_explainer.py +235 -0
- claude_context/synthesis/multi_review_pipeline.py +717 -0
- claude_context/synthesis/prompt_builder.py +439 -0
- claude_context/synthesis/providers.py +115 -0
- claude_context/synthesis/validators.py +458 -0
- code_finder-0.1.0.dist-info/METADATA +823 -0
- code_finder-0.1.0.dist-info/RECORD +37 -0
- code_finder-0.1.0.dist-info/WHEEL +5 -0
- code_finder-0.1.0.dist-info/entry_points.txt +4 -0
- 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
|
+
|