@kaitranntt/ccs 7.65.1 → 7.65.2-dev.1
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/dist/ui/assets/{accounts-BHEYnq6b.js → accounts-Dh95PibK.js} +1 -1
- package/dist/ui/assets/{alert-dialog-D0EFRcfB.js → alert-dialog-C5RdUHi9.js} +1 -1
- package/dist/ui/assets/{api-DhM3BYXr.js → api-C0ROFLme.js} +1 -1
- package/dist/ui/assets/{auth-section-DVp8FQGm.js → auth-section-M2azTP3G.js} +1 -1
- package/dist/ui/assets/{backups-section-CRo0NZkA.js → backups-section-DIDUVa0t.js} +1 -1
- package/dist/ui/assets/{channels-uZ_9CBqO.js → channels-D_5uerEp.js} +1 -1
- package/dist/ui/assets/{checkbox-32DNqW_Q.js → checkbox-CgMg7fDH.js} +1 -1
- package/dist/ui/assets/{claude-extension-BfXlz5gV.js → claude-extension-DA9wMzPz.js} +1 -1
- package/dist/ui/assets/{cliproxy-DjNY9H-U.js → cliproxy-4yUL1fQw.js} +1 -1
- package/dist/ui/assets/{cliproxy-ai-providers-5SHLMHiy.js → cliproxy-ai-providers-DedMcdcc.js} +1 -1
- package/dist/ui/assets/{cliproxy-control-panel-Zax_m1AC.js → cliproxy-control-panel-B0kwxgNi.js} +1 -1
- package/dist/ui/assets/{codex-CRUSpjsu.js → codex-CAWw4ZNl.js} +1 -1
- package/dist/ui/assets/{confirm-dialog-DVf5ZmCZ.js → confirm-dialog-Ds0PYz2R.js} +1 -1
- package/dist/ui/assets/{copilot-BZrihl_Z.js → copilot-m6i00mFy.js} +1 -1
- package/dist/ui/assets/{cursor-BP4nbEk_.js → cursor-COeD0Dgq.js} +1 -1
- package/dist/ui/assets/{droid-BG92rdM2.js → droid-CznUyiRx.js} +1 -1
- package/dist/ui/assets/{globalenv-section-Cf6dKgSf.js → globalenv-section-FgK1eGWk.js} +1 -1
- package/dist/ui/assets/{health-BTy1UZs3.js → health-Cpu6bD6K.js} +1 -1
- package/dist/ui/assets/{index-DuRYaONg.js → index-Bhz6T039.js} +1 -1
- package/dist/ui/assets/{index-N2ZSJurX.js → index-C7sG68Mi.js} +1 -1
- package/dist/ui/assets/index-CcKb4PL_.js +69 -0
- package/dist/ui/assets/{index-wg7UtkFv.js → index-DampXntj.js} +1 -1
- package/dist/ui/assets/{index-BVeN0dIB.js → index-DgnxlKNk.js} +1 -1
- package/dist/ui/assets/{index-DHrTq-0n.js → index-rTSyskt3.js} +1 -1
- package/dist/ui/assets/{masked-input-DX9bedLy.js → masked-input-B_l4FMkE.js} +1 -1
- package/dist/ui/assets/{proxy-status-widget-DVDMuZK5.js → proxy-status-widget-C7wSbfPC.js} +1 -1
- package/dist/ui/assets/{raw-json-settings-editor-panel-Dkt5E6Z_.js → raw-json-settings-editor-panel-CViWFt6t.js} +1 -1
- package/dist/ui/assets/{searchable-select-BP3Q1-Yn.js → searchable-select-7-yJbbw2.js} +1 -1
- package/dist/ui/assets/{separator-BLGGUlh9.js → separator-DApM4Wa5.js} +1 -1
- package/dist/ui/assets/{shared-G0XRyLig.js → shared-Blmm7sMd.js} +1 -1
- package/dist/ui/assets/{table-B4lRrWC-.js → table-BwM4zncv.js} +1 -1
- package/dist/ui/assets/{updates--A2Sdo7N.js → updates-DJ0ofB67.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/package.json +2 -1
- package/scripts/github/build-ai-review-packet.mjs +242 -0
- package/scripts/github/normalize-ai-review-output.mjs +311 -55
- package/scripts/github/prepare-ai-review-scope.mjs +18 -32
- package/scripts/github/run-ai-review-direct.mjs +349 -0
- package/dist/ui/assets/index-Corv1lSo.js +0 -69
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import {
|
|
5
|
+
normalizeStructuredOutput,
|
|
6
|
+
renderIncompleteReview,
|
|
7
|
+
renderStructuredReview,
|
|
8
|
+
} from './normalize-ai-review-output.mjs';
|
|
9
|
+
|
|
10
|
+
function cleanText(value) {
|
|
11
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readTextFile(filePath) {
|
|
15
|
+
try {
|
|
16
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
17
|
+
} catch {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readSelectedFiles(filePath) {
|
|
23
|
+
return readTextFile(filePath)
|
|
24
|
+
.split('\n')
|
|
25
|
+
.map((line) => cleanText(line))
|
|
26
|
+
.filter(Boolean);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function stripCodeFence(value) {
|
|
30
|
+
const text = cleanText(value);
|
|
31
|
+
const fenceMatch = text.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/u);
|
|
32
|
+
return fenceMatch ? cleanText(fenceMatch[1]) : text;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function extractJsonCandidate(value) {
|
|
36
|
+
const text = stripCodeFence(value);
|
|
37
|
+
const firstBrace = text.indexOf('{');
|
|
38
|
+
const lastBrace = text.lastIndexOf('}');
|
|
39
|
+
if (firstBrace >= 0 && lastBrace > firstBrace) {
|
|
40
|
+
return text.slice(firstBrace, lastBrace + 1);
|
|
41
|
+
}
|
|
42
|
+
return text;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function collectMessageText(responseJson) {
|
|
46
|
+
const content = Array.isArray(responseJson?.content) ? responseJson.content : [];
|
|
47
|
+
return content
|
|
48
|
+
.filter((block) => block?.type === 'text' && typeof block?.text === 'string')
|
|
49
|
+
.map((block) => block.text)
|
|
50
|
+
.join('\n\n');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function postReviewRequest({ apiUrl, apiKey, model, system, prompt, timeoutMs, fetchImpl }) {
|
|
54
|
+
const controller = new AbortController();
|
|
55
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const response = await fetchImpl(`${apiUrl.replace(/\/$/, '')}/v1/messages`, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
signal: controller.signal,
|
|
61
|
+
headers: {
|
|
62
|
+
'anthropic-version': '2023-06-01',
|
|
63
|
+
authorization: `Bearer ${apiKey}`,
|
|
64
|
+
'content-type': 'application/json',
|
|
65
|
+
'x-api-key': apiKey,
|
|
66
|
+
},
|
|
67
|
+
body: JSON.stringify({
|
|
68
|
+
model,
|
|
69
|
+
max_tokens: 6000,
|
|
70
|
+
temperature: 0,
|
|
71
|
+
system,
|
|
72
|
+
messages: [{ role: 'user', content: prompt }],
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const errorText = await response.text();
|
|
78
|
+
throw new Error(`review api returned ${response.status}: ${errorText}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return response.json();
|
|
82
|
+
} finally {
|
|
83
|
+
clearTimeout(timeout);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildSystemPrompt(reviewPrompt) {
|
|
88
|
+
return `${reviewPrompt}
|
|
89
|
+
|
|
90
|
+
## Critical Response Contract
|
|
91
|
+
|
|
92
|
+
Return JSON only. Do not wrap it in markdown fences.
|
|
93
|
+
Return a single object with these keys only:
|
|
94
|
+
- summary
|
|
95
|
+
- findings
|
|
96
|
+
- securityChecklist
|
|
97
|
+
- ccsCompliance
|
|
98
|
+
- informational
|
|
99
|
+
- strengths
|
|
100
|
+
- overallAssessment
|
|
101
|
+
- overallRationale
|
|
102
|
+
|
|
103
|
+
Each finding may optionally include:
|
|
104
|
+
- snippets: an array of up to 2 objects with required code plus optional label and language
|
|
105
|
+
|
|
106
|
+
If snippets are present:
|
|
107
|
+
- keep code literal only, without markdown fences
|
|
108
|
+
- keep each snippet under 20 lines
|
|
109
|
+
- use snippets only for short evidence that materially clarifies the finding
|
|
110
|
+
|
|
111
|
+
Use empty arrays rather than inventing low-value feedback.
|
|
112
|
+
Every finding must be confirmed by the review packet.`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function buildPrimaryPrompt({ meta, packet }) {
|
|
116
|
+
return `REPO: ${meta.repository}
|
|
117
|
+
PR NUMBER: ${meta.prNumber}
|
|
118
|
+
PR BASE REF: ${meta.baseRef}
|
|
119
|
+
PR HEAD REF: ${meta.headRef}
|
|
120
|
+
PR HEAD SHA: ${meta.headSha}
|
|
121
|
+
CONTRIBUTOR: @${meta.authorLogin}
|
|
122
|
+
AUTHOR ASSOCIATION: ${meta.authorAssociation}
|
|
123
|
+
REVIEW MODE: ${meta.reviewMode}
|
|
124
|
+
PR SIZE CLASS: ${meta.sizeClass}
|
|
125
|
+
CHANGED FILES: ${meta.changedFiles}
|
|
126
|
+
ADDITIONS: ${meta.additions}
|
|
127
|
+
DELETIONS: ${meta.deletions}
|
|
128
|
+
TOTAL CHURN: ${meta.totalChurn}
|
|
129
|
+
|
|
130
|
+
Review the generated packet below and return the final JSON review object.
|
|
131
|
+
|
|
132
|
+
${packet}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function buildRepairPrompt({ validationReason, previousCandidate }) {
|
|
136
|
+
return `Your previous response did not validate: ${validationReason}.
|
|
137
|
+
|
|
138
|
+
Return corrected JSON only. Keep only confirmed findings. Do not add markdown fences.
|
|
139
|
+
|
|
140
|
+
Previous candidate:
|
|
141
|
+
${previousCandidate}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function resolveAttemptWindow({
|
|
145
|
+
timeoutMinutes,
|
|
146
|
+
configuredTimeoutMs,
|
|
147
|
+
requestBufferMs = 45000,
|
|
148
|
+
minAttemptMs = 20000,
|
|
149
|
+
startedAt = Date.now(),
|
|
150
|
+
now = Date.now(),
|
|
151
|
+
}) {
|
|
152
|
+
if (!Number.isInteger(timeoutMinutes) || timeoutMinutes <= 0) {
|
|
153
|
+
return {
|
|
154
|
+
canAttempt: true,
|
|
155
|
+
timeoutMs: configuredTimeoutMs,
|
|
156
|
+
deadline: null,
|
|
157
|
+
remainingMs: null,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const stepBudgetMs = timeoutMinutes * 60 * 1000;
|
|
162
|
+
const bufferMs = Math.min(Math.max(requestBufferMs, 5000), Math.max(stepBudgetMs - 5000, 5000));
|
|
163
|
+
const deadline = startedAt + Math.max(stepBudgetMs - bufferMs, minAttemptMs);
|
|
164
|
+
const remainingMs = deadline - now;
|
|
165
|
+
|
|
166
|
+
if (remainingMs < minAttemptMs) {
|
|
167
|
+
return {
|
|
168
|
+
canAttempt: false,
|
|
169
|
+
timeoutMs: null,
|
|
170
|
+
deadline,
|
|
171
|
+
remainingMs,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
canAttempt: true,
|
|
177
|
+
timeoutMs: Math.min(configuredTimeoutMs, remainingMs),
|
|
178
|
+
deadline,
|
|
179
|
+
remainingMs,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function resolveCoveredSelectedFiles({
|
|
184
|
+
selectedFiles,
|
|
185
|
+
packetIncludedFiles,
|
|
186
|
+
includedManifestFiles,
|
|
187
|
+
}) {
|
|
188
|
+
if (includedManifestFiles.length > 0) {
|
|
189
|
+
return includedManifestFiles;
|
|
190
|
+
}
|
|
191
|
+
if (!Number.isInteger(packetIncludedFiles) || packetIncludedFiles <= 0) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
if (packetIncludedFiles >= selectedFiles.length) {
|
|
195
|
+
return selectedFiles;
|
|
196
|
+
}
|
|
197
|
+
return selectedFiles.slice(0, packetIncludedFiles);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function writeDirectReviewFromEnv(env = process.env, fetchImpl = globalThis.fetch) {
|
|
201
|
+
const outputFile = cleanText(env.AI_REVIEW_OUTPUT_FILE || 'pr_review.md');
|
|
202
|
+
const logFile = cleanText(env.AI_REVIEW_LOG_FILE || '.ccs-ai-review-attempts.json');
|
|
203
|
+
const prompt = cleanText(env.AI_REVIEW_PROMPT);
|
|
204
|
+
const packet = readTextFile(cleanText(env.AI_REVIEW_PACKET_FILE || '.ccs-ai-review-packet.md'));
|
|
205
|
+
const selectedFiles = readSelectedFiles(
|
|
206
|
+
cleanText(env.AI_REVIEW_SCOPE_MANIFEST_FILE || '.ccs-ai-review-selected-files.txt')
|
|
207
|
+
);
|
|
208
|
+
const includedManifestFiles = readSelectedFiles(
|
|
209
|
+
cleanText(env.AI_REVIEW_PACKET_INCLUDED_MANIFEST_FILE || '.ccs-ai-review-packet-included-files.txt')
|
|
210
|
+
);
|
|
211
|
+
const timeoutMs = Number.parseInt(cleanText(env.AI_REVIEW_REQUEST_TIMEOUT_MS || '240000'), 10) || 240000;
|
|
212
|
+
const timeoutMinutes = Number.parseInt(cleanText(env.AI_REVIEW_TIMEOUT_MINUTES || '0'), 10) || 0;
|
|
213
|
+
const requestBufferMs = Number.parseInt(cleanText(env.AI_REVIEW_REQUEST_BUFFER_MS || '45000'), 10) || 45000;
|
|
214
|
+
const minAttemptMs = Number.parseInt(cleanText(env.AI_REVIEW_REQUEST_MIN_MS || '20000'), 10) || 20000;
|
|
215
|
+
const maxAttempts = Number.parseInt(cleanText(env.AI_REVIEW_MAX_ATTEMPTS || '3'), 10) || 3;
|
|
216
|
+
const startedAt = Date.now();
|
|
217
|
+
const rendering = {
|
|
218
|
+
mode: env.AI_REVIEW_MODE,
|
|
219
|
+
selectedFiles: env.AI_REVIEW_SELECTED_FILES,
|
|
220
|
+
reviewableFiles: env.AI_REVIEW_REVIEWABLE_FILES,
|
|
221
|
+
selectedChanges: env.AI_REVIEW_SELECTED_CHANGES,
|
|
222
|
+
reviewableChanges: env.AI_REVIEW_REVIEWABLE_CHANGES,
|
|
223
|
+
scopeLabel: env.AI_REVIEW_SCOPE_LABEL,
|
|
224
|
+
timeoutMinutes: env.AI_REVIEW_TIMEOUT_MINUTES,
|
|
225
|
+
packetIncludedFiles: env.AI_REVIEW_PACKET_INCLUDED_FILES,
|
|
226
|
+
packetTotalFiles: env.AI_REVIEW_PACKET_TOTAL_FILES,
|
|
227
|
+
packetOmittedFiles: env.AI_REVIEW_PACKET_OMITTED_FILES,
|
|
228
|
+
};
|
|
229
|
+
const packetIncludedFiles = Number.parseInt(cleanText(env.AI_REVIEW_PACKET_INCLUDED_FILES || '0'), 10) || 0;
|
|
230
|
+
const coveredSelectedFiles = resolveCoveredSelectedFiles({
|
|
231
|
+
selectedFiles,
|
|
232
|
+
packetIncludedFiles,
|
|
233
|
+
includedManifestFiles,
|
|
234
|
+
});
|
|
235
|
+
const meta = {
|
|
236
|
+
repository: cleanText(env.GITHUB_REPOSITORY),
|
|
237
|
+
prNumber: cleanText(env.AI_REVIEW_PR_NUMBER),
|
|
238
|
+
baseRef: cleanText(env.AI_REVIEW_BASE_REF),
|
|
239
|
+
headRef: cleanText(env.AI_REVIEW_HEAD_REF),
|
|
240
|
+
headSha: cleanText(env.AI_REVIEW_HEAD_SHA),
|
|
241
|
+
authorLogin: cleanText(env.AI_REVIEW_AUTHOR_LOGIN),
|
|
242
|
+
authorAssociation: cleanText(env.AI_REVIEW_AUTHOR_ASSOCIATION),
|
|
243
|
+
reviewMode: cleanText(env.AI_REVIEW_MODE),
|
|
244
|
+
sizeClass: cleanText(env.AI_REVIEW_PR_SIZE_CLASS),
|
|
245
|
+
changedFiles: cleanText(env.AI_REVIEW_CHANGED_FILES),
|
|
246
|
+
additions: cleanText(env.AI_REVIEW_ADDITIONS),
|
|
247
|
+
deletions: cleanText(env.AI_REVIEW_DELETIONS),
|
|
248
|
+
totalChurn: cleanText(env.AI_REVIEW_TOTAL_CHURN),
|
|
249
|
+
};
|
|
250
|
+
const system = buildSystemPrompt(prompt);
|
|
251
|
+
const attempts = [];
|
|
252
|
+
let finalValidation = null;
|
|
253
|
+
let lastReason = 'missing structured output';
|
|
254
|
+
let previousCandidate = '';
|
|
255
|
+
|
|
256
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
257
|
+
const attemptWindow = resolveAttemptWindow({
|
|
258
|
+
timeoutMinutes,
|
|
259
|
+
configuredTimeoutMs: timeoutMs,
|
|
260
|
+
requestBufferMs,
|
|
261
|
+
minAttemptMs,
|
|
262
|
+
startedAt,
|
|
263
|
+
now: Date.now(),
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
if (!attemptWindow.canAttempt || !attemptWindow.timeoutMs) {
|
|
267
|
+
attempts.push({
|
|
268
|
+
attempt,
|
|
269
|
+
status: 'skipped_budget',
|
|
270
|
+
validationReason: 'reserved remaining runtime for deterministic fallback publication',
|
|
271
|
+
remainingMs: attemptWindow.remainingMs,
|
|
272
|
+
});
|
|
273
|
+
lastReason = 'review runtime budget reserved for deterministic fallback publication';
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const attemptPrompt =
|
|
279
|
+
attempt === 1
|
|
280
|
+
? buildPrimaryPrompt({ meta, packet })
|
|
281
|
+
: `${buildPrimaryPrompt({ meta, packet })}\n\n${buildRepairPrompt({ validationReason: lastReason, previousCandidate })}`;
|
|
282
|
+
const attemptStartedAt = new Date().toISOString();
|
|
283
|
+
const responseJson = await postReviewRequest({
|
|
284
|
+
apiUrl: cleanText(env.ANTHROPIC_BASE_URL),
|
|
285
|
+
apiKey: cleanText(env.ANTHROPIC_AUTH_TOKEN),
|
|
286
|
+
model: cleanText(env.REVIEW_MODEL || env.ANTHROPIC_MODEL || 'glm-5-turbo'),
|
|
287
|
+
system,
|
|
288
|
+
prompt: attemptPrompt,
|
|
289
|
+
timeoutMs: attemptWindow.timeoutMs,
|
|
290
|
+
fetchImpl,
|
|
291
|
+
});
|
|
292
|
+
const rawText = collectMessageText(responseJson);
|
|
293
|
+
previousCandidate = extractJsonCandidate(rawText);
|
|
294
|
+
const validation = normalizeStructuredOutput(previousCandidate);
|
|
295
|
+
attempts.push({
|
|
296
|
+
attempt,
|
|
297
|
+
startedAt: attemptStartedAt,
|
|
298
|
+
status: validation.ok ? 'validated' : 'invalid',
|
|
299
|
+
timeoutMs: attemptWindow.timeoutMs,
|
|
300
|
+
validationReason: validation.ok ? null : validation.reason,
|
|
301
|
+
responsePreview: rawText.slice(0, 800),
|
|
302
|
+
});
|
|
303
|
+
if (validation.ok) {
|
|
304
|
+
finalValidation = validation.value;
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
lastReason = validation.reason || lastReason;
|
|
308
|
+
} catch (error) {
|
|
309
|
+
attempts.push({
|
|
310
|
+
attempt,
|
|
311
|
+
status: 'error',
|
|
312
|
+
timeoutMs: attemptWindow.timeoutMs,
|
|
313
|
+
validationReason: error instanceof Error ? error.message : String(error),
|
|
314
|
+
});
|
|
315
|
+
lastReason = error instanceof Error ? error.message : String(error);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const markdown = finalValidation
|
|
320
|
+
? renderStructuredReview(finalValidation, {
|
|
321
|
+
model: cleanText(env.REVIEW_MODEL || 'glm-5-turbo'),
|
|
322
|
+
rendering,
|
|
323
|
+
})
|
|
324
|
+
: renderIncompleteReview({
|
|
325
|
+
model: cleanText(env.REVIEW_MODEL || 'glm-5-turbo'),
|
|
326
|
+
reason: lastReason,
|
|
327
|
+
runUrl: cleanText(env.AI_REVIEW_RUN_URL || '#'),
|
|
328
|
+
selectedFiles: coveredSelectedFiles,
|
|
329
|
+
rendering,
|
|
330
|
+
status: 'failure',
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|
|
334
|
+
fs.writeFileSync(outputFile, `${markdown}\n`, 'utf8');
|
|
335
|
+
fs.writeFileSync(logFile, `${JSON.stringify({ attempts, success: !!finalValidation }, null, 2)}\n`, 'utf8');
|
|
336
|
+
|
|
337
|
+
return { usedFallback: !finalValidation, attempts };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const isMain =
|
|
341
|
+
process.argv[1] &&
|
|
342
|
+
path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
|
|
343
|
+
|
|
344
|
+
if (isMain) {
|
|
345
|
+
const result = await writeDirectReviewFromEnv();
|
|
346
|
+
if (result.usedFallback) {
|
|
347
|
+
process.exitCode = 1;
|
|
348
|
+
}
|
|
349
|
+
}
|