feat(ui): Steps for servers — route roots, server effects, request/decorator triggers, guards for Python/Java/Kotlin/C#/Go/C

- api/route-roots.ts: the symbol a route runs (references-edge handler, exported page component, or the route itself for an inline handler), shared by steps and screens; the bare Steps tab lists an API's endpoints by router file
- api/effects.ts: database / response / queue / email / payments / cache / auth / process / network / storage / device / telemetry, matched on the call as written per language family, with model + read/write and the literal status on a response site
- graph/branch-guards.ts: callSitesForFile (the whole member chain), memberTypesInTree, decoratorsForFile, request/decorator triggers with the middleware/guard chain; guard + argument rules for Python, Java, Kotlin, C#, Go and C
- steps.ts: classify on the chain before trusting a name match, retarget this.x.y() by declared type, skip test doubles after the effect pre-check, project kind on the wire
- viewer: kindWord/kindWords per project kind, endpoint chooser, response boxes labelled by status codes
- python.ts: FastAPI detected from a monorepo sub-directory; is-test-file: samples/examples package paths are not tests
- tests: ui-steps-api-servers, ui-effects, branch-guards-languages; spec §3.13 Servers paragraph, CHANGELOG, plan doc

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REFyW9hmNrxhwN5wxRoAkC
This commit is contained in:
Colby McHenry
2026-08-28 13:45:38 -05:00
co-authored by Claude Fable 5
parent 5e06204deb
commit 950686def4
22 changed files with 3869 additions and 190 deletions
+32
View File
@@ -14,11 +14,16 @@ import type CodeGraph from '../../index';
import type { Language } from '../../types';
import {
callArgumentsForFile,
callSitesForFile,
decoratorsForFile,
guardLabel,
guardsForFile,
memberTypesForFile,
siteKey,
supportsBranchGuards,
triggersForFile,
type CallSiteText,
type DefinitionDecorators,
type SiteTrigger,
} from '../../graph/branch-guards';
import { resolveProjectFile } from '../security';
@@ -96,6 +101,12 @@ export interface SiteReader {
args(caller: { filePath: string; language: Language }, site: { line?: number; column?: number }): Promise<string | null>;
/** What fires the site — the JSX prop, `on*` option or runs-later call it is written under; null when nothing binds it. */
trigger(caller: { filePath: string; language: Language }, site: { line?: number; column?: number }): Promise<SiteTrigger | null>;
/** The call as written (the whole member chain) and what it passes; null when unreadable. `callee` names the call when a position is shared. */
callSite(caller: { filePath: string; language: Language }, site: { line?: number; column?: number; callee?: string }): Promise<CallSiteText | null>;
/** The decorators / annotations / attributes on a definition, and on its class; null when unreadable. */
decorators(definition: { filePath: string; language: Language; startLine: number }): Promise<DefinitionDecorators | null>;
/** The declared types of the members of the class a definition belongs to, by member name; empty when unreadable. */
memberTypes(definition: { filePath: string; language: Language; startLine: number }): Promise<Map<string, string>>;
}
/**
@@ -151,6 +162,27 @@ export function createSiteReader(cg: CodeGraph, projectRoot: string, maxSites =
const key = { line: site.line, column: typeof site.column === 'number' ? site.column : null };
return (await triggersForFile(file.abs, file.language, [key])).get(siteKey(key)) ?? null;
},
async callSite(caller, site) {
if (!site.line || sites >= maxSites || !supportsBranchGuards(caller.language)) return null;
const file = resolve(caller);
if (!file) return null;
sites++;
const key = { line: site.line, column: typeof site.column === 'number' ? site.column : null, ...(site.callee ? { callee: site.callee } : {}) };
return (await callSitesForFile(file.abs, file.language, [key])).get(siteKey(key)) ?? null;
},
async decorators(definition) {
// Not counted: one lookup per step, on a tree the walk has parsed anyway.
if (!definition.startLine || !supportsBranchGuards(definition.language)) return null;
const file = resolve(definition);
if (!file) return null;
return (await decoratorsForFile(file.abs, file.language, [definition.startLine])).get(definition.startLine) ?? null;
},
async memberTypes(definition) {
if (!definition.startLine || !supportsBranchGuards(definition.language)) return new Map();
const file = resolve(definition);
if (!file) return new Map();
return memberTypesForFile(file.abs, file.language, definition.startLine);
},
};
}