supipowers 1.2.6 → 1.5.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.
Files changed (137) hide show
  1. package/README.md +118 -56
  2. package/bin/install.ts +48 -128
  3. package/package.json +11 -3
  4. package/skills/code-review/SKILL.md +137 -40
  5. package/skills/context-mode/SKILL.md +67 -56
  6. package/skills/creating-supi-agents/SKILL.md +204 -0
  7. package/skills/debugging/SKILL.md +86 -40
  8. package/skills/fix-pr/SKILL.md +96 -65
  9. package/skills/planning/SKILL.md +103 -46
  10. package/skills/qa-strategy/SKILL.md +68 -46
  11. package/skills/receiving-code-review/SKILL.md +60 -53
  12. package/skills/release/SKILL.md +111 -39
  13. package/skills/tdd/SKILL.md +118 -67
  14. package/skills/verification/SKILL.md +71 -37
  15. package/src/bootstrap.ts +27 -5
  16. package/src/commands/agents.ts +249 -0
  17. package/src/commands/ai-review.ts +1113 -0
  18. package/src/commands/config.ts +224 -95
  19. package/src/commands/doctor.ts +19 -13
  20. package/src/commands/fix-pr.ts +8 -11
  21. package/src/commands/generate.ts +200 -0
  22. package/src/commands/model-picker.ts +5 -15
  23. package/src/commands/model.ts +4 -5
  24. package/src/commands/optimize-context.ts +202 -0
  25. package/src/commands/plan.ts +148 -92
  26. package/src/commands/qa.ts +14 -23
  27. package/src/commands/release.ts +504 -275
  28. package/src/commands/review.ts +643 -86
  29. package/src/commands/status.ts +44 -17
  30. package/src/commands/supi.ts +69 -41
  31. package/src/commands/update.ts +57 -2
  32. package/src/config/defaults.ts +6 -39
  33. package/src/config/loader.ts +388 -40
  34. package/src/config/model-resolver.ts +26 -22
  35. package/src/config/schema.ts +113 -48
  36. package/src/context/analyzer.ts +61 -2
  37. package/src/context/optimizer.ts +199 -0
  38. package/src/context-mode/compressor.ts +14 -11
  39. package/src/context-mode/detector.ts +16 -54
  40. package/src/context-mode/event-extractor.ts +45 -16
  41. package/src/context-mode/event-store.ts +225 -16
  42. package/src/context-mode/hooks.ts +195 -22
  43. package/src/context-mode/knowledge/chunker.ts +235 -0
  44. package/src/context-mode/knowledge/store.ts +187 -0
  45. package/src/context-mode/routing.ts +12 -23
  46. package/src/context-mode/sandbox/executor.ts +183 -0
  47. package/src/context-mode/sandbox/runners.ts +40 -0
  48. package/src/context-mode/snapshot-builder.ts +243 -7
  49. package/src/context-mode/tools.ts +440 -0
  50. package/src/context-mode/web/fetcher.ts +117 -0
  51. package/src/context-mode/web/html-to-md.ts +293 -0
  52. package/src/debug/logger.ts +107 -0
  53. package/src/deps/registry.ts +0 -20
  54. package/src/docs/drift.ts +454 -0
  55. package/src/fix-pr/fetch-comments.ts +66 -0
  56. package/src/git/commit-msg.ts +2 -1
  57. package/src/git/commit.ts +123 -141
  58. package/src/git/conventions.ts +2 -2
  59. package/src/git/status.ts +4 -1
  60. package/src/lsp/bridge.ts +138 -12
  61. package/src/planning/approval-flow.ts +125 -19
  62. package/src/planning/plan-writer-prompt.ts +4 -11
  63. package/src/planning/planning-ask-tool.ts +81 -0
  64. package/src/planning/prompt-builder.ts +9 -169
  65. package/src/planning/system-prompt.ts +290 -0
  66. package/src/platform/omp.ts +50 -4
  67. package/src/platform/progress.ts +182 -0
  68. package/src/platform/test-utils.ts +4 -1
  69. package/src/platform/tui-colors.ts +30 -0
  70. package/src/platform/types.ts +1 -0
  71. package/src/qa/detect-app-type.ts +102 -0
  72. package/src/qa/discover-routes.ts +353 -0
  73. package/src/quality/ai-session.ts +96 -0
  74. package/src/quality/ai-setup.ts +86 -0
  75. package/src/quality/gates/ai-review.ts +129 -0
  76. package/src/quality/gates/build.ts +8 -0
  77. package/src/quality/gates/command.ts +150 -0
  78. package/src/quality/gates/format.ts +28 -0
  79. package/src/quality/gates/lint.ts +22 -0
  80. package/src/quality/gates/lsp-diagnostics.ts +84 -0
  81. package/src/quality/gates/test-suite.ts +8 -0
  82. package/src/quality/gates/typecheck.ts +22 -0
  83. package/src/quality/registry.ts +25 -0
  84. package/src/quality/review-gates.ts +33 -0
  85. package/src/quality/runner.ts +268 -0
  86. package/src/quality/schemas.ts +48 -0
  87. package/src/quality/setup.ts +227 -0
  88. package/src/release/changelog.ts +7 -3
  89. package/src/release/channels/custom.ts +43 -0
  90. package/src/release/channels/gitea.ts +35 -0
  91. package/src/release/channels/github.ts +35 -0
  92. package/src/release/channels/gitlab.ts +35 -0
  93. package/src/release/channels/registry.ts +52 -0
  94. package/src/release/channels/types.ts +27 -0
  95. package/src/release/detector.ts +10 -63
  96. package/src/release/executor.ts +61 -51
  97. package/src/release/prompt.ts +38 -38
  98. package/src/release/version.ts +129 -10
  99. package/src/review/agent-loader.ts +331 -0
  100. package/src/review/consolidator.ts +180 -0
  101. package/src/review/default-agents/correctness.md +72 -0
  102. package/src/review/default-agents/maintainability.md +64 -0
  103. package/src/review/default-agents/security.md +67 -0
  104. package/src/review/fixer.ts +219 -0
  105. package/src/review/multi-agent-runner.ts +135 -0
  106. package/src/review/output.ts +147 -0
  107. package/src/review/prompts/agent-review-wrapper.md +36 -0
  108. package/src/review/prompts/fix-findings.md +32 -0
  109. package/src/review/prompts/fix-output-schema.md +18 -0
  110. package/src/review/prompts/invalid-output-retry.md +22 -0
  111. package/src/review/prompts/output-instructions.md +14 -0
  112. package/src/review/prompts/review-output-schema.md +38 -0
  113. package/src/review/prompts/single-review.md +53 -0
  114. package/src/review/prompts/validation-review.md +30 -0
  115. package/src/review/runner.ts +128 -0
  116. package/src/review/scope.ts +353 -0
  117. package/src/review/template.ts +15 -0
  118. package/src/review/types.ts +296 -0
  119. package/src/review/validator.ts +160 -0
  120. package/src/storage/plans.ts +5 -3
  121. package/src/storage/reports.ts +50 -7
  122. package/src/storage/review-sessions.ts +117 -0
  123. package/src/text.ts +19 -0
  124. package/src/types.ts +336 -26
  125. package/src/utils/paths.ts +39 -0
  126. package/src/visual/companion.ts +5 -3
  127. package/src/visual/start-server.ts +101 -0
  128. package/src/visual/stop-server.ts +39 -0
  129. package/bin/ctx-mode-wrapper.mjs +0 -66
  130. package/src/config/profiles.ts +0 -64
  131. package/src/context-mode/installer.ts +0 -38
  132. package/src/quality/ai-review-gate.ts +0 -43
  133. package/src/quality/gate-runner.ts +0 -67
  134. package/src/quality/lsp-gate.ts +0 -24
  135. package/src/quality/test-gate.ts +0 -39
  136. package/src/visual/scripts/start-server.sh +0 -98
  137. package/src/visual/scripts/stop-server.sh +0 -21
