| """Code analysis chip — reads actual campus source before anyone opines. |
| |
| Extracts file paths and symbols from the request, reads those files from |
| the repo (when present), follows one level of imports, and grabs recent |
| git history for each file. Everything read is recorded in the session's |
| evidence log — this is what lets the discipline gate honestly say HIGH |
| confidence ("I read the file") vs LOW ("I'm reasoning from the map"). |
| """ |
|
|
| import re |
|
|
| from kintsugi_core import ( |
| BaseSkillChip, |
| EFEWeights, |
| SkillCapability, |
| SkillContext, |
| SkillDomain, |
| SkillRequest, |
| SkillResponse, |
| ) |
|
|
| PATH_RE = re.compile( |
| r"\b((?:server|client|shared|design-system)/[\w./-]+\.(?:ts|tsx|js|jsx|sql|json))\b" |
| ) |
| SYMBOL_RE = re.compile(r"\b([a-z][a-zA-Z0-9]+(?:Handler|Store|Service|Middleware|Router))\b") |
| IMPORT_RE = re.compile(r"""from\s+['"]([^'"]+)['"]""") |
|
|
| MAX_FILES = 6 |
| MAX_EXCERPT = 4000 |
|
|
|
|
| class CodeAnalysisChip(BaseSkillChip): |
| name = "code_analysis" |
| description = "Read and trace campus source files named in the request" |
| version = "2.0.0" |
| domain = SkillDomain.RESEARCH |
| efe_weights = EFEWeights( |
| mission_alignment=0.20, stakeholder_benefit=0.20, |
| resource_efficiency=0.25, transparency=0.25, equity=0.10, |
| ) |
| capabilities = [SkillCapability.READ_DATA] |
|
|
| def __init__(self, repo_files=None, git_tools=None): |
| super().__init__() |
| self.repo_files = repo_files |
| self.git_tools = git_tools |
|
|
| async def handle(self, request: SkillRequest, |
| context: SkillContext) -> SkillResponse: |
| question = context.metadata.get("question", request.raw_input) |
| session = context.metadata.get("session") |
|
|
| paths = list(dict.fromkeys(PATH_RE.findall(question)))[:MAX_FILES] |
| symbols = list(dict.fromkeys(SYMBOL_RE.findall(question)))[:5] |
|
|
| analysis = { |
| "repo_available": self.repo_files is not None, |
| "requested_paths": paths, |
| "symbols": symbols, |
| "files": [], |
| "symbol_sites": [], |
| "notes": [], |
| } |
|
|
| if self.repo_files is None: |
| analysis["notes"].append( |
| "Campus repo is not on this machine — analysis is limited " |
| "to the architecture map. Confidence will be capped." |
| ) |
| return self._done(analysis, session) |
|
|
| |
| for sym in symbols: |
| hits = self.repo_files.search(rf"\b{re.escape(sym)}\b")[:3] |
| analysis["symbol_sites"].extend(hits) |
| for h in hits: |
| if h["path"] not in paths and len(paths) < MAX_FILES: |
| paths.append(h["path"]) |
|
|
| for path in paths[:MAX_FILES]: |
| read = self.repo_files.read(path) |
| if not read.ok: |
| analysis["notes"].append(f"could not read {path}: {read.error}") |
| continue |
| entry = { |
| "path": path, |
| "excerpt": read.content[:MAX_EXCERPT], |
| "truncated": read.truncated or len(read.content) > MAX_EXCERPT, |
| "git_status": read.git_status, |
| "imports": IMPORT_RE.findall(read.content)[:20], |
| "recent_history": ( |
| self.git_tools.recent_authors(path) if self.git_tools else "" |
| ), |
| } |
| analysis["files"].append(entry) |
| if session: |
| session.record_file_read(path, self.name) |
|
|
| |
| if analysis["files"]: |
| first = analysis["files"][0] |
| local = [imp for imp in first["imports"] if imp.startswith(".")][:3] |
| for imp in local: |
| resolved = self._resolve_import(first["path"], imp) |
| if resolved and len(analysis["files"]) < MAX_FILES: |
| read = self.repo_files.read(resolved) |
| if read.ok: |
| analysis["files"].append({ |
| "path": resolved, |
| "excerpt": read.content[:MAX_EXCERPT // 2], |
| "truncated": True, |
| "git_status": read.git_status, |
| "imports": [], |
| "recent_history": "", |
| "traced_from": first["path"], |
| }) |
| if session: |
| session.record_file_read(resolved, self.name) |
|
|
| return self._done(analysis, session) |
|
|
| def _resolve_import(self, from_path: str, import_spec: str) -> str: |
| base = "/".join(from_path.split("/")[:-1]) |
| candidate = f"{base}/{import_spec.lstrip('./')}" |
| for suffix in (".ts", ".tsx", "/index.ts", ".js"): |
| probe = candidate + suffix |
| if self.repo_files.read(probe).ok: |
| return probe |
| return "" |
|
|
| def _done(self, analysis: dict, session) -> SkillResponse: |
| n_files = len(analysis["files"]) |
| summary = ( |
| f"read {n_files} file(s), " |
| f"{len(analysis['symbol_sites'])} symbol site(s); " |
| f"repo_available={analysis['repo_available']}" |
| ) |
| return SkillResponse(content=summary, success=True, data=analysis) |
|
|