Skip to content

Commit 10a2e41

Browse files
committed
Reduce the last runtime hotspots without changing Flow behavior
The remaining complexity was concentrated in execution, session, and render runtime files. This change preserves the existing public facades while splitting those responsibilities into bounded sibling modules so the lifecycle seams are easier to scan, review, and modify. The refactor stays conservative: no dependency changes, no workflow semantics changes, and no intentional drift in messages, recovery metadata, or release/install behavior. The post-split deslop pass stayed scoped to the changed files and removed duplicate render outcome formatting. Constraint: Must preserve existing Flow behavior and public runtime entrypoints Constraint: No new dependencies or architecture rewrite in this cleanup pass Rejected: Keep helper extraction in the same hotspot files | lower long-term maintainability payoff Rejected: Broader reducer/state-machine redesign | parity risk too high for this scope Confidence: high Scope-risk: moderate Reversibility: clean Directive: Review execution-* and session-* sibling files together when changing lifecycle invariants Tested: bun test tests/runtime.test.ts Tested: bun run typecheck Tested: bun run check Not-tested: Manual OpenCode runtime interaction beyond automated repo checks
1 parent 5fd47c2 commit 10a2e41

14 files changed

Lines changed: 1338 additions & 1294 deletions
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import type { Feature, Session } from "./schema";
2+
import {
3+
bulletList,
4+
joinSections,
5+
maybeQuotedSection,
6+
maybeSection,
7+
maybeTitledList,
8+
renderOutcomeLines,
9+
renderReviewBlock,
10+
toInlineText,
11+
toQuotedBlock,
12+
} from "./render-sections-shared";
13+
14+
function renderFeatureHistory(session: Session, feature: Feature): string {
15+
const entries = session.execution.history.filter((entry) => entry.featureId === feature.id);
16+
if (entries.length === 0) {
17+
return "## Execution History\n\n- none";
18+
}
19+
20+
const renderedEntries = entries.map((entry) => {
21+
const sections = [
22+
maybeTitledList(
23+
"Changed Artifacts",
24+
entry.artifactsChanged.map((artifact) => (artifact.kind ? `${artifact.path} (${artifact.kind})` : artifact.path)),
25+
"####",
26+
),
27+
maybeTitledList(
28+
"Validation",
29+
entry.validationRun.map((item) => `${item.status} | ${item.command} | ${item.summary}`),
30+
"####",
31+
),
32+
maybeTitledList("Decisions", entry.decisions.map((item) => item.summary), "####"),
33+
entry.reviewerDecision
34+
? maybeTitledList(
35+
"Reviewer Decision",
36+
[
37+
`scope: ${entry.reviewerDecision.scope}`,
38+
...(entry.reviewerDecision.featureId ? [`feature id: ${entry.reviewerDecision.featureId}`] : []),
39+
`status: ${entry.reviewerDecision.status}`,
40+
`summary: ${entry.reviewerDecision.summary}`,
41+
],
42+
"####",
43+
)
44+
: "",
45+
entry.outcome ? maybeTitledList("Outcome", renderOutcomeLines(entry.outcome), "####") : "",
46+
maybeTitledList("Notes", entry.featureResult?.notes?.map((item) => item.note) ?? [], "####"),
47+
maybeTitledList(
48+
"Follow Ups",
49+
entry.featureResult?.followUps?.map((item) => (item.severity ? `${item.summary} (${item.severity})` : item.summary)) ?? [],
50+
"####",
51+
),
52+
renderReviewBlock("Feature Review", entry.featureReview),
53+
renderReviewBlock("Final Review", entry.finalReview),
54+
].filter(Boolean);
55+
56+
return joinSections([
57+
`### ${entry.recordedAt}\n\n- status: ${entry.status}\n- outcome: ${entry.outcomeKind ?? "none"}\n- summary: ${toInlineText(entry.summary)}\n- next step: ${entry.nextStep ? toInlineText(entry.nextStep) : "none"}`,
58+
...sections,
59+
]).trimEnd();
60+
});
61+
62+
return `## Execution History\n\n${renderedEntries.join("\n\n")}`;
63+
}
64+
65+
function renderFeatureSummarySection(session: Session, feature: Feature): string {
66+
const isActive = session.execution.activeFeatureId === feature.id;
67+
68+
return `## Summary
69+
70+
- title: ${toInlineText(feature.title)}
71+
- status: ${feature.status}
72+
- active: ${isActive ? "yes" : "no"}
73+
- goal: ${toInlineText(session.goal)}`;
74+
}
75+
76+
function renderFeatureDescriptionSection(feature: Feature): string {
77+
return `## Description\n\n${toQuotedBlock(feature.summary)}`;
78+
}
79+
80+
function renderFeatureTargetsSection(feature: Feature): string {
81+
return `## File Targets\n\n${bulletList(feature.fileTargets)}`;
82+
}
83+
84+
function renderFeatureVerificationSection(feature: Feature): string {
85+
return `## Verification\n\n${bulletList(feature.verification)}`;
86+
}
87+
88+
export function renderFeatureDoc(session: Session, feature: Feature): string {
89+
return joinSections([
90+
`# Feature ${feature.id}`,
91+
renderFeatureSummarySection(session, feature),
92+
renderFeatureDescriptionSection(feature),
93+
maybeQuotedSection("Latest Runtime Summary", session.execution.lastFeatureId === feature.id ? session.execution.lastSummary : null),
94+
renderFeatureTargetsSection(feature),
95+
renderFeatureVerificationSection(feature),
96+
maybeSection("Depends On", feature.dependsOn ?? []),
97+
maybeSection("Blocked By", feature.blockedBy ?? []),
98+
renderFeatureHistory(session, feature),
99+
]);
100+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { deriveNextCommand } from "./summary";
2+
import type { Feature, Session } from "./schema";
3+
import { bulletList, joinSections, maybeSection, maybeTitledList, renderOutcomeLines, toInlineText } from "./render-sections-shared";
4+
5+
function maybeApproachSection(session: Session): string {
6+
const approach = session.planning.implementationApproach;
7+
if (!approach) {
8+
return "";
9+
}
10+
11+
return joinSections([
12+
"## Implementation Approach\n\n" + `- chosen direction: ${toInlineText(approach.chosenDirection)}`,
13+
maybeTitledList("Key Constraints", approach.keyConstraints, "###"),
14+
maybeTitledList("Validation Signals", approach.validationSignals, "###"),
15+
maybeTitledList("Sources", approach.sources, "###"),
16+
]).trimEnd();
17+
}
18+
19+
function formatFeatureLine(feature: Feature): string {
20+
return `- ${feature.id} | ${feature.status} | ${toInlineText(feature.title)}`;
21+
}
22+
23+
function renderFeatureResultDetails(
24+
featureResult:
25+
| {
26+
featureId: string;
27+
verificationStatus?: string;
28+
notes?: Array<{ note: string }>;
29+
followUps?: Array<{ summary: string; severity?: string }>;
30+
}
31+
| null
32+
| undefined,
33+
): string {
34+
if (!featureResult) {
35+
return "";
36+
}
37+
38+
const sections = [
39+
maybeTitledList("Notes", featureResult.notes?.map((item) => item.note) ?? [], "###"),
40+
maybeTitledList(
41+
"Follow Ups",
42+
featureResult.followUps?.map((item) => (item.severity ? `${item.summary} (${item.severity})` : item.summary)) ?? [],
43+
"###",
44+
),
45+
].filter(Boolean);
46+
47+
return joinSections([
48+
`## Feature Result\n\n- feature id: ${featureResult.featureId}\n- verification: ${featureResult.verificationStatus ?? "not_recorded"}`,
49+
...sections,
50+
]).trimEnd();
51+
}
52+
53+
function renderIndexSummarySection(session: Session): string {
54+
const reviewerDecision = session.execution.lastReviewerDecision;
55+
56+
return `## Summary
57+
58+
- session id: ${session.id}
59+
- goal: ${toInlineText(session.goal)}
60+
- status: ${session.status}
61+
- approval: ${session.approval}
62+
- next command: ${deriveNextCommand(session)}
63+
- next step: ${session.execution.lastNextStep ? toInlineText(session.execution.lastNextStep) : "none"}
64+
- reviewer decision: ${reviewerDecision ? `${reviewerDecision.scope} | ${reviewerDecision.status} | ${toInlineText(reviewerDecision.summary)}` : "none"}
65+
- created: ${session.timestamps.createdAt}
66+
- updated: ${session.timestamps.updatedAt}`;
67+
}
68+
69+
function renderPlanSection(session: Session, features: Feature[]): string {
70+
const plan = session.plan;
71+
const activeFeature = features.find((feature) => feature.id === session.execution.activeFeatureId) ?? null;
72+
const completedCount = features.filter((feature) => feature.status === "completed").length;
73+
74+
return joinSections([
75+
`## Plan
76+
77+
- summary: ${toInlineText(plan?.summary ?? "No plan yet.")}
78+
- overview: ${toInlineText(plan?.overview ?? "No plan yet.")}
79+
- progress: ${completedCount}/${features.length} completed
80+
- active feature: ${activeFeature ? activeFeature.id : "none"}`,
81+
maybeSection("Requirements", plan?.requirements ?? []),
82+
maybeSection("Architecture Decisions", plan?.architectureDecisions ?? []),
83+
maybeSection("Repo Profile", session.planning.repoProfile),
84+
maybeSection("Research", session.planning.research),
85+
maybeApproachSection(session),
86+
]).trimEnd();
87+
}
88+
89+
function renderFeaturesSection(features: Feature[]): string {
90+
return `## Features\n\n${features.length === 0 ? "- none" : features.map(formatFeatureLine).join("\n")}`;
91+
}
92+
93+
function renderOutcomeSection(session: Session): string {
94+
if (!session.execution.lastOutcome) {
95+
return "";
96+
}
97+
98+
return `## Outcome\n\n${bulletList(renderOutcomeLines(session.execution.lastOutcome))}`;
99+
}
100+
101+
function renderChangedArtifactsSection(session: Session): string {
102+
if (session.artifacts.length === 0) {
103+
return "";
104+
}
105+
106+
return `## Changed Artifacts\n\n${bulletList(session.artifacts.map((artifact) => (artifact.kind ? `${artifact.path} (${artifact.kind})` : artifact.path)))}`;
107+
}
108+
109+
function renderLastValidationRunSection(session: Session): string {
110+
if (session.execution.lastValidationRun.length === 0) {
111+
return "";
112+
}
113+
114+
return `## Last Validation Run\n\n${bulletList(session.execution.lastValidationRun.map((item) => `${item.status} | ${item.command} | ${item.summary}`))}`;
115+
}
116+
117+
function renderExecutionHistoryOverviewSection(session: Session): string {
118+
if (session.execution.history.length === 0) {
119+
return "";
120+
}
121+
122+
return `## Execution History\n\n${bulletList(session.execution.history.map((item) => `${item.recordedAt} | ${item.featureId} | ${item.status} | ${item.summary}`))}`;
123+
}
124+
125+
export function renderIndexDoc(session: Session): string {
126+
const features = session.plan?.features ?? [];
127+
128+
return joinSections([
129+
"# Flow Session",
130+
renderIndexSummarySection(session),
131+
renderPlanSection(session, features),
132+
renderFeaturesSection(features),
133+
renderOutcomeSection(session),
134+
renderFeatureResultDetails(session.execution.lastFeatureResult),
135+
maybeSection("Notes", session.notes),
136+
renderChangedArtifactsSection(session),
137+
renderLastValidationRunSection(session),
138+
renderExecutionHistoryOverviewSection(session),
139+
]);
140+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
export function toInlineText(value: string): string {
2+
return value.replace(/\r?\n+/g, " / ").trim();
3+
}
4+
5+
export function bulletList(items: string[]): string {
6+
if (items.length === 0) {
7+
return "- none";
8+
}
9+
10+
return items.map((item) => `- ${toInlineText(item)}`).join("\n");
11+
}
12+
13+
export function joinSections(sections: string[]): string {
14+
return `${sections.filter(Boolean).join("\n\n")}\n`;
15+
}
16+
17+
export function maybeSection(title: string, items: string[]): string {
18+
if (items.length === 0) {
19+
return "";
20+
}
21+
22+
return `## ${title}\n\n${bulletList(items)}`;
23+
}
24+
25+
export function maybeTitledList(title: string, items: string[], level = "##"): string {
26+
if (items.length === 0) {
27+
return "";
28+
}
29+
30+
return `${level} ${title}\n\n${bulletList(items)}`;
31+
}
32+
33+
export function toQuotedBlock(value: string): string {
34+
const normalized = value.trim();
35+
if (!normalized) {
36+
return "> none";
37+
}
38+
39+
return normalized
40+
.split(/\r?\n/)
41+
.map((line) => `> ${line}`)
42+
.join("\n");
43+
}
44+
45+
export function maybeQuotedSection(title: string, value: string | null | undefined): string {
46+
if (!value) {
47+
return "";
48+
}
49+
50+
return `## ${title}\n\n${toQuotedBlock(value)}`;
51+
}
52+
53+
export function renderReviewBlock(
54+
title: string,
55+
review:
56+
| {
57+
status: string;
58+
summary: string;
59+
blockingFindings: Array<{ summary: string }>;
60+
}
61+
| undefined,
62+
): string {
63+
if (!review) {
64+
return "";
65+
}
66+
67+
const lines = [
68+
`- status: ${review.status}`,
69+
`- summary: ${toInlineText(review.summary)}`,
70+
...(review.blockingFindings.length > 0 ? [bulletList(review.blockingFindings.map((item) => item.summary))] : []),
71+
];
72+
73+
return `#### ${title}\n\n${lines.join("\n")}`;
74+
}
75+
76+
export function renderOutcomeLines(
77+
outcome:
78+
| {
79+
kind: string;
80+
category?: string;
81+
summary?: string;
82+
resolutionHint?: string;
83+
retryable?: boolean;
84+
autoResolvable?: boolean;
85+
needsHuman?: boolean;
86+
}
87+
| null
88+
| undefined,
89+
): string[] {
90+
if (!outcome) {
91+
return [];
92+
}
93+
94+
return [
95+
`kind: ${outcome.kind}`,
96+
...(outcome.category ? [`category: ${toInlineText(outcome.category)}`] : []),
97+
...(outcome.summary ? [`summary: ${toInlineText(outcome.summary)}`] : []),
98+
...(outcome.resolutionHint ? [`resolution hint: ${toInlineText(outcome.resolutionHint)}`] : []),
99+
...(outcome.retryable !== undefined ? [`retryable: ${outcome.retryable ? "yes" : "no"}`] : []),
100+
...(outcome.autoResolvable !== undefined ? [`auto resolvable: ${outcome.autoResolvable ? "yes" : "no"}`] : []),
101+
...(outcome.needsHuman !== undefined ? [`needs human: ${outcome.needsHuman ? "yes" : "no"}`] : []),
102+
];
103+
}

0 commit comments

Comments
 (0)