@viberaven/cli 0.1.0-beta.1 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,12 @@ var require_src = __commonJS({
155
160
  });
156
161
 
157
162
  // src/cli.ts
163
+ var cli_exports = {};
164
+ __export(cli_exports, {
165
+ formatScanJsonStdout: () => formatScanJsonStdout,
166
+ parseArgs: () => parseArgs
167
+ });
168
+ module.exports = __toCommonJS(cli_exports);
158
169
  var import_node_path8 = require("node:path");
159
170
 
160
171
  // src/config.ts
@@ -241,8 +252,18 @@ async function loadCredentials() {
241
252
  email: typeof parsed.email === "string" ? parsed.email : void 0,
242
253
  plan: typeof parsed.plan === "string" ? parsed.plan : void 0
243
254
  };
244
- } catch {
245
- return void 0;
255
+ } catch (error) {
256
+ if (error.code !== "ENOENT") {
257
+ return void 0;
258
+ }
259
+ const accessToken = process.env.VIBERAVEN_ACCESS_TOKEN?.trim();
260
+ if (!accessToken) {
261
+ return void 0;
262
+ }
263
+ return {
264
+ accessToken,
265
+ apiBaseUrl: resolveApiBaseUrl()
266
+ };
246
267
  }
247
268
  }
248
269
  async function saveCredentials(credentials) {
@@ -454,6 +475,16 @@ function isRecord(value) {
454
475
  return typeof value === "object" && value !== null && !Array.isArray(value);
455
476
  }
456
477
 
478
+ // src/statusLabels.ts
479
+ var READY = "READY";
480
+ var LOGIN_REQUIRED = "LOGIN_REQUIRED";
481
+ var UPGRADE_REQUIRED = "UPGRADE_REQUIRED";
482
+ var MANUAL_ACTION_REQUIRED = "MANUAL_ACTION_REQUIRED";
483
+ var ERROR = "ERROR";
484
+ function formatAgentStatus(label, message) {
485
+ return `${label}: ${message}`;
486
+ }
487
+
457
488
  // src/account.ts
458
489
  async function fetchAccountMe(apiBaseUrl, accessToken) {
459
490
  const url = `${normalizeBaseUrl(apiBaseUrl)}/v1/account/me`;
@@ -510,8 +541,9 @@ function formatUsageLine(usage) {
510
541
  function formatScanLimitMessage(upgradeUrl) {
511
542
  return [
512
543
  "",
513
- "Free scan limit reached. Upgrade to Pro to continue.",
544
+ formatAgentStatus(UPGRADE_REQUIRED, "Free scan limit reached. Upgrade to Pro to continue."),
514
545
  "Your last scan artifacts are unchanged if you already ran a scan in this repo.",
546
+ "Do not keep retrying this scan until the user upgrades or quota resets.",
515
547
  "",
516
548
  `Upgrade & account: ${upgradeUrl}`,
517
549
  ""
@@ -539,7 +571,7 @@ async function runDeviceLogin(apiBaseUrl) {
539
571
  continue;
540
572
  }
541
573
  if (result.status === "expired") {
542
- throw new Error("Sign-in expired. Run `viberaven login` again.");
574
+ 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
575
  }
544
576
  if (result.status === "denied") {
545
577
  throw new Error("Sign-in was denied.");
@@ -570,7 +602,7 @@ async function runDeviceLogin(apiBaseUrl) {
570
602
  return;
571
603
  }
572
604
  }
573
- throw new Error("Sign-in timed out. Run `viberaven login` again.");
605
+ 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
606
  }
575
607
  function buildVerificationUrl(verificationUrl, deviceCode) {
576
608
  try {
@@ -586,7 +618,7 @@ async function requireCredentials(apiBaseUrl) {
586
618
  const creds = await loadCredentials();
587
619
  const base = apiBaseUrl ?? creds?.apiBaseUrl ?? resolveApiBaseUrl();
588
620
  if (!creds?.accessToken) {
589
- throw new Error("Not signed in. Run `viberaven login` first (or set credentials via device flow).");
621
+ 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
622
  }
591
623
  return { accessToken: creds.accessToken, apiBaseUrl: base };
592
624
  }
@@ -1442,6 +1474,17 @@ var PROVIDERS = [
1442
1474
  provider("authjs", "Auth.js", ["authjs", "auth", "nextauth"], ["auth"], ["auth"], "authjs", {
1443
1475
  docsUrl: "https://authjs.dev"
1444
1476
  }),
1477
+ provider("auth0", "Auth0", ["auth0"], ["auth"], ["auth"], "auth0", {
1478
+ docsUrl: "https://auth0.com/docs",
1479
+ dashboardUrl: "https://manage.auth0.com"
1480
+ }),
1481
+ provider("better-auth", "Better Auth", ["betterauth", "better-auth"], ["auth"], ["auth"], "better-auth", {
1482
+ docsUrl: "https://www.better-auth.com/docs"
1483
+ }),
1484
+ provider("firebase", "Firebase", ["firebase", "firestore"], ["database"], ["database"], "firebase", {
1485
+ docsUrl: "https://firebase.google.com/docs",
1486
+ dashboardUrl: "https://console.firebase.google.com"
1487
+ }),
1445
1488
  provider("neon", "Neon", ["neon"], ["database"], ["database"], "neon", {
1446
1489
  docsUrl: "https://neon.tech/docs",
1447
1490
  dashboardUrl: "https://console.neon.tech",
@@ -1511,6 +1554,10 @@ var PROVIDERS = [
1511
1554
  dashboardUrl: "https://polar.sh/dashboard",
1512
1555
  verification: { supportsReadOnly: false }
1513
1556
  }),
1557
+ provider("lemon-squeezy", "Lemon Squeezy", ["lemonsqueezy", "lemon-squeezy", "lemon squeezy"], ["payments"], ["payments"], "lemon-squeezy", {
1558
+ docsUrl: "https://docs.lemonsqueezy.com",
1559
+ dashboardUrl: "https://app.lemonsqueezy.com"
1560
+ }),
1514
1561
  provider("vercel", "Vercel", ["vercel"], ["deployment"], ["deployment"], "vercel", {
1515
1562
  docsUrl: "https://vercel.com/docs",
1516
1563
  dashboardUrl: "https://vercel.com/dashboard",
@@ -1523,7 +1570,7 @@ var PROVIDERS = [
1523
1570
  },
1524
1571
  verification: { supportsReadOnly: true }
1525
1572
  }),
1526
- provider("netlify", "Netlify", ["netlify"], ["deployment"], [], "netlify", {
1573
+ provider("netlify", "Netlify", ["netlify"], ["deployment"], ["deployment"], "netlify", {
1527
1574
  docsUrl: "https://docs.netlify.com",
1528
1575
  dashboardUrl: "https://app.netlify.com",
1529
1576
  mcp: {
@@ -1535,7 +1582,27 @@ var PROVIDERS = [
1535
1582
  },
1536
1583
  verification: { supportsReadOnly: false }
1537
1584
  }),
1538
- provider("aws", "AWS", ["aws"], ["deployment"], [], "aws", {
1585
+ provider("render", "Render", ["render", "rendercom"], ["deployment"], ["deployment"], "render", {
1586
+ docsUrl: "https://render.com/docs",
1587
+ dashboardUrl: "https://dashboard.render.com"
1588
+ }),
1589
+ provider("railway", "Railway", ["railway", "railwayapp"], ["deployment"], ["deployment"], "railway", {
1590
+ docsUrl: "https://docs.railway.com",
1591
+ dashboardUrl: "https://railway.com"
1592
+ }),
1593
+ provider("cloudflare", "Cloudflare", ["cloudflare", "cloudflarepages", "workers"], ["deployment"], ["deployment"], "cloudflare", {
1594
+ docsUrl: "https://developers.cloudflare.com",
1595
+ dashboardUrl: "https://dash.cloudflare.com",
1596
+ mcp: {
1597
+ label: "Cloudflare",
1598
+ serverName: "cloudflare-api",
1599
+ vscodeServer: { type: "http", url: "https://mcp.cloudflare.com/mcp" },
1600
+ cursorServer: { url: "https://mcp.cloudflare.com/mcp" },
1601
+ keyInstructions: "Cloudflare MCP uses Cloudflare account authentication. Finish browser sign-in or provide an API token only when prompted."
1602
+ },
1603
+ verification: { supportsReadOnly: true }
1604
+ }),
1605
+ provider("aws", "AWS", ["aws"], ["deployment"], ["deployment"], "aws", {
1539
1606
  docsUrl: "https://docs.aws.amazon.com",
1540
1607
  dashboardUrl: "https://console.aws.amazon.com"
1541
1608
  }),
@@ -1567,6 +1634,18 @@ var PROVIDERS = [
1567
1634
  docsUrl: "https://docs.logrocket.com",
1568
1635
  dashboardUrl: "https://app.logrocket.com"
1569
1636
  }),
1637
+ provider("github", "GitHub Actions", ["github", "githubactions"], ["testing"], [], "github", {
1638
+ docsUrl: "https://docs.github.com/actions",
1639
+ dashboardUrl: "https://github.com",
1640
+ mcp: {
1641
+ label: "GitHub",
1642
+ serverName: "github",
1643
+ vscodeServer: { type: "http", url: "https://api.githubcopilot.com/mcp/" },
1644
+ cursorServer: { url: "https://api.githubcopilot.com/mcp/" },
1645
+ keyInstructions: "GitHub MCP should use IDE/GitHub authentication. Use read-only repository, Actions, and branch-protection queries for verification."
1646
+ },
1647
+ verification: { supportsReadOnly: true }
1648
+ }),
1570
1649
  provider("playwright", "Playwright", ["playwright", "playwrighttest"], ["testing"], [], "playwright", {
1571
1650
  docsUrl: "https://playwright.dev/docs/intro",
1572
1651
  mcp: {
@@ -1590,7 +1669,7 @@ var PROVIDERS = [
1590
1669
  },
1591
1670
  verification: { supportsReadOnly: false }
1592
1671
  }),
1593
- provider("bot-protection", "Bot protection", ["botprotection", "cloudflareturnstile", "turnstile", "cloudflare"], ["security"], ["security"], "bot-protection", {
1672
+ provider("bot-protection", "Bot protection", ["botprotection", "cloudflareturnstile", "turnstile"], ["security"], ["security"], "bot-protection", {
1594
1673
  docsUrl: "https://developers.cloudflare.com/turnstile",
1595
1674
  dashboardUrl: "https://dash.cloudflare.com",
1596
1675
  mcp: {
@@ -1773,6 +1852,269 @@ function titleizeProvider(provider2) {
1773
1852
  );
1774
1853
  }
1775
1854
 
1855
+ // ../../src/station/verificationLayer/types.ts
1856
+ function providerCheckId(provider2, checkKey) {
1857
+ return `${provider2}:${checkKey}`;
1858
+ }
1859
+ var PROVIDER_CONNECTION_BLOCKS_VERIFY = [
1860
+ "not_configured",
1861
+ "configured",
1862
+ "unknown_runtime",
1863
+ "unsupported"
1864
+ ];
1865
+ function coerceProviderVerificationStatus(status, connectionState) {
1866
+ if (status !== "verified") {
1867
+ return status;
1868
+ }
1869
+ if (PROVIDER_CONNECTION_BLOCKS_VERIFY.includes(connectionState)) {
1870
+ return connectionState === "not_configured" || connectionState === "configured" ? "needs_mcp" : "unknown";
1871
+ }
1872
+ return status;
1873
+ }
1874
+ function mapVerificationStatusToMissionStatus(status) {
1875
+ switch (status) {
1876
+ case "verified":
1877
+ return "passed";
1878
+ case "missing":
1879
+ return "missing";
1880
+ case "unknown":
1881
+ return "unknown";
1882
+ case "needs_mcp":
1883
+ return "needs-connection";
1884
+ case "manual":
1885
+ return "manual-required";
1886
+ default:
1887
+ return "failed";
1888
+ }
1889
+ }
1890
+ function mapEvidenceSourceToLegacyEvidenceClass(source, status) {
1891
+ if (source === "repo") {
1892
+ return status === "verified" ? "repo-verified" : "missing-repo-fix";
1893
+ }
1894
+ if (source === "manual") {
1895
+ return "manual-dashboard";
1896
+ }
1897
+ if (source === "mcp") {
1898
+ return "mcp-verifier";
1899
+ }
1900
+ return "mcp-verifier";
1901
+ }
1902
+ function isProviderLayerCheck(check) {
1903
+ return check.evidenceSource === "provider" || check.evidenceSource === "mcp";
1904
+ }
1905
+ function isRepoLayerCheck(check) {
1906
+ return check.evidenceSource === "repo";
1907
+ }
1908
+ function computeLayerReadinessPercent(checks, layer) {
1909
+ const filtered = checks.filter(
1910
+ (check) => layer === "repo" ? isRepoLayerCheck(check) : isProviderLayerCheck(check)
1911
+ );
1912
+ if (filtered.length === 0) {
1913
+ return 100;
1914
+ }
1915
+ const verified = filtered.filter((check) => check.status === "verified").length;
1916
+ return Math.round(verified / filtered.length * 100);
1917
+ }
1918
+ function aggregateReadinessPercents(providerResults) {
1919
+ if (providerResults.length === 0) {
1920
+ return { repoReadinessPercent: 100, providerReadinessPercent: 100 };
1921
+ }
1922
+ const repoSum = providerResults.reduce((sum, r) => sum + r.repoReadinessPercent, 0);
1923
+ const providerSum = providerResults.reduce((sum, r) => sum + r.providerReadinessPercent, 0);
1924
+ return {
1925
+ repoReadinessPercent: Math.round(repoSum / providerResults.length),
1926
+ providerReadinessPercent: Math.round(providerSum / providerResults.length)
1927
+ };
1928
+ }
1929
+
1930
+ // ../../src/station/verificationLayer/mergeIntoMissionGraph.ts
1931
+ var PROVIDER_WIRING_KEY = {
1932
+ vercel: "vercel-deployment",
1933
+ supabase: "supabase-database",
1934
+ stripe: "stripe-payments"
1935
+ };
1936
+ function mergeVerificationIntoMissionGraph(graph, layer) {
1937
+ const providerMissions = graph.areas.flatMap((area) => area.providerMissions);
1938
+ const missionByKey = new Map(providerMissions.map((mission) => [mission.key, mission]));
1939
+ for (const layerCheck of layer.checks) {
1940
+ if (layerCheck.evidenceSource === "repo") {
1941
+ continue;
1942
+ }
1943
+ const mission = resolveTargetMission(graph, missionByKey, layerCheck);
1944
+ if (!mission) {
1945
+ continue;
1946
+ }
1947
+ if (mission.checks.some((check) => check.verificationCheckId === layerCheck.id || check.id === missionRowId(layerCheck.id))) {
1948
+ continue;
1949
+ }
1950
+ mission.checks.push(verificationCheckToMissionCheck(layerCheck, mission));
1951
+ }
1952
+ recomputeMissionReadiness(providerMissions);
1953
+ const areas = rebuildAreas(providerMissions);
1954
+ return {
1955
+ ...graph,
1956
+ areas,
1957
+ byArea: areas.reduce((acc, area) => {
1958
+ acc[area.key] = area;
1959
+ return acc;
1960
+ }, {}),
1961
+ byProvider: providerMissions.reduce((acc, mission) => {
1962
+ acc[mission.key] = mission;
1963
+ return acc;
1964
+ }, {}),
1965
+ verificationLayer: layer
1966
+ };
1967
+ }
1968
+ function resolveTargetMission(graph, missionByKey, layerCheck) {
1969
+ const wiringKey = PROVIDER_WIRING_KEY[layerCheck.provider];
1970
+ if (wiringKey) {
1971
+ return missionByKey.get(wiringKey) ?? graph.byProvider[wiringKey];
1972
+ }
1973
+ if (layerCheck.provider === "github") {
1974
+ return missionByKey.get("vitest-testing") ?? missionByKey.get("playwright-testing") ?? ensureGitHubMission(graph, missionByKey, layerCheck.area);
1975
+ }
1976
+ return void 0;
1977
+ }
1978
+ function ensureGitHubMission(graph, missionByKey, area) {
1979
+ const key = "vitest-testing";
1980
+ const existing = missionByKey.get(key);
1981
+ if (existing) {
1982
+ return existing;
1983
+ }
1984
+ const mission = {
1985
+ key,
1986
+ provider: "vitest",
1987
+ providerLabel: "GitHub Actions",
1988
+ area,
1989
+ promptSubject: "GitHub Actions CI",
1990
+ readinessPercent: 0,
1991
+ repoReadinessPercent: 100,
1992
+ providerReadinessPercent: 0,
1993
+ checks: []
1994
+ };
1995
+ missionByKey.set(key, mission);
1996
+ const areaEntry = graph.areas.find((entry) => entry.key === area);
1997
+ if (areaEntry) {
1998
+ areaEntry.providerMissions.push(mission);
1999
+ }
2000
+ return mission;
2001
+ }
2002
+ function missionRowId(verificationCheckId) {
2003
+ return `vl-${verificationCheckId}`;
2004
+ }
2005
+ function verificationCheckToMissionCheck(layerCheck, mission) {
2006
+ const evidenceClass = legacyEvidenceClassFor(layerCheck);
2007
+ const status = mapVerificationStatusToMissionStatus(layerCheck.status);
2008
+ return {
2009
+ id: missionRowId(layerCheck.id),
2010
+ label: layerCheck.title,
2011
+ providerKey: mission.key,
2012
+ providerLabel: mission.providerLabel,
2013
+ area: layerCheck.area,
2014
+ evidenceClass,
2015
+ status,
2016
+ evidence: [...layerCheck.repoSignals, ...layerCheck.providerSignals, ...layerCheck.evidenceRefs],
2017
+ promptHint: layerCheck.manualAction ?? layerCheck.description,
2018
+ evidenceSource: layerCheck.evidenceSource,
2019
+ verificationStatus: layerCheck.status,
2020
+ verificationCheckId: layerCheck.id
2021
+ };
2022
+ }
2023
+ function legacyEvidenceClassFor(layerCheck) {
2024
+ return mapEvidenceSourceToLegacyEvidenceClass(layerCheck.evidenceSource, layerCheck.status);
2025
+ }
2026
+ function recomputeMissionReadiness(missions) {
2027
+ for (const mission of missions) {
2028
+ mission.repoReadinessPercent = readinessPercentForRepoChecks(mission.checks);
2029
+ mission.providerReadinessPercent = readinessPercentForProviderChecks(mission.checks);
2030
+ mission.readinessPercent = mission.repoReadinessPercent;
2031
+ }
2032
+ }
2033
+ function readinessPercentForRepoChecks(checks) {
2034
+ const repoChecks = checks.filter(isRepoLayerMissionCheck);
2035
+ if (repoChecks.length === 0) {
2036
+ return 100;
2037
+ }
2038
+ const verified = repoChecks.filter((check) => isVerifiedMissionCheck(check)).length;
2039
+ return Math.round(verified / repoChecks.length * 100);
2040
+ }
2041
+ function readinessPercentForProviderChecks(checks) {
2042
+ const providerChecks = checks.filter(isProviderLayerMissionCheck);
2043
+ if (providerChecks.length === 0) {
2044
+ return 100;
2045
+ }
2046
+ const verified = providerChecks.filter((check) => isVerifiedMissionCheck(check)).length;
2047
+ return Math.round(verified / providerChecks.length * 100);
2048
+ }
2049
+ function isRepoLayerMissionCheck(check) {
2050
+ if (check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.evidenceSource === "manual") {
2051
+ return false;
2052
+ }
2053
+ if (check.evidenceSource === "repo") {
2054
+ return true;
2055
+ }
2056
+ return check.evidenceClass === "repo-verified" || check.evidenceClass === "missing-repo-fix";
2057
+ }
2058
+ function isProviderLayerMissionCheck(check) {
2059
+ if (check.evidenceSource === "provider" || check.evidenceSource === "mcp") {
2060
+ return true;
2061
+ }
2062
+ return check.evidenceClass === "mcp-verifier";
2063
+ }
2064
+ function isVerifiedMissionCheck(check) {
2065
+ if (check.verificationStatus) {
2066
+ return check.verificationStatus === "verified";
2067
+ }
2068
+ return check.status === "passed" || check.status === "user-confirmed";
2069
+ }
2070
+ function rebuildAreas(providerMissions) {
2071
+ const byArea = /* @__PURE__ */ new Map();
2072
+ for (const mission of providerMissions) {
2073
+ const list = byArea.get(mission.area) ?? [];
2074
+ list.push(mission);
2075
+ byArea.set(mission.area, list);
2076
+ }
2077
+ const AREA_LABELS2 = {
2078
+ appFlow: "App Flow",
2079
+ frontend: "Frontend",
2080
+ backend: "Backend / API",
2081
+ database: "Database",
2082
+ auth: "Auth",
2083
+ payments: "Payments",
2084
+ deployment: "Deployment",
2085
+ monitoring: "Monitoring",
2086
+ security: "Security",
2087
+ testing: "Testing",
2088
+ landing: "Landing / Onboarding",
2089
+ errorHandling: "Error Handling"
2090
+ };
2091
+ return [...byArea.entries()].map(([key, missions]) => {
2092
+ const repoChecks = missions.flatMap((m2) => m2.checks).filter(isRepoLayerMissionCheck);
2093
+ const providerChecks = missions.flatMap((m2) => m2.checks).filter(isProviderLayerMissionCheck);
2094
+ const repoReadinessPercent = percentVerified(repoChecks);
2095
+ const providerReadinessPercent = percentVerified(providerChecks);
2096
+ const criticalCount = missions.flatMap((m2) => m2.checks).filter(
2097
+ (check) => isProviderLayerMissionCheck(check) && (check.verificationStatus === "missing" || check.status === "missing" || check.status === "failed")
2098
+ ).length;
2099
+ return {
2100
+ key,
2101
+ label: AREA_LABELS2[key],
2102
+ readinessPercent: repoReadinessPercent,
2103
+ repoReadinessPercent,
2104
+ providerReadinessPercent,
2105
+ criticalCount,
2106
+ providerMissions: missions
2107
+ };
2108
+ });
2109
+ }
2110
+ function percentVerified(checks) {
2111
+ if (checks.length === 0) {
2112
+ return 100;
2113
+ }
2114
+ const verified = checks.filter((check) => isVerifiedMissionCheck(check)).length;
2115
+ return Math.round(verified / checks.length * 100);
2116
+ }
2117
+
1776
2118
  // ../../src/station/missionGraph.ts
1777
2119
  var AREA_LABELS = {
1778
2120
  appFlow: "App Flow",
@@ -1802,13 +2144,14 @@ function buildMissionGraph(input) {
1802
2144
  acc[area.key] = area;
1803
2145
  return acc;
1804
2146
  }, {});
1805
- return {
2147
+ const graph = {
1806
2148
  areas,
1807
2149
  byArea,
1808
2150
  byProvider,
1809
2151
  repositoryEvidence: input.repositoryEvidence,
1810
2152
  ...input.staticInfrastructureFlowGraph ? { staticInfrastructureFlowGraph: input.staticInfrastructureFlowGraph } : {}
1811
2153
  };
2154
+ return input.verificationLayer ? mergeVerificationIntoMissionGraph(graph, input.verificationLayer) : graph;
1812
2155
  }
1813
2156
  function toProviderMission(summary) {
1814
2157
  const checks = summary.items.map((item3) => toMissionCheck(summary, item3));
@@ -1832,6 +2175,8 @@ function toProviderMission(summary) {
1832
2175
  area: summary.area,
1833
2176
  promptSubject: summary.promptSubject,
1834
2177
  readinessPercent: summary.readinessPercent,
2178
+ repoReadinessPercent: summary.readinessPercent,
2179
+ providerReadinessPercent: checks.some((check) => check.evidenceClass === "mcp-verifier") ? 0 : 100,
1835
2180
  checks
1836
2181
  };
1837
2182
  }
@@ -1969,6 +2314,10 @@ function buildAreas(providerMissions) {
1969
2314
  key,
1970
2315
  label: AREA_LABELS[key],
1971
2316
  readinessPercent: Math.round(passed / Math.max(actionableChecks.length, 1) * 100),
2317
+ repoReadinessPercent: Math.round(passed / Math.max(actionableChecks.length, 1) * 100),
2318
+ providerReadinessPercent: missions.some(
2319
+ (mission) => mission.checks.some((check) => check.evidenceClass === "mcp-verifier")
2320
+ ) ? 0 : 100,
1972
2321
  criticalCount: missing,
1973
2322
  providerMissions: missions
1974
2323
  };
@@ -2130,6 +2479,17 @@ var PROVIDER_RULES = [
2130
2479
  imports: ["@supabase/supabase-js", "@supabase/ssr"],
2131
2480
  docs: ["supabase"]
2132
2481
  },
2482
+ {
2483
+ area: "database",
2484
+ provider: "firebase",
2485
+ label: "Firebase",
2486
+ packages: [/^firebase$/, /^firebase-admin$/, /@firebase\//],
2487
+ env: ["FIREBASE_PROJECT_ID", "NEXT_PUBLIC_FIREBASE_PROJECT_ID", "VITE_FIREBASE_PROJECT_ID", "GOOGLE_APPLICATION_CREDENTIALS"],
2488
+ paths: [/firebase/, /firestore/],
2489
+ imports: ["firebase/app", "firebase/firestore", "firebase-admin"],
2490
+ content: [{ pattern: /getFirestore\s*\(|collection\s*\(|firebase-admin/i, signal: "code: Firebase/Firestore usage" }],
2491
+ docs: ["firebase", "firestore"]
2492
+ },
2133
2493
  {
2134
2494
  area: "database",
2135
2495
  provider: "neon",
@@ -2182,10 +2542,33 @@ var PROVIDER_RULES = [
2182
2542
  label: "Auth.js",
2183
2543
  packages: [/^next-auth$/, /^@auth\//],
2184
2544
  env: ["AUTH_SECRET", "NEXTAUTH_SECRET", "NEXTAUTH_URL"],
2185
- paths: [/(^|\/)auth\.[jt]s$/, /api\/auth\//],
2545
+ paths: [/api\/auth\//],
2186
2546
  imports: ["next-auth", "@auth/core", "@auth/nextjs"],
2547
+ content: [{ pattern: /NextAuth\s*\(|getServerSession|useSession\s*\(/i, signal: "code: Auth.js session or route handler" }],
2187
2548
  docs: ["auth.js", "nextauth", "next-auth"]
2188
2549
  },
2550
+ {
2551
+ area: "auth",
2552
+ provider: "auth0",
2553
+ label: "Auth0",
2554
+ packages: [/@auth0\//],
2555
+ env: ["AUTH0_SECRET", "AUTH0_ISSUER_BASE_URL", "AUTH0_CLIENT_ID", "AUTH0_CLIENT_SECRET", "AUTH0_DOMAIN"],
2556
+ paths: [/api\/auth/, /auth0/],
2557
+ imports: ["@auth0/nextjs-auth0", "@auth0/auth0-react", "express-openid-connect"],
2558
+ content: [{ pattern: /handleAuth|withPageAuthRequired|withApiAuthRequired|getSession/i, signal: "code: Auth0 session or route protection" }],
2559
+ docs: ["auth0"]
2560
+ },
2561
+ {
2562
+ area: "auth",
2563
+ provider: "better-auth",
2564
+ label: "Better Auth",
2565
+ packages: [/^better-auth$/],
2566
+ env: ["BETTER_AUTH_SECRET", "BETTER_AUTH_URL", "AUTH_SECRET", "DATABASE_URL"],
2567
+ paths: [/better-auth/, /auth\.[jt]s$/],
2568
+ imports: ["better-auth"],
2569
+ content: [{ pattern: /betterAuth\s*\(|auth\.api|auth\.handler/i, signal: "code: Better Auth config" }],
2570
+ docs: ["better auth", "better-auth"]
2571
+ },
2189
2572
  {
2190
2573
  area: "payments",
2191
2574
  provider: "stripe",
@@ -2206,6 +2589,28 @@ var PROVIDER_RULES = [
2206
2589
  imports: ["@paddle/paddle-js", "@paddle/paddle-node-sdk"],
2207
2590
  docs: ["paddle"]
2208
2591
  },
2592
+ {
2593
+ area: "payments",
2594
+ provider: "polar",
2595
+ label: "Polar",
2596
+ packages: [/@polar-sh\//],
2597
+ env: ["POLAR_ACCESS_TOKEN", "POLAR_WEBHOOK_SECRET", "POLAR_PRODUCT_ID", "POLAR_PRO_PRODUCT_ID"],
2598
+ paths: [/polar.*webhook/, /webhook.*polar/, /polar.*checkout/, /checkout.*polar/],
2599
+ imports: ["@polar-sh/sdk"],
2600
+ content: [{ pattern: /api\.polar\.sh|polar.*checkout|createCheckoutSession/i, signal: "code: Polar checkout or API usage" }],
2601
+ docs: ["polar"]
2602
+ },
2603
+ {
2604
+ area: "payments",
2605
+ provider: "lemon-squeezy",
2606
+ label: "Lemon Squeezy",
2607
+ packages: [/@lemonsqueezy\//, /^lemonsqueezy\.ts$/],
2608
+ env: ["LEMON_SQUEEZY_API_KEY", "LEMONSQUEEZY_API_KEY", "LEMON_SQUEEZY_WEBHOOK_SECRET", "LEMON_SQUEEZY_STORE_ID", "LEMON_SQUEEZY_VARIANT_ID"],
2609
+ paths: [/lemon.*webhook/, /webhook.*lemon/, /lemon.*checkout/, /checkout.*lemon/, /lemonsqueezy/],
2610
+ imports: ["@lemonsqueezy/lemonsqueezy.js", "lemonsqueezy.ts"],
2611
+ content: [{ pattern: /api\.lemonsqueezy\.com|lemon.*checkout|checkout_url|variant_id/i, signal: "code: Lemon Squeezy checkout or API usage" }],
2612
+ docs: ["lemon squeezy", "lemonsqueezy"]
2613
+ },
2209
2614
  {
2210
2615
  area: "deployment",
2211
2616
  provider: "vercel",
@@ -2215,6 +2620,55 @@ var PROVIDER_RULES = [
2215
2620
  paths: [/vercel\.json$/],
2216
2621
  docs: ["vercel"]
2217
2622
  },
2623
+ {
2624
+ area: "deployment",
2625
+ provider: "netlify",
2626
+ label: "Netlify",
2627
+ packages: [/@netlify\//],
2628
+ env: ["NETLIFY_AUTH_TOKEN", "NETLIFY_SITE_ID"],
2629
+ paths: [/netlify\.toml$/, /\.netlify\//, /(^|\/)_redirects$/],
2630
+ imports: ["@netlify/functions"],
2631
+ docs: ["netlify"]
2632
+ },
2633
+ {
2634
+ area: "deployment",
2635
+ provider: "render",
2636
+ label: "Render",
2637
+ env: ["RENDER_API_KEY", "RENDER_SERVICE_ID"],
2638
+ paths: [/render\.ya?ml$/, /(^|\/)\.render\//],
2639
+ content: [{ pattern: /render\.yaml|render\.yml|render deploy|render service/i, signal: "code: Render deployment config" }],
2640
+ docs: ["render.com", "render deployment"]
2641
+ },
2642
+ {
2643
+ area: "deployment",
2644
+ provider: "railway",
2645
+ label: "Railway",
2646
+ env: ["RAILWAY_TOKEN", "RAILWAY_PROJECT_ID", "RAILWAY_SERVICE_ID"],
2647
+ paths: [/railway\.json$/, /nixpacks\.toml$/, /(^|\/)Procfile$/],
2648
+ content: [{ pattern: /railway up|railway deploy|nixpacks|railway\.json/i, signal: "code: Railway deployment config" }],
2649
+ docs: ["railway"]
2650
+ },
2651
+ {
2652
+ area: "deployment",
2653
+ provider: "cloudflare",
2654
+ label: "Cloudflare",
2655
+ packages: [/^wrangler$/, /@cloudflare\//],
2656
+ env: ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID", "CF_PAGES"],
2657
+ paths: [/wrangler\.toml$/, /wrangler\.json$/, /(^|\/)_headers$/, /(^|\/)_redirects$/],
2658
+ imports: ["@cloudflare/workers-types"],
2659
+ content: [{ pattern: /compatibility_date|pages_build_output_dir|cloudflare pages|cloudflare workers/i, signal: "code: Cloudflare Pages/Workers config" }],
2660
+ docs: ["cloudflare", "workers", "pages"]
2661
+ },
2662
+ {
2663
+ area: "deployment",
2664
+ provider: "aws",
2665
+ label: "AWS",
2666
+ packages: [/@aws-sdk\//, /^aws-cdk-lib$/, /^serverless$/],
2667
+ env: ["AWS_REGION", "AWS_ACCESS_KEY_ID"],
2668
+ paths: [/serverless\.ya?ml$/, /template\.ya?ml$/, /cdk\.json$/, /amplify\//],
2669
+ imports: ["aws-sdk", "@aws-sdk/client"],
2670
+ docs: ["aws", "amplify", "serverless"]
2671
+ },
2218
2672
  {
2219
2673
  area: "monitoring",
2220
2674
  provider: "sentry",
@@ -3894,8 +4348,11 @@ function analyzeStackWiring(scan) {
3894
4348
  analyzeSecretsHygiene(ctx),
3895
4349
  analyzeClerkAuth(ctx),
3896
4350
  analyzeAuthJsAuth(ctx),
4351
+ analyzeAuth0Auth(ctx),
4352
+ analyzeBetterAuth(ctx),
3897
4353
  analyzeSupabaseAuth(ctx),
3898
4354
  supabaseDatabase,
4355
+ analyzeFirebaseDatabase(ctx),
3899
4356
  analyzeNeonDatabase(ctx),
3900
4357
  analyzeTursoDatabase(ctx),
3901
4358
  analyzeMongoDatabase(ctx),
@@ -3903,8 +4360,12 @@ function analyzeStackWiring(scan) {
3903
4360
  analyzeStripePayments(ctx),
3904
4361
  analyzePaddlePayments(ctx),
3905
4362
  analyzePolarPayments(ctx),
4363
+ analyzeLemonSqueezyPayments(ctx),
3906
4364
  analyzeVercelDeployment(ctx),
3907
4365
  analyzeNetlifyDeployment(ctx),
4366
+ analyzeRenderDeployment(ctx),
4367
+ analyzeRailwayDeployment(ctx),
4368
+ analyzeCloudflareDeployment(ctx),
3908
4369
  analyzeAwsDeployment(ctx),
3909
4370
  analyzeSupabaseLanding(ctx),
3910
4371
  analyzePostHogMonitoring(ctx),
@@ -4175,6 +4636,44 @@ function analyzeAuthJsAuth(ctx) {
4175
4636
  ]
4176
4637
  });
4177
4638
  }
4639
+ function analyzeAuth0Auth(ctx) {
4640
+ return summarize({
4641
+ key: "auth0-auth",
4642
+ provider: "auth0",
4643
+ providerLabel: "Auth0",
4644
+ area: "auth",
4645
+ areaLabel: "Auth",
4646
+ promptSubject: "Auth0 auth",
4647
+ items: [
4648
+ item2("package-installed", "Auth0 package installed", hasPackage2(ctx, [/@auth0\//]), packageEvidence2(ctx, [/@auth0\//]), "Install the Auth0 package that matches this framework."),
4649
+ 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."),
4650
+ 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."),
4651
+ 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."),
4652
+ 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."),
4653
+ 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."),
4654
+ manualItem("production-dashboard-checked", "Production Auth0 app checked", "Confirm callback URLs, logout URLs, allowed origins, social connections, MFA, and production domain in Auth0.")
4655
+ ]
4656
+ });
4657
+ }
4658
+ function analyzeBetterAuth(ctx) {
4659
+ return summarize({
4660
+ key: "better-auth-auth",
4661
+ provider: "better-auth",
4662
+ providerLabel: "Better Auth",
4663
+ area: "auth",
4664
+ areaLabel: "Auth",
4665
+ promptSubject: "Better Auth",
4666
+ items: [
4667
+ item2("package-installed", "Better Auth package installed", hasPackage2(ctx, [/^better-auth$/]), packageEvidence2(ctx, [/^better-auth$/]), "Install Better Auth for this app framework."),
4668
+ 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."),
4669
+ 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."),
4670
+ 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."),
4671
+ 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."),
4672
+ 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."),
4673
+ manualItem("production-auth-checked", "Production auth settings checked", "Confirm production base URL, trusted origins, OAuth apps, email settings, and session policy.")
4674
+ ]
4675
+ });
4676
+ }
4178
4677
  function analyzeSupabaseAuth(ctx) {
4179
4678
  return summarize({
4180
4679
  key: "supabase-auth",
@@ -4252,6 +4751,38 @@ function analyzePolarPayments(ctx) {
4252
4751
  ]
4253
4752
  });
4254
4753
  }
4754
+ function analyzeLemonSqueezyPayments(ctx) {
4755
+ return summarize({
4756
+ key: "lemon-squeezy-payments",
4757
+ provider: "lemon-squeezy",
4758
+ providerLabel: "Lemon Squeezy",
4759
+ area: "payments",
4760
+ areaLabel: "Payments",
4761
+ promptSubject: "Lemon Squeezy payments",
4762
+ items: [
4763
+ 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."),
4764
+ 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."),
4765
+ 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."),
4766
+ 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."),
4767
+ 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."),
4768
+ 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."),
4769
+ 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.")
4770
+ ]
4771
+ });
4772
+ }
4773
+ function analyzeFirebaseDatabase(ctx) {
4774
+ return databaseSummary(ctx, {
4775
+ key: "firebase-database",
4776
+ provider: "firebase",
4777
+ providerLabel: "Firebase",
4778
+ promptSubject: "Firebase database",
4779
+ packagePatterns: [/^firebase$/, /^firebase-admin$/, /@firebase\//],
4780
+ envPatterns: [/firebase_project_id/i, /firestore_database_url/i, /next_public_firebase/i, /vite_firebase/i, /google_application_credentials/i],
4781
+ usagePatterns: [/firebase\/firestore/i, /getfirestore\s*\(|collection\s*\(|doc\s*\(|firebase-admin/i],
4782
+ manualLabel: "Production Firebase project checked",
4783
+ manualHint: "Confirm production project, Firestore rules, indexes, backups, service account scope, and billing limits in Firebase."
4784
+ });
4785
+ }
4255
4786
  function analyzeNeonDatabase(ctx) {
4256
4787
  return databaseSummary(ctx, {
4257
4788
  key: "neon-database",
@@ -4344,6 +4875,72 @@ function analyzeNetlifyDeployment(ctx) {
4344
4875
  ]
4345
4876
  });
4346
4877
  }
4878
+ function analyzeRenderDeployment(ctx) {
4879
+ const base = deploymentSummary(ctx, {
4880
+ key: "render-deployment",
4881
+ provider: "render",
4882
+ providerLabel: "Render",
4883
+ promptSubject: "Render deployment",
4884
+ packagePatterns: [],
4885
+ configPatterns: [/render\.ya?ml/i, /(^|\n|\/)\.render\//i],
4886
+ envPatterns: [/render_api_key/i, /render_service_id/i],
4887
+ manualLabel: "Production Render service checked",
4888
+ manualHint: "Confirm service type, build/start commands, env groups, health checks, custom domain, autoscaling, and rollback strategy in Render."
4889
+ });
4890
+ const servicePattern = /render\.ya?ml|healthcheckpath|startcommand|buildcommand|envvars|services:\s*|type:\s*web/i;
4891
+ return summarize({
4892
+ ...base,
4893
+ items: [
4894
+ ...base.items.filter((entry) => entry.status !== "manual"),
4895
+ 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."),
4896
+ ...base.items.filter((entry) => entry.status === "manual")
4897
+ ]
4898
+ });
4899
+ }
4900
+ function analyzeRailwayDeployment(ctx) {
4901
+ const base = deploymentSummary(ctx, {
4902
+ key: "railway-deployment",
4903
+ provider: "railway",
4904
+ providerLabel: "Railway",
4905
+ promptSubject: "Railway deployment",
4906
+ packagePatterns: [],
4907
+ configPatterns: [/railway\.json/i, /nixpacks\.toml/i, /(^|\n|\/)Procfile\b/i],
4908
+ envPatterns: [/railway_token/i, /railway_project_id/i, /railway_service_id/i],
4909
+ manualLabel: "Production Railway service checked",
4910
+ manualHint: "Confirm service, variables, start command, volumes/databases, custom domain, deploy policy, and logs in Railway."
4911
+ });
4912
+ const servicePattern = /railway\.json|nixpacks\.toml|railway up|railway deploy|Procfile|startCommand|healthcheck/i;
4913
+ return summarize({
4914
+ ...base,
4915
+ items: [
4916
+ ...base.items.filter((entry) => entry.status !== "manual"),
4917
+ 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."),
4918
+ ...base.items.filter((entry) => entry.status === "manual")
4919
+ ]
4920
+ });
4921
+ }
4922
+ function analyzeCloudflareDeployment(ctx) {
4923
+ const base = deploymentSummary(ctx, {
4924
+ key: "cloudflare-deployment",
4925
+ provider: "cloudflare",
4926
+ providerLabel: "Cloudflare",
4927
+ promptSubject: "Cloudflare deployment",
4928
+ packagePatterns: [/^wrangler$/, /@cloudflare\//],
4929
+ configPatterns: [/wrangler\.toml/i, /wrangler\.json/i, /_headers\b/i, /_redirects\b/i],
4930
+ envPatterns: [/cloudflare_api_token/i, /cloudflare_account_id/i, /cf_pages/i],
4931
+ manualLabel: "Production Cloudflare project checked",
4932
+ manualHint: "Confirm Pages/Workers project, DNS, routes, compatibility date, env vars/secrets, cache rules, and rollback settings in Cloudflare."
4933
+ });
4934
+ const edgePattern = /wrangler\.toml|wrangler\.json|compatibility_date|pages_build_output_dir|workers_dev|routes?\s*=|cloudflare pages|cloudflare workers/i;
4935
+ return summarize({
4936
+ ...base,
4937
+ items: [
4938
+ ...base.items.filter((entry) => entry.status !== "manual"),
4939
+ 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."),
4940
+ ...base.items.filter((entry) => entry.status === "manual")
4941
+ ]
4942
+ });
4943
+ }
4347
4944
  function analyzeAwsDeployment(ctx) {
4348
4945
  const base = deploymentSummary(ctx, {
4349
4946
  key: "aws-deployment",
@@ -5189,48 +5786,758 @@ function providerLabel2(provider2) {
5189
5786
  );
5190
5787
  }
5191
5788
 
5192
- // ../../src/station/orchestrator.ts
5193
- function isScanLimitResult(value) {
5194
- return value.kind === "scan_limit_reached";
5789
+ // ../../src/station/verificationLayer/shared/connection.ts
5790
+ var MCP_PROVIDER_MAP = {
5791
+ vercel: "vercel",
5792
+ supabase: "supabase",
5793
+ stripe: "stripe",
5794
+ github: "github"
5795
+ };
5796
+ function resolveProviderConnectionState(provider2, mcpVerifierState) {
5797
+ const registryProvider = MCP_PROVIDER_MAP[provider2];
5798
+ if (!registryProvider || !mcpVerifierState) {
5799
+ return "unknown_runtime";
5800
+ }
5801
+ const record = mcpVerifierState.records.find((entry) => entry.provider === registryProvider);
5802
+ if (!record) {
5803
+ return "unknown_runtime";
5804
+ }
5805
+ switch (record.status) {
5806
+ case "configured":
5807
+ return "configured";
5808
+ case "missing":
5809
+ return "not_configured";
5810
+ case "unsupported":
5811
+ return "unsupported";
5812
+ case "stale":
5813
+ return "configured";
5814
+ default:
5815
+ return "unknown_runtime";
5816
+ }
5195
5817
  }
5196
- function isManagedRequiredResult(value) {
5197
- return value.kind === "managed_required";
5818
+ function providerResultStatus(input) {
5819
+ if (input.connectionState === "connected") {
5820
+ return input.providerObservationMet ? "verified" : "missing";
5821
+ }
5822
+ if (!input.repoExpectationMet) {
5823
+ return "missing";
5824
+ }
5825
+ if (input.connectionState === "not_configured" || input.connectionState === "configured") {
5826
+ return "needs_mcp";
5827
+ }
5828
+ if (input.connectionState === "unsupported") {
5829
+ return "manual";
5830
+ }
5831
+ return "unknown";
5198
5832
  }
5199
- function isManagedSessionInvalidResult(value) {
5200
- return value.kind === "managed_session_invalid";
5833
+
5834
+ // ../../src/station/verificationLayer/shared/buildCheck.ts
5835
+ function buildVerificationCheck(input) {
5836
+ const status = input.evidenceSource === "repo" ? input.repoExpectationMet ? "verified" : "missing" : providerResultStatus({
5837
+ connectionState: input.connectionState,
5838
+ repoExpectationMet: input.repoExpectationMet,
5839
+ providerObservationMet: input.providerObservationMet
5840
+ });
5841
+ return {
5842
+ id: providerCheckId(input.provider, input.checkKey),
5843
+ provider: input.provider,
5844
+ area: input.area,
5845
+ title: input.title,
5846
+ description: input.description,
5847
+ requiredEvidence: input.requiredEvidence ?? [],
5848
+ repoSignals: input.repoSignals,
5849
+ providerSignals: input.providerSignals,
5850
+ evidenceSource: input.evidenceSource,
5851
+ status,
5852
+ fixType: input.fixType,
5853
+ severity: input.severity,
5854
+ evidenceRefs: input.evidenceRefs ?? [],
5855
+ manualAction: input.manualAction
5856
+ };
5201
5857
  }
5202
- function createStationOrchestrator(deps) {
5858
+
5859
+ // ../../src/station/verificationLayer/shared/repoSignals.ts
5860
+ var ENV_NAME_PATTERN = /\b(?:process\.env|import\.meta\.env)\.([A-Z][A-Z0-9_]{2,})\b/g;
5861
+ var ENV_ASSIGNMENT_PATTERN = /^[ \t]*(?:export[ \t]+)?([A-Z][A-Z0-9_]{2,})[ \t]*=/gm;
5862
+ var SUPABASE_TABLE_PATTERN = /\.from\s*\(\s*['"]([a-z0-9_]+)['"]\s*\)/gi;
5863
+ var STRIPE_WEBHOOK_PATH_PATTERN = /(^|\/)(api\/webhooks\/stripe|api\/stripe\/webhook|webhooks\/stripe)[^/\s]*/i;
5864
+ var GITHUB_WORKFLOW_PATTERN = /(^|\/)\.github\/workflows\/([^/\n]+\.ya?ml)/i;
5865
+ function buildRepoScanContext(scan) {
5866
+ const files = visibleFiles4(scan);
5203
5867
  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,
5868
+ files,
5869
+ pathBlob: `${scan.fileTree}
5870
+ ${scan.files.map((file) => file.path).join("\n")}`.replace(/\\/g, "/"),
5871
+ contentBlob: files.map((file) => file.lowerContent).join("\n"),
5872
+ deps: scan.packageDeps.map((dep) => dep.toLowerCase())
5873
+ };
5874
+ }
5875
+ function visibleFiles4(scan) {
5876
+ return scan.files.filter((file) => !file.isSecret && typeof file.content === "string").map((file) => ({
5877
+ path: file.path.replace(/\\/g, "/"),
5878
+ normalizedPath: file.path.replace(/\\/g, "/").toLowerCase(),
5879
+ content: file.content,
5880
+ lowerContent: file.content.toLowerCase()
5881
+ }));
5882
+ }
5883
+ function collectReferencedEnvNames(scan, repositoryEvidence) {
5884
+ const names = /* @__PURE__ */ new Set();
5885
+ for (const entry of repositoryEvidence.env) {
5886
+ if (entry.present || entry.evidence.length > 0) {
5887
+ names.add(entry.name);
5888
+ }
5889
+ }
5890
+ for (const file of visibleFiles4(scan)) {
5891
+ if (!/(^|\/)\.env(\.|$)|env\.example|readme/i.test(file.normalizedPath)) {
5892
+ ENV_NAME_PATTERN.lastIndex = 0;
5893
+ let match;
5894
+ while ((match = ENV_NAME_PATTERN.exec(file.content)) !== null) {
5895
+ names.add(match[1]);
5896
+ }
5897
+ }
5898
+ if (/\.env\.example|env\.example/i.test(file.normalizedPath)) {
5899
+ ENV_ASSIGNMENT_PATTERN.lastIndex = 0;
5900
+ let assignment;
5901
+ while ((assignment = ENV_ASSIGNMENT_PATTERN.exec(file.content)) !== null) {
5902
+ names.add(assignment[1]);
5903
+ }
5904
+ }
5905
+ }
5906
+ const supplemental = collectEnvVarEvidence(scan, [...names]);
5907
+ for (const entry of supplemental) {
5908
+ if (entry.present || entry.evidence.length > 0) {
5909
+ names.add(entry.name);
5910
+ }
5911
+ }
5912
+ return [...names].sort();
5913
+ }
5914
+ function collectEnvExampleNames(scan) {
5915
+ const names = /* @__PURE__ */ new Set();
5916
+ for (const file of visibleFiles4(scan)) {
5917
+ if (!/\.env\.example|env\.example/i.test(file.normalizedPath)) {
5918
+ continue;
5919
+ }
5920
+ ENV_ASSIGNMENT_PATTERN.lastIndex = 0;
5921
+ let assignment;
5922
+ while ((assignment = ENV_ASSIGNMENT_PATTERN.exec(file.content)) !== null) {
5923
+ names.add(assignment[1]);
5924
+ }
5925
+ }
5926
+ return [...names].sort();
5927
+ }
5928
+ function hasRlsMigrationEvidence(repo) {
5929
+ return /\/policies\/|_rls\.sql|\brls\b/i.test(repo.pathBlob) || repo.files.some(
5930
+ (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(
5931
+ file.content
5932
+ )
5933
+ );
5934
+ }
5935
+ function collectSupabaseReferencedTables(repo) {
5936
+ const tables = /* @__PURE__ */ new Set();
5937
+ for (const file of repo.files) {
5938
+ SUPABASE_TABLE_PATTERN.lastIndex = 0;
5939
+ let match;
5940
+ while ((match = SUPABASE_TABLE_PATTERN.exec(file.content)) !== null) {
5941
+ tables.add(match[1]);
5942
+ }
5943
+ }
5944
+ return [...tables].sort();
5945
+ }
5946
+ function findStripeWebhookRoute(repo) {
5947
+ const pathMatch = repo.pathBlob.match(STRIPE_WEBHOOK_PATH_PATTERN);
5948
+ if (pathMatch) {
5949
+ return pathMatch[0].replace(/^\//, "");
5950
+ }
5951
+ const file = repo.files.find((entry) => /stripe.*webhook|webhook.*stripe/i.test(entry.normalizedPath));
5952
+ return file ? file.path : null;
5953
+ }
5954
+ function hasStripeWebhookSignature(repo) {
5955
+ return /webhooks\.constructevent/i.test(repo.contentBlob);
5956
+ }
5957
+ function collectStripePriceEnvNames(repo, referencedEnv) {
5958
+ return referencedEnv.filter((name) => /^STRIPE_(PRICE_|PRODUCT_)/i.test(name) || /STRIPE.*PRICE/i.test(name));
5959
+ }
5960
+ function findGithubWorkflowPaths(repo) {
5961
+ const paths = /* @__PURE__ */ new Set();
5962
+ for (const line of repo.pathBlob.split(/\r?\n/)) {
5963
+ const match = line.match(GITHUB_WORKFLOW_PATTERN);
5964
+ if (match) {
5965
+ paths.add(line.trim());
5966
+ }
5967
+ }
5968
+ return [...paths].sort();
5969
+ }
5970
+
5971
+ // ../../src/station/verificationLayer/mock/mockGitHubVerifier.ts
5972
+ var mockGitHubVerifier = {
5973
+ provider: "github",
5974
+ detectConfig(ctx) {
5975
+ const repo = buildRepoScanContext(ctx.scan);
5976
+ const workflows = findGithubWorkflowPaths(repo);
5977
+ const signals = [];
5978
+ if (ctx.scan.stackSignals.hasCI) {
5979
+ signals.push("stack: hasCI");
5980
+ }
5981
+ for (const workflow of workflows) {
5982
+ signals.push(`workflow: ${workflow}`);
5983
+ }
5984
+ const detected = workflows.length > 0 || Boolean(ctx.scan.stackSignals.hasCI);
5985
+ return { provider: "github", detected, signals };
5986
+ },
5987
+ connectStatus(ctx) {
5988
+ return resolveProviderConnectionState("github", ctx.mcpVerifierState);
5989
+ },
5990
+ runChecks(ctx) {
5991
+ const connectionState = this.connectStatus(ctx);
5992
+ const repo = buildRepoScanContext(ctx.scan);
5993
+ const workflows = findGithubWorkflowPaths(repo);
5994
+ return [
5995
+ buildVerificationCheck({
5996
+ provider: "github",
5997
+ checkKey: "actions-run-status",
5998
+ area: "testing",
5999
+ title: "GitHub Actions run status",
6000
+ description: "Recent workflow run conclusions require live GitHub API or MCP access.",
6001
+ evidenceSource: "provider",
6002
+ connectionState,
6003
+ repoExpectationMet: workflows.length > 0,
6004
+ providerObservationMet: false,
6005
+ fixType: "mcp-connect",
6006
+ severity: "warning",
6007
+ repoSignals: workflows.map((path) => `workflow: ${path}`),
6008
+ providerSignals: ["GitHub Actions API or MCP"],
6009
+ requiredEvidence: ["Latest workflow run status on default branch"]
6010
+ }),
6011
+ buildVerificationCheck({
6012
+ provider: "github",
6013
+ checkKey: "required-checks",
6014
+ area: "testing",
6015
+ title: "Required GitHub checks",
6016
+ description: "Branch protection and required check contexts need live GitHub verification.",
6017
+ evidenceSource: "provider",
6018
+ connectionState,
6019
+ repoExpectationMet: workflows.length > 0,
6020
+ providerObservationMet: false,
6021
+ fixType: "mcp-connect",
6022
+ severity: "warning",
6023
+ repoSignals: workflows,
6024
+ providerSignals: ["GitHub branch protection API or MCP"],
6025
+ requiredEvidence: ["Required status checks configured for production branch"]
6026
+ }),
6027
+ buildVerificationCheck({
6028
+ provider: "github",
6029
+ checkKey: "failed-checks-recent",
6030
+ area: "testing",
6031
+ title: "Recent failed GitHub checks",
6032
+ description: "Failed workflow runs in the last 7 days require live GitHub verification.",
6033
+ evidenceSource: "provider",
6034
+ connectionState,
6035
+ repoExpectationMet: workflows.length > 0,
6036
+ providerObservationMet: false,
6037
+ fixType: "mcp-connect",
6038
+ severity: "critical",
6039
+ repoSignals: workflows,
6040
+ providerSignals: ["GitHub check run API or MCP"],
6041
+ requiredEvidence: ["No failing required checks on latest default-branch commit"]
6042
+ })
6043
+ ];
6044
+ },
6045
+ buildDiffs(ctx) {
6046
+ const repo = buildRepoScanContext(ctx.scan);
6047
+ const workflows = findGithubWorkflowPaths(repo);
6048
+ if (workflows.length === 0) {
6049
+ return [];
6050
+ }
6051
+ return [
6052
+ {
6053
+ id: providerCheckId("github", "ci-status-unverified"),
6054
+ provider: "github",
6055
+ area: "testing",
6056
+ title: "CI status not verified",
6057
+ description: "GitHub workflow files exist in the repo, but recent Actions conclusions have not been fetched.",
6058
+ repoExpectation: `Workflows: ${workflows.join(", ")}`,
6059
+ providerActual: "Not verified (mock \u2014 connect GitHub MCP read-only)",
6060
+ severity: "warning",
6061
+ suggestedFix: "mcp-connect",
6062
+ evidenceRefs: workflows.map((path) => `workflow: ${path}`)
6063
+ }
6064
+ ];
6065
+ }
6066
+ };
6067
+
6068
+ // ../../src/station/verificationLayer/mock/mockStripeVerifier.ts
6069
+ var REQUIRED_STRIPE_EVENTS = [
6070
+ "checkout.session.completed",
6071
+ "customer.subscription.updated",
6072
+ "customer.subscription.deleted",
6073
+ "invoice.payment_failed"
6074
+ ];
6075
+ var MOCK_STRIPE_HAS_WEBHOOK_ENDPOINT = false;
6076
+ var MOCK_STRIPE_CONFIGURED_EVENTS = /* @__PURE__ */ new Set();
6077
+ var MOCK_STRIPE_LIVE_PRICE_IDS = /* @__PURE__ */ new Set();
6078
+ var mockStripeVerifier = {
6079
+ provider: "stripe",
6080
+ detectConfig(ctx) {
6081
+ const repo = buildRepoScanContext(ctx.scan);
6082
+ const signals = [];
6083
+ if (ctx.scan.stackSignals.hasStripe) {
6084
+ signals.push("stack: hasStripe");
6085
+ }
6086
+ if (repo.deps.some((dep) => dep === "stripe" || dep.startsWith("@stripe/"))) {
6087
+ signals.push("package: stripe");
6088
+ }
6089
+ if (findStripeWebhookRoute(repo)) {
6090
+ signals.push(`route: ${findStripeWebhookRoute(repo)}`);
6091
+ }
6092
+ const detected = signals.length > 0;
6093
+ return { provider: "stripe", detected, signals };
6094
+ },
6095
+ connectStatus(ctx) {
6096
+ return resolveProviderConnectionState("stripe", ctx.mcpVerifierState);
6097
+ },
6098
+ runChecks(ctx) {
6099
+ const connectionState = this.connectStatus(ctx);
6100
+ const repo = buildRepoScanContext(ctx.scan);
6101
+ const webhookRoute = findStripeWebhookRoute(repo);
6102
+ const referencedEnv = collectReferencedEnvNames(ctx.scan, ctx.repositoryEvidence);
6103
+ const priceEnvNames = collectStripePriceEnvNames(repo, referencedEnv);
6104
+ const missingEvents = REQUIRED_STRIPE_EVENTS.filter((event) => !MOCK_STRIPE_CONFIGURED_EVENTS.has(event));
6105
+ return [
6106
+ buildVerificationCheck({
6107
+ provider: "stripe",
6108
+ checkKey: "dashboard-webhook-endpoint",
6109
+ area: "payments",
6110
+ title: "Stripe dashboard webhook endpoint",
6111
+ 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.",
6112
+ evidenceSource: "provider",
6113
+ connectionState,
6114
+ repoExpectationMet: Boolean(webhookRoute),
6115
+ providerObservationMet: MOCK_STRIPE_HAS_WEBHOOK_ENDPOINT,
6116
+ fixType: "provider-config",
6117
+ severity: "critical",
6118
+ repoSignals: webhookRoute ? [`route: ${webhookRoute}`] : [],
6119
+ providerSignals: ["Stripe webhooks API or MCP"],
6120
+ requiredEvidence: ["Webhook endpoint URL registered in Stripe"],
6121
+ manualAction: "Register the production webhook URL in Stripe Dashboard \u2192 Developers \u2192 Webhooks."
6122
+ }),
6123
+ buildVerificationCheck({
6124
+ provider: "stripe",
6125
+ checkKey: "webhook-events",
6126
+ area: "payments",
6127
+ title: "Stripe webhook event subscriptions",
6128
+ description: missingEvents.length > 0 ? `Missing required events: ${missingEvents.join(", ")}` : "Required subscription lifecycle events are configured (mock).",
6129
+ evidenceSource: "provider",
6130
+ connectionState,
6131
+ repoExpectationMet: Boolean(webhookRoute && hasStripeWebhookSignature(repo)),
6132
+ providerObservationMet: missingEvents.length === 0,
6133
+ fixType: "provider-config",
6134
+ severity: "critical",
6135
+ repoSignals: hasStripeWebhookSignature(repo) ? ["webhooks.constructEvent in repo"] : [],
6136
+ providerSignals: ["Stripe webhook endpoint event list"],
6137
+ requiredEvidence: REQUIRED_STRIPE_EVENTS.map((event) => `event: ${event}`)
6138
+ }),
6139
+ buildVerificationCheck({
6140
+ provider: "stripe",
6141
+ checkKey: "live-price-ids",
6142
+ area: "payments",
6143
+ title: "Stripe live price IDs",
6144
+ 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.",
6145
+ evidenceSource: "provider",
6146
+ connectionState,
6147
+ repoExpectationMet: priceEnvNames.length > 0,
6148
+ providerObservationMet: priceEnvNames.length > 0 && priceEnvNames.every((name) => MOCK_STRIPE_LIVE_PRICE_IDS.has(name)),
6149
+ fixType: "provider-config",
6150
+ severity: priceEnvNames.length > 0 ? "warning" : "info",
6151
+ repoSignals: priceEnvNames.map((name) => `env: ${name}`),
6152
+ providerSignals: ["Stripe products/prices API or MCP"],
6153
+ requiredEvidence: ["Live price IDs match env configuration"]
6154
+ })
6155
+ ];
6156
+ },
6157
+ buildDiffs(ctx) {
6158
+ const repo = buildRepoScanContext(ctx.scan);
6159
+ const webhookRoute = findStripeWebhookRoute(repo);
6160
+ const diffs = [];
6161
+ if (webhookRoute && !MOCK_STRIPE_HAS_WEBHOOK_ENDPOINT) {
6162
+ diffs.push({
6163
+ id: providerCheckId("stripe", "webhook-endpoint-mismatch"),
6164
+ provider: "stripe",
6165
+ area: "payments",
6166
+ title: "Stripe webhook endpoint not registered",
6167
+ description: "The repo implements a webhook handler, but no matching endpoint is registered in the mock Stripe account.",
6168
+ repoExpectation: `Webhook route: ${webhookRoute}`,
6169
+ providerActual: "No Stripe webhook endpoint (mock empty dashboard)",
6170
+ severity: "critical",
6171
+ suggestedFix: "provider-config",
6172
+ evidenceRefs: [`route: ${webhookRoute}`]
6173
+ });
6174
+ }
6175
+ const referencedEnv = collectReferencedEnvNames(ctx.scan, ctx.repositoryEvidence);
6176
+ if (referencedEnv.includes("STRIPE_WEBHOOK_SECRET") && !MOCK_STRIPE_HAS_WEBHOOK_ENDPOINT) {
6177
+ diffs.push({
6178
+ id: providerCheckId("stripe", "webhook-secret-without-endpoint"),
6179
+ provider: "stripe",
6180
+ area: "payments",
6181
+ title: "STRIPE_WEBHOOK_SECRET without dashboard endpoint",
6182
+ description: "Repo expects STRIPE_WEBHOOK_SECRET but Stripe dashboard webhook endpoint is not confirmed.",
6183
+ repoExpectation: "STRIPE_WEBHOOK_SECRET referenced in repo",
6184
+ providerActual: "No webhook endpoint to deliver signed events",
6185
+ severity: "critical",
6186
+ suggestedFix: "provider-config",
6187
+ evidenceRefs: ["env: STRIPE_WEBHOOK_SECRET"]
6188
+ });
6189
+ }
6190
+ return diffs;
6191
+ }
6192
+ };
6193
+
6194
+ // ../../src/station/verificationLayer/mock/mockSupabaseVerifier.ts
6195
+ var mockSupabaseVerifier = {
6196
+ provider: "supabase",
6197
+ detectConfig(ctx) {
6198
+ const repo = buildRepoScanContext(ctx.scan);
6199
+ const signals = [];
6200
+ if (ctx.scan.stackSignals.hasSupabase) {
6201
+ signals.push("stack: hasSupabase");
6202
+ }
6203
+ if (repo.deps.some((dep) => dep.startsWith("@supabase/"))) {
6204
+ signals.push(`package: ${repo.deps.find((dep) => dep.startsWith("@supabase/"))}`);
6205
+ }
6206
+ if (/\/supabase\/migrations\//i.test(repo.pathBlob)) {
6207
+ signals.push("path: supabase/migrations");
6208
+ }
6209
+ const detected = signals.length > 0;
6210
+ return { provider: "supabase", detected, signals };
6211
+ },
6212
+ connectStatus(ctx) {
6213
+ return resolveProviderConnectionState("supabase", ctx.mcpVerifierState);
6214
+ },
6215
+ runChecks(ctx) {
6216
+ const connectionState = this.connectStatus(ctx);
6217
+ const repo = buildRepoScanContext(ctx.scan);
6218
+ const tables = collectSupabaseReferencedTables(repo);
6219
+ const rlsInRepo = hasRlsMigrationEvidence(repo);
6220
+ return [
6221
+ buildVerificationCheck({
6222
+ provider: "supabase",
6223
+ checkKey: "live-rls-enabled",
6224
+ area: "database",
6225
+ title: "Live Supabase RLS enabled",
6226
+ 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.",
6227
+ evidenceSource: "provider",
6228
+ connectionState,
6229
+ repoExpectationMet: rlsInRepo,
6230
+ providerObservationMet: false,
6231
+ fixType: rlsInRepo ? "mcp-connect" : "provider-config",
6232
+ severity: "critical",
6233
+ repoSignals: rlsInRepo ? ["migration/policy references RLS"] : ["no RLS migration detected"],
6234
+ providerSignals: ["Supabase project policy API or MCP"],
6235
+ requiredEvidence: ["RLS enabled on user-owned tables in live project"],
6236
+ manualAction: "Enable RLS and policies in Supabase Dashboard \u2192 Authentication \u2192 Policies."
6237
+ }),
6238
+ buildVerificationCheck({
6239
+ provider: "supabase",
6240
+ checkKey: "live-policies",
6241
+ area: "database",
6242
+ title: "Live Supabase policies",
6243
+ description: "Row policies on referenced tables require live Supabase verification.",
6244
+ evidenceSource: "provider",
6245
+ connectionState,
6246
+ repoExpectationMet: tables.length > 0,
6247
+ providerObservationMet: false,
6248
+ fixType: "mcp-connect",
6249
+ severity: tables.length > 0 ? "critical" : "info",
6250
+ repoSignals: tables.map((table) => `table referenced: ${table}`),
6251
+ providerSignals: ["Supabase policy list API or MCP"],
6252
+ requiredEvidence: ["Policies exist for tables used in application code"]
6253
+ }),
6254
+ buildVerificationCheck({
6255
+ provider: "supabase",
6256
+ checkKey: "referenced-tables-protected",
6257
+ area: "database",
6258
+ title: "Referenced tables protected in live project",
6259
+ 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.",
6260
+ evidenceSource: "provider",
6261
+ connectionState,
6262
+ repoExpectationMet: tables.length > 0 && rlsInRepo,
6263
+ providerObservationMet: false,
6264
+ fixType: "provider-config",
6265
+ severity: "warning",
6266
+ repoSignals: tables.map((table) => `.from("${table}")`),
6267
+ providerSignals: ["Live table policy map"],
6268
+ requiredEvidence: ["Each referenced public table has RLS + policy in production"]
6269
+ })
6270
+ ];
6271
+ },
6272
+ buildDiffs(ctx) {
6273
+ const repo = buildRepoScanContext(ctx.scan);
6274
+ const tables = collectSupabaseReferencedTables(repo);
6275
+ const rlsInRepo = hasRlsMigrationEvidence(repo);
6276
+ const diffs = [];
6277
+ if (!rlsInRepo && tables.length > 0) {
6278
+ diffs.push({
6279
+ id: providerCheckId("supabase", "rls-migration-gap"),
6280
+ provider: "supabase",
6281
+ area: "database",
6282
+ title: "RLS migration missing for referenced tables",
6283
+ description: "Code queries Supabase tables but no RLS migration/policy evidence was found in the repo.",
6284
+ repoExpectation: `Tables referenced: ${tables.join(", ")}`,
6285
+ providerActual: "Live RLS state unknown without Supabase MCP",
6286
+ severity: "critical",
6287
+ suggestedFix: "repo-fix",
6288
+ evidenceRefs: tables.map((table) => `table: ${table}`)
6289
+ });
6290
+ }
6291
+ if (rlsInRepo) {
6292
+ diffs.push({
6293
+ id: providerCheckId("supabase", "live-rls-unverified"),
6294
+ provider: "supabase",
6295
+ area: "database",
6296
+ title: "Live RLS not verified",
6297
+ description: "Repo includes RLS migration/policy files, but live Supabase project RLS has not been confirmed.",
6298
+ repoExpectation: "RLS policies defined in repo migrations",
6299
+ providerActual: "Not verified (mock \u2014 connect Supabase MCP read-only)",
6300
+ severity: "critical",
6301
+ suggestedFix: "mcp-connect",
6302
+ 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}`)
6303
+ });
6304
+ }
6305
+ return diffs;
6306
+ }
6307
+ };
6308
+
6309
+ // ../../src/station/verificationLayer/mock/mockVercelVerifier.ts
6310
+ var MOCK_VERCEL_PRODUCTION_ENV = /* @__PURE__ */ new Set();
6311
+ function deploymentEnvCandidates(ctx) {
6312
+ const referenced = collectReferencedEnvNames(ctx.scan, ctx.repositoryEvidence);
6313
+ const fromExample = collectEnvExampleNames(ctx.scan);
6314
+ const names = /* @__PURE__ */ new Set([...referenced, ...fromExample]);
6315
+ return [...names].filter((name) => {
6316
+ if (/^VERCEL_/i.test(name)) {
6317
+ return true;
6318
+ }
6319
+ if (/^(DATABASE_URL|NEXT_PUBLIC_|VITE_|SUPABASE_|STRIPE_|CLERK_|SENTRY_|POSTHOG_|PADDLE_|AUTH_|NEXTAUTH_)/i.test(name)) {
6320
+ return true;
6321
+ }
6322
+ return referenced.includes(name) && fromExample.includes(name);
6323
+ });
6324
+ }
6325
+ var mockVercelVerifier = {
6326
+ provider: "vercel",
6327
+ detectConfig(ctx) {
6328
+ const repo = buildRepoScanContext(ctx.scan);
6329
+ const signals = [];
6330
+ if (ctx.scan.stackSignals.hasVercel) {
6331
+ signals.push("stack: hasVercel");
6332
+ }
6333
+ if (/vercel\.json/i.test(repo.pathBlob)) {
6334
+ signals.push("path: vercel.json");
6335
+ }
6336
+ if (ctx.scan.stackSignals.hasNextJs || ctx.scan.stackSignals.hasVite) {
6337
+ signals.push("framework: Vercel-compatible");
6338
+ }
6339
+ const detected = signals.length > 0;
6340
+ return { provider: "vercel", detected, signals };
6341
+ },
6342
+ connectStatus(ctx) {
6343
+ return resolveProviderConnectionState("vercel", ctx.mcpVerifierState);
6344
+ },
6345
+ runChecks(ctx) {
6346
+ const connectionState = this.connectStatus(ctx);
6347
+ const candidates = deploymentEnvCandidates(ctx);
6348
+ const missingOnVercel = candidates.filter((name) => !MOCK_VERCEL_PRODUCTION_ENV.has(name));
6349
+ const repo = buildRepoScanContext(ctx.scan);
6350
+ const checks = [
6351
+ buildVerificationCheck({
6352
+ provider: "vercel",
6353
+ checkKey: "production-env-sync",
6354
+ area: "deployment",
6355
+ title: "Vercel production environment variables",
6356
+ 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.",
6357
+ evidenceSource: "provider",
6358
+ connectionState,
6359
+ repoExpectationMet: candidates.length > 0,
6360
+ providerObservationMet: missingOnVercel.length === 0,
6361
+ fixType: "provider-config",
6362
+ severity: missingOnVercel.length > 0 ? "critical" : "info",
6363
+ repoSignals: missingOnVercel.map((name) => `env expected: ${name}`),
6364
+ providerSignals: connectionState === "connected" ? ["Vercel production env API"] : ["Awaiting Vercel MCP read-only env lookup"],
6365
+ requiredEvidence: ["Vercel production env var names and values (redacted)"],
6366
+ manualAction: "Add missing keys in Vercel Project Settings \u2192 Environment Variables (Production)."
6367
+ }),
6368
+ buildVerificationCheck({
6369
+ provider: "vercel",
6370
+ checkKey: "latest-deployment",
6371
+ area: "deployment",
6372
+ title: "Latest Vercel deployment status",
6373
+ description: "Production deployment health requires live Vercel project access.",
6374
+ evidenceSource: "provider",
6375
+ connectionState,
6376
+ repoExpectationMet: Boolean(ctx.scan.stackSignals.hasVercel || /vercel\.json/i.test(repo.pathBlob)),
6377
+ providerObservationMet: false,
6378
+ fixType: "mcp-connect",
6379
+ severity: "warning",
6380
+ repoSignals: repo.pathBlob.split(/\r?\n/).filter((p2) => /vercel\.json/i.test(p2)).slice(0, 3),
6381
+ providerSignals: ["Vercel deployments API or MCP"],
6382
+ requiredEvidence: ["Latest production deployment state"]
6383
+ }),
6384
+ buildVerificationCheck({
6385
+ provider: "vercel",
6386
+ checkKey: "production-domain",
6387
+ area: "deployment",
6388
+ title: "Vercel production domain",
6389
+ description: "Custom domain and DNS status require live Vercel project access.",
6390
+ evidenceSource: "provider",
6391
+ connectionState,
6392
+ repoExpectationMet: Boolean(ctx.scan.stackSignals.hasVercel || ctx.scan.stackSignals.hasNextJs),
6393
+ providerObservationMet: false,
6394
+ fixType: "mcp-connect",
6395
+ severity: "warning",
6396
+ repoSignals: [],
6397
+ providerSignals: ["Vercel domains API or MCP"],
6398
+ requiredEvidence: ["Production domain assignment and DNS verification"]
6399
+ })
6400
+ ];
6401
+ return checks;
6402
+ },
6403
+ buildDiffs(ctx) {
6404
+ const candidates = deploymentEnvCandidates(ctx);
6405
+ return candidates.filter((name) => !MOCK_VERCEL_PRODUCTION_ENV.has(name)).map((name) => ({
6406
+ id: providerCheckId("vercel", `env-missing-${name.toLowerCase()}`),
6407
+ provider: "vercel",
6408
+ area: "deployment",
6409
+ title: `${name} missing in Vercel production`,
6410
+ description: `The repo references ${name}, but the mock Vercel production env snapshot does not include it.`,
6411
+ repoExpectation: `Referenced in repo (.env.example, code, or env evidence)`,
6412
+ providerActual: "Not present in Vercel production (mock empty snapshot)",
6413
+ severity: /SECRET|KEY|TOKEN|PASSWORD/i.test(name) ? "critical" : "warning",
6414
+ suggestedFix: "provider-config",
6415
+ evidenceRefs: collectReferencedEnvNames(ctx.scan, ctx.repositoryEvidence).filter((entry) => entry === name).map((entry) => `env: ${entry}`)
6416
+ }));
6417
+ }
6418
+ };
6419
+
6420
+ // ../../src/station/verificationLayer/registry.ts
6421
+ var verifiers = [];
6422
+ function registerProviderVerifier(verifier) {
6423
+ const existing = verifiers.findIndex((entry) => entry.provider === verifier.provider);
6424
+ if (existing >= 0) {
6425
+ verifiers[existing] = verifier;
6426
+ return;
6427
+ }
6428
+ verifiers.push(verifier);
6429
+ }
6430
+ function getRegisteredVerifiers() {
6431
+ return [...verifiers];
6432
+ }
6433
+
6434
+ // ../../src/station/verificationLayer/registerDefaults.ts
6435
+ var defaultsRegistered = false;
6436
+ function ensureDefaultVerifiersRegistered() {
6437
+ if (defaultsRegistered) {
6438
+ return;
6439
+ }
6440
+ registerProviderVerifier(mockVercelVerifier);
6441
+ registerProviderVerifier(mockSupabaseVerifier);
6442
+ registerProviderVerifier(mockStripeVerifier);
6443
+ registerProviderVerifier(mockGitHubVerifier);
6444
+ defaultsRegistered = true;
6445
+ }
6446
+
6447
+ // ../../src/station/verificationLayer/runVerificationLayer.ts
6448
+ function runVerifier(verifier, ctx) {
6449
+ const config = verifier.detectConfig(ctx);
6450
+ if (!config.detected) {
6451
+ return null;
6452
+ }
6453
+ const connectionState = verifier.connectStatus(ctx);
6454
+ const rawChecks = verifier.runChecks(ctx);
6455
+ const checks = rawChecks.map((check) => ({
6456
+ ...check,
6457
+ status: coerceProviderVerificationStatus(check.status, connectionState)
6458
+ }));
6459
+ const diffs = verifier.buildDiffs(ctx);
6460
+ return {
6461
+ provider: verifier.provider,
6462
+ connectionState,
6463
+ config,
6464
+ checks,
6465
+ diffs,
6466
+ repoReadinessPercent: computeLayerReadinessPercent(checks, "repo"),
6467
+ providerReadinessPercent: computeLayerReadinessPercent(checks, "provider")
6468
+ };
6469
+ }
6470
+ function runVerificationLayer(ctx, options) {
6471
+ if (options?.registerDefaults !== false) {
6472
+ ensureDefaultVerifiersRegistered();
6473
+ }
6474
+ const providers = getRegisteredVerifiers().map((verifier) => runVerifier(verifier, ctx)).filter((result) => result !== null);
6475
+ const checks = providers.flatMap((result) => result.checks);
6476
+ const diffs = providers.flatMap((result) => result.diffs);
6477
+ const { repoReadinessPercent, providerReadinessPercent } = aggregateReadinessPercents(providers);
6478
+ return {
6479
+ version: 1,
6480
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6481
+ runtimeMode: "mock",
6482
+ providers,
6483
+ checks,
6484
+ diffs,
6485
+ repoReadinessPercent,
6486
+ providerReadinessPercent
6487
+ };
6488
+ }
6489
+
6490
+ // ../../src/station/orchestrator.ts
6491
+ function isScanLimitResult(value) {
6492
+ return value.kind === "scan_limit_reached";
6493
+ }
6494
+ function isManagedRequiredResult(value) {
6495
+ return value.kind === "managed_required";
6496
+ }
6497
+ function isManagedSessionInvalidResult(value) {
6498
+ return value.kind === "managed_session_invalid";
6499
+ }
6500
+ function createStationOrchestrator(deps) {
6501
+ return {
6502
+ async run(input) {
6503
+ const allowLocal = Boolean(await deps.isLocalStationFallbackAllowed?.());
6504
+ const scan = await deps.scanWorkspace(input.workspaceRoot);
6505
+ const productionConnectionChoices = await loadProductionConnectionChoices(deps);
6506
+ const productionConnectionEvidence = detectProductionConnectionEvidence(scan);
6507
+ const productionConnections = summarizeProductionConnections(
6508
+ productionConnectionChoices,
6509
+ productionConnectionEvidence
6510
+ );
6511
+ const productionConnectionContext = buildProductionConnectionContext(
6512
+ productionConnectionChoices,
6513
+ productionConnectionEvidence
6514
+ );
6515
+ const verificationSummary = buildVerificationSummary(scan, productionConnections);
6516
+ const verificationEvidenceContext = buildVerificationEvidenceContext(verificationSummary);
6517
+ const stackWiring = analyzeStackWiring(scan);
6518
+ const stackWiringContext = buildStackWiringContext(stackWiring);
6519
+ const repositoryEvidence = analyzeRepositoryEvidence(scan);
6520
+ const providerRegistry = buildProviderRegistrySnapshot();
6521
+ const staticInfrastructureFlowGraph = buildStaticInfrastructureFlowGraph(scan);
6522
+ const stackAutomation = buildStackAutomationSummary(stackWiring, { staticInfrastructureFlowGraph });
6523
+ const stackAutomationContext = buildStackAutomationContext(stackAutomation);
6524
+ const mcpVerifierState = await deps.getMcpVerifierState?.();
6525
+ const verificationLayer = runVerificationLayer({
6526
+ workspaceRoot: input.workspaceRoot,
6527
+ scan,
6528
+ stackWiring,
6529
+ repositoryEvidence,
6530
+ mcpVerifierState
6531
+ });
6532
+ const missionGraph = buildMissionGraph({
6533
+ stackWiring,
6534
+ repositoryEvidence,
6535
+ staticInfrastructureFlowGraph,
6536
+ verificationLayer
6537
+ });
6538
+ const specContent = await readSpec(input.workspaceRoot);
6539
+ const modelPrompt = buildAnalysisPrompt(
6540
+ scan,
5234
6541
  specContent,
5235
6542
  productionConnectionContext,
5236
6543
  verificationEvidenceContext,
@@ -5568,25 +6875,49 @@ function generateAgentSummary(artifact) {
5568
6875
  }
5569
6876
  }
5570
6877
  lines.push("");
5571
- lines.push("## Top launch gaps (model)");
6878
+ lines.push("## Agent-code actions");
5572
6879
  if (topGaps.length === 0) {
5573
- lines.push("_No model gaps returned \u2014 rely on mission map checks above._");
6880
+ lines.push("_No model gaps returned. Review mission map checks before changing code._");
5574
6881
  } else {
5575
6882
  topGaps.forEach((gap, index) => {
5576
6883
  lines.push(
5577
6884
  `${index + 1}. **${gap.title}** (\`${gap.id}\`, ${gap.severity}, map: \`${gap.primaryMapCategory}\`)`
5578
6885
  );
5579
6886
  lines.push(` - ${gap.detail}`);
5580
- lines.push(` - Prompt: \`viberaven prompt --gap ${gap.id}\``);
6887
+ lines.push(` - Command: \`viberaven prompt --gap ${gap.id}\``);
6888
+ if (gap.copyPrompt) {
6889
+ lines.push(` - Prompt: ${gap.copyPrompt}`);
6890
+ }
6891
+ });
6892
+ }
6893
+ const manualChecks = (artifact.missionGraph.areas ?? []).flatMap(
6894
+ (area) => area.providerMissions.flatMap(
6895
+ (mission) => mission.checks.filter(
6896
+ (check) => check.evidenceClass === "manual-dashboard" || check.evidenceClass === "mcp-verifier" || check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.status === "needs-connection" || check.status === "unknown"
6897
+ ).map((check) => ({ area: area.label, provider: mission.providerLabel, check }))
6898
+ )
6899
+ );
6900
+ lines.push("");
6901
+ lines.push("## Human-provider actions");
6902
+ if (manualChecks.length === 0) {
6903
+ lines.push("_No manual provider actions were identified in this scan._");
6904
+ } else {
6905
+ manualChecks.slice(0, 8).forEach((item3, index) => {
6906
+ lines.push(`${index + 1}. **${item3.check.label}** (${item3.area} / ${item3.provider})`);
6907
+ lines.push(
6908
+ ` - ${item3.check.promptHint || "Ask the user to confirm this in the provider dashboard or through read-only MCP."}`
6909
+ );
5581
6910
  });
5582
6911
  }
5583
6912
  lines.push("");
6913
+ lines.push("Do not claim human-provider actions as repo-code fixes.");
6914
+ lines.push("");
5584
6915
  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.");
6916
+ lines.push("1. Pick the highest-severity agent-code gap unless the user names an area or provider.");
6917
+ lines.push("2. Run `viberaven prompt --gap <id>` or read `copyPrompt` from `last-scan.json`.");
6918
+ lines.push("3. Implement the repo-code fix.");
6919
+ lines.push("4. Run `npx -y @viberaven/cli@beta scan` again.");
6920
+ lines.push("5. Tell the user to review `.viberaven/report.html` for the visual mission map.");
5590
6921
  lines.push("");
5591
6922
  if (artifact.usage) {
5592
6923
  lines.push("## Account usage");
@@ -5719,6 +7050,24 @@ var PANEL_CLIENT_SCRIPT = `
5719
7050
 
5720
7051
 
5721
7052
 
7053
+ function normalizeProviderToken(value) {
7054
+
7055
+ return String(value == null ? '' : value)
7056
+ .trim()
7057
+ .toLowerCase()
7058
+ .replace(/&/g, 'and')
7059
+ .replace(/[^a-z0-9]+/g, '');
7060
+
7061
+ }
7062
+
7063
+ function buildProviderStackCommand(areaKey, provider) {
7064
+
7065
+ return 'viberaven stack set ' + areaKey + ' ' + normalizeProviderToken(provider) + ' && viberaven scan';
7066
+
7067
+ }
7068
+
7069
+
7070
+
5722
7071
  function normKey(p) {
5723
7072
 
5724
7073
  if (!p) return '';
@@ -5727,6 +7076,8 @@ var PANEL_CLIENT_SCRIPT = `
5727
7076
 
5728
7077
  var compact = raw.replace(/[^a-z0-9]+/g, '');
5729
7078
 
7079
+ if (!compact || compact === 'notselected' || raw === 'not selected' || raw === 'none') return '';
7080
+
5730
7081
  if (logoPayload.aliases[raw]) return logoPayload.aliases[raw];
5731
7082
 
5732
7083
  if (logoPayload.aliases[compact]) return logoPayload.aliases[compact];
@@ -5791,17 +7142,101 @@ var PANEL_CLIENT_SCRIPT = `
5791
7142
 
5792
7143
  }
5793
7144
 
7145
+ if (key && logoPayload.logos[key]) return logoPayload.logos[key];
7146
+
5794
7147
  if (key && logoPayload.iconUrls && logoPayload.iconUrls[key]) {
5795
7148
 
5796
7149
  return '<img class="provider-logo__img" src="' + esc(logoPayload.iconUrls[key]) + '" alt="" decoding="async" data-provider-logo-key="' + esc(key) + '" />';
5797
7150
 
5798
7151
  }
5799
7152
 
5800
- if (key && logoPayload.logos[key]) return logoPayload.logos[key];
7153
+ 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>';
7154
+
7155
+ }
7156
+
7157
+
7158
+
7159
+ function nodeTone(p) {
7160
+
7161
+ var key = normKey(p);
7162
+
7163
+ if (!key || !logoPayload.nodeTones || !logoPayload.nodeTones[key]) return null;
7164
+
7165
+ return logoPayload.nodeTones[key];
7166
+
7167
+ }
7168
+
7169
+
7170
+
7171
+ function syncMapNode(areaKey, providerOption) {
7172
+
7173
+ var node = document.querySelector('.studio-node[data-area-key="' + areaKey + '"]');
7174
+
7175
+ if (!node) return;
7176
+
7177
+ var area = areas.find(function (a) { return a.key === areaKey; });
7178
+
7179
+ var areaLabel = (area && area.label) || LABELS[areaKey] || areaKey;
7180
+
7181
+ var missions = (area && area.providerMissions) || [];
7182
+
7183
+ var mission = missionForProvider(missions, providerOption);
7184
+
7185
+ var providerLabel = providerLabelFor(areaKey, providerOption, mission, areaLabel);
7186
+
7187
+ var iconKey = normKey(providerOption);
7188
+
7189
+ var logoInner = logoHtml(iconKey || providerOption, providerLabel);
5801
7190
 
5802
- var t = (label || p || '?').trim();
7191
+ var cls = logoClass(iconKey || providerOption);
5803
7192
 
5804
- return '<span aria-hidden="true">' + esc(t.slice(0, 2).toUpperCase()) + '</span>';
7193
+ var logoEl = node.querySelector('.studio-node__logo');
7194
+
7195
+ if (logoEl) {
7196
+
7197
+ logoEl.className = 'studio-node__logo provider-logo' + cls;
7198
+
7199
+ logoEl.innerHTML = logoInner;
7200
+
7201
+ }
7202
+
7203
+ node.className = node.className
7204
+
7205
+ .replace(/\\sprovider-logo--[a-z0-9-]+/g, '')
7206
+
7207
+ .replace(/\\sprovider-logo--brand/g, '')
7208
+
7209
+ .trim();
7210
+
7211
+ if (!/\\bprovider-logo\\b/.test(node.className)) node.className += ' provider-logo';
7212
+
7213
+ node.className += cls;
7214
+
7215
+ var provSpan = node.querySelector('.studio-node__provider');
7216
+
7217
+ if (provSpan) provSpan.textContent = providerLabel;
7218
+
7219
+ var tone = nodeTone(providerOption);
7220
+
7221
+ if (tone) {
7222
+
7223
+ node.style.setProperty('--provider-color', tone[0]);
7224
+
7225
+ node.style.setProperty('--provider-glow', tone[1]);
7226
+
7227
+ } else {
7228
+
7229
+ node.style.removeProperty('--provider-color');
7230
+
7231
+ node.style.removeProperty('--provider-glow');
7232
+
7233
+ }
7234
+
7235
+ var metaSpan = node.querySelector('.studio-node__meta');
7236
+
7237
+ var metaText = metaSpan ? metaSpan.textContent : '';
7238
+
7239
+ node.setAttribute('aria-label', areaLabel + ', ' + providerLabel + ', ' + metaText);
5805
7240
 
5806
7241
  }
5807
7242
 
@@ -5887,19 +7322,53 @@ var PANEL_CLIENT_SCRIPT = `
5887
7322
 
5888
7323
 
5889
7324
 
5890
- function sameProvider(a, b) {
7325
+ function firstProviderOption(areaKey) {
5891
7326
 
5892
- if (!a || !b) return false;
7327
+ var options = providerOptions[areaKey] || [];
5893
7328
 
5894
- return String(a).toLowerCase() === String(b).toLowerCase() || normKey(a) === normKey(b);
7329
+ return options[0] || null;
5895
7330
 
5896
7331
  }
5897
7332
 
5898
7333
 
5899
7334
 
5900
- function projectProviderFor(areaKey, missions) {
7335
+ function iconProviderKey(areaKey, currentProvider, mission, providerLabel) {
5901
7336
 
5902
- if (projectProviders[areaKey]) return projectProviders[areaKey];
7337
+ var option = optionFor(areaKey, currentProvider);
7338
+
7339
+ var firstOption = firstProviderOption(areaKey);
7340
+
7341
+ return currentProvider ||
7342
+
7343
+ (mission && (mission.provider || mission.providerLabel || mission.key)) ||
7344
+
7345
+ (option && (option.provider || option.label)) ||
7346
+
7347
+ (firstOption && (firstOption.provider || firstOption.label)) ||
7348
+
7349
+ providerLabel ||
7350
+
7351
+ LABELS[areaKey] ||
7352
+
7353
+ areaKey;
7354
+
7355
+ }
7356
+
7357
+
7358
+
7359
+ function sameProvider(a, b) {
7360
+
7361
+ if (!a || !b) return false;
7362
+
7363
+ return String(a).toLowerCase() === String(b).toLowerCase() || normKey(a) === normKey(b);
7364
+
7365
+ }
7366
+
7367
+
7368
+
7369
+ function projectProviderFor(areaKey, missions) {
7370
+
7371
+ if (projectProviders[areaKey]) return projectProviders[areaKey];
5903
7372
 
5904
7373
  var mission = preferredMission(missions, '');
5905
7374
 
@@ -6037,210 +7506,672 @@ var PANEL_CLIENT_SCRIPT = `
6037
7506
 
6038
7507
  return [
6039
7508
 
6040
- 'Wire ' + subject + ' for this app safely.',
7509
+ 'Wire ' + subject + ' for this app safely.',
7510
+
7511
+ '',
7512
+
7513
+ 'Current ' + subject + ' readiness: ' + passed.length + '/' + Math.max(total, 1) + ' repo checks passed (' + (mission.readinessPercent || 0) + '%).',
7514
+
7515
+ '',
7516
+
7517
+ 'Repo evidence already found:',
7518
+
7519
+ itemLines(passed, '- No ' + subject + ' checks passed yet.', true),
7520
+
7521
+ '',
7522
+
7523
+ 'Missing ' + subject + ' checks:',
7524
+
7525
+ itemLines(missing, '- No missing ' + subject + ' checks were found by VibeRaven.', false),
7526
+
7527
+ '',
7528
+
7529
+ 'Manual checks that repo evidence cannot prove:',
7530
+
7531
+ itemLines(manual, '- No manual dashboard checks were listed.', false),
7532
+
7533
+ '',
7534
+
7535
+ 'First inspect the existing package.json files, env examples, framework routes, provider helpers, and server/client boundaries before editing.',
7536
+
7537
+ '',
7538
+
7539
+ 'Implement:',
7540
+
7541
+ '1. Close only the missing ' + subject + ' checks listed above.',
7542
+
7543
+ '2. Follow the existing file structure and naming patterns.',
7544
+
7545
+ '3. Keep provider secrets in server-only code and documented env templates.',
7546
+
7547
+ '4. Keep external dashboard work explicit instead of claiming it from repo evidence.',
7548
+
7549
+ '',
7550
+
7551
+ 'Constraints:',
7552
+
7553
+ '- Do not rewrite unrelated auth, payments, UI, billing, deployment, or analytics code.',
7554
+
7555
+ '- Do not expose secret keys to browser code, public env variables, or client-executed files.',
7556
+
7557
+ '- Do not claim external provider dashboard setup is complete from repo evidence alone.',
7558
+
7559
+ '',
7560
+
7561
+ 'Verification:',
7562
+
7563
+ '- Run the relevant TypeScript/build/test command for this repo.',
7564
+
7565
+ '- Confirm VibeRaven can rescan and move the missing checks to passed where repo evidence exists.',
7566
+
7567
+ '- Summarize what changed and what still requires manual provider dashboard verification.'
7568
+
7569
+ ].join('\\n');
7570
+
7571
+ }
7572
+
7573
+
7574
+
7575
+ function choiceTilesHtml(areaKey, currentProvider, missions, evidenceMissions) {
7576
+
7577
+ var options = providerOptions[areaKey];
7578
+
7579
+ if (!options || !options.length) return '';
7580
+
7581
+ var projectProvider = projectProviderFor(areaKey, missions);
7582
+
7583
+ var tiles = options.map(function (opt) {
7584
+
7585
+ var p = opt.provider || opt.label;
7586
+
7587
+ var active = sameProvider(p, currentProvider);
7588
+
7589
+ var inProject = sameProvider(p, projectProvider);
7590
+
7591
+ var desc = opt.description || benefitText(p);
7592
+
7593
+ var providerOption = normKey(p) || normalizeProviderToken(p);
7594
+
7595
+ var status = inProject ? 'Using now' : active ? 'Added to setup' : 'Use this path';
7596
+
7597
+ return '<button type="button" class="studio-choice-tile' + (active ? ' studio-choice-tile--selected' : '') + (inProject ? ' studio-choice-tile--in-project' : '') +
7598
+
7599
+ '" data-provider-option="' + attrEsc(providerOption) + '" data-provider-label="' + attrEsc(p) + '" data-area-key="' + attrEsc(areaKey) + '" aria-pressed="' + (active ? 'true' : 'false') + '">' +
7600
+
7601
+ '<span class="studio-choice-tile__icon provider-logo' + logoClass(p) + '" aria-hidden="true">' + logoHtml(p, opt.label) + '</span>' +
7602
+
7603
+ '<span class="studio-choice-tile__name">' + esc(opt.label) + '</span>' +
7604
+
7605
+ '<span class="studio-choice-tile__desc">' + esc(desc) + '</span>' +
7606
+
7607
+ '<span class="studio-choice-tile__status">' + status + '</span>' +
7608
+
7609
+ '</button>';
7610
+
7611
+ }).join('');
7612
+
7613
+ var hintLabel = CHOICE_HINTS[areaKey] || ('Choose ' + (LABELS[areaKey] || areaKey).toLowerCase());
7614
+
7615
+ return '<div class="studio-setup-panel__hint"><span>' + esc(hintLabel) + '</span>' + evidenceBadgeHtml(evidenceMissions || missions) + '</div>' +
7616
+
7617
+ '<div class="studio-choice-list" role="group" aria-label="' + attrEsc(hintLabel) + '">' + tiles + '</div>';
7618
+
7619
+ }
7620
+
7621
+
7622
+
7623
+ function buildPrompt(areaKey, missions, providerLabel, automation) {
7624
+
7625
+ if (automation) {
7626
+
7627
+ if (automation.automationLevel === 'manual-only' && automation.verificationPrompt) return automation.verificationPrompt;
7628
+
7629
+ if (automation.repoPrompt) return automation.repoPrompt;
7630
+
7631
+ if (automation.promptRoutes && automation.promptRoutes['repo-fix'] && automation.promptRoutes['repo-fix'].body) {
7632
+
7633
+ return automation.promptRoutes['repo-fix'].body;
7634
+
7635
+ }
7636
+
7637
+ }
7638
+
7639
+ var areaGaps = missions[0] ? gaps.filter(function (g) { return g.primaryMapCategory === areaKey; }) : [];
7640
+
7641
+ if (areaGaps[0] && areaGaps[0].copyPrompt) return areaGaps[0].copyPrompt;
7642
+
7643
+ var missionPrompt = stackPromptFromMission(missions[0], providerLabel);
7644
+
7645
+ if (missionPrompt) return missionPrompt;
7646
+
7647
+ var missing = (missions[0] && missions[0].checks || []).filter(function (c) {
7648
+
7649
+ return c.evidenceClass === 'missing-repo-fix' || c.status === 'missing' || c.status === 'failed';
7650
+
7651
+ });
7652
+
7653
+ if (missing[0] && missing[0].promptHint) return missing[0].promptHint;
7654
+
7655
+ return setupPromptForProvider(areaKey, providerLabel);
7656
+
7657
+ }
7658
+
7659
+
7660
+
7661
+ function setupPromptForProvider(areaKey, providerLabel) {
7662
+
7663
+ var areaLabel = LABELS[areaKey] || areaKey;
7664
+
7665
+ var provider = providerLabel || areaLabel;
7666
+
7667
+ return [
7668
+
7669
+ 'Set up ' + provider + ' for the ' + areaLabel + ' production section safely.',
7670
+
7671
+ '',
7672
+
7673
+ 'Inspect first:',
7674
+
7675
+ '- Review package.json files, env examples, framework routes, provider helpers, server/client boundaries, and existing ' + areaLabel.toLowerCase() + ' patterns before editing.',
7676
+
7677
+ '- Identify the current framework, folder structure, naming style, validation style, and test/build commands.',
7678
+
7679
+ '- If VibeRaven SIFG leak context is present, treat its leak IDs and allowed files as the source of truth.',
7680
+
7681
+ '',
7682
+
7683
+ 'Implement:',
7684
+
7685
+ '- Make the smallest repo-only changes needed to wire the ' + provider + ' path.',
7686
+
7687
+ '- Add the right package or SDK only if it is missing.',
7688
+
7689
+ '- Document required environment variable names in safe examples or setup docs without reading or exposing real secrets.',
7690
+
7691
+ '- Add server-side integration points, route handlers, webhooks, guards, or helpers only where this repo structure expects them.',
7692
+
7693
+ '',
7694
+
7695
+ 'Provider constraints:',
7696
+
7697
+ '- Do not call provider APIs, mutate external projects, or edit VibeRaven dashboard state.',
7698
+
7699
+ '- Keep dashboard/provider setup as explicit manual steps.',
7700
+
7701
+ '- Do not claim live provider configuration is complete from repo changes alone.',
7702
+
7703
+ '- Keep secrets in server-only code and env examples. Use placeholder env names only.',
7704
+
7705
+ '',
7706
+
7707
+ 'Verification:',
7708
+
7709
+ '- Run the closest relevant build, test, lint, or typecheck command.',
7710
+
7711
+ '- Confirm repo evidence exists for each implemented item.',
7712
+
7713
+ '- List provider dashboard checks separately as manual or read-only MCP verification.',
7714
+
7715
+ '- Rescan VibeRaven after editing so repo evidence can move to verified.'
7716
+
7717
+ ].join('\\n');
7718
+
7719
+
7720
+ }
7721
+
7722
+ function evidenceSourceLabel(check) {
7723
+
7724
+ if (!check) return 'Unknown';
7725
+
7726
+ if (check.evidenceSource === 'provider') return 'Provider live';
7727
+
7728
+ if (check.evidenceSource === 'mcp' || check.evidenceClass === 'mcp-verifier') return 'MCP';
7729
+
7730
+ if (check.evidenceSource === 'manual' || check.evidenceClass === 'manual-dashboard' || check.status === 'user-confirmed') return 'Manual';
7731
+
7732
+ if (check.evidenceSource === 'repo' || check.evidenceClass === 'repo-verified' || check.evidenceClass === 'repo-file') return 'Repo files';
7733
+
7734
+ return 'Unknown';
7735
+
7736
+ }
7737
+
7738
+
7739
+
7740
+ function contractItem(label, detail, source, tone) {
7741
+
7742
+ return {
7743
+
7744
+ label: label || 'Unknown',
7745
+
7746
+ detail: detail || '',
7747
+
7748
+ source: source || 'Unknown',
7749
+
7750
+ tone: tone || 'neutral'
7751
+
7752
+ };
7753
+
7754
+ }
7755
+
7756
+
7757
+
7758
+ function checkDetail(check) {
7759
+
7760
+ if (!check) return '';
7761
+
7762
+ if (check.evidence && check.evidence[0]) return check.evidence[0];
7763
+
7764
+ if (check.promptHint) return check.promptHint;
7765
+
7766
+ if (check.status === 'unknown') return 'Not checked';
7767
+
7768
+ if (check.status === 'needs-connection') return 'Needs verification';
7769
+
7770
+ return '';
7771
+
7772
+ }
7773
+
7774
+
7775
+
7776
+ function buildCliSidebarContract(areaKey, label, mission, categoryGaps, providerLabel, automation, currentProvider) {
7777
+
7778
+ var connected = [];
7779
+
7780
+ var missing = [];
7781
+
7782
+ var manual = [];
7783
+
7784
+ var checks = mission && Array.isArray(mission.checks) ? mission.checks : [];
7785
+
7786
+ var mcpProvider = (automation && automation.mcpProvider) || '';
7787
+
7788
+ checks.forEach(function (check) {
7789
+
7790
+ var source = evidenceSourceLabel(check);
7791
+
7792
+ var item = contractItem(check.label, checkDetail(check), source, source === 'Repo files' ? 'repo' : source === 'MCP' ? 'mcp' : source === 'Manual' ? 'manual' : 'neutral');
7793
+
7794
+ if ((check.status === 'passed' || check.status === 'user-confirmed') && source !== 'Unknown') {
7795
+
7796
+ connected.push(item);
7797
+
7798
+ return;
7799
+
7800
+ }
7801
+
7802
+ if (check.evidenceClass === 'missing-repo-fix' || check.status === 'missing' || check.status === 'failed') {
7803
+
7804
+ missing.push(contractItem(check.label, checkDetail(check), 'Repo files', 'missing'));
7805
+
7806
+ return;
7807
+
7808
+ }
7809
+
7810
+ if (source === 'Manual' || source === 'MCP' || check.status === 'needs-connection' || check.status === 'unknown') {
7811
+
7812
+ manual.push(contractItem(check.label, checkDetail(check) || 'Not checked', source, 'manual'));
7813
+
7814
+ }
7815
+
7816
+ });
7817
+
7818
+ (Array.isArray(categoryGaps) ? categoryGaps : []).slice(0, 4).forEach(function (gap) {
7819
+
7820
+ missing.push(contractItem(gap.title || 'Product gap needs repo review', gap.detail || 'Agent can inspect and adjust repo implementation.', 'Repo files', 'missing'));
7821
+
7822
+ });
7823
+
7824
+ if (!checks.length) {
7825
+
7826
+ manual.push(contractItem('Provider live proof', 'Not checked', 'Unknown', 'manual'));
7827
+
7828
+ }
7829
+
7830
+ if (!manual.length) {
7831
+
7832
+ manual.push(contractItem('Provider live proof', 'Not checked', 'Unknown', 'manual'));
7833
+
7834
+ }
7835
+
7836
+ return {
7837
+
7838
+ areaKey: areaKey,
7839
+
7840
+ areaLabel: label || LABELS[areaKey] || areaKey,
7841
+
7842
+ providerKey: iconProviderKey(areaKey, currentProvider, mission, providerLabel),
7843
+
7844
+ providerLabel: providerLabel || 'Selected provider',
7845
+
7846
+ connected: connected,
7847
+
7848
+ missing: missing,
7849
+
7850
+ manual: manual,
7851
+
7852
+ repoPercent: typeof (mission && mission.repoReadinessPercent) === 'number'
7853
+ ? Math.max(0, Math.min(100, Math.round(mission.repoReadinessPercent)))
7854
+ : typeof (mission && mission.readinessPercent) === 'number'
7855
+ ? Math.max(0, Math.min(100, Math.round(mission.readinessPercent)))
7856
+ : 0,
7857
+
7858
+ providerPercent: typeof (mission && mission.providerReadinessPercent) === 'number'
7859
+ ? Math.max(0, Math.min(100, Math.round(mission.providerReadinessPercent)))
7860
+ : 0,
7861
+
7862
+ hasMcpAccess: Boolean(providerLabel || mcpProvider) || checks.some(function (check) {
7863
+ return check.evidenceSource === 'mcp' || check.evidenceClass === 'mcp-verifier' || check.verificationStatus === 'needs_mcp';
7864
+ })
7865
+
7866
+ };
7867
+
7868
+ }
7869
+
7870
+
7871
+
7872
+ function contractLines(items) {
7873
+
7874
+ return (items && items.length ? items : [{ label: 'None', detail: 'No artifact-backed item.', source: 'Unknown' }]).map(function (item) {
7875
+
7876
+ return '- [' + (item.source || 'Unknown') + '] ' + item.label + (item.detail ? ': ' + item.detail : '');
7877
+
7878
+ }).join('\\n');
7879
+
7880
+ }
7881
+
7882
+
7883
+
7884
+ function focusedContractPrompt(contract) {
7885
+
7886
+ return [
7887
+
7888
+ 'VibeRaven selected-node production checklist',
7889
+
7890
+ '',
7891
+
7892
+ 'Node: ' + contract.areaLabel,
7893
+
7894
+ 'Selected provider context: ' + contract.providerLabel,
7895
+
7896
+ '',
7897
+
7898
+ 'Verified evidence:',
7899
+
7900
+ contractLines(contract.connected),
7901
+
7902
+ '',
7903
+
7904
+ 'Agent-code actions:',
7905
+
7906
+ contractLines(contract.missing),
7907
+
7908
+ '',
7909
+
7910
+ 'Human-provider actions:',
7911
+
7912
+ contractLines(contract.manual),
7913
+
7914
+ '',
7915
+
7916
+ 'Rules:',
7917
+
7918
+ '- Selected provider is context only and does not mean connected.',
7919
+
7920
+ '- Connected evidence requires Repo files, Provider live, MCP, or Manual confirmation in the artifact.',
7921
+
7922
+ '- Do not represent human-provider actions as completed code fixes.'
7923
+
7924
+ ].join('\\n');
7925
+
7926
+ }
7927
+
7928
+
7929
+
7930
+ function sidebarStatusBadge(contract) {
7931
+
7932
+ if ((contract.connected || []).some(function (item) { return item.source === 'Repo files'; })) return 'Repo evidence found';
7933
+
7934
+ if ((contract.missing || []).length) return 'Missing repo fixes';
7935
+
7936
+ if ((contract.manual || []).length) return 'Manual check';
7937
+
7938
+ return 'Selected only';
7939
+
7940
+ }
7941
+
7942
+
7943
+
7944
+ function sidebarReadinessSentence(contract) {
7945
+
7946
+ var hasRepo = (contract.connected || []).some(function (item) { return item.source === 'Repo files'; });
7947
+
7948
+ var hasLive = (contract.connected || []).some(function (item) { return item.source === 'Provider live' || item.source === 'MCP' || item.source === 'Manual'; });
7949
+
7950
+ if (hasRepo && !hasLive) return 'Repo found. Live not checked.';
7951
+
7952
+ if (hasLive) return 'Live proof exists in the scan artifact.';
7953
+
7954
+ if ((contract.missing || []).length) return 'Repo gaps found. Live not checked.';
7955
+
7956
+ return 'No verified evidence yet.';
7957
+
7958
+ }
7959
+
7960
+
7961
+
7962
+ function contractMetricHtml(label, percent, value, isLive) {
7963
+
7964
+ var safePercent = Math.max(0, Math.min(100, Math.round(percent || 0)));
7965
+
7966
+ return '<div class="studio-sidebar-contract__compact-metric' + (isLive ? ' studio-sidebar-contract__compact-metric--live' : '') + '">' +
7967
+
7968
+ '<span>' + esc(label) + '</span>' +
7969
+
7970
+ '<strong>' + esc(value || (safePercent + '%')) + '</strong>' +
7971
+
7972
+ '<i class="studio-sidebar-contract__compact-meter" aria-hidden="true"><b style="width:' + safePercent + '%"></b></i>' +
7973
+
7974
+ '</div>';
7975
+
7976
+ }
6041
7977
 
6042
- '',
6043
7978
 
6044
- 'Current ' + subject + ' readiness: ' + passed.length + '/' + Math.max(total, 1) + ' repo checks passed (' + (mission.readinessPercent || 0) + '%).',
6045
7979
 
6046
- '',
7980
+ function contractReadinessHtml(contract) {
6047
7981
 
6048
- 'Repo evidence already found:',
7982
+ var providerKey = contract.providerKey || contract.providerLabel || contract.areaLabel;
6049
7983
 
6050
- itemLines(passed, '- No ' + subject + ' checks passed yet.', true),
7984
+ var providerStatus = contract.providerPercent > 0 ? contract.providerPercent + '%' : 'Not checked';
6051
7985
 
6052
- '',
7986
+ return '<section class="studio-sidebar-contract__readiness" aria-label="' + attrEsc(contract.providerLabel + ' readiness') + '">' +
6053
7987
 
6054
- 'Missing ' + subject + ' checks:',
7988
+ '<div class="studio-sidebar-contract__head">' +
6055
7989
 
6056
- itemLines(missing, '- No missing ' + subject + ' checks were found by VibeRaven.', false),
7990
+ '<span class="studio-sidebar-contract__logo provider-logo' + logoClass(providerKey) + '" title="' + attrEsc(contract.providerLabel) + '" aria-hidden="true">' + logoHtml(providerKey, contract.providerLabel) + '</span>' +
6057
7991
 
6058
- '',
7992
+ '<div class="studio-sidebar-contract__head-text"><strong>' + esc(contract.providerLabel) + '</strong><span>' + esc(contract.areaLabel) + '</span></div>' +
6059
7993
 
6060
- 'Manual checks that repo evidence cannot prove:',
7994
+ '<span class="studio-sidebar-contract__status">' + esc(sidebarStatusBadge(contract)) + '</span>' +
6061
7995
 
6062
- itemLines(manual, '- No manual dashboard checks were listed.', false),
7996
+ '</div>' +
6063
7997
 
6064
- '',
7998
+ '<div class="studio-sidebar-contract__compact-metrics">' +
6065
7999
 
6066
- 'First inspect the existing package.json files, env examples, framework routes, provider helpers, and server/client boundaries before editing.',
8000
+ contractMetricHtml('Repo files', contract.repoPercent, contract.repoPercent + '%', false) +
6067
8001
 
6068
- '',
8002
+ contractMetricHtml('Provider live', contract.providerPercent, providerStatus, true) +
6069
8003
 
6070
- 'Implement:',
8004
+ '</div>' +
6071
8005
 
6072
- '1. Close only the missing ' + subject + ' checks listed above.',
8006
+ '<p class="studio-sidebar-contract__summary">' + esc(sidebarReadinessSentence(contract)) + '</p>' +
6073
8007
 
6074
- '2. Follow the existing file structure and naming patterns.',
8008
+ '</section>';
6075
8009
 
6076
- '3. Keep provider secrets in server-only code and documented env templates.',
8010
+ }
6077
8011
 
6078
- '4. Keep external dashboard work explicit instead of claiming it from repo evidence.',
6079
8012
 
6080
- '',
6081
8013
 
6082
- 'Constraints:',
8014
+ function contractRepoSignalsHtml(contract) {
6083
8015
 
6084
- '- Do not rewrite unrelated auth, payments, UI, billing, deployment, or analytics code.',
8016
+ var repoItems = (contract.connected || []).filter(function (item) { return item.source === 'Repo files'; });
6085
8017
 
6086
- '- Do not expose secret keys to browser code, public env variables, or client-executed files.',
8018
+ if (!repoItems.length) return '';
6087
8019
 
6088
- '- Do not claim external provider dashboard setup is complete from repo evidence alone.',
8020
+ var rows = repoItems.slice(0, 6).map(function (item) {
6089
8021
 
6090
- '',
8022
+ return '<li><strong>' + esc(item.label || 'Repo evidence') + '</strong><span>' + esc(item.source || 'Repo files') + '</span></li>';
6091
8023
 
6092
- 'Verification:',
8024
+ }).join('');
6093
8025
 
6094
- '- Run the relevant TypeScript/build/test command for this repo.',
8026
+ return '<section class="studio-sidebar-contract__repo-signals">' +
6095
8027
 
6096
- '- Confirm VibeRaven can rescan and move the missing checks to passed where repo evidence exists.',
8028
+ '<h4>' + repoItems.length + ' repo signal' + (repoItems.length === 1 ? '' : 's') + ' in the codebase.</h4>' +
6097
8029
 
6098
- '- Summarize what changed and what still requires manual provider dashboard verification.'
8030
+ '<ul class="studio-sidebar-contract__signal-list">' + rows + '</ul>' +
6099
8031
 
6100
- ].join('\\n');
8032
+ '</section>';
6101
8033
 
6102
8034
  }
6103
8035
 
6104
8036
 
6105
8037
 
6106
- function choiceTilesHtml(areaKey, currentProvider, missions, evidenceMissions) {
8038
+ function contractAttentionItems(contract) {
6107
8039
 
6108
- var options = providerOptions[areaKey];
8040
+ var items = [];
6109
8041
 
6110
- if (!options || !options.length) return '';
8042
+ (contract.missing || []).slice(0, 3).forEach(function (item) {
6111
8043
 
6112
- var projectProvider = projectProviderFor(areaKey, missions);
8044
+ items.push({
6113
8045
 
6114
- var tiles = options.map(function (opt) {
8046
+ label: item.label,
6115
8047
 
6116
- var p = opt.provider || opt.label;
8048
+ detail: item.detail || 'Repo/config check needs attention.',
6117
8049
 
6118
- var active = sameProvider(p, currentProvider);
8050
+ source: 'Agent-code',
6119
8051
 
6120
- var inProject = sameProvider(p, projectProvider);
8052
+ next: 'Ask the agent to inspect related files and patch only repo code/config.'
6121
8053
 
6122
- var desc = opt.description || benefitText(p);
8054
+ });
6123
8055
 
6124
- var status = inProject ? 'Using now' : active ? 'Added to setup' : 'Use this path';
8056
+ });
6125
8057
 
6126
- return '<button type="button" class="studio-choice-tile' + (active ? ' studio-choice-tile--selected' : '') + (inProject ? ' studio-choice-tile--in-project' : '') +
8058
+ (contract.manual || []).slice(0, 3).forEach(function (item) {
6127
8059
 
6128
- '" data-switch-area="' + esc(areaKey) + '" data-switch-provider="' + esc(opt.provider) + '" aria-pressed="' + (active ? 'true' : 'false') + '">' +
8060
+ items.push({
6129
8061
 
6130
- '<span class="studio-choice-tile__icon provider-logo' + logoClass(p) + '" aria-hidden="true">' + logoHtml(p, opt.label) + '</span>' +
8062
+ label: item.label,
6131
8063
 
6132
- '<span class="studio-choice-tile__name">' + esc(opt.label) + '</span>' +
8064
+ detail: item.detail || 'MCP/API/dashboard required.',
6133
8065
 
6134
- '<span class="studio-choice-tile__desc">' + esc(desc) + '</span>' +
8066
+ source: item.source === 'MCP' ? 'MCP' : 'Manual',
6135
8067
 
6136
- '<span class="studio-choice-tile__status">' + status + '</span>' +
8068
+ next: item.source === 'MCP' ? 'Use read-only MCP/API verification.' : 'Ask the user to confirm the provider dashboard.'
6137
8069
 
6138
- '</button>';
8070
+ });
6139
8071
 
6140
- }).join('');
8072
+ });
6141
8073
 
6142
- var hintLabel = CHOICE_HINTS[areaKey] || ('Choose ' + (LABELS[areaKey] || areaKey).toLowerCase());
8074
+ return items;
6143
8075
 
6144
- return '<div class="studio-setup-panel__hint"><span>' + esc(hintLabel) + '</span>' + evidenceBadgeHtml(evidenceMissions || missions) + '</div>' +
8076
+ }
6145
8077
 
6146
- '<div class="studio-choice-list" role="group" aria-label="' + esc(hintLabel) + '">' + tiles + '</div>';
6147
8078
 
6148
- }
6149
8079
 
8080
+ function contractAttentionHtml(contract) {
6150
8081
 
8082
+ var items = contractAttentionItems(contract);
6151
8083
 
6152
- function buildPrompt(areaKey, missions, providerLabel, automation) {
8084
+ if (!items.length) return '';
6153
8085
 
6154
- if (automation) {
8086
+ function attentionRow(item) {
6155
8087
 
6156
- if (automation.automationLevel === 'manual-only' && automation.verificationPrompt) return automation.verificationPrompt;
8088
+ return '<li class="studio-sidebar-contract__attention-item">' +
6157
8089
 
6158
- if (automation.repoPrompt) return automation.repoPrompt;
8090
+ '<div class="studio-sidebar-contract__attention-top"><strong>' + esc(item.label || 'Attention') + '</strong><em>' + esc(item.source || 'Verify') + '</em></div>' +
6159
8091
 
6160
- if (automation.promptRoutes && automation.promptRoutes['repo-fix'] && automation.promptRoutes['repo-fix'].body) {
8092
+ (item.detail ? '<span>' + esc(item.detail) + '</span>' : '') +
6161
8093
 
6162
- return automation.promptRoutes['repo-fix'].body;
8094
+ (item.next ? '<b>' + esc(item.next) + '</b>' : '') +
6163
8095
 
6164
- }
8096
+ '</li>';
6165
8097
 
6166
8098
  }
6167
8099
 
6168
- var areaGaps = missions[0] ? gaps.filter(function (g) { return g.primaryMapCategory === areaKey; }) : [];
8100
+ var first = attentionRow(items[0]);
6169
8101
 
6170
- if (areaGaps[0] && areaGaps[0].copyPrompt) return areaGaps[0].copyPrompt;
8102
+ var more = items.length > 1
6171
8103
 
6172
- var missionPrompt = stackPromptFromMission(missions[0], providerLabel);
8104
+ ? '<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
8105
 
6174
- if (missionPrompt) return missionPrompt;
8106
+ items.slice(1).map(function (item) {
6175
8107
 
6176
- var missing = (missions[0] && missions[0].checks || []).filter(function (c) {
8108
+ return '<li><strong>' + esc(item.label || 'Attention') + '</strong>' +
6177
8109
 
6178
- return c.evidenceClass === 'missing-repo-fix' || c.status === 'missing' || c.status === 'failed';
8110
+ (item.detail ? '<span>' + esc(item.detail) + '</span>' : '') +
6179
8111
 
6180
- });
8112
+ (item.next ? '<b>' + esc(item.next) + '</b>' : '') +
6181
8113
 
6182
- if (missing[0] && missing[0].promptHint) return missing[0].promptHint;
8114
+ '</li>';
6183
8115
 
6184
- return setupPromptForProvider(areaKey, providerLabel);
8116
+ }).join('') +
6185
8117
 
6186
- }
8118
+ '</ul></details></li>'
6187
8119
 
8120
+ : '';
6188
8121
 
8122
+ return '<section class="studio-sidebar-contract__attention"><h4>Attention</h4><ul class="studio-sidebar-contract__attention-list">' + first + more + '</ul></section>';
6189
8123
 
6190
- function setupPromptForProvider(areaKey, providerLabel) {
8124
+ }
6191
8125
 
6192
- var areaLabel = LABELS[areaKey] || areaKey;
6193
8126
 
6194
- var provider = providerLabel || areaLabel;
6195
8127
 
6196
- return [
8128
+ function contractNextActionHtml(contract, prompt) {
6197
8129
 
6198
- 'Set up ' + provider + ' for the ' + areaLabel + ' area of this app safely.',
8130
+ var buttonText = (contract.missing || []).length ? 'Copy focused agent prompt' : (contract.manual || []).length ? 'Copy live check prompt' : 'Copy verification prompt';
6199
8131
 
6200
- '',
8132
+ 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
8133
 
6202
- 'First inspect package.json files, env examples, framework routes, provider helpers, server/client boundaries, and existing billing/auth/deployment patterns before editing.',
8134
+ var accessClass = contract.hasMcpAccess ? ' studio-sidebar-contract__access-state--confirmed' : ' studio-sidebar-contract__access-state--needs-connection';
6203
8135
 
6204
- '',
8136
+ var accessLabel = contract.hasMcpAccess ? 'Available' : 'Connect';
6205
8137
 
6206
- 'Implement the smallest useful setup:',
8138
+ return '<div class="studio-sidebar-contract__next-action">' +
6207
8139
 
6208
- '1. Add the right ' + provider + ' package or SDK only if it is missing.',
8140
+ '<strong>Next best action</strong>' +
6209
8141
 
6210
- '2. Document required environment variables in safe examples, without reading or exposing real secrets.',
8142
+ '<p>' + esc(note) + '</p>' +
6211
8143
 
6212
- '3. Add server-side integration points, route handlers, webhooks, or helpers that match this repo structure.',
8144
+ '<div class="studio-sidebar-contract__access-state' + accessClass + '"><span>MCP/API access</span><b>' + esc(accessLabel) + '</b></div>' +
6213
8145
 
6214
- '4. Keep external provider dashboard setup explicit as a manual step.',
8146
+ '<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
8147
 
6216
- '',
8148
+ '</div>';
6217
8149
 
6218
- 'Constraints:',
8150
+ }
6219
8151
 
6220
- '- Do not rewrite unrelated product, auth, payment, database, deployment, or analytics code.',
6221
8152
 
6222
- '- Do not put secret keys in client code, public env variables, or browser-executed files.',
6223
8153
 
6224
- '- Do not claim live provider configuration is complete from repo changes alone.',
8154
+ function selectedNodeContractHtml(contract) {
6225
8155
 
6226
- '',
8156
+ var prompt = focusedContractPrompt(contract);
6227
8157
 
6228
- 'Verification:',
8158
+ return '<section class="studio-sidebar-contract" aria-label="Selected node production checklist">' +
6229
8159
 
6230
- '- Run the relevant TypeScript/build/test command for this repo.',
8160
+ contractReadinessHtml(contract) +
6231
8161
 
6232
- '- Summarize repo changes and list dashboard/provider checks still needed.',
8162
+ contractRepoSignalsHtml(contract) +
6233
8163
 
6234
- '- Re-run VibeRaven so repo evidence can move from setup needed to verified.'
8164
+ contractAttentionHtml(contract) +
6235
8165
 
6236
- ].join('\\n');
8166
+ contractNextActionHtml(contract, prompt) +
6237
8167
 
8168
+ '</section>';
6238
8169
 
6239
8170
  }
6240
8171
 
6241
8172
 
6242
8173
 
6243
- function setupActionsHtml(areaKey, missions, providerLabel, automation) {
8174
+ function setupActionsHtml(areaKey, missions, providerLabel, automation, currentProvider) {
6244
8175
 
6245
8176
  var prompt = buildPrompt(areaKey, missions, providerLabel, automation);
6246
8177
 
@@ -6264,6 +8195,8 @@ var PANEL_CLIENT_SCRIPT = `
6264
8195
 
6265
8196
  var supportsMcp = Boolean(mcpProvider);
6266
8197
 
8198
+ var providerKey = iconProviderKey(areaKey, currentProvider, missions[0], providerLabel);
8199
+
6267
8200
  var title = esc(providerLabel || LABELS[areaKey] || areaKey) + (isManualOnly ? ' manual check' : hasFixes ? ' fix prompt' : ' setup');
6268
8201
 
6269
8202
  var meta = supportsMcp ? 'MCP verification available' : 'Prompt only';
@@ -6286,7 +8219,13 @@ var PANEL_CLIENT_SCRIPT = `
6286
8219
 
6287
8220
  return '<section class="studio-setup-actions" aria-label="Setup actions">' +
6288
8221
 
6289
- '<div class="studio-setup-actions__head"><strong>' + title + '</strong><span>' + meta + '</span></div>' +
8222
+ '<div class="studio-setup-actions__head">' +
8223
+
8224
+ '<span class="studio-setup-actions__logo provider-logo' + logoClass(providerKey) + '" title="' + attrEsc(providerLabel || providerKey) + '" aria-hidden="true">' + logoHtml(providerKey, providerLabel) + '</span>' +
8225
+
8226
+ '<div class="studio-setup-actions__title"><strong>' + title + '</strong><span>' + meta + '</span></div>' +
8227
+
8228
+ '</div>' +
6290
8229
 
6291
8230
  '<p class="studio-setup-actions__copy">' + esc(copy) + '</p>' +
6292
8231
 
@@ -6342,39 +8281,40 @@ var PANEL_CLIENT_SCRIPT = `
6342
8281
 
6343
8282
  var repoItems = [
6344
8283
 
6345
- { label: provider + ' package or SDK installed', detail: 'VibeRaven has not found repo evidence for this setup path yet.' },
8284
+ { label: provider + ' repo wiring', detail: 'Not checked' },
6346
8285
 
6347
- { label: provider + ' env names documented', detail: 'Use safe examples only. Do not expose real provider secrets.' },
8286
+ { label: provider + ' env names', detail: 'Unknown' },
6348
8287
 
6349
- { label: provider + ' server integration or route found', detail: 'Add provider code on the server side before rescanning.' }
8288
+ { label: provider + ' server route', detail: 'Unknown' }
6350
8289
 
6351
8290
  ];
6352
8291
 
6353
8292
  var manualItems = [
6354
8293
 
6355
- { label: 'Production ' + provider + ' account and product checked', detail: 'Dashboard confirmation stays manual unless a trusted MCP verifier is configured.' },
8294
+ { label: 'Production account', detail: 'Not checked' },
6356
8295
 
6357
- { label: 'Production webhook or provider credentials checked', detail: 'Repo evidence cannot prove live provider settings by itself.' }
8296
+ { label: 'Webhook or credentials', detail: 'Not checked' }
6358
8297
 
6359
8298
  ];
6360
8299
 
6361
8300
  var external = automation && automation.mcpProvider
6362
8301
 
6363
- ? groupHtml('MCP verifier', [{ label: provider + ' MCP verifier available', detail: 'Use read-only MCP verification when already configured by the IDE.' }], 'external')
8302
+ ? 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
8303
 
6365
8304
  : '';
6366
8305
 
6367
- return '<section class="studio-verification studio-provider-readiness" aria-label="' + esc(provider) + ' setup readiness">' +
8306
+ return '<section class="studio-verification studio-provider-readiness" aria-label="' + attrEsc(provider + ' setup readiness') + '">' +
6368
8307
 
6369
8308
  '<h3 class="studio-verification__title">' + esc(provider) + '</h3>' +
6370
8309
 
6371
- readinessMetersHtml(0, 0, 'Not checked') +
8310
+ readinessMetersHtml(0, 0, 'Not checked', provider) +
6372
8311
 
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>' +
6374
8312
 
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>' +
8313
+ repoEvidenceCardHtml(repoItems.map(function (item) {
6376
8314
 
6377
- groupHtml('Repo setup needed', repoItems, 'missing') +
8315
+ return { label: item.label, detail: item.detail, tone: 'missing' };
8316
+
8317
+ }), repoItems.length) +
6378
8318
 
6379
8319
  external +
6380
8320
 
@@ -6386,18 +8326,24 @@ var PANEL_CLIENT_SCRIPT = `
6386
8326
 
6387
8327
 
6388
8328
 
6389
- function readinessMetersHtml(repoPercent, providerPercent, providerStatus) {
8329
+ function readinessMetersHtml(repoPercent, providerPercent, providerStatus, providerLabel) {
6390
8330
 
6391
8331
  var repo = Math.max(0, Math.min(100, Math.round(repoPercent || 0)));
6392
8332
 
6393
8333
  var provider = Math.max(0, Math.min(100, Math.round(providerPercent || 0)));
6394
8334
  var providerValue = providerStatus || (provider + '%');
6395
8335
 
6396
- return '<div class="studio-provider-readiness__meters" aria-label="Provider readiness meters">' +
8336
+ return '<div class="verification-card studio-mission-card studio-mission-card--provider" aria-label="Repo and live provider readiness">' +
8337
+
8338
+ '<span class="studio-mission-card__title">' + esc(providerLabel || 'Provider') + '</span>' +
8339
+
8340
+ '<div class="verification-meter studio-mission-card__meters">' +
8341
+
8342
+ '<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>' +
6397
8343
 
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>' +
8344
+ '<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>' +
6399
8345
 
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>' +
8346
+ '</div>' +
6401
8347
 
6402
8348
  '</div>';
6403
8349
 
@@ -6413,6 +8359,90 @@ var PANEL_CLIENT_SCRIPT = `
6413
8359
 
6414
8360
  }
6415
8361
 
8362
+ function checkGroupKey(check) {
8363
+
8364
+ if (check.evidenceClass === 'manual-dashboard') return 'manual-dashboard';
8365
+
8366
+ if (check.evidenceSource === 'provider' || check.evidenceSource === 'mcp' || check.evidenceClass === 'mcp-verifier' || check.status === 'needs-connection' || check.status === 'unknown') return 'mcp-verifier';
8367
+
8368
+ if (check.evidenceClass === 'repo-verified' || check.status === 'passed') return 'repo-verified';
8369
+
8370
+ return 'missing-repo-fix';
8371
+
8372
+ }
8373
+
8374
+ function providerChecksForMission(mission) {
8375
+
8376
+ return (mission.checks || []).filter(function (check) {
8377
+
8378
+ return check.evidenceSource === 'provider' || check.evidenceSource === 'mcp' || check.evidenceClass === 'mcp-verifier';
8379
+
8380
+ });
8381
+
8382
+ }
8383
+
8384
+ function providerStatusValue(mission, providerPercent) {
8385
+
8386
+ var checks = providerChecksForMission(mission);
8387
+
8388
+ if (!checks.length) return 'No live checks';
8389
+
8390
+ var needsMcp = checks.some(function (check) {
8391
+
8392
+ return check.status === 'needs-connection' || check.verificationStatus === 'needs_mcp';
8393
+
8394
+ });
8395
+
8396
+ if (needsMcp) return 'Needs MCP';
8397
+
8398
+ return providerPercent > 0 ? providerPercent + '%' : 'Not checked';
8399
+
8400
+ }
8401
+
8402
+ function diffHtml(mission) {
8403
+
8404
+ var layer = artifact && artifact.missionGraph && artifact.missionGraph.verificationLayer;
8405
+
8406
+ var diffs = layer && Array.isArray(layer.diffs) ? layer.diffs : [];
8407
+
8408
+ var provider = normalizeProviderToken(mission.provider || '');
8409
+
8410
+ var area = mission.area || '';
8411
+
8412
+ var matches = diffs.filter(function (diff) {
8413
+
8414
+ return normalizeProviderToken(diff.provider || '') === provider && (!area || diff.area === area);
8415
+
8416
+ });
8417
+
8418
+ if (!matches.length) return '';
8419
+
8420
+ var rows = matches.slice(0, 4).map(function (diff) {
8421
+
8422
+ return '<div class="studio-mismatch-card__row">' +
8423
+
8424
+ '<span class="studio-mismatch-card__title">' + esc(diff.title || 'Provider mismatch') + '</span>' +
8425
+
8426
+ '<div class="studio-mismatch-card__comparison">' +
8427
+
8428
+ '<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>' +
8429
+
8430
+ '<span class="studio-mismatch-card__side studio-mismatch-card__side--live"><b>Live</b><em>' + esc(diff.providerActual || 'Not verified') + '</em></span>' +
8431
+
8432
+ '</div></div>';
8433
+
8434
+ }).join('');
8435
+
8436
+ return '<div class="studio-mismatch-card">' +
8437
+
8438
+ '<div class="studio-mismatch-card__head"><strong>Mismatch</strong><span class="studio-mismatch-card__count">' + matches.length + '</span></div>' +
8439
+
8440
+ '<p class="studio-mismatch-card__note">Repo evidence is present, but the live provider still needs MCP or dashboard confirmation.</p>' +
8441
+
8442
+ '<div class="studio-mismatch-card__list">' + rows + '</div></div>';
8443
+
8444
+ }
8445
+
6416
8446
 
6417
8447
 
6418
8448
  function groupHtml(label, items, tone) {
@@ -6423,7 +8453,7 @@ var PANEL_CLIENT_SCRIPT = `
6423
8453
 
6424
8454
  var rows = slice.map(function (item) {
6425
8455
 
6426
- var title = item.detail ? ' title="' + esc(item.detail) + '"' : '';
8456
+ var title = item.detail ? ' title="' + attrEsc(item.detail) + '"' : '';
6427
8457
 
6428
8458
  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
8459
 
@@ -6445,6 +8475,44 @@ var PANEL_CLIENT_SCRIPT = `
6445
8475
 
6446
8476
  }
6447
8477
 
8478
+ function repoEvidenceCardHtml(repoItems, missingCount) {
8479
+
8480
+ if (!repoItems.length) return '';
8481
+
8482
+ var rows = repoItems.slice(0, 7).map(function (item) {
8483
+
8484
+ var tone = item.tone || 'verified';
8485
+ var statusLabel = tone === 'missing' ? 'Fix needed' : 'Verified';
8486
+ var title = item.detail ? ' title="' + attrEsc(item.detail) + '"' : '';
8487
+
8488
+ return '<li class="studio-mission-card__check studio-mission-card__check--' + esc(tone) + '"' + title + '>' +
8489
+
8490
+ '<b class="studio-mission-card__check-label">' + esc(item.label) + '</b>' +
8491
+
8492
+ '<em class="studio-mission-card__check-status">' + esc(statusLabel) + '</em>' +
8493
+
8494
+ '</li>';
8495
+
8496
+ }).join('');
8497
+
8498
+ return '<div class="repo-evidence-card studio-mission-card studio-mission-card--repo">' +
8499
+
8500
+ '<span class="studio-mission-card__title">' +
8501
+
8502
+ (missingCount > 0
8503
+
8504
+ ? missingCount + ' repo fix' + (missingCount === 1 ? '' : 'es') + ' in the codebase.'
8505
+
8506
+ : repoItems.length + ' repo check' + (repoItems.length === 1 ? '' : 's') + ' verified in the codebase.') +
8507
+
8508
+ '</span>' +
8509
+
8510
+ '<ul class="studio-mission-card__check-list">' + rows + '</ul>' +
8511
+
8512
+ '</div>';
8513
+
8514
+ }
8515
+
6448
8516
 
6449
8517
 
6450
8518
  function missionBlockHtml(missions, providerLabel) {
@@ -6467,65 +8535,54 @@ var PANEL_CLIENT_SCRIPT = `
6467
8535
 
6468
8536
  mission.checks.forEach(function (check) {
6469
8537
 
6470
- var bucket = groups[check.evidenceClass] ? check.evidenceClass : 'manual-dashboard';
8538
+ var bucket = checkGroupKey(check);
6471
8539
 
6472
8540
  groups[bucket].push(checkToItem(check));
6473
8541
 
6474
8542
  });
6475
8543
 
6476
8544
  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) {
6479
-
6480
- return check.evidenceClass === 'manual-dashboard' && (check.status === 'passed' || check.status === 'user-confirmed');
6481
-
6482
- }).length;
6483
- var providerPercent = manualTotal ? Math.round((manualDone / manualTotal) * 100) : 0;
6484
- var providerValue = manualTotal ? (manualDone > 0 ? providerPercent + '%' : 'Not checked') : 'No live checks';
6485
-
6486
- var summary =
8545
+ var providerChecks = providerChecksForMission(mission);
8546
+ var providerPercent = typeof mission.providerReadinessPercent === 'number'
8547
+ ? Math.max(0, Math.min(100, Math.round(mission.providerReadinessPercent)))
8548
+ : 0;
8549
+ var providerValue = providerStatusValue(mission, providerPercent);
8550
+ var repoPercent = typeof mission.repoReadinessPercent === 'number'
8551
+ ? Math.max(0, Math.min(100, Math.round(mission.repoReadinessPercent)))
8552
+ : (mission.readinessPercent || 0);
8553
+ var repoItems = groups['missing-repo-fix'].map(function (item) {
6487
8554
 
6488
- 'Stack scanner: ' + groups['repo-verified'].length + ' / ' + Math.max(actionable, 1) +
8555
+ return { label: item.label, detail: item.detail, tone: 'missing' };
6489
8556
 
6490
- ' repo checks verified (' + (mission.readinessPercent || 0) + '%).';
8557
+ }).concat(groups['repo-verified'].map(function (item) {
6491
8558
 
6492
- if (groups['missing-repo-fix'].length > 0) {
8559
+ return { label: item.label, detail: item.detail, tone: 'verified' };
6493
8560
 
6494
- summary += ' Fix ' + groups['missing-repo-fix'].length + ' repo item' +
6495
-
6496
- (groups['missing-repo-fix'].length === 1 ? '' : 's') + ', then rescan.';
6497
-
6498
- }
8561
+ }));
6499
8562
 
6500
8563
  var body =
6501
8564
 
6502
- groupHtml('Repo verified', groups['repo-verified'], 'found') +
8565
+ repoEvidenceCardHtml(repoItems, groups['missing-repo-fix'].length) +
6503
8566
 
6504
- groupHtml('Stack fixes needed', groups['missing-repo-fix'], 'missing') +
8567
+ groupHtml('Provider live check', groups['mcp-verifier'], 'external') +
6505
8568
 
6506
- groupHtml('MCP verifier', groups['mcp-verifier'], 'external') +
8569
+ groupHtml('Manual dashboard check', groups['manual-dashboard'], 'manual') +
6507
8570
 
6508
- groupHtml('Manual dashboard check', groups['manual-dashboard'], 'manual');
8571
+ diffHtml(mission);
6509
8572
 
6510
8573
  if (!body) return '';
6511
8574
 
6512
8575
  return '<section class="studio-verification studio-wiring studio-mission-graph" aria-label="Mission evidence">' +
6513
8576
 
6514
- '<h3 class="studio-verification__title">' + esc(providerLabel || mission.providerLabel || 'Detected evidence') + '</h3>' +
6515
-
6516
- readinessMetersHtml(mission.readinessPercent || 0, providerPercent, providerValue) +
8577
+ readinessMetersHtml(repoPercent, providerPercent, providerValue, providerLabel || mission.providerLabel || 'Detected evidence') +
6517
8578
 
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>' +
6519
-
6520
- '<p class="studio-wiring__summary">' + esc(summary) + '</p>' + body + '</section>';
8579
+ body + '</section>';
6521
8580
 
6522
8581
  }
6523
8582
 
6524
8583
 
6525
8584
 
6526
- function render(areaKey, providerOverride) {
6527
-
6528
- if (providerOverride) selectedProviders[areaKey] = providerOverride;
8585
+ function render(areaKey) {
6529
8586
 
6530
8587
  var area = areas.find(function (a) { return a.key === areaKey; });
6531
8588
 
@@ -6559,6 +8616,10 @@ var PANEL_CLIENT_SCRIPT = `
6559
8616
 
6560
8617
  var automation = automationFor(areaKey, mission, current);
6561
8618
 
8619
+ var categoryGaps = gaps.filter(function (gap) { return gap.primaryMapCategory === areaKey; });
8620
+
8621
+ var contract = buildCliSidebarContract(areaKey, label, mission, categoryGaps, providerLabel, automation, current);
8622
+
6562
8623
 
6563
8624
 
6564
8625
  panel.innerHTML =
@@ -6575,31 +8636,29 @@ var PANEL_CLIENT_SCRIPT = `
6575
8636
 
6576
8637
  addedSetupPathHtml(areaKey, current, providerLabel, missions) +
6577
8638
 
6578
- setupActionsHtml(areaKey, panelMissions, providerLabel, automation) +
8639
+ setupActionsHtml(areaKey, panelMissions, providerLabel, automation, current) +
6579
8640
 
6580
- setupReadinessHtml(areaKey, current, providerLabel, panelMissions, automation) +
8641
+ setupReadinessHtml(areaKey, current, providerLabel, panelMissions, automation) +
6581
8642
 
6582
- missionBlockHtml(panelMissions, providerLabel) +
6583
-
6584
- '</div>';
8643
+ selectedNodeContractHtml(contract) +
6585
8644
 
8645
+ '</div>';
6586
8646
 
6587
8647
 
6588
- panel.querySelectorAll('[data-copy-prompt]').forEach(function (btn) {
6589
8648
 
6590
- btn.addEventListener('click', function () {
8649
+ panel.querySelectorAll('[data-provider-option]').forEach(function (tile) {
6591
8650
 
6592
- var text = btn.getAttribute('data-copy-prompt') || '';
8651
+ tile.addEventListener('click', function () {
6593
8652
 
6594
- navigator.clipboard.writeText(text).then(function () {
8653
+ var providerOption = tile.getAttribute('data-provider-option') || '';
6595
8654
 
6596
- var label = btn.textContent;
8655
+ if (!providerOption) return;
6597
8656
 
6598
- btn.textContent = 'Copied';
8657
+ selectedProviders[areaKey] = providerOption;
6599
8658
 
6600
- setTimeout(function () { btn.textContent = label; }, 1200);
8659
+ syncMapNode(areaKey, providerOption);
6601
8660
 
6602
- });
8661
+ render(areaKey);
6603
8662
 
6604
8663
  });
6605
8664
 
@@ -6607,15 +8666,21 @@ var PANEL_CLIENT_SCRIPT = `
6607
8666
 
6608
8667
 
6609
8668
 
6610
- panel.querySelectorAll('[data-switch-provider]').forEach(function (btn) {
8669
+ panel.querySelectorAll('[data-copy-prompt]').forEach(function (btn) {
6611
8670
 
6612
8671
  btn.addEventListener('click', function () {
6613
8672
 
6614
- var a = btn.getAttribute('data-switch-area');
8673
+ var text = btn.getAttribute('data-copy-prompt') || '';
8674
+
8675
+ navigator.clipboard.writeText(text).then(function () {
8676
+
8677
+ var label = btn.textContent;
8678
+
8679
+ btn.textContent = 'Copied';
6615
8680
 
6616
- var p = btn.getAttribute('data-switch-provider');
8681
+ setTimeout(function () { btn.textContent = label; }, 1200);
6617
8682
 
6618
- render(a, p);
8683
+ });
6619
8684
 
6620
8685
  });
6621
8686
 
@@ -6670,46 +8735,79 @@ var PANEL_CLIENT_SCRIPT = `
6670
8735
  `;
6671
8736
 
6672
8737
  // src/report/providerLogos.ts
8738
+ 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
8739
  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>',
8740
+ 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>',
8741
+ 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>',
8742
+ 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>',
8743
+ auth0: '<svg viewBox="0 0 24 24" aria-hidden="true"><path 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>',
8744
+ "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
8745
  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>',
8746
+ "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>',
8747
+ 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>',
8748
+ 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>',
8749
+ 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>',
8750
+ 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>',
8751
+ 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>',
8752
+ 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
8753
  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
8754
  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
8755
  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>',
8756
+ 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
8757
  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>',
8758
+ 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>',
8759
+ 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>',
8760
+ 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
8761
  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
8762
  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
8763
  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
8764
  "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
8765
  "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
8766
  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>',
8767
+ 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>',
8768
+ 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>',
8769
+ 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>',
8770
+ 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
8771
  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
8772
  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
8773
  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
8774
  "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>',
8775
+ 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
8776
  "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>'
8777
+ "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>',
8778
+ "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>',
8779
+ 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>',
8780
+ 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>'
8781
+ };
8782
+ var PROVIDER_NODE_TONES = {
8783
+ supabase: ["#3ecf8e", "rgba(62, 207, 142, 0.34)"],
8784
+ clerk: ["#6c47ff", "rgba(108, 71, 255, 0.34)"],
8785
+ authjs: ["#6c47ff", "rgba(108, 71, 255, 0.3)"],
8786
+ auth0: ["#eb5424", "rgba(235, 84, 36, 0.34)"],
8787
+ "better-auth": ["#111827", "rgba(17, 24, 39, 0.22)"],
8788
+ firebase: ["#ffa000", "rgba(255, 160, 0, 0.34)"],
8789
+ neon: ["#00e599", "rgba(0, 229, 153, 0.34)"],
8790
+ planetscale: ["#111827", "rgba(17, 24, 39, 0.22)"],
8791
+ mongodb: ["#47a248", "rgba(71, 162, 72, 0.34)"],
8792
+ turso: ["#4ff8d2", "rgba(79, 248, 210, 0.3)"],
8793
+ stripe: ["#635bff", "rgba(99, 91, 255, 0.34)"],
8794
+ paddle: ["#fddd35", "rgba(253, 221, 53, 0.34)"],
8795
+ polar: ["#1d4aff", "rgba(29, 74, 255, 0.3)"],
8796
+ "lemon-squeezy": ["#ffc233", "rgba(255, 194, 51, 0.34)"],
8797
+ vercel: ["#111827", "rgba(17, 24, 39, 0.22)"],
8798
+ netlify: ["#00c7b7", "rgba(0, 199, 183, 0.32)"],
8799
+ cloudflare: ["#f38020", "rgba(243, 128, 32, 0.34)"],
8800
+ sentry: ["#a855f7", "rgba(168, 85, 247, 0.34)"],
8801
+ posthog: ["#f54e00", "rgba(245, 78, 0, 0.34)"],
8802
+ playwright: ["#45ba4b", "rgba(69, 186, 75, 0.32)"],
8803
+ vitest: ["#fcc72b", "rgba(252, 199, 43, 0.34)"],
8804
+ figma: ["#ff7262", "rgba(255, 114, 98, 0.38)"],
8805
+ react: ["#61dafb", "rgba(97, 218, 251, 0.38)"],
8806
+ vue: ["#42b883", "rgba(66, 184, 131, 0.34)"],
8807
+ angular: ["#dd0031", "rgba(221, 0, 49, 0.34)"],
8808
+ nodejs: ["#83cd29", "rgba(131, 205, 41, 0.34)"],
8809
+ github: ["#111827", "rgba(17, 24, 39, 0.22)"],
8810
+ gitlab: ["#fc6d26", "rgba(252, 109, 38, 0.34)"]
6713
8811
  };
6714
8812
  var ALIASES = {
6715
8813
  authjs: "authjs",
@@ -6742,6 +8840,8 @@ var ALIASES = {
6742
8840
  auth0: "auth0",
6743
8841
  "better-auth": "better-auth",
6744
8842
  betterauth: "better-auth",
8843
+ firebase: "firebase",
8844
+ firestore: "firebase",
6745
8845
  "mongodb atlas": "mongodb",
6746
8846
  logrocket: "logrocket",
6747
8847
  sentry: "sentry",
@@ -6777,7 +8877,23 @@ var ALIASES = {
6777
8877
  routes: "route-map",
6778
8878
  ratelimit: "rate-limit"
6779
8879
  };
6780
- var INLINE_ONLY_LOGO_KEYS = /* @__PURE__ */ new Set(["better-auth", "paddle", "polar"]);
8880
+ var INLINE_ONLY_LOGO_KEYS = /* @__PURE__ */ new Set([
8881
+ "auth0",
8882
+ "authjs",
8883
+ "better-auth",
8884
+ "clerk",
8885
+ "cloudflare",
8886
+ "firebase",
8887
+ "lemon-squeezy",
8888
+ "netlify",
8889
+ "paddle",
8890
+ "playwright",
8891
+ "polar",
8892
+ "posthog",
8893
+ "sentry",
8894
+ "svelte",
8895
+ "vitest"
8896
+ ]);
6781
8897
  var PROVIDER_ASSET_FILES = {
6782
8898
  authjs: "provider-authjs.svg",
6783
8899
  logrocket: "provider-logrocket.svg",
@@ -6797,6 +8913,7 @@ var PROVIDER_ICON_URLS = {
6797
8913
  supabase: "https://cdn.simpleicons.org/supabase/3ECF8E",
6798
8914
  clerk: "https://cdn.simpleicons.org/clerk/6C47FF",
6799
8915
  auth0: "https://cdn.simpleicons.org/auth0/EB5424",
8916
+ firebase: "https://cdn.simpleicons.org/firebase/FFCA28",
6800
8917
  neon: "https://cdn.simpleicons.org/neon/00E599",
6801
8918
  planetscale: "https://cdn.simpleicons.org/planetscale/000000",
6802
8919
  mongodb: "https://cdn.simpleicons.org/mongodb/47A248",
@@ -6833,16 +8950,26 @@ var BRAND_LOGO_KEYS = /* @__PURE__ */ new Set([
6833
8950
  "better-auth",
6834
8951
  "clerk",
6835
8952
  "gitlab",
8953
+ "lemon-squeezy",
6836
8954
  "logrocket",
8955
+ "netlify",
6837
8956
  "auth0",
6838
8957
  "paddle",
6839
8958
  "playwright",
6840
8959
  "polar",
6841
8960
  "posthog",
8961
+ "sentry",
6842
8962
  "stripe",
8963
+ "svelte",
6843
8964
  "vitest"
6844
8965
  ]);
8966
+ function isUnselectedProviderToken(raw, compact) {
8967
+ return !compact || compact === "notselected" || raw === "not selected" || raw === "none";
8968
+ }
6845
8969
  function resolveLogoKey(raw, compact) {
8970
+ if (isUnselectedProviderToken(raw, compact)) {
8971
+ return "";
8972
+ }
6846
8973
  if (ALIASES[raw]) {
6847
8974
  return ALIASES[raw];
6848
8975
  }
@@ -6880,13 +9007,13 @@ function normalizeProviderKey2(providerOrLabel) {
6880
9007
  function resolveProviderLogoKey(...candidates) {
6881
9008
  for (const candidate of candidates) {
6882
9009
  const key = normalizeProviderKey2(candidate);
6883
- if (key && PROVIDER_LOGOS[key]) {
9010
+ if (key && key !== "notselected" && PROVIDER_LOGOS[key]) {
6884
9011
  return key;
6885
9012
  }
6886
9013
  }
6887
9014
  for (const candidate of candidates) {
6888
9015
  const key = normalizeProviderKey2(candidate);
6889
- if (key) {
9016
+ if (key && key !== "notselected") {
6890
9017
  return key;
6891
9018
  }
6892
9019
  }
@@ -6894,7 +9021,7 @@ function resolveProviderLogoKey(...candidates) {
6894
9021
  }
6895
9022
  function providerLogoClass(providerOrLabel, ...moreCandidates) {
6896
9023
  const key = resolveProviderLogoKey(providerOrLabel, ...moreCandidates) || normalizeProviderKey2(providerOrLabel);
6897
- if (!key) {
9024
+ if (!key || key === "notselected") {
6898
9025
  return "";
6899
9026
  }
6900
9027
  return ` provider-logo--${key}${BRAND_LOGO_KEYS.has(key) ? " provider-logo--brand" : ""}`;
@@ -6912,16 +9039,14 @@ function providerLogoHtml(providerOrLabel, labelFallback, ...moreCandidates) {
6912
9039
  if (assetUrl) {
6913
9040
  return providerIconImgHtml(assetUrl, key);
6914
9041
  }
9042
+ if (svg) {
9043
+ return svg;
9044
+ }
6915
9045
  const iconUrl = key && PROVIDER_ICON_URLS[key];
6916
9046
  if (iconUrl) {
6917
9047
  return providerIconImgHtml(iconUrl, key);
6918
9048
  }
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>`;
9049
+ return PROVIDER_LOGO_FALLBACK_SVG;
6925
9050
  }
6926
9051
  var PROVIDER_BENEFITS = {
6927
9052
  supabase: "Best when you want auth, data, and storage in one stack.",
@@ -6946,6 +9071,7 @@ var PROVIDER_BENEFITS = {
6946
9071
  sentry: "Best first choice for production errors and traces.",
6947
9072
  posthog: "Best for product analytics and funnel verification.",
6948
9073
  auth0: "Enterprise identity with rules, MFA, and social login.",
9074
+ firebase: "Good fit when auth, Firestore, and hosting stay in one Google stack.",
6949
9075
  "better-auth": "Type-safe auth you own in your codebase.",
6950
9076
  playwright: "Best for browser-flow verification and UI regression checks.",
6951
9077
  logrocket: "Best when session replay is the missing evidence.",
@@ -6973,7 +9099,9 @@ function providerLogosPayloadJson() {
6973
9099
  inlineOnly: [...INLINE_ONLY_LOGO_KEYS],
6974
9100
  aliases: ALIASES,
6975
9101
  benefits: PROVIDER_BENEFITS,
6976
- brandKeys: [...BRAND_LOGO_KEYS]
9102
+ brandKeys: [...BRAND_LOGO_KEYS],
9103
+ nodeTones: PROVIDER_NODE_TONES,
9104
+ fallbackSvg: PROVIDER_LOGO_FALLBACK_SVG
6977
9105
  }).replace(/</g, "\\u003c");
6978
9106
  }
6979
9107
 
@@ -7010,6 +9138,23 @@ function defaultAreaKey(artifact) {
7010
9138
  }
7011
9139
  return areas[0]?.key ?? "frontend";
7012
9140
  }
9141
+ function providerLabelForArea(artifact, areaKey, mission, selectedOverride) {
9142
+ if (selectedOverride) {
9143
+ const token = normalizeProviderKey2(selectedOverride);
9144
+ const options = artifact.providerOptions?.[areaKey] ?? [];
9145
+ const match = options.find(
9146
+ (option) => normalizeProviderKey2(option.provider) === token || normalizeProviderKey2(option.label) === token
9147
+ );
9148
+ if (match?.label) {
9149
+ return match.label;
9150
+ }
9151
+ if (mission && missionMatchesProvider(mission, selectedOverride)) {
9152
+ return mission.providerLabel ?? selectedOverride;
9153
+ }
9154
+ return selectedOverride;
9155
+ }
9156
+ return mission?.providerLabel ?? "Not selected";
9157
+ }
7013
9158
  function buildAccountStripHtml(artifact) {
7014
9159
  if (!artifact.accountEmail) {
7015
9160
  return "";
@@ -7036,21 +9181,22 @@ function buildNodeHtml(artifact, meta, selectedAreaKey) {
7036
9181
  const area = (artifact.missionGraph.areas ?? []).find((a) => a.key === meta.key);
7037
9182
  const selectedOverride = artifact.selectedProviders?.[meta.key] ?? "";
7038
9183
  const mission = preferredMissionForArea(area, selectedOverride);
7039
- const selectedProvider = selectedOverride || mission?.provider || mission?.providerLabel || "";
7040
- const providerLabel3 = mission?.providerLabel ?? "Not selected";
9184
+ const providerLabel3 = providerLabelForArea(artifact, meta.key, mission, selectedOverride);
9185
+ const logoProvider = selectedOverride || mission?.provider || mission?.providerLabel || "";
7041
9186
  const readiness = mission?.readinessPercent ?? area?.readinessPercent ?? 0;
7042
9187
  const openChecks = openChecksForMission(mission);
9188
+ const repoOpenChecks = mission?.checks.filter(
9189
+ (check) => check.evidenceClass === "missing-repo-fix" || check.status === "missing" || check.status === "failed"
9190
+ ).length ?? 0;
9191
+ const providerOpenChecks = mission?.checks.filter(
9192
+ (check) => check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.evidenceClass === "mcp-verifier" || check.status === "needs-connection" || check.status === "unknown"
9193
+ ).length ?? 0;
7043
9194
  const modelGaps = gapCountForArea(artifact, meta.key);
7044
9195
  const stateClass = modelGaps > 0 ? " studio-node--critical" : openChecks > 0 ? " studio-node--warning" : area ? " studio-node--in-project" : "";
7045
9196
  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
- );
9197
+ const metaText = modelGaps > 0 ? `${modelGaps} stack fix${modelGaps === 1 ? "" : "es"}` : repoOpenChecks > 0 ? `${repoOpenChecks} stack fix${repoOpenChecks === 1 ? "" : "es"}` : providerOpenChecks > 0 ? "Live check" : `${readiness}% health`;
9198
+ const logoClass = providerLogoClass(logoProvider, mission?.key, mission?.providerLabel);
9199
+ const logoInner = providerLogoHtml(logoProvider, providerLabel3, mission?.key, mission?.providerLabel);
7054
9200
  const gapBadge = modelGaps > 0 ? `<span class="studio-node__raven-badge" role="presentation">Gap ${modelGaps}</span>` : "";
7055
9201
  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)}">
7056
9202
  <span class="studio-node__logo provider-logo${logoClass}" aria-hidden="true">${logoInner}</span>
@@ -7067,7 +9213,6 @@ function generateReportHtml(artifact) {
7067
9213
  const nodesHtml = CATEGORY_META.map((meta) => buildNodeHtml(hydrated, meta, defaultKey)).join("\n");
7068
9214
  const accountStrip = buildAccountStripHtml(hydrated);
7069
9215
  const logosJson = providerLogosPayloadJson();
7070
- const scannedLabel = escapeHtml(hydrated.scannedAt);
7071
9216
  return `<!DOCTYPE html>
7072
9217
  <html lang="en" class="cli-mission-report" data-surface="panel" data-skin="editorial">
7073
9218
  <head>
@@ -7087,8 +9232,10 @@ function generateReportHtml(artifact) {
7087
9232
  <div class="studio-shell" aria-label="VibeRaven Studio cockpit">
7088
9233
  <header class="studio-top-rail" aria-label="Studio status">
7089
9234
  <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>
9235
+ <div class="studio-top-rail__brand">
9236
+ <span class="studio-top-rail__wordmark">VIBERAVEN</span>
9237
+ <span class="studio-top-rail__label">MISSION MAP</span>
9238
+ </div>
7092
9239
  </header>
7093
9240
  <div class="studio-workspace">
7094
9241
  <main class="studio-map-canvas" aria-label="Interactive full-stack system map">
@@ -7097,7 +9244,7 @@ function generateReportHtml(artifact) {
7097
9244
  <div class="studio-connector-layer" aria-hidden="true"></div>
7098
9245
  <div class="studio-core-group">
7099
9246
  <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" />
9247
+ <span>VIBERAVEN</span>
7101
9248
  <strong>${hydrated.productionCorePercent}%</strong>
7102
9249
  <small>Production core</small>
7103
9250
  </div>
@@ -7149,16 +9296,22 @@ var REPORT_ASSET_FILES = [
7149
9296
  var INLINE_SECRET_PATTERNS = [
7150
9297
  /\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
9298
  /\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
9299
+ /\bghp_[A-Za-z0-9]{36,}\b/g,
9300
+ /\bgithub_pat_[A-Za-z0-9_]{50,}\b/g,
9301
+ /\bxox[bp]-[A-Za-z0-9-]{20,}\b/g,
9302
+ /\bxapp-[A-Za-z0-9-]{20,}\b/g,
9303
+ /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,
9304
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g
7153
9305
  ];
7154
- var SENSITIVE_ENV_KEYS = /^(?:OPENAI_API_KEY|OPENAI_KEY|ANTHROPIC_API_KEY|VIBERAVEN_ACCESS_TOKEN|VRAVEN_.*|SUPABASE_SERVICE_ROLE_KEY|.*_SECRET_KEY)$/i;
9306
+ var SENSITIVE_ENV_KEYS = /(?:^|_)(?:ACCESS_TOKEN|AUTHORIZATION|API_KEY|SECRET|SECRET_KEY|SERVICE_ROLE_KEY|TOKEN|PASSWORD|PRIVATE_KEY|CREDENTIALS?)(?:$|_)/i;
7155
9307
  function redactString(value) {
7156
9308
  let out = value;
7157
9309
  for (const pattern of INLINE_SECRET_PATTERNS) {
7158
- out = out.replace(pattern, "[REDACTED_SECRET]");
9310
+ out = out.replace(pattern, pattern.source.includes("PRIVATE KEY") ? "[REDACTED_PRIVATE_KEY]" : "[REDACTED_SECRET]");
7159
9311
  }
9312
+ out = out.replace(/\bAuthorization\s*:\s*([A-Za-z][A-Za-z0-9._-]*)\s+[^\s;,]+/gi, "Authorization: $1 [REDACTED]");
7160
9313
  return out.replace(
7161
- /\b([A-Za-z0-9_]*(?:API_KEY|SECRET|TOKEN|PASSWORD|PRIVATE_KEY)[A-Za-z0-9_]*)\s*=\s*["']?[^"'\s;,]+["']?/gi,
9314
+ /\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
9315
  "$1=[REDACTED]"
7163
9316
  );
7164
9317
  }
@@ -7397,6 +9550,15 @@ function gapTagColor(modelGaps, open) {
7397
9550
  }
7398
9551
  return import_picocolors.default.dim;
7399
9552
  }
9553
+ function manualActionCheckCount(artifact) {
9554
+ return (artifact.missionGraph.areas ?? []).reduce((areaTotal, area) => {
9555
+ return areaTotal + area.providerMissions.reduce((missionTotal, mission) => {
9556
+ return missionTotal + mission.checks.filter(
9557
+ (check) => check.evidenceClass === "manual-dashboard" || check.evidenceClass === "mcp-verifier" || check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.status === "needs-connection" || check.status === "unknown"
9558
+ ).length;
9559
+ }, 0);
9560
+ }, 0);
9561
+ }
7400
9562
  function printScanSummary(artifact, paths) {
7401
9563
  const pct = artifact.productionCorePercent;
7402
9564
  const gapCount = artifact.gaps.length;
@@ -7432,6 +9594,11 @@ function printScanSummary(artifact, paths) {
7432
9594
  console.log("");
7433
9595
  console.log(import_picocolors.default.dim("Press Enter in `viberaven` menu to rescan \xB7 `viberaven prompt` for top gap"));
7434
9596
  console.log(import_picocolors.default.dim("Agents: read .viberaven/agent-summary.md"));
9597
+ console.log(formatAgentStatus(READY, `Scan complete. Read ${paths.summaryPath} before changing code.`));
9598
+ const manualCount = manualActionCheckCount(artifact);
9599
+ if (manualCount > 0) {
9600
+ 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.`));
9601
+ }
7435
9602
  console.log("");
7436
9603
  }
7437
9604
 
@@ -7972,7 +10139,7 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
7972
10139
  var import_picocolors3 = __toESM(require_picocolors());
7973
10140
 
7974
10141
  // src/version.ts
7975
- var VERSION = "0.1.0-beta.1";
10142
+ var VERSION = "0.1.0-beta.2";
7976
10143
 
7977
10144
  // src/tui/runInteractive.ts
7978
10145
  async function formatStatusLine() {
@@ -8230,6 +10397,10 @@ function parseArgs(argv) {
8230
10397
  flags.help = true;
8231
10398
  continue;
8232
10399
  }
10400
+ if (arg === "--version" || arg === "-v") {
10401
+ flags.version = true;
10402
+ continue;
10403
+ }
8233
10404
  if (arg.startsWith("--")) {
8234
10405
  const key = arg.slice(2);
8235
10406
  const next = argv[i + 1];
@@ -8249,6 +10420,9 @@ function parseArgs(argv) {
8249
10420
  }
8250
10421
  return { command, flags, positional };
8251
10422
  }
10423
+ function formatScanJsonStdout(artifact) {
10424
+ return JSON.stringify(sanitizeArtifactForDisk(artifact), null, 2);
10425
+ }
8252
10426
  async function cmdLogin(flags) {
8253
10427
  const apiBaseUrl = resolveApiBaseUrl(typeof flags["api-url"] === "string" ? flags["api-url"] : void 0);
8254
10428
  await runDeviceLogin(apiBaseUrl);
@@ -8300,13 +10474,17 @@ async function cmdScan(flags, positional) {
8300
10474
  }
8301
10475
  return 2;
8302
10476
  }
8303
- console.error(result.message);
10477
+ if (result.kind === "auth_required" || result.kind === "session_invalid") {
10478
+ console.error(formatAgentStatus(LOGIN_REQUIRED, result.message));
10479
+ return 1;
10480
+ }
10481
+ console.error(formatAgentStatus(ERROR, result.message));
8304
10482
  return 1;
8305
10483
  }
8306
10484
  const artifact = await enrichArtifactWithAccount(result.artifact, apiBaseUrl, accessToken);
8307
10485
  const paths = await writeScanArtifacts({ artifact, cwd: workspacePath });
8308
10486
  if (flags.json) {
8309
- console.log(JSON.stringify(artifact, null, 2));
10487
+ console.log(formatScanJsonStdout(artifact));
8310
10488
  return 0;
8311
10489
  }
8312
10490
  printScanSummary(artifact, paths);
@@ -8429,6 +10607,10 @@ async function main() {
8429
10607
  printHelp();
8430
10608
  return 0;
8431
10609
  }
10610
+ if (flags.version || command === "version") {
10611
+ console.log(VERSION);
10612
+ return 0;
10613
+ }
8432
10614
  if (!command) {
8433
10615
  await runInteractiveSession();
8434
10616
  return 0;
@@ -8454,23 +10636,25 @@ async function main() {
8454
10636
  return cmdPrompt(flags, positional);
8455
10637
  case "stack":
8456
10638
  return cmdStack(positional);
8457
- case "--version":
8458
- case "-v":
8459
- case "version":
8460
- console.log(VERSION);
8461
- return 0;
8462
10639
  default:
8463
10640
  console.error(`Unknown command: ${command}`);
8464
10641
  printHelp();
8465
10642
  return 1;
8466
10643
  }
8467
10644
  }
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;
10645
+ if (require.main === module) {
10646
+ main().then((code) => {
10647
+ if (code !== 0) {
10648
+ process.exitCode = code;
10649
+ }
10650
+ }).catch((error) => {
10651
+ console.error(error instanceof Error ? error.message : String(error));
10652
+ process.exitCode = 1;
10653
+ });
10654
+ }
10655
+ // Annotate the CommonJS export names for ESM import in node:
10656
+ 0 && (module.exports = {
10657
+ formatScanJsonStdout,
10658
+ parseArgs
8475
10659
  });
8476
10660
  //# sourceMappingURL=cli.js.map