@ganakailabs/cloudeval-cli 0.30.2 → 0.30.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -117,7 +117,7 @@ jobs:
117
117
  --non-interactive
118
118
  ```
119
119
 
120
- Public example: [passing baseline PR #6](https://github.com/ganakailabs/cloudeval-azure-arm-review-example/pull/6) in [`ganakailabs/cloudeval-azure-arm-review-example`](https://github.com/ganakailabs/cloudeval-azure-arm-review-example).
120
+ Public example: [passing baseline PR #6](https://github.com/ganakailabs/cloudeval-azure-arm-review-example/pull/6) in [`ganakailabs/cloudeval-azure-arm-review-example`](https://github.com/ganakailabs/cloudeval-azure-arm-review-example). Review comments show a merge-gate table, CloudEval report badges, a visible AI summary, a folded detailed AI reviewer note, a Well-Architected radar/table drilldown, and cost Mermaid charts.
121
121
 
122
122
  ### MCP For Codex, Cursor, Claude, VS Code
123
123
 
@@ -14,7 +14,7 @@ This notice is not a substitute for legal review before public or enterprise dis
14
14
  | --- | ---: |
15
15
  | (MIT OR CC0-1.0) | 2 |
16
16
  | 0BSD | 1 |
17
- | Apache-2.0 | 50 |
17
+ | Apache-2.0 | 51 |
18
18
  | BSD-3-Clause | 3 |
19
19
  | ISC | 12 |
20
20
  | MIT | 172 |
@@ -226,6 +226,7 @@ This notice is not a substitute for legal review before public or enterprise dis
226
226
  | semver | 7.7.3 | ISC | GitHub Inc. | https://github.com/npm/node-semver#readme |
227
227
  | shell-quote | 1.8.4 | MIT | James Halliday | https://github.com/ljharb/shell-quote |
228
228
  | signal-exit | 3.0.7 | ISC | Ben Coe | https://github.com/tapjs/signal-exit |
229
+ | signalstory | 0.1.0 | Apache-2.0 | NOASSERTION | NOASSERTION |
229
230
  | skin-tone | 2.0.0 | MIT | Sindre Sorhus | https://github.com/sindresorhus/skin-tone#readme |
230
231
  | slice-ansi | 5.0.0 | MIT | NOASSERTION | https://github.com/chalk/slice-ansi#readme |
231
232
  | slice-ansi | 6.0.0 | MIT | NOASSERTION | https://github.com/chalk/slice-ansi#readme |
@@ -38,10 +38,10 @@ import {
38
38
  } from "./chunk-NXM4JEOB.js";
39
39
  import {
40
40
  Banner
41
- } from "./chunk-6WVS2D2U.js";
41
+ } from "./chunk-QB3BBKVH.js";
42
42
  import {
43
43
  CLI_VERSION
44
- } from "./chunk-XGKZ2VPZ.js";
44
+ } from "./chunk-FPZWMNAI.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-6WVS2D2U.js";
7
- import "./chunk-XGKZ2VPZ.js";
6
+ } from "./chunk-QB3BBKVH.js";
7
+ import "./chunk-FPZWMNAI.js";
8
8
  import "./chunk-ZDKRIOMB.js";
9
9
  export {
10
10
  Banner,
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var CLI_VERSION = "0.30.2";
2
+ var CLI_VERSION = "0.30.3";
3
3
 
4
4
  export {
5
5
  CLI_VERSION
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CLI_VERSION
3
- } from "./chunk-XGKZ2VPZ.js";
3
+ } from "./chunk-FPZWMNAI.js";
4
4
  import {
5
5
  shouldUseColor,
6
6
  terminalTheme
package/dist/cli.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  } from "./chunk-NXM4JEOB.js";
40
40
  import {
41
41
  CLI_VERSION
42
- } from "./chunk-XGKZ2VPZ.js";
42
+ } from "./chunk-FPZWMNAI.js";
43
43
 
44
44
  // src/runtime/prepareInk.ts
45
45
  import fs from "fs";
@@ -2541,6 +2541,85 @@ var fetchCloudEvalJson = async ({
2541
2541
  return await response.json();
2542
2542
  };
2543
2543
 
2544
+ // src/signalstoryReviewAdapter.ts
2545
+ import {
2546
+ createSignalStoryEngine,
2547
+ renderPlainText
2548
+ } from "signalstory/core";
2549
+ import { renderGithubSummary } from "signalstory/markdown";
2550
+ var renderSignalStoryPlainText = (parts = []) => renderPlainText(parts);
2551
+ var REVIEW_FALLBACK_RULE_PACK = {
2552
+ id: "cloudeval-review-fallback",
2553
+ rules: [
2554
+ {
2555
+ id: "review-fallback",
2556
+ when: { signal: "gateStatus", exists: true },
2557
+ story: {
2558
+ id: "review-fallback",
2559
+ severity: "high",
2560
+ icon: "git-pull-request",
2561
+ priority: 100,
2562
+ sentence: [
2563
+ { text: "CloudEval review completed with " },
2564
+ { path: "gateStatus", marks: ["bold"] },
2565
+ { text: ". Well-Architected posture is " },
2566
+ { path: "score", marks: ["bold"] },
2567
+ { text: " (" },
2568
+ { path: "rating", marks: ["bold"] },
2569
+ { text: "), validation has " },
2570
+ { path: "failedTests", suffix: " failed unit tests", marks: ["bold"] },
2571
+ { text: ", policy checks are " },
2572
+ { path: "policyStatus", marks: ["bold"] },
2573
+ { text: ", and monthly cost is " },
2574
+ { path: "monthlyCost", marks: ["bold"] },
2575
+ { text: ". Prioritize " },
2576
+ { text: "failed validation checks", marks: ["bold"] },
2577
+ { text: " and " },
2578
+ { path: "weakestPillar", marks: ["bold"] },
2579
+ { text: " first." }
2580
+ ],
2581
+ rationale: [
2582
+ {
2583
+ text: "Failed validation, weak architecture pillars, and cost over budget are the highest-signal remediation inputs before merge."
2584
+ }
2585
+ ],
2586
+ action: { label: "Fix failed validation checks and rerun the review." }
2587
+ }
2588
+ }
2589
+ ]
2590
+ };
2591
+ var buildSignalStoryReviewFallback = (input) => {
2592
+ const engine = createSignalStoryEngine({
2593
+ rulePacks: [REVIEW_FALLBACK_RULE_PACK]
2594
+ });
2595
+ const stories = engine.generate({ signals: input });
2596
+ const primary = stories[0];
2597
+ if (!primary) {
2598
+ return null;
2599
+ }
2600
+ const shortSummary = renderSignalStoryPlainText(primary.sentence);
2601
+ const detailsMarkdown = [
2602
+ `**Main risk**
2603
+ ${renderSignalStoryPlainText(primary.sentence)}`,
2604
+ `**Why it matters**
2605
+ ${renderSignalStoryPlainText(primary.rationale ?? [])}`,
2606
+ `**Recommended actions**
2607
+ ${primary.action?.label ?? "Rerun the review after remediation."}`,
2608
+ "**Evidence used**\n**Gate status**, **Well-Architected score**, **validation totals**, **policy totals**, and **monthly cost**."
2609
+ ].join("\n\n");
2610
+ return {
2611
+ enabled: true,
2612
+ status: "fallback",
2613
+ fallbackUsed: true,
2614
+ warnings: [],
2615
+ shortSummary,
2616
+ detailsMarkdown,
2617
+ markdown: renderGithubSummary(stories, {
2618
+ title: "CloudEval review summary"
2619
+ })
2620
+ };
2621
+ };
2622
+
2544
2623
  // src/reviewCommand.ts
2545
2624
  var DIRTY_REVIEW_MESSAGE = "Reviews pushed commits only. Add --ignore-dirty to review HEAD anyway.";
2546
2625
  var runGit = async (cwd, args) => {
@@ -3167,22 +3246,184 @@ var compactCostRowsForChart = (rows, totalAmount, currency, options = {}) => {
3167
3246
  return result;
3168
3247
  };
3169
3248
  var markdownLink = (label, url) => url ? `[${label}](${url})` : label;
3170
- var openInCloudEvalLines = (links) => {
3249
+ var shieldSegment = (value) => encodeURIComponent(value.trim().replace(/-/g, "--").replace(/_/g, "__"));
3250
+ var badgeLink = ({
3251
+ label,
3252
+ message,
3253
+ color,
3254
+ url
3255
+ }) => {
3256
+ if (!url) {
3257
+ return void 0;
3258
+ }
3259
+ return `[![${label}](https://img.shields.io/badge/${shieldSegment(label)}-${shieldSegment(message)}-${color}?style=flat-square)](${url})`;
3260
+ };
3261
+ var openInCloudEvalBadges = (links) => {
3171
3262
  if (!links) {
3172
3263
  return [];
3173
3264
  }
3174
3265
  const reports = asRecord(links.reports);
3175
3266
  const downloads = asRecord(links.downloads);
3176
- const entries = [
3177
- ["Project preview", links.project],
3178
- ["Architecture report", reports.architecture],
3179
- ["Cost report", reports.cost],
3180
- ["Validation details", reports.validation],
3181
- ["Download PDF", downloads.pdf],
3182
- ["Workflow run", links.workflowRun],
3183
- ["Download review artifacts", downloads.reviewArtifacts]
3184
- ].filter((entry) => typeof entry[1] === "string" && entry[1].length > 0);
3185
- return entries.map(([label, url]) => `- ${markdownLink(label, url)}`);
3267
+ return [
3268
+ badgeLink({
3269
+ label: "Project",
3270
+ message: "preview",
3271
+ color: "2563eb",
3272
+ url: typeof links.project === "string" ? links.project : void 0
3273
+ }),
3274
+ badgeLink({
3275
+ label: "Report",
3276
+ message: "architecture",
3277
+ color: "16a34a",
3278
+ url: typeof reports.architecture === "string" ? reports.architecture : void 0
3279
+ }),
3280
+ badgeLink({
3281
+ label: "Cost",
3282
+ message: "drilldown",
3283
+ color: "0f766e",
3284
+ url: typeof reports.cost === "string" ? reports.cost : void 0
3285
+ }),
3286
+ badgeLink({
3287
+ label: "Validation",
3288
+ message: "details",
3289
+ color: "d97706",
3290
+ url: typeof reports.validation === "string" ? reports.validation : void 0
3291
+ }),
3292
+ badgeLink({
3293
+ label: "PDF",
3294
+ message: "download",
3295
+ color: "7c3aed",
3296
+ url: typeof downloads.pdf === "string" ? downloads.pdf : void 0
3297
+ }),
3298
+ badgeLink({
3299
+ label: "Workflow",
3300
+ message: "run",
3301
+ color: "475569",
3302
+ url: typeof links.workflowRun === "string" ? links.workflowRun : void 0
3303
+ }),
3304
+ badgeLink({
3305
+ label: "Artifacts",
3306
+ message: "review",
3307
+ color: "475569",
3308
+ url: typeof downloads.reviewArtifacts === "string" ? downloads.reviewArtifacts : void 0
3309
+ })
3310
+ ].filter((entry) => Boolean(entry));
3311
+ };
3312
+ var signalTableCell = (summaryLine) => {
3313
+ const match = summaryLine.match(/^(\S+)\s+[^:]+:\s*(.+)$/);
3314
+ if (!match) {
3315
+ return compactMarkdownCell(summaryLine);
3316
+ }
3317
+ return `${match[1]} **${compactMarkdownCell(match[2])}**`;
3318
+ };
3319
+ var reviewDecisionLine = ({
3320
+ gateStatus,
3321
+ score,
3322
+ rating
3323
+ }) => {
3324
+ if (gateStatus === "FAIL") {
3325
+ return `${statusIcon(gateStatus)} **FAIL** - configured gates failed. Do not merge until the action queue is resolved and CloudEval is rerun.`;
3326
+ }
3327
+ if (gateStatus === "WARN") {
3328
+ return `${statusIcon(gateStatus)} **WARN** - configured gates are warning-only or non-blocking for this run. Review the action queue before merge.`;
3329
+ }
3330
+ if (rating === "CRITICAL" || rating === "POOR") {
3331
+ return `${statusIcon(gateStatus)} **PASS** - configured gates passed, but observed Well-Architected posture is **${formatScore(score)} (${rating})**. Tighten gate thresholds if this posture should block pull requests.`;
3332
+ }
3333
+ return `${statusIcon(gateStatus)} **PASS** - configured gates passed for this review. Use the drilldowns below to keep the posture improving.`;
3334
+ };
3335
+ var strongestPillarRisk = (pillars) => pillars.map((pillar) => ({
3336
+ label: String(pillar.label ?? pillar.id ?? "Well-Architected pillar"),
3337
+ score: numberFrom(pillar.score)
3338
+ })).filter((pillar) => pillar.score !== void 0).sort((left, right) => left.score - right.score).map((pillar) => ({
3339
+ ...pillar,
3340
+ rating: scoreRating(pillar.score)
3341
+ }))[0];
3342
+ var reviewActionItems = ({
3343
+ data,
3344
+ pillars,
3345
+ cost,
3346
+ validation
3347
+ }) => {
3348
+ const endpointActions = Array.isArray(data.aiSummary?.recommendedActions) ? data.aiSummary.recommendedActions.map((action) => compactMarkdownCell(action, "")).filter(Boolean) : [];
3349
+ const failedUnitTests = numberFrom(validation?.unitTests?.failed) ?? 0;
3350
+ const failedPolicyChecks = numberFrom(validation?.policyChecks?.failed) ?? 0;
3351
+ const weakest = strongestPillarRisk(pillars);
3352
+ const currentCost = numberFrom(cost?.amount);
3353
+ const savings = numberFrom(data.gate?.cost?.estimatedSavings?.amount);
3354
+ const currency = cost?.currency ?? data.gate?.cost?.estimatedSavings?.currency;
3355
+ const actions = [...endpointActions];
3356
+ if (failedUnitTests > 0 || failedPolicyChecks > 0) {
3357
+ const parts = [];
3358
+ if (failedUnitTests > 0) {
3359
+ parts.push(`${displayNumber(failedUnitTests)} failed unit tests`);
3360
+ }
3361
+ if (failedPolicyChecks > 0) {
3362
+ parts.push(`${displayNumber(failedPolicyChecks)} failed policy checks`);
3363
+ }
3364
+ actions.push(
3365
+ `**Fix validation failures** - resolve ${joinReadableList(parts)} and rerun CloudEval review.`
3366
+ );
3367
+ }
3368
+ if (weakest) {
3369
+ actions.push(
3370
+ `**Prioritize ${weakest.label}** - weakest pillar is **${formatScore(weakest.score)} (${weakest.rating ?? "UNKNOWN"})**.`
3371
+ );
3372
+ }
3373
+ if (currentCost !== void 0) {
3374
+ const savingsText = savings !== void 0 && savings > 0 ? `; review the estimated **${formatMonthlyMoney(savings, currency)}** savings` : "";
3375
+ actions.push(
3376
+ `**Review cost drivers** - current monthly cost is **${formatMonthlyMoney(currentCost, currency)}**${savingsText}.`
3377
+ );
3378
+ }
3379
+ if (Array.isArray(data.gate?.failures)) {
3380
+ for (const failure of data.gate.failures) {
3381
+ actions.push(`**Address gate failure** - ${compactMarkdownCell(failure)}.`);
3382
+ }
3383
+ }
3384
+ actions.push("**Rerun CloudEval** - confirm the updated gate, reports, and PR comment after remediation.");
3385
+ const unique2 = [];
3386
+ for (const action of actions) {
3387
+ if (!unique2.includes(action)) {
3388
+ unique2.push(action);
3389
+ }
3390
+ if (unique2.length >= 3) {
3391
+ break;
3392
+ }
3393
+ }
3394
+ return unique2.map((action, index) => `${index + 1}. ${action}`);
3395
+ };
3396
+ var mermaidAxisId = (label) => {
3397
+ const normalized = normalizeKey(label).replace(/[^a-z0-9_]/g, "_");
3398
+ return normalized || "pillar";
3399
+ };
3400
+ var wellArchitectedRadarLines = (pillars) => {
3401
+ const scored = pillars.map((pillar) => {
3402
+ const label = String(pillar.label ?? pillar.id ?? "Pillar");
3403
+ const score = numberFrom(pillar.score);
3404
+ return score === void 0 ? void 0 : {
3405
+ id: mermaidAxisId(label),
3406
+ label,
3407
+ score
3408
+ };
3409
+ }).filter(
3410
+ (pillar) => pillar !== void 0
3411
+ );
3412
+ if (scored.length < 3) {
3413
+ return [];
3414
+ }
3415
+ return [
3416
+ "```mermaid",
3417
+ "radar-beta",
3418
+ " title Well-Architected posture",
3419
+ ` axis ${scored.map((pillar) => `${pillar.id}["${mermaidLabel(pillar.label)}"]`).join(", ")}`,
3420
+ ` curve current["Current"]{${scored.map((pillar) => trimNumber(pillar.score, 3)).join(", ")}}`,
3421
+ " max 100",
3422
+ " min 0",
3423
+ "```",
3424
+ "",
3425
+ "_If GitHub does not render Mermaid radar charts yet, use the table below as the fallback._"
3426
+ ];
3186
3427
  };
3187
3428
  var monthlyCostImpactLines = (currentAmount, savingsAmount, currency) => {
3188
3429
  const current = numberFrom(currentAmount);
@@ -3665,7 +3906,7 @@ var renderAiSummarySections = (shortSummary, detailsMarkdown) => {
3665
3906
  lines.push(
3666
3907
  "",
3667
3908
  "<details>",
3668
- "<summary>AI details</summary>",
3909
+ "<summary><strong>Detailed AI reviewer note - evidence, reasoning, and next actions</strong></summary>",
3669
3910
  "",
3670
3911
  detailsMarkdown.trim(),
3671
3912
  "",
@@ -3698,7 +3939,7 @@ var reviewSurface = () => {
3698
3939
  const ref = String(process.env.GITHUB_REF ?? "").toLowerCase();
3699
3940
  return event.startsWith("pull_request") || ref.startsWith("refs/pull/") ? "pull_request" : "local_review";
3700
3941
  };
3701
- var buildReviewSummaryPayload = (data) => ({
3942
+ var buildReviewSummaryPayload = (data, preferences = {}) => ({
3702
3943
  source: process.env.GITHUB_ACTIONS === "true" ? "github_action" : "cli",
3703
3944
  surface: reviewSurface(),
3704
3945
  project: data.project ?? { id: data.projectId },
@@ -3717,7 +3958,12 @@ var buildReviewSummaryPayload = (data) => ({
3717
3958
  validation: data.gate?.validation ?? {},
3718
3959
  policy: data.gate?.validation?.policy ?? data.gate?.policy ?? {},
3719
3960
  architecture_signals: data.gate?.architecture ?? {},
3720
- changed_files: data.changedFiles ?? []
3961
+ changed_files: data.changedFiles ?? [],
3962
+ ai_preferences: {
3963
+ mode: preferences.mode ?? "ask",
3964
+ ...preferences.agentProfileId ? { agent_profile_id: preferences.agentProfileId } : {},
3965
+ ...preferences.model ? { model: preferences.model } : {}
3966
+ }
3721
3967
  });
3722
3968
  var deterministicAiSummary = (data, error) => {
3723
3969
  const score = data.gate?.wellArchitected?.overall?.score ?? data.gate?.overallScore;
@@ -3732,6 +3978,27 @@ var deterministicAiSummary = (data, error) => {
3732
3978
  )[0] : void 0;
3733
3979
  const weakestPillarLabel = weakestPillar?.label ?? weakestPillar?.id ?? "the weakest Well-Architected pillar";
3734
3980
  const highRisk = numberFrom(data.gate?.wellArchitected?.risks?.high) ?? 0;
3981
+ const signalStorySummary = buildSignalStoryReviewFallback({
3982
+ gateStatus: String(data.gate?.status ?? "UNKNOWN").toUpperCase(),
3983
+ score: formatScore(score),
3984
+ rating,
3985
+ failedTests,
3986
+ policyStatus,
3987
+ monthlyCost: formatMonthlyMoney(cost?.amount, cost?.currency),
3988
+ weakestPillar: weakestPillarLabel
3989
+ });
3990
+ if (signalStorySummary) {
3991
+ const signalStoryShortSummary = String(signalStorySummary.shortSummary ?? "").trim();
3992
+ const signalStoryDetailsMarkdown = String(signalStorySummary.detailsMarkdown ?? "").trim();
3993
+ return {
3994
+ ...signalStorySummary,
3995
+ warnings: error ? [`Review summary endpoint failed: ${error}`] : [],
3996
+ markdown: renderAiSummarySections(
3997
+ signalStoryShortSummary,
3998
+ signalStoryDetailsMarkdown
3999
+ )
4000
+ };
4001
+ }
3735
4002
  const summary = [
3736
4003
  `CloudEval review completed with **${String(data.gate?.status ?? "UNKNOWN").toUpperCase()}**.`,
3737
4004
  `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)}**.`,
@@ -3758,7 +4025,11 @@ Fix **${displayNumber(failedTests)} failed unit tests**, address **${weakestPill
3758
4025
  };
3759
4026
  var generateAiSummary = async (input) => {
3760
4027
  try {
3761
- const payload = buildReviewSummaryPayload(input.data);
4028
+ const payload = buildReviewSummaryPayload(input.data, {
4029
+ model: input.model,
4030
+ mode: input.mode,
4031
+ agentProfileId: input.agentProfileId
4032
+ });
3762
4033
  const response = await fetchCloudEvalJson({
3763
4034
  baseUrl: input.baseUrl,
3764
4035
  authToken: input.token,
@@ -3808,7 +4079,8 @@ var buildMarkdownSummary = (data) => {
3808
4079
  const repository = String(data.repo ?? "unknown repository");
3809
4080
  const ref = String(data.ref ?? "unknown ref");
3810
4081
  const commit = String(data.commitSha ?? "unknown").slice(0, 12);
3811
- const pillarLines = Array.isArray(data.gate?.wellArchitected?.pillars) ? data.gate.wellArchitected.pillars.map((pillar) => {
4082
+ const pillars = Array.isArray(data.gate?.wellArchitected?.pillars) ? data.gate.wellArchitected.pillars : [];
4083
+ const pillarLines = pillars.length ? pillars.map((pillar) => {
3812
4084
  const rating = scoreRating(pillar.score);
3813
4085
  return `| ${pillar.label} | **${formatScore(pillar.score)}** | ${scoreRatingIcon(rating)} ${rating ?? "UNKNOWN"} |`;
3814
4086
  }) : [];
@@ -3834,47 +4106,87 @@ var buildMarkdownSummary = (data) => {
3834
4106
  );
3835
4107
  const costPieRows = namedResourceCosts.length ? positiveResourceCosts : costServices.filter((service) => service.amount > 0);
3836
4108
  const costPieTitle = namedResourceCosts.length ? "Monthly cost by resource" : "Monthly cost by service";
3837
- const openLinks = openInCloudEvalLines(data.links);
4109
+ const linkBadges = openInCloudEvalBadges(data.links);
3838
4110
  const architectureLines = architectureSignalLines({
3839
4111
  architecture,
3840
4112
  costServices,
3841
4113
  costCurrency: cost?.currency,
3842
4114
  highRiskFindings: data.gate?.wellArchitected?.risks?.high,
3843
- pillars: Array.isArray(data.gate?.wellArchitected?.pillars) ? data.gate.wellArchitected.pillars : []
4115
+ pillars
3844
4116
  });
3845
4117
  const validationRows = validationFailureRows(validation);
3846
4118
  const overallRating = scoreRating(score);
4119
+ const actionLines = reviewActionItems({
4120
+ data,
4121
+ pillars,
4122
+ cost,
4123
+ validation
4124
+ });
4125
+ const radarLines = wellArchitectedRadarLines(pillars);
3847
4126
  const lines = [
3848
- `${statusIcon(data.gate?.status)} **Overall** : ${gateStatus}`,
3849
- `${scoreRatingIcon(overallRating)} Well-Architected Posture: ${formatScore(score)} (${overallRating ?? "UNKNOWN"})`,
3850
- validationSummaryLine(validation),
3851
- policySummaryLine(validation),
3852
- costSummaryLine(cost),
4127
+ "## CloudEval infrastructure review",
4128
+ "",
4129
+ "| Signal | Result |",
4130
+ "| --- | --- |",
4131
+ `| Merge gate | ${statusIcon(data.gate?.status)} **${gateStatus}** |`,
4132
+ `| Observed posture | ${scoreRatingIcon(overallRating)} **${formatScore(score)} (${overallRating ?? "UNKNOWN"})** |`,
4133
+ `| Validation | ${signalTableCell(validationSummaryLine(validation))} |`,
4134
+ `| Policy | ${signalTableCell(policySummaryLine(validation))} |`,
4135
+ `| Cost | ${signalTableCell(costSummaryLine(cost))} |`
4136
+ ];
4137
+ if (linkBadges.length) {
4138
+ lines.push("", "### Links", "", linkBadges.join(" "));
4139
+ }
4140
+ lines.push(
3853
4141
  "",
3854
- "#### Source",
4142
+ "### Decision",
4143
+ "",
4144
+ reviewDecisionLine({ gateStatus, score, rating: overallRating }),
4145
+ "",
4146
+ "<details>",
4147
+ "<summary><strong>Source</strong></summary>",
3855
4148
  "",
3856
4149
  `- **CloudEval project**: ${projectDisplay}`,
3857
4150
  `- **Repository**: \`${repository}\``,
3858
4151
  `- **Ref**: \`${ref}\``,
3859
- `- **Commit**: \`${commit}\``
3860
- ];
3861
- if (openLinks.length) {
3862
- lines.push("", "#### Open in CloudEval", "", ...openLinks);
3863
- }
4152
+ `- **Commit**: \`${commit}\``,
4153
+ "",
4154
+ "</details>"
4155
+ );
3864
4156
  if (data.aiSummary?.markdown) {
3865
- lines.push("", "#### AI summary", "", data.aiSummary.markdown);
4157
+ lines.push("", "### AI summary", "", data.aiSummary.markdown);
4158
+ }
4159
+ if (actionLines.length) {
4160
+ lines.push(
4161
+ "",
4162
+ "<details open>",
4163
+ `<summary><strong>Action queue - ${actionLines.length} recommended fixes</strong></summary>`,
4164
+ "",
4165
+ ...actionLines,
4166
+ "",
4167
+ "</details>"
4168
+ );
3866
4169
  }
3867
4170
  if (Array.isArray(data.gate?.failures) && data.gate.failures.length) {
3868
- lines.push("", "#### Gate failures", "", ...data.gate.failures.map((failure) => `- ${failure}`));
4171
+ lines.push(
4172
+ "",
4173
+ "<details>",
4174
+ "<summary><strong>Gate failures</strong></summary>",
4175
+ "",
4176
+ ...data.gate.failures.map((failure) => `- ${failure}`),
4177
+ "",
4178
+ "</details>"
4179
+ );
3869
4180
  }
3870
4181
  if (pillarLines.length) {
3871
4182
  lines.push(
3872
4183
  "",
3873
4184
  "<details>",
3874
- "<summary>Well-Architected drilldown</summary>",
4185
+ "<summary><strong>Well-Architected drilldown</strong></summary>",
3875
4186
  "",
3876
4187
  ...riskLines,
3877
4188
  "",
4189
+ ...radarLines.length ? [...radarLines, ""] : [],
3878
4190
  "| Pillar | Score | Rating |",
3879
4191
  "| --- | ---: | --- |",
3880
4192
  ...pillarLines,
@@ -3921,7 +4233,7 @@ var buildMarkdownSummary = (data) => {
3921
4233
  lines.push(
3922
4234
  "",
3923
4235
  "<details>",
3924
- "<summary>Cost drilldown</summary>",
4236
+ "<summary><strong>Cost drilldown</strong></summary>",
3925
4237
  "",
3926
4238
  ...costLines,
3927
4239
  "",
@@ -3933,7 +4245,7 @@ var buildMarkdownSummary = (data) => {
3933
4245
  lines.push(
3934
4246
  "",
3935
4247
  "<details>",
3936
- `<summary>${validationRows.length ? "Validation failures" : "Validation details"}</summary>`,
4248
+ `<summary><strong>${validationRows.length ? "Validation failures" : "Validation details"}</strong></summary>`,
3937
4249
  "",
3938
4250
  ...validationDetailLines(validation),
3939
4251
  ...validationRows.length ? [
@@ -3950,7 +4262,7 @@ var buildMarkdownSummary = (data) => {
3950
4262
  lines.push(
3951
4263
  "",
3952
4264
  "<details>",
3953
- "<summary>Architecture signals</summary>",
4265
+ "<summary><strong>Architecture signals</strong></summary>",
3954
4266
  "",
3955
4267
  ...architectureLines,
3956
4268
  "",
@@ -16869,7 +17181,7 @@ program.command("tui").description("Open the CloudEval Terminal UI").option("--b
16869
17181
  const { assertSecureBaseUrl } = await import("./dist-6LEMVXIY.js");
16870
17182
  const [{ render }, { App }] = await Promise.all([
16871
17183
  import("ink"),
16872
- import("./App-5LBIATTG.js")
17184
+ import("./App-SKVX7NAF.js")
16873
17185
  ]);
16874
17186
  const baseUrl = await resolveBaseUrl(options, command);
16875
17187
  assertSecureBaseUrl(baseUrl);
@@ -16930,7 +17242,7 @@ program.command("chat").description("Start an interactive chat session").option(
16930
17242
  const { assertSecureBaseUrl } = await import("./dist-6LEMVXIY.js");
16931
17243
  const [{ render }, { App }] = await Promise.all([
16932
17244
  import("ink"),
16933
- import("./App-5LBIATTG.js")
17245
+ import("./App-SKVX7NAF.js")
16934
17246
  ]);
16935
17247
  const baseUrl = await resolveBaseUrl(options, command);
16936
17248
  assertSecureBaseUrl(baseUrl);
@@ -17722,7 +18034,7 @@ Error: ${errorMsg}
17722
18034
  program.command("banner").description("Preview the startup banner and terminal capabilities").action(async () => {
17723
18035
  const { render } = await import("ink");
17724
18036
  const BannerPreview = React.lazy(async () => ({
17725
- default: (await import("./Banner-LJ5AITDD.js")).Banner
18037
+ default: (await import("./Banner-CRBHEOTC.js")).Banner
17726
18038
  }));
17727
18039
  render(
17728
18040
  /* @__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.30.2",
3
+ "version": "0.30.3",
4
4
  "license": "LicenseRef-CloudEval-CLI",
5
5
  "type": "module",
6
6
  "description": "Review Cloud infra-as-code and live environments from CLI, CI, and MCP agents.",
@@ -86,6 +86,7 @@
86
86
  "marked-terminal": "^7.3.0",
87
87
  "react": "^18.3.1",
88
88
  "react-devtools-core": "^4.28.5",
89
+ "signalstory": "https://github.com/ganakailabs/signalstory/archive/refs/tags/v0.1.0.tar.gz",
89
90
  "sql.js": "^1.14.1"
90
91
  },
91
92
  "devDependencies": {
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.30.2",
17
+ "versionInfo": "0.30.3",
18
18
  "downloadLocation": "https://github.com/ganakailabs/cloudeval-cli",
19
19
  "filesAnalyzed": false,
20
20
  "licenseConcluded": "LicenseRef-CloudEval-CLI",
@@ -2254,6 +2254,17 @@
2254
2254
  "copyrightText": "Ben Coe",
2255
2255
  "summary": "when you want to fire an event no matter how a process exits."
2256
2256
  },
2257
+ {
2258
+ "SPDXID": "SPDXRef-Package-signalstory-0.1.0",
2259
+ "name": "signalstory",
2260
+ "versionInfo": "0.1.0",
2261
+ "downloadLocation": "NOASSERTION",
2262
+ "filesAnalyzed": false,
2263
+ "licenseConcluded": "Apache-2.0",
2264
+ "licenseDeclared": "Apache-2.0",
2265
+ "copyrightText": "NOASSERTION",
2266
+ "summary": "Composable signal-to-story generation for evidence-grounded product, ops, and review narratives."
2267
+ },
2257
2268
  {
2258
2269
  "SPDXID": "SPDXRef-Package-skin-tone-2.0.0",
2259
2270
  "name": "skin-tone",
@@ -3678,6 +3689,11 @@
3678
3689
  "relationshipType": "DEPENDS_ON",
3679
3690
  "relatedSpdxElement": "SPDXRef-Package-signal-exit-3.0.7"
3680
3691
  },
3692
+ {
3693
+ "spdxElementId": "SPDXRef-Package-CloudEval-CLI",
3694
+ "relationshipType": "DEPENDS_ON",
3695
+ "relatedSpdxElement": "SPDXRef-Package-signalstory-0.1.0"
3696
+ },
3681
3697
  {
3682
3698
  "spdxElementId": "SPDXRef-Package-CloudEval-CLI",
3683
3699
  "relationshipType": "DEPENDS_ON",