@kaitranntt/ccs 7.64.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.
Files changed (144) hide show
  1. package/README.md +2 -0
  2. package/dist/api/services/profile-types.d.ts +17 -0
  3. package/dist/api/services/profile-types.d.ts.map +1 -1
  4. package/dist/ccs.js +61 -6
  5. package/dist/ccs.js.map +1 -1
  6. package/dist/cliproxy/executor/env-resolver.d.ts +27 -0
  7. package/dist/cliproxy/executor/env-resolver.d.ts.map +1 -1
  8. package/dist/cliproxy/executor/env-resolver.js +87 -3
  9. package/dist/cliproxy/executor/env-resolver.js.map +1 -1
  10. package/dist/cliproxy/executor/index.d.ts.map +1 -1
  11. package/dist/cliproxy/executor/index.js +30 -0
  12. package/dist/cliproxy/executor/index.js.map +1 -1
  13. package/dist/cliproxy/model-catalog.d.ts +6 -0
  14. package/dist/cliproxy/model-catalog.d.ts.map +1 -1
  15. package/dist/cliproxy/model-catalog.js +38 -1
  16. package/dist/cliproxy/model-catalog.js.map +1 -1
  17. package/dist/cliproxy/proxy-config-resolver.d.ts +2 -1
  18. package/dist/cliproxy/proxy-config-resolver.d.ts.map +1 -1
  19. package/dist/cliproxy/proxy-config-resolver.js +1 -0
  20. package/dist/cliproxy/proxy-config-resolver.js.map +1 -1
  21. package/dist/cliproxy/proxy-target-resolver.d.ts +2 -0
  22. package/dist/cliproxy/proxy-target-resolver.d.ts.map +1 -1
  23. package/dist/cliproxy/proxy-target-resolver.js +3 -0
  24. package/dist/cliproxy/proxy-target-resolver.js.map +1 -1
  25. package/dist/cliproxy/remote-auth-fetcher.d.ts.map +1 -1
  26. package/dist/cliproxy/remote-auth-fetcher.js +89 -8
  27. package/dist/cliproxy/remote-auth-fetcher.js.map +1 -1
  28. package/dist/cliproxy/services/variant-settings.d.ts.map +1 -1
  29. package/dist/cliproxy/services/variant-settings.js +19 -4
  30. package/dist/cliproxy/services/variant-settings.js.map +1 -1
  31. package/dist/cliproxy/types.d.ts +2 -0
  32. package/dist/cliproxy/types.d.ts.map +1 -1
  33. package/dist/commands/config-image-analysis-command.d.ts.map +1 -1
  34. package/dist/commands/config-image-analysis-command.js +87 -1
  35. package/dist/commands/config-image-analysis-command.js.map +1 -1
  36. package/dist/config/unified-config-loader.d.ts.map +1 -1
  37. package/dist/config/unified-config-loader.js +9 -4
  38. package/dist/config/unified-config-loader.js.map +1 -1
  39. package/dist/config/unified-config-types.d.ts +4 -0
  40. package/dist/config/unified-config-types.d.ts.map +1 -1
  41. package/dist/config/unified-config-types.js +4 -2
  42. package/dist/config/unified-config-types.js.map +1 -1
  43. package/dist/copilot/copilot-executor.d.ts +13 -0
  44. package/dist/copilot/copilot-executor.d.ts.map +1 -1
  45. package/dist/copilot/copilot-executor.js +52 -2
  46. package/dist/copilot/copilot-executor.js.map +1 -1
  47. package/dist/management/checks/image-analysis-check.js +1 -1
  48. package/dist/management/checks/image-analysis-check.js.map +1 -1
  49. package/dist/shared/compatible-cli-contracts.d.ts +4 -0
  50. package/dist/shared/compatible-cli-contracts.d.ts.map +1 -1
  51. package/dist/types/config.d.ts +5 -0
  52. package/dist/types/config.d.ts.map +1 -1
  53. package/dist/types/config.js.map +1 -1
  54. package/dist/ui/assets/{accounts-D0UZf0nz.js → accounts-BHEYnq6b.js} +1 -1
  55. package/dist/ui/assets/{alert-dialog-Da7R-FpD.js → alert-dialog-D0EFRcfB.js} +1 -1
  56. package/dist/ui/assets/api-DhM3BYXr.js +4 -0
  57. package/dist/ui/assets/{auth-section-CH2_H6xn.js → auth-section-DVp8FQGm.js} +1 -1
  58. package/dist/ui/assets/{backups-section-C0k3svHF.js → backups-section-CRo0NZkA.js} +1 -1
  59. package/dist/ui/assets/channels-uZ_9CBqO.js +1 -0
  60. package/dist/ui/assets/checkbox-32DNqW_Q.js +1 -0
  61. package/dist/ui/assets/{claude-extension-CSnMQC_L.js → claude-extension-BfXlz5gV.js} +1 -1
  62. package/dist/ui/assets/cliproxy-DjNY9H-U.js +3 -0
  63. package/dist/ui/assets/{cliproxy-ai-providers-DdQZEqoN.js → cliproxy-ai-providers-5SHLMHiy.js} +5 -5
  64. package/dist/ui/assets/{cliproxy-control-panel-3Glps_oU.js → cliproxy-control-panel-Zax_m1AC.js} +1 -1
  65. package/dist/ui/assets/codex-CRUSpjsu.js +27 -0
  66. package/dist/ui/assets/{confirm-dialog-tjayxZcM.js → confirm-dialog-DVf5ZmCZ.js} +1 -1
  67. package/dist/ui/assets/copilot-BZrihl_Z.js +3 -0
  68. package/dist/ui/assets/cursor-BP4nbEk_.js +1 -0
  69. package/dist/ui/assets/{droid-C4qduLWT.js → droid-BG92rdM2.js} +2 -2
  70. package/dist/ui/assets/globalenv-section-Cf6dKgSf.js +1 -0
  71. package/dist/ui/assets/{health-ocBJ9xoH.js → health-BTy1UZs3.js} +1 -1
  72. package/dist/ui/assets/icons-CeH5899d.js +1 -0
  73. package/dist/ui/assets/index-B6SrL1O-.css +1 -0
  74. package/dist/ui/assets/index-BVeN0dIB.js +1 -0
  75. package/dist/ui/assets/{index-B3D2Pi4S.js → index-Corv1lSo.js} +32 -32
  76. package/dist/ui/assets/{index-BkXZkwWB.js → index-DHrTq-0n.js} +1 -1
  77. package/dist/ui/assets/index-DuRYaONg.js +1 -0
  78. package/dist/ui/assets/index-N2ZSJurX.js +1 -0
  79. package/dist/ui/assets/index-wg7UtkFv.js +1 -0
  80. package/dist/ui/assets/{masked-input-XGpOJSYQ.js → masked-input-DX9bedLy.js} +1 -1
  81. package/dist/ui/assets/{proxy-status-widget-DwizqfDs.js → proxy-status-widget-DVDMuZK5.js} +1 -1
  82. package/dist/ui/assets/{radix-ui-Dt3edmE5.js → radix-ui-C98W0NRG.js} +1 -1
  83. package/dist/ui/assets/{raw-json-settings-editor-panel-DLf0iq8r.js → raw-json-settings-editor-panel-Dkt5E6Z_.js} +1 -1
  84. package/dist/ui/assets/{searchable-select-C2N5ZoC7.js → searchable-select-BP3Q1-Yn.js} +1 -1
  85. package/dist/ui/assets/separator-BLGGUlh9.js +1 -0
  86. package/dist/ui/assets/{shared-CbpV9mUS.js → shared-G0XRyLig.js} +1 -1
  87. package/dist/ui/assets/{table-CkgTbRUO.js → table-B4lRrWC-.js} +1 -1
  88. package/dist/ui/assets/{tanstack-CkjseTWE.js → tanstack-CfKik0yL.js} +1 -1
  89. package/dist/ui/assets/{updates-HsQEuJIR.js → updates--A2Sdo7N.js} +1 -1
  90. package/dist/ui/index.html +5 -5
  91. package/dist/utils/hooks/get-image-analysis-hook-env.d.ts +3 -2
  92. package/dist/utils/hooks/get-image-analysis-hook-env.d.ts.map +1 -1
  93. package/dist/utils/hooks/get-image-analysis-hook-env.js +15 -6
  94. package/dist/utils/hooks/get-image-analysis-hook-env.js.map +1 -1
  95. package/dist/utils/hooks/image-analysis-backend-resolver.d.ts +53 -0
  96. package/dist/utils/hooks/image-analysis-backend-resolver.d.ts.map +1 -0
  97. package/dist/utils/hooks/image-analysis-backend-resolver.js +376 -0
  98. package/dist/utils/hooks/image-analysis-backend-resolver.js.map +1 -0
  99. package/dist/utils/hooks/image-analysis-runtime-status.d.ts +17 -0
  100. package/dist/utils/hooks/image-analysis-runtime-status.d.ts.map +1 -0
  101. package/dist/utils/hooks/image-analysis-runtime-status.js +132 -0
  102. package/dist/utils/hooks/image-analysis-runtime-status.js.map +1 -0
  103. package/dist/utils/hooks/image-analyzer-profile-hook-injector.d.ts +6 -5
  104. package/dist/utils/hooks/image-analyzer-profile-hook-injector.d.ts.map +1 -1
  105. package/dist/utils/hooks/image-analyzer-profile-hook-injector.js +37 -17
  106. package/dist/utils/hooks/image-analyzer-profile-hook-injector.js.map +1 -1
  107. package/dist/utils/hooks/index.d.ts +2 -0
  108. package/dist/utils/hooks/index.d.ts.map +1 -1
  109. package/dist/utils/hooks/index.js +8 -1
  110. package/dist/utils/hooks/index.js.map +1 -1
  111. package/dist/web-server/index.d.ts.map +1 -1
  112. package/dist/web-server/index.js +6 -1
  113. package/dist/web-server/index.js.map +1 -1
  114. package/dist/web-server/routes/image-analysis-routes.d.ts +3 -0
  115. package/dist/web-server/routes/image-analysis-routes.d.ts.map +1 -0
  116. package/dist/web-server/routes/image-analysis-routes.js +362 -0
  117. package/dist/web-server/routes/image-analysis-routes.js.map +1 -0
  118. package/dist/web-server/routes/index.d.ts.map +1 -1
  119. package/dist/web-server/routes/index.js +2 -0
  120. package/dist/web-server/routes/index.js.map +1 -1
  121. package/dist/web-server/routes/settings-routes.d.ts.map +1 -1
  122. package/dist/web-server/routes/settings-routes.js +67 -5
  123. package/dist/web-server/routes/settings-routes.js.map +1 -1
  124. package/dist/web-server/services/codex-dashboard-service.d.ts.map +1 -1
  125. package/dist/web-server/services/codex-dashboard-service.js +14 -0
  126. package/dist/web-server/services/codex-dashboard-service.js.map +1 -1
  127. package/package.json +2 -2
  128. package/scripts/github/normalize-ai-review-output.mjs +305 -17
  129. package/scripts/github/prepare-ai-review-scope.mjs +338 -0
  130. package/dist/ui/assets/api-DpKfrsn2.js +0 -4
  131. package/dist/ui/assets/channels-Cpxagv1M.js +0 -1
  132. package/dist/ui/assets/checkbox-B_ZY6YZW.js +0 -1
  133. package/dist/ui/assets/cliproxy-D9B-TLY3.js +0 -3
  134. package/dist/ui/assets/codex-xj_XJVjB.js +0 -27
  135. package/dist/ui/assets/copilot-DQ5Dr5Ff.js +0 -3
  136. package/dist/ui/assets/cursor-DtFjK37Z.js +0 -1
  137. package/dist/ui/assets/globalenv-section-B2YBzWoz.js +0 -1
  138. package/dist/ui/assets/icons-Cn04FDSc.js +0 -1
  139. package/dist/ui/assets/index-1TKCj6WA.js +0 -1
  140. package/dist/ui/assets/index-9Qv0NQVB.js +0 -1
  141. package/dist/ui/assets/index-C9UUemEw.js +0 -1
  142. package/dist/ui/assets/index-DGQOg_8Q.css +0 -1
  143. package/dist/ui/assets/separator-CuY8SIhQ.js +0 -1
  144. package/dist/ui/assets/switch-Dz4ptkeA.js +0 -1
