@ganakailabs/cloudeval-cli 0.27.1 → 0.27.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.
@@ -38,10 +38,10 @@ import {
38
38
  } from "./chunk-USSCB2ZU.js";
39
39
  import {
40
40
  Banner
41
- } from "./chunk-LWHSK5SO.js";
41
+ } from "./chunk-3DBCT463.js";
42
42
  import {
43
43
  CLI_VERSION
44
- } from "./chunk-FJDUSS2S.js";
44
+ } from "./chunk-SLRYBS3P.js";
45
45
  import {
46
46
  raisedButtonStyle,
47
47
  terminalTheme
@@ -3,8 +3,8 @@ import {
3
3
  bannerMetaColor,
4
4
  bannerSegmentColor,
5
5
  splitBannerLineSegments
6
- } from "./chunk-LWHSK5SO.js";
7
- import "./chunk-FJDUSS2S.js";
6
+ } from "./chunk-3DBCT463.js";
7
+ import "./chunk-SLRYBS3P.js";
8
8
  import "./chunk-ZDKRIOMB.js";
9
9
  export {
10
10
  Banner,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CLI_VERSION
3
- } from "./chunk-FJDUSS2S.js";
3
+ } from "./chunk-SLRYBS3P.js";
4
4
  import {
5
5
  shouldUseColor,
6
6
  terminalTheme
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var CLI_VERSION = "0.27.1";
2
+ var CLI_VERSION = "0.27.2";
3
3
 
4
4
  export {
5
5
  CLI_VERSION
package/dist/cli.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  } from "./chunk-USSCB2ZU.js";
40
40
  import {
41
41
  CLI_VERSION
42
- } from "./chunk-FJDUSS2S.js";
42
+ } from "./chunk-SLRYBS3P.js";
43
43
 
44
44
  // src/runtime/prepareInk.ts
45
45
  import fs from "fs";
@@ -2506,6 +2506,14 @@ var firstRecord = (...values) => {
2506
2506
  }
2507
2507
  return void 0;
2508
2508
  };
