storycode / schema.py
Codex (via opencode)
Codex: prompt tuning, _loads fix, GitHub ingestion, lint cleanup
7603aa2
Raw
History Blame Contribute Delete
5.31 kB
"""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
# --- Factual structure (from static analysis) -------------------------------
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")
# Resolved intra-project edges (filled by graph.py): paths this file depends on.
depends_on: list[str] = Field(default_factory=list)
# Graph metrics (filled by graph.py).
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 verdict (config.SAFE / CAREFUL / DANGER) + plain reason.
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)
# --- Model narration (text the model writes about the structure) ------------
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)")
# --- guided_json schemas (vLLM XGrammar-friendly subset) ---------------------
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"],
}