@viberaven/cli 0.1.0-beta.1 → 0.1.0-beta.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/dist/cli.js CHANGED
@@ -9,6 +9,10 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
9
  var __commonJS = (cb, mod) => function __require() {
10
10
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
11
  };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
12
16
  var __copyProps = (to, from, except, desc) => {
13
17
  if (from && typeof from === "object" || typeof from === "function") {
14
18
  for (let key of __getOwnPropNames(from))
@@ -25,6 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
29
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
30
  mod
27
31
  ));
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
33
 
29
34
  // node_modules/picocolors/picocolors.js
30
35
  var require_picocolors = __commonJS({
@@ -155,6 +160,13 @@ var require_src = __commonJS({
155
160
  });
156
161
 
157
162
  // src/cli.ts
163
+ var cli_exports = {};
164
+ __export(cli_exports, {
165
+ buildHelpText: () => buildHelpText,
166
+ formatScanJsonStdout: () => formatScanJsonStdout,
167
+ parseArgs: () => parseArgs
168
+ });
169
+ module.exports = __toCommonJS(cli_exports);
158
170
  var import_node_path8 = require("node:path");
159
171
 
160
172
  // src/config.ts
@@ -241,8 +253,18 @@ async function loadCredentials() {
241
253
  email: typeof parsed.email === "string" ? parsed.email : void 0,
242
254
  plan: typeof parsed.plan === "string" ? parsed.plan : void 0
243
255
  };
244
- } catch {
245
- return void 0;
256
+ } catch (error) {
257
+ if (error.code !== "ENOENT") {
258
+ return void 0;
259
+ }
260
+ const accessToken = process.env.VIBERAVEN_ACCESS_TOKEN?.trim();
261
+ if (!accessToken) {
262
+ return void 0;
263
+ }
264
+ return {
265
+ accessToken,
266
+ apiBaseUrl: resolveApiBaseUrl()
267
+ };
246
268
  }
247
269
  }