2509
+ var firstArray = (...values) => {
2510
+ for (const value of values) {
2511
+ if (Array.isArray(value)) {
2512
+ return value;
2513
+ }
2514
+ }
2515
+ return void 0;
2516
+ };
2509
2517
  var entriesAsNamedRecords = (value, amountKey = "amount") => {
2510
2518
  const record = firstRecord(value);
2511
2519
  if (!record) {
@@ -2660,21 +2668,6 @@ var statusIcon = (status) => {
2660
2668
  return "\u26AA";
2661
2669
  }
2662
2670
  };
2663
- var formatValidation = (validation) => {
2664
- const policyFailed = numberFrom(validation?.policyChecks?.failed);
2665
- const unitFailed = numberFrom(validation?.unitTests?.failed);
2666
- if (policyFailed === 0 && unitFailed === 0) {
2667
- return "Validation clean";
2668
- }
2669
- const parts = [];
2670
- if (unitFailed !== void 0) {
2671
- parts.push(unitFailed === 0 ? "unit tests clean" : `${unitFailed} unit tests failed`);
2672
- }
2673
- if (policyFailed !== void 0) {
2674
- parts.push(policyFailed === 0 ? "policy checks clean" : `${policyFailed} policy checks failed`);
2675
- }
2676
- return parts.length ? parts.join(", ") : "Validation not available";
2677
- };
2678
2671
  var validationSummaryLine = (validation) => {
2679
2672
  const unitFailed = numberFrom(validation?.unitTests?.failed);
2680
2673
  if (unitFailed === void 0) {
@@ -2716,6 +2709,135 @@ var validationDetailLines = (validation) => {
2716
2709
  }
2717
2710
  return lines.length ? lines : ["- Validation data was not available."];
2718
2711
  };
2712
+ var failedValidationRecords = (records) => (Array.isArray(records) ? records : []).map(asRecord).filter((record) => {
2713
+ const status = String(
2714
+ record.status ?? record.outcome ?? record.result ?? record.state ?? ""
2715
+ ).toLowerCase();
2716
+ if (record.passed === false || record.success === false) {
2717
+ return true;
2718
+ }
2719
+ return ["fail", "failed", "error", "critical"].includes(status);
2720
+ });
2721
+ var severityIcon = (severity) => {
2722
+ switch (String(severity ?? "").toLowerCase()) {
2723
+ case "critical":
2724
+ case "error":
2725
+ case "high":
2726
+ case "fail":
2727
+ case "failed":
2728
+ return "\u{1F534}";
2729
+ case "warning":
2730
+ case "warn":
2731
+ case "medium":
2732
+ return "\u{1F7E1}";
2733
+ case "info":
2734
+ case "low":
2735
+ return "\u{1F535}";
2736
+ default:
2737
+ return "\u26AA";
2738
+ }
2739
+ };
2740
+ var compactMarkdownCell = (value, fallback = "not available") => {
2741
+ const text = String(value ?? "").replace(/\s+/g, " ").replace(/\|/g, "\\|").trim();
2742
+ return text || fallback;
2743
+ };
2744
+ var failureName = (record, fallback) => compactMarkdownCell(
2745
+ record.test_name ?? record.testName ?? record.rule_name ?? record.ruleName ?? record.name ?? record.title ?? record.id ?? fallback
2746
+ );
2747
+ var failureLocation = (record) => compactMarkdownCell(
2748
+ record.file_path ?? record.filePath ?? record.path ?? record.resource_id ?? record.resourceId ?? record.resource ?? record.target,
2749
+ "-"
2750
+ );
2751
+ var failureWhy = (record) => {
2752
+ const message = compactMarkdownCell(
2753
+ record.message ?? record.reason ?? record.description ?? record.details,
2754
+ ""
2755
+ );
2756
+ const recommendation = compactMarkdownCell(
2757
+ record.recommendation ?? record.remediation ?? record.fix ?? record.next_step,
2758
+ ""
2759
+ );
2760
+ if (message && recommendation && message !== recommendation) {
2761
+ return `${message} ${recommendation}`;
2762
+ }
2763
+ return message || recommendation || "No failure reason was included in the report payload.";
2764
+ };
2765
+ var validationFailureRows = (validation) => {
2766
+ const rows = [];
2767
+ const unitFailures = (Array.isArray(validation?.unitTests?.failures) ? validation?.unitTests?.failures : []).map(asRecord).slice(0, 5);
2768
+ const policyFailures = (Array.isArray(validation?.policyChecks?.failures) ? validation?.policyChecks?.failures : []).map(asRecord).slice(0, 5);
2769
+ const groupedFailures = [
2770
+ ["Unit test", unitFailures],
2771
+ ["Policy check", policyFailures]
2772
+ ];
2773
+ for (const [kind, failures] of groupedFailures) {
2774
+ failures.forEach((failure, index) => {
2775
+ const severity = compactMarkdownCell(
2776
+ failure.severity ?? failure.status ?? failure.outcome,
2777
+ "failed"
2778
+ );
2779
+ rows.push(
2780
+ `| ${kind} | ${failureName(failure, `${kind} ${index + 1}`)} | \`${failureLocation(failure)}\` | ${severityIcon(severity)} ${severity} | ${failureWhy(failure)} |`
2781
+ );
2782
+ });
2783
+ }
2784
+ return rows;
2785
+ };
2786
+ var architectureSignalLines = ({
2787
+ architecture,
2788
+ costServices,
2789
+ costCurrency,
2790
+ highRiskFindings,
2791
+ pillars
2792
+ }) => {
2793
+ const lines = [];
2794
+ const resourceCount = numberFrom(architecture?.resources);
2795
+ const resourceTypeCount = numberFrom(architecture?.resourceTypes);
2796
+ const relationshipCount = numberFrom(architecture?.relationships);
2797
+ const density = numberFrom(architecture?.relationshipDensity);
2798
+ if (resourceCount !== void 0 && resourceTypeCount !== void 0) {
2799
+ lines.push(
2800
+ `- Scale: **${displayNumber(resourceCount)} resources** across **${displayNumber(resourceTypeCount)} resource types**`
2801
+ );
2802
+ } else if (resourceCount !== void 0) {
2803
+ lines.push(`- Scale: **${displayNumber(resourceCount)} resources**`);
2804
+ }
2805
+ if (relationshipCount !== void 0) {
2806
+ const densityText = density !== void 0 ? ` (**${trimNumber(density, 2)} per resource**)` : "";
2807
+ const shape = density !== void 0 && density < 0.5 ? "; sparse dependency graph, review isolated resources and missing links" : density !== void 0 && density > 2 ? "; dense dependency graph, review blast radius before changes" : "";
2808
+ lines.push(
2809
+ `- Dependency shape: **${displayNumber(relationshipCount)} relationships**${densityText}${shape}`
2810
+ );
2811
+ }
2812
+ const highRisk = numberFrom(highRiskFindings);
2813
+ const weakestPillar = pillars.map((pillar) => ({
2814
+ label: String(pillar.label ?? pillar.id ?? "pillar"),
2815
+ score: numberFrom(pillar.score)
2816
+ })).filter((pillar) => pillar.score !== void 0).sort((left, right) => left.score - right.score)[0];
2817
+ if (highRisk !== void 0 || weakestPillar) {
2818
+ const parts = [];
2819
+ if (highRisk !== void 0) {
2820
+ parts.push(`**${displayNumber(highRisk)} high-risk findings**`);
2821
+ }
2822
+ if (weakestPillar) {
2823
+ const rating = scoreRating(weakestPillar.score);
2824
+ parts.push(
2825
+ `weakest pillar **${weakestPillar.label} ${formatScore(weakestPillar.score)} (${rating ?? "UNKNOWN"})**`
2826
+ );
2827
+ }
2828
+ lines.push(`- Risk concentration: ${parts.join("; ")}`);
2829
+ }
2830
+ const topCostDrivers = costServices.filter((service) => service.amount > 0).sort((left, right) => right.amount - left.amount).slice(0, 2);
2831
+ if (topCostDrivers.length) {
2832
+ const total = topCostDrivers.reduce((sum, service) => sum + service.amount, 0);
2833
+ lines.push(
2834
+ `- Cost drivers: ${joinReadableList(
2835
+ topCostDrivers.map((service) => `**${service.name}**`)
2836
+ )} account for **${formatMonthlyMoney(total, costCurrency ?? topCostDrivers[0]?.currency)}**`
2837
+ );
2838
+ }
2839
+ return lines;
2840
+ };
2719
2841
  var namedAmount = (record) => numberFrom(
2720
2842
  record.amount,
2721
2843
  record.monthly_cost,
@@ -2725,6 +2847,15 @@ var namedAmount = (record) => numberFrom(
2725
2847
  );
2726
2848
  var namedLabel = (record, fallback) => String(record.name ?? record.service ?? record.label ?? record.category ?? fallback);
2727
2849
  var mermaidLabel = (value) => value.replace(/"/g, "'");
2850
+ var joinReadableList = (values) => {
2851
+ if (values.length <= 1) {
2852
+ return values[0] ?? "";
2853
+ }
2854
+ if (values.length === 2) {
2855
+ return `${values[0]} and ${values[1]}`;
2856
+ }
2857
+ return `${values.slice(0, -1).join(", ")}, and ${values[values.length - 1]}`;
2858
+ };
2728
2859
  var costServiceRows = (services, currency) => (Array.isArray(services) ? services : []).map((service, index) => {
2729
2860
  const record = asRecord(service);
2730
2861
  const amount = namedAmount(record);
@@ -2818,19 +2949,41 @@ var extractValidation = ({
2818
2949
  preload?.reports?.unit_tests?.metrics,
2819
2950
  project?.status?.unit_tests
2820
2951
  );
2952
+ const unitFailures = failedValidationRecords(
2953
+ firstArray(
2954
+ preload?.latest_payloads?.unit_tests?.test_results,
2955
+ preload?.latest_payloads?.unit_tests?.results,
2956
+ preload?.reports?.unit_tests?.metrics?.test_results,
2957
+ preload?.reports?.unit_tests?.metrics?.results,
2958
+ project?.status?.unit_tests?.test_results,
2959
+ project?.status?.unit_tests?.results
2960
+ )
2961
+ );
2962
+ const policyFailures = failedValidationRecords(
2963
+ firstArray(
2964
+ policySummary?.results,
2965
+ policySummary?.checks,
2966
+ waf?.parsed?.validation_results,
2967
+ waf?.parsed?.rules,
2968
+ waf?.raw?.rules,
2969
+ failedRules
2970
+ )
2971
+ );
2821
2972
  return {
2822
2973
  policyChecks: {
2823
2974
  total: numberFrom(policySummary?.total_rules, policySummary?.totalRules, rules.length),
2824
2975
  passed: numberFrom(policySummary?.passed_rules, policySummary?.passedRules),
2825
2976
  failed: numberFrom(policySummary?.failed_rules, policySummary?.failedRules, failedRules.length),
2826
- errors: numberFrom(policySummary?.error_rules, policySummary?.errorRules)
2977
+ errors: numberFrom(policySummary?.error_rules, policySummary?.errorRules),
2978
+ failures: policyFailures.slice(0, 5)
2827
2979
  },
2828
2980
  unitTests: {
2829
2981
  status: unitSummary?.status,
2830
2982
  total: numberFrom(unitSummary?.total_tests, unitSummary?.totalTests),
2831
2983
  passed: numberFrom(unitSummary?.passed_tests, unitSummary?.passedTests),
2832
2984
  failed: numberFrom(unitSummary?.failed_tests, unitSummary?.failedTests),
2833
- skipped: numberFrom(unitSummary?.skipped_tests, unitSummary?.skippedTests)
2985
+ skipped: numberFrom(unitSummary?.skipped_tests, unitSummary?.skippedTests),
2986
+ failures: unitFailures.slice(0, 5)
2834
2987
  }
2835
2988
  };
2836
2989
  };
@@ -3185,81 +3338,8 @@ var waitForReviewReports = async ({
3185
3338
  }
3186
3339
  return latest;
3187
3340
  };
3188
- var buildAiSummaryPrompt = (data) => [
3189
- "Write a concise CloudEval pull request review summary in Markdown.",
3190
- "Return exactly two sections: Short Summary and Details.",
3191
- "Short Summary: one dense paragraph under 45 words with gate status, Well-Architected posture, validation, and cost.",
3192
- "Details: short bullets with bold labels only when useful, such as **Key risks:**, **Cost posture:**, and **Recommended next step:**.",
3193
- "Keep the full response under 180 words. Do not invent facts not present below.",
3194
- "Do not include citations, source markers, hidden tool ids, or HTML comments.",
3195
- "",
3196
- `Project: ${data.projectId}`,
3197
- `Repository: ${data.repo ?? "unknown"}`,
3198
- `Ref: ${data.ref ?? "unknown"}`,
3199
- `Commit: ${data.commitSha ?? "unknown"}`,
3200
- `Gate: ${String(data.gate?.status ?? "unknown").toUpperCase()}`,
3201
- `Well-Architected score: ${data.gate?.wellArchitected?.overall?.score ?? data.gate?.overallScore ?? "unknown"}`,
3202
- `High-risk findings: ${data.gate?.wellArchitected?.risks?.high ?? data.gate?.highRisk ?? "unknown"}`,
3203
- `Monthly cost: ${formatMoney(data.gate?.cost?.monthly?.amount ?? data.gate?.monthlyCost, data.gate?.cost?.monthly?.currency)}`,
3204
- `Validation: ${formatValidation(data.gate?.validation)}`,
3205
- Array.isArray(data.gate?.failures) && data.gate.failures.length ? `Gate failures: ${data.gate.failures.join("; ")}` : "Gate failures: none reported"
3206
- ].join("\n");
3207
- var isTransientAiSummaryText = (text) => {
3208
- if (!text?.trim()) {
3209
- return false;
3210
- }
3211
- return /too many requests|rate[- ]?limit|try again in a moment|temporarily unavailable|something went wrong while processing|please try again or ask a different question|did not complete within/i.test(
3212
- text
3213
- );
3214
- };
3215
- var normalizeAiSummaryMarkdown = (text) => {
3216
- return normalizeAiSummarySections(text).markdown;
3217
- };
3218
- var normalizeAiSummarySections = (text) => {
3219
- const trimmed = String(text ?? "").trim();
3220
- if (!trimmed) {
3221
- return { shortSummary: "", detailsMarkdown: "", markdown: "" };
3222
- }
3223
- const withNewlines = sanitizeAiSummaryMarkdown(trimmed.replace(/\\n/g, "\n"));
3224
- const requestMatch = /\bRequest:\s*/i.exec(withNewlines);
3225
- const sourceText = requestMatch ? withNewlines.slice(requestMatch.index + requestMatch[0].length).split(/\n\s*\nThe request reached\b/i)[0].trim() || withNewlines : withNewlines;
3226
- const sections = splitAiSummarySections(sourceText);
3227
- return {
3228
- ...sections,
3229
- markdown: renderAiSummarySections(sections.shortSummary, sections.detailsMarkdown)
3230
- };
3231
- };
3232
- var sanitizeAiSummaryMarkdown = (text) => text.replace(/<!--[\s\S]*?-->/g, "").replace(/\s*\[S_[A-Za-z0-9_:-]+\]/g, "").replace(/\n{3,}/g, "\n\n").trim();
3233
- var stripAiSectionLabel = (text, label) => text.replace(new RegExp(`^\\s*#{0,6}\\s*\\*\\*?${label}\\*\\*?\\s*:?\\s*`, "i"), "").replace(new RegExp(`^\\s*${label}\\s*:?\\s*`, "i"), "").trim();
3234
- var normalizeAiDetailHeadings = (text) => text.split("\n").map((line) => {
3235
- const cleaned = line.replace(/^\s*#{1,6}\s*/, "").replace(/^\s*[-*]\s+/, "").trimEnd();
3236
- const labelMatch = cleaned.match(/^\*\*(Key risks|Cost posture|Recommended next step|Recommendation|Validation|Impact):\*\*\s*(.*)$/i) ?? cleaned.match(/^\*\*(Key risks|Cost posture|Recommended next step|Recommendation|Validation|Impact)\*\*\s*:\s*(.*)$/i) ?? cleaned.match(/^(Key risks|Cost posture|Recommended next step|Recommendation|Validation|Impact)\s*:\s*(.*)$/i);
3237
- if (!labelMatch) {
3238
- return cleaned;
3239
- }
3240
- return `- **${labelMatch[1]}:** ${labelMatch[2]}`.trimEnd();
3241
- }).join("\n").trim();
3242
- var splitAiSummarySections = (text) => {
3243
- const cleaned = sanitizeAiSummaryMarkdown(text).replace(/^\s*#{1,6}\s*AI summary\s*$/gim, "").trim();
3244
- const shortMatch = /\bShort Summary\s*:\s*/i.exec(cleaned);
3245
- const detailsMatch = /\bDetails\s*:\s*/i.exec(cleaned);
3246
- if (shortMatch && detailsMatch && shortMatch.index < detailsMatch.index) {
3247
- const shortSummary2 = stripAiSectionLabel(
3248
- cleaned.slice(shortMatch.index, detailsMatch.index).trim(),
3249
- "Short Summary"
3250
- );
3251
- const detailsMarkdown2 = normalizeAiDetailHeadings(
3252
- stripAiSectionLabel(cleaned.slice(detailsMatch.index).trim(), "Details")
3253
- );
3254
- return { shortSummary: shortSummary2, detailsMarkdown: detailsMarkdown2 };
3255
- }
3256
- const paragraphs = cleaned.split(/\n\s*\n/).map((paragraph) => paragraph.trim()).filter(Boolean);
3257
- const shortSummary = stripAiSectionLabel(paragraphs[0] ?? cleaned, "Short Summary");
3258
- const detailsMarkdown = normalizeAiDetailHeadings(paragraphs.slice(1).join("\n\n"));
3259
- return { shortSummary, detailsMarkdown };
3260
- };
3261
3341
  var renderAiSummarySections = (shortSummary, detailsMarkdown) => {
3262
- const lines = [`**Short summary:** ${shortSummary.trim()}`];
3342
+ const lines = [shortSummary.trim()];
3263
3343
  if (detailsMarkdown.trim()) {
3264
3344
  lines.push(
3265
3345
  "",
@@ -3273,195 +3353,108 @@ var renderAiSummarySections = (shortSummary, detailsMarkdown) => {
3273
3353
  }
3274
3354
  return lines.join("\n");
3275
3355
  };
3276
- var aiSummaryAttemptTimeoutMs = () => {
3277
- const raw = process.env.CLOUDEVAL_REVIEW_AI_ATTEMPT_TIMEOUT_MS;
3278
- if (raw?.trim()) {
3279
- const parsed = Number.parseInt(raw.trim(), 10);
3280
- if (Number.isFinite(parsed) && parsed > 0) {
3281
- return parsed;
3282
- }
3283
- }
3284
- return 18e4;
3285
- };
3286
- var aiSummaryRetryDelaysMs = () => {
3287
- const raw = process.env.CLOUDEVAL_REVIEW_AI_RETRY_DELAYS_MS;
3288
- if (raw?.trim()) {
3289
- return raw.split(",").map((value) => Number.parseInt(value.trim(), 10)).filter((value) => Number.isFinite(value) && value >= 0);
3356
+ var githubWorkflowRunUrl = () => {
3357
+ const server = process.env.GITHUB_SERVER_URL;
3358
+ const repo = process.env.GITHUB_REPOSITORY;
3359
+ const runId = process.env.GITHUB_RUN_ID;
3360
+ if (server && repo && runId) {
3361
+ return `${server.replace(/\/$/, "")}/${repo}/actions/runs/${runId}`;
3290
3362
  }
3291
- return [5e3, 15e3];
3363
+ return void 0;
3292
3364
  };
3293
- var generateAiSummaryAttempt = async ({
3294
- baseUrl,
3295
- token,
3296
- user,
3297
- project,
3298
- model,
3299
- mode,
3300
- agentProfileId,
3301
- data
3302
- }) => {
3303
- const core = await import("./dist-PEYJDO7A.js");
3304
- const threadId = `review-${data.projectId}-${Date.now()}`;
3305
- let markdown = "";
3306
- let chatState = { ...core.initialChatState, threadId };
3307
- const attemptTimeoutMs = aiSummaryAttemptTimeoutMs();
3308
- let timedOut = false;
3309
- let timeoutId;
3310
- const timeoutPromise = new Promise((_, reject) => {
3311
- timeoutId = setTimeout(() => {
3312
- timedOut = true;
3313
- reject(
3314
- new Error(`AI summary did not complete within ${attemptTimeoutMs}ms.`)
3315
- );
3316
- }, attemptTimeoutMs);
3317
- timeoutId.unref?.();
3318
- });
3319
- const iterator = core.streamChat({
3320
- baseUrl,
3321
- authToken: token,
3322
- message: buildAiSummaryPrompt(data),
3323
- threadId,
3324
- user: {
3325
- id: String(project?.user_id ?? user?.id ?? "cli-user"),
3326
- name: String(user?.full_name ?? user?.name ?? user?.email ?? "CloudEval CI")
3327
- },
3328
- project: project ? {
3329
- id: String(project.id ?? data.projectId),
3330
- name: String(project.name ?? data.projectId),
3331
- user_id: typeof project.user_id === "string" ? project.user_id : void 0,
3332
- cloud_provider: typeof project.cloud_provider === "string" ? project.cloud_provider : void 0,
3333
- type: typeof project.type === "string" ? project.type : void 0,
3334
- connection_ids: Array.isArray(project.connection_ids) ? project.connection_ids : void 0
3335
- } : {
3336
- id: String(data.projectId),
3337
- name: String(data.projectId)
3338
- },
3339
- settings: {
3340
- mode,
3341
- ...model ? { model } : {}
3342
- },
3343
- ...mode === "agent" ? { agentProfileId: agentProfileId ?? "architecture" } : {},
3344
- completeAfterResponse: true,
3345
- responseCompletionGraceMs: 250,
3346
- streamIdleTimeoutMs: 12e4
3347
- });
3348
- try {
3349
- while (true) {
3350
- const next = await Promise.race([iterator.next(), timeoutPromise]);
3351
- if (next.done) {
3352
- break;
3353
- }
3354
- const chunk = next.value;
3355
- chatState = core.reduceChunk(chatState, chunk);
3356
- const latestMessage = [...chatState.messages ?? []].reverse().find((message) => message.role === "assistant");
3357
- const chunkAssistantMessage = Array.isArray(chunk?.messages) ? [...chunk.messages].reverse().find((message) => message?.role === "assistant" && typeof message?.content === "string")?.content : void 0;
3358
- const content = chunk?.content;
3359
- if (typeof chunkAssistantMessage === "string" && chunkAssistantMessage.trim()) {
3360
- markdown = chunkAssistantMessage;
3361
- }
3362
- if (chunk.type === "responding" && typeof content === "string") {
3363
- markdown = latestMessage?.content || `${markdown}${content}`;
3364
- }
3365
- }
3366
- } finally {
3367
- if (timeoutId) {
3368
- clearTimeout(timeoutId);
3369
- }
3370
- if (timedOut) {
3371
- await iterator.return?.(void 0).catch(() => void 0);
3372
- }
3373
- }
3374
- const finalMessage = [...chatState.messages ?? []].reverse().find((message) => message.role === "assistant");
3375
- const rawMarkdown = String(finalMessage?.content || markdown).trim();
3376
- const normalized = normalizeAiSummarySections(rawMarkdown);
3365
+ var reviewSurface = () => {
3366
+ const event = String(process.env.GITHUB_EVENT_NAME ?? "").toLowerCase();
3367
+ const ref = String(process.env.GITHUB_REF ?? "").toLowerCase();
3368
+ return event.startsWith("pull_request") || ref.startsWith("refs/pull/") ? "pull_request" : "local_review";
3369
+ };
3370
+ var buildReviewSummaryPayload = (data) => ({
3371
+ source: process.env.GITHUB_ACTIONS === "true" ? "github_action" : "cli",
3372
+ surface: reviewSurface(),
3373
+ project: data.project ?? { id: data.projectId },
3374
+ repository: { full_name: data.repo },
3375
+ ref: data.ref,
3376
+ commit_sha: data.commitSha,
3377
+ workflow_run_url: githubWorkflowRunUrl(),
3378
+ gate_result: {
3379
+ status: data.gate?.status,
3380
+ failures: data.gate?.failures ?? [],
3381
+ thresholds: data.gate?.thresholds ?? {},
3382
+ enforcement: data.gate?.enforcement
3383
+ },
3384
+ well_architected: data.gate?.wellArchitected ?? {},
3385
+ cost: data.gate?.cost ?? {},
3386
+ validation: data.gate?.validation ?? {},
3387
+ policy: data.gate?.validation?.policy ?? data.gate?.policy ?? {},
3388
+ architecture_signals: data.gate?.architecture ?? {},
3389
+ changed_files: data.changedFiles ?? []
3390
+ });
3391
+ var deterministicAiSummary = (data, error) => {
3392
+ const score = data.gate?.wellArchitected?.overall?.score ?? data.gate?.overallScore;
3393
+ const rating = scoreRating(score) ?? "UNKNOWN";
3394
+ const validation = data.gate?.validation ?? {};
3395
+ const cost = data.gate?.cost?.monthly ?? {};
3396
+ const failedTests = numberFrom(validation?.unitTests?.failed) ?? numberFrom(validation?.unit_tests?.failed) ?? numberFrom(validation?.failedUnitTests) ?? numberFrom(validation?.failed_tests) ?? 0;
3397
+ const policyFailed = numberFrom(validation?.policyChecks?.failed) ?? numberFrom(validation?.policy_checks?.failed) ?? numberFrom(data.gate?.policy?.failed) ?? 0;
3398
+ const policyStatus = policyFailed > 0 ? "has failed checks" : "GOOD";
3399
+ const summary = [
3400
+ `CloudEval review completed with ${String(data.gate?.status ?? "UNKNOWN").toUpperCase()}.`,
3401
+ `Well-Architected posture is ${formatScore(score)} (${rating}), validation has ${displayNumber(failedTests)} failed unit tests, policy checks are ${policyStatus}, and monthly cost is ${formatMonthlyMoney(cost?.amount, cost?.currency)}.`,
3402
+ "Prioritize failed validation checks and the weakest Well-Architected pillar first."
3403
+ ].join(" ");
3377
3404
  return {
3378
3405
  enabled: true,
3379
- status: "ok",
3380
- mode,
3381
- ...mode === "agent" ? { agentProfileId: agentProfileId ?? "architecture" } : {},
3382
- ...model ? { model } : {},
3383
- ...normalized,
3384
- threadId
3406
+ status: "fallback",
3407
+ fallbackUsed: true,
3408
+ warnings: error ? [`Review summary endpoint failed: ${error}`] : [],
3409
+ shortSummary: summary,
3410
+ detailsMarkdown: [
3411
+ "**Main risk**\nCloudEval could not produce an AI-written review summary, so use the deterministic gate evidence.",
3412
+ "**Why it matters**\nFailed validation and weak architecture pillars are the highest-signal remediation inputs.",
3413
+ "**Recommended actions**\nFix failed validation checks, address the weakest pillar, rerun CloudEval review, and compare the updated gate.",
3414
+ "**Evidence used**\nGate status, Well-Architected score, validation totals, policy totals, and monthly cost."
3415
+ ].join("\n\n"),
3416
+ markdown: renderAiSummarySections(
3417
+ summary,
3418
+ [
3419
+ "**Main risk**\nCloudEval could not produce an AI-written review summary, so use the deterministic gate evidence.",
3420
+ "**Why it matters**\nFailed validation and weak architecture pillars are the highest-signal remediation inputs.",
3421
+ "**Recommended actions**\nFix failed validation checks, address the weakest pillar, rerun CloudEval review, and compare the updated gate.",
3422
+ "**Evidence used**\nGate status, Well-Architected score, validation totals, policy totals, and monthly cost."
3423
+ ].join("\n\n")
3424
+ )
3385
3425
  };
3386
3426
  };
3387
3427
  var generateAiSummary = async (input) => {
3388
- const retryDelays = aiSummaryRetryDelaysMs();
3389
- let lastResult;
3390
- let activeInput = input;
3391
- let fallbackFromMode;
3392
- for (let attemptIndex = 0; attemptIndex <= retryDelays.length; attemptIndex += 1) {
3393
- let result;
3394
- try {
3395
- result = await generateAiSummaryAttempt(activeInput);
3396
- } catch (error) {
3397
- const message = error?.message ?? "AI summary failed";
3398
- if (!isTransientAiSummaryText(message)) {
3399
- throw error;
3400
- }
3401
- result = {
3402
- enabled: true,
3403
- status: "transient_error",
3404
- mode: activeInput.mode,
3405
- ...activeInput.mode === "agent" ? { agentProfileId: activeInput.agentProfileId ?? "architecture" } : {},
3406
- ...activeInput.model ? { model: activeInput.model } : {},
3407
- markdown: message,
3408
- error: message
3409
- };
3410
- }
3411
- result.attempts = attemptIndex + 1;
3412
- if (fallbackFromMode) {
3413
- result.fallbackFromMode = fallbackFromMode;
3414
- }
3415
- const normalizedSummary = typeof result.shortSummary === "string" || typeof result.detailsMarkdown === "string" ? {
3416
- shortSummary: String(result.shortSummary ?? "").trim(),
3417
- detailsMarkdown: String(result.detailsMarkdown ?? "").trim(),
3418
- markdown: renderAiSummarySections(
3419
- String(result.shortSummary ?? "").trim(),
3420
- String(result.detailsMarkdown ?? "").trim()
3421
- )
3422
- } : normalizeAiSummarySections(result.markdown);
3423
- result = { ...result, ...normalizedSummary };
3424
- if (isTransientAiSummaryText(result.markdown)) {
3425
- const normalizedError = normalizeAiSummaryMarkdown(result.error);
3426
- if (normalizedError && !isTransientAiSummaryText(normalizedError)) {
3427
- result = {
3428
- ...result,
3429
- ...normalizeAiSummarySections(normalizedError)
3430
- };
3431
- result.status = "ok";
3432
- delete result.error;
3433
- }
3434
- }
3435
- lastResult = result;
3436
- if (!isTransientAiSummaryText(result.markdown)) {
3437
- return result;
3438
- }
3439
- const retryDelay = retryDelays[attemptIndex];
3440
- if (retryDelay === void 0) {
3441
- break;
3442
- }
3443
- await sleep2(retryDelay);
3444
- if (activeInput.mode === "agent") {
3445
- fallbackFromMode = "agent";
3446
- activeInput = {
3447
- ...input,
3448
- mode: "ask",
3449
- agentProfileId: void 0
3450
- };
3428
+ try {
3429
+ const payload = buildReviewSummaryPayload(input.data);
3430
+ const response = await fetchCloudEvalJson({
3431
+ baseUrl: input.baseUrl,
3432
+ authToken: input.token,
3433
+ path: `/projects/${encodeURIComponent(String(input.data.projectId))}/review/summary`,
3434
+ method: "POST",
3435
+ body: payload,
3436
+ idempotencyKey: `cloudeval-review-summary-${input.data.projectId}-${input.data.commitSha ?? "head"}`
3437
+ });
3438
+ const shortSummary = String(response.summary ?? "").trim();
3439
+ const detailsMarkdown = String(response.details ?? "").trim();
3440
+ if (!shortSummary) {
3441
+ return deterministicAiSummary(input.data, "Review summary endpoint returned no summary.");
3451
3442
  }
3443
+ return {
3444
+ enabled: true,
3445
+ status: response.fallback_used ? "fallback" : "ok",
3446
+ fallbackUsed: Boolean(response.fallback_used),
3447
+ warnings: Array.isArray(response.warnings) ? response.warnings : [],
3448
+ riskHighlights: Array.isArray(response.risk_highlights) ? response.risk_highlights : [],
3449
+ recommendedActions: Array.isArray(response.recommended_actions) ? response.recommended_actions : [],
3450
+ evidenceUsed: Array.isArray(response.evidence_used) ? response.evidence_used : [],
3451
+ shortSummary,
3452
+ detailsMarkdown,
3453
+ markdown: renderAiSummarySections(shortSummary, detailsMarkdown)
3454
+ };
3455
+ } catch (error) {
3456
+ return deterministicAiSummary(input.data, error?.message ?? "request failed");
3452
3457
  }
3453
- return {
3454
- ...lastResult ?? {},
3455
- enabled: true,
3456
- status: "unavailable",
3457
- mode: activeInput.mode,
3458
- ...activeInput.mode === "agent" ? { agentProfileId: activeInput.agentProfileId ?? "architecture" } : {},
3459
- ...input.model ? { model: input.model } : {},
3460
- ...fallbackFromMode ? { fallbackFromMode } : {},
3461
- attempts: lastResult?.attempts ?? retryDelays.length + 1,
3462
- error: lastResult?.markdown || "AI summary unavailable",
3463
- markdown: "AI summary unavailable: CloudEval AI was rate-limited. Retry the workflow or rerun `cloudeval review`."
3464
- };
3465
3458
  };
3466
3459
  var buildMarkdownSummary = (data) => {
3467
3460
  const gateStatus = String(data.gate?.status ?? "unknown").toUpperCase();
@@ -3471,7 +3464,8 @@ var buildMarkdownSummary = (data) => {
3471
3464
  const architecture = data.gate?.architecture;
3472
3465
  const projectLabel = String(data.project?.name ?? data.projectName ?? data.projectId);
3473
3466
  const projectDisplay = markdownLink(projectLabel, data.project?.url ?? data.projectUrl);
3474
- const source = data.repo ? `${data.repo}${data.ref ? ` @ ${data.ref}` : ""}` : data.ref ?? "unknown source";
3467
+ const repository = String(data.repo ?? "unknown repository");
3468
+ const ref = String(data.ref ?? "unknown ref");
3475
3469
  const commit = String(data.commitSha ?? "unknown").slice(0, 12);
3476
3470
  const pillarLines = Array.isArray(data.gate?.wellArchitected?.pillars) ? data.gate.wellArchitected.pillars.map((pillar) => {
3477
3471
  const rating = scoreRating(pillar.score);
@@ -3488,24 +3482,14 @@ var buildMarkdownSummary = (data) => {
3488
3482
  cost?.currency
3489
3483
  );
3490
3484
  const positiveCostServices = costServices.filter((service) => service.amount > 0);
3491
- const architectureLines = [
3492
- ["Resources", architecture?.resources],
3493
- ["Relationships", architecture?.relationships],
3494
- ["Resource types", architecture?.resourceTypes]
3495
- ].filter(([, value]) => numberFrom(value) !== void 0).map(([label, value]) => `- ${label}: **${displayNumber(value)}**`);
3496
- const density = numberFrom(architecture?.relationshipDensity);
3497
- if (density !== void 0) {
3498
- architectureLines.push(
3499
- `- Graph connectivity: **${trimNumber(density, 2)} relationships per resource**`
3500
- );
3501
- }
3502
- const resourceCount = numberFrom(architecture?.resources);
3503
- const resourceTypeCount = numberFrom(architecture?.resourceTypes);
3504
- if (resourceCount !== void 0 && resourceTypeCount !== void 0) {
3505
- architectureLines.push(
3506
- `- Resource diversity: **${displayNumber(resourceTypeCount)} types across ${displayNumber(resourceCount)} resources**`
3507
- );
3508
- }
3485
+ const architectureLines = architectureSignalLines({
3486
+ architecture,
3487
+ costServices,
3488
+ costCurrency: cost?.currency,
3489
+ highRiskFindings: data.gate?.wellArchitected?.risks?.high,
3490
+ pillars: Array.isArray(data.gate?.wellArchitected?.pillars) ? data.gate.wellArchitected.pillars : []
3491
+ });
3492
+ const validationRows = validationFailureRows(validation);
3509
3493
  const overallRating = scoreRating(score);
3510
3494
  const lines = [
3511
3495
  `${statusIcon(data.gate?.status)} **Overall** : ${gateStatus}`,
@@ -3513,9 +3497,13 @@ var buildMarkdownSummary = (data) => {
3513
3497
  validationSummaryLine(validation),
3514
3498
  policySummaryLine(validation),
3515
3499
  costSummaryLine(cost),
3516
- `**Cloudeval Project**: ${projectDisplay}`,
3517
3500
  "",
3518
- `Source: \`${source}\` \xB7 commit \`${commit}\``
3501
+ "#### Source",
3502
+ "",
3503
+ `- **CloudEval project**: ${projectDisplay}`,
3504
+ `- **Repository**: \`${repository}\``,
3505
+ `- **Ref**: \`${ref}\``,
3506
+ `- **Commit**: \`${commit}\``
3519
3507
  ];
3520
3508
  if (data.aiSummary?.markdown) {
3521
3509
  lines.push("", "#### AI summary", "", data.aiSummary.markdown);
@@ -3582,9 +3570,15 @@ var buildMarkdownSummary = (data) => {
3582
3570
  lines.push(
3583
3571
  "",
3584
3572
  "<details>",
3585
- "<summary>Validation details</summary>",
3573
+ `<summary>${validationRows.length ? "Validation failures" : "Validation details"}</summary>`,
3586
3574
  "",
3587
3575
  ...validationDetailLines(validation),
3576
+ ...validationRows.length ? [
3577
+ "",
3578
+ "| Type | Name | Location | Severity | Why / next step |",
3579
+ "| --- | --- | --- | --- | --- |",
3580
+ ...validationRows
3581
+ ] : [],
3588
3582
  "",
3589
3583
  "</details>"
3590
3584
  );
@@ -3593,7 +3587,7 @@ var buildMarkdownSummary = (data) => {
3593
3587
  lines.push(
3594
3588
  "",
3595
3589
  "<details>",
3596
- "<summary>Architecture insights</summary>",
3590
+ "<summary>Architecture signals</summary>",
3597
3591
  "",
3598
3592
  ...architectureLines,
3599
3593
  "",
@@ -15632,7 +15626,7 @@ program.command("tui").description("Open the CloudEval Terminal UI").option(
15632
15626
  const { assertSecureBaseUrl } = await import("./dist-PEYJDO7A.js");
15633
15627
  const [{ render }, { App }] = await Promise.all([
15634
15628
  import("ink"),
15635
- import("./App-XHSK5SJA.js")
15629
+ import("./App-VSOTHLOD.js")
15636
15630
  ]);
15637
15631
  const baseUrl = await resolveBaseUrl(options, command);
15638
15632
  assertSecureBaseUrl(baseUrl);
@@ -15690,7 +15684,7 @@ program.command("chat").description("Start an interactive chat session").option(
15690
15684
  const { assertSecureBaseUrl } = await import("./dist-PEYJDO7A.js");
15691
15685
  const [{ render }, { App }] = await Promise.all([
15692
15686
  import("ink"),
15693
- import("./App-XHSK5SJA.js")
15687
+ import("./App-VSOTHLOD.js")
15694
15688
  ]);
15695
15689
  const baseUrl = await resolveBaseUrl(options, command);
15696
15690
  assertSecureBaseUrl(baseUrl);
@@ -16444,7 +16438,7 @@ Error: ${errorMsg}
16444
16438
  program.command("banner").description("Preview the startup banner and terminal capabilities").action(async () => {
16445
16439
  const { render } = await import("ink");
16446
16440
  const BannerPreview = React.lazy(async () => ({
16447
- default: (await import("./Banner-GAOI3CZR.js")).Banner
16441
+ default: (await import("./Banner-7XUOJYL5.js")).Banner
16448
16442
  }));
16449
16443
  render(
16450
16444
  /* @__PURE__ */ jsx(React.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(BannerPreview, { disable: false }) })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ganakailabs/cloudeval-cli",
3
- "version": "0.27.1",
3
+ "version": "0.27.2",
4
4
  "license": "LicenseRef-CloudEval-CLI",
5
5
  "type": "module",
6
6
  "description": "CloudEval CLI for cloud architecture, cost, report, automation, and MCP workflows.",
package/sbom.spdx.json CHANGED
@@ -14,7 +14,7 @@
14
14
  {
15
15
  "SPDXID": "SPDXRef-Package-CloudEval-CLI",
16
16
  "name": "CloudEval CLI",
17
- "versionInfo": "0.27.1",
17
+ "versionInfo": "0.27.2",
18
18
  "downloadLocation": "https://github.com/ganakailabs/cloudeval-cli",
19
19
  "filesAnalyzed": false,
20
20
  "licenseConcluded": "LicenseRef-CloudEval-CLI",