storycode / analyzer /__init__.py
Codex (via opencode)
Codex: prompt tuning, _loads fix, GitHub ingestion, lint cleanup
7603aa2
Raw
History Blame Contribute Delete
2.46 kB
"""The static-analysis engine: ingested files -> a factual ProjectModel.
This is StoryCode's source of truth. It dispatches each file to the right parser
(Python `ast`, JS/TS tree-sitter, or the lightweight generic pass), reads the
dependency manifests, then builds the project graph (edges, fan-in/out, roles,
entry points, safe-to-edit). The language model is never involved here.
"""
from __future__ import annotations
import posixpath
import config
from ingest import Ingested
from schema import FileInfo, ProjectModel
from analyzer import deps, generic, graph, js_treesitter, python_ast
def _loc(text: str) -> int:
return sum(1 for line in text.splitlines() if line.strip())
def _parse_one(path: str, lang: str, text: str):
if lang == "python":
return python_ast.parse(text)
if lang in ("javascript", "typescript"):
return js_treesitter.parse(text, lang)
if lang in ("html", "css"):
return generic.parse(text, lang)
return [], []
def analyze_project(ingested: Ingested) -> ProjectModel:
"""Turn ingested source files into a fully-populated ProjectModel."""
files: list[FileInfo] = []
dependencies = []
langs: set[str] = set()
for sf in ingested.files:
base = posixpath.basename(sf.path).lower()
if base in config.DEP_MANIFESTS:
dependencies.extend(deps.parse_manifest(base, sf.text))
# package.json / pyproject also count as config files in the graph.
symbols, imports = _parse_one(sf.path, sf.lang, sf.text)
if sf.lang in config.LANG_BY_EXT.values():
langs.add(sf.lang)
files.append(FileInfo(
path=sf.path, lang=sf.lang, loc=_loc(sf.text),
symbols=symbols, imports=imports,
role=graph.classify_role(sf.path, sf.lang),
))
graph.resolve_edges(files)
graph.compute_metrics(files)
# De-dup dependencies by (name, manifest), keep order.
seen: set[tuple[str, str]] = set()
uniq_deps = []
for d in dependencies:
key = (d.name.lower(), d.manifest)
if key not in seen:
seen.add(key)
uniq_deps.append(d)
return ProjectModel(
name=ingested.name,
files=files,
deps=uniq_deps,
entry_points=graph.detect_entry_points(files),
languages=sorted(langs),
skipped=ingested.skipped,
secrets_found=ingested.secrets_found,
note=ingested.note,
)