omegon 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +3 -0
- package/AGENTS.md +16 -0
- package/LICENSE +15 -0
- package/README.md +289 -0
- package/bin/pi.mjs +30 -0
- package/extensions/00-secrets/index.ts +1126 -0
- package/extensions/01-auth/auth.ts +401 -0
- package/extensions/01-auth/index.ts +289 -0
- package/extensions/auto-compact.ts +42 -0
- package/extensions/bootstrap/deps.ts +291 -0
- package/extensions/bootstrap/index.ts +811 -0
- package/extensions/chronos/chronos.sh +487 -0
- package/extensions/chronos/index.ts +148 -0
- package/extensions/cleave/assessment.ts +754 -0
- package/extensions/cleave/bridge.ts +31 -0
- package/extensions/cleave/conflicts.ts +250 -0
- package/extensions/cleave/dispatcher.ts +808 -0
- package/extensions/cleave/guardrails.ts +426 -0
- package/extensions/cleave/index.ts +3121 -0
- package/extensions/cleave/lifecycle-emitter.ts +20 -0
- package/extensions/cleave/openspec.ts +811 -0
- package/extensions/cleave/planner.ts +260 -0
- package/extensions/cleave/review.ts +579 -0
- package/extensions/cleave/skills.ts +355 -0
- package/extensions/cleave/types.ts +261 -0
- package/extensions/cleave/workspace.ts +861 -0
- package/extensions/cleave/worktree.ts +243 -0
- package/extensions/core-renderers.ts +253 -0
- package/extensions/dashboard/context-gauge.ts +58 -0
- package/extensions/dashboard/file-watch.ts +14 -0
- package/extensions/dashboard/footer.ts +1145 -0
- package/extensions/dashboard/git.ts +185 -0
- package/extensions/dashboard/index.ts +478 -0
- package/extensions/dashboard/memory-audit.ts +34 -0
- package/extensions/dashboard/overlay-data.ts +705 -0
- package/extensions/dashboard/overlay.ts +365 -0
- package/extensions/dashboard/render-utils.ts +54 -0
- package/extensions/dashboard/types.ts +191 -0
- package/extensions/dashboard/uri-helper.ts +45 -0
- package/extensions/debug.ts +69 -0
- package/extensions/defaults.ts +282 -0
- package/extensions/design-tree/dashboard-state.ts +161 -0
- package/extensions/design-tree/design-card.ts +362 -0
- package/extensions/design-tree/index.ts +2130 -0
- package/extensions/design-tree/lifecycle-emitter.ts +41 -0
- package/extensions/design-tree/tree.ts +1607 -0
- package/extensions/design-tree/types.ts +163 -0
- package/extensions/distill.ts +127 -0
- package/extensions/effort/index.ts +395 -0
- package/extensions/effort/tiers.ts +146 -0
- package/extensions/effort/types.ts +105 -0
- package/extensions/lib/git-state.ts +227 -0
- package/extensions/lib/local-models.ts +157 -0
- package/extensions/lib/model-preferences.ts +51 -0
- package/extensions/lib/model-routing.ts +720 -0
- package/extensions/lib/operator-fallback.ts +205 -0
- package/extensions/lib/operator-profile.ts +360 -0
- package/extensions/lib/slash-command-bridge.ts +253 -0
- package/extensions/lib/typebox-helpers.ts +16 -0
- package/extensions/local-inference/index.ts +727 -0
- package/extensions/mcp-bridge/README.md +220 -0
- package/extensions/mcp-bridge/index.ts +951 -0
- package/extensions/mcp-bridge/lib.ts +365 -0
- package/extensions/mcp-bridge/mcp.json +3 -0
- package/extensions/mcp-bridge/package.json +11 -0
- package/extensions/model-budget.ts +752 -0
- package/extensions/offline-driver.ts +403 -0
- package/extensions/openspec/archive-gate.ts +164 -0
- package/extensions/openspec/branch-cleanup.ts +64 -0
- package/extensions/openspec/dashboard-state.ts +50 -0
- package/extensions/openspec/index.ts +1917 -0
- package/extensions/openspec/lifecycle-emitter.ts +65 -0
- package/extensions/openspec/lifecycle-files.ts +70 -0
- package/extensions/openspec/lifecycle.ts +50 -0
- package/extensions/openspec/reconcile.ts +187 -0
- package/extensions/openspec/spec.ts +1385 -0
- package/extensions/openspec/types.ts +98 -0
- package/extensions/project-memory/DESIGN-global-mind.md +198 -0
- package/extensions/project-memory/README.md +202 -0
- package/extensions/project-memory/api-types.ts +382 -0
- package/extensions/project-memory/compaction-policy.ts +29 -0
- package/extensions/project-memory/core.ts +164 -0
- package/extensions/project-memory/embeddings.ts +230 -0
- package/extensions/project-memory/extraction-v2.ts +861 -0
- package/extensions/project-memory/factstore.ts +2177 -0
- package/extensions/project-memory/index.ts +3459 -0
- package/extensions/project-memory/injection-metrics.ts +91 -0
- package/extensions/project-memory/jsonl-io.ts +12 -0
- package/extensions/project-memory/lifecycle.ts +331 -0
- package/extensions/project-memory/migration.ts +293 -0
- package/extensions/project-memory/package.json +9 -0
- package/extensions/project-memory/sci-renderers.ts +7 -0
- package/extensions/project-memory/template.ts +103 -0
- package/extensions/project-memory/triggers.ts +52 -0
- package/extensions/project-memory/types.ts +102 -0
- package/extensions/render/composition/fonts/Inter-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Inter-Regular.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Regular.ttf +0 -0
- package/extensions/render/composition/package-lock.json +534 -0
- package/extensions/render/composition/package.json +22 -0
- package/extensions/render/composition/render.mjs +246 -0
- package/extensions/render/composition/test-comp.tsx +87 -0
- package/extensions/render/composition/types.ts +24 -0
- package/extensions/render/excalidraw/UPSTREAM.md +81 -0
- package/extensions/render/excalidraw/elements.ts +764 -0
- package/extensions/render/excalidraw/index.ts +66 -0
- package/extensions/render/excalidraw/types.ts +223 -0
- package/extensions/render/excalidraw-renderer/pyproject.toml +8 -0
- package/extensions/render/excalidraw-renderer/render_excalidraw.py +182 -0
- package/extensions/render/excalidraw-renderer/render_template.html +59 -0
- package/extensions/render/index.ts +830 -0
- package/extensions/render/native-diagrams/index.ts +57 -0
- package/extensions/render/native-diagrams/motifs.ts +542 -0
- package/extensions/render/native-diagrams/raster.ts +8 -0
- package/extensions/render/native-diagrams/scene.ts +75 -0
- package/extensions/render/native-diagrams/spec.ts +204 -0
- package/extensions/render/native-diagrams/svg.ts +116 -0
- package/extensions/sci-ui.ts +304 -0
- package/extensions/session-log.ts +174 -0
- package/extensions/shared-state.ts +146 -0
- package/extensions/spinner-verbs.ts +91 -0
- package/extensions/style.ts +281 -0
- package/extensions/terminal-title.ts +191 -0
- package/extensions/tool-profile/index.ts +291 -0
- package/extensions/tool-profile/profiles.ts +290 -0
- package/extensions/types.d.ts +9 -0
- package/extensions/vault/index.ts +185 -0
- package/extensions/version-check.ts +90 -0
- package/extensions/view/index.ts +859 -0
- package/extensions/view/uri-resolver.ts +148 -0
- package/extensions/web-search/index.ts +182 -0
- package/extensions/web-search/providers.ts +121 -0
- package/extensions/web-ui/index.ts +110 -0
- package/extensions/web-ui/server.ts +265 -0
- package/extensions/web-ui/state.ts +462 -0
- package/extensions/web-ui/static/index.html +145 -0
- package/extensions/web-ui/types.ts +284 -0
- package/package.json +76 -0
- package/prompts/init.md +75 -0
- package/prompts/new-repo.md +54 -0
- package/prompts/oci-login.md +56 -0
- package/prompts/status.md +50 -0
- package/settings.json +4 -0
- package/skills/cleave/SKILL.md +218 -0
- package/skills/git/SKILL.md +209 -0
- package/skills/git/_reference/ci-validation.md +204 -0
- package/skills/oci/SKILL.md +338 -0
- package/skills/openspec/SKILL.md +346 -0
- package/skills/pi-extensions/SKILL.md +191 -0
- package/skills/pi-tui/SKILL.md +517 -0
- package/skills/python/SKILL.md +189 -0
- package/skills/rust/SKILL.md +268 -0
- package/skills/security/SKILL.md +206 -0
- package/skills/style/SKILL.md +264 -0
- package/skills/typescript/SKILL.md +225 -0
- package/skills/vault/SKILL.md +102 -0
- package/themes/alpharius-legacy.json +85 -0
- package/themes/alpharius.conf +59 -0
- package/themes/alpharius.json +88 -0
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cleave/review — Adversarial review loop for child task quality gating.
|
|
3
|
+
*
|
|
4
|
+
* Implements the tiered execution loop (design D4):
|
|
5
|
+
* Execute (cheap) → Review (gloriana) → [pass? done : Fix (cheap) → Review (gloriana)]
|
|
6
|
+
*
|
|
7
|
+
* Severity gating (D4a):
|
|
8
|
+
* - Nits only → PASS, no fix needed
|
|
9
|
+
* - Warnings → 1 fix iteration max
|
|
10
|
+
* - Critical → 2 fix iterations, then escalate
|
|
11
|
+
* - Critical+security → immediate escalate, no fix
|
|
12
|
+
*
|
|
13
|
+
* Diminishing returns (D4b):
|
|
14
|
+
* - Hash issue descriptions between rounds
|
|
15
|
+
* - >50% reappearance → bail + escalate
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { discoverGuardrails, runGuardrails, formatGuardrailResults } from "./guardrails.ts";
|
|
19
|
+
|
|
20
|
+
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/** Severity level for a review issue */
|
|
23
|
+
export type IssueSeverity = "critical" | "warning" | "nit";
|
|
24
|
+
|
|
25
|
+
/** A single issue found during review */
|
|
26
|
+
export interface ReviewIssue {
|
|
27
|
+
/** Unique identifier within a review round, e.g. "C1", "W2", "N3" */
|
|
28
|
+
id: string;
|
|
29
|
+
/** Severity level */
|
|
30
|
+
severity: IssueSeverity;
|
|
31
|
+
/** File path where the issue was found */
|
|
32
|
+
file?: string;
|
|
33
|
+
/** Line number (approximate) */
|
|
34
|
+
line?: number;
|
|
35
|
+
/** Description of the issue */
|
|
36
|
+
description: string;
|
|
37
|
+
/** Whether this is a security or data-loss issue (triggers immediate escalation) */
|
|
38
|
+
security?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Overall verdict from a review round */
|
|
42
|
+
export type ReviewVerdictStatus = "PASS" | "PASS_WITH_CONCERNS" | "NEEDS_REWORK" | "REJECT";
|
|
43
|
+
|
|
44
|
+
/** Result of a single review round */
|
|
45
|
+
export interface ReviewVerdict {
|
|
46
|
+
/** Overall status */
|
|
47
|
+
status: ReviewVerdictStatus;
|
|
48
|
+
/** Issues found, categorized by severity */
|
|
49
|
+
issues: ReviewIssue[];
|
|
50
|
+
/** Raw review output (for history/debugging) */
|
|
51
|
+
rawOutput: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Configuration for the review loop */
|
|
55
|
+
export interface ReviewConfig {
|
|
56
|
+
/** Whether review is enabled at all */
|
|
57
|
+
enabled: boolean;
|
|
58
|
+
/** Maximum fix iterations for warnings (default: 1) */
|
|
59
|
+
maxWarningFixes: number;
|
|
60
|
+
/** Maximum fix iterations for critical issues (default: 2) */
|
|
61
|
+
maxCriticalFixes: number;
|
|
62
|
+
/** Churn detection threshold — fraction of reappearing issues (default: 0.5) */
|
|
63
|
+
churnThreshold: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A single review round entry for history tracking */
|
|
67
|
+
export interface ReviewRound {
|
|
68
|
+
/** Round number (0 = initial review, 1+ = post-fix review) */
|
|
69
|
+
round: number;
|
|
70
|
+
/** The verdict from this round */
|
|
71
|
+
verdict: ReviewVerdict;
|
|
72
|
+
/** Issue IDs that reappeared from the previous round */
|
|
73
|
+
reappeared: string[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Default review configuration */
|
|
77
|
+
export const DEFAULT_REVIEW_CONFIG: ReviewConfig = {
|
|
78
|
+
enabled: false,
|
|
79
|
+
maxWarningFixes: 1,
|
|
80
|
+
maxCriticalFixes: 2,
|
|
81
|
+
churnThreshold: 0.5,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// ─── Prompt Builders ────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Build the adversarial review prompt for a child's work.
|
|
88
|
+
*
|
|
89
|
+
* The review agent runs in the child's worktree (D5) with full file access.
|
|
90
|
+
* Prompt instructs a hostile reviewer to check: spec scenarios, bugs,
|
|
91
|
+
* security, omissions, scope compliance (D4c).
|
|
92
|
+
*
|
|
93
|
+
* @param taskContent - The child's task file content (for scope/mission context)
|
|
94
|
+
* @param rootDirective - The parent directive
|
|
95
|
+
* @param worktreePath - Path to the child's worktree
|
|
96
|
+
*/
|
|
97
|
+
export function buildReviewPrompt(
|
|
98
|
+
taskContent: string,
|
|
99
|
+
rootDirective: string,
|
|
100
|
+
worktreePath: string,
|
|
101
|
+
guardrailOutput?: string,
|
|
102
|
+
): string {
|
|
103
|
+
const guardrailSection = guardrailOutput
|
|
104
|
+
? [
|
|
105
|
+
"### Deterministic Findings",
|
|
106
|
+
"",
|
|
107
|
+
guardrailOutput,
|
|
108
|
+
"",
|
|
109
|
+
"These are compiler/linter results — confirmed issues, not opinions. Any failures here are Critical severity.",
|
|
110
|
+
"",
|
|
111
|
+
].join("\n")
|
|
112
|
+
: "";
|
|
113
|
+
|
|
114
|
+
return [
|
|
115
|
+
"## Adversarial Code Review",
|
|
116
|
+
"",
|
|
117
|
+
"You are a hostile code reviewer. Your job is to find everything wrong with the implementation.",
|
|
118
|
+
"Do not be polite. Do not hedge. If something is broken, say it's broken.",
|
|
119
|
+
"",
|
|
120
|
+
"### Context",
|
|
121
|
+
"",
|
|
122
|
+
`**Root directive:** ${rootDirective}`,
|
|
123
|
+
`**Worktree:** ${worktreePath}`,
|
|
124
|
+
"",
|
|
125
|
+
"The following task file describes what was supposed to be implemented:",
|
|
126
|
+
"",
|
|
127
|
+
"```markdown",
|
|
128
|
+
taskContent,
|
|
129
|
+
"```",
|
|
130
|
+
"",
|
|
131
|
+
...(guardrailSection ? [guardrailSection] : []),
|
|
132
|
+
"### Review Procedure",
|
|
133
|
+
"",
|
|
134
|
+
"1. Read every file in the task's scope",
|
|
135
|
+
"2. Check for:",
|
|
136
|
+
" - Logic errors, off-by-ones, wrong operators, unreachable branches",
|
|
137
|
+
" - Unhandled edge cases: null/undefined, empty inputs, boundary values",
|
|
138
|
+
" - Security: injection vectors, hardcoded secrets, path traversal",
|
|
139
|
+
" - Missing error handling, resource leaks",
|
|
140
|
+
" - Scope violations: modifications outside declared scope",
|
|
141
|
+
" - Spec scenario satisfaction (if acceptance criteria listed in task)",
|
|
142
|
+
"3. Run tests if a test command is declared in the task file",
|
|
143
|
+
"",
|
|
144
|
+
"### Required Output Format",
|
|
145
|
+
"",
|
|
146
|
+
"You MUST output your review in exactly this format:",
|
|
147
|
+
"",
|
|
148
|
+
"```",
|
|
149
|
+
"VERDICT: <PASS|PASS_WITH_CONCERNS|NEEDS_REWORK|REJECT>",
|
|
150
|
+
"",
|
|
151
|
+
"ISSUES:",
|
|
152
|
+
"C1: [file.ts:42] Description of critical issue",
|
|
153
|
+
"C2: [file.ts:99] Another critical issue",
|
|
154
|
+
"C2: SECURITY - SQL injection in query builder",
|
|
155
|
+
"W1: [file.ts:15] Warning description",
|
|
156
|
+
"N1: [file.ts:3] Nit description",
|
|
157
|
+
"```",
|
|
158
|
+
"",
|
|
159
|
+
"Rules:",
|
|
160
|
+
"- Use C prefix for critical issues (bugs, logic errors, security, missing functionality)",
|
|
161
|
+
"- Use W prefix for warnings (fragile patterns, missing edge cases, poor naming)",
|
|
162
|
+
"- Use N prefix for nits (style, dead code, minor improvements)",
|
|
163
|
+
"- Tag security/data-loss criticals with SECURITY after the ID",
|
|
164
|
+
"- Include file path and line number in brackets where possible",
|
|
165
|
+
"- If no issues found, output VERDICT: PASS with empty ISSUES section",
|
|
166
|
+
].join("\n");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Build a fix prompt from review issues.
|
|
171
|
+
*
|
|
172
|
+
* The fix agent (D4: same tier as execute) receives the issue list
|
|
173
|
+
* verbatim with file paths and line numbers.
|
|
174
|
+
*/
|
|
175
|
+
export function buildFixPrompt(
|
|
176
|
+
taskContent: string,
|
|
177
|
+
rootDirective: string,
|
|
178
|
+
issues: ReviewIssue[],
|
|
179
|
+
round: number,
|
|
180
|
+
): string {
|
|
181
|
+
const issueLines = issues.map((issue) => {
|
|
182
|
+
const location = issue.file
|
|
183
|
+
? `[${issue.file}${issue.line ? `:${issue.line}` : ""}]`
|
|
184
|
+
: "";
|
|
185
|
+
const security = issue.security ? " SECURITY" : "";
|
|
186
|
+
return `${issue.id}:${security} ${location} ${issue.description}`;
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return [
|
|
190
|
+
"## Fix Issues from Code Review",
|
|
191
|
+
"",
|
|
192
|
+
`This is fix iteration ${round}. Address ALL issues listed below.`,
|
|
193
|
+
"",
|
|
194
|
+
"### Original Task",
|
|
195
|
+
"",
|
|
196
|
+
"```markdown",
|
|
197
|
+
taskContent,
|
|
198
|
+
"```",
|
|
199
|
+
"",
|
|
200
|
+
`**Root directive:** ${rootDirective}`,
|
|
201
|
+
"",
|
|
202
|
+
"### Issues to Fix",
|
|
203
|
+
"",
|
|
204
|
+
...issueLines,
|
|
205
|
+
"",
|
|
206
|
+
"### Instructions",
|
|
207
|
+
"",
|
|
208
|
+
"1. Fix each issue in order of severity (Critical first, then Warnings)",
|
|
209
|
+
"2. Do NOT introduce new issues while fixing existing ones",
|
|
210
|
+
"3. Run tests after fixing to verify nothing broke",
|
|
211
|
+
"4. Update the task file's Result section if fixes change the summary",
|
|
212
|
+
"5. Commit your fixes with a clear message",
|
|
213
|
+
].join("\n");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ─── Result Parsing ─────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Parse the review agent's output into a structured ReviewVerdict.
|
|
220
|
+
*
|
|
221
|
+
* Expects the format:
|
|
222
|
+
* VERDICT: <status>
|
|
223
|
+
* ISSUES:
|
|
224
|
+
* C1: [file:line] description
|
|
225
|
+
* W1: description
|
|
226
|
+
* N1: description
|
|
227
|
+
*
|
|
228
|
+
* Tolerant parser — handles missing fields and sloppy formatting.
|
|
229
|
+
*/
|
|
230
|
+
export function parseReviewResult(rawOutput: string): ReviewVerdict {
|
|
231
|
+
// Extract verdict
|
|
232
|
+
const verdictMatch = rawOutput.match(/VERDICT:\s*(PASS_WITH_CONCERNS|PASS|NEEDS_REWORK|REJECT)/i);
|
|
233
|
+
let status: ReviewVerdictStatus = "NEEDS_REWORK"; // default if unparseable
|
|
234
|
+
if (verdictMatch) {
|
|
235
|
+
const raw = verdictMatch[1].toUpperCase().replace(/\s+/g, "_");
|
|
236
|
+
if (raw === "PASS") status = "PASS";
|
|
237
|
+
else if (raw === "PASS_WITH_CONCERNS") status = "PASS_WITH_CONCERNS";
|
|
238
|
+
else if (raw === "NEEDS_REWORK") status = "NEEDS_REWORK";
|
|
239
|
+
else if (raw === "REJECT") status = "REJECT";
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Extract issues
|
|
243
|
+
const issues: ReviewIssue[] = [];
|
|
244
|
+
// Match lines like: C1: [file.ts:42] description
|
|
245
|
+
// or: C1: SECURITY [file.ts:42] description
|
|
246
|
+
// or: C1: description (no file/line)
|
|
247
|
+
const issueRegex = /^([CNW])(\d+):\s*(?:(SECURITY)\s*)?(?:\[([^\]]*)\]\s*)?(.+)$/gm;
|
|
248
|
+
let match: RegExpExecArray | null;
|
|
249
|
+
|
|
250
|
+
while ((match = issueRegex.exec(rawOutput)) !== null) {
|
|
251
|
+
const prefix = match[1];
|
|
252
|
+
const num = match[2];
|
|
253
|
+
const securityTag = match[3];
|
|
254
|
+
const locationStr = match[4];
|
|
255
|
+
const description = match[5].trim();
|
|
256
|
+
|
|
257
|
+
let severity: IssueSeverity;
|
|
258
|
+
switch (prefix) {
|
|
259
|
+
case "C": severity = "critical"; break;
|
|
260
|
+
case "W": severity = "warning"; break;
|
|
261
|
+
default: severity = "nit"; break;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let file: string | undefined;
|
|
265
|
+
let line: number | undefined;
|
|
266
|
+
if (locationStr) {
|
|
267
|
+
const parts = locationStr.split(":");
|
|
268
|
+
file = parts[0];
|
|
269
|
+
if (parts.length > 1) {
|
|
270
|
+
const lineNum = parseInt(parts[1], 10);
|
|
271
|
+
if (!isNaN(lineNum)) line = lineNum;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
issues.push({
|
|
276
|
+
id: `${prefix}${num}`,
|
|
277
|
+
severity,
|
|
278
|
+
file,
|
|
279
|
+
line,
|
|
280
|
+
description,
|
|
281
|
+
security: securityTag === "SECURITY" || undefined,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// If the verdict is PASS but there are critical/warning issues, adjust
|
|
286
|
+
if (status === "PASS" && issues.some((i) => i.severity === "critical")) {
|
|
287
|
+
status = "NEEDS_REWORK";
|
|
288
|
+
} else if (status === "PASS" && issues.some((i) => i.severity === "warning")) {
|
|
289
|
+
status = "PASS_WITH_CONCERNS";
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return { status, issues, rawOutput };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ─── Severity Gate ──────────────────────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Severity gate decision — determines what to do after a review round.
|
|
299
|
+
*
|
|
300
|
+
* Per design D4a:
|
|
301
|
+
* - Nits only → accept (PASS)
|
|
302
|
+
* - Warnings → allow up to maxWarningFixes iterations
|
|
303
|
+
* - Critical → allow up to maxCriticalFixes iterations
|
|
304
|
+
* - Critical+security → immediate escalate, no fix
|
|
305
|
+
*/
|
|
306
|
+
export type GateDecision =
|
|
307
|
+
| { action: "accept" }
|
|
308
|
+
| { action: "fix"; issues: ReviewIssue[] }
|
|
309
|
+
| { action: "escalate"; reason: string };
|
|
310
|
+
|
|
311
|
+
export function severityGate(
|
|
312
|
+
verdict: ReviewVerdict,
|
|
313
|
+
currentRound: number,
|
|
314
|
+
config: ReviewConfig,
|
|
315
|
+
): GateDecision {
|
|
316
|
+
const criticals = verdict.issues.filter((i) => i.severity === "critical");
|
|
317
|
+
const warnings = verdict.issues.filter((i) => i.severity === "warning");
|
|
318
|
+
const securityCriticals = criticals.filter((i) => i.security);
|
|
319
|
+
|
|
320
|
+
// PASS → accept
|
|
321
|
+
if (verdict.status === "PASS") {
|
|
322
|
+
return { action: "accept" };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// NEEDS_REWORK or REJECT with zero parseable issues — review output was garbled,
|
|
326
|
+
// escalate rather than silently accepting
|
|
327
|
+
if ((verdict.status === "NEEDS_REWORK" || verdict.status === "REJECT") && verdict.issues.length === 0) {
|
|
328
|
+
return {
|
|
329
|
+
action: "escalate",
|
|
330
|
+
reason: `Review returned ${verdict.status} but no parseable issues — review output may be garbled.`,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Only nits (no criticals, no warnings) → accept
|
|
335
|
+
if (criticals.length === 0 && warnings.length === 0) {
|
|
336
|
+
return { action: "accept" };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Critical+security → immediate escalate (D4a)
|
|
340
|
+
if (securityCriticals.length > 0) {
|
|
341
|
+
return {
|
|
342
|
+
action: "escalate",
|
|
343
|
+
reason: `Security/data-loss critical issues found: ${securityCriticals.map((i) => i.id).join(", ")}. Immediate escalation required.`,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Critical (non-security) → up to maxCriticalFixes iterations
|
|
348
|
+
if (criticals.length > 0) {
|
|
349
|
+
if (currentRound >= config.maxCriticalFixes) {
|
|
350
|
+
return {
|
|
351
|
+
action: "escalate",
|
|
352
|
+
reason: `Critical issues remain after ${currentRound} fix iterations: ${criticals.map((i) => i.id).join(", ")}`,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
// Fix all critical and warning issues
|
|
356
|
+
return { action: "fix", issues: [...criticals, ...warnings] };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Warnings only → up to maxWarningFixes iterations
|
|
360
|
+
if (warnings.length > 0) {
|
|
361
|
+
if (currentRound >= config.maxWarningFixes) {
|
|
362
|
+
// Exceeded warning fix budget — accept with concerns
|
|
363
|
+
return { action: "accept" };
|
|
364
|
+
}
|
|
365
|
+
return { action: "fix", issues: warnings };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return { action: "accept" };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ─── Churn Detection ────────────────────────────────────────────────────────
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Detect churn (diminishing returns) across review rounds.
|
|
375
|
+
*
|
|
376
|
+
* Per design D4b:
|
|
377
|
+
* - Normalize issue descriptions for comparison
|
|
378
|
+
* - Compute reappearance fraction (Jaccard-inspired)
|
|
379
|
+
* - If >threshold fraction of issues reappear → bail
|
|
380
|
+
*
|
|
381
|
+
* @returns Object with churn detected flag, reappearance rate, and reappeared issue IDs
|
|
382
|
+
*/
|
|
383
|
+
export function detectChurn(
|
|
384
|
+
previousIssues: ReviewIssue[],
|
|
385
|
+
currentIssues: ReviewIssue[],
|
|
386
|
+
threshold: number = 0.5,
|
|
387
|
+
): { churning: boolean; reappearanceRate: number; reappeared: string[] } {
|
|
388
|
+
if (previousIssues.length === 0 || currentIssues.length === 0) {
|
|
389
|
+
return { churning: false, reappearanceRate: 0, reappeared: [] };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Normalize descriptions for comparison (lowercase, punctuation→space, collapse whitespace)
|
|
393
|
+
const normalize = (s: string): string =>
|
|
394
|
+
s.toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
395
|
+
|
|
396
|
+
const prevDescriptions = new Set(previousIssues.map((i) => normalize(i.description)));
|
|
397
|
+
const reappeared: string[] = [];
|
|
398
|
+
|
|
399
|
+
for (const issue of currentIssues) {
|
|
400
|
+
if (prevDescriptions.has(normalize(issue.description))) {
|
|
401
|
+
reappeared.push(issue.id);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const reappearanceRate = reappeared.length / currentIssues.length;
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
churning: reappearanceRate > threshold,
|
|
409
|
+
reappearanceRate,
|
|
410
|
+
reappeared,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ─── Execute With Review ────────────────────────────────────────────────────
|
|
415
|
+
|
|
416
|
+
// ─── Guardrail Integration ──────────────────────────────────────────────────
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Callback interface for the review loop to spawn subprocesses.
|
|
420
|
+
*
|
|
421
|
+
* This abstraction allows testing without actual pi process spawning.
|
|
422
|
+
*/
|
|
423
|
+
export interface ReviewExecutor {
|
|
424
|
+
/** Spawn the initial execution (or a fix iteration) */
|
|
425
|
+
execute(prompt: string, cwd: string, modelFlag?: string): Promise<{ exitCode: number; stdout: string; stderr: string }>;
|
|
426
|
+
/** Spawn a review of the completed work */
|
|
427
|
+
review(prompt: string, cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }>;
|
|
428
|
+
/** Read a file from the worktree */
|
|
429
|
+
readFile(path: string): string;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Execute a child task with optional adversarial review loop.
|
|
434
|
+
*
|
|
435
|
+
* Full flow (D4):
|
|
436
|
+
* 1. Execute child task (cheap model)
|
|
437
|
+
* 2. If review enabled: run adversarial review (gloriana)
|
|
438
|
+
* 3. Severity gate determines next action:
|
|
439
|
+
* - ACCEPT → done
|
|
440
|
+
* - FIX → run fix agent (cheap model), then re-review
|
|
441
|
+
* - ESCALATE → report to orchestrator
|
|
442
|
+
* 4. Churn detection between rounds → bail if not converging
|
|
443
|
+
*
|
|
444
|
+
* @returns History of review rounds + final decision
|
|
445
|
+
*/
|
|
446
|
+
export async function executeWithReview(
|
|
447
|
+
executor: ReviewExecutor,
|
|
448
|
+
taskFilePath: string,
|
|
449
|
+
rootDirective: string,
|
|
450
|
+
worktreePath: string,
|
|
451
|
+
config: ReviewConfig,
|
|
452
|
+
executeModelFlag?: string,
|
|
453
|
+
): Promise<{
|
|
454
|
+
executeResult: { exitCode: number; stdout: string; stderr: string };
|
|
455
|
+
reviewHistory: ReviewRound[];
|
|
456
|
+
finalDecision: "accepted" | "escalated" | "no_review";
|
|
457
|
+
escalationReason?: string;
|
|
458
|
+
}> {
|
|
459
|
+
// Step 1: Initial execution
|
|
460
|
+
const taskContent = executor.readFile(taskFilePath);
|
|
461
|
+
const executeResult = await executor.execute(taskContent, worktreePath, executeModelFlag);
|
|
462
|
+
|
|
463
|
+
// If review is disabled, return immediately
|
|
464
|
+
if (!config.enabled) {
|
|
465
|
+
return {
|
|
466
|
+
executeResult,
|
|
467
|
+
reviewHistory: [],
|
|
468
|
+
finalDecision: "no_review",
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// If initial execution failed (timeout, crash), skip review — don't waste
|
|
473
|
+
// a gloriana call reviewing an incomplete/empty worktree
|
|
474
|
+
if (executeResult.exitCode !== 0) {
|
|
475
|
+
return {
|
|
476
|
+
executeResult,
|
|
477
|
+
reviewHistory: [],
|
|
478
|
+
finalDecision: "no_review",
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Discover guardrails once (fast — file existence checks only)
|
|
483
|
+
let guardrailChecks: import("./guardrails.ts").GuardrailCheck[] = [];
|
|
484
|
+
try {
|
|
485
|
+
guardrailChecks = discoverGuardrails(worktreePath);
|
|
486
|
+
} catch {
|
|
487
|
+
// Discovery failed — continue without guardrails
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Step 2+: Review loop
|
|
491
|
+
const reviewHistory: ReviewRound[] = [];
|
|
492
|
+
let previousIssues: ReviewIssue[] = [];
|
|
493
|
+
let round = 0;
|
|
494
|
+
|
|
495
|
+
while (true) {
|
|
496
|
+
// Re-read task content (may have been updated by execute/fix)
|
|
497
|
+
const currentTaskContent = executor.readFile(taskFilePath);
|
|
498
|
+
|
|
499
|
+
// Run guardrails fresh each round (code may have changed after fix iterations)
|
|
500
|
+
let guardrailOutput: string | undefined;
|
|
501
|
+
if (guardrailChecks.length > 0) {
|
|
502
|
+
try {
|
|
503
|
+
const suite = runGuardrails(worktreePath, guardrailChecks);
|
|
504
|
+
const formatted = formatGuardrailResults(suite);
|
|
505
|
+
if (formatted) guardrailOutput = formatted;
|
|
506
|
+
} catch {
|
|
507
|
+
// Execution failed — continue without guardrail output
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Run review — always include latest guardrail output
|
|
512
|
+
const reviewPrompt = buildReviewPrompt(
|
|
513
|
+
currentTaskContent,
|
|
514
|
+
rootDirective,
|
|
515
|
+
worktreePath,
|
|
516
|
+
guardrailOutput,
|
|
517
|
+
);
|
|
518
|
+
const reviewResult = await executor.review(reviewPrompt, worktreePath);
|
|
519
|
+
|
|
520
|
+
// Parse review output
|
|
521
|
+
const verdict = parseReviewResult(reviewResult.stdout);
|
|
522
|
+
|
|
523
|
+
// Check for churn (D4b)
|
|
524
|
+
let reappeared: string[] = [];
|
|
525
|
+
if (round > 0 && previousIssues.length > 0) {
|
|
526
|
+
const churnResult = detectChurn(previousIssues, verdict.issues, config.churnThreshold);
|
|
527
|
+
reappeared = churnResult.reappeared;
|
|
528
|
+
|
|
529
|
+
if (churnResult.churning) {
|
|
530
|
+
reviewHistory.push({ round, verdict, reappeared });
|
|
531
|
+
return {
|
|
532
|
+
executeResult,
|
|
533
|
+
reviewHistory,
|
|
534
|
+
finalDecision: "escalated",
|
|
535
|
+
escalationReason: `Churn detected: ${(churnResult.reappearanceRate * 100).toFixed(0)}% of issues reappeared (threshold: ${(config.churnThreshold * 100).toFixed(0)}%). Fix iterations are not converging.`,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Record this review round
|
|
541
|
+
reviewHistory.push({ round, verdict, reappeared });
|
|
542
|
+
|
|
543
|
+
// Severity gate (D4a)
|
|
544
|
+
const gate = severityGate(verdict, round, config);
|
|
545
|
+
|
|
546
|
+
switch (gate.action) {
|
|
547
|
+
case "accept":
|
|
548
|
+
return {
|
|
549
|
+
executeResult,
|
|
550
|
+
reviewHistory,
|
|
551
|
+
finalDecision: "accepted",
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
case "escalate":
|
|
555
|
+
return {
|
|
556
|
+
executeResult,
|
|
557
|
+
reviewHistory,
|
|
558
|
+
finalDecision: "escalated",
|
|
559
|
+
escalationReason: gate.reason,
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
case "fix": {
|
|
563
|
+
// Build fix prompt and dispatch fix agent
|
|
564
|
+
const fixPrompt = buildFixPrompt(
|
|
565
|
+
currentTaskContent,
|
|
566
|
+
rootDirective,
|
|
567
|
+
gate.issues,
|
|
568
|
+
round + 1,
|
|
569
|
+
);
|
|
570
|
+
await executor.execute(fixPrompt, worktreePath, executeModelFlag);
|
|
571
|
+
|
|
572
|
+
// Prepare for next review round
|
|
573
|
+
previousIssues = verdict.issues;
|
|
574
|
+
round++;
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|