@ganakailabs/cloudeval-cli 0.27.0 → 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-5AH64UNL.js";
41
+ } from "./chunk-3DBCT463.js";
42
42
  import {
43
43
  CLI_VERSION
44
- } from "./chunk-YW5JWFYV.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-5AH64UNL.js";
7
- import "./chunk-YW5JWFYV.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-YW5JWFYV.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.0";
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-YW5JWFYV.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) {
@@ -2570,7 +2578,8 @@ var formatMoney = (amount, currency, fallback = "not available") => {
2570
2578
  if (numericAmount === void 0) {
2571
2579
  return fallback;
2572
2580
  }
2573
- return currency ? `${numericAmount} ${currency}` : String(numericAmount);
2581
+ const formattedAmount = trimNumber(numericAmount);
2582
+ return currency ? `${formattedAmount} ${currency}` : formattedAmount;
2574
2583
  };
2575
2584
  var trimNumber = (value, fractionDigits = 2) => Number.isInteger(value) ? String(value) : String(Number(value.toFixed(fractionDigits)));
2576
2585
  var formatScore = (value, fallback = "unknown") => {
@@ -2659,21 +2668,6 @@ var statusIcon = (status) => {
2659
2668
  return "\u26AA";
2660
2669
  }
2661
2670
  };
2662
- var formatValidation = (validation) => {
2663
- const policyFailed = numberFrom(validation?.policyChecks?.failed);
2664
- const unitFailed = numberFrom(validation?.unitTests?.failed);
2665
- if (policyFailed === 0 && unitFailed === 0) {
2666
- return "Validation clean";
2667
- }
2668
- const parts = [];
2669
- if (unitFailed !== void 0) {
2670
- parts.push(unitFailed === 0 ? "unit tests clean" : `${unitFailed} unit tests failed`);
2671
- }
2672
- if (policyFailed !== void 0) {
2673
- parts.push(policyFailed === 0 ? "policy checks clean" : `${policyFailed} policy checks failed`);
2674
- }
2675
- return parts.length ? parts.join(", ") : "Validation not available";
2676
- };
2677
2671
  var validationSummaryLine = (validation) => {
2678
2672
  const unitFailed = numberFrom(validation?.unitTests?.failed);
2679
2673
  if (unitFailed === void 0) {
@@ -2715,6 +2709,135 @@ var validationDetailLines = (validation) => {
2715
2709
  }
2716
2710
  return lines.length ? lines : ["- Validation data was not available."];
2717
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
+ };
2718
2841
  var namedAmount = (record) => numberFrom(
2719
2842
  record.amount,
2720
2843
  record.monthly_cost,
@@ -2724,6 +2847,15 @@ var namedAmount = (record) => numberFrom(
2724
2847
  );
2725
2848
  var namedLabel = (record, fallback) => String(record.name ?? record.service ?? record.label ?? record.category ?? fallback);
2726
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
+ };
2727
2859
  var costServiceRows = (services, currency) => (Array.isArray(services) ? services : []).map((service, index) => {
2728
2860
  const record = asRecord(service);
2729
2861
  const amount = namedAmount(record);
@@ -2817,19 +2949,41 @@ var extractValidation = ({
2817
2949
  preload?.reports?.unit_tests?.metrics,
2818
2950
  project?.status?.unit_tests
2819
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
+ );
2820
2972
  return {
2821
2973
  policyChecks: {
2822
2974
  total: numberFrom(policySummary?.total_rules, policySummary?.totalRules, rules.length),
2823
2975
  passed: numberFrom(policySummary?.passed_rules, policySummary?.passedRules),
2824
2976
  failed: numberFrom(policySummary?.failed_rules, policySummary?.failedRules, failedRules.length),
2825
- errors: numberFrom(policySummary?.error_rules, policySummary?.errorRules)
2977
+ errors: numberFrom(policySummary?.error_rules, policySummary?.errorRules),
2978
+ failures: policyFailures.slice(0, 5)
2826
2979
  },
2827
2980
  unitTests: {
2828
2981
  status: unitSummary?.status,
2829
2982
  total: numberFrom(unitSummary?.total_tests, unitSummary?.totalTests),
2830
2983
  passed: numberFrom(unitSummary?.passed_tests, unitSummary?.passedTests),
2831
2984
  failed: numberFrom(unitSummary?.failed_tests, unitSummary?.failedTests),
2832
- skipped: numberFrom(unitSummary?.skipped_tests, unitSummary?.skippedTests)
2985
+ skipped: numberFrom(unitSummary?.skipped_tests, unitSummary?.skippedTests),
2986
+ failures: unitFailures.slice(0, 5)
2833
2987
  }
2834
2988
  };
2835
2989
  };
@@ -3184,81 +3338,8 @@ var waitForReviewReports = async ({
3184
3338
  }
3185
3339
  return latest;
3186
3340
  };
3187
- var buildAiSummaryPrompt = (data) => [
3188
- "Write a concise CloudEval pull request review summary in Markdown.",
3189
- "Return exactly two sections: Short Summary and Details.",
3190
- "Short Summary: one dense paragraph under 45 words with gate status, Well-Architected posture, validation, and cost.",
3191
- "Details: short bullets with bold labels only when useful, such as **Key risks:**, **Cost posture:**, and **Recommended next step:**.",
3192
- "Keep the full response under 180 words. Do not invent facts not present below.",
3193
- "Do not include citations, source markers, hidden tool ids, or HTML comments.",
3194
- "",
3195
- `Project: ${data.projectId}`,
3196
- `Repository: ${data.repo ?? "unknown"}`,
3197
- `Ref: ${data.ref ?? "unknown"}`,
3198
- `Commit: ${data.commitSha ?? "unknown"}`,
3199
- `Gate: ${String(data.gate?.status ?? "unknown").toUpperCase()}`,
3200
- `Well-Architected score: ${data.gate?.wellArchitected?.overall?.score ?? data.gate?.overallScore ?? "unknown"}`,
3201
- `High-risk findings: ${data.gate?.wellArchitected?.risks?.high ?? data.gate?.highRisk ?? "unknown"}`,
3202
- `Monthly cost: ${formatMoney(data.gate?.cost?.monthly?.amount ?? data.gate?.monthlyCost, data.gate?.cost?.monthly?.currency)}`,
3203
- `Validation: ${formatValidation(data.gate?.validation)}`,
3204
- Array.isArray(data.gate?.failures) && data.gate.failures.length ? `Gate failures: ${data.gate.failures.join("; ")}` : "Gate failures: none reported"
3205
- ].join("\n");
3206
- var isTransientAiSummaryText = (text) => {
3207
- if (!text?.trim()) {
3208
- return false;
3209
- }
3210
- 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(
3211
- text
3212
- );
3213
- };
3214
- var normalizeAiSummaryMarkdown = (text) => {
3215
- return normalizeAiSummarySections(text).markdown;
3216
- };
3217
- var normalizeAiSummarySections = (text) => {
3218
- const trimmed = String(text ?? "").trim();
3219
- if (!trimmed) {
3220
- return { shortSummary: "", detailsMarkdown: "", markdown: "" };
3221
- }
3222
- const withNewlines = sanitizeAiSummaryMarkdown(trimmed.replace(/\\n/g, "\n"));
3223
- const requestMatch = /\bRequest:\s*/i.exec(withNewlines);
3224
- const sourceText = requestMatch ? withNewlines.slice(requestMatch.index + requestMatch[0].length).split(/\n\s*\nThe request reached\b/i)[0].trim() || withNewlines : withNewlines;
3225
- const sections = splitAiSummarySections(sourceText);
3226
- return {
3227
- ...sections,
3228
- markdown: renderAiSummarySections(sections.shortSummary, sections.detailsMarkdown)
3229
- };
3230
- };
3231
- var sanitizeAiSummaryMarkdown = (text) => text.replace(/<!--[\s\S]*?-->/g, "").replace(/\s*\[S_[A-Za-z0-9_:-]+\]/g, "").replace(/\n{3,}/g, "\n\n").trim();
3232
- 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();
3233
- var normalizeAiDetailHeadings = (text) => text.split("\n").map((line) => {
3234
- const cleaned = line.replace(/^\s*#{1,6}\s*/, "").replace(/^\s*[-*]\s+/, "").trimEnd();
3235
- 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);
3236
- if (!labelMatch) {
3237
- return cleaned;
3238
- }
3239
- return `- **${labelMatch[1]}:** ${labelMatch[2]}`.trimEnd();
3240
- }).join("\n").trim();
3241
- var splitAiSummarySections = (text) => {
3242
- const cleaned = sanitizeAiSummaryMarkdown(text).replace(/^\s*#{1,6}\s*AI summary\s*$/gim, "").trim();
3243
- const shortMatch = /\bShort Summary\s*:\s*/i.exec(cleaned);
3244
- const detailsMatch = /\bDetails\s*:\s*/i.exec(cleaned);
3245
- if (shortMatch && detailsMatch && shortMatch.index < detailsMatch.index) {
3246
- const shortSummary2 = stripAiSectionLabel(
3247
- cleaned.slice(shortMatch.index, detailsMatch.index).trim(),
3248
- "Short Summary"
3249
- );
3250
- const detailsMarkdown2 = normalizeAiDetailHeadings(
3251
- stripAiSectionLabel(cleaned.slice(detailsMatch.index).trim(), "Details")
3252
- );
3253
- return { shortSummary: shortSummary2, detailsMarkdown: detailsMarkdown2 };
3254
- }
3255
- const paragraphs = cleaned.split(/\n\s*\n/).map((paragraph) => paragraph.trim()).filter(Boolean);
3256
- const shortSummary = stripAiSectionLabel(paragraphs[0] ?? cleaned, "Short Summary");
3257
- const detailsMarkdown = normalizeAiDetailHeadings(paragraphs.slice(1).join("\n\n"));
3258
- return { shortSummary, detailsMarkdown };
3259
- };
3260
3341
  var renderAiSummarySections = (shortSummary, detailsMarkdown) => {
3261
- const lines = [`**Short summary:** ${shortSummary.trim()}`];
3342
+ const lines = [shortSummary.trim()];
3262
3343
  if (detailsMarkdown.trim()) {
3263
3344
  lines.push(
3264
3345
  "",
@@ -3272,195 +3353,108 @@ var renderAiSummarySections = (shortSummary, detailsMarkdown) => {
3272
3353
  }
3273
3354
  return lines.join("\n");
3274
3355
  };
3275
- var aiSummaryAttemptTimeoutMs = () => {
3276
- const raw = process.env.CLOUDEVAL_REVIEW_AI_ATTEMPT_TIMEOUT_MS;
3277
- if (raw?.trim()) {
3278
- const parsed = Number.parseInt(raw.trim(), 10);
3279
- if (Number.isFinite(parsed) && parsed > 0) {
3280
- return parsed;
3281
- }
3282
- }
3283
- return 18e4;
3284
- };
3285
- var aiSummaryRetryDelaysMs = () => {
3286
- const raw = process.env.CLOUDEVAL_REVIEW_AI_RETRY_DELAYS_MS;
3287
- if (raw?.trim()) {
3288
- 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}`;
3289
3362
  }
3290
- return [5e3, 15e3];
3363
+ return void 0;
3291
3364
  };
3292
- var generateAiSummaryAttempt = async ({
3293
- baseUrl,
3294
- token,
3295
- user,
3296
- project,
3297
- model,
3298
- mode,
3299
- agentProfileId,
3300
- data
3301
- }) => {
3302
- const core = await import("./dist-PEYJDO7A.js");
3303
- const threadId = `review-${data.projectId}-${Date.now()}`;
3304
- let markdown = "";
3305
- let chatState = { ...core.initialChatState, threadId };
3306
- const attemptTimeoutMs = aiSummaryAttemptTimeoutMs();
3307
- let timedOut = false;
3308
- let timeoutId;
3309
- const timeoutPromise = new Promise((_, reject) => {
3310
- timeoutId = setTimeout(() => {
3311
- timedOut = true;
3312
- reject(
3313
- new Error(`AI summary did not complete within ${attemptTimeoutMs}ms.`)
3314
- );
3315
- }, attemptTimeoutMs);
3316
- timeoutId.unref?.();
3317
- });
3318
- const iterator = core.streamChat({
3319
- baseUrl,
3320
- authToken: token,
3321
- message: buildAiSummaryPrompt(data),
3322
- threadId,
3323
- user: {
3324
- id: String(project?.user_id ?? user?.id ?? "cli-user"),
3325
- name: String(user?.full_name ?? user?.name ?? user?.email ?? "CloudEval CI")
3326
- },
3327
- project: project ? {
3328
- id: String(project.id ?? data.projectId),
3329
- name: String(project.name ?? data.projectId),
3330
- user_id: typeof project.user_id === "string" ? project.user_id : void 0,
3331
- cloud_provider: typeof project.cloud_provider === "string" ? project.cloud_provider : void 0,
3332
- type: typeof project.type === "string" ? project.type : void 0,
3333
- connection_ids: Array.isArray(project.connection_ids) ? project.connection_ids : void 0
3334
- } : {
3335
- id: String(data.projectId),
3336
- name: String(data.projectId)
3337
- },
3338
- settings: {
3339
- mode,
3340
- ...model ? { model } : {}
3341
- },
3342
- ...mode === "agent" ? { agentProfileId: agentProfileId ?? "architecture" } : {},
3343
- completeAfterResponse: true,
3344
- responseCompletionGraceMs: 250,
3345
- streamIdleTimeoutMs: 12e4
3346
- });
3347
- try {
3348
- while (true) {
3349
- const next = await Promise.race([iterator.next(), timeoutPromise]);
3350
- if (next.done) {
3351
- break;
3352
- }
3353
- const chunk = next.value;
3354
- chatState = core.reduceChunk(chatState, chunk);
3355
- const latestMessage = [...chatState.messages ?? []].reverse().find((message) => message.role === "assistant");
3356
- const chunkAssistantMessage = Array.isArray(chunk?.messages) ? [...chunk.messages].reverse().find((message) => message?.role === "assistant" && typeof message?.content === "string")?.content : void 0;
3357
- const content = chunk?.content;
3358
- if (typeof chunkAssistantMessage === "string" && chunkAssistantMessage.trim()) {
3359
- markdown = chunkAssistantMessage;
3360
- }
3361
- if (chunk.type === "responding" && typeof content === "string") {
3362
- markdown = latestMessage?.content || `${markdown}${content}`;
3363
- }
3364
- }
3365
- } finally {
3366
- if (timeoutId) {
3367
- clearTimeout(timeoutId);
3368
- }
3369
- if (timedOut) {
3370
- await iterator.return?.(void 0).catch(() => void 0);
3371
- }
3372
- }
3373
- const finalMessage = [...chatState.messages ?? []].reverse().find((message) => message.role === "assistant");
3374
- const rawMarkdown = String(finalMessage?.content || markdown).trim();
3375
- 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(" ");
3376
3404
  return {
3377
3405
  enabled: true,
3378
- status: "ok",
3379
- mode,
3380
- ...mode === "agent" ? { agentProfileId: agentProfileId ?? "architecture" } : {},
3381
- ...model ? { model } : {},
3382
- ...normalized,
3383
- 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
+ )
3384
3425
  };
3385
3426
  };
3386
3427
  var generateAiSummary = async (input) => {
3387
- const retryDelays = aiSummaryRetryDelaysMs();
3388
- let lastResult;
3389
- let activeInput = input;
3390
- let fallbackFromMode;
3391
- for (let attemptIndex = 0; attemptIndex <= retryDelays.length; attemptIndex += 1) {
3392
- let result;
3393
- try {
3394
- result = await generateAiSummaryAttempt(activeInput);
3395
- } catch (error) {
3396
- const message = error?.message ?? "AI summary failed";
3397
- if (!isTransientAiSummaryText(message)) {
3398
- throw error;
3399
- }
3400
- result = {
3401
- enabled: true,
3402
- status: "transient_error",
3403
- mode: activeInput.mode,
3404
- ...activeInput.mode === "agent" ? { agentProfileId: activeInput.agentProfileId ?? "architecture" } : {},
3405
- ...activeInput.model ? { model: activeInput.model } : {},
3406
- markdown: message,
3407
- error: message
3408
- };
3409
- }
3410
- result.attempts = attemptIndex + 1;
3411
- if (fallbackFromMode) {
3412
- result.fallbackFromMode = fallbackFromMode;
3413
- }
3414
- const normalizedSummary = typeof result.shortSummary === "string" || typeof result.detailsMarkdown === "string" ? {
3415
- shortSummary: String(result.shortSummary ?? "").trim(),
3416
- detailsMarkdown: String(result.detailsMarkdown ?? "").trim(),
3417
- markdown: renderAiSummarySections(
3418
- String(result.shortSummary ?? "").trim(),
3419
- String(result.detailsMarkdown ?? "").trim()
3420
- )
3421
- } : normalizeAiSummarySections(result.markdown);
3422
- result = { ...result, ...normalizedSummary };
3423
- if (isTransientAiSummaryText(result.markdown)) {
3424
- const normalizedError = normalizeAiSummaryMarkdown(result.error);
3425
- if (normalizedError && !isTransientAiSummaryText(normalizedError)) {
3426
- result = {
3427
- ...result,
3428
- ...normalizeAiSummarySections(normalizedError)
3429
- };
3430
- result.status = "ok";
3431
- delete result.error;
3432
- }
3433
- }
3434
- lastResult = result;
3435
- if (!isTransientAiSummaryText(result.markdown)) {
3436
- return result;
3437
- }
3438
- const retryDelay = retryDelays[attemptIndex];
3439
- if (retryDelay === void 0) {
3440
- break;
3441
- }
3442
- await sleep2(retryDelay);
3443
- if (activeInput.mode === "agent") {
3444
- fallbackFromMode = "agent";
3445
- activeInput = {
3446
- ...input,
3447
- mode: "ask",
3448
- agentProfileId: void 0
3449
- };
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.");
3450
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");
3451
3457
  }
3452
- return {
3453
- ...lastResult ?? {},
3454
- enabled: true,
3455
- status: "unavailable",
3456
- mode: activeInput.mode,
3457
- ...activeInput.mode === "agent" ? { agentProfileId: activeInput.agentProfileId ?? "architecture" } : {},
3458
- ...input.model ? { model: input.model } : {},
3459
- ...fallbackFromMode ? { fallbackFromMode } : {},
3460
- attempts: lastResult?.attempts ?? retryDelays.length + 1,
3461
- error: lastResult?.markdown || "AI summary unavailable",
3462
- markdown: "AI summary unavailable: CloudEval AI was rate-limited. Retry the workflow or rerun `cloudeval review`."
3463
- };
3464
3458
  };
3465
3459
  var buildMarkdownSummary = (data) => {
3466
3460
  const gateStatus = String(data.gate?.status ?? "unknown").toUpperCase();
@@ -3470,7 +3464,8 @@ var buildMarkdownSummary = (data) => {
3470
3464
  const architecture = data.gate?.architecture;
3471
3465
  const projectLabel = String(data.project?.name ?? data.projectName ?? data.projectId);
3472
3466
  const projectDisplay = markdownLink(projectLabel, data.project?.url ?? data.projectUrl);
3473
- 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");
3474
3469
  const commit = String(data.commitSha ?? "unknown").slice(0, 12);
3475
3470
  const pillarLines = Array.isArray(data.gate?.wellArchitected?.pillars) ? data.gate.wellArchitected.pillars.map((pillar) => {
3476
3471
  const rating = scoreRating(pillar.score);
@@ -3487,24 +3482,14 @@ var buildMarkdownSummary = (data) => {
3487
3482
  cost?.currency
3488
3483
  );
3489
3484
  const positiveCostServices = costServices.filter((service) => service.amount > 0);
3490
- const architectureLines = [
3491
- ["Resources", architecture?.resources],
3492
- ["Relationships", architecture?.relationships],
3493
- ["Resource types", architecture?.resourceTypes]
3494
- ].filter(([, value]) => numberFrom(value) !== void 0).map(([label, value]) => `- ${label}: **${displayNumber(value)}**`);
3495
- const density = numberFrom(architecture?.relationshipDensity);
3496
- if (density !== void 0) {
3497
- architectureLines.push(
3498
- `- Graph connectivity: **${trimNumber(density, 2)} relationships per resource**`
3499
- );
3500
- }
3501
- const resourceCount = numberFrom(architecture?.resources);
3502
- const resourceTypeCount = numberFrom(architecture?.resourceTypes);
3503
- if (resourceCount !== void 0 && resourceTypeCount !== void 0) {
3504
- architectureLines.push(
3505
- `- Resource diversity: **${displayNumber(resourceTypeCount)} types across ${displayNumber(resourceCount)} resources**`
3506
- );
3507
- }
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);
3508
3493
  const overallRating = scoreRating(score);
3509
3494
  const lines = [
3510
3495
  `${statusIcon(data.gate?.status)} **Overall** : ${gateStatus}`,
@@ -3512,9 +3497,13 @@ var buildMarkdownSummary = (data) => {
3512
3497
  validationSummaryLine(validation),
3513
3498
  policySummaryLine(validation),
3514
3499
  costSummaryLine(cost),
3515
- `**Cloudeval Project**: ${projectDisplay}`,
3516
3500
  "",
3517
- `Source: \`${source}\` \xB7 commit \`${commit}\``
3501
+ "#### Source",
3502
+ "",
3503
+ `- **CloudEval project**: ${projectDisplay}`,
3504
+ `- **Repository**: \`${repository}\``,
3505
+ `- **Ref**: \`${ref}\``,
3506
+ `- **Commit**: \`${commit}\``
3518
3507
  ];
3519
3508
  if (data.aiSummary?.markdown) {
3520
3509
  lines.push("", "#### AI summary", "", data.aiSummary.markdown);
@@ -3581,9 +3570,15 @@ var buildMarkdownSummary = (data) => {
3581
3570
  lines.push(
3582
3571
  "",
3583
3572
  "<details>",
3584
- "<summary>Validation details</summary>",
3573
+ `<summary>${validationRows.length ? "Validation failures" : "Validation details"}</summary>`,
3585
3574
  "",
3586
3575
  ...validationDetailLines(validation),
3576
+ ...validationRows.length ? [
3577
+ "",
3578
+ "| Type | Name | Location | Severity | Why / next step |",
3579
+ "| --- | --- | --- | --- | --- |",
3580
+ ...validationRows
3581
+ ] : [],
3587
3582
  "",
3588
3583
  "</details>"
3589
3584
  );
@@ -3592,7 +3587,7 @@ var buildMarkdownSummary = (data) => {
3592
3587
  lines.push(
3593
3588
  "",
3594
3589
  "<details>",
3595
- "<summary>Architecture insights</summary>",
3590
+ "<summary>Architecture signals</summary>",
3596
3591
  "",
3597
3592
  ...architectureLines,
3598
3593
  "",
@@ -15631,7 +15626,7 @@ program.command("tui").description("Open the CloudEval Terminal UI").option(
15631
15626
  const { assertSecureBaseUrl } = await import("./dist-PEYJDO7A.js");
15632
15627
  const [{ render }, { App }] = await Promise.all([
15633
15628
  import("ink"),
15634
- import("./App-GQMIIL3U.js")
15629
+ import("./App-VSOTHLOD.js")
15635
15630
  ]);
15636
15631
  const baseUrl = await resolveBaseUrl(options, command);
15637
15632
  assertSecureBaseUrl(baseUrl);
@@ -15689,7 +15684,7 @@ program.command("chat").description("Start an interactive chat session").option(
15689
15684
  const { assertSecureBaseUrl } = await import("./dist-PEYJDO7A.js");
15690
15685
  const [{ render }, { App }] = await Promise.all([
15691
15686
  import("ink"),
15692
- import("./App-GQMIIL3U.js")
15687
+ import("./App-VSOTHLOD.js")
15693
15688
  ]);
15694
15689
  const baseUrl = await resolveBaseUrl(options, command);
15695
15690
  assertSecureBaseUrl(baseUrl);
@@ -16443,7 +16438,7 @@ Error: ${errorMsg}
16443
16438
  program.command("banner").description("Preview the startup banner and terminal capabilities").action(async () => {
16444
16439
  const { render } = await import("ink");
16445
16440
  const BannerPreview = React.lazy(async () => ({
16446
- default: (await import("./Banner-J2ESX3JN.js")).Banner
16441
+ default: (await import("./Banner-7XUOJYL5.js")).Banner
16447
16442
  }));
16448
16443
  render(
16449
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.0",
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.0",
17
+ "versionInfo": "0.27.2",
18
18
  "downloadLocation": "https://github.com/ganakailabs/cloudeval-cli",
19
19
  "filesAnalyzed": false,
20
20
  "licenseConcluded": "LicenseRef-CloudEval-CLI",