@kaitranntt/ccs 7.65.0 → 7.65.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/package.json
CHANGED
|
@@ -290,6 +290,66 @@ function readExecutionMetadata(executionFile) {
|
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
function readSelectedFiles(manifestFile) {
|
|
294
|
+
if (!manifestFile || !fs.existsSync(manifestFile)) {
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
return fs
|
|
300
|
+
.readFileSync(manifestFile, 'utf8')
|
|
301
|
+
.split('\n')
|
|
302
|
+
.map((line) => cleanText(line))
|
|
303
|
+
.filter(Boolean);
|
|
304
|
+
} catch {
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function formatHotspotFiles(files) {
|
|
310
|
+
if (!files.length) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const visible = files.slice(0, 4).map(renderCode).join(', ');
|
|
315
|
+
return files.length > 4 ? `${visible}, and ${files.length - 4} more` : visible;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function formatRemainingCoverage(rendering) {
|
|
319
|
+
if (
|
|
320
|
+
typeof rendering.selectedFiles !== 'number' ||
|
|
321
|
+
typeof rendering.reviewableFiles !== 'number'
|
|
322
|
+
) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const remainingFiles = Math.max(rendering.reviewableFiles - rendering.selectedFiles, 0);
|
|
327
|
+
const hasChangeCounts =
|
|
328
|
+
typeof rendering.selectedChanges === 'number' &&
|
|
329
|
+
typeof rendering.reviewableChanges === 'number';
|
|
330
|
+
const remainingChanges = hasChangeCounts
|
|
331
|
+
? Math.max(rendering.reviewableChanges - rendering.selectedChanges, 0)
|
|
332
|
+
: null;
|
|
333
|
+
|
|
334
|
+
if (remainingFiles === 0 && (!hasChangeCounts || remainingChanges === 0)) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (typeof remainingChanges === 'number') {
|
|
339
|
+
return `${remainingFiles} file${remainingFiles === 1 ? '' : 's'}; ${remainingChanges} changed lines`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return `${remainingFiles} file${remainingFiles === 1 ? '' : 's'}`;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function formatFallbackFollowUp(rendering) {
|
|
346
|
+
if (rendering.mode === 'triage') {
|
|
347
|
+
return 'Focus manual review on the hotspot files above, and use `/review` for a deeper pass when release, auth, config, or workflow paths changed.';
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return 'Use `/review` when you need a deeper maintainer rerun with more surrounding context.';
|
|
351
|
+
}
|
|
352
|
+
|
|
293
353
|
export function normalizeStructuredOutput(raw) {
|
|
294
354
|
if (!raw) {
|
|
295
355
|
return { ok: false, reason: 'missing structured output' };
|
|
@@ -461,6 +521,7 @@ export function renderIncompleteReview({
|
|
|
461
521
|
runUrl,
|
|
462
522
|
runtimeTools,
|
|
463
523
|
turnsUsed,
|
|
524
|
+
selectedFiles,
|
|
464
525
|
rendering: renderOptions,
|
|
465
526
|
status,
|
|
466
527
|
}) {
|
|
@@ -468,7 +529,7 @@ export function renderIncompleteReview({
|
|
|
468
529
|
const lines = [
|
|
469
530
|
'### ⚠️ AI Review Incomplete',
|
|
470
531
|
'',
|
|
471
|
-
'Claude did not return validated structured review output, so this workflow
|
|
532
|
+
'Claude did not return validated structured review output, so this workflow published deterministic hotspot context instead of raw scratch text.',
|
|
472
533
|
'',
|
|
473
534
|
`- Outcome: ${describeIncompleteOutcome({ reason, rendering, turnsUsed, status })}`,
|
|
474
535
|
];
|
|
@@ -484,6 +545,15 @@ export function renderIncompleteReview({
|
|
|
484
545
|
if (runtimeBudget) {
|
|
485
546
|
lines.push(`- Runtime budget: ${escapeMarkdownText(runtimeBudget)}`);
|
|
486
547
|
}
|
|
548
|
+
const hotspotFiles = formatHotspotFiles(selectedFiles || []);
|
|
549
|
+
if (hotspotFiles) {
|
|
550
|
+
lines.push(`- Hotspot files in this pass: ${hotspotFiles}`);
|
|
551
|
+
}
|
|
552
|
+
const remainingCoverage = formatRemainingCoverage(rendering);
|
|
553
|
+
if (remainingCoverage) {
|
|
554
|
+
lines.push(`- Remaining reviewable scope not fully covered: ${escapeMarkdownText(remainingCoverage)}`);
|
|
555
|
+
}
|
|
556
|
+
lines.push(`- Manual follow-up: ${escapeMarkdownText(formatFallbackFollowUp(rendering))}`);
|
|
487
557
|
if (runtimeTools?.length) {
|
|
488
558
|
lines.push(`- Runtime tools: ${runtimeTools.map(renderCode).join(', ')}`);
|
|
489
559
|
}
|
|
@@ -501,6 +571,7 @@ export function writeReviewFromEnv(env = process.env) {
|
|
|
501
571
|
const runUrl = env.AI_REVIEW_RUN_URL || '#';
|
|
502
572
|
const validation = normalizeStructuredOutput(env.AI_REVIEW_STRUCTURED_OUTPUT);
|
|
503
573
|
const metadata = readExecutionMetadata(env.AI_REVIEW_EXECUTION_FILE);
|
|
574
|
+
const selectedFiles = readSelectedFiles(env.AI_REVIEW_SCOPE_MANIFEST_FILE);
|
|
504
575
|
const status = cleanText(env.AI_REVIEW_STATUS).toLowerCase() || null;
|
|
505
576
|
const rendering = normalizeRenderingMetadata({
|
|
506
577
|
mode: env.AI_REVIEW_MODE,
|
|
@@ -521,6 +592,7 @@ export function writeReviewFromEnv(env = process.env) {
|
|
|
521
592
|
runUrl,
|
|
522
593
|
runtimeTools: metadata.runtimeTools,
|
|
523
594
|
turnsUsed: metadata.turnsUsed,
|
|
595
|
+
selectedFiles,
|
|
524
596
|
rendering,
|
|
525
597
|
status,
|
|
526
598
|
});
|
|
@@ -4,10 +4,14 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
|
|
5
5
|
const MODE_LIMITS = {
|
|
6
6
|
fast: { maxFiles: 16, maxChangedLines: 900, maxPatchLines: 90, maxPatchChars: 7000 },
|
|
7
|
-
triage: { maxFiles:
|
|
7
|
+
triage: { maxFiles: 6, maxChangedLines: 520, maxPatchLines: 60, maxPatchChars: 4500 },
|
|
8
8
|
deep: { maxFiles: 20, maxChangedLines: 1600, maxPatchLines: 120, maxPatchChars: 9000 },
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
const TRIAGE_SIZE_CLASS_LIMITS = {
|
|
12
|
+
xlarge: { maxFiles: 4, maxChangedLines: 360, maxPatchLines: 45, maxPatchChars: 3200 },
|
|
13
|
+
};
|
|
14
|
+
|
|
11
15
|
const MODE_LABELS = {
|
|
12
16
|
fast: 'diff-focused bounded review',
|
|
13
17
|
triage: 'hotspot-based bounded review (non-exhaustive)',
|
|
@@ -35,6 +39,13 @@ function cleanText(value) {
|
|
|
35
39
|
return typeof value === 'string' ? value.trim().replace(/\s+/g, ' ') : '';
|
|
36
40
|
}
|
|
37
41
|
|
|
42
|
+
function normalizeSizeClass(value) {
|
|
43
|
+
const sizeClass = cleanText(value).toLowerCase();
|
|
44
|
+
return sizeClass === 'small' || sizeClass === 'medium' || sizeClass === 'large' || sizeClass === 'xlarge'
|
|
45
|
+
? sizeClass
|
|
46
|
+
: null;
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
function escapeMarkdown(value) {
|
|
39
50
|
return cleanText(value).replace(/\\/g, '\\\\').replace(/([`*_{}[\]<>|])/g, '\\$1');
|
|
40
51
|
}
|
|
@@ -127,8 +138,17 @@ export function normalizePullFiles(files) {
|
|
|
127
138
|
}).map((file) => ({ ...file, score: scoreFile(file) }));
|
|
128
139
|
}
|
|
129
140
|
|
|
130
|
-
|
|
131
|
-
|
|
141
|
+
function resolveModeLimits(mode, sizeClass) {
|
|
142
|
+
if (mode !== 'triage') {
|
|
143
|
+
return MODE_LIMITS[mode] || MODE_LIMITS.fast;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return TRIAGE_SIZE_CLASS_LIMITS[sizeClass] || MODE_LIMITS.triage;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function buildReviewScope(files, mode, options = {}) {
|
|
150
|
+
const sizeClass = normalizeSizeClass(options.sizeClass);
|
|
151
|
+
const limits = resolveModeLimits(mode, sizeClass);
|
|
132
152
|
const reviewable = files.filter((file) => file.reviewable);
|
|
133
153
|
const lowSignal = files.filter((file) => !file.reviewable);
|
|
134
154
|
const usingChangedFallback = reviewable.length === 0;
|
|
@@ -260,6 +280,7 @@ export async function writeScopeFromEnv(env = process.env, request) {
|
|
|
260
280
|
const prNumber = Number.parseInt(cleanText(env.AI_REVIEW_PR_NUMBER), 10);
|
|
261
281
|
const baseRef = cleanText(env.AI_REVIEW_BASE_REF || 'dev');
|
|
262
282
|
const mode = cleanText(env.AI_REVIEW_MODE || 'fast').toLowerCase();
|
|
283
|
+
const sizeClass = normalizeSizeClass(env.AI_REVIEW_PR_SIZE_CLASS);
|
|
263
284
|
const turnBudget = Number.parseInt(cleanText(env.AI_REVIEW_MAX_TURNS || '0'), 10) || 0;
|
|
264
285
|
const timeoutMinutes = Number.parseInt(cleanText(env.AI_REVIEW_TIMEOUT_MINUTES || '0'), 10) || 0;
|
|
265
286
|
const outputFile = env.AI_REVIEW_SCOPE_FILE || '.ccs-ai-review-scope.md';
|
|
@@ -287,7 +308,7 @@ export async function writeScopeFromEnv(env = process.env, request) {
|
|
|
287
308
|
const files = normalizePullFiles(
|
|
288
309
|
await collectPullRequestFiles(`${apiUrl}/repos/${repository}/pulls/${prNumber}/files?per_page=100`, fetchPage)
|
|
289
310
|
);
|
|
290
|
-
const scope = buildReviewScope(files, mode);
|
|
311
|
+
const scope = buildReviewScope(files, mode, { sizeClass });
|
|
291
312
|
const markdown = renderReviewScope({ prNumber, baseRef, turnBudget, timeoutMinutes, scope });
|
|
292
313
|
|
|
293
314
|
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|