supipowers 2.2.0 → 2.2.2
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/README.md +71 -12
- package/package.json +11 -15
- package/skills/ui-design/SKILL.md +2 -2
- package/src/ai/final-message.ts +15 -1
- package/src/ai/schema-text.ts +60 -40
- package/src/ai/schema-validation.ts +88 -0
- package/src/ai/structured-output.ts +19 -19
- package/src/bootstrap.ts +2 -1
- package/src/commands/doctor.ts +3 -2
- package/src/commands/fix-pr.ts +166 -26
- package/src/commands/plan.ts +2 -1
- package/src/commands/update.ts +7 -5
- package/src/config/schema.ts +102 -139
- package/src/docs/contracts.ts +13 -23
- package/src/fix-pr/assessment.ts +63 -24
- package/src/fix-pr/contracts.ts +15 -23
- package/src/fix-pr/fetch-comments.ts +119 -0
- package/src/fix-pr/prompt-builder.ts +19 -8
- package/src/git/commit-contract.ts +13 -19
- package/src/git/commit.ts +168 -6
- package/src/harness/anti_slop/fallow-adapter.ts +4 -3
- package/src/harness/command.ts +12 -7
- package/src/harness/pipeline.ts +2 -8
- package/src/harness/stage-runner.ts +3 -0
- package/src/harness/stages/docs.ts +82 -0
- package/src/lsp/capabilities.ts +9 -12
- package/src/lsp/contracts.ts +15 -23
- package/src/mempalace/uv.ts +15 -7
- package/src/planning/approval-flow.ts +15 -17
- package/src/planning/planning-ask-tool.ts +13 -2
- package/src/planning/spec.ts +21 -27
- package/src/planning/system-prompt.ts +1 -1
- package/src/planning/validate.ts +4 -7
- package/src/platform/progress.ts +11 -0
- package/src/quality/contracts.ts +15 -23
- package/src/quality/schemas.ts +40 -67
- package/src/release/contracts.ts +19 -28
- package/src/review/types.ts +142 -186
- package/src/types.ts +15 -2
- package/src/ui-design/session.ts +13 -2
- package/src/ui-design/system-prompt.ts +2 -2
- package/src/ultraplan/contracts.ts +458 -524
- package/src/utils/exec-cli.ts +106 -0
- package/src/visual/scripts/npm-shrinkwrap.json +878 -0
- package/src/visual/scripts/package-lock.json +878 -0
package/src/config/schema.ts
CHANGED
|
@@ -1,176 +1,142 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { Value } from "@sinclair/typebox/value";
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import type { ZodType } from "zod/v4";
|
|
4
3
|
import type { SupipowersConfig } from "../types.js";
|
|
5
4
|
import { QualityGatesSchema } from "../quality/schemas.js";
|
|
6
5
|
import { UltraPlanConfigSchema } from "../ultraplan/contracts.js";
|
|
6
|
+
import { collectSchemaValidationErrors } from "../ai/schema-validation.js";
|
|
7
7
|
|
|
8
8
|
const TAG_FORMAT_PATTERN = "^(?:(?!\\$\\{version\\}).)*\\$\\{version\\}(?:(?!\\$\\{version\\}).)*$";
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
export const ConfigSchema =
|
|
11
|
+
export const ConfigSchema = z.object(
|
|
12
12
|
{
|
|
13
|
-
version:
|
|
14
|
-
quality:
|
|
13
|
+
version: z.string(),
|
|
14
|
+
quality: z.object(
|
|
15
15
|
{
|
|
16
16
|
gates: QualityGatesSchema,
|
|
17
17
|
},
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
lsp: Type.Object(
|
|
18
|
+
).strict(),
|
|
19
|
+
lsp: z.object(
|
|
21
20
|
{
|
|
22
|
-
setupGuide:
|
|
21
|
+
setupGuide: z.boolean(),
|
|
23
22
|
},
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
qa: Type.Object(
|
|
23
|
+
).strict(),
|
|
24
|
+
qa: z.object(
|
|
27
25
|
{
|
|
28
|
-
framework:
|
|
29
|
-
e2e:
|
|
26
|
+
framework: z.string().nullable(),
|
|
27
|
+
e2e: z.boolean(),
|
|
30
28
|
},
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
release: Type.Object(
|
|
29
|
+
).strict(),
|
|
30
|
+
release: z.object(
|
|
34
31
|
{
|
|
35
|
-
channels:
|
|
36
|
-
tagFormat:
|
|
37
|
-
customChannels:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
),
|
|
46
|
-
),
|
|
32
|
+
channels: z.array(z.string()),
|
|
33
|
+
tagFormat: z.string().regex(new RegExp(TAG_FORMAT_PATTERN)),
|
|
34
|
+
customChannels: z.record(
|
|
35
|
+
z.string(),
|
|
36
|
+
z.object({
|
|
37
|
+
label: z.string(),
|
|
38
|
+
publishCommand: z.string(),
|
|
39
|
+
detectCommand: z.string().optional(),
|
|
40
|
+
}),
|
|
41
|
+
).optional(),
|
|
47
42
|
},
|
|
48
|
-
|
|
49
|
-
),
|
|
43
|
+
).strict(),
|
|
50
44
|
ultraplan: UltraPlanConfigSchema,
|
|
51
|
-
contextMode:
|
|
45
|
+
contextMode: z.object(
|
|
52
46
|
{
|
|
53
|
-
enabled:
|
|
54
|
-
compressionThreshold:
|
|
55
|
-
blockHttpCommands:
|
|
56
|
-
routingInstructions:
|
|
57
|
-
eventTracking:
|
|
58
|
-
compaction:
|
|
59
|
-
llmSummarization:
|
|
60
|
-
llmThreshold:
|
|
61
|
-
enforceRouting:
|
|
62
|
-
lazyTools:
|
|
47
|
+
enabled: z.boolean(),
|
|
48
|
+
compressionThreshold: z.number().min(1024),
|
|
49
|
+
blockHttpCommands: z.boolean(),
|
|
50
|
+
routingInstructions: z.boolean(),
|
|
51
|
+
eventTracking: z.boolean(),
|
|
52
|
+
compaction: z.boolean(),
|
|
53
|
+
llmSummarization: z.boolean(),
|
|
54
|
+
llmThreshold: z.number().min(4096),
|
|
55
|
+
enforceRouting: z.boolean(),
|
|
56
|
+
lazyTools: z.object(
|
|
63
57
|
{
|
|
64
|
-
enabled:
|
|
65
|
-
mode:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
]),
|
|
70
|
-
alwaysKeep: Type.Array(Type.String()),
|
|
71
|
-
commandAllowlist: Type.Record(Type.String(), Type.Array(Type.String())),
|
|
72
|
-
keywordTools: Type.Record(Type.String(), Type.Array(Type.String())),
|
|
58
|
+
enabled: z.boolean(),
|
|
59
|
+
mode: z.enum(["conservative", "balanced", "aggressive"]),
|
|
60
|
+
alwaysKeep: z.array(z.string()),
|
|
61
|
+
commandAllowlist: z.record(z.string(), z.array(z.string())),
|
|
62
|
+
keywordTools: z.record(z.string(), z.array(z.string())),
|
|
73
63
|
},
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
processors: Type.Object(
|
|
64
|
+
).strict(),
|
|
65
|
+
processors: z.object(
|
|
77
66
|
{
|
|
78
|
-
enabled:
|
|
79
|
-
disable:
|
|
80
|
-
|
|
81
|
-
Type.Literal("git"),
|
|
82
|
-
Type.Literal("test"),
|
|
83
|
-
Type.Literal("lint"),
|
|
84
|
-
Type.Literal("build"),
|
|
85
|
-
Type.Literal("k8s"),
|
|
86
|
-
Type.Literal("docker"),
|
|
87
|
-
Type.Literal("log"),
|
|
88
|
-
Type.Literal("json"),
|
|
89
|
-
]),
|
|
67
|
+
enabled: z.boolean(),
|
|
68
|
+
disable: z.array(
|
|
69
|
+
z.enum(["git", "test", "lint", "build", "k8s", "docker", "log", "json"]),
|
|
90
70
|
),
|
|
91
71
|
},
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
cacheHandles: Type.Object(
|
|
72
|
+
).strict(),
|
|
73
|
+
cacheHandles: z.object(
|
|
95
74
|
{
|
|
96
|
-
enabled:
|
|
97
|
-
spillThresholdBytes:
|
|
98
|
-
previewBytes:
|
|
75
|
+
enabled: z.boolean(),
|
|
76
|
+
spillThresholdBytes: z.number().min(1024),
|
|
77
|
+
previewBytes: z.number().min(256),
|
|
99
78
|
},
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
repomap: Type.Object(
|
|
79
|
+
).strict(),
|
|
80
|
+
repomap: z.object(
|
|
103
81
|
{
|
|
104
|
-
enabled:
|
|
105
|
-
tokenBudget:
|
|
106
|
-
maxFiles:
|
|
82
|
+
enabled: z.boolean(),
|
|
83
|
+
tokenBudget: z.number().min(100),
|
|
84
|
+
maxFiles: z.number().min(1),
|
|
107
85
|
},
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
memory: Type.Object(
|
|
86
|
+
).strict(),
|
|
87
|
+
memory: z.object(
|
|
111
88
|
{
|
|
112
|
-
enabled:
|
|
113
|
-
byteBudget:
|
|
114
|
-
maxRows:
|
|
115
|
-
retentionDays:
|
|
116
|
-
focusChainCadence:
|
|
89
|
+
enabled: z.boolean(),
|
|
90
|
+
byteBudget: z.number().min(256),
|
|
91
|
+
maxRows: z.number().min(1),
|
|
92
|
+
retentionDays: z.number().min(1),
|
|
93
|
+
focusChainCadence: z.number().int().min(1),
|
|
117
94
|
},
|
|
118
|
-
|
|
119
|
-
),
|
|
95
|
+
).strict(),
|
|
120
96
|
},
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
mempalace: Type.Object(
|
|
97
|
+
).strict(),
|
|
98
|
+
mempalace: z.object(
|
|
124
99
|
{
|
|
125
|
-
enabled:
|
|
126
|
-
packageVersion:
|
|
127
|
-
managedVenvPath:
|
|
128
|
-
palacePath:
|
|
129
|
-
defaultWingStrategy:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
explicitWing: Type.Union([Type.String(), Type.Null()]),
|
|
135
|
-
defaultAgentName: Type.String({ minLength: 1 }),
|
|
136
|
-
autoSetup: Type.Boolean(),
|
|
137
|
-
hooks: Type.Object(
|
|
100
|
+
enabled: z.boolean(),
|
|
101
|
+
packageVersion: z.string().min(1),
|
|
102
|
+
managedVenvPath: z.string().min(1),
|
|
103
|
+
palacePath: z.string().min(1),
|
|
104
|
+
defaultWingStrategy: z.enum(["repo-name", "project-slug", "explicit"]),
|
|
105
|
+
explicitWing: z.string().nullable(),
|
|
106
|
+
defaultAgentName: z.string().min(1),
|
|
107
|
+
autoSetup: z.boolean(),
|
|
108
|
+
hooks: z.object(
|
|
138
109
|
{
|
|
139
|
-
wakeUp:
|
|
140
|
-
searchGuidance:
|
|
141
|
-
autoSearchOnPrompt:
|
|
142
|
-
compactionCheckpoint:
|
|
143
|
-
shutdownDiary:
|
|
110
|
+
wakeUp: z.boolean(),
|
|
111
|
+
searchGuidance: z.boolean(),
|
|
112
|
+
autoSearchOnPrompt: z.boolean(),
|
|
113
|
+
compactionCheckpoint: z.boolean(),
|
|
114
|
+
shutdownDiary: z.boolean(),
|
|
144
115
|
},
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
budgets: Type.Object(
|
|
116
|
+
).strict(),
|
|
117
|
+
budgets: z.object(
|
|
148
118
|
{
|
|
149
|
-
wakeUpTokens:
|
|
150
|
-
searchResultChars:
|
|
151
|
-
listResultChars:
|
|
152
|
-
diaryChars:
|
|
153
|
-
autoSearchTokens:
|
|
154
|
-
wakeUpInjectionEvery:
|
|
155
|
-
autoSearchSimilarityFloor:
|
|
156
|
-
autoSearchBm25Floor:
|
|
119
|
+
wakeUpTokens: z.number().int().min(1),
|
|
120
|
+
searchResultChars: z.number().int().min(1),
|
|
121
|
+
listResultChars: z.number().int().min(1),
|
|
122
|
+
diaryChars: z.number().int().min(1),
|
|
123
|
+
autoSearchTokens: z.number().int().min(1),
|
|
124
|
+
wakeUpInjectionEvery: z.number().int().min(1),
|
|
125
|
+
autoSearchSimilarityFloor: z.number().min(0).max(1),
|
|
126
|
+
autoSearchBm25Floor: z.number().min(0),
|
|
157
127
|
},
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
timeouts: Type.Object(
|
|
128
|
+
).strict(),
|
|
129
|
+
timeouts: z.object(
|
|
161
130
|
{
|
|
162
|
-
setupMs:
|
|
163
|
-
bridgeMs:
|
|
164
|
-
hookMs:
|
|
131
|
+
setupMs: z.number().int().min(1),
|
|
132
|
+
bridgeMs: z.number().int().min(1),
|
|
133
|
+
hookMs: z.number().int().min(1),
|
|
165
134
|
},
|
|
166
|
-
|
|
167
|
-
),
|
|
135
|
+
).strict(),
|
|
168
136
|
},
|
|
169
|
-
|
|
170
|
-
),
|
|
137
|
+
).strict(),
|
|
171
138
|
},
|
|
172
|
-
|
|
173
|
-
);
|
|
139
|
+
).strict();
|
|
174
140
|
|
|
175
141
|
export interface ConfigParseError {
|
|
176
142
|
source: "global" | "root";
|
|
@@ -190,13 +156,10 @@ export interface InspectionLoadResult {
|
|
|
190
156
|
validationErrors: ConfigValidationError[];
|
|
191
157
|
}
|
|
192
158
|
|
|
193
|
-
function normalizeErrorPath(path: string): string {
|
|
194
|
-
return path.replace(/^\//, "").replace(/\//g, ".") || "(root)";
|
|
195
|
-
}
|
|
196
159
|
|
|
197
|
-
function collectValidationErrors(schema:
|
|
198
|
-
return
|
|
199
|
-
path:
|
|
160
|
+
function collectValidationErrors(schema: ZodType, data: unknown): ConfigValidationError[] {
|
|
161
|
+
return collectSchemaValidationErrors(schema, data).map((error) => ({
|
|
162
|
+
path: error.path,
|
|
200
163
|
message: error.message,
|
|
201
164
|
}));
|
|
202
165
|
}
|
package/src/docs/contracts.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// retry loop (runWithOutputValidation) will hand validation errors back to
|
|
6
6
|
// the model rather than letting a silent regex heuristic invent findings.
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { z } from "zod/v4"
|
|
9
9
|
|
|
10
10
|
export const DOC_DRIFT_SEVERITIES = ["info", "warning", "error"] as const;
|
|
11
11
|
export const DOC_DRIFT_STATUSES = ["ok", "drifted"] as const;
|
|
@@ -13,27 +13,17 @@ export const DOC_DRIFT_STATUSES = ["ok", "drifted"] as const;
|
|
|
13
13
|
export type DocDriftSeverity = (typeof DOC_DRIFT_SEVERITIES)[number];
|
|
14
14
|
export type DocDriftStatus = (typeof DOC_DRIFT_STATUSES)[number];
|
|
15
15
|
|
|
16
|
-
export const DocDriftFindingSchema =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
),
|
|
23
|
-
relatedFiles: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
24
|
-
},
|
|
25
|
-
{ additionalProperties: false },
|
|
26
|
-
);
|
|
16
|
+
export const DocDriftFindingSchema = z.object({
|
|
17
|
+
file: z.string().min(1),
|
|
18
|
+
description: z.string().min(1),
|
|
19
|
+
severity: z.enum(DOC_DRIFT_SEVERITIES),
|
|
20
|
+
relatedFiles: z.array(z.string().min(1)).optional(),
|
|
21
|
+
}).strict();
|
|
27
22
|
|
|
28
|
-
export const DocDriftOutputSchema =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
DOC_DRIFT_STATUSES.map((value) => Type.Literal(value)),
|
|
33
|
-
),
|
|
34
|
-
},
|
|
35
|
-
{ additionalProperties: false },
|
|
36
|
-
);
|
|
23
|
+
export const DocDriftOutputSchema = z.object({
|
|
24
|
+
findings: z.array(DocDriftFindingSchema),
|
|
25
|
+
status: z.enum(DOC_DRIFT_STATUSES),
|
|
26
|
+
}).strict();
|
|
37
27
|
|
|
38
|
-
export type DocDriftFinding =
|
|
39
|
-
export type DocDriftOutput =
|
|
28
|
+
export type DocDriftFinding = z.infer<typeof DocDriftFindingSchema>;
|
|
29
|
+
export type DocDriftOutput = z.infer<typeof DocDriftOutputSchema>;
|
package/src/fix-pr/assessment.ts
CHANGED
|
@@ -29,6 +29,8 @@ export interface RunFixPrAssessmentInput {
|
|
|
29
29
|
model?: string;
|
|
30
30
|
thinkingLevel?: string | null;
|
|
31
31
|
maxAttempts?: number;
|
|
32
|
+
timeoutMs?: number;
|
|
33
|
+
maxCommentsPerBatch?: number;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
interface BuildAssessmentPromptArgs {
|
|
@@ -74,6 +76,19 @@ function buildAssessmentPrompt(args: BuildAssessmentPromptArgs): string {
|
|
|
74
76
|
].join("\n");
|
|
75
77
|
}
|
|
76
78
|
|
|
79
|
+
function chunkComments(comments: readonly PrComment[], maxCommentsPerBatch: number): PrComment[][] {
|
|
80
|
+
if (maxCommentsPerBatch <= 0 || comments.length <= maxCommentsPerBatch) {
|
|
81
|
+
return [[...comments]];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const chunks: PrComment[][] = [];
|
|
85
|
+
for (let index = 0; index < comments.length; index += maxCommentsPerBatch) {
|
|
86
|
+
chunks.push(comments.slice(index, index + maxCommentsPerBatch));
|
|
87
|
+
}
|
|
88
|
+
return chunks;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
77
92
|
/**
|
|
78
93
|
* Run a schema-backed assessment over a cluster of PR comments.
|
|
79
94
|
*
|
|
@@ -93,30 +108,54 @@ export async function runFixPrAssessment(
|
|
|
93
108
|
}
|
|
94
109
|
|
|
95
110
|
const schemaText = renderSchemaText(FixPrAssessmentBatchSchema);
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
:
|
|
118
|
-
|
|
119
|
-
|
|
111
|
+
const maxCommentsPerBatch = input.maxCommentsPerBatch ?? input.comments.length;
|
|
112
|
+
const commentChunks = chunkComments(input.comments, maxCommentsPerBatch);
|
|
113
|
+
const assessments: FixPrAssessmentBatch["assessments"] = [];
|
|
114
|
+
const rawOutputs: string[] = [];
|
|
115
|
+
let attempts = 0;
|
|
116
|
+
|
|
117
|
+
for (const comments of commentChunks) {
|
|
118
|
+
const prompt = buildAssessmentPrompt({
|
|
119
|
+
schemaText,
|
|
120
|
+
comments,
|
|
121
|
+
repo: input.repo,
|
|
122
|
+
prNumber: input.prNumber,
|
|
123
|
+
selectedTargetLabel: input.selectedTargetLabel,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const result = await runWithOutputValidation<FixPrAssessmentBatch>(
|
|
127
|
+
input.createAgentSession as any,
|
|
128
|
+
{
|
|
129
|
+
cwd: input.cwd,
|
|
130
|
+
prompt,
|
|
131
|
+
schema: schemaText,
|
|
132
|
+
parse: (raw) =>
|
|
133
|
+
parseStructuredOutput<FixPrAssessmentBatch>(raw, FixPrAssessmentBatchSchema),
|
|
134
|
+
model: input.model,
|
|
135
|
+
thinkingLevel: input.thinkingLevel ?? null,
|
|
136
|
+
maxAttempts: input.maxAttempts,
|
|
137
|
+
timeoutMs: input.timeoutMs,
|
|
138
|
+
reliability: input.paths
|
|
139
|
+
? { paths: input.paths, cwd: input.cwd, command: "fix-pr", operation: "assessment" }
|
|
140
|
+
: undefined,
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
attempts += result.attempts;
|
|
145
|
+
if (result.status === "blocked") {
|
|
146
|
+
return { ...result, attempts };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
rawOutputs.push(result.rawOutput);
|
|
150
|
+
assessments.push(...result.output.assessments);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
status: "ok",
|
|
155
|
+
output: { assessments },
|
|
156
|
+
rawOutput: rawOutputs.join("\n"),
|
|
157
|
+
attempts,
|
|
158
|
+
};
|
|
120
159
|
}
|
|
121
160
|
|
|
122
161
|
/**
|
package/src/fix-pr/contracts.ts
CHANGED
|
@@ -5,35 +5,27 @@
|
|
|
5
5
|
// against FixPrAssessmentBatchSchema; downstream work batches are derived
|
|
6
6
|
// from this validated artifact, not from ad-hoc orchestration prose.
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { z } from "zod/v4"
|
|
9
9
|
|
|
10
10
|
export const FIX_PR_ASSESSMENT_VERDICTS = ["apply", "reject", "investigate"] as const;
|
|
11
11
|
export type FixPrAssessmentVerdict = (typeof FIX_PR_ASSESSMENT_VERDICTS)[number];
|
|
12
12
|
|
|
13
|
-
export const FixPrCommentAssessmentSchema =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
rippleEffects: Type.Array(Type.String({ minLength: 1 })),
|
|
22
|
-
verificationPlan: Type.String({ minLength: 1 }),
|
|
23
|
-
},
|
|
24
|
-
{ additionalProperties: false },
|
|
25
|
-
);
|
|
13
|
+
export const FixPrCommentAssessmentSchema = z.object({
|
|
14
|
+
commentId: z.number().int(),
|
|
15
|
+
verdict: z.enum(FIX_PR_ASSESSMENT_VERDICTS),
|
|
16
|
+
rationale: z.string().min(1),
|
|
17
|
+
affectedFiles: z.array(z.string().min(1)),
|
|
18
|
+
rippleEffects: z.array(z.string().min(1)),
|
|
19
|
+
verificationPlan: z.string().min(1),
|
|
20
|
+
}).strict();
|
|
26
21
|
|
|
27
|
-
export const FixPrAssessmentBatchSchema =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
},
|
|
32
|
-
{ additionalProperties: false },
|
|
33
|
-
);
|
|
22
|
+
export const FixPrAssessmentBatchSchema = z.object({
|
|
23
|
+
assessments: z.array(FixPrCommentAssessmentSchema),
|
|
24
|
+
summary: z.string().optional(),
|
|
25
|
+
}).strict();
|
|
34
26
|
|
|
35
|
-
export type FixPrCommentAssessment =
|
|
36
|
-
export type FixPrAssessmentBatch =
|
|
27
|
+
export type FixPrCommentAssessment = z.infer<typeof FixPrCommentAssessmentSchema>;
|
|
28
|
+
export type FixPrAssessmentBatch = z.infer<typeof FixPrAssessmentBatchSchema>;
|
|
37
29
|
|
|
38
30
|
/**
|
|
39
31
|
* A deterministic execution unit derived from a validated FixPrAssessmentBatch.
|
|
@@ -11,6 +11,30 @@ const INLINE_COMMENTS_JQ =
|
|
|
11
11
|
const REVIEW_COMMENTS_JQ =
|
|
12
12
|
'.[] | select(.body != null and .body != "") | {id, path: null, line: null, body, user: .user.login, userType: .user.type, createdAt: .submitted_at, updatedAt: .submitted_at, inReplyToId: null, diffHunk: null, state}';
|
|
13
13
|
|
|
14
|
+
|
|
15
|
+
const RESOLVED_REVIEW_THREAD_COMMENT_IDS_QUERY = `
|
|
16
|
+
query($owner: String!, $name: String!, $number: Int!, $endCursor: String) {
|
|
17
|
+
repository(owner: $owner, name: $name) {
|
|
18
|
+
pullRequest(number: $number) {
|
|
19
|
+
reviewThreads(first: 100, after: $endCursor) {
|
|
20
|
+
nodes {
|
|
21
|
+
isResolved
|
|
22
|
+
comments(first: 100) {
|
|
23
|
+
nodes {
|
|
24
|
+
databaseId
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
pageInfo {
|
|
29
|
+
hasNextPage
|
|
30
|
+
endCursor
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
|
|
14
38
|
export interface ClusteredPrComments<TTarget extends WorkspaceTarget = WorkspaceTarget> {
|
|
15
39
|
allComments: PrComment[];
|
|
16
40
|
commentsByTargetId: Map<string, PrComment[]>;
|
|
@@ -27,6 +51,30 @@ function appendComment(commentsByTargetId: Map<string, PrComment[]>, targetId: s
|
|
|
27
51
|
commentsByTargetId.set(targetId, [comment]);
|
|
28
52
|
}
|
|
29
53
|
|
|
54
|
+
|
|
55
|
+
function parseRepoOwnerAndName(repo: string): { owner: string; name: string } | null {
|
|
56
|
+
const separator = repo.indexOf("/");
|
|
57
|
+
if (separator <= 0 || separator === repo.length - 1) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
owner: repo.slice(0, separator),
|
|
63
|
+
name: repo.slice(separator + 1),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseResolvedCommentIds(output: string): Set<number> {
|
|
68
|
+
const ids = new Set<number>();
|
|
69
|
+
for (const line of output.split(/\r?\n/)) {
|
|
70
|
+
const id = Number.parseInt(line.trim(), 10);
|
|
71
|
+
if (Number.isInteger(id)) {
|
|
72
|
+
ids.add(id);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return ids;
|
|
76
|
+
}
|
|
77
|
+
|
|
30
78
|
export function parsePrCommentsJsonl(content: string): PrComment[] {
|
|
31
79
|
return content
|
|
32
80
|
.split(/\r?\n/)
|
|
@@ -49,6 +97,68 @@ export function stringifyPrCommentsJsonl(comments: readonly PrComment[]): string
|
|
|
49
97
|
return `${comments.map((comment) => JSON.stringify(comment)).join("\n")}\n`;
|
|
50
98
|
}
|
|
51
99
|
|
|
100
|
+
async function fetchResolvedReviewThreadCommentIds(
|
|
101
|
+
platform: Platform,
|
|
102
|
+
repo: string,
|
|
103
|
+
prNumber: number,
|
|
104
|
+
cwd: string,
|
|
105
|
+
): Promise<Set<number> | string> {
|
|
106
|
+
const repoParts = parseRepoOwnerAndName(repo);
|
|
107
|
+
if (!repoParts) {
|
|
108
|
+
return `Invalid repository name: ${repo}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const result = await platform.exec(
|
|
112
|
+
"gh",
|
|
113
|
+
[
|
|
114
|
+
"api",
|
|
115
|
+
"graphql",
|
|
116
|
+
"--paginate",
|
|
117
|
+
"-f",
|
|
118
|
+
`query=${RESOLVED_REVIEW_THREAD_COMMENT_IDS_QUERY}`,
|
|
119
|
+
"-F",
|
|
120
|
+
`owner=${repoParts.owner}`,
|
|
121
|
+
"-F",
|
|
122
|
+
`name=${repoParts.name}`,
|
|
123
|
+
"-F",
|
|
124
|
+
`number=${prNumber}`,
|
|
125
|
+
"--jq",
|
|
126
|
+
".data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == true) | .comments.nodes[].databaseId",
|
|
127
|
+
],
|
|
128
|
+
{ cwd },
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (result.code !== 0) {
|
|
132
|
+
return result.stderr || "gh api graphql failed while fetching resolved review threads";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return parseResolvedCommentIds(result.stdout);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function filterResolvedComments(content: string, resolvedCommentIds: ReadonlySet<number>): string {
|
|
139
|
+
if (!content.trim() || resolvedCommentIds.size === 0) {
|
|
140
|
+
return content;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const unresolvedLines = content
|
|
144
|
+
.split(/\r?\n/)
|
|
145
|
+
.filter((line) => {
|
|
146
|
+
const trimmed = line.trim();
|
|
147
|
+
if (!trimmed) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const comment = JSON.parse(trimmed) as Pick<PrComment, "id">;
|
|
152
|
+
return !resolvedCommentIds.has(comment.id);
|
|
153
|
+
} catch {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return unresolvedLines.length > 0 ? `${unresolvedLines.join("\n")}\n` : "";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
52
162
|
export function clusterPrCommentsByTarget<TTarget extends WorkspaceTarget>(
|
|
53
163
|
targets: readonly TTarget[],
|
|
54
164
|
comments: readonly PrComment[],
|
|
@@ -143,4 +253,13 @@ export async function fetchPrComments(
|
|
|
143
253
|
if (inlineResult.code !== 0 && reviewResult.code !== 0) {
|
|
144
254
|
return inlineResult.stderr || reviewResult.stderr || "gh api calls failed";
|
|
145
255
|
}
|
|
256
|
+
|
|
257
|
+
const resolvedCommentIds = await fetchResolvedReviewThreadCommentIds(platform, repo, prNumber, cwd);
|
|
258
|
+
if (typeof resolvedCommentIds === "string") {
|
|
259
|
+
return resolvedCommentIds;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (resolvedCommentIds.size > 0) {
|
|
263
|
+
fs.writeFileSync(outputPath, filterResolvedComments(fs.readFileSync(outputPath, "utf-8"), resolvedCommentIds));
|
|
264
|
+
}
|
|
146
265
|
}
|