| """Pydantic data model for StoryCode. |
| |
| Two kinds of data live here: |
| |
| 1. The **ProjectModel** — the deterministic, factual structure of the uploaded |
| project, produced entirely by static analysis (`analyzer/`). This is the |
| single source of truth. The language model never edits it. |
| |
| 2. The model's **narration** (`FileSummary`, `ProjectStory`) — plain-language |
| text the model writes *about* the ProjectModel. The JSON schemas at the |
| bottom are handed to vLLM as `guided_json` so the model is forced to emit |
| conforming JSON (no fence-stripping / repair downstream). |
| """ |
| from __future__ import annotations |
|
|
| from pydantic import BaseModel, Field |
|
|
|
|
| |
| class Symbol(BaseModel): |
| """A function or class defined in a file.""" |
| kind: str = Field(description="'function' or 'class'") |
| name: str |
| lineno: int = 0 |
| doc: str | None = Field(default=None, description="First line of its docstring/comment") |
|
|
|
|
| class FileInfo(BaseModel): |
| path: str = Field(description="Project-relative path, e.g. 'analyzer/graph.py'") |
| lang: str = Field(description="language key from config.LANG_BY_EXT, or 'other'") |
| role: str = Field(default="other", description="role key from config.ROLES") |
| loc: int = Field(default=0, description="non-blank lines of code") |
| symbols: list[Symbol] = Field(default_factory=list) |
| imports: list[str] = Field(default_factory=list, description="raw import targets") |
| |
| depends_on: list[str] = Field(default_factory=list) |
| |
| fan_in: int = Field(default=0, description="how many project files depend on this one") |
| fan_out: int = Field(default=0, description="how many project files this one depends on") |
| is_entry: bool = False |
| |
| safety: str = Field(default="careful") |
| safety_reason: str = "" |
|
|
|
|
| class Dependency(BaseModel): |
| name: str |
| manifest: str = Field(description="which file declared it, e.g. 'requirements.txt'") |
| plain: str = Field(default="", description="one-line plain-English description") |
| risky: bool = Field(default=False, description="flagged old/insecure (best-effort)") |
|
|
|
|
| class ProjectModel(BaseModel): |
| """The whole analysed project — the factual backbone of every story.""" |
| name: str = "your project" |
| files: list[FileInfo] = Field(default_factory=list) |
| deps: list[Dependency] = Field(default_factory=list) |
| entry_points: list[str] = Field(default_factory=list) |
| languages: list[str] = Field(default_factory=list, description="distinct langs present") |
| skipped: list[str] = Field(default_factory=list, description="paths listed but not parsed") |
| secrets_found: list[str] = Field(default_factory=list, description="redacted secret hits") |
| note: str = Field(default="", description="user-facing ingest caveat, if any") |
|
|
| def by_path(self, path: str) -> FileInfo | None: |
| return next((f for f in self.files if f.path == path), None) |
|
|
|
|
| |
| class FileSummary(BaseModel): |
| """The MAP step output: a 2-3 sentence plain summary of one file.""" |
| path: str |
| one_liner: str = Field(description="<=8 words, the file's job, plain English") |
| summary: str = Field(description="2-3 sentences, no jargon") |
|
|
|
|
| class StorySection(BaseModel): |
| heading: str |
| body: str |
|
|
|
|
| class ProjectStory(BaseModel): |
| """The REDUCE step output: the narrated story of the whole project.""" |
| title: str |
| overview: str = Field(description="2-4 sentences: what this project is, plainly") |
| steps: list[StorySection] = Field( |
| default_factory=list, |
| description="the step-by-step flow: what happens when the app runs / a user acts") |
| plain_overview: str = Field( |
| default="", |
| description="the SAME content in flat Simple-Walkthrough English (always shown " |
| "beside a creative style so the metaphor is never the only version)") |
|
|
|
|
| |
| def file_summary_schema() -> dict: |
| return { |
| "type": "object", |
| "additionalProperties": False, |
| "properties": { |
| "one_liner": {"type": "string"}, |
| "summary": {"type": "string"}, |
| }, |
| "required": ["one_liner", "summary"], |
| } |
|
|
|
|
| def project_story_schema() -> dict: |
| return { |
| "type": "object", |
| "additionalProperties": False, |
| "properties": { |
| "title": {"type": "string"}, |
| "overview": {"type": "string"}, |
| "steps": { |
| "type": "array", |
| "items": { |
| "type": "object", |
| "additionalProperties": False, |
| "properties": { |
| "heading": {"type": "string"}, |
| "body": {"type": "string"}, |
| }, |
| "required": ["heading", "body"], |
| }, |
| }, |
| "plain_overview": {"type": "string"}, |
| }, |
| "required": ["title", "overview", "steps", "plain_overview"], |
| } |
|
|