248
270
  async function saveCredentials(credentials) {
@@ -454,6 +476,16 @@ function isRecord(value) {
454
476
  return typeof value === "object" && value !== null && !Array.isArray(value);
455
477
  }
456
478
 
479
+ // src/statusLabels.ts
480
+ var READY = "READY";
481
+ var LOGIN_REQUIRED = "LOGIN_REQUIRED";
482
+ var UPGRADE_REQUIRED = "UPGRADE_REQUIRED";
483
+ var MANUAL_ACTION_REQUIRED = "MANUAL_ACTION_REQUIRED";
484
+ var ERROR = "ERROR";
485
+ function formatAgentStatus(label, message) {
486
+ return `${label}: ${message}`;
487
+ }
488
+
457
489
  // src/account.ts
458
490
  async function fetchAccountMe(apiBaseUrl, accessToken) {
459
491
  const url = `${normalizeBaseUrl(apiBaseUrl)}/v1/account/me`;
@@ -510,8 +542,9 @@ function formatUsageLine(usage) {
510
542
  function formatScanLimitMessage(upgradeUrl) {
511
543
  return [
512
544
  "",
513
- "Free scan limit reached. Upgrade to Pro to continue.",
545
+ formatAgentStatus(UPGRADE_REQUIRED, "Free scan limit reached. Upgrade to Pro to continue."),
514
546
  "Your last scan artifacts are unchanged if you already ran a scan in this repo.",
547
+ "Do not keep retrying this scan until the user upgrades or quota resets.",
515
548
  "",
516
549
  `Upgrade & account: ${upgradeUrl}`,
517
550
  ""
@@ -539,7 +572,7 @@ async function runDeviceLogin(apiBaseUrl) {
539
572
  continue;
540
573
  }
541
574
  if (result.status === "expired") {
542
- throw new Error("Sign-in expired. Run `viberaven login` again.");
575
+ throw new Error(formatAgentStatus(LOGIN_REQUIRED, "Sign-in expired. Ask the user to run `npx -y @viberaven/cli@beta login`, complete browser/device approval, then rerun `npx -y @viberaven/cli@beta scan`."));
543
576
  }
544
577
  if (result.status === "denied") {
545
578
  throw new Error("Sign-in was denied.");
@@ -570,7 +603,7 @@ async function runDeviceLogin(apiBaseUrl) {
570
603
  return;
571
604
  }
572
605
  }
573
- throw new Error("Sign-in timed out. Run `viberaven login` again.");
606
+ throw new Error(formatAgentStatus(LOGIN_REQUIRED, "Sign-in timed out. Ask the user to run `npx -y @viberaven/cli@beta login`, complete browser/device approval, then rerun `npx -y @viberaven/cli@beta scan`."));
574
607
  }
575
608
  function buildVerificationUrl(verificationUrl, deviceCode) {
576
609
  try {
@@ -586,7 +619,7 @@ async function requireCredentials(apiBaseUrl) {
586
619
  const creds = await loadCredentials();
587
620
  const base = apiBaseUrl ?? creds?.apiBaseUrl ?? resolveApiBaseUrl();
588
621
  if (!creds?.accessToken) {
589
- throw new Error("Not signed in. Run `viberaven login` first (or set credentials via device flow).");
622
+ throw new Error(formatAgentStatus(LOGIN_REQUIRED, "Not signed in. Ask the user to run `npx -y @viberaven/cli@beta login`, complete browser/device approval, then rerun `npx -y @viberaven/cli@beta scan`."));
590
623
  }
591
624
  return { accessToken: creds.accessToken, apiBaseUrl: base };
592
625
  }
@@ -1442,6 +1475,17 @@ var PROVIDERS = [
1442
1475
  provider("authjs", "Auth.js", ["authjs", "auth", "nextauth"], ["auth"], ["auth"], "authjs", {
1443
1476
  docsUrl: "https://authjs.dev"
1444
1477
  }),
1478
+ provider("auth0", "Auth0", ["auth0"], ["auth"], ["auth"], "auth0", {
1479
+ docsUrl: "https://auth0.com/docs",
1480
+ dashboardUrl: "https://manage.auth0.com"
1481
+ }),
1482
+ provider("better-auth", "Better Auth", ["betterauth", "better-auth"], ["auth"], ["auth"], "better-auth", {
1483
+ docsUrl: "https://www.better-auth.com/docs"
1484
+ }),
1485
+ provider("firebase", "Firebase", ["firebase", "firestore"], ["database"], ["database"], "firebase", {
1486
+ docsUrl: "https://firebase.google.com/docs",
1487
+ dashboardUrl: "https://console.firebase.google.com"
1488
+ }),
1445
1489
  provider("neon", "Neon", ["neon"], ["database"], ["database"], "neon", {
1446
1490
  docsUrl: "https://neon.tech/docs",
1447
1491
  dashboardUrl: "https://console.neon.tech",
@@ -1511,6 +1555,10 @@ var PROVIDERS = [
1511
1555
  dashboardUrl: "https://polar.sh/dashboard",
1512
1556
  verification: { supportsReadOnly: false }
1513
1557
  }),
1558
+ provider("lemon-squeezy", "Lemon Squeezy", ["lemonsqueezy", "lemon-squeezy", "lemon squeezy"], ["payments"], ["payments"], "lemon-squeezy", {
1559
+ docsUrl: "https://docs.lemonsqueezy.com",
1560
+ dashboardUrl: "https://app.lemonsqueezy.com"
1561
+ }),
1514
1562
  provider("vercel", "Vercel", ["vercel"], ["deployment"], ["deployment"], "vercel", {
1515
1563
  docsUrl: "https://vercel.com/docs",
1516
1564
  dashboardUrl: "https://vercel.com/dashboard",
@@ -1523,7 +1571,7 @@ var PROVIDERS = [
1523
1571
  },
1524
1572
  verification: { supportsReadOnly: true }
1525
1573
  }),
1526
- provider("netlify", "Netlify", ["netlify"], ["deployment"], [], "netlify", {
1574
+ provider("netlify", "Netlify", ["netlify"], ["deployment"], ["deployment"], "netlify", {
1527
1575
  docsUrl: "https://docs.netlify.com",
1528
1576
  dashboardUrl: "https://app.netlify.com",
1529
1577
  mcp: {
@@ -1535,7 +1583,27 @@ var PROVIDERS = [
1535
1583
  },
1536
1584
  verification: { supportsReadOnly: false }
1537
1585
  }),
1538
- provider("aws", "AWS", ["aws"], ["deployment"], [], "aws", {
1586
+ provider("render", "Render", ["render", "rendercom"], ["deployment"], ["deployment"], "render", {
1587
+ docsUrl: "https://render.com/docs",
1588
+ dashboardUrl: "https://dashboard.render.com"
1589
+ }),
1590
+ provider("railway", "Railway", ["railway", "railwayapp"], ["deployment"], ["deployment"], "railway", {
1591
+ docsUrl: "https://docs.railway.com",
1592
+ dashboardUrl: "https://railway.com"
1593
+ }),
1594
+ provider("cloudflare", "Cloudflare", ["cloudflare", "cloudflarepages", "workers"], ["deployment"], ["deployment"], "cloudflare", {
1595
+ docsUrl: "https://developers.cloudflare.com",
1596
+ dashboardUrl: "https://dash.cloudflare.com",
1597
+ mcp: {
1598
+ label: "Cloudflare",
1599
+ serverName: "cloudflare-api",
1600
+ vscodeServer: { type: "http", url: "https://mcp.cloudflare.com/mcp" },
1601
+ cursorServer: { url: "https://mcp.cloudflare.com/mcp" },
1602
+ keyInstructions: "Cloudflare MCP uses Cloudflare account authentication. Finish browser sign-in or provide an API token only when prompted."
1603
+ },
1604
+ verification: { supportsReadOnly: true }
1605
+ }),
1606
+ provider("aws", "AWS", ["aws"], ["deployment"], ["deployment"], "aws", {
1539
1607
  docsUrl: "https://docs.aws.amazon.com",
1540
1608
  dashboardUrl: "https://console.aws.amazon.com"
1541
1609
  }),
@@ -1567,6 +1635,18 @@ var PROVIDERS = [
1567
1635
  docsUrl: "https://docs.logrocket.com",
1568
1636
  dashboardUrl: "https://app.logrocket.com"
1569
1637
  }),
1638
+ provider("github", "GitHub Actions", ["github", "githubactions"], ["testing"], [], "github", {
1639
+ docsUrl: "https://docs.github.com/actions",
1640
+ dashboardUrl: "https://github.com",
1641
+ mcp: {
1642
+ label: "GitHub",
1643
+ serverName: "github",
1644
+ vscodeServer: { type: "http", url: "https://api.githubcopilot.com/mcp/" },
1645
+ cursorServer: { url: "https://api.githubcopilot.com/mcp/" },
1646
+ keyInstructions: "GitHub MCP should use IDE/GitHub authentication. Use read-only repository, Actions, and branch-protection queries for verification."
1647
+ },
1648
+ verification: { supportsReadOnly: true }
1649
+ }),
1570
1650
  provider("playwright", "Playwright", ["playwright", "playwrighttest"], ["testing"], [], "playwright", {
1571
1651
  docsUrl: "https://playwright.dev/docs/intro",
1572
1652
  mcp: {
@@ -1590,7 +1670,7 @@ var PROVIDERS = [
1590
1670
  },
1591
1671
  verification: { supportsReadOnly: false }
1592
1672
  }),
1593
- provider("bot-protection", "Bot protection", ["botprotection", "cloudflareturnstile", "turnstile", "cloudflare"], ["security"], ["security"], "bot-protection", {
1673
+ provider("bot-protection", "Bot protection", ["botprotection", "cloudflareturnstile", "turnstile"], ["security"], ["security"], "bot-protection", {
1594
1674
  docsUrl: "https://developers.cloudflare.com/turnstile",
1595
1675
  dashboardUrl: "https://dash.cloudflare.com",
1596
1676
  mcp: {
@@ -1773,6 +1853,269 @@ function titleizeProvider(provider2) {
1773
1853
  );
1774
1854
  }
1775
1855
 
1856
+ // ../../src/station/verificationLayer/types.ts
1857
+ function providerCheckId(provider2, checkKey) {
1858
+ return `${provider2}:${checkKey}`;
1859
+ }
1860
+ var PROVIDER_CONNECTION_BLOCKS_VERIFY = [
1861
+ "not_configured",
1862
+ "configured",
1863
+ "unknown_runtime",
1864
+ "unsupported"
1865
+ ];
1866
+ function coerceProviderVerificationStatus(status, connectionState) {
1867
+ if (status !== "verified") {
1868
+ return status;
1869
+ }
1870
+ if (PROVIDER_CONNECTION_BLOCKS_VERIFY.includes(connectionState)) {
1871
+ return connectionState === "not_configured" || connectionState === "configured" ? "needs_mcp" : "unknown";
1872
+ }
1873
+ return status;
1874
+ }
1875
+ function mapVerificationStatusToMissionStatus(status) {
1876
+ switch (status) {
1877
+ case "verified":
1878
+ return "passed";
1879
+ case "missing":
1880
+ return "missing";
1881
+ case "unknown":
1882
+ return "unknown";
1883
+ case "needs_mcp":
1884
+ return "needs-connection";
1885
+ case "manual":
1886
+ return "manual-required";
1887
+ default:
1888
+ return "failed";
1889
+ }
1890
+ }
1891
+ function mapEvidenceSourceToLegacyEvidenceClass(source, status) {
1892
+ if (source === "repo") {
1893
+ return status === "verified" ? "repo-verified" : "missing-repo-fix";
1894
+ }
1895
+ if (source === "manual") {
1896
+ return "manual-dashboard";
1897
+ }
1898
+ if (source === "mcp") {
1899
+ return "mcp-verifier";
1900
+ }
1901
+ return "mcp-verifier";
1902
+ }
1903
+ function isProviderLayerCheck(check) {
1904
+ return check.evidenceSource === "provider" || check.evidenceSource === "mcp";
1905
+ }
1906
+ function isRepoLayerCheck(check) {
1907
+ return check.evidenceSource === "repo";
1908
+ }
1909
+ function computeLayerReadinessPercent(checks, layer) {
1910
+ const filtered = checks.filter(
1911
+ (check) => layer === "repo" ? isRepoLayerCheck(check) : isProviderLayerCheck(check)
1912
+ );
1913
+ if (filtered.length === 0) {
1914
+ return 100;
1915
+ }
1916
+ const verified = filtered.filter((check) => check.status === "verified").length;
1917
+ return Math.round(verified / filtered.length * 100);
1918
+ }
1919
+ function aggregateReadinessPercents(providerResults) {
1920
+ if (providerResults.length === 0) {
1921
+ return { repoReadinessPercent: 100, providerReadinessPercent: 100 };
1922
+ }
1923
+ const repoSum = providerResults.reduce((sum, r) => sum + r.repoReadinessPercent, 0);
1924
+ const providerSum = providerResults.reduce((sum, r) => sum + r.providerReadinessPercent, 0);
1925
+ return {
1926
+ repoReadinessPercent: Math.round(repoSum / providerResults.length),
1927
+ providerReadinessPercent: Math.round(providerSum / providerResults.length)
1928
+ };
1929
+ }
1930
+
1931
+ // ../../src/station/verificationLayer/mergeIntoMissionGraph.ts
1932
+ var PROVIDER_WIRING_KEY = {
1933
+ vercel: "vercel-deployment",
1934
+ supabase: "supabase-database",
1935
+ stripe: "stripe-payments"
1936
+ };
1937
+ function mergeVerificationIntoMissionGraph(graph, layer) {
1938
+ const providerMissions = graph.areas.flatMap((area) => area.providerMissions);
1939
+ const missionByKey = new Map(providerMissions.map((mission) => [mission.key, mission]));
1940
+ for (const layerCheck of layer.checks) {
1941
+ if (layerCheck.evidenceSource === "repo") {
1942
+ continue;
1943
+ }
1944
+ const mission = resolveTargetMission(graph, missionByKey, layerCheck);
1945
+ if (!mission) {
1946
+ continue;
1947
+ }
1948
+ if (mission.checks.some((check) => check.verificationCheckId === layerCheck.id || check.id === missionRowId(layerCheck.id))) {
1949
+ continue;
1950
+ }
1951
+ mission.checks.push(verificationCheckToMissionCheck(layerCheck, mission));
1952
+ }
1953
+ recomputeMissionReadiness(providerMissions);
1954
+ const areas = rebuildAreas(providerMissions);
1955
+ return {
1956
+ ...graph,
1957
+ areas,
1958
+ byArea: areas.reduce((acc, area) => {
1959
+ acc[area.key] = area;
1960
+ return acc;
1961
+ }, {}),
1962
+ byProvider: providerMissions.reduce((acc, mission) => {
1963
+ acc[mission.key] = mission;
1964
+ return acc;
1965
+ }, {}),
1966
+ verificationLayer: layer
1967
+ };
1968
+ }
1969
+ function resolveTargetMission(graph, missionByKey, layerCheck) {
1970
+ const wiringKey = PROVIDER_WIRING_KEY[layerCheck.provider];
1971
+ if (wiringKey) {
1972
+ return missionByKey.get(wiringKey) ?? graph.byProvider[wiringKey];
1973
+ }
1974
+ if (layerCheck.provider === "github") {
1975
+ return missionByKey.get("vitest-testing") ?? missionByKey.get("playwright-testing") ?? ensureGitHubMission(graph, missionByKey, layerCheck.area);
1976
+ }
1977
+ return void 0;
1978
+ }
1979
+ function ensureGitHubMission(graph, missionByKey, area) {
1980
+ const key = "vitest-testing";
1981
+ const existing = missionByKey.get(key);
1982
+ if (existing) {
1983
+ return existing;
1984
+ }
1985
+ const mission = {
1986
+ key,
1987
+ provider: "vitest",
1988
+ providerLabel: "GitHub Actions",
1989
+ area,
1990
+ promptSubject: "GitHub Actions CI",
1991
+ readinessPercent: 0,
1992
+ repoReadinessPercent: 100,
1993
+ providerReadinessPercent: 0,
1994
+ checks: []
1995
+ };
1996
+ missionByKey.set(key, mission);
1997
+ const areaEntry = graph.areas.find((entry) => entry.key === area);
1998
+ if (areaEntry) {
1999
+ areaEntry.providerMissions.push(mission);
2000
+ }
2001
+ return mission;
2002
+ }
2003
+ function missionRowId(verificationCheckId) {
2004
+ return `vl-${verificationCheckId}`;
2005
+ }
2006
+ function verificationCheckToMissionCheck(layerCheck, mission) {
2007
+ const evidenceClass = legacyEvidenceClassFor(layerCheck);
2008
+ const status = mapVerificationStatusToMissionStatus(layerCheck.status);
2009
+ return {
2010
+ id: missionRowId(layerCheck.id),
2011
+ label: layerCheck.title,
2012
+ providerKey: mission.key,
2013
+ providerLabel: mission.providerLabel,
2014
+ area: layerCheck.area,
2015
+ evidenceClass,
2016
+ status,
2017
+ evidence: [...layerCheck.repoSignals, ...layerCheck.providerSignals, ...layerCheck.evidenceRefs],
2018
+ promptHint: layerCheck.manualAction ?? layerCheck.description,
2019
+ evidenceSource: layerCheck.evidenceSource,
2020
+ verificationStatus: layerCheck.status,
2021
+ verificationCheckId: layerCheck.id
2022
+ };
2023
+ }
2024
+ function legacyEvidenceClassFor(layerCheck) {
2025
+ return mapEvidenceSourceToLegacyEvidenceClass(layerCheck.evidenceSource, layerCheck.status);
2026
+ }
2027
+ function recomputeMissionReadiness(missions) {
2028
+ for (const mission of missions) {
2029
+ mission.repoReadinessPercent = readinessPercentForRepoChecks(mission.checks);
2030
+ mission.providerReadinessPercent = readinessPercentForProviderChecks(mission.checks);
2031
+ mission.readinessPercent = mission.repoReadinessPercent;
2032
+ }
2033
+ }
2034
+ function readinessPercentForRepoChecks(checks) {
2035
+ const repoChecks = checks.filter(isRepoLayerMissionCheck);
2036
+ if (repoChecks.length === 0) {
2037
+ return 100;
2038
+ }
2039
+ const verified = repoChecks.filter((check) => isVerifiedMissionCheck(check)).length;
2040
+ return Math.round(verified / repoChecks.length * 100);
2041
+ }
2042
+ function readinessPercentForProviderChecks(checks) {
2043
+ const providerChecks = checks.filter(isProviderLayerMissionCheck);
2044
+ if (providerChecks.length === 0) {
2045
+ return 100;
2046
+ }
2047
+ const verified = providerChecks.filter((check) => isVerifiedMissionCheck(check)).length;
2048
+ return Math.round(verified / providerChecks.length * 100);
2049
+ }
2050
+ function isRepoLayerMissionCheck(check) {
2051
+ if (check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.evidenceSource === "manual") {
2052
+ return false;
2053
+ }
2054
+ if (check.evidenceSource === "repo") {
2055
+ return true;
2056
+ }
2057
+ return check.evidenceClass === "repo-verified" || check.evidenceClass === "missing-repo-fix";
2058
+ }
2059
+ function isProviderLayerMissionCheck(check) {
2060
+ if (check.evidenceSource === "provider" || check.evidenceSource === "mcp") {
2061
+ return true;
2062
+ }
2063
+ return check.evidenceClass === "mcp-verifier";
2064
+ }
2065
+ function isVerifiedMissionCheck(check) {
2066
+ if (check.verificationStatus) {
2067
+ return check.verificationStatus === "verified";
2068
+ }
2069
+ return check.status === "passed" || check.status === "user-confirmed";
2070
+ }
2071
+ function rebuildAreas(providerMissions) {
2072
+ const byArea = /* @__PURE__ */ new Map();
2073
+ for (const mission of providerMissions) {
2074
+ const list = byArea.get(mission.area) ?? [];
2075
+ list.push(mission);
2076
+ byArea.set(mission.area, list);
2077
+ }
2078
+ const AREA_LABELS2 = {
2079
+ appFlow: "App Flow",
2080
+ frontend: "Frontend",
2081
+ backend: "Backend / API",
2082
+ database: "Database",
2083
+ auth: "Auth",
2084
+ payments: "Payments",
2085
+ deployment: "Deployment",
2086
+ monitoring: "Monitoring",
2087
+ security: "Security",
2088
+ testing: "Testing",
2089
+ landing: "Landing / Onboarding",
2090
+ errorHandling: "Error Handling"
2091
+ };
2092
+ return [...byArea.entries()].map(([key, missions]) => {
2093
+ const repoChecks = missions.flatMap((m2) => m2.checks).filter(isRepoLayerMissionCheck);
2094
+ const providerChecks = missions.flatMap((m2) => m2.checks).filter(isProviderLayerMissionCheck);
2095
+ const repoReadinessPercent = percentVerified(repoChecks);
2096
+ const providerReadinessPercent = percentVerified(providerChecks);
2097
+ const criticalCount = missions.flatMap((m2) => m2.checks).filter(
2098
+ (check) => isProviderLayerMissionCheck(check) && (check.verificationStatus === "missing" || check.status === "missing" || check.status === "failed")
2099
+ ).length;
2100
+ return {
2101
+ key,
2102
+ label: AREA_LABELS2[key],
2103
+ readinessPercent: repoReadinessPercent,
2104
+ repoReadinessPercent,
2105
+ providerReadinessPercent,
2106
+ criticalCount,
2107
+ providerMissions: missions
2108
+ };
2109
+ });
2110
+ }
2111
+ function percentVerified(checks) {
2112
+ if (checks.length === 0) {
2113
+ return 100;
2114
+ }
2115
+ const verified = checks.filter((check) => isVerifiedMissionCheck(check)).length;
2116
+ return Math.round(verified / checks.length * 100);
2117
+ }
2118
+
1776
2119
  // ../../src/station/missionGraph.ts
1777
2120
  var AREA_LABELS = {
1778
2121
  appFlow: "App Flow",
@@ -1802,13 +2145,14 @@ function buildMissionGraph(input) {
1802
2145
  acc[area.key] = area;
1803
2146
  return acc;
1804
2147
  }, {});
1805
- return {
2148
+ const graph = {
1806
2149
  areas,
1807
2150
  byArea,
1808
2151
  byProvider,
1809
2152
  repositoryEvidence: input.repositoryEvidence,
1810
2153
  ...input.staticInfrastructureFlowGraph ? { staticInfrastructureFlowGraph: input.staticInfrastructureFlowGraph } : {}
1811
2154
  };
2155
+ return input.verificationLayer ? mergeVerificationIntoMissionGraph(graph, input.verificationLayer) : graph;
1812
2156
  }
1813
2157
  function toProviderMission(summary) {
1814
2158
  const checks = summary.items.map((item3) => toMissionCheck(summary, item3));
@@ -1832,6 +2176,8 @@ function toProviderMission(summary) {
1832
2176
  area: summary.area,
1833
2177
  promptSubject: summary.promptSubject,
1834
2178
  readinessPercent: summary.readinessPercent,
2179
+ repoReadinessPercent: summary.readinessPercent,
2180
+ providerReadinessPercent: checks.some((check) => check.evidenceClass === "mcp-verifier") ? 0 : 100,
1835
2181
  checks
1836
2182
  };
1837
2183
  }
@@ -1969,6 +2315,10 @@ function buildAreas(providerMissions) {
1969
2315
  key,
1970
2316
  label: AREA_LABELS[key],
1971
2317
  readinessPercent: Math.round(passed / Math.max(actionableChecks.length, 1) * 100),
2318
+ repoReadinessPercent: Math.round(passed / Math.max(actionableChecks.length, 1) * 100),
2319
+ providerReadinessPercent: missions.some(
2320
+ (mission) => mission.checks.some((check) => check.evidenceClass === "mcp-verifier")
2321
+ ) ? 0 : 100,
1972
2322
  criticalCount: missing,
1973
2323
  providerMissions: missions
1974
2324
  };
@@ -2130,6 +2480,17 @@ var PROVIDER_RULES = [
2130
2480
  imports: ["@supabase/supabase-js", "@supabase/ssr"],
2131
2481
  docs: ["supabase"]
2132
2482
  },
2483
+ {
2484
+ area: "database",
2485
+ provider: "firebase",
2486
+ label: "Firebase",
2487
+ packages: [/^firebase$/, /^firebase-admin$/, /@firebase\//],
2488
+ env: ["FIREBASE_PROJECT_ID", "NEXT_PUBLIC_FIREBASE_PROJECT_ID", "VITE_FIREBASE_PROJECT_ID", "GOOGLE_APPLICATION_CREDENTIALS"],
2489
+ paths: [/firebase/, /firestore/],
2490
+ imports: ["firebase/app", "firebase/firestore", "firebase-admin"],
2491
+ content: [{ pattern: /getFirestore\s*\(|collection\s*\(|firebase-admin/i, signal: "code: Firebase/Firestore usage" }],
2492
+ docs: ["firebase", "firestore"]
2493
+ },
2133
2494
  {
2134
2495
  area: "database",
2135
2496
  provider: "neon",
@@ -2182,10 +2543,33 @@ var PROVIDER_RULES = [
2182
2543
  label: "Auth.js",
2183
2544
  packages: [/^next-auth$/, /^@auth\//],
2184
2545
  env: ["AUTH_SECRET", "NEXTAUTH_SECRET", "NEXTAUTH_URL"],
2185
- paths: [/(^|\/)auth\.[jt]s$/, /api\/auth\//],
2546
+ paths: [/api\/auth\//],
2186
2547
  imports: ["next-auth", "@auth/core", "@auth/nextjs"],
2548
+ content: [{ pattern: /NextAuth\s*\(|getServerSession|useSession\s*\(/i, signal: "code: Auth.js session or route handler" }],
2187
2549
  docs: ["auth.js", "nextauth", "next-auth"]
2188
2550
  },
2551
+ {
2552
+ area: "auth",
2553
+ provider: "auth0",
2554
+ label: "Auth0",
2555
+ packages: [/@auth0\//],
2556
+ env: ["AUTH0_SECRET", "AUTH0_ISSUER_BASE_URL", "AUTH0_CLIENT_ID", "AUTH0_CLIENT_SECRET", "AUTH0_DOMAIN"],
2557
+ paths: [/api\/auth/, /auth0/],
2558
+ imports: ["@auth0/nextjs-auth0", "@auth0/auth0-react", "express-openid-connect"],
2559
+ content: [{ pattern: /handleAuth|withPageAuthRequired|withApiAuthRequired|getSession/i, signal: "code: Auth0 session or route protection" }],
2560
+ docs: ["auth0"]
2561
+ },
2562
+ {
2563
+ area: "auth",
2564
+ provider: "better-auth",
2565
+ label: "Better Auth",
2566
+ packages: [/^better-auth$/],
2567
+ env: ["BETTER_AUTH_SECRET", "BETTER_AUTH_URL", "AUTH_SECRET", "DATABASE_URL"],
2568
+ paths: [/better-auth/, /auth\.[jt]s$/],
2569
+ imports: ["better-auth"],
2570
+ content: [{ pattern: /betterAuth\s*\(|auth\.api|auth\.handler/i, signal: "code: Better Auth config" }],
2571
+ docs: ["better auth", "better-auth"]
2572
+ },
2189
2573
  {
2190
2574
  area: "payments",
2191
2575
  provider: "stripe",
@@ -2206,6 +2590,28 @@ var PROVIDER_RULES = [
2206
2590
  imports: ["@paddle/paddle-js", "@paddle/paddle-node-sdk"],
2207
2591
  docs: ["paddle"]
2208
2592
  },
2593
+ {
2594
+ area: "payments",
2595
+ provider: "polar",
2596
+ label: "Polar",
2597
+ packages: [/@polar-sh\//],
2598
+ env: ["POLAR_ACCESS_TOKEN", "POLAR_WEBHOOK_SECRET", "POLAR_PRODUCT_ID", "POLAR_PRO_PRODUCT_ID"],
2599
+ paths: [/polar.*webhook/, /webhook.*polar/, /polar.*checkout/, /checkout.*polar/],
2600
+ imports: ["@polar-sh/sdk"],
2601
+ content: [{ pattern: /api\.polar\.sh|polar.*checkout|createCheckoutSession/i, signal: "code: Polar checkout or API usage" }],
2602
+ docs: ["polar"]
2603
+ },
2604
+ {
2605
+ area: "payments",
2606
+ provider: "lemon-squeezy",
2607
+ label: "Lemon Squeezy",
2608
+ packages: [/@lemonsqueezy\//, /^lemonsqueezy\.ts$/],
2609
+ env: ["LEMON_SQUEEZY_API_KEY", "LEMONSQUEEZY_API_KEY", "LEMON_SQUEEZY_WEBHOOK_SECRET", "LEMON_SQUEEZY_STORE_ID", "LEMON_SQUEEZY_VARIANT_ID"],
2610
+ paths: [/lemon.*webhook/, /webhook.*lemon/, /lemon.*checkout/, /checkout.*lemon/, /lemonsqueezy/],
2611
+ imports: ["@lemonsqueezy/lemonsqueezy.js", "lemonsqueezy.ts"],
2612
+ content: [{ pattern: /api\.lemonsqueezy\.com|lemon.*checkout|checkout_url|variant_id/i, signal: "code: Lemon Squeezy checkout or API usage" }],
2613
+ docs: ["lemon squeezy", "lemonsqueezy"]
2614
+ },
2209
2615
  {
2210
2616
  area: "deployment",
2211
2617
  provider: "vercel",
@@ -2215,6 +2621,55 @@ var PROVIDER_RULES = [
2215
2621
  paths: [/vercel\.json$/],
2216
2622
  docs: ["vercel"]
2217
2623
  },
2624
+ {
2625
+ area: "deployment",
2626
+ provider: "netlify",
2627
+ label: "Netlify",
2628
+ packages: [/@netlify\//],
2629
+ env: ["NETLIFY_AUTH_TOKEN", "NETLIFY_SITE_ID"],
2630
+ paths: [/netlify\.toml$/, /\.netlify\//, /(^|\/)_redirects$/],
2631
+ imports: ["@netlify/functions"],
2632
+ docs: ["netlify"]
2633
+ },
2634
+ {
2635
+ area: "deployment",
2636
+ provider: "render",
2637
+ label: "Render",
2638
+ env: ["RENDER_API_KEY", "RENDER_SERVICE_ID"],
2639
+ paths: [/render\.ya?ml$/, /(^|\/)\.render\//],
2640
+ content: [{ pattern: /render\.yaml|render\.yml|render deploy|render service/i, signal: "code: Render deployment config" }],
2641
+ docs: ["render.com", "render deployment"]
2642
+ },
2643
+ {
2644
+ area: "deployment",
2645
+ provider: "railway",
2646
+ label: "Railway",
2647
+ env: ["RAILWAY_TOKEN", "RAILWAY_PROJECT_ID", "RAILWAY_SERVICE_ID"],
2648
+ paths: [/railway\.json$/, /nixpacks\.toml$/, /(^|\/)Procfile$/],
2649
+ content: [{ pattern: /railway up|railway deploy|nixpacks|railway\.json/i, signal: "code: Railway deployment config" }],
2650
+ docs: ["railway"]
2651
+ },
2652
+ {
2653
+ area: "deployment",
2654
+ provider: "cloudflare",
2655
+ label: "Cloudflare",
2656
+ packages: [/^wrangler$/, /@cloudflare\//],
2657
+ env: ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID", "CF_PAGES"],
2658
+ paths: [/wrangler\.toml$/, /wrangler\.json$/, /(^|\/)_headers$/, /(^|\/)_redirects$/],
2659
+ imports: ["@cloudflare/workers-types"],
2660
+ content: [{ pattern: /compatibility_date|pages_build_output_dir|cloudflare pages|cloudflare workers/i, signal: "code: Cloudflare Pages/Workers config" }],
2661
+ docs: ["cloudflare", "workers", "pages"]
2662
+ },
2663
+ {
2664
+ area: "deployment",
2665
+ provider: "aws",
2666
+ label: "AWS",
2667
+ packages: [/@aws-sdk\//, /^aws-cdk-lib$/, /^serverless$/],
2668
+ env: ["AWS_REGION", "AWS_ACCESS_KEY_ID"],
2669
+ paths: [/serverless\.ya?ml$/, /template\.ya?ml$/, /cdk\.json$/, /amplify\//],
2670
+ imports: ["aws-sdk", "@aws-sdk/client"],
2671
+ docs: ["aws", "amplify", "serverless"]
2672
+ },
2218
2673
  {
2219
2674
  area: "monitoring",
2220
2675
  provider: "sentry",
@@ -3894,8 +4349,11 @@ function analyzeStackWiring(scan) {
3894
4349
  analyzeSecretsHygiene(ctx),
3895
4350
  analyzeClerkAuth(ctx),
3896
4351
  analyzeAuthJsAuth(ctx),
4352
+ analyzeAuth0Auth(ctx),
4353
+ analyzeBetterAuth(ctx),
3897
4354
  analyzeSupabaseAuth(ctx),
3898
4355
  supabaseDatabase,
4356
+ analyzeFirebaseDatabase(ctx),
3899
4357
  analyzeNeonDatabase(ctx),
3900
4358
  analyzeTursoDatabase(ctx),
3901
4359
  analyzeMongoDatabase(ctx),
@@ -3903,8 +4361,12 @@ function analyzeStackWiring(scan) {
3903
4361
  analyzeStripePayments(ctx),
3904
4362
  analyzePaddlePayments(ctx),
3905
4363
  analyzePolarPayments(ctx),
4364
+ analyzeLemonSqueezyPayments(ctx),
3906
4365
  analyzeVercelDeployment(ctx),
3907
4366
  analyzeNetlifyDeployment(ctx),
4367
+ analyzeRenderDeployment(ctx),
4368
+ analyzeRailwayDeployment(ctx),
4369
+ analyzeCloudflareDeployment(ctx),
3908
4370
  analyzeAwsDeployment(ctx),
3909
4371
  analyzeSupabaseLanding(ctx),
3910
4372
  analyzePostHogMonitoring(ctx),
@@ -4175,6 +4637,44 @@ function analyzeAuthJsAuth(ctx) {
4175
4637
  ]
4176
4638
  });
4177
4639
  }
4640
+ function analyzeAuth0Auth(ctx) {
4641
+ return summarize({
4642
+ key: "auth0-auth",
4643
+ provider: "auth0",
4644
+ providerLabel: "Auth0",
4645
+ area: "auth",
4646
+ areaLabel: "Auth",
4647
+ promptSubject: "Auth0 auth",
4648
+ items: [
4649
+ item2("package-installed", "Auth0 package installed", hasPackage2(ctx, [/@auth0\//]), packageEvidence2(ctx, [/@auth0\//]), "Install the Auth0 package that matches this framework."),
4650
+ item2("env-names-documented", "Auth0 env names documented", /auth0_secret|auth0_issuer_base_url|auth0_client_id|auth0_client_secret|auth0_domain/i.test(ctx.contentBlob), envEvidence2(ctx, [/auth0_secret/i, /auth0_issuer_base_url/i, /auth0_client_id/i, /auth0_client_secret/i, /auth0_domain/i]), "Document Auth0 issuer/domain, client ID, client secret, and app secret env names in safe examples."),
4651
+ item2("auth-route-found", "Auth0 callback or auth route found", /api\/auth|auth0|handleauth|callback/i.test(ctx.pathBlob + "\n" + ctx.contentBlob), pathEvidence2(ctx, /api\/auth|callback/i).concat(fileEvidence(ctx, /auth0|handleauth|callback/i)), "Expose the Auth0 callback/login/logout routes required by this framework."),
4652
+ item2("session-usage-found", "Auth0 session usage found", /getsession|withapirequiredauth|withpagerequiredauth|useuser\s*\(/i.test(ctx.contentBlob), fileEvidence(ctx, /getsession|withapirequiredauth|withpagerequiredauth|useuser\s*\(/i), "Use Auth0 session checks around authenticated app routes and API handlers."),
4653
+ item2("route-protection-found", "Protected route evidence found", /withpagerequiredauth|withapirequiredauth|middleware|protected|requires?auth/i.test(ctx.contentBlob + "\n" + ctx.pathBlob), fileEvidence(ctx, /withpagerequiredauth|withapirequiredauth|middleware|protected|requires?auth/i), "Protect private routes with Auth0 middleware or server session guards."),
4654
+ secretSafetyItem(ctx, "secret-not-exposed", "Auth0 secrets not exposed to frontend", /auth0_client_secret|auth0_secret/i, "Keep Auth0 secrets in server-only env and never expose them through public env variables."),
4655
+ manualItem("production-dashboard-checked", "Production Auth0 app checked", "Confirm callback URLs, logout URLs, allowed origins, social connections, MFA, and production domain in Auth0.")
4656
+ ]
4657
+ });
4658
+ }
4659
+ function analyzeBetterAuth(ctx) {
4660
+ return summarize({
4661
+ key: "better-auth-auth",
4662
+ provider: "better-auth",
4663
+ providerLabel: "Better Auth",
4664
+ area: "auth",
4665
+ areaLabel: "Auth",
4666
+ promptSubject: "Better Auth",
4667
+ items: [
4668
+ item2("package-installed", "Better Auth package installed", hasPackage2(ctx, [/^better-auth$/]), packageEvidence2(ctx, [/^better-auth$/]), "Install Better Auth for this app framework."),
4669
+ item2("env-names-documented", "Better Auth env names documented", /better_auth_secret|better_auth_url|auth_secret|database_url/i.test(ctx.contentBlob), envEvidence2(ctx, [/better_auth_secret/i, /better_auth_url/i, /auth_secret/i, /database_url/i]), "Document BETTER_AUTH_SECRET, BETTER_AUTH_URL, and database env names in safe examples."),
4670
+ item2("auth-config-found", "Better Auth config found", /betterauth\s*\(|better-auth|auth\.api|auth\.handler/i.test(ctx.contentBlob + "\n" + ctx.pathBlob), fileEvidence(ctx, /betterauth\s*\(|better-auth|auth\.api|auth\.handler/i), "Add a central Better Auth config and route handler."),
4671
+ item2("database-adapter-found", "Auth database adapter or schema found", /database|adapter|drizzle|prisma|schema|migration/i.test(ctx.contentBlob + "\n" + ctx.pathBlob), fileEvidence(ctx, /adapter|drizzle|prisma|schema|migration/i), "Persist Better Auth users/sessions through a real database adapter and migrations."),
4672
+ item2("session-usage-found", "Session usage found", /getsession|usesession|auth\.api\.getsession|session/i.test(ctx.contentBlob), fileEvidence(ctx, /getsession|usesession|auth\.api\.getsession|session/i), "Use Better Auth session reads around private product routes."),
4673
+ secretSafetyItem(ctx, "secret-not-exposed", "Better Auth secret not exposed to frontend", /better_auth_secret|auth_secret/i, "Keep Better Auth secrets in server-only env and never expose them through public env variables."),
4674
+ manualItem("production-auth-checked", "Production auth settings checked", "Confirm production base URL, trusted origins, OAuth apps, email settings, and session policy.")
4675
+ ]
4676
+ });
4677
+ }
4178
4678
  function analyzeSupabaseAuth(ctx) {
4179
4679
  return summarize({
4180
4680
  key: "supabase-auth",
@@ -4252,6 +4752,38 @@ function analyzePolarPayments(ctx) {
4252
4752
  ]
4253
4753
  });
4254
4754
  }
4755
+ function analyzeLemonSqueezyPayments(ctx) {
4756
+ return summarize({
4757
+ key: "lemon-squeezy-payments",
4758
+ provider: "lemon-squeezy",
4759
+ providerLabel: "Lemon Squeezy",
4760
+ area: "payments",
4761
+ areaLabel: "Payments",
4762
+ promptSubject: "Lemon Squeezy payments",
4763
+ items: [
4764
+ item2("api-client-found", "Lemon Squeezy SDK or API client found", hasPackage2(ctx, [/@lemonsqueezy\//, /^lemonsqueezy\.ts$/]) || /api\.lemonsqueezy\.com|lemonsqueezy|lemon_squeezy/i.test(ctx.contentBlob), packageEvidence2(ctx, [/@lemonsqueezy\//, /^lemonsqueezy\.ts$/]).concat(fileEvidence(ctx, /api\.lemonsqueezy\.com|lemonsqueezy|lemon_squeezy/i)), "Add a server-side Lemon Squeezy API client for checkout and subscription state."),
4765
+ item2("env-names-documented", "Lemon Squeezy env names documented", /lemon_squeezy_api_key|lemonsqueezy_api_key|lemon_squeezy_webhook_secret|lemon_squeezy_store_id|lemon_squeezy_variant_id/i.test(ctx.contentBlob), envEvidence2(ctx, [/lemon_squeezy_api_key/i, /lemonsqueezy_api_key/i, /lemon_squeezy_webhook_secret/i, /lemon_squeezy_store_id/i, /lemon_squeezy_variant_id/i]), "Document Lemon Squeezy API key, webhook secret, store ID, and variant/product ID env names."),
4766
+ item2("checkout-found", "Lemon Squeezy checkout flow found", /lemon.*checkout|checkout.*lemon|checkout_url|variant_id|store_id/i.test(ctx.contentBlob + "\n" + ctx.pathBlob), fileEvidence(ctx, /lemon.*checkout|checkout.*lemon|checkout_url|variant_id|store_id/i), "Create a checkout URL/session flow for paid plans."),
4767
+ item2("webhook-route-found", "Lemon Squeezy webhook route found", /lemon.*webhook|webhook.*lemon|lemonsqueezy.*webhook/i.test(ctx.pathBlob + "\n" + ctx.contentBlob), pathEvidence2(ctx, /lemon.*webhook|webhook.*lemon|lemonsqueezy.*webhook/i).concat(fileEvidence(ctx, /lemonsqueezy.*webhook|lemon.*webhook/i)), "Add a Lemon Squeezy webhook route for order/subscription lifecycle events."),
4768
+ item2("webhook-signature-found", "Lemon Squeezy webhook verification found", /x-signature|lemon_squeezy_webhook_secret|verify.*lemon|webhook.*signature/i.test(ctx.contentBlob), fileEvidence(ctx, /x-signature|lemon_squeezy_webhook_secret|verify.*lemon|webhook.*signature/i), "Verify Lemon Squeezy webhook signatures before processing billing events."),
4769
+ secretSafetyItem(ctx, "secret-not-exposed", "Lemon Squeezy secret not exposed to frontend", /lemon_squeezy_api_key|lemonsqueezy_api_key|lemon_squeezy_webhook_secret/i, "Move Lemon Squeezy API keys and webhook secrets to server-only code."),
4770
+ manualItem("production-dashboard-checked", "Production Lemon Squeezy store checked", "Confirm products, variants, tax/merchant settings, license keys if used, and webhook URL in Lemon Squeezy.")
4771
+ ]
4772
+ });
4773
+ }
4774
+ function analyzeFirebaseDatabase(ctx) {
4775
+ return databaseSummary(ctx, {
4776
+ key: "firebase-database",
4777
+ provider: "firebase",
4778
+ providerLabel: "Firebase",
4779
+ promptSubject: "Firebase database",
4780
+ packagePatterns: [/^firebase$/, /^firebase-admin$/, /@firebase\//],
4781
+ envPatterns: [/firebase_project_id/i, /firestore_database_url/i, /next_public_firebase/i, /vite_firebase/i, /google_application_credentials/i],
4782
+ usagePatterns: [/firebase\/firestore/i, /getfirestore\s*\(|collection\s*\(|doc\s*\(|firebase-admin/i],
4783
+ manualLabel: "Production Firebase project checked",
4784
+ manualHint: "Confirm production project, Firestore rules, indexes, backups, service account scope, and billing limits in Firebase."
4785
+ });
4786
+ }
4255
4787
  function analyzeNeonDatabase(ctx) {
4256
4788
  return databaseSummary(ctx, {
4257
4789
  key: "neon-database",
@@ -4344,6 +4876,72 @@ function analyzeNetlifyDeployment(ctx) {
4344
4876
  ]
4345
4877
  });
4346
4878
  }
4879
+ function analyzeRenderDeployment(ctx) {
4880
+ const base = deploymentSummary(ctx, {
4881
+ key: "render-deployment",
4882
+ provider: "render",
4883
+ providerLabel: "Render",
4884
+ promptSubject: "Render deployment",
4885
+ packagePatterns: [],
4886
+ configPatterns: [/render\.ya?ml/i, /(^|\n|\/)\.render\//i],
4887
+ envPatterns: [/render_api_key/i, /render_service_id/i],
4888
+ manualLabel: "Production Render service checked",
4889
+ manualHint: "Confirm service type, build/start commands, env groups, health checks, custom domain, autoscaling, and rollback strategy in Render."
4890
+ });
4891
+ const servicePattern = /render\.ya?ml|healthcheckpath|startcommand|buildcommand|envvars|services:\s*|type:\s*web/i;
4892
+ return summarize({
4893
+ ...base,
4894
+ items: [
4895
+ ...base.items.filter((entry) => entry.status !== "manual"),
4896
+ item2("service-config-found", "Render service config found", servicePattern.test(ctx.contentBlob + "\n" + ctx.pathBlob), fileEvidence(ctx, servicePattern).concat(pathEvidence2(ctx, servicePattern)), "Add render.yaml or deployment docs covering service type, commands, health checks, and env groups."),
4897
+ ...base.items.filter((entry) => entry.status === "manual")
4898
+ ]
4899
+ });
4900
+ }
4901
+ function analyzeRailwayDeployment(ctx) {
4902
+ const base = deploymentSummary(ctx, {
4903
+ key: "railway-deployment",
4904
+ provider: "railway",
4905
+ providerLabel: "Railway",
4906
+ promptSubject: "Railway deployment",
4907
+ packagePatterns: [],
4908
+ configPatterns: [/railway\.json/i, /nixpacks\.toml/i, /(^|\n|\/)Procfile\b/i],
4909
+ envPatterns: [/railway_token/i, /railway_project_id/i, /railway_service_id/i],
4910
+ manualLabel: "Production Railway service checked",
4911
+ manualHint: "Confirm service, variables, start command, volumes/databases, custom domain, deploy policy, and logs in Railway."
4912
+ });
4913
+ const servicePattern = /railway\.json|nixpacks\.toml|railway up|railway deploy|Procfile|startCommand|healthcheck/i;
4914
+ return summarize({
4915
+ ...base,
4916
+ items: [
4917
+ ...base.items.filter((entry) => entry.status !== "manual"),
4918
+ item2("service-config-found", "Railway service config found", servicePattern.test(ctx.contentBlob + "\n" + ctx.pathBlob), fileEvidence(ctx, servicePattern).concat(pathEvidence2(ctx, servicePattern)), "Add Railway/Nixpacks/Procfile config or deployment docs for commands, health checks, and service wiring."),
4919
+ ...base.items.filter((entry) => entry.status === "manual")
4920
+ ]
4921
+ });
4922
+ }
4923
+ function analyzeCloudflareDeployment(ctx) {
4924
+ const base = deploymentSummary(ctx, {
4925
+ key: "cloudflare-deployment",
4926
+ provider: "cloudflare",
4927
+ providerLabel: "Cloudflare",
4928
+ promptSubject: "Cloudflare deployment",
4929
+ packagePatterns: [/^wrangler$/, /@cloudflare\//],
4930
+ configPatterns: [/wrangler\.toml/i, /wrangler\.json/i, /_headers\b/i, /_redirects\b/i],
4931
+ envPatterns: [/cloudflare_api_token/i, /cloudflare_account_id/i, /cf_pages/i],
4932
+ manualLabel: "Production Cloudflare project checked",
4933
+ manualHint: "Confirm Pages/Workers project, DNS, routes, compatibility date, env vars/secrets, cache rules, and rollback settings in Cloudflare."
4934
+ });
4935
+ const edgePattern = /wrangler\.toml|wrangler\.json|compatibility_date|pages_build_output_dir|workers_dev|routes?\s*=|cloudflare pages|cloudflare workers/i;
4936
+ return summarize({
4937
+ ...base,
4938
+ items: [
4939
+ ...base.items.filter((entry) => entry.status !== "manual"),
4940
+ item2("edge-config-found", "Cloudflare Pages or Workers config found", edgePattern.test(ctx.contentBlob + "\n" + ctx.pathBlob), fileEvidence(ctx, edgePattern).concat(pathEvidence2(ctx, edgePattern)), "Add Wrangler/Pages config for build output, compatibility date, routes, and edge runtime behavior."),
4941
+ ...base.items.filter((entry) => entry.status === "manual")
4942
+ ]
4943
+ });
4944
+ }
4347
4945
  function analyzeAwsDeployment(ctx) {
4348
4946
  const base = deploymentSummary(ctx, {
4349
4947
  key: "aws-deployment",
@@ -5189,48 +5787,758 @@ function providerLabel2(provider2) {
5189
5787
  );
5190
5788
  }
5191
5789
 
5192
- // ../../src/station/orchestrator.ts
5193
- function isScanLimitResult(value) {
5194
- return value.kind === "scan_limit_reached";
5790
+ // ../../src/station/verificationLayer/shared/connection.ts
5791
+ var MCP_PROVIDER_MAP = {
5792
+ vercel: "vercel",
5793
+ supabase: "supabase",
5794
+ stripe: "stripe",
5795
+ github: "github"
5796
+ };
5797
+ function resolveProviderConnectionState(provider2, mcpVerifierState) {
5798
+ const registryProvider = MCP_PROVIDER_MAP[provider2];
5799
+ if (!registryProvider || !mcpVerifierState) {
5800
+ return "unknown_runtime";
5801
+ }
5802
+ const record = mcpVerifierState.records.find((entry) => entry.provider === registryProvider);
5803
+ if (!record) {
5804
+ return "unknown_runtime";
5805
+ }
5806
+ switch (record.status) {
5807
+ case "configured":
5808
+ return "configured";
5809
+ case "missing":
5810
+ return "not_configured";
5811
+ case "unsupported":
5812
+ return "unsupported";
5813
+ case "stale":
5814
+ return "configured";
5815
+ default:
5816
+ return "unknown_runtime";
5817
+ }
5195
5818
  }
5196
- function isManagedRequiredResult(value) {
5197
- return value.kind === "managed_required";
5819
+ function providerResultStatus(input) {
5820
+ if (input.connectionState === "connected") {
5821
+ return input.providerObservationMet ? "verified" : "missing";
5822
+ }
5823
+ if (!input.repoExpectationMet) {
5824
+ return "missing";
5825
+ }
5826
+ if (input.connectionState === "not_configured" || input.connectionState === "configured") {
5827
+ return "needs_mcp";
5828
+ }
5829
+ if (input.connectionState === "unsupported") {
5830
+ return "manual";
5831
+ }
5832
+ return "unknown";
5198
5833
  }
5199
- function isManagedSessionInvalidResult(value) {
5200
- return value.kind === "managed_session_invalid";
5834
+
5835
+ // ../../src/station/verificationLayer/shared/buildCheck.ts
5836
+ function buildVerificationCheck(input) {
5837
+ const status = input.evidenceSource === "repo" ? input.repoExpectationMet ? "verified" : "missing" : providerResultStatus({
5838
+ connectionState: input.connectionState,
5839
+ repoExpectationMet: input.repoExpectationMet,
5840
+ providerObservationMet: input.providerObservationMet
5841
+ });
5842
+ return {
5843
+ id: providerCheckId(input.provider, input.checkKey),
5844
+ provider: input.provider,
5845
+ area: input.area,
5846
+ title: input.title,
5847
+ description: input.description,
5848
+ requiredEvidence: input.requiredEvidence ?? [],
5849
+ repoSignals: input.repoSignals,
5850
+ providerSignals: input.providerSignals,
5851
+ evidenceSource: input.evidenceSource,
5852
+ status,
5853
+ fixType: input.fixType,
5854
+ severity: input.severity,
5855
+ evidenceRefs: input.evidenceRefs ?? [],
5856
+ manualAction: input.manualAction
5857
+ };
5201
5858
  }
5202
- function createStationOrchestrator(deps) {
5859
+
5860
+ // ../../src/station/verificationLayer/shared/repoSignals.ts
5861
+ var ENV_NAME_PATTERN = /\b(?:process\.env|import\.meta\.env)\.([A-Z][A-Z0-9_]{2,})\b/g;
5862
+ var ENV_ASSIGNMENT_PATTERN = /^[ \t]*(?:export[ \t]+)?([A-Z][A-Z0-9_]{2,})[ \t]*=/gm;
5863
+ var SUPABASE_TABLE_PATTERN = /\.from\s*\(\s*['"]([a-z0-9_]+)['"]\s*\)/gi;
5864
+ var STRIPE_WEBHOOK_PATH_PATTERN = /(^|\/)(api\/webhooks\/stripe|api\/stripe\/webhook|webhooks\/stripe)[^/\s]*/i;
5865
+ var GITHUB_WORKFLOW_PATTERN = /(^|\/)\.github\/workflows\/([^/\n]+\.ya?ml)/i;
5866
+ function buildRepoScanContext(scan) {
5867
+ const files = visibleFiles4(scan);
5203
5868
  return {
5204
- async run(input) {
5205
- const allowLocal = Boolean(await deps.isLocalStationFallbackAllowed?.());
5206
- const scan = await deps.scanWorkspace(input.workspaceRoot);
5207
- const productionConnectionChoices = await loadProductionConnectionChoices(deps);
5208
- const productionConnectionEvidence = detectProductionConnectionEvidence(scan);
5209
- const productionConnections = summarizeProductionConnections(
5210
- productionConnectionChoices,
5211
- productionConnectionEvidence
5212
- );
5213
- const productionConnectionContext = buildProductionConnectionContext(
5214
- productionConnectionChoices,
5215
- productionConnectionEvidence
5216
- );
5217
- const verificationSummary = buildVerificationSummary(scan, productionConnections);
5218
- const verificationEvidenceContext = buildVerificationEvidenceContext(verificationSummary);
5219
- const stackWiring = analyzeStackWiring(scan);
5220
- const stackWiringContext = buildStackWiringContext(stackWiring);
5221
- const repositoryEvidence = analyzeRepositoryEvidence(scan);
5222
- const providerRegistry = buildProviderRegistrySnapshot();
5223
- const staticInfrastructureFlowGraph = buildStaticInfrastructureFlowGraph(scan);
5224
- const stackAutomation = buildStackAutomationSummary(stackWiring, { staticInfrastructureFlowGraph });
5225
- const stackAutomationContext = buildStackAutomationContext(stackAutomation);
5226
- const missionGraph = buildMissionGraph({
5227
- stackWiring,
5228
- repositoryEvidence,
5229
- staticInfrastructureFlowGraph
5230
- });
5231
- const specContent = await readSpec(input.workspaceRoot);
5232
- const modelPrompt = buildAnalysisPrompt(
5233
- scan,
5869
+ files,
5870
+ pathBlob: `${scan.fileTree}
5871
+ ${scan.files.map((file) => file.path).join("\n")}`.replace(/\\/g, "/"),
5872
+ contentBlob: files.map((file) => file.lowerContent).join("\n"),
5873
+ deps: scan.packageDeps.map((dep) => dep.toLowerCase())
5874
+ };
5875
+ }
5876
+ function visibleFiles4(scan) {
5877
+ return scan.files.filter((file) => !file.isSecret && typeof file.content === "string").map((file) => ({
5878
+ path: file.path.replace(/\\/g, "/"),
5879
+ normalizedPath: file.path.replace(/\\/g, "/").toLowerCase(),
5880
+ content: file.content,
5881
+ lowerContent: file.content.toLowerCase()
5882
+ }));
5883
+ }
5884
+ function collectReferencedEnvNames(scan, repositoryEvidence) {
5885
+ const names = /* @__PURE__ */ new Set();
5886
+ for (const entry of repositoryEvidence.env) {
5887
+ if (entry.present || entry.evidence.length > 0) {
5888
+ names.add(entry.name);
5889
+ }
5890
+ }
5891
+ for (const file of visibleFiles4(scan)) {
5892
+ if (!/(^|\/)\.env(\.|$)|env\.example|readme/i.test(file.normalizedPath)) {
5893
+ ENV_NAME_PATTERN.lastIndex = 0;
5894
+ let match;
5895
+ while ((match = ENV_NAME_PATTERN.exec(file.content)) !== null) {
5896
+ names.add(match[1]);
5897
+ }
5898
+ }
5899
+ if (/\.env\.example|env\.example/i.test(file.normalizedPath)) {
5900
+ ENV_ASSIGNMENT_PATTERN.lastIndex = 0;
5901
+ let assignment;
5902
+ while ((assignment = ENV_ASSIGNMENT_PATTERN.exec(file.content)) !== null) {
5903
+ names.add(assignment[1]);
5904
+ }
5905
+ }
5906
+ }
5907
+ const supplemental = collectEnvVarEvidence(scan, [...names]);
5908
+ for (const entry of supplemental) {
5909
+ if (entry.present || entry.evidence.length > 0) {
5910
+ names.add(entry.name);
5911
+ }
5912
+ }
5913
+ return [...names].sort();
5914
+ }
5915
+ function collectEnvExampleNames(scan) {
5916
+ const names = /* @__PURE__ */ new Set();
5917
+ for (const file of visibleFiles4(scan)) {
5918
+ if (!/\.env\.example|env\.example/i.test(file.normalizedPath)) {
5919
+ continue;
5920
+ }
5921
+ ENV_ASSIGNMENT_PATTERN.lastIndex = 0;
5922
+ let assignment;
5923
+ while ((assignment = ENV_ASSIGNMENT_PATTERN.exec(file.content)) !== null) {
5924
+ names.add(assignment[1]);
5925
+ }
5926
+ }
5927
+ return [...names].sort();
5928
+ }
5929
+ function hasRlsMigrationEvidence(repo) {
5930
+ return /\/policies\/|_rls\.sql|\brls\b/i.test(repo.pathBlob) || repo.files.some(
5931
+ (file) => /enable\s+row\s+level\s+security|create\s+policy|alter\s+table[\s\S]{0,200}enable\s+row\s+level/i.test(
5932
+ file.content
5933
+ )
5934
+ );
5935
+ }
5936
+ function collectSupabaseReferencedTables(repo) {
5937
+ const tables = /* @__PURE__ */ new Set();
5938
+ for (const file of repo.files) {
5939
+ SUPABASE_TABLE_PATTERN.lastIndex = 0;
5940
+ let match;
5941
+ while ((match = SUPABASE_TABLE_PATTERN.exec(file.content)) !== null) {
5942
+ tables.add(match[1]);
5943
+ }
5944
+ }
5945
+ return [...tables].sort();
5946
+ }
5947
+ function findStripeWebhookRoute(repo) {
5948
+ const pathMatch = repo.pathBlob.match(STRIPE_WEBHOOK_PATH_PATTERN);
5949
+ if (pathMatch) {
5950
+ return pathMatch[0].replace(/^\//, "");
5951
+ }
5952
+ const file = repo.files.find((entry) => /stripe.*webhook|webhook.*stripe/i.test(entry.normalizedPath));
5953
+ return file ? file.path : null;
5954
+ }
5955
+ function hasStripeWebhookSignature(repo) {
5956
+ return /webhooks\.constructevent/i.test(repo.contentBlob);
5957
+ }
5958
+ function collectStripePriceEnvNames(repo, referencedEnv) {
5959
+ return referencedEnv.filter((name) => /^STRIPE_(PRICE_|PRODUCT_)/i.test(name) || /STRIPE.*PRICE/i.test(name));
5960
+ }
5961
+ function findGithubWorkflowPaths(repo) {
5962
+ const paths = /* @__PURE__ */ new Set();
5963
+ for (const line of repo.pathBlob.split(/\r?\n/)) {
5964
+ const match = line.match(GITHUB_WORKFLOW_PATTERN);
5965
+ if (match) {
5966
+ paths.add(line.trim());
5967
+ }
5968
+ }
5969
+ return [...paths].sort();
5970
+ }
5971
+
5972
+ // ../../src/station/verificationLayer/mock/mockGitHubVerifier.ts
5973
+ var mockGitHubVerifier = {
5974
+ provider: "github",
5975
+ detectConfig(ctx) {
5976
+ const repo = buildRepoScanContext(ctx.scan);
5977
+ const workflows = findGithubWorkflowPaths(repo);
5978
+ const signals = [];
5979
+ if (ctx.scan.stackSignals.hasCI) {
5980
+ signals.push("stack: hasCI");
5981
+ }
5982
+ for (const workflow of workflows) {
5983
+ signals.push(`workflow: ${workflow}`);
5984
+ }
5985
+ const detected = workflows.length > 0 || Boolean(ctx.scan.stackSignals.hasCI);
5986
+ return { provider: "github", detected, signals };
5987
+ },
5988
+ connectStatus(ctx) {
5989
+ return resolveProviderConnectionState("github", ctx.mcpVerifierState);
5990
+ },
5991
+ runChecks(ctx) {
5992
+ const connectionState = this.connectStatus(ctx);
5993
+ const repo = buildRepoScanContext(ctx.scan);
5994
+ const workflows = findGithubWorkflowPaths(repo);
5995
+ return [
5996
+ buildVerificationCheck({
5997
+ provider: "github",
5998
+ checkKey: "actions-run-status",
5999
+ area: "testing",
6000
+ title: "GitHub Actions run status",
6001
+ description: "Recent workflow run conclusions require live GitHub API or MCP access.",
6002
+ evidenceSource: "provider",
6003
+ connectionState,
6004
+ repoExpectationMet: workflows.length > 0,
6005
+ providerObservationMet: false,
6006
+ fixType: "mcp-connect",
6007
+ severity: "warning",
6008
+ repoSignals: workflows.map((path) => `workflow: ${path}`),
6009
+ providerSignals: ["GitHub Actions API or MCP"],
6010
+ requiredEvidence: ["Latest workflow run status on default branch"]
6011
+ }),
6012
+ buildVerificationCheck({
6013
+ provider: "github",
6014
+ checkKey: "required-checks",
6015
+ area: "testing",
6016
+ title: "Required GitHub checks",
6017
+ description: "Branch protection and required check contexts need live GitHub verification.",
6018
+ evidenceSource: "provider",
6019
+ connectionState,
6020
+ repoExpectationMet: workflows.length > 0,
6021
+ providerObservationMet: false,
6022
+ fixType: "mcp-connect",
6023
+ severity: "warning",
6024
+ repoSignals: workflows,
6025
+ providerSignals: ["GitHub branch protection API or MCP"],
6026
+ requiredEvidence: ["Required status checks configured for production branch"]
6027
+ }),
6028
+ buildVerificationCheck({
6029
+ provider: "github",
6030
+ checkKey: "failed-checks-recent",
6031
+ area: "testing",
6032
+ title: "Recent failed GitHub checks",
6033
+ description: "Failed workflow runs in the last 7 days require live GitHub verification.",
6034
+ evidenceSource: "provider",
6035
+ connectionState,
6036
+ repoExpectationMet: workflows.length > 0,
6037
+ providerObservationMet: false,
6038
+ fixType: "mcp-connect",
6039
+ severity: "critical",
6040
+ repoSignals: workflows,
6041
+ providerSignals: ["GitHub check run API or MCP"],
6042
+ requiredEvidence: ["No failing required checks on latest default-branch commit"]
6043
+ })
6044
+ ];
6045
+ },
6046
+ buildDiffs(ctx) {
6047
+ const repo = buildRepoScanContext(ctx.scan);
6048
+ const workflows = findGithubWorkflowPaths(repo);
6049
+ if (workflows.length === 0) {
6050
+ return [];
6051
+ }
6052
+ return [
6053
+ {
6054
+ id: providerCheckId("github", "ci-status-unverified"),
6055
+ provider: "github",
6056
+ area: "testing",
6057
+ title: "CI status not verified",
6058
+ description: "GitHub workflow files exist in the repo, but recent Actions conclusions have not been fetched.",
6059
+ repoExpectation: `Workflows: ${workflows.join(", ")}`,
6060
+ providerActual: "Not verified (mock \u2014 connect GitHub MCP read-only)",
6061
+ severity: "warning",
6062
+ suggestedFix: "mcp-connect",
6063
+ evidenceRefs: workflows.map((path) => `workflow: ${path}`)
6064
+ }
6065
+ ];
6066
+ }
6067
+ };
6068
+
6069
+ // ../../src/station/verificationLayer/mock/mockStripeVerifier.ts
6070
+ var REQUIRED_STRIPE_EVENTS = [
6071
+ "checkout.session.completed",
6072
+ "customer.subscription.updated",
6073
+ "customer.subscription.deleted",
6074
+ "invoice.payment_failed"
6075
+ ];
6076
+ var MOCK_STRIPE_HAS_WEBHOOK_ENDPOINT = false;
6077
+ var MOCK_STRIPE_CONFIGURED_EVENTS = /* @__PURE__ */ new Set();
6078
+ var MOCK_STRIPE_LIVE_PRICE_IDS = /* @__PURE__ */ new Set();
6079
+ var mockStripeVerifier = {
6080
+ provider: "stripe",
6081
+ detectConfig(ctx) {
6082
+ const repo = buildRepoScanContext(ctx.scan);
6083
+ const signals = [];
6084
+ if (ctx.scan.stackSignals.hasStripe) {
6085
+ signals.push("stack: hasStripe");
6086
+ }
6087
+ if (repo.deps.some((dep) => dep === "stripe" || dep.startsWith("@stripe/"))) {
6088
+ signals.push("package: stripe");
6089
+ }
6090
+ if (findStripeWebhookRoute(repo)) {
6091
+ signals.push(`route: ${findStripeWebhookRoute(repo)}`);
6092
+ }
6093
+ const detected = signals.length > 0;
6094
+ return { provider: "stripe", detected, signals };
6095
+ },
6096
+ connectStatus(ctx) {
6097
+ return resolveProviderConnectionState("stripe", ctx.mcpVerifierState);
6098
+ },
6099
+ runChecks(ctx) {
6100
+ const connectionState = this.connectStatus(ctx);
6101
+ const repo = buildRepoScanContext(ctx.scan);
6102
+ const webhookRoute = findStripeWebhookRoute(repo);
6103
+ const referencedEnv = collectReferencedEnvNames(ctx.scan, ctx.repositoryEvidence);
6104
+ const priceEnvNames = collectStripePriceEnvNames(repo, referencedEnv);
6105
+ const missingEvents = REQUIRED_STRIPE_EVENTS.filter((event) => !MOCK_STRIPE_CONFIGURED_EVENTS.has(event));
6106
+ return [
6107
+ buildVerificationCheck({
6108
+ provider: "stripe",
6109
+ checkKey: "dashboard-webhook-endpoint",
6110
+ area: "payments",
6111
+ title: "Stripe dashboard webhook endpoint",
6112
+ description: webhookRoute ? "Repo defines a webhook route; Stripe dashboard endpoint must match and be reachable." : "No Stripe webhook route found in repo; dashboard endpoint check still applies when Stripe is used.",
6113
+ evidenceSource: "provider",
6114
+ connectionState,
6115
+ repoExpectationMet: Boolean(webhookRoute),
6116
+ providerObservationMet: MOCK_STRIPE_HAS_WEBHOOK_ENDPOINT,
6117
+ fixType: "provider-config",
6118
+ severity: "critical",
6119
+ repoSignals: webhookRoute ? [`route: ${webhookRoute}`] : [],
6120
+ providerSignals: ["Stripe webhooks API or MCP"],
6121
+ requiredEvidence: ["Webhook endpoint URL registered in Stripe"],
6122
+ manualAction: "Register the production webhook URL in Stripe Dashboard \u2192 Developers \u2192 Webhooks."
6123
+ }),
6124
+ buildVerificationCheck({
6125
+ provider: "stripe",
6126
+ checkKey: "webhook-events",
6127
+ area: "payments",
6128
+ title: "Stripe webhook event subscriptions",
6129
+ description: missingEvents.length > 0 ? `Missing required events: ${missingEvents.join(", ")}` : "Required subscription lifecycle events are configured (mock).",
6130
+ evidenceSource: "provider",
6131
+ connectionState,
6132
+ repoExpectationMet: Boolean(webhookRoute && hasStripeWebhookSignature(repo)),
6133
+ providerObservationMet: missingEvents.length === 0,
6134
+ fixType: "provider-config",
6135
+ severity: "critical",
6136
+ repoSignals: hasStripeWebhookSignature(repo) ? ["webhooks.constructEvent in repo"] : [],
6137
+ providerSignals: ["Stripe webhook endpoint event list"],
6138
+ requiredEvidence: REQUIRED_STRIPE_EVENTS.map((event) => `event: ${event}`)
6139
+ }),
6140
+ buildVerificationCheck({
6141
+ provider: "stripe",
6142
+ checkKey: "live-price-ids",
6143
+ area: "payments",
6144
+ title: "Stripe live price IDs",
6145
+ description: priceEnvNames.length > 0 ? "Price/product env vars are referenced in repo; live Stripe prices must match." : "No Stripe price env vars detected; confirm products/prices if billing is enabled.",
6146
+ evidenceSource: "provider",
6147
+ connectionState,
6148
+ repoExpectationMet: priceEnvNames.length > 0,
6149
+ providerObservationMet: priceEnvNames.length > 0 && priceEnvNames.every((name) => MOCK_STRIPE_LIVE_PRICE_IDS.has(name)),
6150
+ fixType: "provider-config",
6151
+ severity: priceEnvNames.length > 0 ? "warning" : "info",
6152
+ repoSignals: priceEnvNames.map((name) => `env: ${name}`),
6153
+ providerSignals: ["Stripe products/prices API or MCP"],
6154
+ requiredEvidence: ["Live price IDs match env configuration"]
6155
+ })
6156
+ ];
6157
+ },
6158
+ buildDiffs(ctx) {
6159
+ const repo = buildRepoScanContext(ctx.scan);
6160
+ const webhookRoute = findStripeWebhookRoute(repo);
6161
+ const diffs = [];
6162
+ if (webhookRoute && !MOCK_STRIPE_HAS_WEBHOOK_ENDPOINT) {
6163
+ diffs.push({
6164
+ id: providerCheckId("stripe", "webhook-endpoint-mismatch"),
6165
+ provider: "stripe",
6166
+ area: "payments",
6167
+ title: "Stripe webhook endpoint not registered",
6168
+ description: "The repo implements a webhook handler, but no matching endpoint is registered in the mock Stripe account.",
6169
+ repoExpectation: `Webhook route: ${webhookRoute}`,
6170
+ providerActual: "No Stripe webhook endpoint (mock empty dashboard)",
6171
+ severity: "critical",
6172
+ suggestedFix: "provider-config",
6173
+ evidenceRefs: [`route: ${webhookRoute}`]
6174
+ });
6175
+ }
6176
+ const referencedEnv = collectReferencedEnvNames(ctx.scan, ctx.repositoryEvidence);
6177
+ if (referencedEnv.includes("STRIPE_WEBHOOK_SECRET") && !MOCK_STRIPE_HAS_WEBHOOK_ENDPOINT) {
6178
+ diffs.push({
6179
+ id: providerCheckId("stripe", "webhook-secret-without-endpoint"),
6180
+ provider: "stripe",
6181
+ area: "payments",
6182
+ title: "STRIPE_WEBHOOK_SECRET without dashboard endpoint",
6183
+ description: "Repo expects STRIPE_WEBHOOK_SECRET but Stripe dashboard webhook endpoint is not confirmed.",
6184
+ repoExpectation: "STRIPE_WEBHOOK_SECRET referenced in repo",
6185
+ providerActual: "No webhook endpoint to deliver signed events",
6186
+ severity: "critical",
6187
+ suggestedFix: "provider-config",
6188
+ evidenceRefs: ["env: STRIPE_WEBHOOK_SECRET"]
6189
+ });
6190
+ }
6191
+ return diffs;
6192
+ }
6193
+ };
6194
+
6195
+ // ../../src/station/verificationLayer/mock/mockSupabaseVerifier.ts
6196
+ var mockSupabaseVerifier = {
6197
+ provider: "supabase",
6198
+ detectConfig(ctx) {
6199
+ const repo = buildRepoScanContext(ctx.scan);
6200
+ const signals = [];
6201
+ if (ctx.scan.stackSignals.hasSupabase) {
6202
+ signals.push("stack: hasSupabase");
6203
+ }
6204
+ if (repo.deps.some((dep) => dep.startsWith("@supabase/"))) {
6205
+ signals.push(`package: ${repo.deps.find((dep) => dep.startsWith("@supabase/"))}`);
6206
+ }
6207
+ if (/\/supabase\/migrations\//i.test(repo.pathBlob)) {
6208
+ signals.push("path: supabase/migrations");
6209
+ }
6210
+ const detected = signals.length > 0;
6211
+ return { provider: "supabase", detected, signals };
6212
+ },
6213
+ connectStatus(ctx) {
6214
+ return resolveProviderConnectionState("supabase", ctx.mcpVerifierState);
6215
+ },
6216
+ runChecks(ctx) {
6217
+ const connectionState = this.connectStatus(ctx);
6218
+ const repo = buildRepoScanContext(ctx.scan);
6219
+ const tables = collectSupabaseReferencedTables(repo);
6220
+ const rlsInRepo = hasRlsMigrationEvidence(repo);
6221
+ return [
6222
+ buildVerificationCheck({
6223
+ provider: "supabase",
6224
+ checkKey: "live-rls-enabled",
6225
+ area: "database",
6226
+ title: "Live Supabase RLS enabled",
6227
+ description: rlsInRepo ? "Repo contains RLS migration/policy evidence; live project RLS must be confirmed via Supabase MCP or dashboard." : "No RLS migration evidence in repo; live project likely needs RLS before launch.",
6228
+ evidenceSource: "provider",
6229
+ connectionState,
6230
+ repoExpectationMet: rlsInRepo,
6231
+ providerObservationMet: false,
6232
+ fixType: rlsInRepo ? "mcp-connect" : "provider-config",
6233
+ severity: "critical",
6234
+ repoSignals: rlsInRepo ? ["migration/policy references RLS"] : ["no RLS migration detected"],
6235
+ providerSignals: ["Supabase project policy API or MCP"],
6236
+ requiredEvidence: ["RLS enabled on user-owned tables in live project"],
6237
+ manualAction: "Enable RLS and policies in Supabase Dashboard \u2192 Authentication \u2192 Policies."
6238
+ }),
6239
+ buildVerificationCheck({
6240
+ provider: "supabase",
6241
+ checkKey: "live-policies",
6242
+ area: "database",
6243
+ title: "Live Supabase policies",
6244
+ description: "Row policies on referenced tables require live Supabase verification.",
6245
+ evidenceSource: "provider",
6246
+ connectionState,
6247
+ repoExpectationMet: tables.length > 0,
6248
+ providerObservationMet: false,
6249
+ fixType: "mcp-connect",
6250
+ severity: tables.length > 0 ? "critical" : "info",
6251
+ repoSignals: tables.map((table) => `table referenced: ${table}`),
6252
+ providerSignals: ["Supabase policy list API or MCP"],
6253
+ requiredEvidence: ["Policies exist for tables used in application code"]
6254
+ }),
6255
+ buildVerificationCheck({
6256
+ provider: "supabase",
6257
+ checkKey: "referenced-tables-protected",
6258
+ area: "database",
6259
+ title: "Referenced tables protected in live project",
6260
+ description: tables.length > 0 ? `Application code references ${tables.length} Supabase table(s); confirm live RLS/policies cover them.` : "No Supabase table references detected in scanned code.",
6261
+ evidenceSource: "provider",
6262
+ connectionState,
6263
+ repoExpectationMet: tables.length > 0 && rlsInRepo,
6264
+ providerObservationMet: false,
6265
+ fixType: "provider-config",
6266
+ severity: "warning",
6267
+ repoSignals: tables.map((table) => `.from("${table}")`),
6268
+ providerSignals: ["Live table policy map"],
6269
+ requiredEvidence: ["Each referenced public table has RLS + policy in production"]
6270
+ })
6271
+ ];
6272
+ },
6273
+ buildDiffs(ctx) {
6274
+ const repo = buildRepoScanContext(ctx.scan);
6275
+ const tables = collectSupabaseReferencedTables(repo);
6276
+ const rlsInRepo = hasRlsMigrationEvidence(repo);
6277
+ const diffs = [];
6278
+ if (!rlsInRepo && tables.length > 0) {
6279
+ diffs.push({
6280
+ id: providerCheckId("supabase", "rls-migration-gap"),
6281
+ provider: "supabase",
6282
+ area: "database",
6283
+ title: "RLS migration missing for referenced tables",
6284
+ description: "Code queries Supabase tables but no RLS migration/policy evidence was found in the repo.",
6285
+ repoExpectation: `Tables referenced: ${tables.join(", ")}`,
6286
+ providerActual: "Live RLS state unknown without Supabase MCP",
6287
+ severity: "critical",
6288
+ suggestedFix: "repo-fix",
6289
+ evidenceRefs: tables.map((table) => `table: ${table}`)
6290
+ });
6291
+ }
6292
+ if (rlsInRepo) {
6293
+ diffs.push({
6294
+ id: providerCheckId("supabase", "live-rls-unverified"),
6295
+ provider: "supabase",
6296
+ area: "database",
6297
+ title: "Live RLS not verified",
6298
+ description: "Repo includes RLS migration/policy files, but live Supabase project RLS has not been confirmed.",
6299
+ repoExpectation: "RLS policies defined in repo migrations",
6300
+ providerActual: "Not verified (mock \u2014 connect Supabase MCP read-only)",
6301
+ severity: "critical",
6302
+ suggestedFix: "mcp-connect",
6303
+ evidenceRefs: repo.files.filter((file) => /enable\s+row\s+level\s+security|create\s+policy/i.test(file.content)).slice(0, 3).map((file) => `file: ${file.path}`)
6304
+ });
6305
+ }
6306
+ return diffs;
6307
+ }
6308
+ };
6309
+
6310
+ // ../../src/station/verificationLayer/mock/mockVercelVerifier.ts
6311
+ var MOCK_VERCEL_PRODUCTION_ENV = /* @__PURE__ */ new Set();
6312
+ function deploymentEnvCandidates(ctx) {
6313
+ const referenced = collectReferencedEnvNames(ctx.scan, ctx.repositoryEvidence);
6314
+ const fromExample = collectEnvExampleNames(ctx.scan);
6315
+ const names = /* @__PURE__ */ new Set([...referenced, ...fromExample]);
6316
+ return [...names].filter((name) => {
6317
+ if (/^VERCEL_/i.test(name)) {
6318
+ return true;
6319
+ }
6320
+ if (/^(DATABASE_URL|NEXT_PUBLIC_|VITE_|SUPABASE_|STRIPE_|CLERK_|SENTRY_|POSTHOG_|PADDLE_|AUTH_|NEXTAUTH_)/i.test(name)) {
6321
+ return true;
6322
+ }
6323
+ return referenced.includes(name) && fromExample.includes(name);
6324
+ });
6325
+ }
6326
+ var mockVercelVerifier = {
6327
+ provider: "vercel",
6328
+ detectConfig(ctx) {
6329
+ const repo = buildRepoScanContext(ctx.scan);
6330
+ const signals = [];
6331
+ if (ctx.scan.stackSignals.hasVercel) {
6332
+ signals.push("stack: hasVercel");
6333
+ }
6334
+ if (/vercel\.json/i.test(repo.pathBlob)) {
6335
+ signals.push("path: vercel.json");
6336
+ }
6337
+ if (ctx.scan.stackSignals.hasNextJs || ctx.scan.stackSignals.hasVite) {
6338
+ signals.push("framework: Vercel-compatible");
6339
+ }
6340
+ const detected = signals.length > 0;
6341
+ return { provider: "vercel", detected, signals };
6342
+ },
6343
+ connectStatus(ctx) {
6344
+ return resolveProviderConnectionState("vercel", ctx.mcpVerifierState);
6345
+ },
6346
+ runChecks(ctx) {
6347
+ const connectionState = this.connectStatus(ctx);
6348
+ const candidates = deploymentEnvCandidates(ctx);
6349
+ const missingOnVercel = candidates.filter((name) => !MOCK_VERCEL_PRODUCTION_ENV.has(name));
6350
+ const repo = buildRepoScanContext(ctx.scan);
6351
+ const checks = [
6352
+ buildVerificationCheck({
6353
+ provider: "vercel",
6354
+ checkKey: "production-env-sync",
6355
+ area: "deployment",
6356
+ title: "Vercel production environment variables",
6357
+ description: missingOnVercel.length > 0 ? `${missingOnVercel.length} repo-referenced env var(s) are not confirmed in Vercel production.` : "Repo env names align with the mock Vercel production snapshot.",
6358
+ evidenceSource: "provider",
6359
+ connectionState,
6360
+ repoExpectationMet: candidates.length > 0,
6361
+ providerObservationMet: missingOnVercel.length === 0,
6362
+ fixType: "provider-config",
6363
+ severity: missingOnVercel.length > 0 ? "critical" : "info",
6364
+ repoSignals: missingOnVercel.map((name) => `env expected: ${name}`),
6365
+ providerSignals: connectionState === "connected" ? ["Vercel production env API"] : ["Awaiting Vercel MCP read-only env lookup"],
6366
+ requiredEvidence: ["Vercel production env var names and values (redacted)"],
6367
+ manualAction: "Add missing keys in Vercel Project Settings \u2192 Environment Variables (Production)."
6368
+ }),
6369
+ buildVerificationCheck({
6370
+ provider: "vercel",
6371
+ checkKey: "latest-deployment",
6372
+ area: "deployment",
6373
+ title: "Latest Vercel deployment status",
6374
+ description: "Production deployment health requires live Vercel project access.",
6375
+ evidenceSource: "provider",
6376
+ connectionState,
6377
+ repoExpectationMet: Boolean(ctx.scan.stackSignals.hasVercel || /vercel\.json/i.test(repo.pathBlob)),
6378
+ providerObservationMet: false,
6379
+ fixType: "mcp-connect",
6380
+ severity: "warning",
6381
+ repoSignals: repo.pathBlob.split(/\r?\n/).filter((p2) => /vercel\.json/i.test(p2)).slice(0, 3),
6382
+ providerSignals: ["Vercel deployments API or MCP"],
6383
+ requiredEvidence: ["Latest production deployment state"]
6384
+ }),
6385
+ buildVerificationCheck({
6386
+ provider: "vercel",
6387
+ checkKey: "production-domain",
6388
+ area: "deployment",
6389
+ title: "Vercel production domain",
6390
+ description: "Custom domain and DNS status require live Vercel project access.",
6391
+ evidenceSource: "provider",
6392
+ connectionState,
6393
+ repoExpectationMet: Boolean(ctx.scan.stackSignals.hasVercel || ctx.scan.stackSignals.hasNextJs),
6394
+ providerObservationMet: false,
6395
+ fixType: "mcp-connect",
6396
+ severity: "warning",
6397
+ repoSignals: [],
6398
+ providerSignals: ["Vercel domains API or MCP"],
6399
+ requiredEvidence: ["Production domain assignment and DNS verification"]
6400
+ })
6401
+ ];
6402
+ return checks;
6403
+ },
6404
+ buildDiffs(ctx) {
6405
+ const candidates = deploymentEnvCandidates(ctx);
6406
+ return candidates.filter((name) => !MOCK_VERCEL_PRODUCTION_ENV.has(name)).map((name) => ({
6407
+ id: providerCheckId("vercel", `env-missing-${name.toLowerCase()}`),
6408
+ provider: "vercel",
6409
+ area: "deployment",
6410
+ title: `${name} missing in Vercel production`,
6411
+ description: `The repo references ${name}, but the mock Vercel production env snapshot does not include it.`,
6412
+ repoExpectation: `Referenced in repo (.env.example, code, or env evidence)`,
6413
+ providerActual: "Not present in Vercel production (mock empty snapshot)",
6414
+ severity: /SECRET|KEY|TOKEN|PASSWORD/i.test(name) ? "critical" : "warning",
6415
+ suggestedFix: "provider-config",
6416
+ evidenceRefs: collectReferencedEnvNames(ctx.scan, ctx.repositoryEvidence).filter((entry) => entry === name).map((entry) => `env: ${entry}`)
6417
+ }));
6418
+ }
6419
+ };
6420
+
6421
+ // ../../src/station/verificationLayer/registry.ts
6422
+ var verifiers = [];
6423
+ function registerProviderVerifier(verifier) {
6424
+ const existing = verifiers.findIndex((entry) => entry.provider === verifier.provider);
6425
+ if (existing >= 0) {
6426
+ verifiers[existing] = verifier;
6427
+ return;
6428
+ }
6429
+ verifiers.push(verifier);
6430
+ }
6431
+ function getRegisteredVerifiers() {
6432
+ return [...verifiers];
6433
+ }
6434
+
6435
+ // ../../src/station/verificationLayer/registerDefaults.ts
6436
+ var defaultsRegistered = false;
6437
+ function ensureDefaultVerifiersRegistered() {
6438
+ if (defaultsRegistered) {
6439
+ return;
6440
+ }
6441
+ registerProviderVerifier(mockVercelVerifier);
6442
+ registerProviderVerifier(mockSupabaseVerifier);
6443
+ registerProviderVerifier(mockStripeVerifier);
6444
+ registerProviderVerifier(mockGitHubVerifier);
6445
+ defaultsRegistered = true;
6446
+ }
6447
+
6448
+ // ../../src/station/verificationLayer/runVerificationLayer.ts
6449
+ function runVerifier(verifier, ctx) {
6450
+ const config = verifier.detectConfig(ctx);
6451
+ if (!config.detected) {
6452
+ return null;
6453
+ }
6454
+ const connectionState = verifier.connectStatus(ctx);
6455
+ const rawChecks = verifier.runChecks(ctx);
6456
+ const checks = rawChecks.map((check) => ({
6457
+ ...check,
6458
+ status: coerceProviderVerificationStatus(check.status, connectionState)
6459
+ }));
6460
+ const diffs = verifier.buildDiffs(ctx);
6461
+ return {
6462
+ provider: verifier.provider,
6463
+ connectionState,
6464
+ config,
6465
+ checks,
6466
+ diffs,
6467
+ repoReadinessPercent: computeLayerReadinessPercent(checks, "repo"),
6468
+ providerReadinessPercent: computeLayerReadinessPercent(checks, "provider")
6469
+ };
6470
+ }
6471
+ function runVerificationLayer(ctx, options) {
6472
+ if (options?.registerDefaults !== false) {
6473
+ ensureDefaultVerifiersRegistered();
6474
+ }
6475
+ const providers = getRegisteredVerifiers().map((verifier) => runVerifier(verifier, ctx)).filter((result) => result !== null);
6476
+ const checks = providers.flatMap((result) => result.checks);
6477
+ const diffs = providers.flatMap((result) => result.diffs);
6478
+ const { repoReadinessPercent, providerReadinessPercent } = aggregateReadinessPercents(providers);
6479
+ return {
6480
+ version: 1,
6481
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6482
+ runtimeMode: "mock",
6483
+ providers,
6484
+ checks,
6485
+ diffs,
6486
+ repoReadinessPercent,
6487
+ providerReadinessPercent
6488
+ };
6489
+ }
6490
+
6491
+ // ../../src/station/orchestrator.ts
6492
+ function isScanLimitResult(value) {
6493
+ return value.kind === "scan_limit_reached";
6494
+ }
6495
+ function isManagedRequiredResult(value) {
6496
+ return value.kind === "managed_required";
6497
+ }
6498
+ function isManagedSessionInvalidResult(value) {
6499
+ return value.kind === "managed_session_invalid";
6500
+ }
6501
+ function createStationOrchestrator(deps) {
6502
+ return {
6503
+ async run(input) {
6504
+ const allowLocal = Boolean(await deps.isLocalStationFallbackAllowed?.());
6505
+ const scan = await deps.scanWorkspace(input.workspaceRoot);
6506
+ const productionConnectionChoices = await loadProductionConnectionChoices(deps);
6507
+ const productionConnectionEvidence = detectProductionConnectionEvidence(scan);
6508
+ const productionConnections = summarizeProductionConnections(
6509
+ productionConnectionChoices,
6510
+ productionConnectionEvidence
6511
+ );
6512
+ const productionConnectionContext = buildProductionConnectionContext(
6513
+ productionConnectionChoices,
6514
+ productionConnectionEvidence
6515
+ );
6516
+ const verificationSummary = buildVerificationSummary(scan, productionConnections);
6517
+ const verificationEvidenceContext = buildVerificationEvidenceContext(verificationSummary);
6518
+ const stackWiring = analyzeStackWiring(scan);
6519
+ const stackWiringContext = buildStackWiringContext(stackWiring);
6520
+ const repositoryEvidence = analyzeRepositoryEvidence(scan);
6521
+ const providerRegistry = buildProviderRegistrySnapshot();
6522
+ const staticInfrastructureFlowGraph = buildStaticInfrastructureFlowGraph(scan);
6523
+ const stackAutomation = buildStackAutomationSummary(stackWiring, { staticInfrastructureFlowGraph });
6524
+ const stackAutomationContext = buildStackAutomationContext(stackAutomation);
6525
+ const mcpVerifierState = await deps.getMcpVerifierState?.();
6526
+ const verificationLayer = runVerificationLayer({
6527
+ workspaceRoot: input.workspaceRoot,
6528
+ scan,
6529
+ stackWiring,
6530
+ repositoryEvidence,
6531
+ mcpVerifierState
6532
+ });
6533
+ const missionGraph = buildMissionGraph({
6534
+ stackWiring,
6535
+ repositoryEvidence,
6536
+ staticInfrastructureFlowGraph,
6537
+ verificationLayer
6538
+ });
6539
+ const specContent = await readSpec(input.workspaceRoot);
6540
+ const modelPrompt = buildAnalysisPrompt(
6541
+ scan,
5234
6542
  specContent,
5235
6543
  productionConnectionContext,
5236
6544
  verificationEvidenceContext,
@@ -5568,25 +6876,49 @@ function generateAgentSummary(artifact) {
5568
6876
  }
5569
6877
  }
5570
6878
  lines.push("");
5571
- lines.push("## Top launch gaps (model)");
6879
+ lines.push("## Agent-code actions");
5572
6880
  if (topGaps.length === 0) {
5573
- lines.push("_No model gaps returned \u2014 rely on mission map checks above._");
6881
+ lines.push("_No model gaps returned. Review mission map checks before changing code._");
5574
6882
  } else {
5575
6883
  topGaps.forEach((gap, index) => {
5576
6884
  lines.push(
5577
6885
  `${index + 1}. **${gap.title}** (\`${gap.id}\`, ${gap.severity}, map: \`${gap.primaryMapCategory}\`)`
5578
6886
  );
5579
6887
  lines.push(` - ${gap.detail}`);
5580
- lines.push(` - Prompt: \`viberaven prompt --gap ${gap.id}\``);
6888
+ lines.push(` - Command: \`viberaven prompt --gap ${gap.id}\``);
6889
+ if (gap.copyPrompt) {
6890
+ lines.push(` - Prompt: ${gap.copyPrompt}`);
6891
+ }
6892
+ });
6893
+ }
6894
+ const manualChecks = (artifact.missionGraph.areas ?? []).flatMap(
6895
+ (area) => area.providerMissions.flatMap(
6896
+ (mission) => mission.checks.filter(
6897
+ (check) => check.evidenceClass === "manual-dashboard" || check.evidenceClass === "mcp-verifier" || check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.status === "needs-connection" || check.status === "unknown"
6898
+ ).map((check) => ({ area: area.label, provider: mission.providerLabel, check }))
6899
+ )
6900
+ );
6901
+ lines.push("");
6902
+ lines.push("## Human-provider actions");
6903
+ if (manualChecks.length === 0) {
6904
+ lines.push("_No manual provider actions were identified in this scan._");
6905
+ } else {
6906
+ manualChecks.slice(0, 8).forEach((item3, index) => {
6907
+ lines.push(`${index + 1}. **${item3.check.label}** (${item3.area} / ${item3.provider})`);
6908
+ lines.push(
6909
+ ` - ${item3.check.promptHint || "Ask the user to confirm this in the provider dashboard or through read-only MCP."}`
6910
+ );
5581
6911
  });
5582
6912
  }
5583
6913
  lines.push("");
6914
+ lines.push("Do not claim human-provider actions as repo-code fixes.");
6915
+ lines.push("");
5584
6916
  lines.push("## Agent workflow");
5585
- lines.push("1. Pick the highest-severity gap unless the user names an area or provider.");
5586
- lines.push("2. Run `viberaven prompt --gap <id>` (or read `copyPrompt` from `last-scan.json`).");
5587
- lines.push("3. Implement the fix in the repo.");
5588
- lines.push("4. Run `npx -y @viberaven/cli@beta scan` again to verify readiness improved.");
5589
- lines.push("5. Tell the user to open `.viberaven/report.html` for the visual mission map.");
6917
+ lines.push("1. Pick the highest-severity agent-code gap unless the user names an area or provider.");
6918
+ lines.push("2. Run `viberaven prompt --gap <id>` or read `copyPrompt` from `last-scan.json`.");
6919
+ lines.push("3. Implement the repo-code fix.");
6920
+ lines.push("4. Run `npx -y @viberaven/cli@beta scan` again.");
6921
+ lines.push("5. Tell the user to review `.viberaven/report.html` for the visual mission map.");
5590
6922
  lines.push("");
5591
6923
  if (artifact.usage) {
5592
6924
  lines.push("## Account usage");
@@ -5719,6 +7051,24 @@ var PANEL_CLIENT_SCRIPT = `
5719
7051
 
5720
7052
 
5721
7053
 
7054
+ function normalizeProviderToken(value) {
7055
+
7056
+ return String(value == null ? '' : value)
7057
+ .trim()
7058
+ .toLowerCase()
7059
+ .replace(/&/g, 'and')
7060
+ .replace(/[^a-z0-9]+/g, '');
7061
+
7062
+ }
7063
+
7064
+ function buildProviderStackCommand(areaKey, provider) {
7065
+
7066
+ return 'viberaven stack set ' + areaKey + ' ' + normalizeProviderToken(provider) + ' && viberaven scan';
7067
+
7068
+ }
7069
+
7070
+
7071
+
5722
7072
  function normKey(p) {
5723
7073
 
5724
7074
  if (!p) return '';
@@ -5727,6 +7077,8 @@ var PANEL_CLIENT_SCRIPT = `
5727
7077
 
5728
7078
  var compact = raw.replace(/[^a-z0-9]+/g, '');
5729
7079
 
7080
+ if (!compact || compact === 'notselected' || raw === 'not selected' || raw === 'none') return '';
7081
+
5730
7082
  if (logoPayload.aliases[raw]) return logoPayload.aliases[raw];
5731
7083
 
5732
7084
  if (logoPayload.aliases[compact]) return logoPayload.aliases[compact];
@@ -5791,17 +7143,101 @@ var PANEL_CLIENT_SCRIPT = `
5791
7143
 
5792
7144
  }
5793
7145
 
7146
+ if (key && logoPayload.logos[key]) return logoPayload.logos[key];
7147
+
5794
7148
  if (key && logoPayload.iconUrls && logoPayload.iconUrls[key]) {
5795
7149
 
5796
7150
  return '<img class="provider-logo__img" src="' + esc(logoPayload.iconUrls[key]) + '" alt="" decoding="async" data-provider-logo-key="' + esc(key) + '" />';
5797
7151
 
5798
7152
  }
5799
7153
 
5800
- if (key && logoPayload.logos[key]) return logoPayload.logos[key];
7154
+ return logoPayload.fallbackSvg || '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3 20 7.5v9L12 21 4 16.5v-9L12 3Zm0 3.3-5 2.8v5.8l5 2.8 5-2.8V9.1l-5-2.8Zm0 3.2a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z"/></svg>';
7155
+
7156
+ }
7157
+
7158
+
7159
+
7160
+ function nodeTone(p) {
7161
+
7162
+ var key = normKey(p);
7163
+
7164
+ if (!key || !logoPayload.nodeTones || !logoPayload.nodeTones[key]) return null;
7165
+
7166
+ return logoPayload.nodeTones[key];
7167
+
7168
+ }
7169
+
7170
+
7171
+
7172
+ function syncMapNode(areaKey, providerOption) {
7173
+
7174
+ var node = document.querySelector('.studio-node[data-area-key="' + areaKey + '"]');
7175
+
7176
+ if (!node) return;
7177
+
7178
+ var area = areas.find(function (a) { return a.key === areaKey; });
7179
+
7180
+ var areaLabel = (area && area.label) || LABELS[areaKey] || areaKey;
7181
+
7182
+ var missions = (area && area.providerMissions) || [];
7183
+
7184
+ var mission = missionForProvider(missions, providerOption);
7185
+
7186
+ var providerLabel = providerLabelFor(areaKey, providerOption, mission, areaLabel);
7187
+
7188
+ var iconKey = normKey(providerOption);
7189
+
7190
+ var logoInner = logoHtml(iconKey || providerOption, providerLabel);
5801
7191
 
5802
- var t = (label || p || '?').trim();
7192
+ var cls = logoClass(iconKey || providerOption);
5803
7193
 
5804
- return '<span aria-hidden="true">' + esc(t.slice(0, 2).toUpperCase()) + '</span>';
7194
+ var logoEl = node.querySelector('.studio-node__logo');
7195
+
7196
+ if (logoEl) {
7197
+
7198
+ logoEl.className = 'studio-node__logo provider-logo' + cls;
7199
+
7200
+ logoEl.innerHTML = logoInner;
7201
+
7202
+ }
7203
+
7204
+ node.className = node.className
7205
+
7206
+ .replace(/\\sprovider-logo--[a-z0-9-]+/g, '')
7207
+
7208
+ .replace(/\\sprovider-logo--brand/g, '')
7209
+
7210
+ .trim();
7211
+
7212
+ if (!/\\bprovider-logo\\b/.test(node.className)) node.className += ' provider-logo';
7213
+
7214
+ node.className += cls;
7215
+
7216
+ var provSpan = node.querySelector('.studio-node__provider');
7217
+
7218
+ if (provSpan) provSpan.textContent = providerLabel;
7219
+
7220
+ var tone = nodeTone(providerOption);
7221
+
7222
+ if (tone) {
7223
+
7224
+ node.style.setProperty('--provider-color', tone[0]);
7225
+
7226
+ node.style.setProperty('--provider-glow', tone[1]);
7227
+
7228
+ } else {
7229
+
7230
+ node.style.removeProperty('--provider-color');
7231
+
7232
+ node.style.removeProperty('--provider-glow');
7233
+
7234
+ }
7235
+
7236
+ var metaSpan = node.querySelector('.studio-node__meta');
7237
+
7238
+ var metaText = metaSpan ? metaSpan.textContent : '';
7239
+
7240
+ node.setAttribute('aria-label', areaLabel + ', ' + providerLabel + ', ' + metaText);
5805
7241
 
5806
7242
  }
5807
7243
 
@@ -5887,19 +7323,53 @@ var PANEL_CLIENT_SCRIPT = `
5887
7323
 
5888
7324
 
5889
7325
 
5890
- function sameProvider(a, b) {
7326
+ function firstProviderOption(areaKey) {
5891
7327
 
5892
- if (!a || !b) return false;
7328
+ var options = providerOptions[areaKey] || [];
5893
7329
 
5894
- return String(a).toLowerCase() === String(b).toLowerCase() || normKey(a) === normKey(b);
7330
+ return options[0] || null;
5895
7331
 
5896
7332
  }
5897
7333
 
5898
7334
 
5899
7335
 
5900
- function projectProviderFor(areaKey, missions) {
7336
+ function iconProviderKey(areaKey, currentProvider, mission, providerLabel) {
5901
7337
 
5902
- if (projectProviders[areaKey]) return projectProviders[areaKey];
7338
+ var option = optionFor(areaKey, currentProvider);
7339
+
7340
+ var firstOption = firstProviderOption(areaKey);
7341
+
7342
+ return currentProvider ||
7343
+
7344
+ (mission && (mission.provider || mission.providerLabel || mission.key)) ||
7345
+
7346
+ (option && (option.provider || option.label)) ||
7347
+
7348
+ (firstOption && (firstOption.provider || firstOption.label)) ||
7349
+
7350
+ providerLabel ||
7351
+
7352
+ LABELS[areaKey] ||
7353
+
7354
+ areaKey;
7355
+
7356
+ }
7357
+
7358
+
7359
+
7360
+ function sameProvider(a, b) {
7361
+
7362
+ if (!a || !b) return false;
7363
+
7364
+ return String(a).toLowerCase() === String(b).toLowerCase() || normKey(a) === normKey(b);
7365
+
7366
+ }
7367
+
7368
+
7369
+
7370
+ function projectProviderFor(areaKey, missions) {
7371
+
7372
+ if (projectProviders[areaKey]) return projectProviders[areaKey];
5903
7373
 
5904
7374
  var mission = preferredMission(missions, '');
5905
7375
 
@@ -6121,126 +7591,588 @@ var PANEL_CLIENT_SCRIPT = `
6121
7591
 
6122
7592
  var desc = opt.description || benefitText(p);
6123
7593
 
6124
- var status = inProject ? 'Using now' : active ? 'Added to setup' : 'Use this path';
7594
+ var providerOption = normKey(p) || normalizeProviderToken(p);
7595
+
7596
+ var status = inProject ? 'Using now' : active ? 'Added to setup' : 'Use this path';
7597
+
7598
+ return '<button type="button" class="studio-choice-tile' + (active ? ' studio-choice-tile--selected' : '') + (inProject ? ' studio-choice-tile--in-project' : '') +
7599
+
7600
+ '" data-provider-option="' + attrEsc(providerOption) + '" data-provider-label="' + attrEsc(p) + '" data-area-key="' + attrEsc(areaKey) + '" aria-pressed="' + (active ? 'true' : 'false') + '">' +
7601
+
7602
+ '<span class="studio-choice-tile__icon provider-logo' + logoClass(p) + '" aria-hidden="true">' + logoHtml(p, opt.label) + '</span>' +
7603
+
7604
+ '<span class="studio-choice-tile__name">' + esc(opt.label) + '</span>' +
7605
+
7606
+ '<span class="studio-choice-tile__desc">' + esc(desc) + '</span>' +
7607
+
7608
+ '<span class="studio-choice-tile__status">' + status + '</span>' +
7609
+
7610
+ '</button>';
7611
+
7612
+ }).join('');
7613
+
7614
+ var hintLabel = CHOICE_HINTS[areaKey] || ('Choose ' + (LABELS[areaKey] || areaKey).toLowerCase());
7615
+
7616
+ return '<div class="studio-setup-panel__hint"><span>' + esc(hintLabel) + '</span>' + evidenceBadgeHtml(evidenceMissions || missions) + '</div>' +
7617
+
7618
+ '<div class="studio-choice-list" role="group" aria-label="' + attrEsc(hintLabel) + '">' + tiles + '</div>';
7619
+
7620
+ }
7621
+
7622
+
7623
+
7624
+ function buildPrompt(areaKey, missions, providerLabel, automation) {
7625
+
7626
+ if (automation) {
7627
+
7628
+ if (automation.automationLevel === 'manual-only' && automation.verificationPrompt) return automation.verificationPrompt;
7629
+
7630
+ if (automation.repoPrompt) return automation.repoPrompt;
7631
+
7632
+ if (automation.promptRoutes && automation.promptRoutes['repo-fix'] && automation.promptRoutes['repo-fix'].body) {
7633
+
7634
+ return automation.promptRoutes['repo-fix'].body;
7635
+
7636
+ }
7637
+
7638
+ }
7639
+
7640
+ var areaGaps = missions[0] ? gaps.filter(function (g) { return g.primaryMapCategory === areaKey; }) : [];
7641
+
7642
+ if (areaGaps[0] && areaGaps[0].copyPrompt) return areaGaps[0].copyPrompt;
7643
+
7644
+ var missionPrompt = stackPromptFromMission(missions[0], providerLabel);
7645
+
7646
+ if (missionPrompt) return missionPrompt;
7647
+
7648
+ var missing = (missions[0] && missions[0].checks || []).filter(function (c) {
7649
+
7650
+ return c.evidenceClass === 'missing-repo-fix' || c.status === 'missing' || c.status === 'failed';
7651
+
7652
+ });
7653
+
7654
+ if (missing[0] && missing[0].promptHint) return missing[0].promptHint;
7655
+
7656
+ return setupPromptForProvider(areaKey, providerLabel);
7657
+
7658
+ }
7659
+
7660
+
7661
+
7662
+ function setupPromptForProvider(areaKey, providerLabel) {
7663
+
7664
+ var areaLabel = LABELS[areaKey] || areaKey;
7665
+
7666
+ var provider = providerLabel || areaLabel;
7667
+
7668
+ return [
7669
+
7670
+ 'Set up ' + provider + ' for the ' + areaLabel + ' production section safely.',
7671
+
7672
+ '',
7673
+
7674
+ 'Inspect first:',
7675
+
7676
+ '- Review package.json files, env examples, framework routes, provider helpers, server/client boundaries, and existing ' + areaLabel.toLowerCase() + ' patterns before editing.',
7677
+
7678
+ '- Identify the current framework, folder structure, naming style, validation style, and test/build commands.',
7679
+
7680
+ '- If VibeRaven SIFG leak context is present, treat its leak IDs and allowed files as the source of truth.',
7681
+
7682
+ '',
7683
+
7684
+ 'Implement:',
7685
+
7686
+ '- Make the smallest repo-only changes needed to wire the ' + provider + ' path.',
7687
+
7688
+ '- Add the right package or SDK only if it is missing.',
7689
+
7690
+ '- Document required environment variable names in safe examples or setup docs without reading or exposing real secrets.',
7691
+
7692
+ '- Add server-side integration points, route handlers, webhooks, guards, or helpers only where this repo structure expects them.',
7693
+
7694
+ '',
7695
+
7696
+ 'Provider constraints:',
7697
+
7698
+ '- Do not call provider APIs, mutate external projects, or edit VibeRaven dashboard state.',
7699
+
7700
+ '- Keep dashboard/provider setup as explicit manual steps.',
7701
+
7702
+ '- Do not claim live provider configuration is complete from repo changes alone.',
7703
+
7704
+ '- Keep secrets in server-only code and env examples. Use placeholder env names only.',
7705
+
7706
+ '',
7707
+
7708
+ 'Verification:',
7709
+
7710
+ '- Run the closest relevant build, test, lint, or typecheck command.',
7711
+
7712
+ '- Confirm repo evidence exists for each implemented item.',
7713
+
7714
+ '- List provider dashboard checks separately as manual or read-only MCP verification.',
7715
+
7716
+ '- Rescan VibeRaven after editing so repo evidence can move to verified.'
7717
+
7718
+ ].join('\\n');
7719
+
7720
+
7721
+ }
7722
+
7723
+ function evidenceSourceLabel(check) {
7724
+
7725
+ if (!check) return 'Unknown';
7726
+
7727
+ if (check.evidenceSource === 'provider') return 'Provider live';
7728
+
7729
+ if (check.evidenceSource === 'mcp' || check.evidenceClass === 'mcp-verifier') return 'MCP';
7730
+
7731
+ if (check.evidenceSource === 'manual' || check.evidenceClass === 'manual-dashboard' || check.status === 'user-confirmed') return 'Manual';
7732
+
7733
+ if (check.evidenceSource === 'repo' || check.evidenceClass === 'repo-verified' || check.evidenceClass === 'repo-file') return 'Repo files';
7734
+
7735
+ return 'Unknown';
7736
+
7737
+ }
7738
+
7739
+
7740
+
7741
+ function contractItem(label, detail, source, tone) {
7742
+
7743
+ return {
7744
+
7745
+ label: label || 'Unknown',
7746
+
7747
+ detail: detail || '',
7748
+
7749
+ source: source || 'Unknown',
7750
+
7751
+ tone: tone || 'neutral'
7752
+
7753
+ };
7754
+
7755
+ }
7756
+
7757
+
7758
+
7759
+ function checkDetail(check) {
7760
+
7761
+ if (!check) return '';
7762
+
7763
+ if (check.evidence && check.evidence[0]) return check.evidence[0];
7764
+
7765
+ if (check.promptHint) return check.promptHint;
7766
+
7767
+ if (check.status === 'unknown') return 'Not checked';
7768
+
7769
+ if (check.status === 'needs-connection') return 'Needs verification';
7770
+
7771
+ return '';
7772
+
7773
+ }
7774
+
7775
+
7776
+
7777
+ function buildCliSidebarContract(areaKey, label, mission, categoryGaps, providerLabel, automation, currentProvider) {
7778
+
7779
+ var connected = [];
7780
+
7781
+ var missing = [];
7782
+
7783
+ var manual = [];
7784
+
7785
+ var checks = mission && Array.isArray(mission.checks) ? mission.checks : [];
7786
+
7787
+ var mcpProvider = (automation && automation.mcpProvider) || '';
7788
+
7789
+ checks.forEach(function (check) {
7790
+
7791
+ var source = evidenceSourceLabel(check);
7792
+
7793
+ var item = contractItem(check.label, checkDetail(check), source, source === 'Repo files' ? 'repo' : source === 'MCP' ? 'mcp' : source === 'Manual' ? 'manual' : 'neutral');
7794
+
7795
+ if ((check.status === 'passed' || check.status === 'user-confirmed') && source !== 'Unknown') {
7796
+
7797
+ connected.push(item);
7798
+
7799
+ return;
7800
+
7801
+ }
7802
+
7803
+ if (check.evidenceClass === 'missing-repo-fix' || check.status === 'missing' || check.status === 'failed') {
7804
+
7805
+ missing.push(contractItem(check.label, checkDetail(check), 'Repo files', 'missing'));
7806
+
7807
+ return;
7808
+
7809
+ }
7810
+
7811
+ if (source === 'Manual' || source === 'MCP' || check.status === 'needs-connection' || check.status === 'unknown') {
7812
+
7813
+ manual.push(contractItem(check.label, checkDetail(check) || 'Not checked', source, 'manual'));
7814
+
7815
+ }
7816
+
7817
+ });
7818
+
7819
+ (Array.isArray(categoryGaps) ? categoryGaps : []).slice(0, 4).forEach(function (gap) {
7820
+
7821
+ missing.push(contractItem(gap.title || 'Product gap needs repo review', gap.detail || 'Agent can inspect and adjust repo implementation.', 'Repo files', 'missing'));
7822
+
7823
+ });
7824
+
7825
+ if (!checks.length) {
7826
+
7827
+ manual.push(contractItem('Provider live proof', 'Not checked', 'Unknown', 'manual'));
7828
+
7829
+ }
7830
+
7831
+ if (!manual.length) {
7832
+
7833
+ manual.push(contractItem('Provider live proof', 'Not checked', 'Unknown', 'manual'));
7834
+
7835
+ }
7836
+
7837
+ return {
7838
+
7839
+ areaKey: areaKey,
7840
+
7841
+ areaLabel: label || LABELS[areaKey] || areaKey,
7842
+
7843
+ providerKey: iconProviderKey(areaKey, currentProvider, mission, providerLabel),
7844
+
7845
+ providerLabel: providerLabel || 'Selected provider',
7846
+
7847
+ connected: connected,
7848
+
7849
+ missing: missing,
7850
+
7851
+ manual: manual,
7852
+
7853
+ repoPercent: typeof (mission && mission.repoReadinessPercent) === 'number'
7854
+ ? Math.max(0, Math.min(100, Math.round(mission.repoReadinessPercent)))
7855
+ : typeof (mission && mission.readinessPercent) === 'number'
7856
+ ? Math.max(0, Math.min(100, Math.round(mission.readinessPercent)))
7857
+ : 0,
7858
+
7859
+ providerPercent: typeof (mission && mission.providerReadinessPercent) === 'number'
7860
+ ? Math.max(0, Math.min(100, Math.round(mission.providerReadinessPercent)))
7861
+ : 0,
7862
+
7863
+ hasMcpAccess: Boolean(providerLabel || mcpProvider) || checks.some(function (check) {
7864
+ return check.evidenceSource === 'mcp' || check.evidenceClass === 'mcp-verifier' || check.verificationStatus === 'needs_mcp';
7865
+ })
7866
+
7867
+ };
7868
+
7869
+ }
7870
+
7871
+
7872
+
7873
+ function contractLines(items) {
7874
+
7875
+ return (items && items.length ? items : [{ label: 'None', detail: 'No artifact-backed item.', source: 'Unknown' }]).map(function (item) {
7876
+
7877
+ return '- [' + (item.source || 'Unknown') + '] ' + item.label + (item.detail ? ': ' + item.detail : '');
7878
+
7879
+ }).join('\\n');
7880
+
7881
+ }
7882
+
7883
+
7884
+
7885
+ function focusedContractPrompt(contract) {
7886
+
7887
+ return [
7888
+
7889
+ 'VibeRaven selected-node production checklist',
7890
+
7891
+ '',
7892
+
7893
+ 'Node: ' + contract.areaLabel,
7894
+
7895
+ 'Selected provider context: ' + contract.providerLabel,
7896
+
7897
+ '',
7898
+
7899
+ 'Verified evidence:',
7900
+
7901
+ contractLines(contract.connected),
7902
+
7903
+ '',
7904
+
7905
+ 'Agent-code actions:',
7906
+
7907
+ contractLines(contract.missing),
7908
+
7909
+ '',
7910
+
7911
+ 'Human-provider actions:',
7912
+
7913
+ contractLines(contract.manual),
7914
+
7915
+ '',
7916
+
7917
+ 'Rules:',
7918
+
7919
+ '- Selected provider is context only and does not mean connected.',
7920
+
7921
+ '- Connected evidence requires Repo files, Provider live, MCP, or Manual confirmation in the artifact.',
7922
+
7923
+ '- Do not represent human-provider actions as completed code fixes.'
7924
+
7925
+ ].join('\\n');
7926
+
7927
+ }
7928
+
7929
+
7930
+
7931
+ function sidebarStatusBadge(contract) {
7932
+
7933
+ if ((contract.connected || []).some(function (item) { return item.source === 'Repo files'; })) return 'Repo evidence found';
7934
+
7935
+ if ((contract.missing || []).length) return 'Missing repo fixes';
7936
+
7937
+ if ((contract.manual || []).length) return 'Manual check';
7938
+
7939
+ return 'Selected only';
7940
+
7941
+ }
7942
+
7943
+
7944
+
7945
+ function sidebarReadinessSentence(contract) {
7946
+
7947
+ var hasRepo = (contract.connected || []).some(function (item) { return item.source === 'Repo files'; });
7948
+
7949
+ var hasLive = (contract.connected || []).some(function (item) { return item.source === 'Provider live' || item.source === 'MCP' || item.source === 'Manual'; });
7950
+
7951
+ if (hasRepo && !hasLive) return 'Repo found. Live not checked.';
7952
+
7953
+ if (hasLive) return 'Live proof exists in the scan artifact.';
7954
+
7955
+ if ((contract.missing || []).length) return 'Repo gaps found. Live not checked.';
7956
+
7957
+ return 'No verified evidence yet.';
7958
+
7959
+ }
7960
+
7961
+
7962
+
7963
+ function contractMetricHtml(label, percent, value, isLive) {
7964
+
7965
+ var safePercent = Math.max(0, Math.min(100, Math.round(percent || 0)));
7966
+
7967
+ return '<div class="studio-sidebar-contract__compact-metric' + (isLive ? ' studio-sidebar-contract__compact-metric--live' : '') + '">' +
7968
+
7969
+ '<span>' + esc(label) + '</span>' +
7970
+
7971
+ '<strong>' + esc(value || (safePercent + '%')) + '</strong>' +
7972
+
7973
+ '<i class="studio-sidebar-contract__compact-meter" aria-hidden="true"><b style="width:' + safePercent + '%"></b></i>' +
7974
+
7975
+ '</div>';
7976
+
7977
+ }
7978
+
7979
+
7980
+
7981
+ function contractReadinessHtml(contract) {
7982
+
7983
+ var providerKey = contract.providerKey || contract.providerLabel || contract.areaLabel;
7984
+
7985
+ var providerStatus = contract.providerPercent > 0 ? contract.providerPercent + '%' : 'Not checked';
7986
+
7987
+ return '<section class="studio-sidebar-contract__readiness" aria-label="' + attrEsc(contract.providerLabel + ' readiness') + '">' +
7988
+
7989
+ '<div class="studio-sidebar-contract__head">' +
7990
+
7991
+ '<span class="studio-sidebar-contract__logo provider-logo' + logoClass(providerKey) + '" title="' + attrEsc(contract.providerLabel) + '" aria-hidden="true">' + logoHtml(providerKey, contract.providerLabel) + '</span>' +
7992
+
7993
+ '<div class="studio-sidebar-contract__head-text"><strong>' + esc(contract.providerLabel) + '</strong><span>' + esc(contract.areaLabel) + '</span></div>' +
7994
+
7995
+ '<span class="studio-sidebar-contract__status">' + esc(sidebarStatusBadge(contract)) + '</span>' +
7996
+
7997
+ '</div>' +
7998
+
7999
+ '<div class="studio-sidebar-contract__compact-metrics">' +
8000
+
8001
+ contractMetricHtml('Repo files', contract.repoPercent, contract.repoPercent + '%', false) +
8002
+
8003
+ contractMetricHtml('Provider live', contract.providerPercent, providerStatus, true) +
8004
+
8005
+ '</div>' +
8006
+
8007
+ '<p class="studio-sidebar-contract__summary">' + esc(sidebarReadinessSentence(contract)) + '</p>' +
8008
+
8009
+ '</section>';
8010
+
8011
+ }
8012
+
8013
+
8014
+
8015
+ function contractRepoSignalsHtml(contract) {
8016
+
8017
+ var repoItems = (contract.connected || []).filter(function (item) { return item.source === 'Repo files'; });
8018
+
8019
+ if (!repoItems.length) return '';
8020
+
8021
+ var rows = repoItems.slice(0, 6).map(function (item) {
8022
+
8023
+ return '<li><strong>' + esc(item.label || 'Repo evidence') + '</strong><span>' + esc(item.source || 'Repo files') + '</span></li>';
8024
+
8025
+ }).join('');
8026
+
8027
+ return '<section class="studio-sidebar-contract__repo-signals">' +
8028
+
8029
+ '<h4>' + repoItems.length + ' repo signal' + (repoItems.length === 1 ? '' : 's') + ' in the codebase.</h4>' +
8030
+
8031
+ '<ul class="studio-sidebar-contract__signal-list">' + rows + '</ul>' +
8032
+
8033
+ '</section>';
8034
+
8035
+ }
8036
+
8037
+
8038
+
8039
+ function contractAttentionItems(contract) {
8040
+
8041
+ var items = [];
8042
+
8043
+ (contract.missing || []).slice(0, 3).forEach(function (item) {
8044
+
8045
+ items.push({
8046
+
8047
+ label: item.label,
8048
+
8049
+ detail: item.detail || 'Repo/config check needs attention.',
8050
+
8051
+ source: 'Agent-code',
8052
+
8053
+ next: 'Ask the agent to inspect related files and patch only repo code/config.'
6125
8054
 
6126
- return '<button type="button" class="studio-choice-tile' + (active ? ' studio-choice-tile--selected' : '') + (inProject ? ' studio-choice-tile--in-project' : '') +
8055
+ });
6127
8056
 
6128
- '" data-switch-area="' + esc(areaKey) + '" data-switch-provider="' + esc(opt.provider) + '" aria-pressed="' + (active ? 'true' : 'false') + '">' +
8057
+ });
6129
8058
 
6130
- '<span class="studio-choice-tile__icon provider-logo' + logoClass(p) + '" aria-hidden="true">' + logoHtml(p, opt.label) + '</span>' +
8059
+ (contract.manual || []).slice(0, 3).forEach(function (item) {
6131
8060
 
6132
- '<span class="studio-choice-tile__name">' + esc(opt.label) + '</span>' +
8061
+ items.push({
6133
8062
 
6134
- '<span class="studio-choice-tile__desc">' + esc(desc) + '</span>' +
8063
+ label: item.label,
6135
8064
 
6136
- '<span class="studio-choice-tile__status">' + status + '</span>' +
8065
+ detail: item.detail || 'MCP/API/dashboard required.',
6137
8066
 
6138
- '</button>';
8067
+ source: item.source === 'MCP' ? 'MCP' : 'Manual',
6139
8068
 
6140
- }).join('');
8069
+ next: item.source === 'MCP' ? 'Use read-only MCP/API verification.' : 'Ask the user to confirm the provider dashboard.'
6141
8070
 
6142
- var hintLabel = CHOICE_HINTS[areaKey] || ('Choose ' + (LABELS[areaKey] || areaKey).toLowerCase());
8071
+ });
6143
8072
 
6144
- return '<div class="studio-setup-panel__hint"><span>' + esc(hintLabel) + '</span>' + evidenceBadgeHtml(evidenceMissions || missions) + '</div>' +
8073
+ });
6145
8074
 
6146
- '<div class="studio-choice-list" role="group" aria-label="' + esc(hintLabel) + '">' + tiles + '</div>';
8075
+ return items;
6147
8076
 
6148
8077
  }
6149
8078
 
6150
8079
 
6151
8080
 
6152
- function buildPrompt(areaKey, missions, providerLabel, automation) {
8081
+ function contractAttentionHtml(contract) {
6153
8082
 
6154
- if (automation) {
8083
+ var items = contractAttentionItems(contract);
6155
8084
 
6156
- if (automation.automationLevel === 'manual-only' && automation.verificationPrompt) return automation.verificationPrompt;
8085
+ if (!items.length) return '';
6157
8086
 
6158
- if (automation.repoPrompt) return automation.repoPrompt;
8087
+ function attentionRow(item) {
6159
8088
 
6160
- if (automation.promptRoutes && automation.promptRoutes['repo-fix'] && automation.promptRoutes['repo-fix'].body) {
8089
+ return '<li class="studio-sidebar-contract__attention-item">' +
6161
8090
 
6162
- return automation.promptRoutes['repo-fix'].body;
8091
+ '<div class="studio-sidebar-contract__attention-top"><strong>' + esc(item.label || 'Attention') + '</strong><em>' + esc(item.source || 'Verify') + '</em></div>' +
6163
8092
 
6164
- }
8093
+ (item.detail ? '<span>' + esc(item.detail) + '</span>' : '') +
8094
+
8095
+ (item.next ? '<b>' + esc(item.next) + '</b>' : '') +
8096
+
8097
+ '</li>';
6165
8098
 
6166
8099
  }
6167
8100
 
6168
- var areaGaps = missions[0] ? gaps.filter(function (g) { return g.primaryMapCategory === areaKey; }) : [];
8101
+ var first = attentionRow(items[0]);
6169
8102
 
6170
- if (areaGaps[0] && areaGaps[0].copyPrompt) return areaGaps[0].copyPrompt;
8103
+ var more = items.length > 1
6171
8104
 
6172
- var missionPrompt = stackPromptFromMission(missions[0], providerLabel);
8105
+ ? '<li class="studio-sidebar-contract__attention-more"><details class="studio-sidebar-contract__attention-details"><summary>+' + (items.length - 1) + ' more details</summary><ul class="studio-sidebar-contract__attention-detail-list">' +
6173
8106
 
6174
- if (missionPrompt) return missionPrompt;
8107
+ items.slice(1).map(function (item) {
6175
8108
 
6176
- var missing = (missions[0] && missions[0].checks || []).filter(function (c) {
8109
+ return '<li><strong>' + esc(item.label || 'Attention') + '</strong>' +
6177
8110
 
6178
- return c.evidenceClass === 'missing-repo-fix' || c.status === 'missing' || c.status === 'failed';
8111
+ (item.detail ? '<span>' + esc(item.detail) + '</span>' : '') +
6179
8112
 
6180
- });
8113
+ (item.next ? '<b>' + esc(item.next) + '</b>' : '') +
6181
8114
 
6182
- if (missing[0] && missing[0].promptHint) return missing[0].promptHint;
8115
+ '</li>';
6183
8116
 
6184
- return setupPromptForProvider(areaKey, providerLabel);
8117
+ }).join('') +
6185
8118
 
6186
- }
8119
+ '</ul></details></li>'
6187
8120
 
8121
+ : '';
6188
8122
 
8123
+ return '<section class="studio-sidebar-contract__attention"><h4>Attention</h4><ul class="studio-sidebar-contract__attention-list">' + first + more + '</ul></section>';
6189
8124
 
6190
- function setupPromptForProvider(areaKey, providerLabel) {
8125
+ }
6191
8126
 
6192
- var areaLabel = LABELS[areaKey] || areaKey;
6193
8127
 
6194
- var provider = providerLabel || areaLabel;
6195
8128
 
6196
- return [
8129
+ function contractNextActionHtml(contract, prompt) {
6197
8130
 
6198
- 'Set up ' + provider + ' for the ' + areaLabel + ' area of this app safely.',
8131
+ var buttonText = (contract.missing || []).length ? 'Copy focused agent prompt' : (contract.manual || []).length ? 'Copy live check prompt' : 'Copy verification prompt';
6199
8132
 
6200
- '',
8133
+ var note = (contract.missing || []).length ? 'Inspect repo wiring, patch scoped gaps, then rescan.' : (contract.manual || []).length ? 'Confirm in dashboard or connect MCP.' : 'Inspect repo wiring, then verify live setup.';
6201
8134
 
6202
- 'First inspect package.json files, env examples, framework routes, provider helpers, server/client boundaries, and existing billing/auth/deployment patterns before editing.',
8135
+ var accessClass = contract.hasMcpAccess ? ' studio-sidebar-contract__access-state--confirmed' : ' studio-sidebar-contract__access-state--needs-connection';
6203
8136
 
6204
- '',
8137
+ var accessLabel = contract.hasMcpAccess ? 'Available' : 'Connect';
6205
8138
 
6206
- 'Implement the smallest useful setup:',
8139
+ return '<div class="studio-sidebar-contract__next-action">' +
6207
8140
 
6208
- '1. Add the right ' + provider + ' package or SDK only if it is missing.',
8141
+ '<strong>Next best action</strong>' +
6209
8142
 
6210
- '2. Document required environment variables in safe examples, without reading or exposing real secrets.',
8143
+ '<p>' + esc(note) + '</p>' +
6211
8144
 
6212
- '3. Add server-side integration points, route handlers, webhooks, or helpers that match this repo structure.',
8145
+ '<div class="studio-sidebar-contract__access-state' + accessClass + '"><span>MCP/API access</span><b>' + esc(accessLabel) + '</b></div>' +
6213
8146
 
6214
- '4. Keep external provider dashboard setup explicit as a manual step.',
8147
+ '<div class="studio-sidebar-contract__actions"><button type="button" class="studio-action-button studio-action-button--primary" data-copy-prompt="' + attrEsc(prompt) + '">' + esc(buttonText) + '</button></div>' +
6215
8148
 
6216
- '',
8149
+ '</div>';
6217
8150
 
6218
- 'Constraints:',
8151
+ }
6219
8152
 
6220
- '- Do not rewrite unrelated product, auth, payment, database, deployment, or analytics code.',
6221
8153
 
6222
- '- Do not put secret keys in client code, public env variables, or browser-executed files.',
6223
8154
 
6224
- '- Do not claim live provider configuration is complete from repo changes alone.',
8155
+ function selectedNodeContractHtml(contract) {
6225
8156
 
6226
- '',
8157
+ var prompt = focusedContractPrompt(contract);
6227
8158
 
6228
- 'Verification:',
8159
+ return '<section class="studio-sidebar-contract" aria-label="Selected node production checklist">' +
6229
8160
 
6230
- '- Run the relevant TypeScript/build/test command for this repo.',
8161
+ contractReadinessHtml(contract) +
6231
8162
 
6232
- '- Summarize repo changes and list dashboard/provider checks still needed.',
8163
+ contractRepoSignalsHtml(contract) +
6233
8164
 
6234
- '- Re-run VibeRaven so repo evidence can move from setup needed to verified.'
8165
+ contractAttentionHtml(contract) +
6235
8166
 
6236
- ].join('\\n');
8167
+ contractNextActionHtml(contract, prompt) +
6237
8168
 
8169
+ '</section>';
6238
8170
 
6239
8171
  }
6240
8172
 
6241
8173
 
6242
8174
 
6243
- function setupActionsHtml(areaKey, missions, providerLabel, automation) {
8175
+ function setupActionsHtml(areaKey, missions, providerLabel, automation, currentProvider) {
6244
8176
 
6245
8177
  var prompt = buildPrompt(areaKey, missions, providerLabel, automation);
6246
8178
 
@@ -6264,6 +8196,8 @@ var PANEL_CLIENT_SCRIPT = `
6264
8196
 
6265
8197
  var supportsMcp = Boolean(mcpProvider);
6266
8198
 
8199
+ var providerKey = iconProviderKey(areaKey, currentProvider, missions[0], providerLabel);
8200
+
6267
8201
  var title = esc(providerLabel || LABELS[areaKey] || areaKey) + (isManualOnly ? ' manual check' : hasFixes ? ' fix prompt' : ' setup');
6268
8202
 
6269
8203
  var meta = supportsMcp ? 'MCP verification available' : 'Prompt only';
@@ -6286,7 +8220,13 @@ var PANEL_CLIENT_SCRIPT = `
6286
8220
 
6287
8221
  return '<section class="studio-setup-actions" aria-label="Setup actions">' +
6288
8222
 
6289
- '<div class="studio-setup-actions__head"><strong>' + title + '</strong><span>' + meta + '</span></div>' +
8223
+ '<div class="studio-setup-actions__head">' +
8224
+
8225
+ '<span class="studio-setup-actions__logo provider-logo' + logoClass(providerKey) + '" title="' + attrEsc(providerLabel || providerKey) + '" aria-hidden="true">' + logoHtml(providerKey, providerLabel) + '</span>' +
8226
+
8227
+ '<div class="studio-setup-actions__title"><strong>' + title + '</strong><span>' + meta + '</span></div>' +
8228
+
8229
+ '</div>' +
6290
8230
 
6291
8231
  '<p class="studio-setup-actions__copy">' + esc(copy) + '</p>' +
6292
8232
 
@@ -6342,39 +8282,40 @@ var PANEL_CLIENT_SCRIPT = `
6342
8282
 
6343
8283
  var repoItems = [
6344
8284
 
6345
- { label: provider + ' package or SDK installed', detail: 'VibeRaven has not found repo evidence for this setup path yet.' },
8285
+ { label: provider + ' repo wiring', detail: 'Not checked' },
6346
8286
 
6347
- { label: provider + ' env names documented', detail: 'Use safe examples only. Do not expose real provider secrets.' },
8287
+ { label: provider + ' env names', detail: 'Unknown' },
6348
8288
 
6349
- { label: provider + ' server integration or route found', detail: 'Add provider code on the server side before rescanning.' }
8289
+ { label: provider + ' server route', detail: 'Unknown' }
6350
8290
 
6351
8291
  ];
6352
8292
 
6353
8293
  var manualItems = [
6354
8294
 
6355
- { label: 'Production ' + provider + ' account and product checked', detail: 'Dashboard confirmation stays manual unless a trusted MCP verifier is configured.' },
8295
+ { label: 'Production account', detail: 'Not checked' },
6356
8296
 
6357
- { label: 'Production webhook or provider credentials checked', detail: 'Repo evidence cannot prove live provider settings by itself.' }
8297
+ { label: 'Webhook or credentials', detail: 'Not checked' }
6358
8298
 
6359
8299
  ];
6360
8300
 
6361
8301
  var external = automation && automation.mcpProvider
6362
8302
 
6363
- ? groupHtml('MCP verifier', [{ label: provider + ' MCP verifier available', detail: 'Use read-only MCP verification when already configured by the IDE.' }], 'external')
8303
+ ? groupHtml('Provider live check', [{ label: provider + ' read-only MCP check available', detail: 'Use read-only MCP verification when already configured by the IDE.' }], 'external')
6364
8304
 
6365
8305
  : '';
6366
8306
 
6367
- return '<section class="studio-verification studio-provider-readiness" aria-label="' + esc(provider) + ' setup readiness">' +
8307
+ return '<section class="studio-verification studio-provider-readiness" aria-label="' + attrEsc(provider + ' setup readiness') + '">' +
6368
8308
 
6369
8309
  '<h3 class="studio-verification__title">' + esc(provider) + '</h3>' +
6370
8310
 
6371
- readinessMetersHtml(0, 0, 'Not checked') +
8311
+ readinessMetersHtml(0, 0, 'Not checked', provider) +
8312
+
6372
8313
 
6373
- '<p class="studio-provider-readiness__note">Provider live moves after MCP verification or manual dashboard confirmation. Repo scans cannot prove live provider state.</p>' +
8314
+ repoEvidenceCardHtml(repoItems.map(function (item) {
6374
8315
 
6375
- '<p class="studio-wiring__summary">' + esc(provider) + ' is added as a setup path, but VibeRaven has not found repo evidence for it yet.</p>' +
8316
+ return { label: item.label, detail: item.detail, tone: 'missing' };
6376
8317
 
6377
- groupHtml('Repo setup needed', repoItems, 'missing') +
8318
+ }), repoItems.length) +
6378
8319
 
6379
8320
  external +
6380
8321
 
@@ -6386,18 +8327,24 @@ var PANEL_CLIENT_SCRIPT = `
6386
8327
 
6387
8328
 
6388
8329
 
6389
- function readinessMetersHtml(repoPercent, providerPercent, providerStatus) {
8330
+ function readinessMetersHtml(repoPercent, providerPercent, providerStatus, providerLabel) {
6390
8331
 
6391
8332
  var repo = Math.max(0, Math.min(100, Math.round(repoPercent || 0)));
6392
8333
 
6393
8334
  var provider = Math.max(0, Math.min(100, Math.round(providerPercent || 0)));
6394
8335
  var providerValue = providerStatus || (provider + '%');
6395
8336
 
6396
- return '<div class="studio-provider-readiness__meters" aria-label="Provider readiness meters">' +
8337
+ return '<div class="verification-card studio-mission-card studio-mission-card--provider" aria-label="Repo and live provider readiness">' +
8338
+
8339
+ '<span class="studio-mission-card__title">' + esc(providerLabel || 'Provider') + '</span>' +
8340
+
8341
+ '<div class="verification-meter studio-mission-card__meters">' +
6397
8342
 
6398
- '<div class="studio-provider-readiness__meter-row"><span>Repo &middot; files</span><span class="studio-provider-readiness__bar" aria-hidden="true"><span class="studio-provider-readiness__bar-fill" style="width:' + repo + '%"></span></span><strong>' + repo + '%</strong></div>' +
8343
+ '<b class="studio-mission-card__meter-row studio-mission-card__meter-row--repo"><span class="studio-mission-card__meter-label">Repo files</span><span class="verification-meter__bar studio-mission-card__meter" aria-hidden="true"><span class="verification-meter__fill studio-mission-card__meter-fill" style="width:' + repo + '%"></span></span><span class="studio-mission-card__meter-value">' + repo + '%</span></b>' +
6399
8344
 
6400
- '<div class="studio-provider-readiness__meter-row"><span>Provider &middot; live</span><span class="studio-provider-readiness__bar" aria-hidden="true"><span class="studio-provider-readiness__bar-fill" style="width:' + provider + '%"></span></span><strong>' + esc(providerValue) + '</strong></div>' +
8345
+ '<b class="studio-mission-card__meter-row studio-mission-card__meter-row--provider"><span class="studio-mission-card__meter-label">Provider live</span><span class="verification-meter__bar studio-mission-card__meter" aria-hidden="true"><span class="verification-meter__fill studio-mission-card__meter-fill" style="width:' + provider + '%"></span></span><span class="studio-mission-card__meter-value">' + esc(providerValue) + '</span></b>' +
8346
+
8347
+ '</div>' +
6401
8348
 
6402
8349
  '</div>';
6403
8350
 
@@ -6413,6 +8360,90 @@ var PANEL_CLIENT_SCRIPT = `
6413
8360
 
6414
8361
  }
6415
8362
 
8363
+ function checkGroupKey(check) {
8364
+
8365
+ if (check.evidenceClass === 'manual-dashboard') return 'manual-dashboard';
8366
+
8367
+ if (check.evidenceSource === 'provider' || check.evidenceSource === 'mcp' || check.evidenceClass === 'mcp-verifier' || check.status === 'needs-connection' || check.status === 'unknown') return 'mcp-verifier';
8368
+
8369
+ if (check.evidenceClass === 'repo-verified' || check.status === 'passed') return 'repo-verified';
8370
+
8371
+ return 'missing-repo-fix';
8372
+
8373
+ }
8374
+
8375
+ function providerChecksForMission(mission) {
8376
+
8377
+ return (mission.checks || []).filter(function (check) {
8378
+
8379
+ return check.evidenceSource === 'provider' || check.evidenceSource === 'mcp' || check.evidenceClass === 'mcp-verifier';
8380
+
8381
+ });
8382
+
8383
+ }
8384
+
8385
+ function providerStatusValue(mission, providerPercent) {
8386
+
8387
+ var checks = providerChecksForMission(mission);
8388
+
8389
+ if (!checks.length) return 'No live checks';
8390
+
8391
+ var needsMcp = checks.some(function (check) {
8392
+
8393
+ return check.status === 'needs-connection' || check.verificationStatus === 'needs_mcp';
8394
+
8395
+ });
8396
+
8397
+ if (needsMcp) return 'Needs MCP';
8398
+
8399
+ return providerPercent > 0 ? providerPercent + '%' : 'Not checked';
8400
+
8401
+ }
8402
+
8403
+ function diffHtml(mission) {
8404
+
8405
+ var layer = artifact && artifact.missionGraph && artifact.missionGraph.verificationLayer;
8406
+
8407
+ var diffs = layer && Array.isArray(layer.diffs) ? layer.diffs : [];
8408
+
8409
+ var provider = normalizeProviderToken(mission.provider || '');
8410
+
8411
+ var area = mission.area || '';
8412
+
8413
+ var matches = diffs.filter(function (diff) {
8414
+
8415
+ return normalizeProviderToken(diff.provider || '') === provider && (!area || diff.area === area);
8416
+
8417
+ });
8418
+
8419
+ if (!matches.length) return '';
8420
+
8421
+ var rows = matches.slice(0, 4).map(function (diff) {
8422
+
8423
+ return '<div class="studio-mismatch-card__row">' +
8424
+
8425
+ '<span class="studio-mismatch-card__title">' + esc(diff.title || 'Provider mismatch') + '</span>' +
8426
+
8427
+ '<div class="studio-mismatch-card__comparison">' +
8428
+
8429
+ '<span class="studio-mismatch-card__side studio-mismatch-card__side--repo"><b>Repo</b><em>' + esc(diff.repoExpectation || 'Expected setup evidence in repository') + '</em></span>' +
8430
+
8431
+ '<span class="studio-mismatch-card__side studio-mismatch-card__side--live"><b>Live</b><em>' + esc(diff.providerActual || 'Not verified') + '</em></span>' +
8432
+
8433
+ '</div></div>';
8434
+
8435
+ }).join('');
8436
+
8437
+ return '<div class="studio-mismatch-card">' +
8438
+
8439
+ '<div class="studio-mismatch-card__head"><strong>Mismatch</strong><span class="studio-mismatch-card__count">' + matches.length + '</span></div>' +
8440
+
8441
+ '<p class="studio-mismatch-card__note">Repo evidence is present, but the live provider still needs MCP or dashboard confirmation.</p>' +
8442
+
8443
+ '<div class="studio-mismatch-card__list">' + rows + '</div></div>';
8444
+
8445
+ }
8446
+
6416
8447
 
6417
8448
 
6418
8449
  function groupHtml(label, items, tone) {
@@ -6423,7 +8454,7 @@ var PANEL_CLIENT_SCRIPT = `
6423
8454
 
6424
8455
  var rows = slice.map(function (item) {
6425
8456
 
6426
- var title = item.detail ? ' title="' + esc(item.detail) + '"' : '';
8457
+ var title = item.detail ? ' title="' + attrEsc(item.detail) + '"' : '';
6427
8458
 
6428
8459
  return '<li class="studio-verification__item' + (tone === 'manual' ? ' studio-verification__item--manual' : '') + '"' + title + '><span class="studio-verification__item-label">' + esc(item.label) + '</span></li>';
6429
8460
 
@@ -6445,6 +8476,44 @@ var PANEL_CLIENT_SCRIPT = `
6445
8476
 
6446
8477
  }
6447
8478
 
8479
+ function repoEvidenceCardHtml(repoItems, missingCount) {
8480
+
8481
+ if (!repoItems.length) return '';
8482
+
8483
+ var rows = repoItems.slice(0, 7).map(function (item) {
8484
+
8485
+ var tone = item.tone || 'verified';
8486
+ var statusLabel = tone === 'missing' ? 'Fix needed' : 'Verified';
8487
+ var title = item.detail ? ' title="' + attrEsc(item.detail) + '"' : '';
8488
+
8489
+ return '<li class="studio-mission-card__check studio-mission-card__check--' + esc(tone) + '"' + title + '>' +
8490
+
8491
+ '<b class="studio-mission-card__check-label">' + esc(item.label) + '</b>' +
8492
+
8493
+ '<em class="studio-mission-card__check-status">' + esc(statusLabel) + '</em>' +
8494
+
8495
+ '</li>';
8496
+
8497
+ }).join('');
8498
+
8499
+ return '<div class="repo-evidence-card studio-mission-card studio-mission-card--repo">' +
8500
+
8501
+ '<span class="studio-mission-card__title">' +
8502
+
8503
+ (missingCount > 0
8504
+
8505
+ ? missingCount + ' repo fix' + (missingCount === 1 ? '' : 'es') + ' in the codebase.'
8506
+
8507
+ : repoItems.length + ' repo check' + (repoItems.length === 1 ? '' : 's') + ' verified in the codebase.') +
8508
+
8509
+ '</span>' +
8510
+
8511
+ '<ul class="studio-mission-card__check-list">' + rows + '</ul>' +
8512
+
8513
+ '</div>';
8514
+
8515
+ }
8516
+
6448
8517
 
6449
8518
 
6450
8519
  function missionBlockHtml(missions, providerLabel) {
@@ -6467,65 +8536,104 @@ var PANEL_CLIENT_SCRIPT = `
6467
8536
 
6468
8537
  mission.checks.forEach(function (check) {
6469
8538
 
6470
- var bucket = groups[check.evidenceClass] ? check.evidenceClass : 'manual-dashboard';
8539
+ var bucket = checkGroupKey(check);
6471
8540
 
6472
8541
  groups[bucket].push(checkToItem(check));
6473
8542
 
6474
8543
  });
6475
8544
 
6476
8545
  var actionable = groups['repo-verified'].length + groups['missing-repo-fix'].length;
6477
- var manualTotal = groups['manual-dashboard'].length;
6478
- var manualDone = mission.checks.filter(function (check) {
8546
+ var providerChecks = providerChecksForMission(mission);
8547
+ var providerPercent = typeof mission.providerReadinessPercent === 'number'
8548
+ ? Math.max(0, Math.min(100, Math.round(mission.providerReadinessPercent)))
8549
+ : 0;
8550
+ var providerValue = providerStatusValue(mission, providerPercent);
8551
+ var repoPercent = typeof mission.repoReadinessPercent === 'number'
8552
+ ? Math.max(0, Math.min(100, Math.round(mission.repoReadinessPercent)))
8553
+ : (mission.readinessPercent || 0);
8554
+ var repoItems = groups['missing-repo-fix'].map(function (item) {
6479
8555
 
6480
- return check.evidenceClass === 'manual-dashboard' && (check.status === 'passed' || check.status === 'user-confirmed');
8556
+ return { label: item.label, detail: item.detail, tone: 'missing' };
6481
8557
 
6482
- }).length;
6483
- var providerPercent = manualTotal ? Math.round((manualDone / manualTotal) * 100) : 0;
6484
- var providerValue = manualTotal ? (manualDone > 0 ? providerPercent + '%' : 'Not checked') : 'No live checks';
8558
+ }).concat(groups['repo-verified'].map(function (item) {
6485
8559
 
6486
- var summary =
8560
+ return { label: item.label, detail: item.detail, tone: 'verified' };
6487
8561
 
6488
- 'Stack scanner: ' + groups['repo-verified'].length + ' / ' + Math.max(actionable, 1) +
8562
+ }));
6489
8563
 
6490
- ' repo checks verified (' + (mission.readinessPercent || 0) + '%).';
8564
+ var body =
6491
8565
 
6492
- if (groups['missing-repo-fix'].length > 0) {
8566
+ repoEvidenceCardHtml(repoItems, groups['missing-repo-fix'].length) +
6493
8567
 
6494
- summary += ' Fix ' + groups['missing-repo-fix'].length + ' repo item' +
8568
+ groupHtml('Provider live check', groups['mcp-verifier'], 'external') +
6495
8569
 
6496
- (groups['missing-repo-fix'].length === 1 ? '' : 's') + ', then rescan.';
8570
+ groupHtml('Manual dashboard check', groups['manual-dashboard'], 'manual') +
6497
8571
 
6498
- }
8572
+ diffHtml(mission);
6499
8573
 
6500
- var body =
8574
+ if (!body) return '';
8575
+
8576
+ return '<section class="studio-verification studio-wiring studio-mission-graph" aria-label="Mission evidence">' +
6501
8577
 
6502
- groupHtml('Repo verified', groups['repo-verified'], 'found') +
8578
+ readinessMetersHtml(repoPercent, providerPercent, providerValue, providerLabel || mission.providerLabel || 'Detected evidence') +
6503
8579
 
6504
- groupHtml('Stack fixes needed', groups['missing-repo-fix'], 'missing') +
8580
+ body + '</section>';
6505
8581
 
6506
- groupHtml('MCP verifier', groups['mcp-verifier'], 'external') +
8582
+ }
6507
8583
 
6508
- groupHtml('Manual dashboard check', groups['manual-dashboard'], 'manual');
6509
8584
 
6510
- if (!body) return '';
6511
8585
 
6512
- return '<section class="studio-verification studio-wiring studio-mission-graph" aria-label="Mission evidence">' +
8586
+ function isLockedArea(areaKey) {
8587
+
8588
+ var nodes = document.querySelectorAll('.studio-node');
6513
8589
 
6514
- '<h3 class="studio-verification__title">' + esc(providerLabel || mission.providerLabel || 'Detected evidence') + '</h3>' +
8590
+ for (var i = 0; i < nodes.length; i += 1) {
6515
8591
 
6516
- readinessMetersHtml(mission.readinessPercent || 0, providerPercent, providerValue) +
8592
+ if (nodes[i].getAttribute('data-area-key') === areaKey) {
6517
8593
 
6518
- '<p class="studio-provider-readiness__note">Provider live moves after MCP verification or manual dashboard confirmation. Repo scans cannot prove live provider state.</p>' +
8594
+ return nodes[i].getAttribute('data-locked-map') === '1';
6519
8595
 
6520
- '<p class="studio-wiring__summary">' + esc(summary) + '</p>' + body + '</section>';
8596
+ }
8597
+
8598
+ }
8599
+
8600
+ return false;
6521
8601
 
6522
8602
  }
6523
8603
 
6524
8604
 
6525
8605
 
6526
- function render(areaKey, providerOverride) {
8606
+ function lockedAreaHtml(label) {
8607
+
8608
+ return '<div class="studio-setup-panel__inner studio-setup-panel__inner--locked">' +
8609
+
8610
+ '<div class="studio-setup-panel__head">' +
8611
+
8612
+ '<div class="studio-setup-panel__title"><strong>Upgrade to Pro</strong><span>Full mission map</span></div>' +
8613
+
8614
+ '</div>' +
8615
+
8616
+ '<section class="studio-setup-actions studio-setup-actions--locked" aria-label="Locked Pro section">' +
8617
+
8618
+ '<div class="studio-setup-actions__head">' +
8619
+
8620
+ '<span class="studio-setup-actions__logo provider-logo" aria-hidden="true">' + logoHtml('', label) + '</span>' +
8621
+
8622
+ '<div class="studio-setup-actions__title"><strong>' + esc(label) + ' is locked on the Free plan</strong><span>Pro-only production area</span></div>' +
8623
+
8624
+ '</div>' +
8625
+
8626
+ '<p class="studio-setup-actions__copy">Free shows App Flow, Frontend, Backend / API, Auth, Database, and Payments. Pro unlocks Deployment, Monitoring, Security, Testing, Landing, and Error Handling.</p>' +
8627
+
8628
+ '</section>' +
8629
+
8630
+ '</div>';
8631
+
8632
+ }
8633
+
8634
+
6527
8635
 
6528
- if (providerOverride) selectedProviders[areaKey] = providerOverride;
8636
+ function render(areaKey) {
6529
8637
 
6530
8638
  var area = areas.find(function (a) { return a.key === areaKey; });
6531
8639
 
@@ -6541,6 +8649,14 @@ var PANEL_CLIENT_SCRIPT = `
6541
8649
 
6542
8650
  var label = (area && area.label) || LABELS[areaKey] || areaKey;
6543
8651
 
8652
+ if (isLockedArea(areaKey)) {
8653
+
8654
+ panel.innerHTML = lockedAreaHtml(label);
8655
+
8656
+ return;
8657
+
8658
+ }
8659
+
6544
8660
  var missions = (area && area.providerMissions) || [];
6545
8661
 
6546
8662
  var selectedBeforeRender = selectedProviders[areaKey] || '';
@@ -6559,6 +8675,10 @@ var PANEL_CLIENT_SCRIPT = `
6559
8675
 
6560
8676
  var automation = automationFor(areaKey, mission, current);
6561
8677
 
8678
+ var categoryGaps = gaps.filter(function (gap) { return gap.primaryMapCategory === areaKey; });
8679
+
8680
+ var contract = buildCliSidebarContract(areaKey, label, mission, categoryGaps, providerLabel, automation, current);
8681
+
6562
8682
 
6563
8683
 
6564
8684
  panel.innerHTML =
@@ -6575,31 +8695,29 @@ var PANEL_CLIENT_SCRIPT = `
6575
8695
 
6576
8696
  addedSetupPathHtml(areaKey, current, providerLabel, missions) +
6577
8697
 
6578
- setupActionsHtml(areaKey, panelMissions, providerLabel, automation) +
8698
+ setupActionsHtml(areaKey, panelMissions, providerLabel, automation, current) +
6579
8699
 
6580
- setupReadinessHtml(areaKey, current, providerLabel, panelMissions, automation) +
8700
+ setupReadinessHtml(areaKey, current, providerLabel, panelMissions, automation) +
6581
8701
 
6582
- missionBlockHtml(panelMissions, providerLabel) +
6583
-
6584
- '</div>';
8702
+ selectedNodeContractHtml(contract) +
6585
8703
 
8704
+ '</div>';
6586
8705
 
6587
8706
 
6588
- panel.querySelectorAll('[data-copy-prompt]').forEach(function (btn) {
6589
8707
 
6590
- btn.addEventListener('click', function () {
8708
+ panel.querySelectorAll('[data-provider-option]').forEach(function (tile) {
6591
8709
 
6592
- var text = btn.getAttribute('data-copy-prompt') || '';
8710
+ tile.addEventListener('click', function () {
6593
8711
 
6594
- navigator.clipboard.writeText(text).then(function () {
8712
+ var providerOption = tile.getAttribute('data-provider-option') || '';
6595
8713
 
6596
- var label = btn.textContent;
8714
+ if (!providerOption) return;
6597
8715
 
6598
- btn.textContent = 'Copied';
8716
+ selectedProviders[areaKey] = providerOption;
6599
8717
 
6600
- setTimeout(function () { btn.textContent = label; }, 1200);
8718
+ syncMapNode(areaKey, providerOption);
6601
8719
 
6602
- });
8720
+ render(areaKey);
6603
8721
 
6604
8722
  });
6605
8723
 
@@ -6607,15 +8725,21 @@ var PANEL_CLIENT_SCRIPT = `
6607
8725
 
6608
8726
 
6609
8727
 
6610
- panel.querySelectorAll('[data-switch-provider]').forEach(function (btn) {
8728
+ panel.querySelectorAll('[data-copy-prompt]').forEach(function (btn) {
6611
8729
 
6612
8730
  btn.addEventListener('click', function () {
6613
8731
 
6614
- var a = btn.getAttribute('data-switch-area');
8732
+ var text = btn.getAttribute('data-copy-prompt') || '';
8733
+
8734
+ navigator.clipboard.writeText(text).then(function () {
8735
+
8736
+ var label = btn.textContent;
8737
+
8738
+ btn.textContent = 'Copied';
6615
8739
 
6616
- var p = btn.getAttribute('data-switch-provider');
8740
+ setTimeout(function () { btn.textContent = label; }, 1200);
6617
8741
 
6618
- render(a, p);
8742
+ });
6619
8743
 
6620
8744
  });
6621
8745
 
@@ -6670,46 +8794,79 @@ var PANEL_CLIENT_SCRIPT = `
6670
8794
  `;
6671
8795
 
6672
8796
  // src/report/providerLogos.ts
8797
+ var PROVIDER_LOGO_FALLBACK_SVG = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3 20 7.5v9L12 21 4 16.5v-9L12 3Zm0 3.3-5 2.8v5.8l5 2.8 5-2.8V9.1l-5-2.8Zm0 3.2a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z"/></svg>';
6673
8798
  var PROVIDER_LOGOS = {
6674
- supabase: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M13.8 2.4 5.6 13.1c-.5.7 0 1.7.9 1.7h4.7l-1 6.1c-.2 1 .9 1.6 1.5.8l8.1-10.8c.5-.7 0-1.7-.9-1.7h-4.7l1-6.1c.2-1-.8-1.6-1.4-.7Z"/></svg>',
6675
- clerk: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#6C47FF" d="M19.2 12c0 3.9-3.1 7.1-7.1 7.1-3.2 0-5.9-2.1-6.8-5h2.9c.7 1.8 2.5 3.1 4.5 3.1 2.7 0 4.9-2.2 4.9-4.9S13.2 7.4 10.5 7.4c-2 0-3.8 1.2-4.5 3H3.3c.9-2.9 3.6-5 6.8-5 4 0 7.1 3.2 7.1 7.1Z"/></svg>',
6676
- authjs: '<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" aria-hidden="true"><path fill="#412991" d="M12 2 4 6.5v5.2c0 4.6 3.4 8.9 8 10.3 4.6-1.4 8-5.7 8-10.3V6.5L12 2Z"/><path fill="#EB5424" d="M12 2v18.5c-1.2-.4-2.3-1-3.3-1.7L12 2Z"/><path fill="#FBC22C" d="M12 2 15.3 18.8c-1-.7-2.1-1.3-3.3-1.7V2Z"/></svg>',
6677
- auth0: '<svg viewBox="0 0 24 24" aria-hidden="true"><rect fill="#EB5424" x="3" y="3" width="18" height="18" rx="3.2"/><path fill="#ffffff" d="M12 7.4c2.5 0 4.6 2.1 4.6 4.6S14.5 16.6 12 16.6 7.4 14.5 7.4 12 9.5 7.4 12 7.4Zm0 2.2a2.4 2.4 0 1 0 0 4.8 2.4 2.4 0 0 0 0-4.8Z"/></svg>',
6678
- "better-auth": '<svg viewBox="0 0 24 24" aria-hidden="true"><rect fill="#171717" x="3" y="3" width="18" height="18" rx="4"/><path fill="#ffffff" d="M8 8h3v3H8V8Zm5 0h3v3h-3V8ZM8 13h3v3H8v-3Zm5 0h3v3h-3v-3Z"/></svg>',
8799
+ supabase: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#3fcf8e" d="M11.9 1.036c-.015-.986-1.26-1.41-1.874-.637L.764 12.05C-.33 13.427.65 15.455 2.409 15.455h9.579l.113 7.51c.014.985 1.259 1.408 1.873.636l9.262-11.653c1.093-1.375.113-3.403-1.645-3.403h-9.642z"/></svg>',
8800
+ clerk: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m21.47 20.829-2.881-2.881a.572.572 0 0 0-.7-.084 6.854 6.854 0 0 1-7.081 0 .576.576 0 0 0-.7.084l-2.881 2.881a.576.576 0 0 0-.103.69.57.57 0 0 0 .166.186 12 12 0 0 0 14.113 0 .58.58 0 0 0 .239-.423.576.576 0 0 0-.172-.453Zm.002-17.668-2.88 2.88a.569.569 0 0 1-.701.084A6.857 6.857 0 0 0 8.724 8.08a6.862 6.862 0 0 0-1.222 3.692 6.86 6.86 0 0 0 .978 3.764.573.573 0 0 1-.083.699l-2.881 2.88a.567.567 0 0 1-.864-.063A11.993 11.993 0 0 1 6.771 2.7a11.99 11.99 0 0 1 14.637-.405.566.566 0 0 1 .232.418.57.57 0 0 1-.168.448Zm-7.118 12.261a3.427 3.427 0 1 0 0-6.854 3.427 3.427 0 0 0 0 6.854Z"/></svg>',
8801
+ authjs: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#18c6cf" d="M12 1.4 21 4.9v6.7c0 5.1-3.5 8.7-9 11-5.5-2.3-9-5.9-9-11V4.9l9-3.5Z"/><path fill="#8b2ff5" d="M12 1.4 21 4.9v6.7c0 5.1-3.5 8.7-9 11V1.4Z"/><path fill="#fff7df" d="M12 7a5.2 5.2 0 1 0 0 10.4A5.2 5.2 0 0 0 12 7Z"/><path fill="#ff8a00" d="M10.2 9.6a2.2 2.2 0 0 1 2.9 2.9l3.5 3.5h-2.2v1.5h-1.8V16h-1.4l-1.3-1.3a2.2 2.2 0 0 1 .3-5.1Zm.9 1.8a.7.7 0 1 0 0 1.4.7.7 0 0 0 0-1.4Z"/></svg>',
8802
+ auth0: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#eb5424" d="M21.98 7.448L19.62 0H4.347L2.02 7.448c-1.352 4.312.03 9.206 3.815 12.015L12.007 24l6.157-4.552c3.755-2.81 5.182-7.688 3.815-12.015l-6.16 4.58 2.343 7.45-6.157-4.597-6.158 4.58 2.358-7.433-6.188-4.55 7.63-.045L12.008 0l2.356 7.404 7.615.044z"/></svg>',
8803
+ "better-auth": '<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="1.5" y="1.5" width="21" height="21" rx="3" fill="#111827"/><path fill="#fffdf7" d="M5.2 6.2h5.15v4.25H5.2V6.2Zm8.45 0h5.15v4.25h-5.15V6.2Zm-8.45 7.35h5.15v4.25H5.2v-4.25Zm8.45 0h5.15v4.25h-5.15v-4.25Z"/><path fill="#3b82f6" d="M10.35 10.45h3.3v3.1h-3.3v-3.1Z"/></svg>',
6679
8804
  polar: '<svg viewBox="0 0 29 29" aria-hidden="true"><path fill="#0062FF" fill-rule="evenodd" clip-rule="evenodd" d="M9.077 23.057c4.801 3.25 11.328 1.992 14.577-2.808 3.25-4.801 1.993-11.328-2.808-14.578C16.045 2.422 9.519 3.679 6.269 8.48c-3.25 4.801-1.993 11.327 2.808 14.577Zm1.393.086c4.392 2.247 9.963.138 12.444-4.711 2.48-4.848.93-10.6-3.461-12.847-4.392-2.247-9.963-.138-12.443 4.711-2.481 4.848-.932 10.6 3.46 12.847Z"/><path fill="#0062FF" fill-rule="evenodd" clip-rule="evenodd" d="M11.722 24.29c3.965 1.29 8.628-2.118 10.417-7.613 1.788-5.495.024-10.996-3.94-12.286-3.964-1.29-8.628 2.118-10.416 7.613-1.789 5.495-.025 10.995 3.939 12.286Zm1.213-.418c3.355.716 6.982-2.961 8.102-8.212 1.12-5.252-.691-10.089-4.046-10.804-3.356-.716-6.983 2.96-8.103 8.212-1.12 5.251.692 10.088 4.047 10.804Z"/><path fill="#0062FF" fill-rule="evenodd" clip-rule="evenodd" d="M13.854 24.738c2.652.284 5.3-4.14 5.912-9.882.613-5.74-1.04-10.624-3.692-10.907-2.653-.283-5.3 4.141-5.913 9.882-.613 5.741 1.04 10.624 3.693 10.907Zm1.241-1.747c1.92-.031 3.415-3.917 3.34-8.68-.075-4.764-1.693-8.6-3.612-8.57-1.92.03-3.415 3.916-3.34 8.68.076 4.763 1.693 8.6 3.612 8.57Z"/></svg>',
6680
- "lemon-squeezy": '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M12 3c-3.2 2.8-5 5.8-5 8.6a5 5 0 1 0 10 0c0-2.8-1.8-5.8-5-8.6Zm0 14.2a1.6 1.6 0 1 1 0-3.2 1.6 1.6 0 0 1 0 3.2Z"/></svg>',
6681
- github: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M12 2C6.48 2 2 6.58 2 12.26c0 4.52 2.87 8.35 6.86 9.71.5.1.69-.22.69-.49 0-.24-.01-.87-.01-1.7-2.78.62-3.37-1.36-3.37-1.36-.45-1.17-1.11-1.48-1.11-1.48-.91-.64.07-.63.07-.63 1 .07 1.53 1.05 1.53 1.05.9 1.56 2.36 1.11 2.94.85.09-.67.35-1.11.63-1.37-2.22-.26-4.56-1.14-4.56-5.07 0-1.12.39-2.03 1.03-2.75-.1-.26-.45-1.3.1-2.7 0 0 .84-.28 2.75 1.05A9.2 9.2 0 0 1 12 6.84c.85 0 1.71.12 2.51.34 1.91-1.33 2.75-1.05 2.75-1.05.55 1.4.2 2.44.1 2.7.64.72 1.03 1.63 1.03 2.75 0 3.94-2.34 4.8-4.57 5.06.36.32.68.94.68 1.9 0 1.37-.01 2.47-.01 2.8 0 .27.18.6.7.49A10.03 10.03 0 0 0 22 12.26C22 6.58 17.52 2 12 2Z"/></svg>',
6682
- gitlab: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#FC6D26" d="M23.2 9.4 12 2.2.8 9.4l1.4 8.2L12 21.8l9.8-4.2 1.4-8.2Z"/><path fill="#E24329" d="M12 2.2v7.1l3.8 1.9 1.8-5.5L12 2.2Z"/><path fill="#FC6D26" d="M12 10.2 7.7 12l1.8-5.5L12 2.2Z"/><path fill="#FCA326" d="M.8 9.4 12 21.8 7.7 12 12 10.2Z"/><path fill="#FC6D26" d="M12 10.2l4.3 1.8 6.1-2.6L12 2.2Z"/></svg>',
6683
- neon: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 4.5c0-1 .8-1.7 1.7-1.3l9.5 4.3c1.1.5 1.8 1.6 1.8 2.8v8.6c0 1-.8 1.7-1.7 1.3L6.8 15.9A3 3 0 0 1 5 13.1V4.5Zm4.1 4.1v5l4.9 2.2v-5L9.1 8.6Z"/></svg>',
6684
- planetscale: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M19.1 4.9A10 10 0 0 0 4.9 19.1L19.1 4.9Zm-12 16A10 10 0 0 0 20.9 7.1L7.1 20.9Z"/></svg>',
6685
- mongodb: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2c3 3 4.8 6.2 4.8 9.6 0 4.2-2.2 7.1-4.8 9.9-2.6-2.8-4.8-5.7-4.8-9.9C7.2 8.2 9 5 12 2Zm0 5.2v10.2"/></svg>',
6686
- turso: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7.5h16v3H4v-3Zm0 6h16v3H4v-3ZM7.5 4h9v3h-9V4Zm0 13h9v3h-9v-3Z"/></svg>',
8805
+ "lemon-squeezy": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m7.4916 10.835 2.3748-6.5114a3.1497 3.1497 0 0 0-.065-2.3418C9.0315.183 6.9427-.398 5.2928.265 3.643.929 2.71 2.4348 3.512 4.3046l2.8197 6.5615c.219.509.97.489 1.16-.03m1.6798 1.0969 6.5334-2.7758c2.1699-.9219 2.7218-3.6907 1.022-5.2905l-.068-.063c-1.6669-1.5469-4.4217-1.002-5.3706 1.0359L8.3566 11.135c-.234.503.295 1.0199.8159.7979m.373.87 6.6454-2.5119c2.2078-.8349 4.6206.745 4.5886 3.0398l-.002.09c-.048 2.2358-2.3938 3.7376-4.5536 2.9467l-6.6724-2.4418a.595.595 0 0 1-.006-1.1229m-.386 1.9269 6.4375 2.9767a3.2997 3.2997 0 0 1 1.6658 1.6989c.769 1.7998-.283 3.6396-1.9328 4.3016-1.6499.662-3.4097.235-4.2097-1.6359l-2.8027-6.5694c-.217-.509.328-1.009.8419-.772"/></svg>',
8806
+ github: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>',
8807
+ gitlab: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#FC6D26" d="m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z"/></svg>',
8808
+ neon: '<svg viewBox="0 0 64 64" aria-hidden="true"><path fill="#37c38f" d="M63 0.0177909V63.5526L38.4178 42.2501V63.5526H0V0L63 0.0177909ZM7.72251 55.8389H30.6953V25.3238L55.2779 47.0476V7.72922L7.72251 7.71559V55.8389Z"/></svg>',
8809
+ planetscale: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M0 12C0 5.373 5.373 0 12 0c4.873 0 9.067 2.904 10.947 7.077l-15.87 15.87a11.981 11.981 0 0 1-1.935-1.099L14.99 12H12l-8.485 8.485A11.962 11.962 0 0 1 0 12Zm12.004 12L24 12.004C23.998 18.628 18.628 23.998 12.004 24Z"/></svg>',
8810
+ mongodb: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M17.193 9.555c-1.264-5.58-4.252-7.414-4.573-8.115-.28-.394-.53-.954-.735-1.44-.036.495-.055.685-.523 1.184-.723.566-4.438 3.682-4.74 10.02-.282 5.912 4.27 9.435 4.888 9.884l.07.05A73.49 73.49 0 0 1 11.91 24h.481c.114-1.032.284-2.056.51-3.07.417-.296.604-.463.85-.693a11.342 11.342 0 0 0 3.639-8.464c.01-.814-.103-1.662-.197-2.218Zm-5.336 8.195s0-8.291.275-8.29c.213 0 .49 10.695.49 10.695-.381-.045-.765-1.76-.765-2.405Z"/></svg>',
8811
+ turso: '<svg viewBox="0 0 241 240" aria-hidden="true"><path fill="#4ff8d2" d="M220.035 83.61C215.365 55.67 190.875 35 190.875 35V65.78L176.335 69.53L167.225 58.56L162.415 68.02C152.495 65.32 138.835 63.58 120.045 63.58C101.255 63.58 87.5949 65.33 77.6749 68.02L72.8649 58.56L63.7549 69.53L49.2149 65.78V35C49.2149 35 24.7249 55.67 20.0549 83.61L52.1949 94.73C53.2449 114.16 61.9849 166.61 64.4849 171.37C67.1449 176.44 81.2649 190.93 92.3149 196.5C92.3149 196.5 96.3149 192.27 98.7549 188.54C101.855 192.19 117.865 204.99 120.055 204.99C122.245 204.99 138.255 192.2 141.355 188.54C143.795 192.27 147.795 196.5 147.795 196.5C158.845 190.93 172.965 176.44 175.625 171.37C178.125 166.61 186.865 114.16 187.915 94.73L220.055 83.61H220.035ZM173.845 128.35L152.095 130.29L154.005 156.96C154.005 156.96 140.775 167.91 120.045 167.91C99.3149 167.91 86.0849 156.96 86.0849 156.96L87.9949 130.29L66.2449 128.35L62.5249 98.31L98.5749 110.79L95.7749 148.18C102.475 149.88 109.525 151.57 120.055 151.57C130.585 151.57 137.625 149.88 144.325 148.18L141.525 110.79L177.575 98.31L173.855 128.35H173.845Z"/></svg>',
6687
8812
  stripe: '<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" aria-hidden="true"><path fill="#635BFF" d="M4.5 15.4c1.5.9 3.5 1.4 5.4 1.4 1.6 0 2.5-.4 2.5-1.2 0-.7-.7-1-3-1.5-3-.7-4.7-1.8-4.7-4.1 0-2.6 2.1-4.4 5.8-4.4 2.1 0 3.9.4 5.3 1.1v3.4a10 10 0 0 0-5.1-1.4c-1.5 0-2.2.4-2.2 1.1 0 .7.8 1 3 1.5 3.1.7 4.8 1.8 4.8 4.1 0 2.7-2.2 4.5-6.2 4.5-2.2 0-4.3-.5-5.6-1.3v-3.2Z"/></svg>',
6688
8813
  paddle: '<svg viewBox="0 0 90 90" aria-hidden="true"><rect x="11" y="11" width="68" height="68" rx="17" fill="#101318"/><rect x="11.5" y="11.5" width="67" height="67" rx="16.5" fill="none" stroke="#343942"/><path fill="#FFD21E" d="M8.49991 17C8.51217 21.6945 12.3128 25.5001 17 25.5001C12.3128 25.5001 8.51217 29.3055 8.49991 34C8.48783 29.3055 4.68717 25.5001 0 25.5001C4.68717 25.5001 8.48783 21.6945 8.49991 17Z" transform="translate(19.5 -31.5) scale(3)"/></svg>',
6689
8814
  vercel: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4 22 20H2L12 4Z"/></svg>',
6690
- netlify: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m12 2 3.1 5.4 6.2 1.2-4.2 4.7.8 6.3-5.9-2.6-5.9 2.6.8-6.3-4.2-4.7 6.2-1.2L12 2Z"/></svg>',
8815
+ netlify: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.49 19.04h-.23L5.13 17.9v-.23l1.73-1.71h1.2l.15.15v1.2L6.5 19.04ZM5.13 6.31V6.1l1.13-1.13h.23L8.2 6.68v1.2l-.15.15h-1.2L5.13 6.31Zm9.96 9.09h-1.65l-.14-.13v-3.83c0-.68-.27-1.2-1.1-1.23-.42 0-.9 0-1.43.02l-.07.08v4.96l-.14.14H8.9l-.13-.14V8.73l.13-.14h3.7a2.6 2.6 0 0 1 2.61 2.6v4.08l-.13.14Zm-8.37-2.44H.14L0 12.82v-1.64l.14-.14h6.58l.14.14v1.64l-.14.14Zm17.14 0h-6.58l-.14-.14v-1.64l.14-.14h6.58l.14.14v1.64l-.14.14ZM11.05 6.55V1.64l.14-.14h1.65l.14.14v4.9l-.14.14h-1.65l-.14-.13Zm0 15.81v-4.9l.14-.14h1.65l.14.13v4.91l-.14.14h-1.65l-.14-.14Z"/></svg>',
6691
8816
  aws: '<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" aria-hidden="true"><path fill="#FF9900" d="M6.763 10.582c4.95-2.283 10.563-2.283 15.475 0 .532.243.848.741.848 1.256 0 .532-.323 1.026-.85 1.256-4.912 2.283-10.525 2.283-15.474 0-.528-.23-.851-.724-.851-.279 0-.544.098-.754.243-4.95 2.283-10.563 2.283-15.475 0-.532-.243-.848-.741-.848-1.256 0-.532.323-1.026.85-1.256.228-.098.492-.196.754-.196.279 0 .544.098.754.243Z"/><path fill="#FF9900" d="M12 3.65c-2.772 0-5.027 1.147-5.027 2.553S9.228 8.756 12 8.756s5.027-1.147 5.027-2.553S14.772 3.65 12 3.65Z"/></svg>',
6692
- sentry: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3 22 20H2L12 3Zm0 5.1L6.8 17h2.1l3.1-5.3 3.1 5.3h2.1L12 8.1Z"/></svg>',
6693
- posthog: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#1D4AFF" d="M5 6h8.06c3.19 0 5.44 2.06 5.44 5.31S16.25 16.62 13.06 16.62H5V6Z"/><path fill="#F54E00" d="M4.13 3.94v4.5h3.94a2.25 2.25 0 0 0 0-4.5H4.13Z"/><circle cx="13.5" cy="11.81" r="1.01" fill="#F9BD2B"/><path fill="#F54E00" d="m16.39 9.49 1.88-1.13.79 1.2-2.03 1.28-.64-1.35Zm.34 4.61 2.33.83-.74 1.54-2.33-.98.74-1.39Z"/></svg>',
6694
- playwright: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#2EAD33" d="M8.4 8.2 5.4 17.2h2.3l.8-2.8h2.7l.8 2.8h2.3L11.6 8.2H8.4Zm1.1 4.8.8-2.4.8 2.4H9.5Z"/><path fill="#E2574C" d="M15.6 8.2 12.6 17.2h2.3l.8-2.8h2.7l.8 2.8h2.3L18.8 8.2h-3.2Zm1.1 4.8.8-2.4.8 2.4h-1.6Z"/><ellipse fill="#2EAD33" cx="9.5" cy="13.1" rx="1.1" ry="1.4"/><ellipse fill="#E2574C" cx="16.5" cy="13.1" rx="1.1" ry="1.4"/></svg>',
8817
+ sentry: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z"/></svg>',
8818
+ posthog: '<svg viewBox="0 0 156 90" aria-hidden="true"><path fill="#f9bd2b" d="M0 68.33c0-2.9 3.505-4.352 5.556-2.302l14.916 14.917c2.05 2.05.598 5.555-2.301 5.555H3.254A3.254 3.254 0 0 1 0 83.246zm0-15.712c0 .863.343 1.69.953 2.301l30.628 30.628c.61.61 1.438.953 2.301.953h16.823c2.9 0 4.351-3.505 2.301-5.555L5.556 33.494C3.506 31.444 0 32.896 0 35.795zm0-32.534c0 .863.343 1.69.953 2.3l63.162 63.163c.61.61 1.439.953 2.302.953h16.822c2.9 0 4.352-3.505 2.302-5.555L5.556.96C3.506-1.09 0 .362 0 3.26zm32.534 0c0 .863.343 1.69.954 2.3l58.56 58.56c2.05 2.05 5.555.599 5.555-2.3V61.82c0-.863-.343-1.691-.953-2.301L38.09.96c-2.05-2.05-5.556-.598-5.556 2.3zM70.624.96c-2.05-2.05-5.555-.598-5.555 2.3v16.824c0 .863.343 1.69.953 2.3L92.047 48.41c2.05 2.05 5.556.598 5.556-2.3V29.285c0-.863-.343-1.69-.953-2.3z"/><path fill="#000000" d="M138.393 68.729 107.76 38.095c-2.05-2.05-5.555-.598-5.555 2.302v42.849a3.254 3.254 0 0 0 3.254 3.254h47.451a3.254 3.254 0 0 0 3.255-3.254v-3.903c0-1.797-1.463-3.232-3.246-3.464a25.14 25.14 0 0 1-14.526-7.15m-20.572 7.36a5.207 5.207 0 0 1-5.205-5.205 5.207 5.207 0 0 1 5.205-5.206 5.21 5.21 0 0 1 5.206 5.206 5.207 5.207 0 0 1-5.206 5.205"/><path fill="#1d4aff" d="m5.28 65.78.276.248 14.916 14.916c1.96 1.961.718 5.254-1.932 5.536l-.37.02H3.255a3.255 3.255 0 0 1-3.232-2.875L0 83.245V68.33c0-2.773 3.207-4.222 5.28-2.549m0-32.533.276.247 26.978 26.98V86.5L.954 54.919a3.26 3.26 0 0 1-.926-1.873L0 52.618V35.796c0-2.774 3.207-4.223 5.28-2.55M0 3.26C0 .488 3.207-.961 5.28.712l.276.248 26.978 26.978v26.028L.954 22.385a3.25 3.25 0 0 1-.926-1.874L0 20.084z"/><path fill="#f54e00" d="m32.534 27.939 31.581 31.58c.51.51.832 1.17.925 1.874l.029.428v24.68L33.488 54.919a3.26 3.26 0 0 1-.925-1.873l-.029-.428zm0 32.533 20.472 20.472c1.961 1.961.718 5.254-1.931 5.536l-.37.02h-18.17zm5.28-59.76.276.248 26.025 26.025c.51.509.832 1.168.925 1.874l.029.427v24.68L33.488 22.385a3.26 3.26 0 0 1-.925-1.874l-.029-.427V3.26c0-2.773 3.208-4.222 5.28-2.549"/></svg>',
8819
+ playwright: '<svg viewBox="0 0 400 400" aria-hidden="true"><path fill="#2d4552" d="M136.444 221.556C123.558 225.213 115.104 231.625 109.535 238.032C114.869 233.364 122.014 229.08 131.652 226.348C141.51 223.554 149.92 223.574 156.869 224.915V219.481C150.941 218.939 144.145 219.371 136.444 221.556ZM108.946 175.876L61.0895 188.484C61.0895 188.484 61.9617 189.716 63.5767 191.36L104.153 180.668C104.153 180.668 103.578 188.077 98.5847 194.705C108.03 187.559 108.946 175.876 108.946 175.876ZM149.005 288.347C81.6582 306.486 46.0272 228.438 35.2396 187.928C30.2556 169.229 28.0799 155.067 27.5 145.928C27.4377 144.979 27.4665 144.179 27.5336 143.446C24.04 143.657 22.3674 145.473 22.7077 150.721C23.2876 159.855 25.4633 174.016 30.4473 192.721C41.2301 233.225 76.8659 311.273 144.213 293.134C158.872 289.185 169.885 281.992 178.152 272.81C170.532 279.692 160.995 285.112 149.005 288.347ZM161.661 128.11V132.903H188.077C187.535 131.206 186.989 129.677 186.447 128.11H161.661Z"/><path fill="#2d4552" d="M193.981 167.584C205.861 170.958 212.144 179.287 215.465 186.658L228.711 190.42C228.711 190.42 226.904 164.623 203.57 157.995C181.741 151.793 168.308 170.124 166.674 172.496C173.024 167.972 182.297 164.268 193.981 167.584ZM299.422 186.777C277.573 180.547 264.145 198.916 262.535 201.255C268.89 196.736 278.158 193.031 289.837 196.362C301.698 199.741 307.976 208.06 311.307 215.436L324.572 219.212C324.572 219.212 322.736 193.41 299.422 186.777ZM286.262 254.795L176.072 223.99C176.072 223.99 177.265 230.038 181.842 237.869L274.617 263.805C282.255 259.386 286.262 254.795 286.262 254.795ZM209.867 321.102C122.618 297.71 133.166 186.543 147.284 133.865C153.097 112.156 159.073 96.0203 164.029 85.204C161.072 84.5953 158.623 86.1529 156.203 91.0746C150.941 101.747 144.212 119.124 137.7 143.45C123.586 196.127 113.038 307.29 200.283 330.682C241.406 341.699 273.442 324.955 297.323 298.659C274.655 319.19 245.714 330.701 209.867 321.102Z"/><path fill="#e2574c" d="M161.661 262.296V239.863L99.3324 257.537C99.3324 257.537 103.938 230.777 136.444 221.556C146.302 218.762 154.713 218.781 161.661 220.123V128.11H192.869C189.471 117.61 186.184 109.526 183.423 103.909C178.856 94.612 174.174 100.775 163.545 109.665C156.059 115.919 137.139 129.261 108.668 136.933C80.1966 144.61 57.179 142.574 47.5752 140.911C33.9601 138.562 26.8387 135.572 27.5049 145.928C28.0847 155.062 30.2605 169.224 35.2445 187.928C46.0272 228.433 81.663 306.481 149.01 288.342C166.602 283.602 179.019 274.233 187.626 262.291H161.661V262.296ZM61.0848 188.484L108.946 175.876C108.946 175.876 107.551 194.288 89.6087 199.018C71.6614 203.743 61.0848 188.484 61.0848 188.484Z"/><path fill="#2ead33" d="M341.786 129.174C329.345 131.355 299.498 134.072 262.612 124.185C225.716 114.304 201.236 97.0224 191.537 88.8994C177.788 77.3834 171.74 69.3802 165.788 81.4857C160.526 92.163 153.797 109.54 147.284 133.866C133.171 186.543 122.623 297.706 209.867 321.098C297.093 344.47 343.53 242.92 357.644 190.238C364.157 165.917 367.013 147.5 367.799 135.625C368.695 122.173 359.455 126.078 341.786 129.174ZM166.497 172.756C166.497 172.756 180.246 151.372 203.565 158C226.899 164.628 228.706 190.425 228.706 190.425L166.497 172.756ZM223.42 268.713C182.403 256.698 176.077 223.99 176.077 223.99L286.262 254.796C286.262 254.791 264.021 280.578 223.42 268.713ZM262.377 201.495C262.377 201.495 276.107 180.126 299.422 186.773C322.736 193.411 324.572 219.208 324.572 219.208L262.377 201.495Z"/><path fill="#d65348" d="M139.88 246.04L99.3324 257.532C99.3324 257.532 103.737 232.44 133.607 222.496L110.647 136.33L108.663 136.933C80.1918 144.611 57.1742 142.574 47.5704 140.911C33.9554 138.563 26.834 135.572 27.5001 145.929C28.08 155.063 30.2557 169.224 35.2397 187.929C46.0225 228.433 81.6583 306.481 149.005 288.342L150.989 287.719L139.88 246.04ZM61.0848 188.485L108.946 175.876C108.946 175.876 107.551 194.288 89.6087 199.018C71.6615 203.743 61.0848 188.485 61.0848 188.485Z"/><path fill="#1d8d22" d="M225.27 269.163L223.415 268.712C182.398 256.698 176.072 223.99 176.072 223.99L232.89 239.872L262.971 124.281L262.607 124.185C225.711 114.304 201.232 97.0224 191.532 88.8994C177.783 77.3834 171.735 69.3802 165.783 81.4857C160.526 92.163 153.797 109.54 147.284 133.866C133.171 186.543 122.623 297.706 209.867 321.097L211.655 321.5L225.27 269.163ZM166.497 172.756C166.497 172.756 180.246 151.372 203.565 158C226.899 164.628 228.706 190.425 228.706 190.425L166.497 172.756Z"/></svg>',
6695
8820
  logrocket: '<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" aria-hidden="true"><rect x="2" y="2" width="20" height="20" rx="5" fill="#764ABC"/><path fill="#FFFFFF" d="M12 7.4c2.7 0 4.9 2.2 4.9 4.9s-2.2 4.9-4.9 4.9-4.9-2.2-4.9-4.9 4.9-4.9 4.9-4.9Zm0 1.9a3 3 0 1 0 0 6 3 3 0 0 0 0-6Zm-3.2 7.4h6.4v1.5H8.8v-1.5Z"/></svg>',
6696
8821
  figma: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8.5 3h3.5v6H8.5a3 3 0 1 1 0-6Zm3.5 6h3.5a3 3 0 1 0 0-6H12v6Zm0 0h3.5a3 3 0 1 1 0 6H12V9Zm-3.5 0H12v6H8.5a3 3 0 1 1 0-6ZM8.5 15H12v2.5A3.5 3.5 0 1 1 8.5 15Z"/></svg>',
6697
8822
  storybook: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.2 3.4 17.9 2l.7 18.1-12.4.7V3.4Zm8.7 3.2.3 2.4 1.7-1.3 1.7 1.1.3-3.4-4 .5v.7Zm-5.2 5.1c0 2 1.6 3.5 4.2 3.5 2.4 0 3.8-1.2 3.8-3 0-1.7-1.1-2.6-3.4-3l-1.2-.2c-.7-.1-1-.4-1-.8 0-.5.5-.8 1.3-.8.9 0 1.5.4 1.8 1.1l2.1-.8c-.5-1.4-1.8-2.2-3.8-2.2-2.3 0-3.8 1.2-3.8 2.9 0 1.6 1.1 2.5 3.3 2.9l1.2.2c.8.1 1.1.4 1.1.9 0 .6-.5.9-1.4.9-1.1 0-1.8-.5-2.1-1.3l-2.1.7Z"/></svg>',
6698
8823
  "product-spec": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 3.2h9.2L19 7v13.8H6V3.2Zm8.2 1.9v3h3l-3-3ZM8.4 10h7.2v1.7H8.4V10Zm0 3.2h7.2v1.7H8.4v-1.7Zm0 3.2h4.9v1.7H8.4v-1.7Z"/></svg>',
6699
8824
  "route-map": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.5 4.2a3 3 0 0 1 3 3c0 2-3 5.1-3 5.1s-3-3.1-3-5.1a3 3 0 0 1 3-3Zm0 1.8a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4Zm11 5.8a3 3 0 0 1 3 3c0 2-3 5.1-3 5.1s-3-3.1-3-5.1a3 3 0 0 1 3-3Zm0 1.8a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4ZM9.5 7h2.1c2.8 0 4.9 2 4.9 4.8h-2c0-1.7-1.2-2.8-2.9-2.8H9.5V7Zm4.9 10h-2.1c-2.8 0-4.9-2-4.9-4.8h2c0 1.7 1.2 2.8 2.9 2.8h2.1v2Z"/></svg>',
6700
8825
  react: '<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="2.2"/><ellipse cx="12" cy="12" rx="9" ry="3.6" fill="none" stroke="currentColor" stroke-width="1.6"/><ellipse cx="12" cy="12" rx="9" ry="3.6" fill="none" stroke="currentColor" stroke-width="1.6" transform="rotate(60 12 12)"/><ellipse cx="12" cy="12" rx="9" ry="3.6" fill="none" stroke="currentColor" stroke-width="1.6" transform="rotate(120 12 12)"/></svg>',
6701
- vue: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.6 4h5.1L12 11.4 16.3 4h5.1L12 20 2.6 4Zm5.6 0L12 10.5 15.8 4h-2.7L12 5.9 10.9 4H8.2Z"/></svg>',
6702
- svelte: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M16.9 3.4a5.2 5.2 0 0 0-6.9 1.5L6.3 9.7a4.6 4.6 0 0 0 .8 6.4 5.1 5.1 0 0 0 6.9-1.2l.6-.8a1.6 1.6 0 0 0-.3-2.2 1.8 1.8 0 0 0-2.4.4l-.6.8a1.3 1.3 0 0 1-1.8.3 1.2 1.2 0 0 1-.2-1.6L13 7a1.3 1.3 0 0 1 1.8-.4c.5.4.7 1.1.3 1.6l-.3.4 2.8 2 .3-.4a4.7 4.7 0 0 0-1-6.8Zm-9.8 17.2a5.2 5.2 0 0 0 6.9-1.5l3.7-4.8a4.6 4.6 0 0 0-.8-6.4A5.1 5.1 0 0 0 10 9.1l-.6.8a1.6 1.6 0 0 0 .3 2.2 1.8 1.8 0 0 0 2.4-.4l.6-.8a1.3 1.3 0 0 1 1.8-.3 1.2 1.2 0 0 1 .2 1.6L11 17a1.3 1.3 0 0 1-1.8.4 1.2 1.2 0 0 1-.3-1.6l.3-.4-2.8-2-.3.4a4.7 4.7 0 0 0 1 6.8Z"/></svg>',
6703
- angular: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2.5 20.5 5.6 19.2 17 12 21.5 4.8 17 3.5 5.6 12 2.5Zm0 4.2-5 11h2.4l1-2.4h3.2l1 2.4H17l-5-11Zm0 3.8 1.1 2.9h-2.2l1.1-2.9Z"/></svg>',
6704
- nodejs: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2.5 20.2 7v10L12 21.5 3.8 17V7L12 2.5Zm-3.8 6.7v5.6h1.9v-3.1l3.8 3.1h1.9V9.2h-1.9v3.2l-3.8-3.2H8.2Z"/></svg>',
8826
+ vue: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#42b883" d="M24 1.61H14.06L12 5.16 9.94 1.61H0l12 20.78L24 1.61Z"/><path fill="#35495e" d="M12 14.08 5.16 2.23h4.43L12 6.41l2.41-4.18h4.43L12 14.08Z"/></svg>',
8827
+ svelte: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#ff3e00" d="M10.354 21.125a4.44 4.44 0 0 1-4.765-1.767 4.109 4.109 0 0 1-.703-3.107 3.898 3.898 0 0 1 .134-.522l.105-.321.287.21a7.21 7.21 0 0 0 2.187 1.099l.208.063-.02.208a1.173 1.173 0 0 0 .21.766 1.297 1.297 0 0 0 1.39.514 1.22 1.22 0 0 0 .54-.31l3.927-3.986a.927.927 0 0 0 .203-.42.908.908 0 0 0-.154-.7 1.01 1.01 0 0 0-1.08-.4.946.946 0 0 0-.42.242l-1.5 1.522a4.15 4.15 0 0 1-1.831 1.045 4.453 4.453 0 0 1-4.767-1.767 4.108 4.108 0 0 1-.702-3.107 3.855 3.855 0 0 1 1.066-2.018l3.927-3.986A4.205 4.205 0 0 1 10.65 3.34a4.442 4.442 0 0 1 4.766 1.767 4.109 4.109 0 0 1 .702 3.108 3.943 3.943 0 0 1-.133.521l-.106.321-.286-.21a7.206 7.206 0 0 0-2.188-1.098l-.208-.063.02-.208a1.18 1.18 0 0 0-.21-.767 1.297 1.297 0 0 0-1.39-.514 1.229 1.229 0 0 0-.54.31L7.15 10.493a.929.929 0 0 0-.203.42.909.909 0 0 0 .154.7 1.01 1.01 0 0 0 1.08.4.943.943 0 0 0 .42-.242l1.5-1.522a4.144 4.144 0 0 1 1.831-1.045 4.443 4.443 0 0 1 4.766 1.767 4.109 4.109 0 0 1 .702 3.107 3.857 3.857 0 0 1-1.066 2.018l-3.927 3.986a4.193 4.193 0 0 1-2.053 1.043Z"/></svg>',
8828
+ angular: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#dd0031" d="M12 2 3.5 5.1l1.3 11.2L12 22l7.2-5.7 1.3-11.2L12 2Z"/><path fill="#c3002f" d="M12 2v20l7.2-5.7 1.3-11.2L12 2Z"/><path fill="#fff" d="M12 5.6 6.7 17.6h2l1.1-2.7h4.4l1.1 2.7h2L12 5.6Zm0 3.8 1.5 3.7h-3L12 9.4Z"/></svg>',
8829
+ nodejs: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#5fa04e" d="M12 2.15 20.7 7.1v9.8L12 21.85 3.3 16.9V7.1L12 2.15Z"/><path fill="#ffffff" d="M8.1 9.15h2.15v4.55c0 1.35-.75 2.15-2.2 2.15-.78 0-1.37-.18-1.85-.48l.57-1.67c.28.17.6.28.95.28.27 0 .38-.15.38-.48V9.15Zm4.05 4.05c.42.5 1.02.82 1.72.82.5 0 .82-.18.82-.52 0-.38-.32-.48-1.08-.7l-.57-.17c-1.15-.35-1.88-.92-1.88-1.95 0-1.13.92-1.72 2.28-1.72 1.02 0 1.82.32 2.4.98l-1.1 1.28c-.35-.35-.78-.55-1.25-.55-.4 0-.65.15-.65.45 0 .32.28.43.93.63l.57.17c1.32.4 2.05.93 2.05 2.05 0 1.25-1.05 1.88-2.52 1.88-1.32 0-2.35-.5-2.95-1.28l1.23-1.37Z"/></svg>',
6705
8830
  python: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M11.8 3c2.7 0 4.2.8 4.2 2.5V9H9.6A2.6 2.6 0 0 0 7 11.6V13H4.5C3.5 13 3 12.2 3 11c0-3.2 1.9-4.9 5.6-4.9h3.8V5H8.8V3.6A10 10 0 0 1 11.8 3Zm-1.4 1.5a.8.8 0 1 0 0 1.6.8.8 0 0 0 0-1.6ZM12.2 21c-2.7 0-4.2-.8-4.2-2.5V15h6.4a2.6 2.6 0 0 0 2.6-2.6V11h2.5c1 0 1.5.8 1.5 2 0 3.2-1.9 4.9-5.6 4.9h-3.8V19h3.6v1.4a10 10 0 0 1-3 .6Zm1.4-3.1a.8.8 0 1 0 0 1.6.8.8 0 0 0 0-1.6Z"/></svg>',
6706
8831
  rails: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 17.7C5.8 9.5 11.7 5.2 21 4.8v3.1C13.5 8.3 8.7 11.6 6 18.5L3 17.7Zm4.6.7c2.1-5 5.9-7.5 11.4-7.9v2.4c-4.4.4-7.4 2.5-9 6.2l-2.4-.7Zm4.7.7c1.3-2.4 3.4-3.7 6.7-4.1v2.2c-2.2.4-3.6 1.3-4.5 2.6l-2.2-.7Z"/></svg>',
6707
8832
  go: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 8.2h7.4v1.5H3V8.2Zm-1 3h7.4v1.5H2v-1.5Zm2 3h5.4v1.5H4v-1.5Zm10.5-6.1c3.1 0 5.5 2 5.5 4.6s-2.4 4.6-5.5 4.6-5.5-2-5.5-4.6 2.4-4.6 5.5-4.6Zm0 2.1c-1.7 0-3 1.1-3 2.5s1.3 2.5 3 2.5 3-1.1 3-2.5-1.3-2.5-3-2.5Zm5.1-2h2.4l-1.2 9h-2.4l1.2-9Z"/></svg>',
6708
8833
  "testing-library": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 3h10v4.2l-3.4 4.5 5.4 7.9c.5.8 0 1.9-1 1.9H6c-1 0-1.6-1.1-1-1.9l5.4-7.9L7 7.2V3Zm3 3.5 2 2.7 2-2.7H10Zm1.9 8.4-2.6 3.8h5.4l-2.8-3.8Z"/></svg>',
6709
- vitest: '<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" aria-hidden="true"><path fill="#FCC72B" d="M13.4 3 21 7.4 12 21 3 7.4 10.6 3l1.4 4.5L13.4 3Zm-1.4 7.3-2.1 4h4.2l-2.1-4Z"/><path fill="#729B1B" d="M12 10.3 9.9 14.3h4.2L12 10.3Z"/></svg>',
8834
+ vitest: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M13.74024 1.05293a.49504.49504 0 0 0-.1569.02512.49338.49338 0 0 0-.25056.1876L7.59513 9.56159a.4895.4895 0 0 0-.08373.22327.48846.48846 0 0 0 .03163.23629.4893.4893 0 0 0 .13985.19319.4927.4927 0 0 0 .2149.10481l3.70685.78609-.22947 4.58007a.48834.48834 0 0 0 .08466.30017.49205.49205 0 0 0 .24931.18854c.10157.03398.21174.03444.3135.00064a.49387.49387 0 0 0 .25056-.18761l5.73735-8.29594a.4884.4884 0 0 0 .08404-.22327c.009-.08015-.0016-.16137-.03163-.23629a.48835.48835 0 0 0-.13985-.19319.49318.49318 0 0 0-.2149-.1048l-3.70686-.7861.22947-4.58008a.48802.48802 0 0 0-.08466-.30017.4913.4913 0 0 0-.24931-.18853.49439.49439 0 0 0-.1566-.02574zM1.15697 9.78795c-.30647.0012-.60009.12378-.81679.34048a1.16107 1.16107 0 0 0-.34017.81648 1.162 1.162 0 0 0 .33366.81957l10.84241 10.8421a1.15762 1.15762 0 0 0 .37677.25211 1.1583 1.1583 0 0 0 .44467.08838c.00084 0 .0016-.00031.0025-.00031.00073 0 .0014.00031.0022.00031a1.15827 1.15827 0 0 0 .44467-.08838 1.15731 1.15731 0 0 0 .37677-.2521l10.84236-10.8421a1.16272 1.16272 0 0 0 .33397-.81958c-.0013-.30647-.12376-.59976-.34048-.81648a1.1616 1.1616 0 0 0-.81679-.34048 1.16114 1.16114 0 0 0-.81926.33366l-5.4012 5.4009c-.0078.0074-.01718.01255-.02482.02015L12 20.14011l-4.59776-4.59745c-.0074-.0074-.01659-.01238-.02419-.01954l-5.4015-5.40151a1.162 1.162 0 0 0-.81958-.33366Z"/></svg>',
6710
8835
  "rate-limit": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3a9 9 0 1 0 9 9h-3a6 6 0 1 1-1.8-4.3L13 11h8V3l-2.7 2.7A9 9 0 0 0 12 3Z"/></svg>',
6711
- "bot-protection": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2 20 6v6c0 5-3.5 8.6-8 10-4.5-1.4-8-5-8-10V6l8-4Zm-3 8h6v4H9v-4Zm1.5-3.2h3V10h-3V6.8Z"/></svg>',
6712
- "secrets-hygiene": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 10V7a5 5 0 0 1 10 0v3h1.5v11h-13V10H7Zm3 0h4V7a2 2 0 0 0-4 0v3Z"/></svg>'
8836
+ "bot-protection": '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#f38020" d="M12 2.4 20 5.5v6.2c0 4.75-3.12 8.1-8 10.1-4.88-2-8-5.35-8-10.1V5.5l8-3.1Z"/><path fill="#fff7df" d="M12 5.9 16.9 8v3.72c0 2.64-1.8 4.64-4.9 6.05-3.1-1.41-4.9-3.41-4.9-6.05V8L12 5.9Z"/><path fill="#f38020" d="M12 8.2a3.15 3.15 0 0 1 3.15 3.15A3.15 3.15 0 0 1 12 14.5a3.15 3.15 0 0 1-3.15-3.15A3.15 3.15 0 0 1 12 8.2Zm0 1.65a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"/></svg>',
8837
+ "secrets-hygiene": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 10V7a5 5 0 0 1 10 0v3h1.5v11h-13V10H7Zm3 0h4V7a2 2 0 0 0-4 0v3Z"/></svg>',
8838
+ firebase: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#ffa000" d="m3.9 18.7 2.4-15.2c.1-.6.9-.8 1.2-.3l2.6 4.9 1.1-2.1c.2-.4.8-.4 1 0l7.9 12.7-8.1 4.5-8.1-4.5Z"/><path fill="#f57c00" d="m12 23.2 8.1-4.5-2.4-14.8c-.1-.6-.9-.8-1.2-.3L12 12.1v11.1Z"/><path fill="#ffca28" d="m3.9 18.7 6.2-10.6 1.9 3.7-8.1 6.9Z"/><path fill="#fff3c4" d="m12 12.1 4.5-8.5c.3-.5 1.1-.3 1.2.3l2.4 14.8-8.1-6.6Z"/></svg>',
8839
+ cloudflare: '<svg viewBox="0 0 32 32" aria-hidden="true"><rect x="7" y="7" width="21" height="21" rx="1.4" fill="#111827"/><rect x="4" y="4" width="21" height="21" rx="1.4" fill="#f38020"/><path fill="#fff7df" d="M11.1 17.6h9.2c1.4 0 2.5-1 2.5-2.2 0-1.1-.9-2-2.2-2.2-.6-2-2.4-3.4-4.6-3.4-1.9 0-3.6 1.1-4.3 2.7-.2 0-.4-.1-.7-.1-1.7 0-3.1 1.2-3.4 2.7-1.1.2-1.9 1-1.9 1.9 0 .9.8 1.6 1.8 1.6h3.6Z"/><path fill="#f38020" d="M18.1 13.3c1.2.4 2.1 1.4 2.4 2.6h.2c.6 0 1.1-.4 1.1-.9 0-.5-.5-.9-1.1-1h-.7l-.2-.7c-.4-1.5-1.8-2.5-3.5-2.5-.7 0-1.3.2-1.9.5 1.7.2 3 .9 3.7 2Z"/></svg>'
8840
+ };
8841
+ var PROVIDER_NODE_TONES = {
8842
+ supabase: ["#3ecf8e", "rgba(62, 207, 142, 0.34)"],
8843
+ clerk: ["#6c47ff", "rgba(108, 71, 255, 0.34)"],
8844
+ authjs: ["#6c47ff", "rgba(108, 71, 255, 0.3)"],
8845
+ auth0: ["#eb5424", "rgba(235, 84, 36, 0.34)"],
8846
+ "better-auth": ["#111827", "rgba(17, 24, 39, 0.22)"],
8847
+ firebase: ["#ffa000", "rgba(255, 160, 0, 0.34)"],
8848
+ neon: ["#00e599", "rgba(0, 229, 153, 0.34)"],
8849
+ planetscale: ["#111827", "rgba(17, 24, 39, 0.22)"],
8850
+ mongodb: ["#47a248", "rgba(71, 162, 72, 0.34)"],
8851
+ turso: ["#4ff8d2", "rgba(79, 248, 210, 0.3)"],
8852
+ stripe: ["#635bff", "rgba(99, 91, 255, 0.34)"],
8853
+ paddle: ["#fddd35", "rgba(253, 221, 53, 0.34)"],
8854
+ polar: ["#1d4aff", "rgba(29, 74, 255, 0.3)"],
8855
+ "lemon-squeezy": ["#ffc233", "rgba(255, 194, 51, 0.34)"],
8856
+ vercel: ["#111827", "rgba(17, 24, 39, 0.22)"],
8857
+ netlify: ["#00c7b7", "rgba(0, 199, 183, 0.32)"],
8858
+ cloudflare: ["#f38020", "rgba(243, 128, 32, 0.34)"],
8859
+ sentry: ["#a855f7", "rgba(168, 85, 247, 0.34)"],
8860
+ posthog: ["#f54e00", "rgba(245, 78, 0, 0.34)"],
8861
+ playwright: ["#45ba4b", "rgba(69, 186, 75, 0.32)"],
8862
+ vitest: ["#fcc72b", "rgba(252, 199, 43, 0.34)"],
8863
+ figma: ["#ff7262", "rgba(255, 114, 98, 0.38)"],
8864
+ react: ["#61dafb", "rgba(97, 218, 251, 0.38)"],
8865
+ vue: ["#42b883", "rgba(66, 184, 131, 0.34)"],
8866
+ angular: ["#dd0031", "rgba(221, 0, 49, 0.34)"],
8867
+ nodejs: ["#83cd29", "rgba(131, 205, 41, 0.34)"],
8868
+ github: ["#111827", "rgba(17, 24, 39, 0.22)"],
8869
+ gitlab: ["#fc6d26", "rgba(252, 109, 38, 0.34)"]
6713
8870
  };
6714
8871
  var ALIASES = {
6715
8872
  authjs: "authjs",
@@ -6742,6 +8899,8 @@ var ALIASES = {
6742
8899
  auth0: "auth0",
6743
8900
  "better-auth": "better-auth",
6744
8901
  betterauth: "better-auth",
8902
+ firebase: "firebase",
8903
+ firestore: "firebase",
6745
8904
  "mongodb atlas": "mongodb",
6746
8905
  logrocket: "logrocket",
6747
8906
  sentry: "sentry",
@@ -6777,7 +8936,23 @@ var ALIASES = {
6777
8936
  routes: "route-map",
6778
8937
  ratelimit: "rate-limit"
6779
8938
  };
6780
- var INLINE_ONLY_LOGO_KEYS = /* @__PURE__ */ new Set(["better-auth", "paddle", "polar"]);
8939
+ var INLINE_ONLY_LOGO_KEYS = /* @__PURE__ */ new Set([
8940
+ "auth0",
8941
+ "authjs",
8942
+ "better-auth",
8943
+ "clerk",
8944
+ "cloudflare",
8945
+ "firebase",
8946
+ "lemon-squeezy",
8947
+ "netlify",
8948
+ "paddle",
8949
+ "playwright",
8950
+ "polar",
8951
+ "posthog",
8952
+ "sentry",
8953
+ "svelte",
8954
+ "vitest"
8955
+ ]);
6781
8956
  var PROVIDER_ASSET_FILES = {
6782
8957
  authjs: "provider-authjs.svg",
6783
8958
  logrocket: "provider-logrocket.svg",
@@ -6797,6 +8972,7 @@ var PROVIDER_ICON_URLS = {
6797
8972
  supabase: "https://cdn.simpleicons.org/supabase/3ECF8E",
6798
8973
  clerk: "https://cdn.simpleicons.org/clerk/6C47FF",
6799
8974
  auth0: "https://cdn.simpleicons.org/auth0/EB5424",
8975
+ firebase: "https://cdn.simpleicons.org/firebase/FFCA28",
6800
8976
  neon: "https://cdn.simpleicons.org/neon/00E599",
6801
8977
  planetscale: "https://cdn.simpleicons.org/planetscale/000000",
6802
8978
  mongodb: "https://cdn.simpleicons.org/mongodb/47A248",
@@ -6833,16 +9009,26 @@ var BRAND_LOGO_KEYS = /* @__PURE__ */ new Set([
6833
9009
  "better-auth",
6834
9010
  "clerk",
6835
9011
  "gitlab",
9012
+ "lemon-squeezy",
6836
9013
  "logrocket",
9014
+ "netlify",
6837
9015
  "auth0",
6838
9016
  "paddle",
6839
9017
  "playwright",
6840
9018
  "polar",
6841
9019
  "posthog",
9020
+ "sentry",
6842
9021
  "stripe",
9022
+ "svelte",
6843
9023
  "vitest"
6844
9024
  ]);
9025
+ function isUnselectedProviderToken(raw, compact) {
9026
+ return !compact || compact === "notselected" || raw === "not selected" || raw === "none";
9027
+ }
6845
9028
  function resolveLogoKey(raw, compact) {
9029
+ if (isUnselectedProviderToken(raw, compact)) {
9030
+ return "";
9031
+ }
6846
9032
  if (ALIASES[raw]) {
6847
9033
  return ALIASES[raw];
6848
9034
  }
@@ -6880,13 +9066,13 @@ function normalizeProviderKey2(providerOrLabel) {
6880
9066
  function resolveProviderLogoKey(...candidates) {
6881
9067
  for (const candidate of candidates) {
6882
9068
  const key = normalizeProviderKey2(candidate);
6883
- if (key && PROVIDER_LOGOS[key]) {
9069
+ if (key && key !== "notselected" && PROVIDER_LOGOS[key]) {
6884
9070
  return key;
6885
9071
  }
6886
9072
  }
6887
9073
  for (const candidate of candidates) {
6888
9074
  const key = normalizeProviderKey2(candidate);
6889
- if (key) {
9075
+ if (key && key !== "notselected") {
6890
9076
  return key;
6891
9077
  }
6892
9078
  }
@@ -6894,7 +9080,7 @@ function resolveProviderLogoKey(...candidates) {
6894
9080
  }
6895
9081
  function providerLogoClass(providerOrLabel, ...moreCandidates) {
6896
9082
  const key = resolveProviderLogoKey(providerOrLabel, ...moreCandidates) || normalizeProviderKey2(providerOrLabel);
6897
- if (!key) {
9083
+ if (!key || key === "notselected") {
6898
9084
  return "";
6899
9085
  }
6900
9086
  return ` provider-logo--${key}${BRAND_LOGO_KEYS.has(key) ? " provider-logo--brand" : ""}`;
@@ -6912,16 +9098,14 @@ function providerLogoHtml(providerOrLabel, labelFallback, ...moreCandidates) {
6912
9098
  if (assetUrl) {
6913
9099
  return providerIconImgHtml(assetUrl, key);
6914
9100
  }
9101
+ if (svg) {
9102
+ return svg;
9103
+ }
6915
9104
  const iconUrl = key && PROVIDER_ICON_URLS[key];
6916
9105
  if (iconUrl) {
6917
9106
  return providerIconImgHtml(iconUrl, key);
6918
9107
  }
6919
- if (svg) {
6920
- return svg;
6921
- }
6922
- const label = (labelFallback ?? providerOrLabel ?? "?").trim();
6923
- const initials = label ? label.slice(0, 2).toUpperCase() : "?";
6924
- return `<span aria-hidden="true">${initials}</span>`;
9108
+ return PROVIDER_LOGO_FALLBACK_SVG;
6925
9109
  }
6926
9110
  var PROVIDER_BENEFITS = {
6927
9111
  supabase: "Best when you want auth, data, and storage in one stack.",
@@ -6946,6 +9130,7 @@ var PROVIDER_BENEFITS = {
6946
9130
  sentry: "Best first choice for production errors and traces.",
6947
9131
  posthog: "Best for product analytics and funnel verification.",
6948
9132
  auth0: "Enterprise identity with rules, MFA, and social login.",
9133
+ firebase: "Good fit when auth, Firestore, and hosting stay in one Google stack.",
6949
9134
  "better-auth": "Type-safe auth you own in your codebase.",
6950
9135
  playwright: "Best for browser-flow verification and UI regression checks.",
6951
9136
  logrocket: "Best when session replay is the missing evidence.",
@@ -6973,7 +9158,9 @@ function providerLogosPayloadJson() {
6973
9158
  inlineOnly: [...INLINE_ONLY_LOGO_KEYS],
6974
9159
  aliases: ALIASES,
6975
9160
  benefits: PROVIDER_BENEFITS,
6976
- brandKeys: [...BRAND_LOGO_KEYS]
9161
+ brandKeys: [...BRAND_LOGO_KEYS],
9162
+ nodeTones: PROVIDER_NODE_TONES,
9163
+ fallbackSvg: PROVIDER_LOGO_FALLBACK_SVG
6977
9164
  }).replace(/</g, "\\u003c");
6978
9165
  }
6979
9166
 
@@ -6996,19 +9183,59 @@ var CATEGORY_META = [
6996
9183
  { key: "landing", label: "Landing / Onboarding" },
6997
9184
  { key: "errorHandling", label: "Error Handling" }
6998
9185
  ];
9186
+ var DEFAULT_FREE_UNLOCKED_MAP_KEYS = [
9187
+ "appFlow",
9188
+ "frontend",
9189
+ "backend",
9190
+ "auth",
9191
+ "database",
9192
+ "payments"
9193
+ ];
9194
+ var DEFAULT_FREE_UNLOCKED_MAP_KEY_SET = new Set(DEFAULT_FREE_UNLOCKED_MAP_KEYS);
9195
+ function isMapCategoryUnlocked(artifact, categoryKey) {
9196
+ if (artifact.plan === "pro" || artifact.usage?.plan === "pro") {
9197
+ return true;
9198
+ }
9199
+ if (!artifact.plan && !artifact.usage) {
9200
+ return true;
9201
+ }
9202
+ const keys = Array.isArray(artifact.usage?.unlockedMapCategoryKeys) && artifact.usage.unlockedMapCategoryKeys.length > 0 ? artifact.usage.unlockedMapCategoryKeys : DEFAULT_FREE_UNLOCKED_MAP_KEYS;
9203
+ return keys.includes(categoryKey) && DEFAULT_FREE_UNLOCKED_MAP_KEY_SET.has(categoryKey);
9204
+ }
6999
9205
  function gapCountForArea(artifact, areaKey) {
7000
9206
  return artifact.gaps.filter((g2) => g2.primaryMapCategory === areaKey).length;
7001
9207
  }
7002
9208
  function defaultAreaKey(artifact) {
7003
- const fromGap = artifact.gaps[0]?.primaryMapCategory;
9209
+ const fromGap = artifact.gaps.find((gap) => isMapCategoryUnlocked(artifact, gap.primaryMapCategory))?.primaryMapCategory;
7004
9210
  if (fromGap) {
7005
9211
  return fromGap;
7006
9212
  }
7007
9213
  const areas = artifact.missionGraph.areas ?? [];
7008
- if (areas.some((a) => a.key === "frontend")) {
9214
+ const fromArea = areas.find((area) => isMapCategoryUnlocked(artifact, area.key))?.key;
9215
+ if (fromArea) {
9216
+ return fromArea;
9217
+ }
9218
+ if (isMapCategoryUnlocked(artifact, "frontend")) {
7009
9219
  return "frontend";
7010
9220
  }
7011
- return areas[0]?.key ?? "frontend";
9221
+ return CATEGORY_META.find((meta) => isMapCategoryUnlocked(artifact, meta.key))?.key ?? "frontend";
9222
+ }
9223
+ function providerLabelForArea(artifact, areaKey, mission, selectedOverride) {
9224
+ if (selectedOverride) {
9225
+ const token = normalizeProviderKey2(selectedOverride);
9226
+ const options = artifact.providerOptions?.[areaKey] ?? [];
9227
+ const match = options.find(
9228
+ (option) => normalizeProviderKey2(option.provider) === token || normalizeProviderKey2(option.label) === token
9229
+ );
9230
+ if (match?.label) {
9231
+ return match.label;
9232
+ }
9233
+ if (mission && missionMatchesProvider(mission, selectedOverride)) {
9234
+ return mission.providerLabel ?? selectedOverride;
9235
+ }
9236
+ return selectedOverride;
9237
+ }
9238
+ return mission?.providerLabel ?? "Not selected";
7012
9239
  }
7013
9240
  function buildAccountStripHtml(artifact) {
7014
9241
  if (!artifact.accountEmail) {
@@ -7036,28 +9263,35 @@ function buildNodeHtml(artifact, meta, selectedAreaKey) {
7036
9263
  const area = (artifact.missionGraph.areas ?? []).find((a) => a.key === meta.key);
7037
9264
  const selectedOverride = artifact.selectedProviders?.[meta.key] ?? "";
7038
9265
  const mission = preferredMissionForArea(area, selectedOverride);
7039
- const selectedProvider = selectedOverride || mission?.provider || mission?.providerLabel || "";
7040
- const providerLabel3 = mission?.providerLabel ?? "Not selected";
9266
+ const providerLabel3 = providerLabelForArea(artifact, meta.key, mission, selectedOverride);
9267
+ const logoProvider = selectedOverride || mission?.provider || mission?.providerLabel || "";
7041
9268
  const readiness = mission?.readinessPercent ?? area?.readinessPercent ?? 0;
7042
9269
  const openChecks = openChecksForMission(mission);
9270
+ const repoOpenChecks = mission?.checks.filter(
9271
+ (check) => check.evidenceClass === "missing-repo-fix" || check.status === "missing" || check.status === "failed"
9272
+ ).length ?? 0;
9273
+ const providerOpenChecks = mission?.checks.filter(
9274
+ (check) => check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.evidenceClass === "mcp-verifier" || check.status === "needs-connection" || check.status === "unknown"
9275
+ ).length ?? 0;
7043
9276
  const modelGaps = gapCountForArea(artifact, meta.key);
9277
+ const unlocked = isMapCategoryUnlocked(artifact, meta.key);
7044
9278
  const stateClass = modelGaps > 0 ? " studio-node--critical" : openChecks > 0 ? " studio-node--warning" : area ? " studio-node--in-project" : "";
7045
9279
  const selectedClass = selectedAreaKey === meta.key ? " studio-node--selected" : "";
7046
- const metaText = modelGaps > 0 ? `${modelGaps} stack fix${modelGaps === 1 ? "" : "es"}` : openChecks > 0 ? `${openChecks} stack fix${openChecks === 1 ? "" : "es"}` : `${readiness}% health`;
7047
- const logoClass = providerLogoClass(mission?.provider, mission?.key, String(selectedProvider), providerLabel3);
7048
- const logoInner = providerLogoHtml(
7049
- mission?.provider,
7050
- providerLabel3,
7051
- mission?.key,
7052
- String(selectedProvider)
7053
- );
9280
+ const lockedClass = unlocked ? "" : " studio-node--locked";
9281
+ const metaText = modelGaps > 0 ? `${modelGaps} stack fix${modelGaps === 1 ? "" : "es"}` : repoOpenChecks > 0 ? `${repoOpenChecks} stack fix${repoOpenChecks === 1 ? "" : "es"}` : providerOpenChecks > 0 ? "Live check" : `${readiness}% health`;
9282
+ const logoClass = providerLogoClass(logoProvider, mission?.key, mission?.providerLabel);
9283
+ const logoInner = providerLogoHtml(logoProvider, providerLabel3, mission?.key, mission?.providerLabel);
7054
9284
  const gapBadge = modelGaps > 0 ? `<span class="studio-node__raven-badge" role="presentation">Gap ${modelGaps}</span>` : "";
7055
- return `<button type="button" class="studio-node studio-node--${escapeHtml(meta.key)} provider-logo${logoClass}${stateClass}${selectedClass}" data-area-key="${escapeHtml(meta.key)}" aria-label="${escapeHtml(meta.label)}, ${escapeHtml(providerLabel3)}, ${escapeHtml(metaText)}">
9285
+ const lockBadge = unlocked ? "" : '<span class="studio-node__lock">Pro</span>';
9286
+ const lockedAttrs = unlocked ? "" : ' data-locked-map="1" title="Pro only - this section is locked on the Free plan"';
9287
+ const lockedAria = unlocked ? "" : ", Pro only. Upgrade to unlock";
9288
+ return `<button type="button" class="studio-node studio-node--${escapeHtml(meta.key)} provider-logo${logoClass}${stateClass}${selectedClass}${lockedClass}" data-area-key="${escapeHtml(meta.key)}"${lockedAttrs} aria-label="${escapeHtml(meta.label)}, ${escapeHtml(providerLabel3)}, ${escapeHtml(metaText)}${lockedAria}">
7056
9289
  <span class="studio-node__logo provider-logo${logoClass}" aria-hidden="true">${logoInner}</span>
7057
9290
  <span class="studio-node__title">${escapeHtml(meta.label)}</span>
7058
9291
  <span class="studio-node__provider">${escapeHtml(providerLabel3)}</span>
7059
9292
  <span class="studio-node__meta">${escapeHtml(metaText)}</span>
7060
9293
  ${gapBadge}
9294
+ ${lockBadge}
7061
9295
  </button>`;
7062
9296
  }
7063
9297
  function generateReportHtml(artifact) {
@@ -7067,7 +9301,6 @@ function generateReportHtml(artifact) {
7067
9301
  const nodesHtml = CATEGORY_META.map((meta) => buildNodeHtml(hydrated, meta, defaultKey)).join("\n");
7068
9302
  const accountStrip = buildAccountStripHtml(hydrated);
7069
9303
  const logosJson = providerLogosPayloadJson();
7070
- const scannedLabel = escapeHtml(hydrated.scannedAt);
7071
9304
  return `<!DOCTYPE html>
7072
9305
  <html lang="en" class="cli-mission-report" data-surface="panel" data-skin="editorial">
7073
9306
  <head>
@@ -7087,8 +9320,10 @@ function generateReportHtml(artifact) {
7087
9320
  <div class="studio-shell" aria-label="VibeRaven Studio cockpit">
7088
9321
  <header class="studio-top-rail" aria-label="Studio status">
7089
9322
  <img class="studio-top-rail__logo" src="report/assets/viberaven-logo.png" alt="" width="32" height="32" />
7090
- <span class="studio-top-rail__brand"><span>VIBERAVEN / MISSION MAP</span></span>
7091
- <div class="studio-top-rail__build">Last scan \xB7 ${scannedLabel}</div>
9323
+ <div class="studio-top-rail__brand">
9324
+ <span class="studio-top-rail__wordmark">VIBERAVEN</span>
9325
+ <span class="studio-top-rail__label">MISSION MAP</span>
9326
+ </div>
7092
9327
  </header>
7093
9328
  <div class="studio-workspace">
7094
9329
  <main class="studio-map-canvas" aria-label="Interactive full-stack system map">
@@ -7097,7 +9332,7 @@ function generateReportHtml(artifact) {
7097
9332
  <div class="studio-connector-layer" aria-hidden="true"></div>
7098
9333
  <div class="studio-core-group">
7099
9334
  <div class="studio-core-node" aria-label="Production core score">
7100
- <img class="studio-core-node__mark" src="report/assets/viberaven-logo.png" alt="VibeRaven" width="92" height="92" />
9335
+ <span>VIBERAVEN</span>
7101
9336
  <strong>${hydrated.productionCorePercent}%</strong>
7102
9337
  <small>Production core</small>
7103
9338
  </div>
@@ -7149,16 +9384,22 @@ var REPORT_ASSET_FILES = [
7149
9384
  var INLINE_SECRET_PATTERNS = [
7150
9385
  /\b(sk_(?:live|test)_[A-Za-z0-9]{12,}|sk-proj-[A-Za-z0-9_-]{16,}|sk-[A-Za-z0-9_-]{20,})\b/g,
7151
9386
  /\b(whsec_[A-Za-z0-9]{12,}|rk_(?:live|test)_[A-Za-z0-9]{12,})\b/g,
7152
- /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g
9387
+ /\bghp_[A-Za-z0-9]{36,}\b/g,
9388
+ /\bgithub_pat_[A-Za-z0-9_]{50,}\b/g,
9389
+ /\bxox[bp]-[A-Za-z0-9-]{20,}\b/g,
9390
+ /\bxapp-[A-Za-z0-9-]{20,}\b/g,
9391
+ /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,
9392
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g
7153
9393
  ];
7154
- var SENSITIVE_ENV_KEYS = /^(?:OPENAI_API_KEY|OPENAI_KEY|ANTHROPIC_API_KEY|VIBERAVEN_ACCESS_TOKEN|VRAVEN_.*|SUPABASE_SERVICE_ROLE_KEY|.*_SECRET_KEY)$/i;
9394
+ var SENSITIVE_ENV_KEYS = /(?:^|_)(?:ACCESS_TOKEN|AUTHORIZATION|API_KEY|SECRET|SECRET_KEY|SERVICE_ROLE_KEY|TOKEN|PASSWORD|PRIVATE_KEY|CREDENTIALS?)(?:$|_)/i;
7155
9395
  function redactString(value) {
7156
9396
  let out = value;
7157
9397
  for (const pattern of INLINE_SECRET_PATTERNS) {
7158
- out = out.replace(pattern, "[REDACTED_SECRET]");
9398
+ out = out.replace(pattern, pattern.source.includes("PRIVATE KEY") ? "[REDACTED_PRIVATE_KEY]" : "[REDACTED_SECRET]");
7159
9399
  }
9400
+ out = out.replace(/\bAuthorization\s*:\s*([A-Za-z][A-Za-z0-9._-]*)\s+[^\s;,]+/gi, "Authorization: $1 [REDACTED]");
7160
9401
  return out.replace(
7161
- /\b([A-Za-z0-9_]*(?:API_KEY|SECRET|TOKEN|PASSWORD|PRIVATE_KEY)[A-Za-z0-9_]*)\s*=\s*["']?[^"'\s;,]+["']?/gi,
9402
+ /\b([A-Za-z0-9_]*(?:ACCESS_TOKEN|AUTHORIZATION|API_KEY|SECRET|SECRET_KEY|SERVICE_ROLE_KEY|TOKEN|PASSWORD|PRIVATE_KEY|CREDENTIALS?)[A-Za-z0-9_]*)\s*=\s*["']?[^"'\s;,]+["']?/gi,
7162
9403
  "$1=[REDACTED]"
7163
9404
  );
7164
9405
  }
@@ -7397,6 +9638,15 @@ function gapTagColor(modelGaps, open) {
7397
9638
  }
7398
9639
  return import_picocolors.default.dim;
7399
9640
  }
9641
+ function manualActionCheckCount(artifact) {
9642
+ return (artifact.missionGraph.areas ?? []).reduce((areaTotal, area) => {
9643
+ return areaTotal + area.providerMissions.reduce((missionTotal, mission) => {
9644
+ return missionTotal + mission.checks.filter(
9645
+ (check) => check.evidenceClass === "manual-dashboard" || check.evidenceClass === "mcp-verifier" || check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.status === "needs-connection" || check.status === "unknown"
9646
+ ).length;
9647
+ }, 0);
9648
+ }, 0);
9649
+ }
7400
9650
  function printScanSummary(artifact, paths) {
7401
9651
  const pct = artifact.productionCorePercent;
7402
9652
  const gapCount = artifact.gaps.length;
@@ -7432,6 +9682,11 @@ function printScanSummary(artifact, paths) {
7432
9682
  console.log("");
7433
9683
  console.log(import_picocolors.default.dim("Press Enter in `viberaven` menu to rescan \xB7 `viberaven prompt` for top gap"));
7434
9684
  console.log(import_picocolors.default.dim("Agents: read .viberaven/agent-summary.md"));
9685
+ console.log(formatAgentStatus(READY, `Scan complete. Read ${paths.summaryPath} before changing code.`));
9686
+ const manualCount = manualActionCheckCount(artifact);
9687
+ if (manualCount > 0) {
9688
+ console.log(formatAgentStatus(MANUAL_ACTION_REQUIRED, `${manualCount} provider dashboard or read-only MCP check${manualCount === 1 ? "" : "s"} require user/provider verification. Do not claim these as repo-code fixes.`));
9689
+ }
7435
9690
  console.log("");
7436
9691
  }
7437
9692
 
@@ -7972,7 +10227,7 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
7972
10227
  var import_picocolors3 = __toESM(require_picocolors());
7973
10228
 
7974
10229
  // src/version.ts
7975
- var VERSION = "0.1.0-beta.1";
10230
+ var VERSION = "0.1.0-beta.3";
7976
10231
 
7977
10232
  // src/tui/runInteractive.ts
7978
10233
  async function formatStatusLine() {
@@ -8164,6 +10419,18 @@ async function runInteractiveSession(startDir = process.cwd()) {
8164
10419
  }
8165
10420
 
8166
10421
  // src/cli.ts
10422
+ function buildHelpText() {
10423
+ return `First-time terminal flow:
10424
+
10425
+ npx -y @viberaven/cli@beta login
10426
+ npx -y @viberaven/cli@beta status
10427
+ npx -y @viberaven/cli@beta scan --open
10428
+
10429
+ Quota:
10430
+
10431
+ Free: 2 lifetime scans
10432
+ Pro: 50 scans per month`;
10433
+ }
8167
10434
  function printHelp() {
8168
10435
  console.log(`viberaven ${VERSION} \u2014 launch readiness for AI-built apps
8169
10436
 
@@ -8219,6 +10486,7 @@ Security:
8219
10486
  CLI scans use VibeRaven login \u2014 not OPENAI_API_KEY. See packages/cli/SECURITY.md.
8220
10487
 
8221
10488
  `);
10489
+ console.log(buildHelpText());
8222
10490
  }
8223
10491
  function parseArgs(argv) {
8224
10492
  const flags = {};
@@ -8230,6 +10498,10 @@ function parseArgs(argv) {
8230
10498
  flags.help = true;
8231
10499
  continue;
8232
10500
  }
10501
+ if (arg === "--version" || arg === "-v") {
10502
+ flags.version = true;
10503
+ continue;
10504
+ }
8233
10505
  if (arg.startsWith("--")) {
8234
10506
  const key = arg.slice(2);
8235
10507
  const next = argv[i + 1];
@@ -8249,6 +10521,9 @@ function parseArgs(argv) {
8249
10521
  }
8250
10522
  return { command, flags, positional };
8251
10523
  }
10524
+ function formatScanJsonStdout(artifact) {
10525
+ return JSON.stringify(sanitizeArtifactForDisk(artifact), null, 2);
10526
+ }
8252
10527
  async function cmdLogin(flags) {
8253
10528
  const apiBaseUrl = resolveApiBaseUrl(typeof flags["api-url"] === "string" ? flags["api-url"] : void 0);
8254
10529
  await runDeviceLogin(apiBaseUrl);
@@ -8300,13 +10575,17 @@ async function cmdScan(flags, positional) {
8300
10575
  }
8301
10576
  return 2;
8302
10577
  }
8303
- console.error(result.message);
10578
+ if (result.kind === "auth_required" || result.kind === "session_invalid") {
10579
+ console.error(formatAgentStatus(LOGIN_REQUIRED, result.message));
10580
+ return 1;
10581
+ }
10582
+ console.error(formatAgentStatus(ERROR, result.message));
8304
10583
  return 1;
8305
10584
  }
8306
10585
  const artifact = await enrichArtifactWithAccount(result.artifact, apiBaseUrl, accessToken);
8307
10586
  const paths = await writeScanArtifacts({ artifact, cwd: workspacePath });
8308
10587
  if (flags.json) {
8309
- console.log(JSON.stringify(artifact, null, 2));
10588
+ console.log(formatScanJsonStdout(artifact));
8310
10589
  return 0;
8311
10590
  }
8312
10591
  printScanSummary(artifact, paths);
@@ -8429,6 +10708,10 @@ async function main() {
8429
10708
  printHelp();
8430
10709
  return 0;
8431
10710
  }
10711
+ if (flags.version || command === "version") {
10712
+ console.log(VERSION);
10713
+ return 0;
10714
+ }
8432
10715
  if (!command) {
8433
10716
  await runInteractiveSession();
8434
10717
  return 0;
@@ -8454,23 +10737,26 @@ async function main() {
8454
10737
  return cmdPrompt(flags, positional);
8455
10738
  case "stack":
8456
10739
  return cmdStack(positional);
8457
- case "--version":
8458
- case "-v":
8459
- case "version":
8460
- console.log(VERSION);
8461
- return 0;
8462
10740
  default:
8463
10741
  console.error(`Unknown command: ${command}`);
8464
10742
  printHelp();
8465
10743
  return 1;
8466
10744
  }
8467
10745
  }
8468
- main().then((code) => {
8469
- if (code !== 0) {
8470
- process.exitCode = code;
8471
- }
8472
- }).catch((error) => {
8473
- console.error(error instanceof Error ? error.message : String(error));
8474
- process.exitCode = 1;
10746
+ if (require.main === module) {
10747
+ main().then((code) => {
10748
+ if (code !== 0) {
10749
+ process.exitCode = code;
10750
+ }
10751
+ }).catch((error) => {
10752
+ console.error(error instanceof Error ? error.message : String(error));
10753
+ process.exitCode = 1;
10754
+ });
10755
+ }
10756
+ // Annotate the CommonJS export names for ESM import in node:
10757
+ 0 && (module.exports = {
10758
+ buildHelpText,
10759
+ formatScanJsonStdout,
10760
+ parseArgs
8475
10761
  });
8476
10762
  //# sourceMappingURL=cli.js.map