@@ -0,0 +1,331 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import correctnessAgentTemplate from "./default-agents/correctness.md" with { type: "text" };
5
+ import maintainabilityAgentTemplate from "./default-agents/maintainability.md" with { type: "text" };
6
+ import securityAgentTemplate from "./default-agents/security.md" with { type: "text" };
7
+ import type { PlatformPaths } from "../platform/types.js";
8
+ import type {
9
+ ConfiguredReviewAgent,
10
+ ReviewAgentConfig,
11
+ ReviewAgentDefinition,
12
+ ReviewAgentsConfig,
13
+ } from "../types.js";
14
+ import { normalizeLineEndings } from "../text.js";
15
+ import {
16
+ ReviewAgentFrontmatterSchema,
17
+ ReviewAgentsConfigSchema,
18
+ collectReviewValidationErrors,
19
+ formatReviewValidationErrors,
20
+ } from "./types.js";
21
+
22
+ const REVIEW_AGENTS_DIR = "review-agents";
23
+ const CONFIG_FILE = "config.yml";
24
+
25
+ const DEFAULT_AGENT_TEMPLATES: Record<string, string> = {
26
+ "security.md": securityAgentTemplate,
27
+ "correctness.md": correctnessAgentTemplate,
28
+ "maintainability.md": maintainabilityAgentTemplate,
29
+ };
30
+
31
+ const DEFAULT_REVIEW_AGENTS_CONFIG: ReviewAgentConfig[] = [
32
+ { name: "security", enabled: true, data: "security.md", model: null },
33
+ { name: "correctness", enabled: true, data: "correctness.md", model: null },
34
+ { name: "maintainability", enabled: true, data: "maintainability.md", model: null },
35
+ ];
36
+
37
+ export interface LoadedReviewAgents {
38
+ agentsDir: string;
39
+ configPath: string;
40
+ config: ReviewAgentsConfig;
41
+ agents: ConfiguredReviewAgent[];
42
+ }
43
+
44
+ function buildDefaultConfigText(): string {
45
+ return [
46
+ "agents:",
47
+ ...DEFAULT_REVIEW_AGENTS_CONFIG.flatMap((agent) => [
48
+ ` - name: ${agent.name}`,
49
+ ` enabled: ${agent.enabled}`,
50
+ ` data: ${agent.data}`,
51
+ " model: null",
52
+ ]),
53
+ "",
54
+ ].join("\n");
55
+ }
56
+
57
+ function writeIfMissing(filePath: string, content: string): void {
58
+ if (fs.existsSync(filePath)) {
59
+ return;
60
+ }
61
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
62
+ fs.writeFileSync(filePath, content);
63
+ }
64
+
65
+ function validateReviewAgentsConfig(data: unknown): ReviewAgentsConfig {
66
+ const errors = formatReviewValidationErrors(collectReviewValidationErrors(ReviewAgentsConfigSchema, data));
67
+ if (errors.length > 0) {
68
+ throw new Error(`Invalid review-agents config: ${errors.join("; ")}`);
69
+ }
70
+ return data as ReviewAgentsConfig;
71
+ }
72
+
73
+ async function importYamlFile(filePath: string): Promise<unknown> {
74
+ const stat = fs.statSync(filePath);
75
+ const url = pathToFileURL(filePath);
76
+ const imported = await import(`${url.href}?mtime=${stat.mtimeMs}`, { with: { type: "yaml" } });
77
+ return imported.default;
78
+ }
79
+
80
+ function parseFrontmatter(frontmatter: string, filePath: string): ReviewAgentDefinition {
81
+ const metadata: Record<string, unknown> = {};
82
+
83
+ for (const line of frontmatter.split("\n")) {
84
+ const trimmed = line.trim();
85
+ if (!trimmed) {
86
+ continue;
87
+ }
88
+
89
+ const separator = trimmed.indexOf(":");
90
+ if (separator === -1) {
91
+ continue;
92
+ }
93
+
94
+ const key = trimmed.slice(0, separator).trim();
95
+ const value = trimmed.slice(separator + 1).trim();
96
+ metadata[key] = value;
97
+ }
98
+
99
+ const errors = formatReviewValidationErrors(
100
+ collectReviewValidationErrors(ReviewAgentFrontmatterSchema, metadata),
101
+ );
102
+ if (errors.length > 0) {
103
+ throw new Error(`Invalid agent frontmatter in ${filePath}: ${errors.join("; ")}`);
104
+ }
105
+
106
+ return {
107
+ name: String(metadata.name),
108
+ description: String(metadata.description),
109
+ focus: typeof metadata.focus === "string" ? metadata.focus : null,
110
+ prompt: "",
111
+ filePath,
112
+ };
113
+ }
114
+
115
+ export function parseReviewAgentMarkdown(content: string, filePath: string): ReviewAgentDefinition {
116
+ const normalized = normalizeLineEndings(content);
117
+ const match = normalized.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
118
+ if (!match) {
119
+ throw new Error(`Review agent file ${filePath} is missing YAML frontmatter.`);
120
+ }
121
+
122
+ const definition = parseFrontmatter(match[1], filePath);
123
+ const prompt = match[2]?.trim();
124
+ if (!prompt) {
125
+ throw new Error(`Review agent file ${filePath} has an empty prompt body.`);
126
+ }
127
+
128
+ return {
129
+ ...definition,
130
+ prompt,
131
+ };
132
+ }
133
+
134
+ export function getReviewAgentsDir(paths: PlatformPaths, cwd: string): string {
135
+ return paths.project(cwd, REVIEW_AGENTS_DIR);
136
+ }
137
+
138
+ export function getReviewAgentsConfigPath(paths: PlatformPaths, cwd: string): string {
139
+ return path.join(getReviewAgentsDir(paths, cwd), CONFIG_FILE);
140
+ }
141
+
142
+ export function ensureDefaultReviewAgents(paths: PlatformPaths, cwd: string): void {
143
+ const agentsDir = getReviewAgentsDir(paths, cwd);
144
+ fs.mkdirSync(agentsDir, { recursive: true });
145
+
146
+ for (const [fileName, content] of Object.entries(DEFAULT_AGENT_TEMPLATES)) {
147
+ writeIfMissing(path.join(agentsDir, fileName), content);
148
+ }
149
+
150
+ writeIfMissing(getReviewAgentsConfigPath(paths, cwd), buildDefaultConfigText());
151
+ }
152
+
153
+ export async function loadReviewAgentsConfig(paths: PlatformPaths, cwd: string): Promise<ReviewAgentsConfig> {
154
+ ensureDefaultReviewAgents(paths, cwd);
155
+ return validateReviewAgentsConfig(await importYamlFile(getReviewAgentsConfigPath(paths, cwd)));
156
+ }
157
+
158
+ export async function loadReviewAgents(paths: PlatformPaths, cwd: string): Promise<LoadedReviewAgents> {
159
+ const agentsDir = getReviewAgentsDir(paths, cwd);
160
+ const configPath = getReviewAgentsConfigPath(paths, cwd);
161
+ const config = await loadReviewAgentsConfig(paths, cwd);
162
+
163
+ const agents = config.agents
164
+ .filter((agent) => agent.enabled)
165
+ .map((agent) => {
166
+ const filePath = path.join(agentsDir, agent.data);
167
+ if (!fs.existsSync(filePath)) {
168
+ throw new Error(`Configured review agent file does not exist: ${filePath}`);
169
+ }
170
+
171
+ const definition = parseReviewAgentMarkdown(fs.readFileSync(filePath, "utf-8"), filePath);
172
+ if (definition.name !== agent.name) {
173
+ throw new Error(
174
+ `Configured agent name \"${agent.name}\" does not match frontmatter name \"${definition.name}\" in ${filePath}.`,
175
+ );
176
+ }
177
+
178
+ return {
179
+ ...definition,
180
+ enabled: agent.enabled,
181
+ data: agent.data,
182
+ model: agent.model,
183
+ } satisfies ConfiguredReviewAgent;
184
+ });
185
+
186
+ return {
187
+ agentsDir,
188
+ configPath,
189
+ config,
190
+ agents,
191
+ };
192
+ }
193
+
194
+ // ── Global Agent Support ────────────────────────────────────
195
+
196
+ export function getGlobalReviewAgentsDir(paths: PlatformPaths): string {
197
+ return paths.global(REVIEW_AGENTS_DIR);
198
+ }
199
+
200
+ export function getGlobalReviewAgentsConfigPath(paths: PlatformPaths): string {
201
+ return path.join(getGlobalReviewAgentsDir(paths), CONFIG_FILE);
202
+ }
203
+
204
+ export function ensureGlobalDefaultReviewAgents(paths: PlatformPaths): void {
205
+ const agentsDir = getGlobalReviewAgentsDir(paths);
206
+ fs.mkdirSync(agentsDir, { recursive: true });
207
+
208
+ for (const [fileName, content] of Object.entries(DEFAULT_AGENT_TEMPLATES)) {
209
+ writeIfMissing(path.join(agentsDir, fileName), content);
210
+ }
211
+
212
+ writeIfMissing(getGlobalReviewAgentsConfigPath(paths), buildDefaultConfigText());
213
+ }
214
+
215
+ export async function loadGlobalReviewAgentsConfig(paths: PlatformPaths): Promise<ReviewAgentsConfig> {
216
+ ensureGlobalDefaultReviewAgents(paths);
217
+ return validateReviewAgentsConfig(await importYamlFile(getGlobalReviewAgentsConfigPath(paths)));
218
+ }
219
+
220
+ export async function loadGlobalReviewAgents(paths: PlatformPaths): Promise<LoadedReviewAgents> {
221
+ const agentsDir = getGlobalReviewAgentsDir(paths);
222
+ const configPath = getGlobalReviewAgentsConfigPath(paths);
223
+ const config = await loadGlobalReviewAgentsConfig(paths);
224
+
225
+ const agents = config.agents
226
+ .filter((agent) => agent.enabled)
227
+ .map((agent) => {
228
+ const filePath = path.join(agentsDir, agent.data);
229
+ if (!fs.existsSync(filePath)) {
230
+ throw new Error(`Configured review agent file does not exist: ${filePath}`);
231
+ }
232
+
233
+ const definition = parseReviewAgentMarkdown(fs.readFileSync(filePath, "utf-8"), filePath);
234
+ if (definition.name !== agent.name) {
235
+ throw new Error(
236
+ `Configured agent name "${agent.name}" does not match frontmatter name "${definition.name}" in ${filePath}.`,
237
+ );
238
+ }
239
+
240
+ return {
241
+ ...definition,
242
+ enabled: agent.enabled,
243
+ data: agent.data,
244
+ model: agent.model,
245
+ scope: "global" as const,
246
+ } satisfies ConfiguredReviewAgent;
247
+ });
248
+
249
+ return {
250
+ agentsDir,
251
+ configPath,
252
+ config,
253
+ agents,
254
+ };
255
+ }
256
+
257
+ // ── Merged Loading (Global + Project) ──────────────────────
258
+
259
+ export async function loadMergedReviewAgents(
260
+ paths: PlatformPaths,
261
+ cwd: string,
262
+ ): Promise<LoadedReviewAgents> {
263
+ const globalResult = await loadGlobalReviewAgents(paths);
264
+ const projectResult = await loadReviewAgents(paths, cwd);
265
+
266
+ // Tag project agents with scope
267
+ const projectAgents = projectResult.agents.map((agent) => ({
268
+ ...agent,
269
+ scope: "project" as const,
270
+ }));
271
+
272
+ // Project agents override global agents with the same name
273
+ const projectNames = new Set(projectAgents.map((a) => a.name));
274
+ const uniqueGlobalAgents = globalResult.agents.filter((a) => !projectNames.has(a.name));
275
+
276
+ return {
277
+ agentsDir: projectResult.agentsDir,
278
+ configPath: projectResult.configPath,
279
+ config: projectResult.config,
280
+ agents: [...uniqueGlobalAgents, ...projectAgents],
281
+ };
282
+ }
283
+
284
+ // ── Write Helpers ──────────────────────────────────────────
285
+
286
+ export function writeAgentFile(
287
+ agentsDir: string,
288
+ name: string,
289
+ frontmatter: { name: string; description: string; focus: string | null },
290
+ promptBody: string,
291
+ ): string {
292
+ const fileName = `${name}.md`;
293
+ const filePath = path.join(agentsDir, fileName);
294
+ const focusLine = frontmatter.focus ? `\nfocus: ${frontmatter.focus}` : "";
295
+ const content = `---\nname: ${frontmatter.name}\ndescription: ${frontmatter.description}${focusLine}\n---\n\n${promptBody}\n`;
296
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
297
+ fs.writeFileSync(filePath, content);
298
+ return fileName;
299
+ }
300
+
301
+ export async function addAgentToConfig(
302
+ configPath: string,
303
+ agent: ReviewAgentConfig,
304
+ ): Promise<void> {
305
+ let config: ReviewAgentsConfig;
306
+ try {
307
+ config = validateReviewAgentsConfig(await importYamlFile(configPath));
308
+ } catch {
309
+ config = { agents: [] };
310
+ }
311
+
312
+ // Idempotent: replace if name exists, append otherwise
313
+ const idx = config.agents.findIndex((a) => a.name === agent.name);
314
+ if (idx >= 0) {
315
+ config.agents[idx] = agent;
316
+ } else {
317
+ config.agents.push(agent);
318
+ }
319
+
320
+ const text = [
321
+ "agents:",
322
+ ...config.agents.flatMap((a) => [
323
+ ` - name: ${a.name}`,
324
+ ` enabled: ${a.enabled}`,
325
+ ` data: ${a.data}`,
326
+ ` model: ${a.model ?? "null"}`,
327
+ ]),
328
+ "",
329
+ ].join("\n");
330
+ fs.writeFileSync(configPath, text);
331
+ }
@@ -0,0 +1,180 @@
1
+ import type { ReviewFinding, ReviewOutput } from "../types.js";
2
+
3
+ const PRIORITY_ORDER: Record<ReviewFinding["priority"], number> = {
4
+ P0: 0,
5
+ P1: 1,
6
+ P2: 2,
7
+ P3: 3,
8
+ };
9
+
10
+ const SEVERITY_ORDER: Record<ReviewFinding["severity"], number> = {
11
+ error: 0,
12
+ warning: 1,
13
+ info: 2,
14
+ };
15
+
16
+ function normalizeText(text: string): string[] {
17
+ return text
18
+ .toLowerCase()
19
+ .replace(/[^a-z0-9\s]/g, " ")
20
+ .split(/\s+/)
21
+ .map((token) => token.trim())
22
+ .filter((token) => token.length >= 4);
23
+ }
24
+
25
+ function tokenSimilarity(left: string, right: string): number {
26
+ const leftTokens = normalizeText(left);
27
+ const rightTokens = normalizeText(right);
28
+ if (leftTokens.length === 0 || rightTokens.length === 0) {
29
+ return 0;
30
+ }
31
+
32
+ const leftSet = new Set(leftTokens);
33
+ const rightSet = new Set(rightTokens);
34
+ let shared = 0;
35
+ for (const token of leftSet) {
36
+ if (rightSet.has(token)) {
37
+ shared += 1;
38
+ }
39
+ }
40
+
41
+ return shared / Math.max(leftSet.size, rightSet.size);
42
+ }
43
+
44
+ function linesOverlap(left: ReviewFinding, right: ReviewFinding): boolean {
45
+ if (!left.file || !right.file || left.file !== right.file) {
46
+ return false;
47
+ }
48
+ if (left.lineStart === null || left.lineEnd === null || right.lineStart === null || right.lineEnd === null) {
49
+ return false;
50
+ }
51
+
52
+ return left.lineStart <= right.lineEnd && right.lineStart <= left.lineEnd;
53
+ }
54
+
55
+ function areDuplicateFindings(left: ReviewFinding, right: ReviewFinding): boolean {
56
+ if (!linesOverlap(left, right)) {
57
+ return false;
58
+ }
59
+
60
+ const titleSimilarity = tokenSimilarity(left.title, right.title);
61
+ const bodySimilarity = tokenSimilarity(left.body, right.body);
62
+ return titleSimilarity >= 0.4 || bodySimilarity >= 0.35;
63
+ }
64
+
65
+ function comparePriority(left: ReviewFinding, right: ReviewFinding): number {
66
+ const priorityDelta = PRIORITY_ORDER[left.priority] - PRIORITY_ORDER[right.priority];
67
+ if (priorityDelta !== 0) {
68
+ return priorityDelta;
69
+ }
70
+
71
+ const severityDelta = SEVERITY_ORDER[left.severity] - SEVERITY_ORDER[right.severity];
72
+ if (severityDelta !== 0) {
73
+ return severityDelta;
74
+ }
75
+
76
+ return right.confidence - left.confidence;
77
+ }
78
+
79
+ function mergeAgentLabels(left: string | undefined, right: string | undefined): string | undefined {
80
+ const labels = new Set(
81
+ [left, right]
82
+ .flatMap((value) => value?.split(",") ?? [])
83
+ .map((value) => value.trim())
84
+ .filter((value) => value.length > 0),
85
+ );
86
+
87
+ if (labels.size === 0) {
88
+ return undefined;
89
+ }
90
+
91
+ return [...labels].sort().join(",");
92
+ }
93
+
94
+ function mergeValidation(left: ReviewFinding, right: ReviewFinding): ReviewFinding["validation"] | undefined {
95
+ const candidates = [left.validation, right.validation].filter((value) => value !== undefined);
96
+ if (candidates.length === 0) {
97
+ return undefined;
98
+ }
99
+
100
+ const ranking = { confirmed: 0, uncertain: 1, rejected: 2 } as const;
101
+ candidates.sort((a, b) => ranking[a.verdict] - ranking[b.verdict]);
102
+ return candidates[0];
103
+ }
104
+
105
+ function mergeFindings(left: ReviewFinding, right: ReviewFinding): ReviewFinding {
106
+ const preferred = comparePriority(left, right) <= 0 ? left : right;
107
+ const alternate = preferred === left ? right : left;
108
+
109
+ return {
110
+ ...preferred,
111
+ confidence: Math.max(left.confidence, right.confidence),
112
+ suggestion: preferred.suggestion ?? alternate.suggestion,
113
+ agent: mergeAgentLabels(left.agent, right.agent),
114
+ validation: mergeValidation(left, right),
115
+ };
116
+ }
117
+
118
+ function sortFindings(findings: ReviewFinding[]): ReviewFinding[] {
119
+ return [...findings].sort((left, right) => {
120
+ const priority = comparePriority(left, right);
121
+ if (priority !== 0) {
122
+ return priority;
123
+ }
124
+
125
+ const fileLeft = left.file ?? "~";
126
+ const fileRight = right.file ?? "~";
127
+ const fileComparison = fileLeft.localeCompare(fileRight);
128
+ if (fileComparison !== 0) {
129
+ return fileComparison;
130
+ }
131
+
132
+ return (left.lineStart ?? Number.MAX_SAFE_INTEGER) - (right.lineStart ?? Number.MAX_SAFE_INTEGER);
133
+ });
134
+ }
135
+
136
+ function statusFromFindings(findings: ReviewFinding[]): ReviewOutput["status"] {
137
+ if (findings.length === 0) {
138
+ return "passed";
139
+ }
140
+
141
+ const hasValidation = findings.some((finding) => finding.validation !== undefined);
142
+ if (!hasValidation) {
143
+ return "failed";
144
+ }
145
+
146
+ if (findings.some((finding) => finding.validation?.verdict === "confirmed")) {
147
+ return "failed";
148
+ }
149
+ if (findings.some((finding) => finding.validation?.verdict === "uncertain")) {
150
+ return "blocked";
151
+ }
152
+ return "passed";
153
+ }
154
+
155
+ export function consolidateReviewFindings(findings: ReviewFinding[]): ReviewFinding[] {
156
+ const consolidated: ReviewFinding[] = [];
157
+
158
+ for (const finding of sortFindings(findings)) {
159
+ const index = consolidated.findIndex((candidate) => areDuplicateFindings(candidate, finding));
160
+ if (index === -1) {
161
+ consolidated.push(finding);
162
+ continue;
163
+ }
164
+
165
+ consolidated[index] = mergeFindings(consolidated[index], finding);
166
+ }
167
+
168
+ return sortFindings(consolidated);
169
+ }
170
+
171
+ export function consolidateReviewOutputs(outputs: ReviewOutput[]): ReviewOutput {
172
+ const allFindings = outputs.flatMap((output) => output.findings);
173
+ const findings = consolidateReviewFindings(allFindings);
174
+
175
+ return {
176
+ findings,
177
+ summary: `Consolidated ${allFindings.length} findings into ${findings.length} unique findings.`,
178
+ status: statusFromFindings(findings),
179
+ };
180
+ }
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: correctness
3
+ description: Correctness-focused code reviewer targeting logic errors, edge cases, and contract violations
4
+ focus: Logic and control flow, edge cases, error handling, state management, async correctness, type safety, contract fidelity
5
+ ---
6
+
7
+ You are a correctness-focused code reviewer. Analyze the provided code diff for bugs, logic errors, and contract violations that would produce wrong results in production.
8
+
9
+ ## What to Check
10
+
11
+ ### Logic & Control Flow
12
+ - Off-by-one errors — wrong loop bounds, fencepost errors, `<` vs `<=`
13
+ - Wrong boolean operators — `&&` where `||` is needed, incorrect negation, missing parentheses around compound conditions
14
+ - Operator precedence — implicit grouping that changes meaning (e.g., bitwise vs logical operators)
15
+ - Unreachable code — dead branches after early returns, conditions that are always true/false
16
+ - Short-circuit evaluation — relying on side effects in short-circuited expressions
17
+
18
+ ### Edge Cases & Boundary Conditions
19
+ - Null/undefined inputs — missing guards on optional parameters or nullable return values
20
+ - Empty collections — code that assumes at least one element (e.g., `array[0]` without length check)
21
+ - Zero and negative values — division by zero, negative indices, unsigned/signed confusion
22
+ - Overflow/underflow — integer arithmetic exceeding safe bounds, unbounded string/array growth
23
+ - Single-element and max-boundary cases — code that works for N>1 but fails for N=0 or N=1
24
+
25
+ ### Error Handling & Failure Paths
26
+ - Swallowed errors — empty `catch` blocks, `catch` that logs but doesn't rethrow or return a failure signal
27
+ - Missing cleanup — resources (file handles, connections, timers) not released in `finally` or on early return
28
+ - Fail-open patterns — exceptions that cause code to proceed as if nothing failed instead of aborting
29
+ - Misleading error messages — error text that doesn't match the actual failure, or that omits the root cause
30
+ - Unhandled promise rejections — async functions called without `await` or `.catch()`, floating promises
31
+
32
+ ### State Management
33
+ - Invalid state transitions — state set to a value that violates the domain's invariants
34
+ - Implicit state machines — multiple boolean flags used where a discriminated union or enum belongs
35
+ - Stale state after async operations — reading state that may have changed during an `await`
36
+ - Shared-state mutation — concurrent writers to the same object/array without coordination
37
+ - Missing state resets — state that accumulates across invocations when it should be fresh each time
38
+
39
+ ### Async & Concurrency
40
+ - Race conditions — time-of-check to time-of-use (TOCTTOU) gaps, read-modify-write without atomicity
41
+ - Missing `await` — async function called but the returned promise is ignored
42
+ - Parallel mutations — multiple concurrent operations modifying the same resource
43
+ - Event listener leaks — listeners registered without corresponding cleanup or removal
44
+ - Callback ordering assumptions — code that assumes callback A fires before callback B without a guarantee
45
+
46
+ ### Type & Data Integrity
47
+ - Implicit coercions — `==` instead of `===`, truthy/falsy checks on values where `0`, `""`, or `false` are valid
48
+ - Lossy conversions — float to integer truncation, string to number parsing without validation
49
+ - Wrong data shape assumptions — accessing nested properties without verifying the structure exists
50
+ - Unsafe casts — type assertions (`as`, `!`) that bypass the type checker without runtime validation
51
+ - Missing type narrowing — using a union type without discriminating, leading to property access on the wrong variant
52
+
53
+ ### Contract Violations
54
+ - Return values that don't match documented or implied behavior — function says it returns X but returns Y under certain paths
55
+ - Partial results returned as complete — a function returns a subset of expected data without signaling incompleteness
56
+ - Plausible output on failure — a function that fails but returns default-looking data instead of throwing or returning an error signal
57
+ - Mismatched nullability — function declares a non-null return but has a code path that returns null/undefined
58
+
59
+ ## Severity Guide
60
+
61
+ - **error**: Will produce wrong results, data corruption, or crash in production (e.g., off-by-one causing data loss, swallowed error hiding a failure, missing await dropping a write)
62
+ - **warning**: Likely incorrect under certain inputs or conditions that may realistically arise (e.g., empty-array access without guard, stale state read after await)
63
+ - **info**: Code smell that increases risk of future correctness bugs (e.g., implicit coercion, unsafe cast on stable code)
64
+
65
+ ## Out of Scope
66
+
67
+ - Security vulnerabilities (handled by security agent)
68
+ - Code style or formatting (handled by linter)
69
+ - Maintainability concerns (handled by maintainability agent)
70
+ - Performance optimizations
71
+
72
+ {output_instructions}
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: maintainability
3
+ description: Maintainability-focused code reviewer targeting readability, cohesion, coupling, and long-term cost
4
+ focus: Readability, cohesion, coupling, duplication, abstraction quality, interface design, long-term cost
5
+ ---
6
+
7
+ You are a maintainability-focused code reviewer. Analyze the provided code diff for patterns that increase the cost of future changes, obscure intent, or make the codebase harder to work with safely.
8
+
9
+ ## What to Check
10
+
11
+ ### Readability & Clarity
12
+ - Misleading names — variables, functions, or types whose names suggest something different from what they actually do
13
+ - Unclear intent — code that requires reading the implementation to understand the purpose, where a name or comment would suffice
14
+ - Magic numbers and strings — unexplained literal values embedded in logic instead of named constants
15
+ - Missing comments on non-obvious invariants — implicit assumptions, tradeoffs, or constraints that the next editor will not know
16
+ - Inconsistent conventions — naming, patterns, or structure that departs from what the surrounding code does without justification
17
+
18
+ ### Cohesion & Responsibility
19
+ - God classes or functions — units that do too many things, violating single-responsibility
20
+ - Mixed levels of abstraction — high-level orchestration mixed with low-level manipulation in the same function
21
+ - Feature envy — a function or method that is more interested in another module's data than its own, suggesting the logic belongs elsewhere
22
+
23
+ ### Coupling & Dependencies
24
+ - Tight coupling to implementation details — callers depending on internal structure rather than a stable interface
25
+ - Hidden dependencies — behavior that depends on global state, environment variables, or import side effects without making it explicit
26
+ - Shotgun surgery — a single logical change that requires edits across many unrelated files, indicating a missing abstraction
27
+ - Missing dependency inversion — concrete implementations hardcoded where an interface or injection point would reduce coupling
28
+
29
+ ### Duplication & Redundancy
30
+ - Copy-pasted logic — identical or near-identical code blocks that should be extracted into a shared function
31
+ - Near-duplicate functions — functions with minor variations that could be unified with a parameter or strategy
32
+ - Knowledge in more than one place — the same business rule, constant, or mapping expressed redundantly across files
33
+
34
+ ### Abstraction Quality
35
+ - Leaky abstractions — modules that expose internal details callers should not depend on
36
+ - Incomplete abstractions — abstractions that handle most cases but force callers to work around them for the rest, giving the appearance of encapsulation without the reality
37
+ - Premature abstractions — generalized solutions for problems that only exist in one place, adding indirection without earning it
38
+
39
+ ### Interface Design
40
+ - Ignored inputs — functions that accept parameters they silently discard or never use
41
+ - Misleading return shapes — return types that are technically correct but semantically wrong (e.g., returning an empty result on failure instead of signaling the failure)
42
+ - Boolean flag parameters — a single function that behaves differently based on boolean arguments, conflating multiple behaviors behind one entry point
43
+ - Unstable interfaces — public APIs whose shape will likely need to change, forcing callers to update when it does
44
+
45
+ ### Long-Term Cost Signals
46
+ - Fragile inheritance — deep or wide class hierarchies where changing a base class risks breaking descendants
47
+ - Dead code left reachable — unused functions, unreachable branches, or stale exports that remain importable
48
+ - Unaddressed debt markers — TODO, FIXME, HACK, or similar annotations that indicate known problems left in place
49
+ - Distant coupling — code that requires understanding modules far from the call site to modify safely
50
+
51
+ ## Severity Guide
52
+
53
+ - **error**: Actively harmful to maintainability — will cause bugs or incorrect changes by the next person who edits nearby code (e.g., misleading name that inverts the reader's understanding, leaky abstraction that callers already work around)
54
+ - **warning**: Significant maintainability cost that compounds over time (e.g., duplication across files, tight coupling, mixed responsibilities in a single function)
55
+ - **info**: Improvement opportunity that would make the code easier to work with (e.g., extract helper, add clarifying comment, rename for consistency)
56
+
57
+ ## Out of Scope
58
+
59
+ - Security vulnerabilities (handled by security agent)
60
+ - Correctness or logic bugs (handled by correctness agent)
61
+ - Code style or formatting (handled by linter)
62
+ - Performance optimizations
63
+
64
+ {output_instructions}