@@ -21,6 +21,12 @@ const STATUS_LABELS = {
21
21
  na: 'N/A',
22
22
  };
23
23
 
24
+ const REVIEW_MODE_DETAILS = {
25
+ fast: 'diff-focused bounded review',
26
+ triage: 'hotspot-based bounded review (non-exhaustive)',
27
+ deep: 'expanded surrounding-code review',
28
+ };
29
+
24
30
  const RENDERER_OWNED_MARKUP_PATTERNS = [
25
31
  { pattern: /^#{1,6}\s/u, reason: 'markdown heading' },
26
32
  { pattern: /^\s*Verdict\s*:/iu, reason: 'verdict label' },
@@ -44,6 +50,171 @@ function renderCode(value) {
44
50
  return `${fence}${text}${fence}`;
45
51
  }
46
52
 
53
+ function parsePositiveInteger(value) {
54
+ if (value === null || value === undefined || value === '') {
55
+ return null;
56
+ }
57
+
58
+ const parsed = typeof value === 'number' ? value : Number.parseInt(cleanText(value), 10);
59
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
60
+ }
61
+
62
+ function normalizeReviewMode(value) {
63
+ const mode = cleanText(value).toLowerCase();
64
+ return REVIEW_MODE_DETAILS[mode] ? mode : null;
65
+ }
66
+
67
+ function normalizeRenderingMetadata(raw) {
68
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
69
+ return {};
70
+ }
71
+
72
+ const mode = normalizeReviewMode(raw.mode);
73
+ const maxTurns = parsePositiveInteger(raw.maxTurns);
74
+ const timeoutMinutes = parsePositiveInteger(raw.timeoutMinutes);
75
+ const timeoutSeconds = parsePositiveInteger(raw.timeoutSeconds);
76
+ const selectedFiles = parsePositiveInteger(raw.selectedFiles);
77
+ const reviewableFiles = parsePositiveInteger(raw.reviewableFiles);
78
+ const selectedChanges = parsePositiveInteger(raw.selectedChanges);
79
+ const reviewableChanges = parsePositiveInteger(raw.reviewableChanges);
80
+ const scopeLabel = cleanText(raw.scopeLabel).toLowerCase();
81
+ const metadata = {};
82
+
83
+ if (mode) metadata.mode = mode;
84
+ if (maxTurns) metadata.maxTurns = maxTurns;
85
+ if (timeoutMinutes) metadata.timeoutMinutes = timeoutMinutes;
86
+ if (timeoutSeconds) metadata.timeoutSeconds = timeoutSeconds;
87
+ if (selectedFiles) metadata.selectedFiles = selectedFiles;
88
+ if (reviewableFiles) metadata.reviewableFiles = reviewableFiles;
89
+ if (selectedChanges) metadata.selectedChanges = selectedChanges;
90
+ if (reviewableChanges) metadata.reviewableChanges = reviewableChanges;
91
+ if (scopeLabel === 'reviewable files' || scopeLabel === 'changed files') metadata.scopeLabel = scopeLabel;
92
+
93
+ return metadata;
94
+ }
95
+
96
+ function mergeRenderingMetadata(...sources) {
97
+ const merged = {};
98
+ for (const source of sources) {
99
+ Object.assign(merged, normalizeRenderingMetadata(source));
100
+ }
101
+ return merged;
102
+ }
103
+
104
+ function formatTurnBudget(rendering) {
105
+ return typeof rendering.maxTurns === 'number' ? `${rendering.maxTurns} turns` : null;
106
+ }
107
+
108
+ function formatTimeBudget(rendering) {
109
+ if (typeof rendering.timeoutMinutes === 'number') {
110
+ return `${rendering.timeoutMinutes} minute${rendering.timeoutMinutes === 1 ? '' : 's'}`;
111
+ }
112
+
113
+ if (typeof rendering.timeoutSeconds === 'number') {
114
+ return `${rendering.timeoutSeconds} second${rendering.timeoutSeconds === 1 ? '' : 's'}`;
115
+ }
116
+
117
+ return null;
118
+ }
119
+
120
+ function formatCombinedBudget(rendering) {
121
+ const parts = [formatTurnBudget(rendering), formatTimeBudget(rendering)].filter(Boolean);
122
+ return parts.length > 0 ? parts.join(' / ') : null;
123
+ }
124
+
125
+ function formatScopeSummary(rendering) {
126
+ if (
127
+ typeof rendering.selectedFiles !== 'number' ||
128
+ typeof rendering.reviewableFiles !== 'number'
129
+ ) {
130
+ return null;
131
+ }
132
+
133
+ const scopeLabel = rendering.scopeLabel || 'reviewable files';
134
+ const fileScope = `${rendering.selectedFiles}/${rendering.reviewableFiles} ${scopeLabel}`;
135
+ if (
136
+ typeof rendering.selectedChanges === 'number' &&
137
+ typeof rendering.reviewableChanges === 'number'
138
+ ) {
139
+ const changeLabel = scopeLabel === 'reviewable files' ? 'reviewable changed lines' : 'changed lines';
140
+ return `${fileScope}; ${rendering.selectedChanges}/${rendering.reviewableChanges} ${changeLabel}`;
141
+ }
142
+
143
+ return fileScope;
144
+ }
145
+
146
+ function formatReviewContext(rendering) {
147
+ const parts = [];
148
+
149
+ if (rendering.mode) {
150
+ parts.push(`mode ${renderCode(rendering.mode)}`);
151
+ parts.push(REVIEW_MODE_DETAILS[rendering.mode]);
152
+ }
153
+
154
+ const scopeSummary = formatScopeSummary(rendering);
155
+ if (scopeSummary) {
156
+ parts.push(`scope ${scopeSummary}`);
157
+ }
158
+
159
+ const turnBudget = formatTurnBudget(rendering);
160
+ if (turnBudget) {
161
+ parts.push(`turn budget ${turnBudget}`);
162
+ }
163
+
164
+ const timeBudget = formatTimeBudget(rendering);
165
+ if (timeBudget) {
166
+ parts.push(`workflow cap ${timeBudget}`);
167
+ }
168
+
169
+ if (parts.length === 0) {
170
+ return null;
171
+ }
172
+
173
+ return `> 🧭 Review context: ${parts.join('; ')}.`;
174
+ }
175
+
176
+ function classifyFallbackReason(reason) {
177
+ const normalized = cleanText(reason).toLowerCase();
178
+ if (!normalized || normalized === 'missing structured output') {
179
+ return 'missing';
180
+ }
181
+
182
+ if (normalized === 'structured output is not valid json') {
183
+ return 'invalid_json';
184
+ }
185
+
186
+ return 'invalid_fields';
187
+ }
188
+
189
+ function describeIncompleteOutcome({ reason, rendering, turnsUsed, status }) {
190
+ const reviewLabel = rendering.mode ? `${renderCode(rendering.mode)} review` : 'bounded review';
191
+ const turnBudget = formatTurnBudget(rendering);
192
+ const timeBudget = formatTimeBudget(rendering);
193
+ const combinedBudget = formatCombinedBudget(rendering);
194
+ const exhaustedTurnBudget =
195
+ typeof turnsUsed === 'number' &&
196
+ typeof rendering.maxTurns === 'number' &&
197
+ turnsUsed >= rendering.maxTurns;
198
+
199
+ if (status === 'cancelled' && timeBudget) {
200
+ return `The ${reviewLabel} hit the workflow runtime cap before it produced validated structured output. The run stayed bounded to ${timeBudget}.`;
201
+ }
202
+
203
+ if (exhaustedTurnBudget) {
204
+ return `The ${reviewLabel} reached its ${rendering.maxTurns}-turn runtime budget before it produced validated structured output.`;
205
+ }
206
+
207
+ if (combinedBudget && classifyFallbackReason(reason) === 'missing') {
208
+ return `The ${reviewLabel} ended before it could produce validated structured output within the available ${combinedBudget} runtime budget.`;
209
+ }
210
+
211
+ if (classifyFallbackReason(reason) === 'missing' || classifyFallbackReason(reason) === 'invalid_json') {
212
+ return `The ${reviewLabel} ended without validated structured output, so the normalizer published the safe fallback comment instead.`;
213
+ }
214
+
215
+ return `The ${reviewLabel} returned incomplete structured data, so the normalizer published the safe fallback comment instead.`;
216
+ }
217
+
47
218
  function validatePlainTextField(fieldName, value) {
48
219
  const text = cleanText(value);
49
220
  if (!text) {
@@ -119,6 +290,66 @@ function readExecutionMetadata(executionFile) {
119
290
  }
120
291
  }
121
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
+
122
353
  export function normalizeStructuredOutput(raw) {
123
354
  if (!raw) {
124
355
  return { ok: false, reason: 'missing structured output' };
@@ -155,6 +386,8 @@ export function normalizeStructuredOutput(raw) {
155
386
  const strengths = normalizeStringList('strengths', parsed.strengths);
156
387
  if (!strengths.ok) return strengths;
157
388
 
389
+ const rendering = normalizeRenderingMetadata(parsed.rendering);
390
+
158
391
  if (!ASSESSMENTS[overallAssessment] || findings === null) {
159
392
  return { ok: false, reason: 'structured output is missing required review fields' };
160
393
  }
@@ -203,19 +436,22 @@ export function normalizeStructuredOutput(raw) {
203
436
  });
204
437
  }
205
438
 
206
- return {
207
- ok: true,
208
- value: {
209
- summary: summary.value,
210
- findings: normalizedFindings,
211
- overallAssessment,
212
- overallRationale: overallRationale.value,
213
- securityChecklist: securityChecklist.value,
214
- ccsCompliance: ccsCompliance.value,
215
- informational: informational.value,
216
- strengths: strengths.value,
217
- },
439
+ const value = {
440
+ summary: summary.value,
441
+ findings: normalizedFindings,
442
+ overallAssessment,
443
+ overallRationale: overallRationale.value,
444
+ securityChecklist: securityChecklist.value,
445
+ ccsCompliance: ccsCompliance.value,
446
+ informational: informational.value,
447
+ strengths: strengths.value,
218
448
  };
449
+
450
+ if (Object.keys(rendering).length > 0) {
451
+ value.rendering = rendering;
452
+ }
453
+
454
+ return { ok: true, value };
219
455
  }
220
456
 
221
457
  function renderChecklistTable(title, labelHeader, labelKey, rows) {
@@ -233,8 +469,14 @@ function renderBulletSection(title, items) {
233
469
  return ['', title, ...items.map((item) => `- ${escapeMarkdownText(item)}`)];
234
470
  }
235
471
 
236
- export function renderStructuredReview(review, { model }) {
472
+ export function renderStructuredReview(review, { model, rendering: renderOptions } = {}) {
473
+ const rendering = mergeRenderingMetadata(review?.rendering, renderOptions);
237
474
  const lines = ['### 📋 Summary', '', escapeMarkdownText(review.summary), '', '### 🔍 Findings'];
475
+ const reviewContext = formatReviewContext(rendering);
476
+
477
+ if (reviewContext) {
478
+ lines.splice(4, 0, reviewContext, '');
479
+ }
238
480
 
239
481
  if (review.findings.length === 0) {
240
482
  lines.push('No confirmed issues found after reviewing the diff and surrounding code.');
@@ -273,15 +515,45 @@ export function renderStructuredReview(review, { model }) {
273
515
  return lines.join('\n');
274
516
  }
275
517
 
276
- export function renderIncompleteReview({ model, reason, runUrl, runtimeTools, turnsUsed }) {
518
+ export function renderIncompleteReview({
519
+ model,
520
+ reason,
521
+ runUrl,
522
+ runtimeTools,
523
+ turnsUsed,
524
+ selectedFiles,
525
+ rendering: renderOptions,
526
+ status,
527
+ }) {
528
+ const rendering = mergeRenderingMetadata(renderOptions);
277
529
  const lines = [
278
530
  '### ⚠️ AI Review Incomplete',
279
531
  '',
280
- 'Claude did not return validated structured review output, so this workflow did not publish raw scratch text.',
532
+ 'Claude did not return validated structured review output, so this workflow published deterministic hotspot context instead of raw scratch text.',
281
533
  '',
282
- `- Reason: ${escapeMarkdownText(reason)}`,
534
+ `- Outcome: ${describeIncompleteOutcome({ reason, rendering, turnsUsed, status })}`,
283
535
  ];
284
536
 
537
+ if (rendering.mode) {
538
+ lines.push(`- Review mode: ${renderCode(rendering.mode)} (${escapeMarkdownText(REVIEW_MODE_DETAILS[rendering.mode])})`);
539
+ }
540
+ const scopeSummary = formatScopeSummary(rendering);
541
+ if (scopeSummary) {
542
+ lines.push(`- Review scope: ${escapeMarkdownText(scopeSummary)}`);
543
+ }
544
+ const runtimeBudget = formatCombinedBudget(rendering);
545
+ if (runtimeBudget) {
546
+ lines.push(`- Runtime budget: ${escapeMarkdownText(runtimeBudget)}`);
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))}`);
285
557
  if (runtimeTools?.length) {
286
558
  lines.push(`- Runtime tools: ${runtimeTools.map(renderCode).join(', ')}`);
287
559
  }
@@ -299,14 +571,30 @@ export function writeReviewFromEnv(env = process.env) {
299
571
  const runUrl = env.AI_REVIEW_RUN_URL || '#';
300
572
  const validation = normalizeStructuredOutput(env.AI_REVIEW_STRUCTURED_OUTPUT);
301
573
  const metadata = readExecutionMetadata(env.AI_REVIEW_EXECUTION_FILE);
574
+ const selectedFiles = readSelectedFiles(env.AI_REVIEW_SCOPE_MANIFEST_FILE);
575
+ const status = cleanText(env.AI_REVIEW_STATUS).toLowerCase() || null;
576
+ const rendering = normalizeRenderingMetadata({
577
+ mode: env.AI_REVIEW_MODE,
578
+ selectedFiles: env.AI_REVIEW_SELECTED_FILES,
579
+ reviewableFiles: env.AI_REVIEW_REVIEWABLE_FILES,
580
+ selectedChanges: env.AI_REVIEW_SELECTED_CHANGES,
581
+ reviewableChanges: env.AI_REVIEW_REVIEWABLE_CHANGES,
582
+ scopeLabel: env.AI_REVIEW_SCOPE_LABEL,
583
+ maxTurns: env.AI_REVIEW_MAX_TURNS,
584
+ timeoutMinutes: env.AI_REVIEW_TIMEOUT_MINUTES ?? env.AI_REVIEW_TIMEOUT_MINUTES_BUDGET,
585
+ timeoutSeconds: env.AI_REVIEW_TIMEOUT_SECONDS ?? env.AI_REVIEW_TIMEOUT_SEC,
586
+ });
302
587
  const content = validation.ok
303
- ? renderStructuredReview(validation.value, { model })
588
+ ? renderStructuredReview(validation.value, { model, rendering })
304
589
  : renderIncompleteReview({
305
590
  model,
306
591
  reason: validation.reason,
307
592
  runUrl,
308
593
  runtimeTools: metadata.runtimeTools,
309
594
  turnsUsed: metadata.turnsUsed,
595
+ selectedFiles,
596
+ rendering,
597
+ status,
310
598
  });
311
599
 
312
600
  fs.mkdirSync(path.dirname(outputFile), { recursive: true });