@viberaven/cli 1.1.3 → 1.1.5
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/SECURITY.md +53 -53
- package/assets/report/assets/provider-authjs.svg +5 -5
- package/assets/report/assets/provider-aws.svg +5 -5
- package/assets/report/assets/provider-logrocket.svg +4 -4
- package/assets/report/station.css +9 -146
- package/assets/report/station.js +24 -108
- package/dist/cli.js +811 -1927
- package/dist/cli.js.map +4 -4
- package/dist/report/assets/provider-authjs.svg +5 -5
- package/dist/report/assets/provider-aws.svg +5 -5
- package/dist/report/assets/provider-logrocket.svg +4 -4
- package/dist/report/station.css +9 -146
- package/dist/report/station.js +24 -108
- package/package.json +1 -2
- package/fixtures/demo-saas/.env.example +0 -3
- package/fixtures/demo-saas/app/api/stripe/webhook/route.ts +0 -12
- package/fixtures/demo-saas/package.json +0 -11
- package/fixtures/demo-saas/supabase/migrations/0001_init.sql +0 -6
package/dist/cli.js
CHANGED
|
@@ -31,9 +31,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
31
31
|
));
|
|
32
32
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
33
|
|
|
34
|
-
//
|
|
34
|
+
// ../../node_modules/picocolors/picocolors.js
|
|
35
35
|
var require_picocolors = __commonJS({
|
|
36
|
-
"
|
|
36
|
+
"../../node_modules/picocolors/picocolors.js"(exports2, module2) {
|
|
37
37
|
var p2 = process || {};
|
|
38
38
|
var argv = p2.argv || [];
|
|
39
39
|
var env = p2.env || {};
|
|
@@ -103,9 +103,9 @@ var require_picocolors = __commonJS({
|
|
|
103
103
|
}
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
//
|
|
106
|
+
// ../../node_modules/sisteransi/src/index.js
|
|
107
107
|
var require_src = __commonJS({
|
|
108
|
-
"
|
|
108
|
+
"../../node_modules/sisteransi/src/index.js"(exports2, module2) {
|
|
109
109
|
"use strict";
|
|
110
110
|
var ESC = "\x1B";
|
|
111
111
|
var CSI = `${ESC}[`;
|
|
@@ -170,8 +170,8 @@ __export(cli_exports, {
|
|
|
170
170
|
runScanCommand: () => runScanCommand
|
|
171
171
|
});
|
|
172
172
|
module.exports = __toCommonJS(cli_exports);
|
|
173
|
-
var
|
|
174
|
-
var
|
|
173
|
+
var import_promises19 = require("node:fs/promises");
|
|
174
|
+
var import_node_path25 = require("node:path");
|
|
175
175
|
|
|
176
176
|
// src/config.ts
|
|
177
177
|
var import_node_os = require("node:os");
|
|
@@ -528,8 +528,8 @@ var UPGRADE_REQUIRED = "UPGRADE_REQUIRED";
|
|
|
528
528
|
var MANUAL_ACTION_REQUIRED = "MANUAL_ACTION_REQUIRED";
|
|
529
529
|
var AGENT_ACTION = "AGENT_ACTION";
|
|
530
530
|
var ERROR = "ERROR";
|
|
531
|
-
function formatAgentStatus(
|
|
532
|
-
return `${
|
|
531
|
+
function formatAgentStatus(label, message) {
|
|
532
|
+
return `${label}: ${message}`;
|
|
533
533
|
}
|
|
534
534
|
|
|
535
535
|
// src/account.ts
|
|
@@ -621,12 +621,12 @@ function createOpenCommand(target, platform = process.platform) {
|
|
|
621
621
|
}
|
|
622
622
|
async function openWithSystemDefault(target) {
|
|
623
623
|
const { command, args, shell } = createOpenCommand(target);
|
|
624
|
-
await new Promise((
|
|
624
|
+
await new Promise((resolve5, reject) => {
|
|
625
625
|
const child = (0, import_node_child_process.spawn)(command, args, { stdio: "ignore", shell });
|
|
626
626
|
child.on("error", reject);
|
|
627
627
|
child.on("exit", (code) => {
|
|
628
628
|
if (code === 0) {
|
|
629
|
-
|
|
629
|
+
resolve5();
|
|
630
630
|
} else {
|
|
631
631
|
reject(new Error(`Could not open browser (exit ${code ?? "unknown"}). Open manually: ${target}`));
|
|
632
632
|
}
|
|
@@ -661,14 +661,11 @@ function promptGapCommand(gapId) {
|
|
|
661
661
|
function healPlanGapCommand(gapId) {
|
|
662
662
|
return `${PUBLIC_COMMAND} --heal --plan --gap ${gapId}`;
|
|
663
663
|
}
|
|
664
|
-
function healApplyGapCommand(gapId) {
|
|
665
|
-
return `${PUBLIC_COMMAND} --heal --apply --gap ${gapId}`;
|
|
666
|
-
}
|
|
667
664
|
|
|
668
665
|
// src/auth.ts
|
|
669
666
|
var PUBLIC_LOGIN_COMMAND = `${PUBLIC_COMMAND} login`;
|
|
670
667
|
function sleep(ms) {
|
|
671
|
-
return new Promise((
|
|
668
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
672
669
|
}
|
|
673
670
|
async function runDeviceLogin(apiBaseUrl) {
|
|
674
671
|
const signIn = await startManagedSignIn(apiBaseUrl);
|
|
@@ -1097,7 +1094,7 @@ function createGitignoreMatcher(gitignoreContent) {
|
|
|
1097
1094
|
const rules = gitignoreContent.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("!")).map(parseGitignoreRule);
|
|
1098
1095
|
return (relPath) => {
|
|
1099
1096
|
const normalized = relPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
1100
|
-
return rules.some((
|
|
1097
|
+
return rules.some((rule) => ruleMatchesPath(rule, normalized));
|
|
1101
1098
|
};
|
|
1102
1099
|
}
|
|
1103
1100
|
function parseGitignoreRule(raw) {
|
|
@@ -1114,22 +1111,22 @@ function parseGitignoreRule(raw) {
|
|
|
1114
1111
|
regex: new RegExp(`^${gitignoreGlobToRegex(pattern)}$`)
|
|
1115
1112
|
};
|
|
1116
1113
|
}
|
|
1117
|
-
function ruleMatchesPath(
|
|
1118
|
-
const candidates =
|
|
1119
|
-
const directMatch = candidates.some((candidate) =>
|
|
1120
|
-
if (directMatch && !
|
|
1114
|
+
function ruleMatchesPath(rule, relPath) {
|
|
1115
|
+
const candidates = rule.anchored || rule.hasSlash ? [relPath] : relPath.split("/");
|
|
1116
|
+
const directMatch = candidates.some((candidate) => rule.regex.test(candidate));
|
|
1117
|
+
if (directMatch && !rule.directoryOnly) {
|
|
1121
1118
|
return true;
|
|
1122
1119
|
}
|
|
1123
|
-
if (directMatch &&
|
|
1120
|
+
if (directMatch && rule.directoryOnly) {
|
|
1124
1121
|
return true;
|
|
1125
1122
|
}
|
|
1126
|
-
if (!
|
|
1123
|
+
if (!rule.directoryOnly) {
|
|
1127
1124
|
return false;
|
|
1128
1125
|
}
|
|
1129
|
-
if (
|
|
1130
|
-
return relPath ===
|
|
1126
|
+
if (rule.anchored || rule.hasSlash) {
|
|
1127
|
+
return relPath === rule.pattern || relPath.startsWith(`${rule.pattern}/`);
|
|
1131
1128
|
}
|
|
1132
|
-
return relPath.split("/").includes(
|
|
1129
|
+
return relPath.split("/").includes(rule.pattern);
|
|
1133
1130
|
}
|
|
1134
1131
|
function gitignoreGlobToRegex(pattern) {
|
|
1135
1132
|
let out = "";
|
|
@@ -1249,7 +1246,7 @@ function fallbackMapCategoryForGap(category, text) {
|
|
|
1249
1246
|
function inferMapCategories(category, title, detail, copyPrompt, explicitPrimary, explicitAffected) {
|
|
1250
1247
|
const text = [category, title, detail, copyPrompt].filter(Boolean).join(" ");
|
|
1251
1248
|
const fallback = fallbackMapCategoryForGap(category, text);
|
|
1252
|
-
const matched = MAP_CATEGORY_RULES.filter((
|
|
1249
|
+
const matched = MAP_CATEGORY_RULES.filter((rule) => rule.match.test(text)).map((rule) => rule.key);
|
|
1253
1250
|
const explicit = isProductionMapCategoryKey(explicitPrimary) ? [explicitPrimary] : [];
|
|
1254
1251
|
const affected = Array.isArray(explicitAffected) ? explicitAffected.filter(isProductionMapCategoryKey) : [];
|
|
1255
1252
|
const primarySeed = matched[0] ? [matched[0], fallback] : [fallback];
|
|
@@ -1294,12 +1291,12 @@ function normalizeGap(v) {
|
|
|
1294
1291
|
affectedMapCategories
|
|
1295
1292
|
};
|
|
1296
1293
|
}
|
|
1297
|
-
function rootGapKey(
|
|
1298
|
-
const titleKey = slugify(
|
|
1294
|
+
function rootGapKey(gap) {
|
|
1295
|
+
const titleKey = slugify(gap.title);
|
|
1299
1296
|
if (titleKey) {
|
|
1300
1297
|
return titleKey;
|
|
1301
1298
|
}
|
|
1302
|
-
return slugify([
|
|
1299
|
+
return slugify([gap.category, gap.copyPrompt].join(" ")) || gap.id;
|
|
1303
1300
|
}
|
|
1304
1301
|
function mergeToolSuggestions(a, b3) {
|
|
1305
1302
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1329,17 +1326,17 @@ function mergeRootGaps(a, b3) {
|
|
|
1329
1326
|
function dedupeRootGaps(gaps) {
|
|
1330
1327
|
const byKey = /* @__PURE__ */ new Map();
|
|
1331
1328
|
const order = [];
|
|
1332
|
-
for (const
|
|
1333
|
-
const key = rootGapKey(
|
|
1329
|
+
for (const gap of gaps) {
|
|
1330
|
+
const key = rootGapKey(gap);
|
|
1334
1331
|
const existing = byKey.get(key);
|
|
1335
1332
|
if (!existing) {
|
|
1336
|
-
byKey.set(key,
|
|
1333
|
+
byKey.set(key, gap);
|
|
1337
1334
|
order.push(key);
|
|
1338
1335
|
continue;
|
|
1339
1336
|
}
|
|
1340
|
-
byKey.set(key, mergeRootGaps(existing,
|
|
1337
|
+
byKey.set(key, mergeRootGaps(existing, gap));
|
|
1341
1338
|
}
|
|
1342
|
-
return order.map((key) => byKey.get(key)).filter((
|
|
1339
|
+
return order.map((key) => byKey.get(key)).filter((gap) => Boolean(gap));
|
|
1343
1340
|
}
|
|
1344
1341
|
function normalizeChecklist(v) {
|
|
1345
1342
|
const defaults = {
|
|
@@ -1483,15 +1480,15 @@ function buildLaunchValidationReport(input) {
|
|
|
1483
1480
|
promptTemplate: conflict.recommendedAction.promptTemplate
|
|
1484
1481
|
}))
|
|
1485
1482
|
);
|
|
1486
|
-
const gapIssues = (input.gaps ?? []).filter((
|
|
1487
|
-
const area = gapArea(
|
|
1483
|
+
const gapIssues = (input.gaps ?? []).filter((gap) => gap.severity !== "info").map((gap) => {
|
|
1484
|
+
const area = gapArea(gap);
|
|
1488
1485
|
return {
|
|
1489
|
-
id: `gap-${
|
|
1486
|
+
id: `gap-${gap.id}`,
|
|
1490
1487
|
area,
|
|
1491
|
-
severity:
|
|
1492
|
-
title:
|
|
1493
|
-
detail:
|
|
1494
|
-
promptTemplate:
|
|
1488
|
+
severity: gap.severity,
|
|
1489
|
+
title: gap.title,
|
|
1490
|
+
detail: gap.detail,
|
|
1491
|
+
promptTemplate: gap.severity === "critical" && isLaunchCriticalArea(area) ? "launch-blocker" : "repo-fix"
|
|
1495
1492
|
};
|
|
1496
1493
|
});
|
|
1497
1494
|
const providerCoverageWarnings = providerCoverageIssues(input.providerTruth);
|
|
@@ -1606,8 +1603,8 @@ function isActionableProviderCheckRow(row) {
|
|
|
1606
1603
|
function hasCompletedManualConfirmation(row) {
|
|
1607
1604
|
return row.manualProof.status === "manual-confirmed" || row.roles.some((role) => role === "manual-confirmed");
|
|
1608
1605
|
}
|
|
1609
|
-
function gapArea(
|
|
1610
|
-
return VERIFICATION_AREAS.includes(
|
|
1606
|
+
function gapArea(gap) {
|
|
1607
|
+
return VERIFICATION_AREAS.includes(gap.primaryMapCategory) ? gap.primaryMapCategory : "appFlow";
|
|
1611
1608
|
}
|
|
1612
1609
|
function isLaunchCriticalArea(area) {
|
|
1613
1610
|
return LAUNCH_CRITICAL_AREAS.includes(area);
|
|
@@ -2076,11 +2073,11 @@ function buildProviderRegistrySnapshot(now = /* @__PURE__ */ new Date()) {
|
|
|
2076
2073
|
providers
|
|
2077
2074
|
};
|
|
2078
2075
|
}
|
|
2079
|
-
function provider(providerKey,
|
|
2076
|
+
function provider(providerKey, label, aliases, areas, productionAreas, iconKey, extras = {}) {
|
|
2080
2077
|
const sifgTemplateIds = areas.flatMap((area) => sifgTemplatesForRegistryProviderArea(providerKey, area)).map((template) => template.id);
|
|
2081
2078
|
return {
|
|
2082
2079
|
provider: providerKey,
|
|
2083
|
-
label
|
|
2080
|
+
label,
|
|
2084
2081
|
aliases,
|
|
2085
2082
|
areas,
|
|
2086
2083
|
productionAreas,
|
|
@@ -2938,14 +2935,14 @@ function detectProductionConnectionEvidence(scan) {
|
|
|
2938
2935
|
content: file.isSecret || typeof file.content !== "string" ? "" : file.content,
|
|
2939
2936
|
lowerContent: file.isSecret || typeof file.content !== "string" ? "" : file.content.toLowerCase()
|
|
2940
2937
|
}));
|
|
2941
|
-
const secretPathBlob = scan.secretsFound.map((
|
|
2938
|
+
const secretPathBlob = scan.secretsFound.map((path) => normalizePath(path)).join("\n");
|
|
2942
2939
|
const pathBlob = `${scan.fileTree}
|
|
2943
2940
|
${files.map((file) => file.path).join("\n")}`.toLowerCase();
|
|
2944
2941
|
const contentBlob = files.map((file) => file.lowerContent).join("\n").slice(0, 12e4);
|
|
2945
2942
|
const secretsHygieneBlob = `${pathBlob}
|
|
2946
2943
|
${secretPathBlob}`;
|
|
2947
|
-
for (const
|
|
2948
|
-
detectProvider(evidence,
|
|
2944
|
+
for (const rule of PROVIDER_RULES) {
|
|
2945
|
+
detectProvider(evidence, rule, deps, files, pathBlob);
|
|
2949
2946
|
}
|
|
2950
2947
|
detectSecretsHygiene(evidence, secretsHygieneBlob);
|
|
2951
2948
|
for (const item3 of Object.values(evidence)) {
|
|
@@ -2999,55 +2996,55 @@ function buildProductionConnectionContext(choices, evidence) {
|
|
|
2999
2996
|
}
|
|
3000
2997
|
return lines.length > 0 ? lines.join("\n") : "production connections: no selected or detected providers";
|
|
3001
2998
|
}
|
|
3002
|
-
function detectProvider(evidence,
|
|
3003
|
-
if (evidence[
|
|
2999
|
+
function detectProvider(evidence, rule, deps, files, pathBlob) {
|
|
3000
|
+
if (evidence[rule.area]) {
|
|
3004
3001
|
return;
|
|
3005
3002
|
}
|
|
3006
|
-
const signals = collectSignals(
|
|
3003
|
+
const signals = collectSignals(rule, deps, files, pathBlob);
|
|
3007
3004
|
if (signals.length === 0) {
|
|
3008
3005
|
return;
|
|
3009
3006
|
}
|
|
3010
|
-
evidence[
|
|
3011
|
-
area:
|
|
3012
|
-
provider:
|
|
3007
|
+
evidence[rule.area] = {
|
|
3008
|
+
area: rule.area,
|
|
3009
|
+
provider: rule.provider,
|
|
3013
3010
|
status: ["detected"],
|
|
3014
3011
|
signals
|
|
3015
3012
|
};
|
|
3016
3013
|
}
|
|
3017
|
-
function collectSignals(
|
|
3014
|
+
function collectSignals(rule, deps, files, pathBlob) {
|
|
3018
3015
|
const signals = [];
|
|
3019
3016
|
for (const dep of deps) {
|
|
3020
|
-
if ((
|
|
3017
|
+
if ((rule.packages ?? []).some((pattern) => testRegex(pattern, dep))) {
|
|
3021
3018
|
addSignal(signals, `package: ${dep}`);
|
|
3022
3019
|
}
|
|
3023
3020
|
}
|
|
3024
3021
|
const upperContents = files.map((file) => file.content).join("\n").toUpperCase();
|
|
3025
|
-
for (const envName of
|
|
3022
|
+
for (const envName of rule.env ?? []) {
|
|
3026
3023
|
if (upperContents.includes(envName.toUpperCase())) {
|
|
3027
3024
|
addSignal(signals, `env: ${envName}`);
|
|
3028
3025
|
}
|
|
3029
3026
|
}
|
|
3030
|
-
const pathLines = pathBlob.split(/\r?\n/).map((
|
|
3031
|
-
for (const
|
|
3032
|
-
if ((
|
|
3033
|
-
addSignal(signals, `${pathSignalPrefix(
|
|
3027
|
+
const pathLines = pathBlob.split(/\r?\n/).map((path) => path.trim()).filter(Boolean);
|
|
3028
|
+
for (const path of pathLines) {
|
|
3029
|
+
if ((rule.paths ?? []).some((pattern) => testRegex(pattern, path))) {
|
|
3030
|
+
addSignal(signals, `${pathSignalPrefix(path)}: ${path}`);
|
|
3034
3031
|
}
|
|
3035
3032
|
}
|
|
3036
3033
|
for (const file of files) {
|
|
3037
|
-
for (const importName of
|
|
3034
|
+
for (const importName of rule.imports ?? []) {
|
|
3038
3035
|
if (containsImport(file.lowerContent, importName)) {
|
|
3039
3036
|
addSignal(signals, `import: ${importName}`);
|
|
3040
3037
|
}
|
|
3041
3038
|
}
|
|
3042
|
-
for (const item3 of
|
|
3039
|
+
for (const item3 of rule.content ?? []) {
|
|
3043
3040
|
if (testRegex(item3.pattern, file.content)) {
|
|
3044
3041
|
addSignal(signals, item3.signal);
|
|
3045
3042
|
}
|
|
3046
3043
|
}
|
|
3047
3044
|
if (isDocsPath(file.path)) {
|
|
3048
|
-
for (const docsTerm of
|
|
3045
|
+
for (const docsTerm of rule.docs ?? []) {
|
|
3049
3046
|
if (file.lowerContent.includes(docsTerm.toLowerCase())) {
|
|
3050
|
-
addSignal(signals, `docs: ${file.displayPath} mentions ${
|
|
3047
|
+
addSignal(signals, `docs: ${file.displayPath} mentions ${rule.label}`);
|
|
3051
3048
|
break;
|
|
3052
3049
|
}
|
|
3053
3050
|
}
|
|
@@ -3059,14 +3056,14 @@ function containsImport(content, importName) {
|
|
|
3059
3056
|
const escaped = importName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").toLowerCase();
|
|
3060
3057
|
return new RegExp(`(?:from\\s+['"]${escaped}['"]|import\\s*\\(\\s*['"]${escaped}['"]|require\\s*\\(\\s*['"]${escaped}['"])`).test(content);
|
|
3061
3058
|
}
|
|
3062
|
-
function isDocsPath(
|
|
3063
|
-
return /(^|\/)(readme|product|spec)\.md$/.test(
|
|
3059
|
+
function isDocsPath(path) {
|
|
3060
|
+
return /(^|\/)(readme|product|spec)\.md$/.test(path) || /(^|\/)docs\/.*\.md$/.test(path);
|
|
3064
3061
|
}
|
|
3065
|
-
function pathSignalPrefix(
|
|
3066
|
-
if (/webhook|checkout|billing|api\/|route\.[jt]s$/.test(
|
|
3062
|
+
function pathSignalPrefix(path) {
|
|
3063
|
+
if (/webhook|checkout|billing|api\/|route\.[jt]s$/.test(path)) {
|
|
3067
3064
|
return "route";
|
|
3068
3065
|
}
|
|
3069
|
-
if (/config|\.json$|\.toml$|\.ya?ml$/.test(
|
|
3066
|
+
if (/config|\.json$|\.toml$|\.ya?ml$/.test(path)) {
|
|
3070
3067
|
return "config";
|
|
3071
3068
|
}
|
|
3072
3069
|
return "file";
|
|
@@ -3077,19 +3074,19 @@ function addSignal(signals, signal) {
|
|
|
3077
3074
|
}
|
|
3078
3075
|
}
|
|
3079
3076
|
function freezeProviderRules(rules) {
|
|
3080
|
-
for (const
|
|
3081
|
-
Object.freeze(
|
|
3082
|
-
Object.freeze(
|
|
3083
|
-
Object.freeze(
|
|
3084
|
-
Object.freeze(
|
|
3085
|
-
Object.freeze(
|
|
3086
|
-
if (
|
|
3087
|
-
for (const item3 of
|
|
3077
|
+
for (const rule of rules) {
|
|
3078
|
+
Object.freeze(rule.packages ?? []);
|
|
3079
|
+
Object.freeze(rule.env ?? []);
|
|
3080
|
+
Object.freeze(rule.paths ?? []);
|
|
3081
|
+
Object.freeze(rule.imports ?? []);
|
|
3082
|
+
Object.freeze(rule.docs ?? []);
|
|
3083
|
+
if (rule.content) {
|
|
3084
|
+
for (const item3 of rule.content) {
|
|
3088
3085
|
Object.freeze(item3);
|
|
3089
3086
|
}
|
|
3090
|
-
Object.freeze(
|
|
3087
|
+
Object.freeze(rule.content);
|
|
3091
3088
|
}
|
|
3092
|
-
Object.freeze(
|
|
3089
|
+
Object.freeze(rule);
|
|
3093
3090
|
}
|
|
3094
3091
|
return Object.freeze(rules);
|
|
3095
3092
|
}
|
|
@@ -3217,8 +3214,8 @@ function normalizeSelectedAt(value) {
|
|
|
3217
3214
|
}
|
|
3218
3215
|
return null;
|
|
3219
3216
|
}
|
|
3220
|
-
function normalizePath(
|
|
3221
|
-
return
|
|
3217
|
+
function normalizePath(path) {
|
|
3218
|
+
return path.replace(/\\/g, "/").toLowerCase();
|
|
3222
3219
|
}
|
|
3223
3220
|
function isObject(value) {
|
|
3224
3221
|
return typeof value === "object" && value !== null;
|
|
@@ -3246,8 +3243,8 @@ var WEAK_PATH_SEGMENTS = /* @__PURE__ */ new Set([
|
|
|
3246
3243
|
"demo"
|
|
3247
3244
|
]);
|
|
3248
3245
|
var TEST_FILE_PATTERN = /\.(test|spec)\.[jt]sx?$/;
|
|
3249
|
-
function classifyProviderEvidencePath(
|
|
3250
|
-
const normalized = normalizePath2(
|
|
3246
|
+
function classifyProviderEvidencePath(path) {
|
|
3247
|
+
const normalized = normalizePath2(path);
|
|
3251
3248
|
const segments = normalized.split("/").filter(Boolean);
|
|
3252
3249
|
if (isDocsLikePath(normalized) || segments.some((segment) => WEAK_PATH_SEGMENTS.has(segment)) || TEST_FILE_PATTERN.test(normalized)) {
|
|
3253
3250
|
return "weak";
|
|
@@ -3262,14 +3259,14 @@ function buildProviderTruthSnapshot(input) {
|
|
|
3262
3259
|
const rowsByArea = {};
|
|
3263
3260
|
void input.mcpVerifierState;
|
|
3264
3261
|
void input.verificationLayer;
|
|
3265
|
-
for (const
|
|
3266
|
-
const evidence = collectProviderTruthEvidence(
|
|
3267
|
-
const selected = choices.choices[
|
|
3262
|
+
for (const rule of PROVIDER_RULES) {
|
|
3263
|
+
const evidence = collectProviderTruthEvidence(rule, deps, files, pathLines);
|
|
3264
|
+
const selected = choices.choices[rule.area]?.provider === rule.provider;
|
|
3268
3265
|
if (evidence.length === 0 && !selected) {
|
|
3269
3266
|
continue;
|
|
3270
3267
|
}
|
|
3271
|
-
const row = buildRow(
|
|
3272
|
-
rowsByArea[
|
|
3268
|
+
const row = buildRow(rule, evidence, selected);
|
|
3269
|
+
rowsByArea[rule.area] = [...rowsByArea[rule.area] ?? [], row];
|
|
3273
3270
|
}
|
|
3274
3271
|
for (const [area, choice] of Object.entries(choices.choices)) {
|
|
3275
3272
|
if (!choice) {
|
|
@@ -3301,32 +3298,32 @@ function buildProviderTruthSnapshot(input) {
|
|
|
3301
3298
|
summary
|
|
3302
3299
|
};
|
|
3303
3300
|
}
|
|
3304
|
-
function collectProviderTruthEvidence(
|
|
3301
|
+
function collectProviderTruthEvidence(rule, deps, files, pathLines) {
|
|
3305
3302
|
const evidence = [];
|
|
3306
3303
|
for (const dep of deps) {
|
|
3307
|
-
if ((
|
|
3308
|
-
addEvidence(evidence,
|
|
3304
|
+
if ((rule.packages ?? []).some((pattern) => testRegex2(pattern, dep))) {
|
|
3305
|
+
addEvidence(evidence, rule, {
|
|
3309
3306
|
kind: "package-installed",
|
|
3310
3307
|
strength: "medium",
|
|
3311
|
-
label: `${
|
|
3308
|
+
label: `${rule.label} package installed`,
|
|
3312
3309
|
detail: dep,
|
|
3313
3310
|
points: 20,
|
|
3314
3311
|
isRuntimeEvidence: false
|
|
3315
3312
|
});
|
|
3316
3313
|
}
|
|
3317
3314
|
}
|
|
3318
|
-
for (const
|
|
3319
|
-
if (!(
|
|
3315
|
+
for (const path of pathLines) {
|
|
3316
|
+
if (!(rule.paths ?? []).some((pattern) => testRegex2(pattern, path))) {
|
|
3320
3317
|
continue;
|
|
3321
3318
|
}
|
|
3322
|
-
const pathClass = classifyProviderEvidencePath(
|
|
3323
|
-
const kind = weakPathKind(
|
|
3324
|
-
addEvidence(evidence,
|
|
3319
|
+
const pathClass = classifyProviderEvidencePath(path);
|
|
3320
|
+
const kind = weakPathKind(path, "active-runtime-route") ?? routeKind(path);
|
|
3321
|
+
addEvidence(evidence, rule, {
|
|
3325
3322
|
kind,
|
|
3326
3323
|
strength: pathClass === "runtime" ? "strong" : "weak",
|
|
3327
|
-
label: `${
|
|
3328
|
-
file:
|
|
3329
|
-
points: pathClass === "runtime" ? 35 : weakEvidencePoints(
|
|
3324
|
+
label: `${rule.label} ${pathClass === "runtime" ? "runtime route or path" : "weak path reference"}`,
|
|
3325
|
+
file: path,
|
|
3326
|
+
points: pathClass === "runtime" ? 35 : weakEvidencePoints(path),
|
|
3330
3327
|
isRuntimeEvidence: pathClass === "runtime"
|
|
3331
3328
|
});
|
|
3332
3329
|
}
|
|
@@ -3334,45 +3331,45 @@ function collectProviderTruthEvidence(rule2, deps, files, pathLines) {
|
|
|
3334
3331
|
const pathClass = classifyProviderEvidencePath(file.path);
|
|
3335
3332
|
const sourceContent = pathClass === "runtime" ? file.executableContent : file.content;
|
|
3336
3333
|
const sourceLowerContent = pathClass === "runtime" ? file.lowerExecutableContent : file.lowerContent;
|
|
3337
|
-
for (const envName of
|
|
3334
|
+
for (const envName of rule.env ?? []) {
|
|
3338
3335
|
if (!sourceContent.toUpperCase().includes(envName.toUpperCase())) {
|
|
3339
3336
|
continue;
|
|
3340
3337
|
}
|
|
3341
|
-
addEvidence(evidence,
|
|
3338
|
+
addEvidence(evidence, rule, {
|
|
3342
3339
|
kind: pathClass === "runtime" ? "runtime-env-usage" : "env-name-only",
|
|
3343
3340
|
strength: pathClass === "runtime" ? "medium" : "weak",
|
|
3344
|
-
label: `${
|
|
3341
|
+
label: `${rule.label} env name ${pathClass === "runtime" ? "used in runtime source" : "mentioned outside runtime source"}`,
|
|
3345
3342
|
file: file.displayPath,
|
|
3346
3343
|
detail: envName,
|
|
3347
3344
|
points: pathClass === "runtime" ? 20 : weakEvidencePoints(file.path),
|
|
3348
3345
|
isRuntimeEvidence: pathClass === "runtime"
|
|
3349
3346
|
});
|
|
3350
3347
|
}
|
|
3351
|
-
for (const importName of
|
|
3348
|
+
for (const importName of rule.imports ?? []) {
|
|
3352
3349
|
if (!containsImport2(sourceLowerContent, importName)) {
|
|
3353
3350
|
continue;
|
|
3354
3351
|
}
|
|
3355
3352
|
const weakKind = weakPathKind(file.path, "sdk-import-source");
|
|
3356
|
-
addEvidence(evidence,
|
|
3353
|
+
addEvidence(evidence, rule, {
|
|
3357
3354
|
kind: weakKind ?? "sdk-import-source",
|
|
3358
3355
|
strength: pathClass === "runtime" ? "strong" : "weak",
|
|
3359
|
-
label: `${
|
|
3356
|
+
label: `${rule.label} SDK import ${pathClass === "runtime" ? "in runtime source" : "outside runtime source"}`,
|
|
3360
3357
|
file: file.displayPath,
|
|
3361
3358
|
detail: importName,
|
|
3362
3359
|
points: pathClass === "runtime" ? 35 : weakEvidencePoints(file.path),
|
|
3363
3360
|
isRuntimeEvidence: pathClass === "runtime"
|
|
3364
3361
|
});
|
|
3365
3362
|
}
|
|
3366
|
-
for (const item3 of
|
|
3363
|
+
for (const item3 of rule.content ?? []) {
|
|
3367
3364
|
if (!testRegex2(item3.pattern, sourceContent)) {
|
|
3368
3365
|
continue;
|
|
3369
3366
|
}
|
|
3370
3367
|
const strongKind = contentSignalKind(item3.signal);
|
|
3371
3368
|
const weakKind = weakPathKind(file.path, strongKind);
|
|
3372
|
-
addEvidence(evidence,
|
|
3369
|
+
addEvidence(evidence, rule, {
|
|
3373
3370
|
kind: weakKind ?? strongKind,
|
|
3374
3371
|
strength: pathClass === "runtime" ? "strong" : "weak",
|
|
3375
|
-
label: `${
|
|
3372
|
+
label: `${rule.label} ${pathClass === "runtime" ? "runtime content signal" : "weak content reference"}`,
|
|
3376
3373
|
file: file.displayPath,
|
|
3377
3374
|
detail: item3.signal,
|
|
3378
3375
|
points: pathClass === "runtime" ? 35 : weakEvidencePoints(file.path),
|
|
@@ -3380,14 +3377,14 @@ function collectProviderTruthEvidence(rule2, deps, files, pathLines) {
|
|
|
3380
3377
|
});
|
|
3381
3378
|
}
|
|
3382
3379
|
if (isDocsLikePath(file.path)) {
|
|
3383
|
-
for (const docsTerm of
|
|
3380
|
+
for (const docsTerm of rule.docs ?? []) {
|
|
3384
3381
|
if (!file.lowerContent.includes(docsTerm.toLowerCase())) {
|
|
3385
3382
|
continue;
|
|
3386
3383
|
}
|
|
3387
|
-
addEvidence(evidence,
|
|
3384
|
+
addEvidence(evidence, rule, {
|
|
3388
3385
|
kind: "docs-mention",
|
|
3389
3386
|
strength: "weak",
|
|
3390
|
-
label: `${
|
|
3387
|
+
label: `${rule.label} mentioned in docs`,
|
|
3391
3388
|
file: file.displayPath,
|
|
3392
3389
|
detail: docsTerm,
|
|
3393
3390
|
points: 4,
|
|
@@ -3399,16 +3396,16 @@ function collectProviderTruthEvidence(rule2, deps, files, pathLines) {
|
|
|
3399
3396
|
}
|
|
3400
3397
|
return evidence.sort(compareEvidence);
|
|
3401
3398
|
}
|
|
3402
|
-
function buildRow(
|
|
3399
|
+
function buildRow(rule, evidence, selected) {
|
|
3403
3400
|
const score = evidence.reduce((total, item3) => total + item3.points, 0);
|
|
3404
3401
|
const roles = rolesForEvidence(evidence, selected);
|
|
3405
|
-
applyMcpSupportRoles(
|
|
3402
|
+
applyMcpSupportRoles(rule.provider, roles);
|
|
3406
3403
|
const confidence = confidenceForEvidence(evidence, roles);
|
|
3407
|
-
const mcpProof = mcpProofForProvider(
|
|
3404
|
+
const mcpProof = mcpProofForProvider(rule.provider);
|
|
3408
3405
|
return {
|
|
3409
|
-
area:
|
|
3410
|
-
provider:
|
|
3411
|
-
providerLabel: providerLabel(
|
|
3406
|
+
area: rule.area,
|
|
3407
|
+
provider: rule.provider,
|
|
3408
|
+
providerLabel: providerLabel(rule.provider),
|
|
3412
3409
|
roles,
|
|
3413
3410
|
confidence,
|
|
3414
3411
|
score,
|
|
@@ -3665,10 +3662,10 @@ function statusBadgesForRoles(roles, confidence) {
|
|
|
3665
3662
|
badges.push(confidence.toUpperCase());
|
|
3666
3663
|
return badges;
|
|
3667
3664
|
}
|
|
3668
|
-
function addEvidence(evidence,
|
|
3665
|
+
function addEvidence(evidence, rule, item3) {
|
|
3669
3666
|
const id = [
|
|
3670
|
-
|
|
3671
|
-
|
|
3667
|
+
rule.area,
|
|
3668
|
+
rule.provider,
|
|
3672
3669
|
item3.kind,
|
|
3673
3670
|
item3.file ?? "",
|
|
3674
3671
|
item3.detail ?? item3.label
|
|
@@ -3683,29 +3680,29 @@ function addEvidence(evidence, rule2, item3) {
|
|
|
3683
3680
|
isManualProof: false
|
|
3684
3681
|
});
|
|
3685
3682
|
}
|
|
3686
|
-
function weakPathKind(
|
|
3687
|
-
if (classifyProviderEvidencePath(
|
|
3683
|
+
function weakPathKind(path, fallback) {
|
|
3684
|
+
if (classifyProviderEvidencePath(path) === "runtime") {
|
|
3688
3685
|
return null;
|
|
3689
3686
|
}
|
|
3690
|
-
if (isDocsLikePath(
|
|
3687
|
+
if (isDocsLikePath(path)) {
|
|
3691
3688
|
return "docs-mention";
|
|
3692
3689
|
}
|
|
3693
|
-
if (/(^|\/)(__tests__|tests)(\/|$)|\.(test|spec)\.[jt]sx?$/.test(normalizePath2(
|
|
3690
|
+
if (/(^|\/)(__tests__|tests)(\/|$)|\.(test|spec)\.[jt]sx?$/.test(normalizePath2(path))) {
|
|
3694
3691
|
return "test-reference";
|
|
3695
3692
|
}
|
|
3696
|
-
if (/(^|\/)(tmp|out|outputs|videos|marketing|examples|demo)(\/|$)/.test(normalizePath2(
|
|
3693
|
+
if (/(^|\/)(tmp|out|outputs|videos|marketing|examples|demo)(\/|$)/.test(normalizePath2(path))) {
|
|
3697
3694
|
return "tmp-demo-example";
|
|
3698
3695
|
}
|
|
3699
3696
|
return fallback;
|
|
3700
3697
|
}
|
|
3701
|
-
function routeKind(
|
|
3702
|
-
if (/webhook/.test(
|
|
3698
|
+
function routeKind(path) {
|
|
3699
|
+
if (/webhook/.test(path)) {
|
|
3703
3700
|
return "webhook-handler";
|
|
3704
3701
|
}
|
|
3705
|
-
if (/checkout|billing/.test(
|
|
3702
|
+
if (/checkout|billing/.test(path)) {
|
|
3706
3703
|
return "checkout-handler";
|
|
3707
3704
|
}
|
|
3708
|
-
if (/config|\.json$|\.toml$|\.ya?ml$/.test(
|
|
3705
|
+
if (/config|\.json$|\.toml$|\.ya?ml$/.test(path)) {
|
|
3709
3706
|
return "deployment-config";
|
|
3710
3707
|
}
|
|
3711
3708
|
return "active-runtime-route";
|
|
@@ -3722,11 +3719,11 @@ function contentSignalKind(signal) {
|
|
|
3722
3719
|
}
|
|
3723
3720
|
return "active-runtime-route";
|
|
3724
3721
|
}
|
|
3725
|
-
function weakEvidencePoints(
|
|
3726
|
-
if (isDocsLikePath(
|
|
3722
|
+
function weakEvidencePoints(path) {
|
|
3723
|
+
if (isDocsLikePath(path)) {
|
|
3727
3724
|
return 4;
|
|
3728
3725
|
}
|
|
3729
|
-
if (/(^|\/)(__tests__|tests)(\/|$)|\.(test|spec)\.[jt]sx?$/.test(normalizePath2(
|
|
3726
|
+
if (/(^|\/)(__tests__|tests)(\/|$)|\.(test|spec)\.[jt]sx?$/.test(normalizePath2(path))) {
|
|
3730
3727
|
return 6;
|
|
3731
3728
|
}
|
|
3732
3729
|
return 8;
|
|
@@ -3745,8 +3742,8 @@ function toScannableFile(file) {
|
|
|
3745
3742
|
}
|
|
3746
3743
|
function collectPathLines(scan, files) {
|
|
3747
3744
|
const paths = /* @__PURE__ */ new Set();
|
|
3748
|
-
for (const
|
|
3749
|
-
const normalized = normalizePath2(
|
|
3745
|
+
for (const path of scan.fileTree.split(/\r?\n/)) {
|
|
3746
|
+
const normalized = normalizePath2(path.trim());
|
|
3750
3747
|
if (normalized) {
|
|
3751
3748
|
paths.add(normalized);
|
|
3752
3749
|
}
|
|
@@ -3760,8 +3757,8 @@ function containsImport2(content, importName) {
|
|
|
3760
3757
|
const escaped = importName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").toLowerCase();
|
|
3761
3758
|
return new RegExp(`(?:from\\s+['"]${escaped}['"]|import\\s*\\(\\s*['"]${escaped}['"]|require\\s*\\(\\s*['"]${escaped}['"])`).test(content);
|
|
3762
3759
|
}
|
|
3763
|
-
function isDocsLikePath(
|
|
3764
|
-
const normalized = normalizePath2(
|
|
3760
|
+
function isDocsLikePath(path) {
|
|
3761
|
+
const normalized = normalizePath2(path);
|
|
3765
3762
|
return /(^|\/)(readme|product|spec)\.mdx?$/.test(normalized) || /(^|\/)docs\/.*\.mdx?$/.test(normalized);
|
|
3766
3763
|
}
|
|
3767
3764
|
function stripComments(content) {
|
|
@@ -3773,8 +3770,8 @@ function compareRows(a, b3) {
|
|
|
3773
3770
|
function compareEvidence(a, b3) {
|
|
3774
3771
|
return b3.points - a.points || a.kind.localeCompare(b3.kind) || (a.file ?? "").localeCompare(b3.file ?? "");
|
|
3775
3772
|
}
|
|
3776
|
-
function normalizePath2(
|
|
3777
|
-
return
|
|
3773
|
+
function normalizePath2(path) {
|
|
3774
|
+
return path.replace(/\\/g, "/").toLowerCase();
|
|
3778
3775
|
}
|
|
3779
3776
|
function rowPriority(row) {
|
|
3780
3777
|
if (row.roles.includes("live-verified")) {
|
|
@@ -3969,8 +3966,8 @@ Return ONLY the JSON object \u2014 no markdown, no explanation.
|
|
|
3969
3966
|
}
|
|
3970
3967
|
|
|
3971
3968
|
// ../../src/station/envEvidence.ts
|
|
3972
|
-
function normalizeEvidencePath(
|
|
3973
|
-
return
|
|
3969
|
+
function normalizeEvidencePath(path) {
|
|
3970
|
+
return path.replace(/\\/g, "/");
|
|
3974
3971
|
}
|
|
3975
3972
|
function uniquePaths(paths) {
|
|
3976
3973
|
return Array.from(new Set(paths.map(normalizeEvidencePath)));
|
|
@@ -4038,7 +4035,7 @@ function collectEnvVarEvidence(scan, names) {
|
|
|
4038
4035
|
present: true,
|
|
4039
4036
|
mode,
|
|
4040
4037
|
source: "non-secret-content",
|
|
4041
|
-
evidence: uniquePaths(contentEvidence).map((
|
|
4038
|
+
evidence: uniquePaths(contentEvidence).map((path) => `file: ${path}`)
|
|
4042
4039
|
};
|
|
4043
4040
|
}
|
|
4044
4041
|
if (nonEmptyAssignmentEvidence.length > 0) {
|
|
@@ -4047,7 +4044,7 @@ function collectEnvVarEvidence(scan, names) {
|
|
|
4047
4044
|
present: true,
|
|
4048
4045
|
mode: "unknown",
|
|
4049
4046
|
source: "non-secret-content",
|
|
4050
|
-
evidence: uniquePaths(nonEmptyAssignmentEvidence).map((
|
|
4047
|
+
evidence: uniquePaths(nonEmptyAssignmentEvidence).map((path) => `file: ${path}`)
|
|
4051
4048
|
};
|
|
4052
4049
|
}
|
|
4053
4050
|
if (nameOnlyEvidence.length > 0) {
|
|
@@ -4056,7 +4053,7 @@ function collectEnvVarEvidence(scan, names) {
|
|
|
4056
4053
|
present: true,
|
|
4057
4054
|
mode: "unknown",
|
|
4058
4055
|
source: "variable-name-only",
|
|
4059
|
-
evidence: uniquePaths(nameOnlyEvidence).map((
|
|
4056
|
+
evidence: uniquePaths(nameOnlyEvidence).map((path) => `file: ${path}`)
|
|
4060
4057
|
};
|
|
4061
4058
|
}
|
|
4062
4059
|
if (secretEvidence.length > 0) {
|
|
@@ -4065,7 +4062,7 @@ function collectEnvVarEvidence(scan, names) {
|
|
|
4065
4062
|
present: false,
|
|
4066
4063
|
mode: "unknown",
|
|
4067
4064
|
source: "secret-file-path",
|
|
4068
|
-
evidence: secretEvidence.map((
|
|
4065
|
+
evidence: secretEvidence.map((path) => `secret file: ${path}`)
|
|
4069
4066
|
};
|
|
4070
4067
|
}
|
|
4071
4068
|
return {
|
|
@@ -4160,10 +4157,10 @@ ${pathBlob}`),
|
|
|
4160
4157
|
function visibleFiles(scan) {
|
|
4161
4158
|
return scan.files.filter((file) => !file.isSecret && typeof file.content === "string");
|
|
4162
4159
|
}
|
|
4163
|
-
function foundOrMissing(id,
|
|
4160
|
+
function foundOrMissing(id, label, found, evidence) {
|
|
4164
4161
|
return {
|
|
4165
4162
|
id,
|
|
4166
|
-
label
|
|
4163
|
+
label,
|
|
4167
4164
|
status: found ? "found" : "missing",
|
|
4168
4165
|
evidence: unique(evidence).slice(0, 6)
|
|
4169
4166
|
};
|
|
@@ -4200,7 +4197,7 @@ function evidenceFor(files, pattern) {
|
|
|
4200
4197
|
return files.filter((file) => pattern.test(file.content)).map((file) => `file: ${normalizePath3(file.path)}`).slice(0, 6);
|
|
4201
4198
|
}
|
|
4202
4199
|
function pathEvidence(scan, pattern) {
|
|
4203
|
-
return scan.files.map((file) => normalizePath3(file.path)).filter((
|
|
4200
|
+
return scan.files.map((file) => normalizePath3(file.path)).filter((path) => pattern.test(path)).map((path) => `file: ${path}`).slice(0, 6);
|
|
4204
4201
|
}
|
|
4205
4202
|
function isClientReachableFile(file) {
|
|
4206
4203
|
const content = file.content;
|
|
@@ -4216,22 +4213,22 @@ function isClientReachableFile(file) {
|
|
|
4216
4213
|
function hasUseClientDirective(content) {
|
|
4217
4214
|
return /^(?:\s|;|\/\/[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/)*['"]use client['"]/.test(content);
|
|
4218
4215
|
}
|
|
4219
|
-
function isBrowserOnlyPath(
|
|
4220
|
-
const normalized = normalizePath3(
|
|
4221
|
-
if (isServerOnlyPath(
|
|
4216
|
+
function isBrowserOnlyPath(path) {
|
|
4217
|
+
const normalized = normalizePath3(path).toLowerCase();
|
|
4218
|
+
if (isServerOnlyPath(path)) {
|
|
4222
4219
|
return false;
|
|
4223
4220
|
}
|
|
4224
4221
|
return /(^|\/)(components|hooks|contexts|providers)\//.test(normalized) || /\.(jsx|tsx)$/.test(normalized);
|
|
4225
4222
|
}
|
|
4226
|
-
function isServerOnlyPath(
|
|
4227
|
-
const normalized = normalizePath3(
|
|
4223
|
+
function isServerOnlyPath(path) {
|
|
4224
|
+
const normalized = normalizePath3(path).toLowerCase();
|
|
4228
4225
|
return /\/api\/|app\/api\/|pages\/api\/|route\.[jt]s$|\.server\.[jt]sx?$|\/server\//.test(normalized);
|
|
4229
4226
|
}
|
|
4230
|
-
function isEnvOrDocPath(
|
|
4231
|
-
return ENV_OR_DOC_PATH.test(normalizePath3(
|
|
4227
|
+
function isEnvOrDocPath(path) {
|
|
4228
|
+
return ENV_OR_DOC_PATH.test(normalizePath3(path).toLowerCase());
|
|
4232
4229
|
}
|
|
4233
|
-
function normalizePath3(
|
|
4234
|
-
return
|
|
4230
|
+
function normalizePath3(path) {
|
|
4231
|
+
return path.replace(/\\/g, "/");
|
|
4235
4232
|
}
|
|
4236
4233
|
function unique(values) {
|
|
4237
4234
|
return [...new Set(values)];
|
|
@@ -5342,7 +5339,7 @@ ${scan.files.map((file) => file.path).join("\n")}`.replace(/\\/g, "/").toLowerCa
|
|
|
5342
5339
|
];
|
|
5343
5340
|
const passedCount = items.filter((entry) => entry.status === "passed").length;
|
|
5344
5341
|
const totalCount = items.length;
|
|
5345
|
-
const
|
|
5342
|
+
const readinessPercent = Math.round(passedCount / Math.max(totalCount, 1) * 100);
|
|
5346
5343
|
return {
|
|
5347
5344
|
key: "supabase-database",
|
|
5348
5345
|
provider: "supabase",
|
|
@@ -5353,7 +5350,7 @@ ${scan.files.map((file) => file.path).join("\n")}`.replace(/\\/g, "/").toLowerCa
|
|
|
5353
5350
|
items,
|
|
5354
5351
|
passedCount,
|
|
5355
5352
|
totalCount,
|
|
5356
|
-
readinessPercent
|
|
5353
|
+
readinessPercent
|
|
5357
5354
|
};
|
|
5358
5355
|
}
|
|
5359
5356
|
function visibleFiles2(scan) {
|
|
@@ -5364,10 +5361,10 @@ function visibleFiles2(scan) {
|
|
|
5364
5361
|
lowerContent: file.content.toLowerCase()
|
|
5365
5362
|
}));
|
|
5366
5363
|
}
|
|
5367
|
-
function item(id,
|
|
5364
|
+
function item(id, label, passed, evidence, promptHint) {
|
|
5368
5365
|
return {
|
|
5369
5366
|
id,
|
|
5370
|
-
label
|
|
5367
|
+
label,
|
|
5371
5368
|
status: passed ? "passed" : "missing",
|
|
5372
5369
|
evidence,
|
|
5373
5370
|
promptHint
|
|
@@ -5397,7 +5394,7 @@ function hasSupabaseClient(files, pathBlob) {
|
|
|
5397
5394
|
}
|
|
5398
5395
|
function clientEvidence(files, pathBlob) {
|
|
5399
5396
|
const evidence = [];
|
|
5400
|
-
const pathMatch = pathBlob.split(/\r?\n/).find((
|
|
5397
|
+
const pathMatch = pathBlob.split(/\r?\n/).find((path) => /(^|\/)(lib|utils|src\/lib|src\/utils)\/supabase\.[jt]s\b/i.test(path));
|
|
5401
5398
|
if (pathMatch) {
|
|
5402
5399
|
evidence.push(`file: ${pathMatch}`);
|
|
5403
5400
|
}
|
|
@@ -5414,11 +5411,11 @@ function hasSchemaOrMigration(files, pathBlob) {
|
|
|
5414
5411
|
return /(^|\n|\/)supabase\/migrations\/[^/\n]+\.sql\b/i.test(pathBlob) || /(^|\n|\/)(migrations?|schema)\/[^/\n]+\.(sql|ts|js)\b/i.test(pathBlob) || files.some((file) => /create\s+table|alter\s+table/i.test(file.content));
|
|
5415
5412
|
}
|
|
5416
5413
|
function schemaEvidence(files, pathBlob) {
|
|
5417
|
-
const
|
|
5414
|
+
const path = pathBlob.split(/\r?\n/).find(
|
|
5418
5415
|
(entry) => /(^|\/)supabase\/migrations\/[^/]+\.sql\b/i.test(entry) || /(^|\/)(migrations?|schema)\/[^/]+\.(sql|ts|js)\b/i.test(entry)
|
|
5419
5416
|
);
|
|
5420
|
-
if (
|
|
5421
|
-
return [`schema: ${
|
|
5417
|
+
if (path) {
|
|
5418
|
+
return [`schema: ${path}`];
|
|
5422
5419
|
}
|
|
5423
5420
|
const file = files.find((entry) => /create\s+table|alter\s+table/i.test(entry.content));
|
|
5424
5421
|
return file ? [`schema: ${file.path}`] : [];
|
|
@@ -5427,9 +5424,9 @@ function hasRlsEvidence(files, pathBlob) {
|
|
|
5427
5424
|
return /\/policies\/|_rls\.sql|\brls\b/i.test(pathBlob) || files.some((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(file.content));
|
|
5428
5425
|
}
|
|
5429
5426
|
function rlsEvidence(files, pathBlob) {
|
|
5430
|
-
const
|
|
5431
|
-
if (
|
|
5432
|
-
return [`rls: ${
|
|
5427
|
+
const path = pathBlob.split(/\r?\n/).find((entry) => /\/policies\/|_rls\.sql|\brls\b/i.test(entry));
|
|
5428
|
+
if (path) {
|
|
5429
|
+
return [`rls: ${path}`];
|
|
5433
5430
|
}
|
|
5434
5431
|
const file = files.find((entry) => /enable\s+row\s+level\s+security|create\s+policy|alter\s+table[\s\S]{0,200}enable\s+row\s+level/i.test(entry.content));
|
|
5435
5432
|
return file ? [`rls: ${file.path}`] : [];
|
|
@@ -5438,11 +5435,11 @@ function hasGeneratedTypes(files, pathBlob) {
|
|
|
5438
5435
|
return /database\.types\.[jt]s\b|supabase.*types\.[jt]s\b|types\/database\.[jt]s\b/i.test(pathBlob) || files.some((file) => /export\s+type\s+database\b|export\s+interface\s+database\b/i.test(file.content));
|
|
5439
5436
|
}
|
|
5440
5437
|
function generatedTypeEvidence(files, pathBlob) {
|
|
5441
|
-
const
|
|
5438
|
+
const path = pathBlob.split(/\r?\n/).find(
|
|
5442
5439
|
(entry) => /database\.types\.[jt]s\b|supabase.*types\.[jt]s\b|types\/database\.[jt]s\b/i.test(entry)
|
|
5443
5440
|
);
|
|
5444
|
-
if (
|
|
5445
|
-
return [`types: ${
|
|
5441
|
+
if (path) {
|
|
5442
|
+
return [`types: ${path}`];
|
|
5446
5443
|
}
|
|
5447
5444
|
const file = files.find((entry) => /export\s+type\s+database\b|export\s+interface\s+database\b/i.test(entry.content));
|
|
5448
5445
|
return file ? [`types: ${file.path}`] : [];
|
|
@@ -5459,8 +5456,8 @@ function serviceRoleSafetyItem(files) {
|
|
|
5459
5456
|
promptHint: "Move service-role usage to server-only code and use public anon keys in frontend clients."
|
|
5460
5457
|
};
|
|
5461
5458
|
}
|
|
5462
|
-
function isClientExecutedPath(
|
|
5463
|
-
return /\.(tsx|jsx)$/.test(
|
|
5459
|
+
function isClientExecutedPath(path) {
|
|
5460
|
+
return /\.(tsx|jsx)$/.test(path) || /(^|\/)(components|pages|app|client|frontend|web)\//.test(path) || /\.client\.[jt]sx?$/.test(path);
|
|
5464
5461
|
}
|
|
5465
5462
|
|
|
5466
5463
|
// ../../src/station/stackWiring.ts
|
|
@@ -6285,7 +6282,7 @@ function analyzeSecretsHygiene(ctx) {
|
|
|
6285
6282
|
promptSubject: "secrets hygiene",
|
|
6286
6283
|
items: [
|
|
6287
6284
|
item2("env-example-found", "Env example or docs found", Boolean(ctx.scan.stackSignals.hasEnvExample) || /\.env\.example|env\.example|environment variables/i.test(ctx.pathBlob + "\n" + ctx.contentBlob), pathEvidence2(ctx, /\.env\.example|env\.example/i), "Add an env example or setup docs with variable names only."),
|
|
6288
|
-
item2("secret-files-ignored", "Secret files detected as private", Array.isArray(ctx.scan.secretsFound) && ctx.scan.secretsFound.length > 0, ctx.scan.secretsFound.slice(0, 4).map((
|
|
6285
|
+
item2("secret-files-ignored", "Secret files detected as private", Array.isArray(ctx.scan.secretsFound) && ctx.scan.secretsFound.length > 0, ctx.scan.secretsFound.slice(0, 4).map((path) => `secret file: ${path}`), "Keep real .env files private and out of copied prompts or docs."),
|
|
6289
6286
|
item2("frontend-secrets-clean", "No obvious frontend secret exposure found", unsafePublicSecret.length === 0, unsafePublicSecret.slice(0, 4).map((file) => `unsafe reference: ${file.path}`), "Move secret values and private keys out of frontend/client-executed files."),
|
|
6290
6287
|
item2("gitignore-env-found", "Env files ignored by git", /\.gitignore/i.test(ctx.pathBlob) && /\.env/i.test(ctx.contentBlob), pathEvidence2(ctx, /\.gitignore/i), "Ensure .gitignore excludes real .env files while keeping .env.example committed."),
|
|
6291
6288
|
manualItem("production-secret-rotation-checked", "Production secret rotation checked", "Confirm production secrets can be rotated and revoked in provider dashboards.")
|
|
@@ -6361,29 +6358,29 @@ function summarize(summary) {
|
|
|
6361
6358
|
readinessPercent: Math.round(passedCount / Math.max(totalCount, 1) * 100)
|
|
6362
6359
|
};
|
|
6363
6360
|
}
|
|
6364
|
-
function item2(id,
|
|
6361
|
+
function item2(id, label, passed, evidence, promptHint) {
|
|
6365
6362
|
return {
|
|
6366
6363
|
id,
|
|
6367
|
-
label
|
|
6364
|
+
label,
|
|
6368
6365
|
status: passed ? "passed" : "missing",
|
|
6369
6366
|
evidence,
|
|
6370
6367
|
promptHint
|
|
6371
6368
|
};
|
|
6372
6369
|
}
|
|
6373
|
-
function manualItem(id,
|
|
6370
|
+
function manualItem(id, label, promptHint) {
|
|
6374
6371
|
return {
|
|
6375
6372
|
id,
|
|
6376
|
-
label
|
|
6373
|
+
label,
|
|
6377
6374
|
status: "manual",
|
|
6378
6375
|
evidence: [],
|
|
6379
6376
|
promptHint
|
|
6380
6377
|
};
|
|
6381
6378
|
}
|
|
6382
|
-
function secretSafetyItem(ctx, id,
|
|
6379
|
+
function secretSafetyItem(ctx, id, label, pattern, promptHint) {
|
|
6383
6380
|
const exposed = ctx.files.filter((file) => isClientExecutedPath2(file.normalizedPath) && pattern.test(file.content));
|
|
6384
6381
|
return {
|
|
6385
6382
|
id,
|
|
6386
|
-
label
|
|
6383
|
+
label,
|
|
6387
6384
|
status: exposed.length > 0 ? "missing" : "passed",
|
|
6388
6385
|
evidence: exposed.slice(0, 4).map((file) => `unsafe reference: ${file.path}`),
|
|
6389
6386
|
promptHint
|
|
@@ -6408,13 +6405,13 @@ function envEvidence2(ctx, patterns) {
|
|
|
6408
6405
|
return evidence.slice(0, 4);
|
|
6409
6406
|
}
|
|
6410
6407
|
function pathEvidence2(ctx, pattern) {
|
|
6411
|
-
return ctx.pathBlob.split(/\r?\n/).filter((
|
|
6408
|
+
return ctx.pathBlob.split(/\r?\n/).filter((path) => pattern.test(path)).slice(0, 4).map((path) => `file: ${path}`);
|
|
6412
6409
|
}
|
|
6413
6410
|
function fileEvidence(ctx, pattern) {
|
|
6414
6411
|
return ctx.files.filter((file) => pattern.test(file.path) || pattern.test(file.content)).slice(0, 4).map((file) => `file: ${file.path}`);
|
|
6415
6412
|
}
|
|
6416
|
-
function isClientExecutedPath2(
|
|
6417
|
-
return /\.(tsx|jsx)$/.test(
|
|
6413
|
+
function isClientExecutedPath2(path) {
|
|
6414
|
+
return /\.(tsx|jsx)$/.test(path) || /(^|\/)(components|pages|app|client|frontend|web)\//.test(path) || /\.client\.[jt]sx?$/.test(path);
|
|
6418
6415
|
}
|
|
6419
6416
|
|
|
6420
6417
|
// ../../src/station/verification.ts
|
|
@@ -6543,27 +6540,27 @@ function emptyArea(area) {
|
|
|
6543
6540
|
manual: []
|
|
6544
6541
|
};
|
|
6545
6542
|
}
|
|
6546
|
-
function normalizePath4(
|
|
6547
|
-
return
|
|
6543
|
+
function normalizePath4(path) {
|
|
6544
|
+
return path.replace(/\\/g, "/").replace(/^[\s\u2500\u2502\u2514\u251c>*+-]+/u, "").trim().toLowerCase();
|
|
6548
6545
|
}
|
|
6549
|
-
function addFound(area,
|
|
6550
|
-
addItem(area, "found",
|
|
6546
|
+
function addFound(area, label, source, detail) {
|
|
6547
|
+
addItem(area, "found", label, source, detail);
|
|
6551
6548
|
}
|
|
6552
|
-
function addMissing(area,
|
|
6553
|
-
addItem(area, "missing",
|
|
6549
|
+
function addMissing(area, label, source, detail) {
|
|
6550
|
+
addItem(area, "missing", label, source, detail);
|
|
6554
6551
|
}
|
|
6555
|
-
function addManual(area,
|
|
6556
|
-
addItem(area, "manual",
|
|
6552
|
+
function addManual(area, label, source, detail) {
|
|
6553
|
+
addItem(area, "manual", label, source, detail);
|
|
6557
6554
|
}
|
|
6558
|
-
function addItem(area, status,
|
|
6559
|
-
const item3 = compactItem(
|
|
6555
|
+
function addItem(area, status, label, source, detail) {
|
|
6556
|
+
const item3 = compactItem(label, status, source, detail);
|
|
6560
6557
|
const bucket = area[status];
|
|
6561
6558
|
if (!bucket.some((existing) => existing.label === item3.label)) {
|
|
6562
6559
|
bucket.push(item3);
|
|
6563
6560
|
}
|
|
6564
6561
|
}
|
|
6565
|
-
function compactItem(
|
|
6566
|
-
return detail ? { label
|
|
6562
|
+
function compactItem(label, status, source, detail) {
|
|
6563
|
+
return detail ? { label, status, source, detail } : { label, status, source };
|
|
6567
6564
|
}
|
|
6568
6565
|
function hasDep(ctx, patterns) {
|
|
6569
6566
|
return patterns.some((pattern) => {
|
|
@@ -6572,7 +6569,7 @@ function hasDep(ctx, patterns) {
|
|
|
6572
6569
|
});
|
|
6573
6570
|
}
|
|
6574
6571
|
function hasPath(ctx, patterns) {
|
|
6575
|
-
return [...ctx.paths].some((
|
|
6572
|
+
return [...ctx.paths].some((path) => !ctx.secretPaths.has(path) && patterns.some((pattern) => pattern.test(path)));
|
|
6576
6573
|
}
|
|
6577
6574
|
function hasContent(ctx, patterns) {
|
|
6578
6575
|
return patterns.some((pattern) => pattern.test(ctx.contents));
|
|
@@ -7141,7 +7138,7 @@ var mockGitHubVerifier = {
|
|
|
7141
7138
|
providerObservationMet: false,
|
|
7142
7139
|
fixType: "mcp-connect",
|
|
7143
7140
|
severity: "warning",
|
|
7144
|
-
repoSignals: workflows.map((
|
|
7141
|
+
repoSignals: workflows.map((path) => `workflow: ${path}`),
|
|
7145
7142
|
providerSignals: ["GitHub Actions API or MCP"],
|
|
7146
7143
|
requiredEvidence: ["Latest workflow run status on default branch"]
|
|
7147
7144
|
}),
|
|
@@ -7196,7 +7193,7 @@ var mockGitHubVerifier = {
|
|
|
7196
7193
|
providerActual: "Not verified (mock \u2014 connect GitHub MCP read-only)",
|
|
7197
7194
|
severity: "warning",
|
|
7198
7195
|
suggestedFix: "mcp-connect",
|
|
7199
|
-
evidenceRefs: workflows.map((
|
|
7196
|
+
evidenceRefs: workflows.map((path) => `workflow: ${path}`)
|
|
7200
7197
|
}
|
|
7201
7198
|
];
|
|
7202
7199
|
}
|
|
@@ -7998,12 +7995,12 @@ var import_promises5 = require("node:fs/promises");
|
|
|
7998
7995
|
var import_node_path8 = require("node:path");
|
|
7999
7996
|
|
|
8000
7997
|
// src/capabilities/classify.ts
|
|
8001
|
-
function gapText(
|
|
8002
|
-
return `${
|
|
7998
|
+
function gapText(gap) {
|
|
7999
|
+
return `${gap.id} ${gap.title} ${gap.detail} ${gap.primaryMapCategory}`.toLowerCase();
|
|
8003
8000
|
}
|
|
8004
8001
|
function statusForGaps(gaps) {
|
|
8005
|
-
if (gaps.some((
|
|
8006
|
-
if (gaps.some((
|
|
8002
|
+
if (gaps.some((gap) => gap.severity === "critical")) return "critical";
|
|
8003
|
+
if (gaps.some((gap) => gap.severity === "warning")) return "warning";
|
|
8007
8004
|
if (gaps.length > 0) return "warning";
|
|
8008
8005
|
return "unknown";
|
|
8009
8006
|
}
|
|
@@ -8023,12 +8020,12 @@ function capabilityFromArea(area) {
|
|
|
8023
8020
|
// src/capabilities/database.ts
|
|
8024
8021
|
var databasePack = {
|
|
8025
8022
|
key: "database",
|
|
8026
|
-
classify(
|
|
8027
|
-
const text = gapText(
|
|
8023
|
+
classify(gap) {
|
|
8024
|
+
const text = gapText(gap);
|
|
8028
8025
|
return /database|supabase|rls|migration|postgres|pooler|query/.test(text);
|
|
8029
8026
|
},
|
|
8030
|
-
riskTags(
|
|
8031
|
-
const text = gapText(
|
|
8027
|
+
riskTags(gap) {
|
|
8028
|
+
const text = gapText(gap);
|
|
8032
8029
|
return [
|
|
8033
8030
|
text.includes("rls") ? "rls" : "",
|
|
8034
8031
|
text.includes("migration") ? "migration" : "",
|
|
@@ -8040,12 +8037,12 @@ var databasePack = {
|
|
|
8040
8037
|
// src/capabilities/payments.ts
|
|
8041
8038
|
var paymentsPack = {
|
|
8042
8039
|
key: "payments",
|
|
8043
|
-
classify(
|
|
8044
|
-
const text = gapText(
|
|
8040
|
+
classify(gap) {
|
|
8041
|
+
const text = gapText(gap);
|
|
8045
8042
|
return /payment|stripe|checkout|billing|entitlement|subscription|refund|cancel/.test(text);
|
|
8046
8043
|
},
|
|
8047
|
-
riskTags(
|
|
8048
|
-
const text = gapText(
|
|
8044
|
+
riskTags(gap) {
|
|
8045
|
+
const text = gapText(gap);
|
|
8049
8046
|
return [
|
|
8050
8047
|
text.includes("entitlement") ? "entitlement-source" : "",
|
|
8051
8048
|
text.includes("checkout") ? "checkout-flow" : "",
|
|
@@ -8057,12 +8054,12 @@ var paymentsPack = {
|
|
|
8057
8054
|
// src/capabilities/scaling.ts
|
|
8058
8055
|
var scalingPack = {
|
|
8059
8056
|
key: "scaling",
|
|
8060
|
-
classify(
|
|
8061
|
-
const text = gapText(
|
|
8057
|
+
classify(gap) {
|
|
8058
|
+
const text = gapText(gap);
|
|
8062
8059
|
return /serverless|vercel|pooler|rate limit|rate-limit|cache|queue|cron|worker|runtime|connection/.test(text);
|
|
8063
8060
|
},
|
|
8064
|
-
riskTags(
|
|
8065
|
-
const text = gapText(
|
|
8061
|
+
riskTags(gap) {
|
|
8062
|
+
const text = gapText(gap);
|
|
8066
8063
|
return [
|
|
8067
8064
|
text.includes("serverless") || text.includes("vercel") ? "serverless" : "",
|
|
8068
8065
|
text.includes("pooler") || text.includes("connection") ? "db-connection" : "",
|
|
@@ -8074,12 +8071,12 @@ var scalingPack = {
|
|
|
8074
8071
|
// src/capabilities/security.ts
|
|
8075
8072
|
var securityPack = {
|
|
8076
8073
|
key: "security",
|
|
8077
|
-
classify(
|
|
8078
|
-
const text = gapText(
|
|
8074
|
+
classify(gap) {
|
|
8075
|
+
const text = gapText(gap);
|
|
8079
8076
|
return /secret|service role|token|api key|auth|authorization|browser-exposed|cors|csrf|session/.test(text);
|
|
8080
8077
|
},
|
|
8081
|
-
riskTags(
|
|
8082
|
-
const text = gapText(
|
|
8078
|
+
riskTags(gap) {
|
|
8079
|
+
const text = gapText(gap);
|
|
8083
8080
|
return [
|
|
8084
8081
|
text.includes("secret") || text.includes("api key") || text.includes("service role") ? "secret-boundary" : "",
|
|
8085
8082
|
text.includes("auth") || text.includes("authorization") ? "auth-boundary" : "",
|
|
@@ -8091,12 +8088,12 @@ var securityPack = {
|
|
|
8091
8088
|
// src/capabilities/webhooks.ts
|
|
8092
8089
|
var webhooksPack = {
|
|
8093
8090
|
key: "webhooks",
|
|
8094
|
-
classify(
|
|
8095
|
-
const text = gapText(
|
|
8091
|
+
classify(gap) {
|
|
8092
|
+
const text = gapText(gap);
|
|
8096
8093
|
return /webhook|signature|idempotency|retry|replay|dead-letter/.test(text);
|
|
8097
8094
|
},
|
|
8098
|
-
riskTags(
|
|
8099
|
-
const text = gapText(
|
|
8095
|
+
riskTags(gap) {
|
|
8096
|
+
const text = gapText(gap);
|
|
8100
8097
|
return [
|
|
8101
8098
|
text.includes("signature") ? "signature" : "",
|
|
8102
8099
|
text.includes("idempotency") ? "idempotency" : "",
|
|
@@ -8107,10 +8104,10 @@ var webhooksPack = {
|
|
|
8107
8104
|
|
|
8108
8105
|
// src/capabilities/index.ts
|
|
8109
8106
|
var PACKS = [scalingPack, securityPack, webhooksPack, paymentsPack, databasePack];
|
|
8110
|
-
function classifyGapCapability(
|
|
8111
|
-
const direct = PACKS.find((pack) => pack.classify(
|
|
8107
|
+
function classifyGapCapability(gap) {
|
|
8108
|
+
const direct = PACKS.find((pack) => pack.classify(gap));
|
|
8112
8109
|
if (direct) return direct.key;
|
|
8113
|
-
return capabilityFromArea(String(
|
|
8110
|
+
return capabilityFromArea(String(gap.primaryMapCategory)) ?? "security";
|
|
8114
8111
|
}
|
|
8115
8112
|
function summarizeCapabilities(gaps) {
|
|
8116
8113
|
const result = Object.fromEntries(
|
|
@@ -8120,13 +8117,13 @@ function summarizeCapabilities(gaps) {
|
|
|
8120
8117
|
])
|
|
8121
8118
|
);
|
|
8122
8119
|
for (const pack of PACKS) {
|
|
8123
|
-
const matching = gaps.filter((
|
|
8120
|
+
const matching = gaps.filter((gap) => classifyGapCapability(gap) === pack.key);
|
|
8124
8121
|
result[pack.key] = {
|
|
8125
8122
|
key: pack.key,
|
|
8126
8123
|
status: statusForGaps(matching),
|
|
8127
|
-
topGapIds: matching.slice(0, 5).map((
|
|
8124
|
+
topGapIds: matching.slice(0, 5).map((gap) => gap.id),
|
|
8128
8125
|
evidenceCount: matching.length,
|
|
8129
|
-
riskTags: unique2(matching.flatMap((
|
|
8126
|
+
riskTags: unique2(matching.flatMap((gap) => pack.riskTags(gap)))
|
|
8130
8127
|
};
|
|
8131
8128
|
}
|
|
8132
8129
|
return result;
|
|
@@ -8134,8 +8131,8 @@ function summarizeCapabilities(gaps) {
|
|
|
8134
8131
|
|
|
8135
8132
|
// src/contracts/contextMap.ts
|
|
8136
8133
|
function generateContextMap(artifact) {
|
|
8137
|
-
const criticalCount = artifact.gaps.filter((
|
|
8138
|
-
const warningCount = artifact.gaps.filter((
|
|
8134
|
+
const criticalCount = artifact.gaps.filter((gap) => gap.severity === "critical").length;
|
|
8135
|
+
const warningCount = artifact.gaps.filter((gap) => gap.severity === "warning").length;
|
|
8139
8136
|
return {
|
|
8140
8137
|
$schema: "https://viberaven.dev/schemas/context-map.schema.json",
|
|
8141
8138
|
schemaVersion: "v1",
|
|
@@ -8168,12 +8165,12 @@ function generateContextMap(artifact) {
|
|
|
8168
8165
|
warningCount,
|
|
8169
8166
|
providerDashboardChecksRequired: true
|
|
8170
8167
|
},
|
|
8171
|
-
topGaps: artifact.gaps.slice(0, 10).map((
|
|
8172
|
-
id:
|
|
8173
|
-
severity:
|
|
8174
|
-
title:
|
|
8175
|
-
area: String(
|
|
8176
|
-
promptCommand: promptGapCommand(
|
|
8168
|
+
topGaps: artifact.gaps.slice(0, 10).map((gap) => ({
|
|
8169
|
+
id: gap.id,
|
|
8170
|
+
severity: gap.severity,
|
|
8171
|
+
title: gap.title,
|
|
8172
|
+
area: String(gap.primaryMapCategory),
|
|
8173
|
+
promptCommand: promptGapCommand(gap.id)
|
|
8177
8174
|
}))
|
|
8178
8175
|
};
|
|
8179
8176
|
}
|
|
@@ -8188,10 +8185,10 @@ function runIdFrom(scannedAt) {
|
|
|
8188
8185
|
return `vr_${scannedAt.replace(/\D/g, "").slice(0, 14) || "scan"}`;
|
|
8189
8186
|
}
|
|
8190
8187
|
function generateGateResult(artifact, options = {}) {
|
|
8191
|
-
const criticalCount = artifact.gaps.filter((
|
|
8192
|
-
const warningCount = artifact.gaps.filter((
|
|
8188
|
+
const criticalCount = artifact.gaps.filter((gap) => gap.severity === "critical").length;
|
|
8189
|
+
const warningCount = artifact.gaps.filter((gap) => gap.severity === "warning").length;
|
|
8193
8190
|
const capabilities = summarizeCapabilities(artifact.gaps);
|
|
8194
|
-
const topGapIds = artifact.gaps.slice(0, 10).map((
|
|
8191
|
+
const topGapIds = artifact.gaps.slice(0, 10).map((gap) => gap.id);
|
|
8195
8192
|
const firstGapId = topGapIds[0];
|
|
8196
8193
|
return {
|
|
8197
8194
|
$schema: "https://viberaven.dev/schemas/gate-result.schema.json",
|
|
@@ -8240,24 +8237,24 @@ function generateGateResult(artifact, options = {}) {
|
|
|
8240
8237
|
}
|
|
8241
8238
|
|
|
8242
8239
|
// src/contracts/gapEvidence.ts
|
|
8243
|
-
function summarizeGap(
|
|
8244
|
-
return `${
|
|
8240
|
+
function summarizeGap(gap) {
|
|
8241
|
+
return `${gap.title}. See the tasklist and original scan for redacted detail.`;
|
|
8245
8242
|
}
|
|
8246
8243
|
function generateGapEvidenceFiles(artifact) {
|
|
8247
|
-
return artifact.gaps.slice(0, 50).map((
|
|
8248
|
-
path: `.viberaven/gaps/${
|
|
8244
|
+
return artifact.gaps.slice(0, 50).map((gap) => ({
|
|
8245
|
+
path: `.viberaven/gaps/${gap.id}.json`,
|
|
8249
8246
|
content: {
|
|
8250
8247
|
$schema: "https://viberaven.dev/schemas/gap.schema.json",
|
|
8251
8248
|
schemaVersion: "v1",
|
|
8252
|
-
id:
|
|
8253
|
-
severity:
|
|
8254
|
-
capability: classifyGapCapability(
|
|
8255
|
-
title:
|
|
8256
|
-
summary: summarizeGap(
|
|
8257
|
-
evidence: [{ kind: String(
|
|
8249
|
+
id: gap.id,
|
|
8250
|
+
severity: gap.severity,
|
|
8251
|
+
capability: classifyGapCapability(gap),
|
|
8252
|
+
title: gap.title,
|
|
8253
|
+
summary: summarizeGap(gap),
|
|
8254
|
+
evidence: [{ kind: String(gap.primaryMapCategory), redacted: true }],
|
|
8258
8255
|
commands: {
|
|
8259
|
-
prompt: promptGapCommand(
|
|
8260
|
-
healPlan: healPlanGapCommand(
|
|
8256
|
+
prompt: promptGapCommand(gap.id),
|
|
8257
|
+
healPlan: healPlanGapCommand(gap.id),
|
|
8261
8258
|
verify: PUBLIC_VERIFY_COMMAND
|
|
8262
8259
|
},
|
|
8263
8260
|
providerBoundary: {
|
|
@@ -8306,6 +8303,39 @@ var PRODUCTION_MAP_CATEGORY_KEYS_ALL = PRODUCTION_MAP_LANES.map(
|
|
|
8306
8303
|
(lane) => lane.extensionKey
|
|
8307
8304
|
);
|
|
8308
8305
|
|
|
8306
|
+
// src/playbooks/checkMap.ts
|
|
8307
|
+
var CHECK_TO_PLAYBOOK = {
|
|
8308
|
+
"vercel-deployment": "vercel",
|
|
8309
|
+
"vercel-project": "vercel",
|
|
8310
|
+
"supabase-project": "supabase",
|
|
8311
|
+
"supabase-env": "supabase",
|
|
8312
|
+
"stripe-webhook": "stripe",
|
|
8313
|
+
"stripe-product": "stripe",
|
|
8314
|
+
"stripe-keys": "stripe"
|
|
8315
|
+
};
|
|
8316
|
+
function mapCheckToPlaybook(check) {
|
|
8317
|
+
if (CHECK_TO_PLAYBOOK[check.id]) {
|
|
8318
|
+
return CHECK_TO_PLAYBOOK[check.id];
|
|
8319
|
+
}
|
|
8320
|
+
const providerKey = check.providerKey.toLowerCase();
|
|
8321
|
+
if (providerKey.includes("vercel") || check.area === "deployment") {
|
|
8322
|
+
return "vercel";
|
|
8323
|
+
}
|
|
8324
|
+
if (providerKey.includes("stripe") || check.area === "payments") {
|
|
8325
|
+
return "stripe";
|
|
8326
|
+
}
|
|
8327
|
+
if (providerKey.includes("supabase") && check.area === "auth") {
|
|
8328
|
+
return "auth-supabase";
|
|
8329
|
+
}
|
|
8330
|
+
if (providerKey.includes("supabase") || check.area === "database") {
|
|
8331
|
+
return "supabase";
|
|
8332
|
+
}
|
|
8333
|
+
if (check.area === "auth") {
|
|
8334
|
+
return "auth-supabase";
|
|
8335
|
+
}
|
|
8336
|
+
return "vercel";
|
|
8337
|
+
}
|
|
8338
|
+
|
|
8309
8339
|
// src/playbooks/loadPlaybook.ts
|
|
8310
8340
|
var import_node_fs5 = require("node:fs");
|
|
8311
8341
|
var import_promises3 = require("node:fs/promises");
|
|
@@ -8416,8 +8446,8 @@ async function loadPlaybook(provider2) {
|
|
|
8416
8446
|
`Unknown provider "${provider2}". Available: ${PLAYBOOK_PROVIDERS.join(", ")}`
|
|
8417
8447
|
);
|
|
8418
8448
|
}
|
|
8419
|
-
const
|
|
8420
|
-
const raw = JSON.parse(await (0, import_promises3.readFile)(
|
|
8449
|
+
const path = (0, import_node_path5.join)(playbooksRoot(), `${normalized}.json`);
|
|
8450
|
+
const raw = JSON.parse(await (0, import_promises3.readFile)(path, "utf-8"));
|
|
8421
8451
|
const playbook = parsePlaybook(raw);
|
|
8422
8452
|
if (playbook.provider !== normalized) {
|
|
8423
8453
|
throw new Error(`Playbook file ${normalized}.json has mismatched provider field`);
|
|
@@ -8431,8 +8461,8 @@ function loadPlaybookSync(provider2) {
|
|
|
8431
8461
|
`Unknown provider "${provider2}". Available: ${PLAYBOOK_PROVIDERS.join(", ")}`
|
|
8432
8462
|
);
|
|
8433
8463
|
}
|
|
8434
|
-
const
|
|
8435
|
-
const raw = JSON.parse((0, import_node_fs5.readFileSync)(
|
|
8464
|
+
const path = (0, import_node_path5.join)(playbooksRoot(), `${normalized}.json`);
|
|
8465
|
+
const raw = JSON.parse((0, import_node_fs5.readFileSync)(path, "utf-8"));
|
|
8436
8466
|
const playbook = parsePlaybook(raw);
|
|
8437
8467
|
if (playbook.provider !== normalized) {
|
|
8438
8468
|
throw new Error(`Playbook file ${normalized}.json has mismatched provider field`);
|
|
@@ -8440,35 +8470,28 @@ function loadPlaybookSync(provider2) {
|
|
|
8440
8470
|
return playbook;
|
|
8441
8471
|
}
|
|
8442
8472
|
|
|
8443
|
-
// src/
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
supabase: [
|
|
8447
|
-
"NEXT_PUBLIC_SUPABASE_URL",
|
|
8448
|
-
"NEXT_PUBLIC_SUPABASE_ANON_KEY",
|
|
8449
|
-
"SUPABASE_SERVICE_ROLE_KEY"
|
|
8450
|
-
],
|
|
8451
|
-
vercel: []
|
|
8452
|
-
};
|
|
8453
|
-
var GAP_KEYS = {
|
|
8454
|
-
stripe_webhook_signature_missing: ["STRIPE_WEBHOOK_SECRET"],
|
|
8455
|
-
missing_prod_env_stripe_webhook_secret: ["STRIPE_WEBHOOK_SECRET"],
|
|
8456
|
-
rls_disabled: ["SUPABASE_SERVICE_ROLE_KEY"]
|
|
8457
|
-
};
|
|
8458
|
-
function normalizeProvider2(provider2) {
|
|
8459
|
-
const normalized = provider2.trim().toLowerCase().replace(/\s+/g, "-");
|
|
8460
|
-
if (normalized === "auth-supabase" || normalized === "supabase-auth") {
|
|
8461
|
-
return "supabase";
|
|
8462
|
-
}
|
|
8463
|
-
return normalized;
|
|
8473
|
+
// src/playbooks/manualChecks.ts
|
|
8474
|
+
function isManualProviderCheck(check) {
|
|
8475
|
+
return check.evidenceClass === "manual-dashboard" || check.evidenceClass === "mcp-verifier" || check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.status === "needs-connection" || check.status === "unknown";
|
|
8464
8476
|
}
|
|
8465
|
-
function
|
|
8466
|
-
const
|
|
8467
|
-
|
|
8468
|
-
|
|
8477
|
+
function collectManualChecks(artifact) {
|
|
8478
|
+
const refs = [];
|
|
8479
|
+
for (const area of artifact.missionGraph.areas ?? []) {
|
|
8480
|
+
for (const mission of area.providerMissions) {
|
|
8481
|
+
for (const check of mission.checks) {
|
|
8482
|
+
if (!isManualProviderCheck(check)) {
|
|
8483
|
+
continue;
|
|
8484
|
+
}
|
|
8485
|
+
refs.push({
|
|
8486
|
+
areaLabel: area.label,
|
|
8487
|
+
providerLabel: mission.providerLabel,
|
|
8488
|
+
check,
|
|
8489
|
+
mapCategory: area.key
|
|
8490
|
+
});
|
|
8491
|
+
}
|
|
8492
|
+
}
|
|
8469
8493
|
}
|
|
8470
|
-
|
|
8471
|
-
return providerKeys ? [...providerKeys] : [];
|
|
8494
|
+
return refs;
|
|
8472
8495
|
}
|
|
8473
8496
|
|
|
8474
8497
|
// src/tui/menu.ts
|
|
@@ -8522,10 +8545,10 @@ function formatTopGapsList(artifact, limit = 10) {
|
|
|
8522
8545
|
if (sorted.length === 0) {
|
|
8523
8546
|
return "No gaps found \u2014 production core looks solid.";
|
|
8524
8547
|
}
|
|
8525
|
-
return sorted.slice(0, limit).map((
|
|
8526
|
-
const severity =
|
|
8527
|
-
const area =
|
|
8528
|
-
return `${index + 1}. [${severity}] ${area} ${
|
|
8548
|
+
return sorted.slice(0, limit).map((gap, index) => {
|
|
8549
|
+
const severity = gap.severity.toUpperCase().padEnd(8);
|
|
8550
|
+
const area = gap.primaryMapCategory.padEnd(12);
|
|
8551
|
+
return `${index + 1}. [${severity}] ${area} ${gap.title}`;
|
|
8529
8552
|
}).join("\n");
|
|
8530
8553
|
}
|
|
8531
8554
|
async function loadLastArtifact(startDir) {
|
|
@@ -8533,563 +8556,123 @@ async function loadLastArtifact(startDir) {
|
|
|
8533
8556
|
if (!workspace) {
|
|
8534
8557
|
throw new ScanNotFoundError(needsScanMessage(startDir));
|
|
8535
8558
|
}
|
|
8536
|
-
const
|
|
8559
|
+
const path = (0, import_node_path6.join)(getProjectArtifactsDir(workspace), "last-scan.json");
|
|
8537
8560
|
try {
|
|
8538
|
-
const raw = await (0, import_promises4.readFile)(
|
|
8561
|
+
const raw = await (0, import_promises4.readFile)(path, "utf-8");
|
|
8539
8562
|
return JSON.parse(raw);
|
|
8540
8563
|
} catch {
|
|
8541
8564
|
throw new ScanNotFoundError(needsScanMessage(startDir));
|
|
8542
8565
|
}
|
|
8543
8566
|
}
|
|
8544
8567
|
|
|
8545
|
-
// src/
|
|
8546
|
-
var
|
|
8547
|
-
|
|
8548
|
-
"
|
|
8549
|
-
"
|
|
8550
|
-
"
|
|
8551
|
-
|
|
8552
|
-
"
|
|
8553
|
-
"
|
|
8554
|
-
|
|
8555
|
-
// W3 file-create recipes
|
|
8556
|
-
"missing_error_boundary",
|
|
8557
|
-
"missing_health_route",
|
|
8558
|
-
"missing_loading_state",
|
|
8559
|
-
"missing_404_page",
|
|
8560
|
-
// W3 file-patch recipes
|
|
8561
|
-
"missing_csp_header",
|
|
8562
|
-
"missing_rate_limit",
|
|
8563
|
-
"eslint_restricted_imports"
|
|
8564
|
-
// NOTE: 'rls_disabled' is intentionally NOT here — it is provider-action only,
|
|
8565
|
-
// canAutoApply=false, and should be classified as 'provider-action' by buildTaskList.
|
|
8566
|
-
]);
|
|
8567
|
-
function hasRecipe(gapId) {
|
|
8568
|
-
if (KNOWN_RECIPE_GAP_IDS.has(gapId)) return true;
|
|
8569
|
-
const lower = gapId.toLowerCase();
|
|
8570
|
-
return lower.includes("empty") && lower.includes("catch");
|
|
8571
|
-
}
|
|
8572
|
-
function gapToPlaybookProvider(gap2) {
|
|
8573
|
-
const cat = gap2.primaryMapCategory.toLowerCase();
|
|
8574
|
-
if (cat === "deployment") return "vercel";
|
|
8575
|
-
if (cat === "payments") return "stripe";
|
|
8576
|
-
if (cat === "auth") return "auth-supabase";
|
|
8577
|
-
if (cat === "database") return "supabase";
|
|
8578
|
-
const id = gap2.id.toLowerCase();
|
|
8579
|
-
if (id.includes("vercel") || id.includes("deploy")) return "vercel";
|
|
8580
|
-
if (id.includes("stripe") || id.includes("payment")) return "stripe";
|
|
8581
|
-
if (id.includes("supabase") || id.includes("database") || id.includes("db")) return "supabase";
|
|
8582
|
-
if (id.includes("auth")) return "auth-supabase";
|
|
8583
|
-
return void 0;
|
|
8584
|
-
}
|
|
8585
|
-
function hasPlaybook(gap2) {
|
|
8586
|
-
try {
|
|
8587
|
-
const provider2 = gapToPlaybookProvider(gap2);
|
|
8588
|
-
if (!provider2) return false;
|
|
8589
|
-
return PLAYBOOK_PROVIDERS.includes(provider2);
|
|
8590
|
-
} catch {
|
|
8591
|
-
return false;
|
|
8592
|
-
}
|
|
8593
|
-
}
|
|
8594
|
-
function buildProviderAction(gap2, provider2) {
|
|
8595
|
-
try {
|
|
8596
|
-
const playbook = loadPlaybookSync(provider2);
|
|
8597
|
-
const step = playbook.steps[0];
|
|
8598
|
-
if (!step) return void 0;
|
|
8599
|
-
return {
|
|
8600
|
-
provider: provider2,
|
|
8601
|
-
dashboardUrl: step.openUrl ?? `https://${provider2}.com`,
|
|
8602
|
-
// PlaybookStep uses `instruction` — map to exactStep
|
|
8603
|
-
exactStep: step.instruction,
|
|
8604
|
-
envKeyName: envKeysForGap(gap2.id, provider2)[0],
|
|
8605
|
-
envKeyExample: void 0,
|
|
8606
|
-
// Use step title as the done signal
|
|
8607
|
-
doneSignal: `${step.title} step completed`,
|
|
8608
|
-
verifyCommand: PUBLIC_VERIFY_COMMAND
|
|
8609
|
-
};
|
|
8610
|
-
} catch {
|
|
8611
|
-
return void 0;
|
|
8612
|
-
}
|
|
8613
|
-
}
|
|
8568
|
+
// src/resolveNextAction.ts
|
|
8569
|
+
var UPGRADE_URL = "https://viberaven.dev/pricing";
|
|
8570
|
+
var LOCKED_LANE_LABELS = {
|
|
8571
|
+
deployment: "Deployment",
|
|
8572
|
+
monitoring: "Monitoring / Analytics",
|
|
8573
|
+
security: "Security",
|
|
8574
|
+
testing: "Testing",
|
|
8575
|
+
landing: "Onboarding",
|
|
8576
|
+
errorHandling: "Error handling / observability"
|
|
8577
|
+
};
|
|
8614
8578
|
function unlockedKeys(artifact) {
|
|
8615
8579
|
const keys = artifact.usage?.unlockedMapCategoryKeys ?? FREE_TRIAL_UNLOCKED_MAP_CATEGORY_KEYS;
|
|
8616
8580
|
return new Set(keys);
|
|
8617
8581
|
}
|
|
8618
|
-
function
|
|
8582
|
+
function resolveNextAction(artifact) {
|
|
8619
8583
|
const unlocked = unlockedKeys(artifact);
|
|
8620
|
-
const
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
}
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8584
|
+
const repoGap = sortGapsByPriority(artifact.gaps).find(
|
|
8585
|
+
(gap) => unlocked.has(gap.primaryMapCategory)
|
|
8586
|
+
);
|
|
8587
|
+
if (repoGap) {
|
|
8588
|
+
return {
|
|
8589
|
+
type: "repo-fix",
|
|
8590
|
+
title: repoGap.title,
|
|
8591
|
+
detail: repoGap.detail,
|
|
8592
|
+
command: `viberaven prompt --gap ${repoGap.id}`
|
|
8593
|
+
};
|
|
8594
|
+
}
|
|
8595
|
+
const manual = collectManualChecks(artifact).find((item3) => unlocked.has(item3.mapCategory));
|
|
8596
|
+
if (manual) {
|
|
8597
|
+
const provider2 = mapCheckToPlaybook(manual.check);
|
|
8598
|
+
let openUrl;
|
|
8599
|
+
try {
|
|
8600
|
+
const playbook = loadPlaybookSync(provider2);
|
|
8601
|
+
openUrl = playbook.steps[0]?.openUrl;
|
|
8602
|
+
} catch {
|
|
8603
|
+
openUrl = void 0;
|
|
8633
8604
|
}
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8605
|
+
return {
|
|
8606
|
+
type: "provider-guide",
|
|
8607
|
+
title: manual.check.label,
|
|
8608
|
+
detail: manual.check.promptHint || `Configure ${manual.providerLabel} in the provider dashboard (${manual.areaLabel}).`,
|
|
8609
|
+
provider: provider2,
|
|
8610
|
+
playbookStep: 1,
|
|
8611
|
+
command: `viberaven guide ${provider2} --step 1`,
|
|
8612
|
+
openUrl
|
|
8642
8613
|
};
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8614
|
+
}
|
|
8615
|
+
const lockedGap = sortGapsByPriority(artifact.gaps).find(
|
|
8616
|
+
(gap) => !unlocked.has(gap.primaryMapCategory)
|
|
8617
|
+
);
|
|
8618
|
+
if (lockedGap) {
|
|
8619
|
+
const lane = lockedGap.primaryMapCategory;
|
|
8620
|
+
return {
|
|
8621
|
+
type: "upgrade",
|
|
8622
|
+
title: lockedGap.title,
|
|
8623
|
+
detail: `Free plan covers 6/12 mission map lanes. Upgrade to fix ${LOCKED_LANE_LABELS[lane] ?? lane}.`,
|
|
8624
|
+
lockedLane: lane,
|
|
8625
|
+
upgradeUrl: UPGRADE_URL
|
|
8626
|
+
};
|
|
8627
|
+
}
|
|
8628
|
+
const lockedManual = collectManualChecks(artifact).find((item3) => !unlocked.has(item3.mapCategory));
|
|
8629
|
+
if (lockedManual) {
|
|
8630
|
+
const lane = lockedManual.mapCategory;
|
|
8631
|
+
return {
|
|
8632
|
+
type: "upgrade",
|
|
8633
|
+
title: lockedManual.check.label,
|
|
8634
|
+
detail: `This check is in the ${LOCKED_LANE_LABELS[lane] ?? lane} lane (Pro).`,
|
|
8635
|
+
lockedLane: lane,
|
|
8636
|
+
upgradeUrl: UPGRADE_URL
|
|
8637
|
+
};
|
|
8638
|
+
}
|
|
8639
|
+
if (unlocked.size < 12) {
|
|
8640
|
+
return {
|
|
8641
|
+
type: "done",
|
|
8642
|
+
title: "No blockers in unlocked lanes",
|
|
8643
|
+
detail: "Run scan again after changes. Upgrade for deployment, monitoring, security, testing, and onboarding lanes.",
|
|
8644
|
+
upgradeUrl: UPGRADE_URL
|
|
8645
|
+
};
|
|
8646
|
+
}
|
|
8647
|
+
return {
|
|
8648
|
+
type: "done",
|
|
8649
|
+
title: "No blockers found",
|
|
8650
|
+
detail: "Re-scan after changes to confirm production readiness."
|
|
8651
|
+
};
|
|
8652
|
+
}
|
|
8653
|
+
|
|
8654
|
+
// src/report/agentSummary.ts
|
|
8655
|
+
var UPGRADE_URL2 = "https://viberaven.dev/pricing";
|
|
8656
|
+
var LOCKED_LANE_KEYS = [
|
|
8657
|
+
"deployment",
|
|
8658
|
+
"monitoring",
|
|
8659
|
+
"security",
|
|
8660
|
+
"testing",
|
|
8661
|
+
"landing",
|
|
8662
|
+
"errorHandling"
|
|
8663
|
+
];
|
|
8664
|
+
var SEVERITY_ORDER = {
|
|
8665
|
+
critical: 0,
|
|
8666
|
+
warning: 1,
|
|
8667
|
+
info: 2
|
|
8668
|
+
};
|
|
8669
|
+
function sortGaps(gaps) {
|
|
8670
|
+
return [...gaps].sort((a, b3) => {
|
|
8671
|
+
const sev = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b3.severity];
|
|
8672
|
+
if (sev !== 0) {
|
|
8673
|
+
return sev;
|
|
8664
8674
|
}
|
|
8665
|
-
return
|
|
8666
|
-
});
|
|
8667
|
-
}
|
|
8668
|
-
function buildTaskListMarkdown(tasks) {
|
|
8669
|
-
if (tasks.length === 0) {
|
|
8670
|
-
return "# VibeRaven Agent Tasklist\n\n_No gaps found \u2014 production core looks solid._\n";
|
|
8671
|
-
}
|
|
8672
|
-
const lines = ["# VibeRaven Agent Tasklist", ""];
|
|
8673
|
-
for (const task of tasks) {
|
|
8674
|
-
const severityLabel = task.severity.toUpperCase();
|
|
8675
|
-
lines.push(`## ${task.id} \xB7 ${task.gapId} \xB7 ${severityLabel}`, "");
|
|
8676
|
-
lines.push(`**Fix type:** ${task.fixType} `);
|
|
8677
|
-
if (task.file) {
|
|
8678
|
-
lines.push(`**File:** \`${task.file}\` `);
|
|
8679
|
-
}
|
|
8680
|
-
if (task.action) {
|
|
8681
|
-
lines.push(`**Action:** ${task.action} `);
|
|
8682
|
-
}
|
|
8683
|
-
if (task.exactFix) {
|
|
8684
|
-
lines.push(`**Exact fix:** ${task.exactFix} `);
|
|
8685
|
-
} else {
|
|
8686
|
-
lines.push(`**Exact fix:** No automated recipe \u2014 see scanner hint. `);
|
|
8687
|
-
}
|
|
8688
|
-
lines.push(`**Verify:** \`${task.verifyCommand}\` `);
|
|
8689
|
-
if (task.mcpTool && task.mcpArgs) {
|
|
8690
|
-
lines.push(
|
|
8691
|
-
`**MCP:** \`${task.mcpTool} ${JSON.stringify(task.mcpArgs)}\` `
|
|
8692
|
-
);
|
|
8693
|
-
}
|
|
8694
|
-
lines.push(`**Requires user action:** ${task.requiresUserAction}`);
|
|
8695
|
-
if (task.providerAction) {
|
|
8696
|
-
const pa = task.providerAction;
|
|
8697
|
-
lines.push("");
|
|
8698
|
-
lines.push("**Provider action:**");
|
|
8699
|
-
lines.push(`- Provider: ${pa.provider}`);
|
|
8700
|
-
lines.push(`- Dashboard: ${pa.dashboardUrl}`);
|
|
8701
|
-
lines.push(`- Step: ${pa.exactStep}`);
|
|
8702
|
-
if (pa.envKeyName) lines.push(`- Env key: \`${pa.envKeyName}\``);
|
|
8703
|
-
if (pa.doneSignal) lines.push(`- Done when: ${pa.doneSignal}`);
|
|
8704
|
-
if (pa.mcpAlternative) lines.push(`- MCP alternative: \`${pa.mcpAlternative}\``);
|
|
8705
|
-
}
|
|
8706
|
-
lines.push("");
|
|
8707
|
-
}
|
|
8708
|
-
return `${lines.join("\n")}
|
|
8709
|
-
`;
|
|
8710
|
-
}
|
|
8711
|
-
|
|
8712
|
-
// src/sanitizeArtifact.ts
|
|
8713
|
-
var INLINE_SECRET_PATTERNS = [
|
|
8714
|
-
/\b(sk_(?:live|test)_[A-Za-z0-9]{12,}|sk-proj-[A-Za-z0-9_-]{16,}|sk-[A-Za-z0-9_-]{20,})\b/g,
|
|
8715
|
-
/\b(whsec_[A-Za-z0-9]{12,}|rk_(?:live|test)_[A-Za-z0-9]{12,})\b/g,
|
|
8716
|
-
/\bghp_[A-Za-z0-9]{36,}\b/g,
|
|
8717
|
-
/\bgithub_pat_[A-Za-z0-9_]{50,}\b/g,
|
|
8718
|
-
/\bxox[bp]-[A-Za-z0-9-]{20,}\b/g,
|
|
8719
|
-
/\bxapp-[A-Za-z0-9-]{20,}\b/g,
|
|
8720
|
-
/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,
|
|
8721
|
-
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g
|
|
8722
|
-
];
|
|
8723
|
-
var SENSITIVE_ENV_KEYS = /(?:^|_)(?:ACCESS_TOKEN|AUTHORIZATION|API_KEY|SECRET|SECRET_KEY|SERVICE_ROLE_KEY|TOKEN|PASSWORD|PRIVATE_KEY|CREDENTIALS?)(?:$|_)/i;
|
|
8724
|
-
function redactString(value) {
|
|
8725
|
-
let out = value;
|
|
8726
|
-
for (const pattern of INLINE_SECRET_PATTERNS) {
|
|
8727
|
-
out = out.replace(pattern, pattern.source.includes("PRIVATE KEY") ? "[REDACTED_PRIVATE_KEY]" : "[REDACTED_SECRET]");
|
|
8728
|
-
}
|
|
8729
|
-
out = out.replace(/\bAuthorization\s*:\s*([A-Za-z][A-Za-z0-9._-]*)\s+[^\s;,]+/gi, "Authorization: $1 [REDACTED]");
|
|
8730
|
-
return out.replace(
|
|
8731
|
-
/\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,
|
|
8732
|
-
"$1=[REDACTED]"
|
|
8733
|
-
);
|
|
8734
|
-
}
|
|
8735
|
-
function redactUnknown(value) {
|
|
8736
|
-
if (typeof value === "string") {
|
|
8737
|
-
return redactString(value);
|
|
8738
|
-
}
|
|
8739
|
-
if (Array.isArray(value)) {
|
|
8740
|
-
return value.map(redactUnknown);
|
|
8741
|
-
}
|
|
8742
|
-
if (value && typeof value === "object") {
|
|
8743
|
-
const record = value;
|
|
8744
|
-
const next = {};
|
|
8745
|
-
for (const [key, entry] of Object.entries(record)) {
|
|
8746
|
-
if (SENSITIVE_ENV_KEYS.test(key)) {
|
|
8747
|
-
next[key] = "[REDACTED]";
|
|
8748
|
-
} else {
|
|
8749
|
-
next[key] = redactUnknown(entry);
|
|
8750
|
-
}
|
|
8751
|
-
}
|
|
8752
|
-
return next;
|
|
8753
|
-
}
|
|
8754
|
-
return value;
|
|
8755
|
-
}
|
|
8756
|
-
function sanitizeArtifactForDisk(artifact) {
|
|
8757
|
-
return redactUnknown(artifact);
|
|
8758
|
-
}
|
|
8759
|
-
|
|
8760
|
-
// src/risk/riskMapping.ts
|
|
8761
|
-
var OWASP = {
|
|
8762
|
-
promptInjection: "LLM01:2025 Prompt Injection",
|
|
8763
|
-
sensitiveDisclosure: "LLM06:2025 Sensitive Information Disclosure",
|
|
8764
|
-
improperOutputHandling: "LLM05:2025 Improper Output Handling",
|
|
8765
|
-
excessiveAgency: "LLM08:2025 Excessive Agency",
|
|
8766
|
-
unboundedConsumption: "LLM10:2025 Unbounded Consumption"
|
|
8767
|
-
};
|
|
8768
|
-
var SAFECODE = {
|
|
8769
|
-
dataProtection: "Data protection and least-data exposure",
|
|
8770
|
-
callbackValidation: "Callback validation and message integrity",
|
|
8771
|
-
leastPrivilege: "Least privilege and access control",
|
|
8772
|
-
rateLimiting: "Rate limiting and quota enforcement",
|
|
8773
|
-
outputValidation: "Output validation and encoding",
|
|
8774
|
-
promptBoundary: "Prompt boundary validation"
|
|
8775
|
-
};
|
|
8776
|
-
function tokenize(text) {
|
|
8777
|
-
return text.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
8778
|
-
}
|
|
8779
|
-
function hasKeyword(tokens, keyword) {
|
|
8780
|
-
const keywordTokens = tokenize(keyword);
|
|
8781
|
-
if (keywordTokens.length === 0) {
|
|
8782
|
-
return false;
|
|
8783
|
-
}
|
|
8784
|
-
if (keywordTokens.length === 1) {
|
|
8785
|
-
return tokens.includes(keywordTokens[0]);
|
|
8786
|
-
}
|
|
8787
|
-
const textPhrase = ` ${tokens.join(" ")} `;
|
|
8788
|
-
const keywordPhrase = keywordTokens.join(" ");
|
|
8789
|
-
return textPhrase.includes(` ${keywordPhrase} `);
|
|
8790
|
-
}
|
|
8791
|
-
function hasAny(tokens, keywords) {
|
|
8792
|
-
return keywords.some((keyword) => hasKeyword(tokens, keyword));
|
|
8793
|
-
}
|
|
8794
|
-
function addUnique(values, value) {
|
|
8795
|
-
if (!values.includes(value)) {
|
|
8796
|
-
values.push(value);
|
|
8797
|
-
}
|
|
8798
|
-
}
|
|
8799
|
-
function mapGapToRisk(gap2) {
|
|
8800
|
-
const haystack = `${gap2.id} ${gap2.title} ${gap2.primaryMapCategory}`.toLowerCase();
|
|
8801
|
-
const tokens = tokenize(haystack);
|
|
8802
|
-
const owaspLlm = [];
|
|
8803
|
-
const safecode = [];
|
|
8804
|
-
if (hasAny(tokens, ["prompt injection", "prompt_injection", "jailbreak"])) {
|
|
8805
|
-
addUnique(owaspLlm, OWASP.promptInjection);
|
|
8806
|
-
addUnique(safecode, SAFECODE.promptBoundary);
|
|
8807
|
-
}
|
|
8808
|
-
if (hasAny(tokens, [
|
|
8809
|
-
"rls",
|
|
8810
|
-
"row level security",
|
|
8811
|
-
"data exposure",
|
|
8812
|
-
"sensitive information",
|
|
8813
|
-
"sensitive info",
|
|
8814
|
-
"secret exposure"
|
|
8815
|
-
])) {
|
|
8816
|
-
addUnique(owaspLlm, OWASP.sensitiveDisclosure);
|
|
8817
|
-
addUnique(safecode, SAFECODE.dataProtection);
|
|
8818
|
-
}
|
|
8819
|
-
if (hasAny(tokens, ["webhook", "callback"]) && hasAny(tokens, ["signature", "signing", "integrity", "payment", "stripe"])) {
|
|
8820
|
-
addUnique(owaspLlm, OWASP.improperOutputHandling);
|
|
8821
|
-
addUnique(safecode, SAFECODE.callbackValidation);
|
|
8822
|
-
}
|
|
8823
|
-
if (hasAny(tokens, ["auth", "access", "permission", "role", "privilege"])) {
|
|
8824
|
-
addUnique(owaspLlm, OWASP.excessiveAgency);
|
|
8825
|
-
addUnique(safecode, SAFECODE.leastPrivilege);
|
|
8826
|
-
}
|
|
8827
|
-
if (hasAny(tokens, ["rate limit", "rate_limit", "quota", "usage limit", "throttle"])) {
|
|
8828
|
-
addUnique(owaspLlm, OWASP.unboundedConsumption);
|
|
8829
|
-
addUnique(safecode, SAFECODE.rateLimiting);
|
|
8830
|
-
}
|
|
8831
|
-
if (hasAny(tokens, ["output handling", "output validation", "sanitize", "escaping", "encoding"])) {
|
|
8832
|
-
addUnique(owaspLlm, OWASP.improperOutputHandling);
|
|
8833
|
-
addUnique(safecode, SAFECODE.outputValidation);
|
|
8834
|
-
}
|
|
8835
|
-
return { owaspLlm, safecode };
|
|
8836
|
-
}
|
|
8837
|
-
|
|
8838
|
-
// src/contracts/prp.ts
|
|
8839
|
-
function decisionFromGateStatus(status) {
|
|
8840
|
-
if (status === "clear") return "CLEAR";
|
|
8841
|
-
if (status === "warning") return "WARNING";
|
|
8842
|
-
return "BLOCKED";
|
|
8843
|
-
}
|
|
8844
|
-
function actionContractFor(task) {
|
|
8845
|
-
if (task.fixType === "repo-code") {
|
|
8846
|
-
return {
|
|
8847
|
-
owner: "coding_agent",
|
|
8848
|
-
actionClass: "local_repo_write",
|
|
8849
|
-
command: healApplyGapCommand(task.gapId)
|
|
8850
|
-
};
|
|
8851
|
-
}
|
|
8852
|
-
if (task.fixType === "provider-action") {
|
|
8853
|
-
return {
|
|
8854
|
-
owner: "human",
|
|
8855
|
-
actionClass: "manual_provider_step",
|
|
8856
|
-
command: promptGapCommand(task.gapId)
|
|
8857
|
-
};
|
|
8858
|
-
}
|
|
8859
|
-
if (task.fixType === "upgrade-required") {
|
|
8860
|
-
return {
|
|
8861
|
-
owner: "human",
|
|
8862
|
-
actionClass: "upgrade_required",
|
|
8863
|
-
command: promptGapCommand(task.gapId)
|
|
8864
|
-
};
|
|
8865
|
-
}
|
|
8866
|
-
if (task.fixType === "manual-verify") {
|
|
8867
|
-
return {
|
|
8868
|
-
owner: "human",
|
|
8869
|
-
actionClass: "local_repo_read",
|
|
8870
|
-
command: promptGapCommand(task.gapId)
|
|
8871
|
-
};
|
|
8872
|
-
}
|
|
8873
|
-
return {
|
|
8874
|
-
owner: "coding_agent",
|
|
8875
|
-
actionClass: "local_repo_read",
|
|
8876
|
-
command: promptGapCommand(task.gapId)
|
|
8877
|
-
};
|
|
8878
|
-
}
|
|
8879
|
-
function nextActionFromTask(task) {
|
|
8880
|
-
return {
|
|
8881
|
-
id: task.id,
|
|
8882
|
-
gapId: task.gapId,
|
|
8883
|
-
severity: task.severity,
|
|
8884
|
-
title: task.title,
|
|
8885
|
-
fixType: task.fixType,
|
|
8886
|
-
requiresUserAction: task.requiresUserAction,
|
|
8887
|
-
verifyCommand: task.verifyCommand,
|
|
8888
|
-
...actionContractFor(task)
|
|
8889
|
-
};
|
|
8890
|
-
}
|
|
8891
|
-
function generateProductionProtocol(artifact, options = {}) {
|
|
8892
|
-
const safeArtifact = sanitizeArtifactForDisk(artifact);
|
|
8893
|
-
const contextMap = generateContextMap(safeArtifact);
|
|
8894
|
-
const tasks = buildTaskList(safeArtifact);
|
|
8895
|
-
return {
|
|
8896
|
-
$schema: "https://viberaven.dev/schemas/prp.schema.json",
|
|
8897
|
-
schemaVersion: "v1",
|
|
8898
|
-
generatedAt: safeArtifact.scannedAt,
|
|
8899
|
-
mode: options.mode ?? "live",
|
|
8900
|
-
decision: decisionFromGateStatus(contextMap.productionGate.status),
|
|
8901
|
-
detectedStack: {
|
|
8902
|
-
archetype: safeArtifact.archetype ?? null,
|
|
8903
|
-
deployment: contextMap.stack.deployment,
|
|
8904
|
-
database: contextMap.stack.database,
|
|
8905
|
-
auth: contextMap.stack.auth,
|
|
8906
|
-
payments: contextMap.stack.payments,
|
|
8907
|
-
monitoring: contextMap.stack.monitoring
|
|
8908
|
-
},
|
|
8909
|
-
findings: safeArtifact.gaps.map((gap2) => ({
|
|
8910
|
-
id: gap2.id,
|
|
8911
|
-
severity: gap2.severity,
|
|
8912
|
-
title: gap2.title,
|
|
8913
|
-
area: String(gap2.primaryMapCategory),
|
|
8914
|
-
riskMapping: mapGapToRisk(gap2)
|
|
8915
|
-
})),
|
|
8916
|
-
nextActions: tasks.map(nextActionFromTask),
|
|
8917
|
-
connectedTools: options.connectedTools ?? {},
|
|
8918
|
-
providerActionsRequired: tasks.some((task) => task.fixType === "provider-action"),
|
|
8919
|
-
verifyCommand: PUBLIC_VERIFY_COMMAND,
|
|
8920
|
-
agentInstructions: [
|
|
8921
|
-
"Read .viberaven/agent-tasklist.md before making launch fixes.",
|
|
8922
|
-
`Apply repo-code nextActions first, then run ${PUBLIC_VERIFY_COMMAND} once per batch.`,
|
|
8923
|
-
"For human-owned actions, use the command to open the focused provider prompt and do not request secrets."
|
|
8924
|
-
]
|
|
8925
|
-
};
|
|
8926
|
-
}
|
|
8927
|
-
|
|
8928
|
-
// src/playbooks/checkMap.ts
|
|
8929
|
-
var CHECK_TO_PLAYBOOK = {
|
|
8930
|
-
"vercel-deployment": "vercel",
|
|
8931
|
-
"vercel-project": "vercel",
|
|
8932
|
-
"supabase-project": "supabase",
|
|
8933
|
-
"supabase-env": "supabase",
|
|
8934
|
-
"stripe-webhook": "stripe",
|
|
8935
|
-
"stripe-product": "stripe",
|
|
8936
|
-
"stripe-keys": "stripe"
|
|
8937
|
-
};
|
|
8938
|
-
function mapCheckToPlaybook(check) {
|
|
8939
|
-
if (CHECK_TO_PLAYBOOK[check.id]) {
|
|
8940
|
-
return CHECK_TO_PLAYBOOK[check.id];
|
|
8941
|
-
}
|
|
8942
|
-
const providerKey = check.providerKey.toLowerCase();
|
|
8943
|
-
if (providerKey.includes("vercel") || check.area === "deployment") {
|
|
8944
|
-
return "vercel";
|
|
8945
|
-
}
|
|
8946
|
-
if (providerKey.includes("stripe") || check.area === "payments") {
|
|
8947
|
-
return "stripe";
|
|
8948
|
-
}
|
|
8949
|
-
if (providerKey.includes("supabase") && check.area === "auth") {
|
|
8950
|
-
return "auth-supabase";
|
|
8951
|
-
}
|
|
8952
|
-
if (providerKey.includes("supabase") || check.area === "database") {
|
|
8953
|
-
return "supabase";
|
|
8954
|
-
}
|
|
8955
|
-
if (check.area === "auth") {
|
|
8956
|
-
return "auth-supabase";
|
|
8957
|
-
}
|
|
8958
|
-
return "vercel";
|
|
8959
|
-
}
|
|
8960
|
-
|
|
8961
|
-
// src/playbooks/manualChecks.ts
|
|
8962
|
-
function isManualProviderCheck(check) {
|
|
8963
|
-
return check.evidenceClass === "manual-dashboard" || check.evidenceClass === "mcp-verifier" || check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.status === "needs-connection" || check.status === "unknown";
|
|
8964
|
-
}
|
|
8965
|
-
function collectManualChecks(artifact) {
|
|
8966
|
-
const refs = [];
|
|
8967
|
-
for (const area of artifact.missionGraph.areas ?? []) {
|
|
8968
|
-
for (const mission of area.providerMissions) {
|
|
8969
|
-
for (const check of mission.checks) {
|
|
8970
|
-
if (!isManualProviderCheck(check)) {
|
|
8971
|
-
continue;
|
|
8972
|
-
}
|
|
8973
|
-
refs.push({
|
|
8974
|
-
areaLabel: area.label,
|
|
8975
|
-
providerLabel: mission.providerLabel,
|
|
8976
|
-
check,
|
|
8977
|
-
mapCategory: area.key
|
|
8978
|
-
});
|
|
8979
|
-
}
|
|
8980
|
-
}
|
|
8981
|
-
}
|
|
8982
|
-
return refs;
|
|
8983
|
-
}
|
|
8984
|
-
|
|
8985
|
-
// src/resolveNextAction.ts
|
|
8986
|
-
var UPGRADE_URL = "https://viberaven.dev/pricing";
|
|
8987
|
-
var LOCKED_LANE_LABELS = {
|
|
8988
|
-
deployment: "Deployment",
|
|
8989
|
-
monitoring: "Monitoring / Analytics",
|
|
8990
|
-
security: "Security",
|
|
8991
|
-
testing: "Testing",
|
|
8992
|
-
landing: "Onboarding",
|
|
8993
|
-
errorHandling: "Error handling / observability"
|
|
8994
|
-
};
|
|
8995
|
-
function unlockedKeys2(artifact) {
|
|
8996
|
-
const keys = artifact.usage?.unlockedMapCategoryKeys ?? FREE_TRIAL_UNLOCKED_MAP_CATEGORY_KEYS;
|
|
8997
|
-
return new Set(keys);
|
|
8998
|
-
}
|
|
8999
|
-
function resolveNextAction(artifact) {
|
|
9000
|
-
const unlocked = unlockedKeys2(artifact);
|
|
9001
|
-
const repoGap = sortGapsByPriority(artifact.gaps).find(
|
|
9002
|
-
(gap2) => unlocked.has(gap2.primaryMapCategory)
|
|
9003
|
-
);
|
|
9004
|
-
if (repoGap) {
|
|
9005
|
-
return {
|
|
9006
|
-
type: "repo-fix",
|
|
9007
|
-
title: repoGap.title,
|
|
9008
|
-
detail: repoGap.detail,
|
|
9009
|
-
command: `viberaven prompt --gap ${repoGap.id}`
|
|
9010
|
-
};
|
|
9011
|
-
}
|
|
9012
|
-
const manual = collectManualChecks(artifact).find((item3) => unlocked.has(item3.mapCategory));
|
|
9013
|
-
if (manual) {
|
|
9014
|
-
const provider2 = mapCheckToPlaybook(manual.check);
|
|
9015
|
-
let openUrl;
|
|
9016
|
-
try {
|
|
9017
|
-
const playbook = loadPlaybookSync(provider2);
|
|
9018
|
-
openUrl = playbook.steps[0]?.openUrl;
|
|
9019
|
-
} catch {
|
|
9020
|
-
openUrl = void 0;
|
|
9021
|
-
}
|
|
9022
|
-
return {
|
|
9023
|
-
type: "provider-guide",
|
|
9024
|
-
title: manual.check.label,
|
|
9025
|
-
detail: manual.check.promptHint || `Configure ${manual.providerLabel} in the provider dashboard (${manual.areaLabel}).`,
|
|
9026
|
-
provider: provider2,
|
|
9027
|
-
playbookStep: 1,
|
|
9028
|
-
command: `viberaven guide ${provider2} --step 1`,
|
|
9029
|
-
openUrl
|
|
9030
|
-
};
|
|
9031
|
-
}
|
|
9032
|
-
const lockedGap = sortGapsByPriority(artifact.gaps).find(
|
|
9033
|
-
(gap2) => !unlocked.has(gap2.primaryMapCategory)
|
|
9034
|
-
);
|
|
9035
|
-
if (lockedGap) {
|
|
9036
|
-
const lane = lockedGap.primaryMapCategory;
|
|
9037
|
-
return {
|
|
9038
|
-
type: "upgrade",
|
|
9039
|
-
title: lockedGap.title,
|
|
9040
|
-
detail: `Free plan covers 6/12 mission map lanes. Upgrade to fix ${LOCKED_LANE_LABELS[lane] ?? lane}.`,
|
|
9041
|
-
lockedLane: lane,
|
|
9042
|
-
upgradeUrl: UPGRADE_URL
|
|
9043
|
-
};
|
|
9044
|
-
}
|
|
9045
|
-
const lockedManual = collectManualChecks(artifact).find((item3) => !unlocked.has(item3.mapCategory));
|
|
9046
|
-
if (lockedManual) {
|
|
9047
|
-
const lane = lockedManual.mapCategory;
|
|
9048
|
-
return {
|
|
9049
|
-
type: "upgrade",
|
|
9050
|
-
title: lockedManual.check.label,
|
|
9051
|
-
detail: `This check is in the ${LOCKED_LANE_LABELS[lane] ?? lane} lane (Pro).`,
|
|
9052
|
-
lockedLane: lane,
|
|
9053
|
-
upgradeUrl: UPGRADE_URL
|
|
9054
|
-
};
|
|
9055
|
-
}
|
|
9056
|
-
if (unlocked.size < 12) {
|
|
9057
|
-
return {
|
|
9058
|
-
type: "done",
|
|
9059
|
-
title: "No blockers in unlocked lanes",
|
|
9060
|
-
detail: "Run scan again after changes. Upgrade for deployment, monitoring, security, testing, and onboarding lanes.",
|
|
9061
|
-
upgradeUrl: UPGRADE_URL
|
|
9062
|
-
};
|
|
9063
|
-
}
|
|
9064
|
-
return {
|
|
9065
|
-
type: "done",
|
|
9066
|
-
title: "No blockers found",
|
|
9067
|
-
detail: "Re-scan after changes to confirm production readiness."
|
|
9068
|
-
};
|
|
9069
|
-
}
|
|
9070
|
-
|
|
9071
|
-
// src/report/agentSummary.ts
|
|
9072
|
-
var UPGRADE_URL2 = "https://viberaven.dev/pricing";
|
|
9073
|
-
var LOCKED_LANE_KEYS = [
|
|
9074
|
-
"deployment",
|
|
9075
|
-
"monitoring",
|
|
9076
|
-
"security",
|
|
9077
|
-
"testing",
|
|
9078
|
-
"landing",
|
|
9079
|
-
"errorHandling"
|
|
9080
|
-
];
|
|
9081
|
-
var SEVERITY_ORDER = {
|
|
9082
|
-
critical: 0,
|
|
9083
|
-
warning: 1,
|
|
9084
|
-
info: 2
|
|
9085
|
-
};
|
|
9086
|
-
function sortGaps(gaps) {
|
|
9087
|
-
return [...gaps].sort((a, b3) => {
|
|
9088
|
-
const sev = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b3.severity];
|
|
9089
|
-
if (sev !== 0) {
|
|
9090
|
-
return sev;
|
|
9091
|
-
}
|
|
9092
|
-
return a.title.localeCompare(b3.title);
|
|
8675
|
+
return a.title.localeCompare(b3.title);
|
|
9093
8676
|
});
|
|
9094
8677
|
}
|
|
9095
8678
|
function generateAgentSummary(artifact) {
|
|
@@ -9154,14 +8737,14 @@ function generateAgentSummary(artifact) {
|
|
|
9154
8737
|
if (topGaps.length === 0) {
|
|
9155
8738
|
lines.push("_No model gaps returned. Review mission map checks before changing code._");
|
|
9156
8739
|
} else {
|
|
9157
|
-
topGaps.forEach((
|
|
8740
|
+
topGaps.forEach((gap, index) => {
|
|
9158
8741
|
lines.push(
|
|
9159
|
-
`${index + 1}. **${
|
|
8742
|
+
`${index + 1}. **${gap.title}** (\`${gap.id}\`, ${gap.severity}, map: \`${gap.primaryMapCategory}\`)`
|
|
9160
8743
|
);
|
|
9161
|
-
lines.push(` - ${
|
|
9162
|
-
lines.push(` - Command: \`viberaven prompt --gap ${
|
|
9163
|
-
if (
|
|
9164
|
-
lines.push(` - Prompt: ${
|
|
8744
|
+
lines.push(` - ${gap.detail}`);
|
|
8745
|
+
lines.push(` - Command: \`viberaven prompt --gap ${gap.id}\``);
|
|
8746
|
+
if (gap.copyPrompt) {
|
|
8747
|
+
lines.push(` - Prompt: ${gap.copyPrompt}`);
|
|
9165
8748
|
}
|
|
9166
8749
|
});
|
|
9167
8750
|
}
|
|
@@ -9206,6 +8789,174 @@ function generateAgentSummary(artifact) {
|
|
|
9206
8789
|
`;
|
|
9207
8790
|
}
|
|
9208
8791
|
|
|
8792
|
+
// src/buildTaskList.ts
|
|
8793
|
+
var KNOWN_RECIPE_GAP_IDS = /* @__PURE__ */ new Set([
|
|
8794
|
+
// emptyCatch (original recipe)
|
|
8795
|
+
"empty-catch",
|
|
8796
|
+
"empty_catch",
|
|
8797
|
+
"emptyCatch",
|
|
8798
|
+
// W3 env-add recipes
|
|
8799
|
+
"auth_secret_missing",
|
|
8800
|
+
"node_env_not_set",
|
|
8801
|
+
"database_url_missing",
|
|
8802
|
+
// W3 file-create recipes
|
|
8803
|
+
"missing_error_boundary",
|
|
8804
|
+
"missing_health_route",
|
|
8805
|
+
"missing_loading_state",
|
|
8806
|
+
"missing_404_page",
|
|
8807
|
+
// W3 file-patch recipes
|
|
8808
|
+
"missing_csp_header",
|
|
8809
|
+
"missing_rate_limit",
|
|
8810
|
+
"eslint_restricted_imports"
|
|
8811
|
+
// NOTE: 'rls_disabled' is intentionally NOT here — it is provider-action only,
|
|
8812
|
+
// canAutoApply=false, and should be classified as 'provider-action' by buildTaskList.
|
|
8813
|
+
]);
|
|
8814
|
+
function hasRecipe(gapId) {
|
|
8815
|
+
if (KNOWN_RECIPE_GAP_IDS.has(gapId)) return true;
|
|
8816
|
+
const lower = gapId.toLowerCase();
|
|
8817
|
+
return lower.includes("empty") && lower.includes("catch");
|
|
8818
|
+
}
|
|
8819
|
+
function gapToPlaybookProvider(gap) {
|
|
8820
|
+
const cat = gap.primaryMapCategory.toLowerCase();
|
|
8821
|
+
if (cat === "deployment") return "vercel";
|
|
8822
|
+
if (cat === "payments") return "stripe";
|
|
8823
|
+
if (cat === "auth") return "auth-supabase";
|
|
8824
|
+
if (cat === "database") return "supabase";
|
|
8825
|
+
const id = gap.id.toLowerCase();
|
|
8826
|
+
if (id.includes("vercel") || id.includes("deploy")) return "vercel";
|
|
8827
|
+
if (id.includes("stripe") || id.includes("payment")) return "stripe";
|
|
8828
|
+
if (id.includes("supabase") || id.includes("database") || id.includes("db")) return "supabase";
|
|
8829
|
+
if (id.includes("auth")) return "auth-supabase";
|
|
8830
|
+
return void 0;
|
|
8831
|
+
}
|
|
8832
|
+
function hasPlaybook(gap) {
|
|
8833
|
+
try {
|
|
8834
|
+
const provider2 = gapToPlaybookProvider(gap);
|
|
8835
|
+
if (!provider2) return false;
|
|
8836
|
+
return PLAYBOOK_PROVIDERS.includes(provider2);
|
|
8837
|
+
} catch {
|
|
8838
|
+
return false;
|
|
8839
|
+
}
|
|
8840
|
+
}
|
|
8841
|
+
function buildProviderAction(gap, provider2) {
|
|
8842
|
+
try {
|
|
8843
|
+
const playbook = loadPlaybookSync(provider2);
|
|
8844
|
+
const step = playbook.steps[0];
|
|
8845
|
+
if (!step) return void 0;
|
|
8846
|
+
return {
|
|
8847
|
+
provider: provider2,
|
|
8848
|
+
dashboardUrl: step.openUrl ?? `https://${provider2}.com`,
|
|
8849
|
+
// PlaybookStep uses `instruction` — map to exactStep
|
|
8850
|
+
exactStep: step.instruction,
|
|
8851
|
+
// No envKeyName/envKeyExample in current PlaybookStep schema — leave undefined
|
|
8852
|
+
envKeyName: void 0,
|
|
8853
|
+
envKeyExample: void 0,
|
|
8854
|
+
// Use step title as the done signal
|
|
8855
|
+
doneSignal: `${step.title} step completed`,
|
|
8856
|
+
verifyCommand: PUBLIC_VERIFY_COMMAND
|
|
8857
|
+
};
|
|
8858
|
+
} catch {
|
|
8859
|
+
return void 0;
|
|
8860
|
+
}
|
|
8861
|
+
}
|
|
8862
|
+
function unlockedKeys2(artifact) {
|
|
8863
|
+
const keys = artifact.usage?.unlockedMapCategoryKeys ?? FREE_TRIAL_UNLOCKED_MAP_CATEGORY_KEYS;
|
|
8864
|
+
return new Set(keys);
|
|
8865
|
+
}
|
|
8866
|
+
function buildTaskList(artifact) {
|
|
8867
|
+
const unlocked = unlockedKeys2(artifact);
|
|
8868
|
+
const sorted = sortGapsByPriority(artifact.gaps);
|
|
8869
|
+
return sorted.map((gap, index) => {
|
|
8870
|
+
const id = `TASK-${String(index + 1).padStart(3, "0")}`;
|
|
8871
|
+
const isLocked = !unlocked.has(gap.primaryMapCategory);
|
|
8872
|
+
let fixType;
|
|
8873
|
+
if (isLocked) {
|
|
8874
|
+
fixType = "upgrade-required";
|
|
8875
|
+
} else if (hasRecipe(gap.id)) {
|
|
8876
|
+
fixType = "repo-code";
|
|
8877
|
+
} else if (hasPlaybook(gap)) {
|
|
8878
|
+
fixType = "provider-action";
|
|
8879
|
+
} else {
|
|
8880
|
+
fixType = "manual-verify";
|
|
8881
|
+
}
|
|
8882
|
+
const base = {
|
|
8883
|
+
id,
|
|
8884
|
+
gapId: gap.id,
|
|
8885
|
+
severity: gap.severity,
|
|
8886
|
+
fixType,
|
|
8887
|
+
title: gap.title,
|
|
8888
|
+
verifyCommand: PUBLIC_VERIFY_COMMAND,
|
|
8889
|
+
requiresUserAction: fixType !== "repo-code"
|
|
8890
|
+
};
|
|
8891
|
+
if (fixType === "repo-code") {
|
|
8892
|
+
base.mcpTool = "viberaven_heal_apply";
|
|
8893
|
+
base.mcpArgs = { gap: gap.id, yes: true };
|
|
8894
|
+
base.requiresUserAction = false;
|
|
8895
|
+
if (gap.copyPrompt) {
|
|
8896
|
+
base.exactFix = gap.copyPrompt.trim();
|
|
8897
|
+
}
|
|
8898
|
+
} else if (fixType === "provider-action") {
|
|
8899
|
+
try {
|
|
8900
|
+
const provider2 = gapToPlaybookProvider(gap);
|
|
8901
|
+
if (provider2) {
|
|
8902
|
+
const pa = buildProviderAction(gap, provider2);
|
|
8903
|
+
if (pa) {
|
|
8904
|
+
base.providerAction = pa;
|
|
8905
|
+
base.action = pa.exactStep;
|
|
8906
|
+
}
|
|
8907
|
+
}
|
|
8908
|
+
} catch {
|
|
8909
|
+
base.fixType = "manual-verify";
|
|
8910
|
+
}
|
|
8911
|
+
base.requiresUserAction = true;
|
|
8912
|
+
}
|
|
8913
|
+
return base;
|
|
8914
|
+
});
|
|
8915
|
+
}
|
|
8916
|
+
function buildTaskListMarkdown(tasks) {
|
|
8917
|
+
if (tasks.length === 0) {
|
|
8918
|
+
return "# VibeRaven Agent Tasklist\n\n_No gaps found \u2014 production core looks solid._\n";
|
|
8919
|
+
}
|
|
8920
|
+
const lines = ["# VibeRaven Agent Tasklist", ""];
|
|
8921
|
+
for (const task of tasks) {
|
|
8922
|
+
const severityLabel = task.severity.toUpperCase();
|
|
8923
|
+
lines.push(`## ${task.id} \xB7 ${task.gapId} \xB7 ${severityLabel}`, "");
|
|
8924
|
+
lines.push(`**Fix type:** ${task.fixType} `);
|
|
8925
|
+
if (task.file) {
|
|
8926
|
+
lines.push(`**File:** \`${task.file}\` `);
|
|
8927
|
+
}
|
|
8928
|
+
if (task.action) {
|
|
8929
|
+
lines.push(`**Action:** ${task.action} `);
|
|
8930
|
+
}
|
|
8931
|
+
if (task.exactFix) {
|
|
8932
|
+
lines.push(`**Exact fix:** ${task.exactFix} `);
|
|
8933
|
+
} else {
|
|
8934
|
+
lines.push(`**Exact fix:** No automated recipe \u2014 see scanner hint. `);
|
|
8935
|
+
}
|
|
8936
|
+
lines.push(`**Verify:** \`${task.verifyCommand}\` `);
|
|
8937
|
+
if (task.mcpTool && task.mcpArgs) {
|
|
8938
|
+
lines.push(
|
|
8939
|
+
`**MCP:** \`${task.mcpTool} ${JSON.stringify(task.mcpArgs)}\` `
|
|
8940
|
+
);
|
|
8941
|
+
}
|
|
8942
|
+
lines.push(`**Requires user action:** ${task.requiresUserAction}`);
|
|
8943
|
+
if (task.providerAction) {
|
|
8944
|
+
const pa = task.providerAction;
|
|
8945
|
+
lines.push("");
|
|
8946
|
+
lines.push("**Provider action:**");
|
|
8947
|
+
lines.push(`- Provider: ${pa.provider}`);
|
|
8948
|
+
lines.push(`- Dashboard: ${pa.dashboardUrl}`);
|
|
8949
|
+
lines.push(`- Step: ${pa.exactStep}`);
|
|
8950
|
+
if (pa.envKeyName) lines.push(`- Env key: \`${pa.envKeyName}\``);
|
|
8951
|
+
if (pa.doneSignal) lines.push(`- Done when: ${pa.doneSignal}`);
|
|
8952
|
+
if (pa.mcpAlternative) lines.push(`- MCP alternative: \`${pa.mcpAlternative}\``);
|
|
8953
|
+
}
|
|
8954
|
+
lines.push("");
|
|
8955
|
+
}
|
|
8956
|
+
return `${lines.join("\n")}
|
|
8957
|
+
`;
|
|
8958
|
+
}
|
|
8959
|
+
|
|
9209
8960
|
// src/report/launchPlaybook.ts
|
|
9210
8961
|
var UPGRADE_URL3 = "https://viberaven.dev/pricing";
|
|
9211
8962
|
function unlockedSet(artifact) {
|
|
@@ -9219,13 +8970,13 @@ function generateLaunchPlaybook(artifact) {
|
|
|
9219
8970
|
lines.push(`Generated from scan at ${artifact.scannedAt}. Work top to bottom.`, "");
|
|
9220
8971
|
lines.push("## Repo fixes (agent code)", "");
|
|
9221
8972
|
const repoGaps = sortGapsByPriority(artifact.gaps).filter(
|
|
9222
|
-
(
|
|
8973
|
+
(gap) => unlocked.has(gap.primaryMapCategory)
|
|
9223
8974
|
);
|
|
9224
8975
|
if (repoGaps.length === 0) {
|
|
9225
8976
|
lines.push("_No repo-code gaps in unlocked lanes._", "");
|
|
9226
8977
|
} else {
|
|
9227
|
-
for (const
|
|
9228
|
-
lines.push(`- [ ] ${
|
|
8978
|
+
for (const gap of repoGaps) {
|
|
8979
|
+
lines.push(`- [ ] ${gap.title} \u2014 \`viberaven prompt --gap ${gap.id}\``);
|
|
9229
8980
|
}
|
|
9230
8981
|
lines.push("");
|
|
9231
8982
|
}
|
|
@@ -9245,14 +8996,14 @@ function generateLaunchPlaybook(artifact) {
|
|
|
9245
8996
|
if (!isPro) {
|
|
9246
8997
|
lines.push("## [Pro] Deployment, monitoring, and full map", "");
|
|
9247
8998
|
const proGaps = sortGapsByPriority(artifact.gaps).filter(
|
|
9248
|
-
(
|
|
8999
|
+
(gap) => !unlocked.has(gap.primaryMapCategory)
|
|
9249
9000
|
);
|
|
9250
9001
|
const proManuals = collectManualChecks(artifact).filter((item3) => !unlocked.has(item3.mapCategory));
|
|
9251
9002
|
if (proGaps.length === 0 && proManuals.length === 0) {
|
|
9252
9003
|
lines.push("_No Pro-only blockers detected._", "");
|
|
9253
9004
|
} else {
|
|
9254
|
-
for (const
|
|
9255
|
-
lines.push(`- [ ] ${
|
|
9005
|
+
for (const gap of proGaps.slice(0, 6)) {
|
|
9006
|
+
lines.push(`- [ ] ${gap.title} [Pro] \u2014 upgrade at ${UPGRADE_URL3}`);
|
|
9256
9007
|
}
|
|
9257
9008
|
for (const item3 of proManuals.slice(0, 6)) {
|
|
9258
9009
|
lines.push(`- [ ] ${item3.check.label} [Pro] \u2014 upgrade at ${UPGRADE_URL3}`);
|
|
@@ -9377,21 +9128,6 @@ function getStationHtml(nonce, cssUri, jsUri, options) {
|
|
|
9377
9128
|
</div>
|
|
9378
9129
|
</header>
|
|
9379
9130
|
|
|
9380
|
-
<section class="studio-launch-hero" aria-label="VibeRaven readiness">
|
|
9381
|
-
<div class="studio-launch-hero__main">
|
|
9382
|
-
<p class="studio-launch-hero__wordmark">VibeRaven</p>
|
|
9383
|
-
<div class="studio-launch-hero__decision" id="studio-launch-decision">\u25C7 BLOCKED</div>
|
|
9384
|
-
<div class="studio-launch-hero__gauge" aria-hidden="true">
|
|
9385
|
-
<span id="studio-launch-gauge-fill" class="studio-launch-hero__gauge-fill" style="width:25%"></span>
|
|
9386
|
-
</div>
|
|
9387
|
-
</div>
|
|
9388
|
-
<div class="studio-launch-hero__next">
|
|
9389
|
-
<span class="studio-launch-hero__next-label">Next action</span>
|
|
9390
|
-
<strong id="studio-launch-next-action">Run a scan to map production gaps.</strong>
|
|
9391
|
-
<button type="button" class="studio-launch-hero__verify" data-station-action="rescan">Run verify</button>
|
|
9392
|
-
</div>
|
|
9393
|
-
</section>
|
|
9394
|
-
|
|
9395
9131
|
<div class="studio-workspace">
|
|
9396
9132
|
<nav class="studio-nav-rail" aria-label="Studio areas">
|
|
9397
9133
|
<span class="studio-nav-rail__item studio-nav-rail__item--active" aria-label="Architecture">ARCH</span>
|
|
@@ -9623,6 +9359,54 @@ var REPORT_ASSET_FILES = [
|
|
|
9623
9359
|
"assets/provider-logrocket.svg"
|
|
9624
9360
|
];
|
|
9625
9361
|
|
|
9362
|
+
// src/sanitizeArtifact.ts
|
|
9363
|
+
var INLINE_SECRET_PATTERNS = [
|
|
9364
|
+
/\b(sk_(?:live|test)_[A-Za-z0-9]{12,}|sk-proj-[A-Za-z0-9_-]{16,}|sk-[A-Za-z0-9_-]{20,})\b/g,
|
|
9365
|
+
/\b(whsec_[A-Za-z0-9]{12,}|rk_(?:live|test)_[A-Za-z0-9]{12,})\b/g,
|
|
9366
|
+
/\bghp_[A-Za-z0-9]{36,}\b/g,
|
|
9367
|
+
/\bgithub_pat_[A-Za-z0-9_]{50,}\b/g,
|
|
9368
|
+
/\bxox[bp]-[A-Za-z0-9-]{20,}\b/g,
|
|
9369
|
+
/\bxapp-[A-Za-z0-9-]{20,}\b/g,
|
|
9370
|
+
/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,
|
|
9371
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g
|
|
9372
|
+
];
|
|
9373
|
+
var SENSITIVE_ENV_KEYS = /(?:^|_)(?:ACCESS_TOKEN|AUTHORIZATION|API_KEY|SECRET|SECRET_KEY|SERVICE_ROLE_KEY|TOKEN|PASSWORD|PRIVATE_KEY|CREDENTIALS?)(?:$|_)/i;
|
|
9374
|
+
function redactString(value) {
|
|
9375
|
+
let out = value;
|
|
9376
|
+
for (const pattern of INLINE_SECRET_PATTERNS) {
|
|
9377
|
+
out = out.replace(pattern, pattern.source.includes("PRIVATE KEY") ? "[REDACTED_PRIVATE_KEY]" : "[REDACTED_SECRET]");
|
|
9378
|
+
}
|
|
9379
|
+
out = out.replace(/\bAuthorization\s*:\s*([A-Za-z][A-Za-z0-9._-]*)\s+[^\s;,]+/gi, "Authorization: $1 [REDACTED]");
|
|
9380
|
+
return out.replace(
|
|
9381
|
+
/\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,
|
|
9382
|
+
"$1=[REDACTED]"
|
|
9383
|
+
);
|
|
9384
|
+
}
|
|
9385
|
+
function redactUnknown(value) {
|
|
9386
|
+
if (typeof value === "string") {
|
|
9387
|
+
return redactString(value);
|
|
9388
|
+
}
|
|
9389
|
+
if (Array.isArray(value)) {
|
|
9390
|
+
return value.map(redactUnknown);
|
|
9391
|
+
}
|
|
9392
|
+
if (value && typeof value === "object") {
|
|
9393
|
+
const record = value;
|
|
9394
|
+
const next = {};
|
|
9395
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
9396
|
+
if (SENSITIVE_ENV_KEYS.test(key)) {
|
|
9397
|
+
next[key] = "[REDACTED]";
|
|
9398
|
+
} else {
|
|
9399
|
+
next[key] = redactUnknown(entry);
|
|
9400
|
+
}
|
|
9401
|
+
}
|
|
9402
|
+
return next;
|
|
9403
|
+
}
|
|
9404
|
+
return value;
|
|
9405
|
+
}
|
|
9406
|
+
function sanitizeArtifactForDisk(artifact) {
|
|
9407
|
+
return redactUnknown(artifact);
|
|
9408
|
+
}
|
|
9409
|
+
|
|
9626
9410
|
// src/artifacts.ts
|
|
9627
9411
|
async function copyReportAssets(reportAssetsDir) {
|
|
9628
9412
|
const sourceDir = getBundledReportAssetsDir();
|
|
@@ -9638,7 +9422,6 @@ async function writeScanArtifacts(options) {
|
|
|
9638
9422
|
const jsonPath = (0, import_node_path8.join)(dir, "last-scan.json");
|
|
9639
9423
|
const gateResultPath = (0, import_node_path8.join)(dir, "gate-result.json");
|
|
9640
9424
|
const contextMapPath = (0, import_node_path8.join)(dir, "context-map.json");
|
|
9641
|
-
const prpPath = (0, import_node_path8.join)(dir, "prp.json");
|
|
9642
9425
|
const gapsDir = (0, import_node_path8.join)(dir, "gaps");
|
|
9643
9426
|
const tasklistPath = (0, import_node_path8.join)(dir, "agent-tasklist.md");
|
|
9644
9427
|
const summaryPath = (0, import_node_path8.join)(dir, "agent-summary.md");
|
|
@@ -9657,20 +9440,13 @@ async function writeScanArtifacts(options) {
|
|
|
9657
9440
|
const gateResult = `${JSON.stringify(generateGateResult(safe), null, 2)}
|
|
9658
9441
|
`;
|
|
9659
9442
|
const contextMap = `${JSON.stringify(generateContextMap(safe), null, 2)}
|
|
9660
|
-
`;
|
|
9661
|
-
const prp = `${JSON.stringify(
|
|
9662
|
-
generateProductionProtocol(safe, { connectedTools: options.connectedTools, mode: options.prpMode }),
|
|
9663
|
-
null,
|
|
9664
|
-
2
|
|
9665
|
-
)}
|
|
9666
9443
|
`;
|
|
9667
9444
|
const gapEvidenceFiles = generateGapEvidenceFiles(safe);
|
|
9668
9445
|
await copyReportAssets(reportAssetsDir);
|
|
9669
9446
|
await (0, import_promises5.writeFile)(gateResultPath, gateResult, "utf-8");
|
|
9670
9447
|
await (0, import_promises5.writeFile)(contextMapPath, contextMap, "utf-8");
|
|
9671
|
-
|
|
9672
|
-
|
|
9673
|
-
await (0, import_promises5.writeFile)((0, import_node_path8.join)(dir, "gaps", `${gap2.content.id}.json`), `${JSON.stringify(gap2.content, null, 2)}
|
|
9448
|
+
for (const gap of gapEvidenceFiles) {
|
|
9449
|
+
await (0, import_promises5.writeFile)((0, import_node_path8.join)(dir, "gaps", `${gap.content.id}.json`), `${JSON.stringify(gap.content, null, 2)}
|
|
9674
9450
|
`, "utf-8");
|
|
9675
9451
|
}
|
|
9676
9452
|
await (0, import_promises5.writeFile)(tasklistPath, tasklist, "utf-8");
|
|
@@ -9683,7 +9459,6 @@ async function writeScanArtifacts(options) {
|
|
|
9683
9459
|
jsonPath,
|
|
9684
9460
|
gateResultPath,
|
|
9685
9461
|
contextMapPath,
|
|
9686
|
-
prpPath,
|
|
9687
9462
|
gapsDir,
|
|
9688
9463
|
tasklistPath,
|
|
9689
9464
|
summaryPath,
|
|
@@ -9766,12 +9541,12 @@ async function copyToClipboard(text) {
|
|
|
9766
9541
|
}
|
|
9767
9542
|
}
|
|
9768
9543
|
function pipeToCommand(command, text, extraArgs = []) {
|
|
9769
|
-
return new Promise((
|
|
9544
|
+
return new Promise((resolve5, reject) => {
|
|
9770
9545
|
const child = (0, import_node_child_process2.spawn)(command, extraArgs, { stdio: ["pipe", "ignore", "ignore"] });
|
|
9771
9546
|
child.on("error", reject);
|
|
9772
9547
|
child.on("close", (code) => {
|
|
9773
9548
|
if (code === 0) {
|
|
9774
|
-
|
|
9549
|
+
resolve5();
|
|
9775
9550
|
} else {
|
|
9776
9551
|
reject(new Error(`${command} exited with code ${code ?? "unknown"}`));
|
|
9777
9552
|
}
|
|
@@ -9879,10 +9654,10 @@ function printScanSummary(artifact, paths) {
|
|
|
9879
9654
|
const modelGaps = artifact.gaps.filter((g2) => g2.primaryMapCategory === area.key).length;
|
|
9880
9655
|
const tag = modelGaps > 0 ? ` GAP ${modelGaps}` : open > 0 ? ` ${open} fix` : "";
|
|
9881
9656
|
const tagColored = tag ? gapTagColor(modelGaps, open)(tag) : "";
|
|
9882
|
-
const
|
|
9657
|
+
const label = area.label.padEnd(18);
|
|
9883
9658
|
const provider2 = mission.providerLabel.padEnd(14);
|
|
9884
9659
|
const readiness = readinessColor(mission.readinessPercent)(`${mission.readinessPercent}%`);
|
|
9885
|
-
console.log(` ${import_picocolors.default.dim(
|
|
9660
|
+
console.log(` ${import_picocolors.default.dim(label)} ${provider2} ${readiness}${tagColored}`);
|
|
9886
9661
|
}
|
|
9887
9662
|
console.log("");
|
|
9888
9663
|
console.log(import_picocolors.default.bold("Artifacts:"));
|
|
@@ -9904,7 +9679,7 @@ function printScanSummary(artifact, paths) {
|
|
|
9904
9679
|
if (next.upgradeUrl) {
|
|
9905
9680
|
console.log(`Upgrade: ${next.upgradeUrl}`);
|
|
9906
9681
|
}
|
|
9907
|
-
console.log(formatAgentStatus(AGENT_ACTION, "
|
|
9682
|
+
console.log(formatAgentStatus(AGENT_ACTION, "Keep operating: read .viberaven/agent-tasklist.md, apply the next safe repo fix, then continue the VibeRaven loop."));
|
|
9908
9683
|
console.log("");
|
|
9909
9684
|
if (artifact.usage) {
|
|
9910
9685
|
const lanes = artifact.usage.unlockedMapCategoryKeys.length;
|
|
@@ -10009,8 +9784,8 @@ function validateSafeFixJobInput(kind, rawInput) {
|
|
|
10009
9784
|
}
|
|
10010
9785
|
};
|
|
10011
9786
|
}
|
|
10012
|
-
function validateSafeFixRelativePath(
|
|
10013
|
-
const trimmed =
|
|
9787
|
+
function validateSafeFixRelativePath(path) {
|
|
9788
|
+
const trimmed = path.trim();
|
|
10014
9789
|
if (!trimmed) {
|
|
10015
9790
|
return { ok: false, reason: "Safe fix path must not be empty." };
|
|
10016
9791
|
}
|
|
@@ -10052,11 +9827,11 @@ var SECRET_VALUE_PATTERNS = [
|
|
|
10052
9827
|
/\bwhsec_[A-Za-z0-9]{12,}\b/,
|
|
10053
9828
|
/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/
|
|
10054
9829
|
];
|
|
10055
|
-
function isAllowedSafeFixPath(
|
|
10056
|
-
if (
|
|
9830
|
+
function isAllowedSafeFixPath(path) {
|
|
9831
|
+
if (path === ".env.example") {
|
|
10057
9832
|
return true;
|
|
10058
9833
|
}
|
|
10059
|
-
const segments =
|
|
9834
|
+
const segments = path.split("/");
|
|
10060
9835
|
if (segments.length !== 2) {
|
|
10061
9836
|
return false;
|
|
10062
9837
|
}
|
|
@@ -10167,10 +9942,10 @@ function sleepForRunnerPoll(ms, signal) {
|
|
|
10167
9942
|
if (signal?.aborted) {
|
|
10168
9943
|
return Promise.reject(createRunnerWatchAbortError());
|
|
10169
9944
|
}
|
|
10170
|
-
return new Promise((
|
|
9945
|
+
return new Promise((resolve5, reject) => {
|
|
10171
9946
|
const timer = setTimeout(() => {
|
|
10172
9947
|
cleanup();
|
|
10173
|
-
|
|
9948
|
+
resolve5();
|
|
10174
9949
|
}, ms);
|
|
10175
9950
|
const onAbort = () => {
|
|
10176
9951
|
cleanup();
|
|
@@ -10362,8 +10137,8 @@ async function resolveSafeFixTarget(workspaceRoot, relativePath) {
|
|
|
10362
10137
|
}
|
|
10363
10138
|
return { ok: true, path: target };
|
|
10364
10139
|
}
|
|
10365
|
-
async function realpathNearestExisting(
|
|
10366
|
-
let candidate =
|
|
10140
|
+
async function realpathNearestExisting(path, root) {
|
|
10141
|
+
let candidate = path;
|
|
10367
10142
|
while (isInsideRoot(root, candidate)) {
|
|
10368
10143
|
try {
|
|
10369
10144
|
await (0, import_promises6.lstat)(candidate);
|
|
@@ -10494,8 +10269,8 @@ function buildStationScanProof(kind, artifact) {
|
|
|
10494
10269
|
`gaps: ${artifact.gaps.length}`,
|
|
10495
10270
|
`summary: ${artifact.summary || "No summary returned."}`
|
|
10496
10271
|
];
|
|
10497
|
-
for (const
|
|
10498
|
-
evidence.push(`gap: ${
|
|
10272
|
+
for (const gap of artifact.gaps.slice(0, 5)) {
|
|
10273
|
+
evidence.push(`gap: ${gap.title} (${gap.severity})`);
|
|
10499
10274
|
}
|
|
10500
10275
|
for (const area of (artifact.missionGraph.areas ?? []).slice(0, 6)) {
|
|
10501
10276
|
for (const mission of area.providerMissions.slice(0, 1)) {
|
|
@@ -10561,13 +10336,13 @@ function summarizeSafeNoopJob(job) {
|
|
|
10561
10336
|
proofItems: [proofItemForJob(job, "runner_summary", "Runner summary", summary, [])]
|
|
10562
10337
|
};
|
|
10563
10338
|
}
|
|
10564
|
-
function proofItemForJob(job, kind,
|
|
10339
|
+
function proofItemForJob(job, kind, label, summary, evidence, redactionSecrets = []) {
|
|
10565
10340
|
return {
|
|
10566
10341
|
deploySessionId: job.deploySessionId,
|
|
10567
10342
|
runnerSessionId: job.runnerSessionId,
|
|
10568
10343
|
jobId: job.id,
|
|
10569
10344
|
kind,
|
|
10570
|
-
label
|
|
10345
|
+
label,
|
|
10571
10346
|
summary: redactRunnerProofText(summary, redactionSecrets),
|
|
10572
10347
|
evidence: evidence.map((value) => redactRunnerProofText(value, redactionSecrets)),
|
|
10573
10348
|
redacted: true
|
|
@@ -10590,10 +10365,10 @@ function packageScriptArgs(command, scriptName) {
|
|
|
10590
10365
|
}
|
|
10591
10366
|
return ["run", scriptName];
|
|
10592
10367
|
}
|
|
10593
|
-
function summarizeCommandOutput(
|
|
10368
|
+
function summarizeCommandOutput(label, result, redactionSecrets = []) {
|
|
10594
10369
|
const lines = `${result.stdout}
|
|
10595
10370
|
${result.stderr ?? ""}`.split(/\r?\n/).map((line) => redactRunnerProofText(line.trim(), redactionSecrets)).filter(Boolean).slice(0, 8);
|
|
10596
|
-
return [`${
|
|
10371
|
+
return [`${label} ${result.ok ? "succeeded" : "failed"}`, ...lines];
|
|
10597
10372
|
}
|
|
10598
10373
|
function redactRunnerProofText(value, additionalSecrets = []) {
|
|
10599
10374
|
let out = redactAdditionalSecrets(value, additionalSecrets);
|
|
@@ -10742,9 +10517,9 @@ function formatRepoMatch(repoMatch) {
|
|
|
10742
10517
|
}
|
|
10743
10518
|
}
|
|
10744
10519
|
async function runCommand(command, args, cwd) {
|
|
10745
|
-
return new Promise((
|
|
10520
|
+
return new Promise((resolve5) => {
|
|
10746
10521
|
(0, import_node_child_process3.execFile)(command, args, { cwd, windowsHide: true }, (error, stdout, stderr) => {
|
|
10747
|
-
|
|
10522
|
+
resolve5({
|
|
10748
10523
|
ok: !error,
|
|
10749
10524
|
stdout: String(stdout ?? ""),
|
|
10750
10525
|
stderr: String(stderr ?? "")
|
|
@@ -10869,7 +10644,7 @@ function isRecord6(value) {
|
|
|
10869
10644
|
// src/tui/runInteractive.ts
|
|
10870
10645
|
var import_node_path14 = require("node:path");
|
|
10871
10646
|
|
|
10872
|
-
//
|
|
10647
|
+
// ../../node_modules/@clack/core/dist/index.mjs
|
|
10873
10648
|
var import_sisteransi = __toESM(require_src(), 1);
|
|
10874
10649
|
var import_node_process = require("node:process");
|
|
10875
10650
|
var g = __toESM(require("node:readline"), 1);
|
|
@@ -11227,7 +11002,7 @@ var LD = class extends x {
|
|
|
11227
11002
|
}
|
|
11228
11003
|
};
|
|
11229
11004
|
|
|
11230
|
-
//
|
|
11005
|
+
// ../../node_modules/@clack/prompts/dist/index.mjs
|
|
11231
11006
|
var import_node_process2 = __toESM(require("node:process"), 1);
|
|
11232
11007
|
var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
11233
11008
|
var import_sisteransi2 = __toESM(require_src(), 1);
|
|
@@ -11406,12 +11181,12 @@ var import_picocolors4 = __toESM(require_picocolors());
|
|
|
11406
11181
|
function compact(value) {
|
|
11407
11182
|
return String(value ?? "").replace(/\s+/g, " ").trim();
|
|
11408
11183
|
}
|
|
11409
|
-
function originalHint(
|
|
11410
|
-
const hint = compact(
|
|
11184
|
+
function originalHint(gap) {
|
|
11185
|
+
const hint = compact(gap.copyPrompt);
|
|
11411
11186
|
return hint ? hint : "No scanner hint was returned for this gap.";
|
|
11412
11187
|
}
|
|
11413
|
-
function buildAgentFixPrompt(artifact,
|
|
11414
|
-
const affected = [
|
|
11188
|
+
function buildAgentFixPrompt(artifact, gap) {
|
|
11189
|
+
const affected = [gap.primaryMapCategory, ...gap.affectedMapCategories ?? []].filter(Boolean).filter((value, index, all) => all.indexOf(value) === index).join(", ");
|
|
11415
11190
|
return [
|
|
11416
11191
|
"You are an AI coding agent using VibeRaven as the production-readiness map.",
|
|
11417
11192
|
"",
|
|
@@ -11419,11 +11194,11 @@ function buildAgentFixPrompt(artifact, gap2) {
|
|
|
11419
11194
|
"",
|
|
11420
11195
|
`Project: ${artifact.workspacePath}`,
|
|
11421
11196
|
`Current VibeRaven state: production core ${artifact.productionCorePercent}% | score ${artifact.score} (${artifact.scoreLabel})`,
|
|
11422
|
-
`Gap: ${
|
|
11423
|
-
`Gap id: ${
|
|
11424
|
-
`Severity: ${
|
|
11425
|
-
`Production area: ${
|
|
11426
|
-
`Scanner detail: ${compact(
|
|
11197
|
+
`Gap: ${gap.title}`,
|
|
11198
|
+
`Gap id: ${gap.id}`,
|
|
11199
|
+
`Severity: ${gap.severity}`,
|
|
11200
|
+
`Production area: ${gap.primaryMapCategory}${affected ? ` | affected: ${affected}` : ""}`,
|
|
11201
|
+
`Scanner detail: ${compact(gap.detail)}`,
|
|
11427
11202
|
"",
|
|
11428
11203
|
"Required workflow:",
|
|
11429
11204
|
"1. Read `.viberaven/agent-summary.md` and `.viberaven/launch-playbook.md` before changing code.",
|
|
@@ -11449,12 +11224,12 @@ function buildAgentFixPrompt(artifact, gap2) {
|
|
|
11449
11224
|
"- Report what changed, what verification passed, and which VibeRaven gap remains next.",
|
|
11450
11225
|
"",
|
|
11451
11226
|
"Original scanner hint:",
|
|
11452
|
-
originalHint(
|
|
11227
|
+
originalHint(gap)
|
|
11453
11228
|
].join("\n");
|
|
11454
11229
|
}
|
|
11455
11230
|
|
|
11456
11231
|
// src/version.ts
|
|
11457
|
-
var VERSION = "1.1.
|
|
11232
|
+
var VERSION = "1.1.5";
|
|
11458
11233
|
|
|
11459
11234
|
// src/commands/guide.ts
|
|
11460
11235
|
var import_picocolors3 = __toESM(require_picocolors());
|
|
@@ -11740,6 +11515,15 @@ Maps repo evidence to OWASP LLM Top 10 launch risks. Supports SAFECode-style dep
|
|
|
11740
11515
|
var STACK_FRAMING = `Stack context for this repo: Next.js, Supabase, Vercel, VibeRaven.
|
|
11741
11516
|
Do NOT deploy to Vercel or ship to production users without running \`${PUBLIC_AGENT_MODE_COMMAND}\`.
|
|
11742
11517
|
Do NOT rely on manual production checklists when VibeRaven gate artifacts exist in \`.viberaven/\`.`;
|
|
11518
|
+
var PRODUCTION_PROTOCOL = `## VibeRaven Production Protocol
|
|
11519
|
+
|
|
11520
|
+
Before making deploy, auth, database, payment, API, or scaling changes:
|
|
11521
|
+
|
|
11522
|
+
1. Read \`.viberaven/prp.json\`.
|
|
11523
|
+
2. Follow \`.viberaven/mission-map.md\`.
|
|
11524
|
+
3. Do not deploy if \`decision.status\` is \`blocked\`.
|
|
11525
|
+
4. Fix \`nextActions\` in order.
|
|
11526
|
+
5. Re-run \`${PUBLIC_AGENT_MODE_COMMAND}\`.`;
|
|
11743
11527
|
var AGENT_NEGATIVE_CONSTRAINTS = `## Event-Triggered Production Verification
|
|
11744
11528
|
|
|
11745
11529
|
Trigger: modifications to \`package.json\`, \`vercel.json\`, \`supabase/migrations/**/*\`, \`.env.example\`, auth/billing/webhook paths, or deploy workflow files.
|
|
@@ -11765,6 +11549,8 @@ var AGENT_RULES_BODY = `${AGENT_RULES_PREAMBLE}
|
|
|
11765
11549
|
|
|
11766
11550
|
${STACK_FRAMING}
|
|
11767
11551
|
|
|
11552
|
+
${PRODUCTION_PROTOCOL}
|
|
11553
|
+
|
|
11768
11554
|
${AGENT_NEGATIVE_CONSTRAINTS}
|
|
11769
11555
|
|
|
11770
11556
|
## VibeRaven Production-Readiness Gate
|
|
@@ -11814,10 +11600,11 @@ var AGENT_CONTEXT_BODY = `${AGENT_RULES_PREAMBLE}
|
|
|
11814
11600
|
|
|
11815
11601
|
After \`--agent-mode\`, read these artifacts in order:
|
|
11816
11602
|
|
|
11817
|
-
1. \`.viberaven/
|
|
11818
|
-
2. \`.viberaven/
|
|
11819
|
-
3. \`.viberaven/
|
|
11820
|
-
4. \`.viberaven/
|
|
11603
|
+
1. \`.viberaven/prp.json\`
|
|
11604
|
+
2. \`.viberaven/mission-map.md\`
|
|
11605
|
+
3. \`.viberaven/agent-tasklist.md\`
|
|
11606
|
+
4. \`.viberaven/gate-result.json\`
|
|
11607
|
+
5. \`.viberaven/context-map.json\``;
|
|
11821
11608
|
var MISSION_MAP_BODY = `${AGENT_RULES_PREAMBLE}
|
|
11822
11609
|
|
|
11823
11610
|
## Mission Map loop
|
|
@@ -12038,17 +11825,17 @@ function renderCursorCoreRulePreview() {
|
|
|
12038
11825
|
async function initCursorRulesPack(options) {
|
|
12039
11826
|
const results = [];
|
|
12040
11827
|
const pack = buildCursorRulesPack();
|
|
12041
|
-
for (const
|
|
12042
|
-
const file = `${CURSOR_RULES_DIR}/${
|
|
12043
|
-
const
|
|
12044
|
-
const existing = await readExistingFile(
|
|
12045
|
-
const changed = !existing.exists || existing.content !==
|
|
11828
|
+
for (const rule of pack) {
|
|
11829
|
+
const file = `${CURSOR_RULES_DIR}/${rule.filename}`;
|
|
11830
|
+
const path = (0, import_node_path11.join)(options.cwd, file);
|
|
11831
|
+
const existing = await readExistingFile(path);
|
|
11832
|
+
const changed = !existing.exists || existing.content !== rule.content;
|
|
12046
11833
|
const action = !existing.exists ? "created" : changed ? "updated" : "unchanged";
|
|
12047
11834
|
if (!options.dryRun && changed) {
|
|
12048
11835
|
await (0, import_promises8.mkdir)((0, import_node_path11.join)(options.cwd, CURSOR_RULES_DIR), { recursive: true });
|
|
12049
|
-
await (0, import_promises8.writeFile)(
|
|
11836
|
+
await (0, import_promises8.writeFile)(path, rule.content, "utf-8");
|
|
12050
11837
|
}
|
|
12051
|
-
results.push({ target: "cursor", file, path
|
|
11838
|
+
results.push({ target: "cursor", file, path, action });
|
|
12052
11839
|
}
|
|
12053
11840
|
const legacyPath = (0, import_node_path11.join)(options.cwd, LEGACY_CURSOR_RULE_FILE);
|
|
12054
11841
|
if (!options.dryRun && await fileExists(legacyPath)) {
|
|
@@ -12056,9 +11843,9 @@ async function initCursorRulesPack(options) {
|
|
|
12056
11843
|
}
|
|
12057
11844
|
return results;
|
|
12058
11845
|
}
|
|
12059
|
-
async function readExistingFile(
|
|
11846
|
+
async function readExistingFile(path) {
|
|
12060
11847
|
try {
|
|
12061
|
-
return { exists: true, content: await (0, import_promises8.readFile)(
|
|
11848
|
+
return { exists: true, content: await (0, import_promises8.readFile)(path, "utf-8") };
|
|
12062
11849
|
} catch (error) {
|
|
12063
11850
|
if (isFileNotFoundError(error)) {
|
|
12064
11851
|
return { exists: false, content: "" };
|
|
@@ -12066,9 +11853,9 @@ async function readExistingFile(path3) {
|
|
|
12066
11853
|
throw error;
|
|
12067
11854
|
}
|
|
12068
11855
|
}
|
|
12069
|
-
async function fileExists(
|
|
11856
|
+
async function fileExists(path) {
|
|
12070
11857
|
try {
|
|
12071
|
-
await (0, import_promises8.access)(
|
|
11858
|
+
await (0, import_promises8.access)(path);
|
|
12072
11859
|
return true;
|
|
12073
11860
|
} catch {
|
|
12074
11861
|
return false;
|
|
@@ -12219,15 +12006,15 @@ async function initAgentRules(options) {
|
|
|
12219
12006
|
continue;
|
|
12220
12007
|
}
|
|
12221
12008
|
const file = AGENT_RULE_TARGETS[target].file;
|
|
12222
|
-
const
|
|
12223
|
-
const existing = await readExistingFile2(
|
|
12009
|
+
const path = (0, import_node_path13.join)(options.cwd, file);
|
|
12010
|
+
const existing = await readExistingFile2(path);
|
|
12224
12011
|
const injected = injectAgentRulesBlock(existing.content, renderAgentRulesForTarget(target));
|
|
12225
12012
|
const action = !existing.exists ? "created" : injected.changed ? "updated" : "unchanged";
|
|
12226
12013
|
if (!options.dryRun && injected.changed) {
|
|
12227
|
-
await (0, import_promises10.mkdir)((0, import_node_path13.dirname)(
|
|
12228
|
-
await (0, import_promises10.writeFile)(
|
|
12014
|
+
await (0, import_promises10.mkdir)((0, import_node_path13.dirname)(path), { recursive: true });
|
|
12015
|
+
await (0, import_promises10.writeFile)(path, injected.content, "utf-8");
|
|
12229
12016
|
}
|
|
12230
|
-
results.push({ target, file, path
|
|
12017
|
+
results.push({ target, file, path, action });
|
|
12231
12018
|
}
|
|
12232
12019
|
const packageJsonScripts = await seedPackageJsonScripts({
|
|
12233
12020
|
cwd: options.cwd,
|
|
@@ -12238,7 +12025,7 @@ async function initAgentRules(options) {
|
|
|
12238
12025
|
function renderAgentRulesDryRun(targets) {
|
|
12239
12026
|
const files = targets.flatMap((target) => {
|
|
12240
12027
|
if (target === "cursor") {
|
|
12241
|
-
return buildCursorRulesPack().map((
|
|
12028
|
+
return buildCursorRulesPack().map((rule) => `- cursor: .cursor/rules/${rule.filename}`);
|
|
12242
12029
|
}
|
|
12243
12030
|
return [`- ${target}: ${AGENT_RULE_TARGETS[target].file}`];
|
|
12244
12031
|
}).join("\n");
|
|
@@ -12291,9 +12078,9 @@ function formatAgentRulesInitSummary(output) {
|
|
|
12291
12078
|
);
|
|
12292
12079
|
return lines.join("\n");
|
|
12293
12080
|
}
|
|
12294
|
-
async function readExistingFile2(
|
|
12081
|
+
async function readExistingFile2(path) {
|
|
12295
12082
|
try {
|
|
12296
|
-
return { exists: true, content: await (0, import_promises10.readFile)(
|
|
12083
|
+
return { exists: true, content: await (0, import_promises10.readFile)(path, "utf-8") };
|
|
12297
12084
|
} catch (error) {
|
|
12298
12085
|
if (isFileNotFoundError2(error)) {
|
|
12299
12086
|
return { exists: false, content: "" };
|
|
@@ -12461,15 +12248,15 @@ async function handleViewGaps(cwd) {
|
|
|
12461
12248
|
async function handlePrompt(cwd) {
|
|
12462
12249
|
try {
|
|
12463
12250
|
const artifact = await loadLastArtifact(cwd);
|
|
12464
|
-
const
|
|
12465
|
-
if (!
|
|
12251
|
+
const gap = pickGap(artifact);
|
|
12252
|
+
if (!gap) {
|
|
12466
12253
|
M2.warn("No gaps to fix. Run a scan or pick a different project.");
|
|
12467
12254
|
return;
|
|
12468
12255
|
}
|
|
12469
|
-
const prompt = buildAgentFixPrompt(artifact,
|
|
12256
|
+
const prompt = buildAgentFixPrompt(artifact, gap);
|
|
12470
12257
|
try {
|
|
12471
12258
|
await copyToClipboard(prompt);
|
|
12472
|
-
M2.success(import_picocolors4.default.green(`Copied top prompt to clipboard \u2014 ${
|
|
12259
|
+
M2.success(import_picocolors4.default.green(`Copied top prompt to clipboard \u2014 ${gap.title}`));
|
|
12473
12260
|
} catch (error) {
|
|
12474
12261
|
M2.warn(error instanceof Error ? error.message : String(error));
|
|
12475
12262
|
console.log("");
|
|
@@ -13880,8 +13667,8 @@ async function checkAgentInjection(cwd) {
|
|
|
13880
13667
|
}
|
|
13881
13668
|
for (const target of CORE_AGENT_INJECTION_TARGETS) {
|
|
13882
13669
|
const file = target === "cursor" ? ".cursor/rules/viberaven-core.mdc" : AGENT_RULE_TARGETS[target].file;
|
|
13883
|
-
const
|
|
13884
|
-
const exists = await fileExists2(
|
|
13670
|
+
const path = (0, import_node_path23.join)(cwd, file);
|
|
13671
|
+
const exists = await fileExists2(path);
|
|
13885
13672
|
if (!exists) {
|
|
13886
13673
|
checks.push({
|
|
13887
13674
|
id: `canonical-${target}`,
|
|
@@ -13890,7 +13677,7 @@ async function checkAgentInjection(cwd) {
|
|
|
13890
13677
|
});
|
|
13891
13678
|
continue;
|
|
13892
13679
|
}
|
|
13893
|
-
const content = await (0, import_promises18.readFile)(
|
|
13680
|
+
const content = await (0, import_promises18.readFile)(path, "utf-8");
|
|
13894
13681
|
const hasCommand = content.includes(PUBLIC_AGENT_MODE_COMMAND);
|
|
13895
13682
|
checks.push({
|
|
13896
13683
|
id: `canonical-${target}`,
|
|
@@ -13922,9 +13709,9 @@ function formatDoctorAgentsReport(report) {
|
|
|
13922
13709
|
lines.push(report.ok ? "All agent injection checks passed." : "Agent injection checks failed.");
|
|
13923
13710
|
return lines.join("\n");
|
|
13924
13711
|
}
|
|
13925
|
-
async function fileExists2(
|
|
13712
|
+
async function fileExists2(path) {
|
|
13926
13713
|
try {
|
|
13927
|
-
await (0, import_promises18.access)(
|
|
13714
|
+
await (0, import_promises18.access)(path);
|
|
13928
13715
|
return true;
|
|
13929
13716
|
} catch {
|
|
13930
13717
|
return false;
|
|
@@ -14114,241 +13901,27 @@ async function runValidateNpmPackageCommand(options) {
|
|
|
14114
13901
|
for (const result of results) {
|
|
14115
13902
|
console.log(`${result.name}: ${result.verdict}`);
|
|
14116
13903
|
for (const reason of result.reasons) {
|
|
14117
|
-
console.log(` - ${reason}`);
|
|
14118
|
-
}
|
|
14119
|
-
console.log(` followUp: ${result.followUpCommand}`);
|
|
14120
|
-
}
|
|
14121
|
-
}
|
|
14122
|
-
const hasBlocking = results.some((result) => result.verdict !== "ok");
|
|
14123
|
-
return hasBlocking ? 2 : 0;
|
|
14124
|
-
}
|
|
14125
|
-
|
|
14126
|
-
// src/commands/runAudit.ts
|
|
14127
|
-
async function runAuditCommand(input) {
|
|
14128
|
-
const auditInput = await collectVercelSupabaseAuditInput(input.cwd);
|
|
14129
|
-
const result = buildVercelSupabaseAudit(auditInput);
|
|
14130
|
-
if (input.json) {
|
|
14131
|
-
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
14132
|
-
`);
|
|
14133
|
-
} else {
|
|
14134
|
-
process.stdout.write(`${renderVercelSupabaseAudit(result)}
|
|
14135
|
-
`);
|
|
14136
|
-
}
|
|
14137
|
-
return result.status === "pass" ? 0 : 1;
|
|
14138
|
-
}
|
|
14139
|
-
|
|
14140
|
-
// src/commands/badge.ts
|
|
14141
|
-
var import_promises19 = require("node:fs/promises");
|
|
14142
|
-
var import_node_path24 = require("node:path");
|
|
14143
|
-
|
|
14144
|
-
// src/brand/palette.ts
|
|
14145
|
-
var import_picocolors5 = __toESM(require_picocolors());
|
|
14146
|
-
var STATUS_GLYPH = {
|
|
14147
|
-
clear: "\u2713",
|
|
14148
|
-
warning: "\u25C8",
|
|
14149
|
-
blocked: "\u25C6",
|
|
14150
|
-
fixable: "\u25B8",
|
|
14151
|
-
manual: "\u25C7",
|
|
14152
|
-
done: "\u2713",
|
|
14153
|
-
pending: "\xB7"
|
|
14154
|
-
};
|
|
14155
|
-
var DECISION_GLYPH = {
|
|
14156
|
-
CLEAR: STATUS_GLYPH.clear,
|
|
14157
|
-
WARNING: STATUS_GLYPH.warning,
|
|
14158
|
-
BLOCKED: STATUS_GLYPH.blocked
|
|
14159
|
-
};
|
|
14160
|
-
function identity(text) {
|
|
14161
|
-
return text;
|
|
14162
|
-
}
|
|
14163
|
-
function colorsFor(mode) {
|
|
14164
|
-
return import_picocolors5.default.createColors(mode === "rich");
|
|
14165
|
-
}
|
|
14166
|
-
function decisionColor(decision, mode) {
|
|
14167
|
-
const colors = colorsFor(mode);
|
|
14168
|
-
if (decision === "CLEAR") return colors.green;
|
|
14169
|
-
if (decision === "WARNING") return colors.yellow;
|
|
14170
|
-
return colors.red;
|
|
14171
|
-
}
|
|
14172
|
-
function decisionStyle(decision, mode) {
|
|
14173
|
-
const colorize = mode === "rich" ? decisionColor(decision, mode) : identity;
|
|
14174
|
-
return {
|
|
14175
|
-
glyph: DECISION_GLYPH[decision],
|
|
14176
|
-
label: colorize(decision),
|
|
14177
|
-
colorize
|
|
14178
|
-
};
|
|
14179
|
-
}
|
|
14180
|
-
function richOrPlain(text, mode, colorize) {
|
|
14181
|
-
return mode === "rich" ? colorize(text) : text;
|
|
14182
|
-
}
|
|
14183
|
-
var accent = {
|
|
14184
|
-
brand(text, mode) {
|
|
14185
|
-
return richOrPlain(text, mode, colorsFor(mode).cyan);
|
|
14186
|
-
},
|
|
14187
|
-
dim(text, mode) {
|
|
14188
|
-
return richOrPlain(text, mode, colorsFor(mode).dim);
|
|
14189
|
-
},
|
|
14190
|
-
bold(text, mode) {
|
|
14191
|
-
return richOrPlain(text, mode, colorsFor(mode).bold);
|
|
14192
|
-
}
|
|
14193
|
-
};
|
|
14194
|
-
|
|
14195
|
-
// src/brand/ui.ts
|
|
14196
|
-
var import_picocolors6 = __toESM(require_picocolors());
|
|
14197
|
-
var ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
|
|
14198
|
-
function colorsFor2(mode) {
|
|
14199
|
-
return import_picocolors6.default.createColors(mode === "rich");
|
|
14200
|
-
}
|
|
14201
|
-
function stripAnsi(value) {
|
|
14202
|
-
return value.replace(ANSI_PATTERN, "");
|
|
14203
|
-
}
|
|
14204
|
-
function visibleWidth(value) {
|
|
14205
|
-
return stripAnsi(value).length;
|
|
14206
|
-
}
|
|
14207
|
-
function padVisible(value, width) {
|
|
14208
|
-
return `${value}${" ".repeat(Math.max(0, width - visibleWidth(value)))}`;
|
|
14209
|
-
}
|
|
14210
|
-
function safeWidth(width) {
|
|
14211
|
-
if (!Number.isFinite(width)) return 1;
|
|
14212
|
-
return Math.max(1, Math.floor(width));
|
|
14213
|
-
}
|
|
14214
|
-
function clampPercent(percent) {
|
|
14215
|
-
if (Number.isNaN(percent)) return 0;
|
|
14216
|
-
return Math.max(0, Math.min(100, percent));
|
|
14217
|
-
}
|
|
14218
|
-
function colorizeGaugeFill(text, percent, mode) {
|
|
14219
|
-
const colors = colorsFor2(mode);
|
|
14220
|
-
if (mode !== "rich") return text;
|
|
14221
|
-
if (percent >= 80) return colors.green(text);
|
|
14222
|
-
if (percent >= 50) return colors.yellow(text);
|
|
14223
|
-
return colors.red(text);
|
|
14224
|
-
}
|
|
14225
|
-
function rule(width, options) {
|
|
14226
|
-
const line = "\u2500".repeat(safeWidth(width));
|
|
14227
|
-
return accent.dim(line, options.mode);
|
|
14228
|
-
}
|
|
14229
|
-
function box(lines, options) {
|
|
14230
|
-
const content = lines.length > 0 ? lines : [""];
|
|
14231
|
-
const innerWidth = Math.max(1, ...content.map(visibleWidth));
|
|
14232
|
-
const horizontal = "\u2500".repeat(innerWidth + 2);
|
|
14233
|
-
const colors = colorsFor2(options.mode);
|
|
14234
|
-
const border = (value) => options.mode === "rich" ? colors.cyan(value) : value;
|
|
14235
|
-
const body = content.map((line) => border("\u2502") + ` ${padVisible(line, innerWidth)} ` + border("\u2502"));
|
|
14236
|
-
return [border(`\u250C${horizontal}\u2510`), ...body, border(`\u2514${horizontal}\u2518`)].join("\n");
|
|
14237
|
-
}
|
|
14238
|
-
function gauge(percent, options) {
|
|
14239
|
-
const width = safeWidth(options.width);
|
|
14240
|
-
const clamped = clampPercent(percent);
|
|
14241
|
-
const label2 = `${Math.round(clamped)}%`;
|
|
14242
|
-
const filledWidth = Math.round(clamped / 100 * width);
|
|
14243
|
-
const emptyWidth = width - filledWidth;
|
|
14244
|
-
const filled = colorizeGaugeFill("\u2588".repeat(filledWidth), clamped, options.mode);
|
|
14245
|
-
const empty = accent.dim("\u2591".repeat(emptyWidth), options.mode);
|
|
14246
|
-
return `[${filled}${empty}] ${label2}`;
|
|
14247
|
-
}
|
|
14248
|
-
function banner(decision, options) {
|
|
14249
|
-
const style = decisionStyle(decision, options.mode);
|
|
14250
|
-
return `${style.glyph} ${style.label}`;
|
|
14251
|
-
}
|
|
14252
|
-
function kv(key, value, options) {
|
|
14253
|
-
return `${accent.dim(key, options.mode)}: ${value}`;
|
|
14254
|
-
}
|
|
14255
|
-
|
|
14256
|
-
// src/brand/wordmark.ts
|
|
14257
|
-
var DEFAULT_TAGLINE = "production operator for AI-built apps";
|
|
14258
|
-
function wordmark(options) {
|
|
14259
|
-
const tagline = options.tagline ?? DEFAULT_TAGLINE;
|
|
14260
|
-
const brand = accent.brand("VibeRaven", options.mode);
|
|
14261
|
-
const mutedTagline = accent.dim(tagline, options.mode);
|
|
14262
|
-
if (options.variant === "compact") {
|
|
14263
|
-
return `${brand} - ${mutedTagline}`;
|
|
14264
|
-
}
|
|
14265
|
-
return [accent.bold(brand, options.mode), mutedTagline].join("\n");
|
|
14266
|
-
}
|
|
14267
|
-
|
|
14268
|
-
// src/badge/renderBadge.ts
|
|
14269
|
-
var COLORS = {
|
|
14270
|
-
CLEAR: "#2ea44f",
|
|
14271
|
-
WARNING: "#dbab09",
|
|
14272
|
-
BLOCKED: "#d1242f"
|
|
14273
|
-
};
|
|
14274
|
-
function label(prp) {
|
|
14275
|
-
return prp.decision.toLowerCase();
|
|
14276
|
-
}
|
|
14277
|
-
function renderBadgeSvg(prp) {
|
|
14278
|
-
const text = label(prp);
|
|
14279
|
-
const color = COLORS[prp.decision];
|
|
14280
|
-
const width = 132 + text.length * 7;
|
|
14281
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="20" role="img" aria-label="VibeRaven: ${text}">
|
|
14282
|
-
<rect width="${width}" height="20" fill="#24292f"/>
|
|
14283
|
-
<rect x="86" width="${width - 86}" height="20" fill="${color}"/>
|
|
14284
|
-
<g fill="#fff" font-family="Verdana,Geneva,sans-serif" font-size="11">
|
|
14285
|
-
<text x="8" y="14">VibeRaven</text>
|
|
14286
|
-
<text x="94" y="14">${text}</text>
|
|
14287
|
-
</g>
|
|
14288
|
-
</svg>`;
|
|
14289
|
-
}
|
|
14290
|
-
function renderBadgeMarkdown(prp) {
|
|
14291
|
-
const text = label(prp);
|
|
14292
|
-
const date = prp.generatedAt.slice(0, 10);
|
|
14293
|
-
return `[](https://viberaven.dev/production-protocol.md?utm_source=badge) \`gate: ${text} - ${date}\``;
|
|
14294
|
-
}
|
|
14295
|
-
function renderBadgeCard(prp, mode) {
|
|
14296
|
-
const text = label(prp);
|
|
14297
|
-
const date = prp.generatedAt.slice(0, 10);
|
|
14298
|
-
const style = decisionStyle(prp.decision, mode);
|
|
14299
|
-
return box(
|
|
14300
|
-
[
|
|
14301
|
-
wordmark({ variant: "compact", mode, tagline: "launch gate proof" }),
|
|
14302
|
-
`${accent.dim("gate", mode)}: ${style.colorize(text)}`,
|
|
14303
|
-
`${accent.dim("date", mode)}: ${date}`,
|
|
14304
|
-
accent.brand("viberaven.dev", mode)
|
|
14305
|
-
],
|
|
14306
|
-
{ mode }
|
|
14307
|
-
);
|
|
14308
|
-
}
|
|
14309
|
-
|
|
14310
|
-
// src/brand/renderMode.ts
|
|
14311
|
-
function resolveRenderMode(input) {
|
|
14312
|
-
if (input.env.VIBERAVEN_FORCE_RICH === "1") return "rich";
|
|
14313
|
-
if (input.env.VIBERAVEN_PLAIN === "1") return "plain";
|
|
14314
|
-
if (input.env.NO_COLOR !== void 0 && input.env.NO_COLOR !== "") return "plain";
|
|
14315
|
-
if (input.surface === "agent-mode") return "plain";
|
|
14316
|
-
return input.isTTY ? "rich" : "plain";
|
|
14317
|
-
}
|
|
14318
|
-
function currentRenderMode(surface) {
|
|
14319
|
-
return resolveRenderMode({
|
|
14320
|
-
surface,
|
|
14321
|
-
isTTY: Boolean(process.stdout.isTTY),
|
|
14322
|
-
env: process.env
|
|
14323
|
-
});
|
|
13904
|
+
console.log(` - ${reason}`);
|
|
13905
|
+
}
|
|
13906
|
+
console.log(` followUp: ${result.followUpCommand}`);
|
|
13907
|
+
}
|
|
13908
|
+
}
|
|
13909
|
+
const hasBlocking = results.some((result) => result.verdict !== "ok");
|
|
13910
|
+
return hasBlocking ? 2 : 0;
|
|
14324
13911
|
}
|
|
14325
13912
|
|
|
14326
|
-
// src/commands/
|
|
14327
|
-
async function
|
|
14328
|
-
const
|
|
14329
|
-
const
|
|
14330
|
-
|
|
14331
|
-
|
|
14332
|
-
|
|
14333
|
-
|
|
14334
|
-
|
|
14335
|
-
|
|
14336
|
-
return 1;
|
|
14337
|
-
}
|
|
14338
|
-
if (options.svg) {
|
|
14339
|
-
const outputPath = (0, import_node_path24.join)(artifactDir, "badge.svg");
|
|
14340
|
-
await (0, import_promises19.mkdir)(artifactDir, { recursive: true });
|
|
14341
|
-
await (0, import_promises19.writeFile)(outputPath, renderBadgeSvg(prp), "utf8");
|
|
14342
|
-
log(`Wrote ${outputPath}`);
|
|
14343
|
-
return 0;
|
|
14344
|
-
}
|
|
14345
|
-
if (options.card) {
|
|
14346
|
-
log(renderBadgeCard(prp, currentRenderMode("human-command")));
|
|
14347
|
-
return 0;
|
|
13913
|
+
// src/commands/runAudit.ts
|
|
13914
|
+
async function runAuditCommand(input) {
|
|
13915
|
+
const auditInput = await collectVercelSupabaseAuditInput(input.cwd);
|
|
13916
|
+
const result = buildVercelSupabaseAudit(auditInput);
|
|
13917
|
+
if (input.json) {
|
|
13918
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
13919
|
+
`);
|
|
13920
|
+
} else {
|
|
13921
|
+
process.stdout.write(`${renderVercelSupabaseAudit(result)}
|
|
13922
|
+
`);
|
|
14348
13923
|
}
|
|
14349
|
-
|
|
14350
|
-
log(renderBadgeMarkdown(prp));
|
|
14351
|
-
return 0;
|
|
13924
|
+
return result.status === "pass" ? 0 : 1;
|
|
14352
13925
|
}
|
|
14353
13926
|
|
|
14354
13927
|
// src/output/nextActionBlock.ts
|
|
@@ -14365,7 +13938,11 @@ function buildNextActionBlock(tasks, loopState, plan) {
|
|
|
14365
13938
|
return {
|
|
14366
13939
|
...base,
|
|
14367
13940
|
type: "stalled",
|
|
14368
|
-
title: "Loop stalled
|
|
13941
|
+
title: "Loop stalled - no gap reduction after 2+ consecutive scans",
|
|
13942
|
+
agentInstruction: "Stop the scan loop. Explain the blocker plainly, then either guide the provider/user action or report that no safe repo-code recipe is available.",
|
|
13943
|
+
executionPlan: [
|
|
13944
|
+
{ kind: "stop", note: "Do not keep spending scans while the loop is stalled." }
|
|
13945
|
+
],
|
|
14369
13946
|
requiresUserAction: true,
|
|
14370
13947
|
stallReason
|
|
14371
13948
|
};
|
|
@@ -14374,42 +13951,67 @@ function buildNextActionBlock(tasks, loopState, plan) {
|
|
|
14374
13951
|
return {
|
|
14375
13952
|
...base,
|
|
14376
13953
|
type: "verify",
|
|
14377
|
-
title: "Batch complete
|
|
13954
|
+
title: "Batch complete - run verify to rescan before next batch",
|
|
13955
|
+
agentInstruction: "Run verify now, read the refreshed VibeRaven artifacts, then continue with the next safe repo fix or provider step. Do not stop at a summary.",
|
|
13956
|
+
executionPlan: [
|
|
13957
|
+
{ kind: "verify", command: "viberaven_verify" },
|
|
13958
|
+
{ kind: "continue-loop", note: "After verify, continue until the gate is clear or a provider/user blocker remains." }
|
|
13959
|
+
],
|
|
14378
13960
|
mcpTool: "viberaven_verify",
|
|
14379
13961
|
mcpArgs: {},
|
|
14380
13962
|
requiresUserAction: false
|
|
14381
13963
|
};
|
|
14382
13964
|
}
|
|
14383
|
-
const repoCodeTask = tasks.find((
|
|
13965
|
+
const repoCodeTask = tasks.find((task) => task.fixType === "repo-code" && !task.requiresUserAction);
|
|
14384
13966
|
if (repoCodeTask) {
|
|
13967
|
+
const fallbackCommand = repoCodeTask.mcpArgs ? `npx -y viberaven --heal --apply --gap ${repoCodeTask.gapId} --yes` : void 0;
|
|
14385
13968
|
return {
|
|
14386
13969
|
...base,
|
|
14387
13970
|
type: "repo-code",
|
|
14388
13971
|
gapId: repoCodeTask.gapId,
|
|
14389
13972
|
title: repoCodeTask.title,
|
|
13973
|
+
agentInstruction: "Apply this repo-code fix now using the MCP tool or fallback command, then continue the local VibeRaven loop. Do not stop at a summary.",
|
|
13974
|
+
executionPlan: [
|
|
13975
|
+
{
|
|
13976
|
+
kind: "apply-repo-fix",
|
|
13977
|
+
command: repoCodeTask.mcpTool ?? fallbackCommand,
|
|
13978
|
+
note: "Patch only this gap and preserve unrelated user changes."
|
|
13979
|
+
},
|
|
13980
|
+
{ kind: "continue-loop", note: "Apply remaining safe repo fixes until the batch is full, then verify once." }
|
|
13981
|
+
],
|
|
14390
13982
|
mcpTool: repoCodeTask.mcpTool,
|
|
14391
13983
|
mcpArgs: repoCodeTask.mcpArgs,
|
|
14392
|
-
fallbackCommand
|
|
13984
|
+
fallbackCommand,
|
|
14393
13985
|
requiresUserAction: false
|
|
14394
13986
|
};
|
|
14395
13987
|
}
|
|
14396
|
-
const providerTask = tasks.find((
|
|
13988
|
+
const providerTask = tasks.find((task) => task.fixType === "provider-action");
|
|
14397
13989
|
if (providerTask) {
|
|
14398
13990
|
return {
|
|
14399
13991
|
...base,
|
|
14400
13992
|
type: "provider-action",
|
|
14401
13993
|
gapId: providerTask.gapId,
|
|
14402
13994
|
title: providerTask.title,
|
|
13995
|
+
agentInstruction: "Open or present the provider step now. If browser/tool access is available, open the dashboard link for the user; otherwise show the exact step and copy values. Do not keep telling the user to verify until the provider step is done.",
|
|
13996
|
+
executionPlan: [
|
|
13997
|
+
{ kind: "open-provider-step", note: "Use VIBERAVEN_PROVIDER_ACTION for the exact dashboard step and URL." },
|
|
13998
|
+
{ kind: "wait-for-user", note: "Wait for confirmation or read-only MCP evidence before verifying." }
|
|
13999
|
+
],
|
|
14403
14000
|
requiresUserAction: true
|
|
14404
14001
|
};
|
|
14405
14002
|
}
|
|
14406
|
-
const upgradeTask = tasks.find((
|
|
14003
|
+
const upgradeTask = tasks.find((task) => task.fixType === "upgrade-required");
|
|
14407
14004
|
if (upgradeTask) {
|
|
14408
14005
|
return {
|
|
14409
14006
|
...base,
|
|
14410
14007
|
type: "upgrade-required",
|
|
14411
14008
|
gapId: upgradeTask.gapId,
|
|
14412
14009
|
title: upgradeTask.title,
|
|
14010
|
+
agentInstruction: "Explain that this production lane needs an upgrade or connected provider evidence. Continue with any remaining unlocked repo-code fixes if present; otherwise stop cleanly.",
|
|
14011
|
+
executionPlan: [
|
|
14012
|
+
{ kind: "upgrade", command: "https://viberaven.dev/pricing" },
|
|
14013
|
+
{ kind: "stop", note: "Do not pretend locked lanes or provider state were fixed locally." }
|
|
14014
|
+
],
|
|
14413
14015
|
requiresUserAction: true,
|
|
14414
14016
|
upgradeUrl: "https://viberaven.dev/pricing"
|
|
14415
14017
|
};
|
|
@@ -14417,15 +14019,20 @@ function buildNextActionBlock(tasks, loopState, plan) {
|
|
|
14417
14019
|
return {
|
|
14418
14020
|
...base,
|
|
14419
14021
|
type: "done",
|
|
14420
|
-
title: "All gaps resolved
|
|
14022
|
+
title: "All gaps resolved - production gate is clear",
|
|
14023
|
+
agentInstruction: "The repo-code gate is clear. Run strict before deploy and still verify provider dashboard state through MCP or the provider dashboard.",
|
|
14024
|
+
executionPlan: [
|
|
14025
|
+
{ kind: "verify", command: "npx -y viberaven --strict" },
|
|
14026
|
+
{ kind: "stop", note: "Do not deploy until provider dashboard state is verified where applicable." }
|
|
14027
|
+
],
|
|
14421
14028
|
requiresUserAction: false
|
|
14422
14029
|
};
|
|
14423
14030
|
}
|
|
14424
14031
|
function resolveStallReason(tasks) {
|
|
14425
14032
|
if (tasks.length === 0) return "no-recipes";
|
|
14426
|
-
const allUpgradeOrEmpty = tasks.every((
|
|
14033
|
+
const allUpgradeOrEmpty = tasks.every((task) => task.fixType === "upgrade-required");
|
|
14427
14034
|
if (allUpgradeOrEmpty) return "no-recipes";
|
|
14428
|
-
const allProviderAction = tasks.every((
|
|
14035
|
+
const allProviderAction = tasks.every((task) => task.fixType === "provider-action");
|
|
14429
14036
|
if (allProviderAction) return "provider-action-required";
|
|
14430
14037
|
return "unknown";
|
|
14431
14038
|
}
|
|
@@ -14445,6 +14052,7 @@ function buildProviderActionBlock(task) {
|
|
|
14445
14052
|
dashboardUrl: pa.dashboardUrl,
|
|
14446
14053
|
exactStep: pa.exactStep,
|
|
14447
14054
|
envKeyName: pa.envKeyName ?? null,
|
|
14055
|
+
envKeyExample: pa.envKeyExample ?? null,
|
|
14448
14056
|
doneSignal: pa.doneSignal,
|
|
14449
14057
|
verifyCommand: task.verifyCommand,
|
|
14450
14058
|
mcpAlternative: task.mcpTool ?? pa.mcpAlternative ?? null
|
|
@@ -14453,7 +14061,7 @@ function buildProviderActionBlock(task) {
|
|
|
14453
14061
|
}
|
|
14454
14062
|
function printProviderActionBlock(tasks) {
|
|
14455
14063
|
const task = tasks.find(
|
|
14456
|
-
(
|
|
14064
|
+
(entry) => entry.fixType === "provider-action" && entry.requiresUserAction === true
|
|
14457
14065
|
);
|
|
14458
14066
|
if (!task) return;
|
|
14459
14067
|
const block = buildProviderActionBlock(task);
|
|
@@ -14468,251 +14076,10 @@ function printNextActionBlock(block) {
|
|
|
14468
14076
|
console.log(NEXT_ACTION_END);
|
|
14469
14077
|
}
|
|
14470
14078
|
|
|
14471
|
-
// src/output/actionPanelBlock.ts
|
|
14472
|
-
var ACTION_PANEL_START = "VIBERAVEN_ACTION_PANEL_START";
|
|
14473
|
-
var ACTION_PANEL_END = "VIBERAVEN_ACTION_PANEL_END";
|
|
14474
|
-
function buildActionPanelBlock(reportPath) {
|
|
14475
|
-
return {
|
|
14476
|
-
VIBERAVEN_ACTION_PANEL: {
|
|
14477
|
-
surface: "local-report",
|
|
14478
|
-
title: "Visual action panel",
|
|
14479
|
-
reportPath,
|
|
14480
|
-
openCommand: "npx -y viberaven report --open",
|
|
14481
|
-
nextCommand: "npx -y viberaven next --json",
|
|
14482
|
-
agentInstruction: "Use the native chat for reasoning. Open this local report only when the user needs buttons, provider links, copy actions, or visual progress."
|
|
14483
|
-
}
|
|
14484
|
-
};
|
|
14485
|
-
}
|
|
14486
|
-
function printActionPanelBlock(reportPath) {
|
|
14487
|
-
const block = buildActionPanelBlock(reportPath);
|
|
14488
|
-
console.log(ACTION_PANEL_START);
|
|
14489
|
-
console.log(JSON.stringify(block, null, 2));
|
|
14490
|
-
console.log(ACTION_PANEL_END);
|
|
14491
|
-
}
|
|
14492
|
-
|
|
14493
|
-
// src/output/operatorBlock.ts
|
|
14494
|
-
var OPERATOR_START = "VIBERAVEN_OPERATOR_START";
|
|
14495
|
-
var OPERATOR_END = "VIBERAVEN_OPERATOR_END";
|
|
14496
|
-
function stackLine(prp) {
|
|
14497
|
-
const parts = Array.from(
|
|
14498
|
-
/* @__PURE__ */ new Set([
|
|
14499
|
-
...prp.detectedStack.deployment,
|
|
14500
|
-
...prp.detectedStack.database,
|
|
14501
|
-
...prp.detectedStack.auth,
|
|
14502
|
-
...prp.detectedStack.payments,
|
|
14503
|
-
...prp.detectedStack.monitoring
|
|
14504
|
-
])
|
|
14505
|
-
);
|
|
14506
|
-
if (parts.length > 0) {
|
|
14507
|
-
return parts.join(" + ");
|
|
14508
|
-
}
|
|
14509
|
-
return prp.detectedStack.archetype ?? "unknown";
|
|
14510
|
-
}
|
|
14511
|
-
function uniqueEnvNames(tasks) {
|
|
14512
|
-
return Array.from(
|
|
14513
|
-
new Set(
|
|
14514
|
-
tasks.map((task) => task.providerAction?.envKeyName).filter((name) => Boolean(name))
|
|
14515
|
-
)
|
|
14516
|
-
);
|
|
14517
|
-
}
|
|
14518
|
-
function humanTaskText(task) {
|
|
14519
|
-
if (task.fixType === "provider-action") {
|
|
14520
|
-
return task.providerAction?.exactStep ?? task.title;
|
|
14521
|
-
}
|
|
14522
|
-
if (task.fixType === "upgrade-required") {
|
|
14523
|
-
return `Upgrade required: ${task.action ?? task.exactFix ?? task.title}`;
|
|
14524
|
-
}
|
|
14525
|
-
if (task.fixType === "manual-verify") {
|
|
14526
|
-
return `Manual verification required: ${task.action ?? task.exactFix ?? task.title}`;
|
|
14527
|
-
}
|
|
14528
|
-
return task.title;
|
|
14529
|
-
}
|
|
14530
|
-
function riskSuffixFor(task, prp) {
|
|
14531
|
-
const topRisk = prp.findings.find((finding) => finding.id === task.gapId)?.riskMapping?.owaspLlm?.[0];
|
|
14532
|
-
return topRisk ? ` (risk: ${topRisk})` : "";
|
|
14533
|
-
}
|
|
14534
|
-
function readinessPercent(prp) {
|
|
14535
|
-
if (prp.decision === "CLEAR") return 100;
|
|
14536
|
-
if (prp.decision === "WARNING") return 65;
|
|
14537
|
-
return 25;
|
|
14538
|
-
}
|
|
14539
|
-
function buildPlainOperatorBlock(prp, tasks) {
|
|
14540
|
-
const repoTasks = tasks.filter((task) => task.fixType === "repo-code");
|
|
14541
|
-
const humanTasks = tasks.filter((task) => task.fixType !== "repo-code");
|
|
14542
|
-
const providerTasks = tasks.filter((task) => task.fixType === "provider-action");
|
|
14543
|
-
const nextRepoTask = repoTasks[0];
|
|
14544
|
-
const envNames = uniqueEnvNames(providerTasks);
|
|
14545
|
-
const lines = [];
|
|
14546
|
-
lines.push(`Decision: ${prp.decision}`);
|
|
14547
|
-
lines.push(`Detected stack: ${stackLine(prp)}`);
|
|
14548
|
-
lines.push("");
|
|
14549
|
-
lines.push("What I can fix:");
|
|
14550
|
-
if (repoTasks.length === 0) {
|
|
14551
|
-
lines.push(" (none - no automated repo-code recipes apply)");
|
|
14552
|
-
} else {
|
|
14553
|
-
repoTasks.forEach((task, index) => {
|
|
14554
|
-
lines.push(` ${index + 1}. ${task.title}${riskSuffixFor(task, prp)}`);
|
|
14555
|
-
});
|
|
14556
|
-
}
|
|
14557
|
-
lines.push("");
|
|
14558
|
-
lines.push("What you must do:");
|
|
14559
|
-
if (humanTasks.length === 0) {
|
|
14560
|
-
lines.push(" (none - no human-owned steps required)");
|
|
14561
|
-
} else {
|
|
14562
|
-
humanTasks.forEach((task, index) => {
|
|
14563
|
-
lines.push(` ${index + 1}. ${humanTaskText(task)}${riskSuffixFor(task, prp)}`);
|
|
14564
|
-
});
|
|
14565
|
-
}
|
|
14566
|
-
lines.push("");
|
|
14567
|
-
lines.push("Next repo-code fix:");
|
|
14568
|
-
lines.push(
|
|
14569
|
-
nextRepoTask ? ` ${nextRepoTask.title}${riskSuffixFor(nextRepoTask, prp)} -> npx -y viberaven --heal --apply --gap ${nextRepoTask.gapId} --yes` : " (none)"
|
|
14570
|
-
);
|
|
14571
|
-
lines.push("");
|
|
14572
|
-
lines.push("Provider actions:");
|
|
14573
|
-
if (providerTasks.length === 0) {
|
|
14574
|
-
lines.push(" (none)");
|
|
14575
|
-
} else {
|
|
14576
|
-
providerTasks.forEach((task) => {
|
|
14577
|
-
const providerAction = task.providerAction;
|
|
14578
|
-
if (!providerAction) {
|
|
14579
|
-
lines.push(` - ${task.title}${riskSuffixFor(task, prp)}`);
|
|
14580
|
-
return;
|
|
14581
|
-
}
|
|
14582
|
-
lines.push(` - ${providerAction.provider}: ${providerAction.dashboardUrl}`);
|
|
14583
|
-
lines.push(` Step: ${providerAction.exactStep}${riskSuffixFor(task, prp)}`);
|
|
14584
|
-
lines.push(` Done when: ${providerAction.doneSignal}`);
|
|
14585
|
-
});
|
|
14586
|
-
}
|
|
14587
|
-
lines.push("");
|
|
14588
|
-
lines.push("Copy these env var names:");
|
|
14589
|
-
lines.push(envNames.length > 0 ? ` ${envNames.join(", ")}` : " (none)");
|
|
14590
|
-
lines.push("");
|
|
14591
|
-
lines.push(`Verify command: ${prp.verifyCommand}`);
|
|
14592
|
-
lines.push("");
|
|
14593
|
-
lines.push(`Do not deploy until: decision is CLEAR (current: ${prp.decision}). Run the verify command after fixes.`);
|
|
14594
|
-
if (prp.decision === "CLEAR") {
|
|
14595
|
-
lines.push("Share your launch proof: npx -y viberaven badge");
|
|
14596
|
-
}
|
|
14597
|
-
return lines.join("\n");
|
|
14598
|
-
}
|
|
14599
|
-
function buildRichOperatorBlock(prp, tasks) {
|
|
14600
|
-
const repoTasks = tasks.filter((task) => task.fixType === "repo-code");
|
|
14601
|
-
const humanTasks = tasks.filter((task) => task.fixType !== "repo-code");
|
|
14602
|
-
const providerTasks = tasks.filter((task) => task.fixType === "provider-action");
|
|
14603
|
-
const nextRepoTask = repoTasks[0];
|
|
14604
|
-
const envNames = uniqueEnvNames(providerTasks);
|
|
14605
|
-
const mode = "rich";
|
|
14606
|
-
const lines = [];
|
|
14607
|
-
lines.push(wordmark({ variant: "compact", mode }));
|
|
14608
|
-
lines.push(rule(64, { mode }));
|
|
14609
|
-
lines.push(
|
|
14610
|
-
`${accent.bold("Decision", mode)}: ${banner(prp.decision, { mode })} ${gauge(readinessPercent(prp), {
|
|
14611
|
-
width: 18,
|
|
14612
|
-
mode
|
|
14613
|
-
})}`
|
|
14614
|
-
);
|
|
14615
|
-
lines.push(kv("Detected stack", stackLine(prp), { mode }));
|
|
14616
|
-
lines.push("");
|
|
14617
|
-
lines.push(`${accent.bold("What I can fix", mode)}:`);
|
|
14618
|
-
if (repoTasks.length === 0) {
|
|
14619
|
-
lines.push(` ${accent.dim("(none - no automated repo-code recipes apply)", mode)}`);
|
|
14620
|
-
} else {
|
|
14621
|
-
repoTasks.forEach((task, index) => {
|
|
14622
|
-
lines.push(` ${index + 1}. ${task.title}${riskSuffixFor(task, prp)}`);
|
|
14623
|
-
});
|
|
14624
|
-
}
|
|
14625
|
-
lines.push("");
|
|
14626
|
-
lines.push(`${accent.bold("What you must do", mode)}:`);
|
|
14627
|
-
if (humanTasks.length === 0) {
|
|
14628
|
-
lines.push(` ${accent.dim("(none - no human-owned steps required)", mode)}`);
|
|
14629
|
-
} else {
|
|
14630
|
-
humanTasks.forEach((task, index) => {
|
|
14631
|
-
lines.push(` ${index + 1}. ${humanTaskText(task)}${riskSuffixFor(task, prp)}`);
|
|
14632
|
-
});
|
|
14633
|
-
}
|
|
14634
|
-
lines.push("");
|
|
14635
|
-
lines.push(`${accent.bold("Next repo-code fix", mode)}:`);
|
|
14636
|
-
lines.push(
|
|
14637
|
-
nextRepoTask ? ` ${nextRepoTask.title}${riskSuffixFor(nextRepoTask, prp)} -> npx -y viberaven --heal --apply --gap ${nextRepoTask.gapId} --yes` : ` ${accent.dim("(none)", mode)}`
|
|
14638
|
-
);
|
|
14639
|
-
lines.push("");
|
|
14640
|
-
lines.push(`${accent.bold("Provider actions", mode)}:`);
|
|
14641
|
-
if (providerTasks.length === 0) {
|
|
14642
|
-
lines.push(` ${accent.dim("(none)", mode)}`);
|
|
14643
|
-
} else {
|
|
14644
|
-
providerTasks.forEach((task) => {
|
|
14645
|
-
const providerAction = task.providerAction;
|
|
14646
|
-
if (!providerAction) {
|
|
14647
|
-
lines.push(` - ${task.title}${riskSuffixFor(task, prp)}`);
|
|
14648
|
-
return;
|
|
14649
|
-
}
|
|
14650
|
-
lines.push(` - ${accent.brand(providerAction.provider, mode)}: ${providerAction.dashboardUrl}`);
|
|
14651
|
-
lines.push(` ${kv("Step", `${providerAction.exactStep}${riskSuffixFor(task, prp)}`, { mode })}`);
|
|
14652
|
-
lines.push(` ${kv("Done when", providerAction.doneSignal, { mode })}`);
|
|
14653
|
-
});
|
|
14654
|
-
}
|
|
14655
|
-
lines.push("");
|
|
14656
|
-
lines.push(`${accent.bold("Copy these env var names", mode)}:`);
|
|
14657
|
-
lines.push(envNames.length > 0 ? ` ${envNames.join(", ")}` : ` ${accent.dim("(none)", mode)}`);
|
|
14658
|
-
lines.push("");
|
|
14659
|
-
lines.push(kv("Verify command", prp.verifyCommand, { mode }));
|
|
14660
|
-
lines.push("");
|
|
14661
|
-
lines.push(
|
|
14662
|
-
`${accent.bold("Do not deploy until", mode)}: decision is CLEAR (current: ${banner(
|
|
14663
|
-
prp.decision,
|
|
14664
|
-
{ mode }
|
|
14665
|
-
)}). Run the verify command after fixes.`
|
|
14666
|
-
);
|
|
14667
|
-
if (prp.decision === "CLEAR") {
|
|
14668
|
-
lines.push(kv("Share your launch proof", "npx -y viberaven badge", { mode }));
|
|
14669
|
-
}
|
|
14670
|
-
return lines.join("\n");
|
|
14671
|
-
}
|
|
14672
|
-
function buildOperatorBlock(prp, tasks, mode = "plain") {
|
|
14673
|
-
return mode === "rich" ? buildRichOperatorBlock(prp, tasks) : buildPlainOperatorBlock(prp, tasks);
|
|
14674
|
-
}
|
|
14675
|
-
function printOperatorBlock(prp, tasks, mode = "plain") {
|
|
14676
|
-
if (mode === "rich") {
|
|
14677
|
-
console.log(buildOperatorBlock(prp, tasks, "rich"));
|
|
14678
|
-
}
|
|
14679
|
-
console.log(OPERATOR_START);
|
|
14680
|
-
console.log(buildOperatorBlock(prp, tasks, "plain"));
|
|
14681
|
-
console.log(OPERATOR_END);
|
|
14682
|
-
}
|
|
14683
|
-
|
|
14684
|
-
// src/output/celebration.ts
|
|
14685
|
-
function renderClearCelebration(prp, mode) {
|
|
14686
|
-
if (prp.decision !== "CLEAR") return "";
|
|
14687
|
-
const verifiedDate = prp.generatedAt.slice(0, 10);
|
|
14688
|
-
const style = decisionStyle("CLEAR", mode);
|
|
14689
|
-
const lines = [
|
|
14690
|
-
`${style.glyph} ${style.label} production gate is clear`,
|
|
14691
|
-
accent.dim(`Verified ${verifiedDate}`, mode),
|
|
14692
|
-
"",
|
|
14693
|
-
`Share your launch proof: ${accent.bold("npx -y viberaven badge", mode)}`
|
|
14694
|
-
];
|
|
14695
|
-
return [wordmark({ variant: "compact", mode }), box(lines, { mode }), rule(40, { mode })].join("\n");
|
|
14696
|
-
}
|
|
14697
|
-
|
|
14698
|
-
// src/output/loopProgress.ts
|
|
14699
|
-
function safeCount(value) {
|
|
14700
|
-
if (!Number.isFinite(value)) return 0;
|
|
14701
|
-
return Math.max(0, Math.floor(value));
|
|
14702
|
-
}
|
|
14703
|
-
function renderLoopProgress(input, mode) {
|
|
14704
|
-
if (mode === "plain" && input.silentInPlain) return "";
|
|
14705
|
-
const total = safeCount(input.total);
|
|
14706
|
-
const applied = total > 0 ? Math.min(safeCount(input.applied), total) : safeCount(input.applied);
|
|
14707
|
-
const percent = total === 0 ? 100 : applied / total * 100;
|
|
14708
|
-
const label2 = `${STATUS_GLYPH.fixable} ${input.label}`;
|
|
14709
|
-
return `${accent.bold(label2, mode)} ${gauge(percent, { width: 12, mode })} ${applied}/${total}`;
|
|
14710
|
-
}
|
|
14711
|
-
|
|
14712
14079
|
// src/providerMcpBridge.ts
|
|
14713
14080
|
var import_node_fs12 = require("node:fs");
|
|
14714
14081
|
var import_node_os2 = require("node:os");
|
|
14715
|
-
var
|
|
14082
|
+
var import_node_path24 = require("node:path");
|
|
14716
14083
|
var UPGRADE_URL4 = "https://viberaven.dev/pricing";
|
|
14717
14084
|
var FALLBACK_COMMAND = "npx -y viberaven audit --vercel-supabase --json";
|
|
14718
14085
|
var SUPPORTED_PROVIDERS = /* @__PURE__ */ new Set(["supabase", "vercel"]);
|
|
@@ -14720,9 +14087,9 @@ var configPathsOverride;
|
|
|
14720
14087
|
function defaultMcpConfigPaths() {
|
|
14721
14088
|
const home = (0, import_node_os2.homedir)();
|
|
14722
14089
|
return [
|
|
14723
|
-
(0,
|
|
14724
|
-
(0,
|
|
14725
|
-
(0,
|
|
14090
|
+
(0, import_node_path24.join)(home, ".config", "claude", "claude_desktop_config.json"),
|
|
14091
|
+
(0, import_node_path24.join)(home, ".cursor", "mcp.json"),
|
|
14092
|
+
(0, import_node_path24.join)(home, ".gemini", "antigravity", "mcp_config.json")
|
|
14726
14093
|
];
|
|
14727
14094
|
}
|
|
14728
14095
|
function resolveConfigPaths() {
|
|
@@ -14750,12 +14117,12 @@ function findServerEntry(servers, provider2) {
|
|
|
14750
14117
|
}
|
|
14751
14118
|
function findProviderMcpConfig(provider2, configPaths) {
|
|
14752
14119
|
const paths = configPaths ?? resolveConfigPaths();
|
|
14753
|
-
for (const
|
|
14754
|
-
if (!(0, import_node_fs12.existsSync)(
|
|
14120
|
+
for (const path of paths) {
|
|
14121
|
+
if (!(0, import_node_fs12.existsSync)(path)) {
|
|
14755
14122
|
continue;
|
|
14756
14123
|
}
|
|
14757
14124
|
try {
|
|
14758
|
-
const raw = JSON.parse((0, import_node_fs12.readFileSync)(
|
|
14125
|
+
const raw = JSON.parse((0, import_node_fs12.readFileSync)(path, "utf8"));
|
|
14759
14126
|
const servers = parseMcpServers(raw);
|
|
14760
14127
|
if (!servers) {
|
|
14761
14128
|
continue;
|
|
@@ -14769,7 +14136,7 @@ function findProviderMcpConfig(provider2, configPaths) {
|
|
|
14769
14136
|
command: typeof server.command === "string" ? server.command : void 0,
|
|
14770
14137
|
args: Array.isArray(server.args) ? server.args.filter((arg) => typeof arg === "string") : void 0,
|
|
14771
14138
|
url: typeof server.url === "string" ? server.url : void 0,
|
|
14772
|
-
source:
|
|
14139
|
+
source: path
|
|
14773
14140
|
};
|
|
14774
14141
|
} catch {
|
|
14775
14142
|
continue;
|
|
@@ -14777,19 +14144,6 @@ function findProviderMcpConfig(provider2, configPaths) {
|
|
|
14777
14144
|
}
|
|
14778
14145
|
return void 0;
|
|
14779
14146
|
}
|
|
14780
|
-
function hasUsableProviderMcpConfig(config) {
|
|
14781
|
-
return Boolean(config.command?.trim() || config.url?.trim());
|
|
14782
|
-
}
|
|
14783
|
-
function findUsableProviderMcpConfig(provider2, configPaths) {
|
|
14784
|
-
const paths = configPaths ?? resolveConfigPaths();
|
|
14785
|
-
for (const path3 of paths) {
|
|
14786
|
-
const config = findProviderMcpConfig(provider2, [path3]);
|
|
14787
|
-
if (config && hasUsableProviderMcpConfig(config)) {
|
|
14788
|
-
return config;
|
|
14789
|
-
}
|
|
14790
|
-
}
|
|
14791
|
-
return void 0;
|
|
14792
|
-
}
|
|
14793
14147
|
async function verifyProviderGap(options) {
|
|
14794
14148
|
if (options.plan !== "pro") {
|
|
14795
14149
|
return {
|
|
@@ -14820,338 +14174,6 @@ async function verifyProviderGap(options) {
|
|
|
14820
14174
|
};
|
|
14821
14175
|
}
|
|
14822
14176
|
|
|
14823
|
-
// src/connectedTools.ts
|
|
14824
|
-
var DETECTED_PROVIDERS = ["supabase", "vercel", "stripe", "github"];
|
|
14825
|
-
var INSTALL_HINTS = {
|
|
14826
|
-
supabase: "claude mcp add --transport http supabase https://mcp.supabase.com/",
|
|
14827
|
-
vercel: "claude mcp add --transport http vercel https://mcp.vercel.com/",
|
|
14828
|
-
stripe: "claude mcp add --transport http stripe https://mcp.stripe.com/",
|
|
14829
|
-
github: "claude mcp add --transport http github https://api.githubcopilot.com/mcp/"
|
|
14830
|
-
};
|
|
14831
|
-
var READONLY_PROVIDERS = /* @__PURE__ */ new Set(["vercel"]);
|
|
14832
|
-
function isDetectedProvider(provider2) {
|
|
14833
|
-
return DETECTED_PROVIDERS.includes(provider2);
|
|
14834
|
-
}
|
|
14835
|
-
function installHintFor(provider2) {
|
|
14836
|
-
const normalizedProvider = provider2.toLowerCase().trim();
|
|
14837
|
-
if (isDetectedProvider(normalizedProvider)) {
|
|
14838
|
-
return INSTALL_HINTS[normalizedProvider];
|
|
14839
|
-
}
|
|
14840
|
-
return `Add the ${provider2} MCP server to your agent config.`;
|
|
14841
|
-
}
|
|
14842
|
-
function detectConnectedTools() {
|
|
14843
|
-
const tools = {};
|
|
14844
|
-
for (const provider2 of DETECTED_PROVIDERS) {
|
|
14845
|
-
const config = findUsableProviderMcpConfig(provider2);
|
|
14846
|
-
if (!config) {
|
|
14847
|
-
tools[`${provider2}Mcp`] = "missing";
|
|
14848
|
-
continue;
|
|
14849
|
-
}
|
|
14850
|
-
tools[`${provider2}Mcp`] = READONLY_PROVIDERS.has(provider2) ? "available_readonly" : "available";
|
|
14851
|
-
}
|
|
14852
|
-
tools.browser = "available";
|
|
14853
|
-
return tools;
|
|
14854
|
-
}
|
|
14855
|
-
|
|
14856
|
-
// src/demo/runDemo.ts
|
|
14857
|
-
var import_node_fs13 = require("node:fs");
|
|
14858
|
-
var import_node_path27 = __toESM(require("node:path"));
|
|
14859
|
-
|
|
14860
|
-
// src/demo/localRules.ts
|
|
14861
|
-
var import_promises20 = require("node:fs/promises");
|
|
14862
|
-
var import_node_path26 = __toESM(require("node:path"));
|
|
14863
|
-
var DEMO_SCANNED_AT = "2026-06-14T00:00:00.000Z";
|
|
14864
|
-
async function readKnownFile(root, relativePath) {
|
|
14865
|
-
try {
|
|
14866
|
-
return await (0, import_promises20.readFile)(import_node_path26.default.join(root, relativePath), "utf8");
|
|
14867
|
-
} catch (error) {
|
|
14868
|
-
const code = error.code;
|
|
14869
|
-
if (code === "ENOENT") return "";
|
|
14870
|
-
throw error;
|
|
14871
|
-
}
|
|
14872
|
-
}
|
|
14873
|
-
function dependencyNames(packageJson) {
|
|
14874
|
-
if (!packageJson.trim()) return /* @__PURE__ */ new Set();
|
|
14875
|
-
const parsed = JSON.parse(packageJson);
|
|
14876
|
-
return /* @__PURE__ */ new Set([
|
|
14877
|
-
...Object.keys(parsed.dependencies ?? {}),
|
|
14878
|
-
...Object.keys(parsed.devDependencies ?? {})
|
|
14879
|
-
]);
|
|
14880
|
-
}
|
|
14881
|
-
function stripTypeScriptComments(source) {
|
|
14882
|
-
return source.replace(/\/\*[\s\S]*?\*\//g, "").split("\n").map((line) => {
|
|
14883
|
-
let quote = null;
|
|
14884
|
-
let escaped = false;
|
|
14885
|
-
for (let index = 0; index < line.length - 1; index += 1) {
|
|
14886
|
-
const char = line[index];
|
|
14887
|
-
const next = line[index + 1];
|
|
14888
|
-
if (quote) {
|
|
14889
|
-
if (escaped) {
|
|
14890
|
-
escaped = false;
|
|
14891
|
-
} else if (char === "\\") {
|
|
14892
|
-
escaped = true;
|
|
14893
|
-
} else if (char === quote) {
|
|
14894
|
-
quote = null;
|
|
14895
|
-
}
|
|
14896
|
-
continue;
|
|
14897
|
-
}
|
|
14898
|
-
if (char === '"' || char === "'" || char === "`") {
|
|
14899
|
-
quote = char;
|
|
14900
|
-
continue;
|
|
14901
|
-
}
|
|
14902
|
-
if (char === "/" && next === "/") {
|
|
14903
|
-
return line.slice(0, index);
|
|
14904
|
-
}
|
|
14905
|
-
}
|
|
14906
|
-
return line;
|
|
14907
|
-
}).join("\n");
|
|
14908
|
-
}
|
|
14909
|
-
function stripSqlComments(source) {
|
|
14910
|
-
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/--.*$/gm, "");
|
|
14911
|
-
}
|
|
14912
|
-
function hasStripeWebhookSignatureVerification(source) {
|
|
14913
|
-
const executable = stripTypeScriptComments(source);
|
|
14914
|
-
return /webhooks\s*\.\s*constructEvent\s*\(/.test(executable) && /Stripe-Signature/.test(executable);
|
|
14915
|
-
}
|
|
14916
|
-
function hasEnabledRowLevelSecurity(source) {
|
|
14917
|
-
return /enable\s+row\s+level\s+security/i.test(stripSqlComments(source));
|
|
14918
|
-
}
|
|
14919
|
-
function hasEnvAssignment(source, name) {
|
|
14920
|
-
const pattern = new RegExp(`^\\s*${name}\\s*=`, "m");
|
|
14921
|
-
return pattern.test(source);
|
|
14922
|
-
}
|
|
14923
|
-
function gap(input) {
|
|
14924
|
-
return {
|
|
14925
|
-
...input,
|
|
14926
|
-
toolSuggestions: [],
|
|
14927
|
-
mcpSuggestion: null,
|
|
14928
|
-
affectedMapCategories: input.affectedMapCategories ?? []
|
|
14929
|
-
};
|
|
14930
|
-
}
|
|
14931
|
-
function emptyMissionGraph() {
|
|
14932
|
-
return {
|
|
14933
|
-
areas: [],
|
|
14934
|
-
byArea: {},
|
|
14935
|
-
byProvider: {},
|
|
14936
|
-
repositoryEvidence: {
|
|
14937
|
-
env: [],
|
|
14938
|
-
security: []
|
|
14939
|
-
}
|
|
14940
|
-
};
|
|
14941
|
-
}
|
|
14942
|
-
async function scanDemoFixture(root) {
|
|
14943
|
-
const [packageJson, stripeWebhook, migration, envExample] = await Promise.all([
|
|
14944
|
-
readKnownFile(root, "package.json"),
|
|
14945
|
-
readKnownFile(root, "app/api/stripe/webhook/route.ts"),
|
|
14946
|
-
readKnownFile(root, "supabase/migrations/0001_init.sql"),
|
|
14947
|
-
readKnownFile(root, ".env.example")
|
|
14948
|
-
]);
|
|
14949
|
-
const dependencies = dependencyNames(packageJson);
|
|
14950
|
-
const hasNext = dependencies.has("next");
|
|
14951
|
-
const hasSupabase = dependencies.has("@supabase/supabase-js");
|
|
14952
|
-
const hasStripe = dependencies.has("stripe");
|
|
14953
|
-
const selectedProviders = {};
|
|
14954
|
-
if (hasNext) selectedProviders.deployment = "vercel";
|
|
14955
|
-
if (hasSupabase) {
|
|
14956
|
-
selectedProviders.database = "supabase";
|
|
14957
|
-
selectedProviders.auth = "supabase";
|
|
14958
|
-
}
|
|
14959
|
-
if (hasStripe) selectedProviders.payments = "stripe";
|
|
14960
|
-
const gaps = [];
|
|
14961
|
-
if (hasStripe && !hasStripeWebhookSignatureVerification(stripeWebhook)) {
|
|
14962
|
-
gaps.push(
|
|
14963
|
-
gap({
|
|
14964
|
-
id: "stripe_webhook_signature_missing",
|
|
14965
|
-
category: "SECURITY & AUTH",
|
|
14966
|
-
severity: "critical",
|
|
14967
|
-
title: "Verify Stripe webhook signatures",
|
|
14968
|
-
detail: "The demo Stripe webhook handler does not verify the Stripe-Signature header with stripe.webhooks.constructEvent.",
|
|
14969
|
-
copyPrompt: "Update the Stripe webhook route to read the raw request body, read the Stripe-Signature header, and verify events with stripe.webhooks.constructEvent using STRIPE_WEBHOOK_SECRET.",
|
|
14970
|
-
primaryMapCategory: "payments",
|
|
14971
|
-
affectedMapCategories: ["security"]
|
|
14972
|
-
})
|
|
14973
|
-
);
|
|
14974
|
-
}
|
|
14975
|
-
if (hasSupabase && !hasEnabledRowLevelSecurity(migration)) {
|
|
14976
|
-
gaps.push(
|
|
14977
|
-
gap({
|
|
14978
|
-
id: "rls_disabled",
|
|
14979
|
-
category: "DATABASE & DATA",
|
|
14980
|
-
severity: "critical",
|
|
14981
|
-
title: "Enable Supabase row level security",
|
|
14982
|
-
detail: "The demo migration creates application data without enabling row level security.",
|
|
14983
|
-
copyPrompt: "Add alter table statements that enable row level security and add least-privilege policies for the Supabase tables in the demo migration.",
|
|
14984
|
-
primaryMapCategory: "database",
|
|
14985
|
-
affectedMapCategories: ["auth", "security"]
|
|
14986
|
-
})
|
|
14987
|
-
);
|
|
14988
|
-
}
|
|
14989
|
-
if (hasStripe && !hasEnvAssignment(envExample, "STRIPE_WEBHOOK_SECRET")) {
|
|
14990
|
-
gaps.push(
|
|
14991
|
-
gap({
|
|
14992
|
-
id: "missing_prod_env_stripe_webhook_secret",
|
|
14993
|
-
category: "DEPLOYMENT",
|
|
14994
|
-
severity: "warning",
|
|
14995
|
-
title: "Document STRIPE_WEBHOOK_SECRET",
|
|
14996
|
-
detail: "The demo .env.example does not include a STRIPE_WEBHOOK_SECRET assignment.",
|
|
14997
|
-
copyPrompt: "Add STRIPE_WEBHOOK_SECRET to .env.example as an empty variable name so deploy setup documents the required Stripe webhook secret without exposing a value.",
|
|
14998
|
-
primaryMapCategory: "deployment",
|
|
14999
|
-
affectedMapCategories: ["payments"]
|
|
15000
|
-
})
|
|
15001
|
-
);
|
|
15002
|
-
}
|
|
15003
|
-
const hasCriticalGaps = gaps.some((item3) => item3.severity === "critical");
|
|
15004
|
-
return {
|
|
15005
|
-
version: 1,
|
|
15006
|
-
scannedAt: DEMO_SCANNED_AT,
|
|
15007
|
-
workspacePath: root,
|
|
15008
|
-
score: hasCriticalGaps ? 55 : 90,
|
|
15009
|
-
scoreLabel: hasCriticalGaps ? "Needs work" : "Looks solid",
|
|
15010
|
-
summary: `Demo scan: ${gaps.length} launch gap(s) detected by local rules.`,
|
|
15011
|
-
archetype: hasNext && hasSupabase && hasStripe ? "nextjs-supabase-stripe" : "demo-saas",
|
|
15012
|
-
gaps,
|
|
15013
|
-
missionGraph: emptyMissionGraph(),
|
|
15014
|
-
stackWiring: { items: [], byKey: {} },
|
|
15015
|
-
providerRegistry: {
|
|
15016
|
-
version: 1,
|
|
15017
|
-
source: "bundled",
|
|
15018
|
-
generatedAt: DEMO_SCANNED_AT,
|
|
15019
|
-
staleAfterDays: 30,
|
|
15020
|
-
status: "fresh",
|
|
15021
|
-
providers: []
|
|
15022
|
-
},
|
|
15023
|
-
verificationSummary: { byArea: {} },
|
|
15024
|
-
productionCorePercent: hasCriticalGaps ? 40 : 95,
|
|
15025
|
-
selectedProviders
|
|
15026
|
-
};
|
|
15027
|
-
}
|
|
15028
|
-
|
|
15029
|
-
// src/demo/runDemo.ts
|
|
15030
|
-
var import_picocolors7 = __toESM(require_picocolors());
|
|
15031
|
-
function bundledFixtureRoot() {
|
|
15032
|
-
const candidates = [
|
|
15033
|
-
import_node_path27.default.join(__dirname, "..", "fixtures", "demo-saas"),
|
|
15034
|
-
import_node_path27.default.join(__dirname, "..", "..", "fixtures", "demo-saas")
|
|
15035
|
-
];
|
|
15036
|
-
return candidates.find((candidate) => (0, import_node_fs13.existsSync)(import_node_path27.default.join(candidate, "package.json"))) ?? candidates[0];
|
|
15037
|
-
}
|
|
15038
|
-
function canUseInteractiveTryMenu(options) {
|
|
15039
|
-
return options.label === "try" && !options.agentMode && !options.openReport && process.stdin.isTTY === true && process.stdout.isTTY === true && process.env.CI !== "true";
|
|
15040
|
-
}
|
|
15041
|
-
function printTryMenuSummary(input) {
|
|
15042
|
-
const providerStack = Array.from(new Set(Object.values(input.artifact.selectedProviders ?? {}))).filter(Boolean).join(" + ");
|
|
15043
|
-
console.log("");
|
|
15044
|
-
console.log("VibeRaven Try");
|
|
15045
|
-
console.log("No login. No OpenAI key. Local fixture only.");
|
|
15046
|
-
console.log("");
|
|
15047
|
-
console.log(`Demo stack: ${input.artifact.archetype}${providerStack ? ` (${providerStack})` : ""}`);
|
|
15048
|
-
console.log(
|
|
15049
|
-
`Decision: BLOCKED | Production core: ${input.artifact.productionCorePercent}% | Gaps: ${input.artifact.gaps.length}`
|
|
15050
|
-
);
|
|
15051
|
-
console.log("");
|
|
15052
|
-
console.log("Menu:");
|
|
15053
|
-
console.log(" 1. Open visual report");
|
|
15054
|
-
console.log(" npx -y viberaven try --open");
|
|
15055
|
-
console.log(" 2. Show Codex/Claude agent output");
|
|
15056
|
-
console.log(" npx -y viberaven try --agent-mode");
|
|
15057
|
-
console.log(" 3. Run VibeRaven on your real app");
|
|
15058
|
-
console.log(" npx -y viberaven --agent-mode");
|
|
15059
|
-
console.log("");
|
|
15060
|
-
console.log(`Local UI: ${input.reportPath}`);
|
|
15061
|
-
if (input.opened) {
|
|
15062
|
-
console.log("Opened visual report in your browser.");
|
|
15063
|
-
}
|
|
15064
|
-
console.log("");
|
|
15065
|
-
}
|
|
15066
|
-
async function runInteractiveTryMenu(input) {
|
|
15067
|
-
Ie(`${import_picocolors7.default.bold("VibeRaven Try")} ${import_picocolors7.default.dim("local, no login, no API spend")}`);
|
|
15068
|
-
M2.message(
|
|
15069
|
-
`Demo stack: ${input.artifact.archetype} | Production core ${input.artifact.productionCorePercent}% | ${input.artifact.gaps.length} gaps`
|
|
15070
|
-
);
|
|
15071
|
-
const action = await ve({
|
|
15072
|
-
message: "What do you want to see?",
|
|
15073
|
-
options: [
|
|
15074
|
-
{ value: "open-report", label: "Open visual report", hint: "Browser action panel" },
|
|
15075
|
-
{ value: "agent-output", label: "Show Codex/Claude output", hint: "Native chat transcript" },
|
|
15076
|
-
{ value: "real-app", label: "Run on my real app", hint: "Copy/use the agent command" },
|
|
15077
|
-
{ value: "exit", label: "Exit" }
|
|
15078
|
-
]
|
|
15079
|
-
});
|
|
15080
|
-
if (pD(action) || action === "exit") {
|
|
15081
|
-
Se(import_picocolors7.default.dim("Run npx -y viberaven try anytime."));
|
|
15082
|
-
return;
|
|
15083
|
-
}
|
|
15084
|
-
if (action === "open-report") {
|
|
15085
|
-
await openPathInBrowser(input.reportPath);
|
|
15086
|
-
Se(`Opened ${input.reportPath}`);
|
|
15087
|
-
return;
|
|
15088
|
-
}
|
|
15089
|
-
if (action === "agent-output") {
|
|
15090
|
-
const prp = generateProductionProtocol(input.artifact, {
|
|
15091
|
-
mode: "demo",
|
|
15092
|
-
connectedTools: input.connectedTools
|
|
15093
|
-
});
|
|
15094
|
-
printOperatorBlock(prp, buildTaskList(input.artifact), currentRenderMode("demo"));
|
|
15095
|
-
printActionPanelBlock(input.reportPath);
|
|
15096
|
-
Se(import_picocolors7.default.dim("This is the output Codex/Claude/Cursor should reason over."));
|
|
15097
|
-
return;
|
|
15098
|
-
}
|
|
15099
|
-
console.log("");
|
|
15100
|
-
console.log("Run this inside Codex, Claude Code, Cursor, Windsurf, or Gemini CLI:");
|
|
15101
|
-
console.log(" npx -y viberaven --agent-mode");
|
|
15102
|
-
console.log("");
|
|
15103
|
-
Se(import_picocolors7.default.dim("The native agent chat stays the main UX; the report is the visual action panel."));
|
|
15104
|
-
}
|
|
15105
|
-
async function runDemo(options) {
|
|
15106
|
-
const label2 = options.label ?? "demo";
|
|
15107
|
-
const connectedTools = detectConnectedTools();
|
|
15108
|
-
const fixtureRoot = options.fixtureRoot ?? bundledFixtureRoot();
|
|
15109
|
-
const scannedArtifact = await scanDemoFixture(fixtureRoot);
|
|
15110
|
-
const artifact = {
|
|
15111
|
-
...scannedArtifact,
|
|
15112
|
-
workspacePath: options.cwd
|
|
15113
|
-
};
|
|
15114
|
-
const paths = await writeScanArtifacts({
|
|
15115
|
-
artifact,
|
|
15116
|
-
cwd: options.cwd,
|
|
15117
|
-
connectedTools,
|
|
15118
|
-
prpMode: "demo"
|
|
15119
|
-
});
|
|
15120
|
-
let reportOpened = false;
|
|
15121
|
-
if (options.openReport) {
|
|
15122
|
-
try {
|
|
15123
|
-
await openPathInBrowser(paths.reportPath);
|
|
15124
|
-
reportOpened = true;
|
|
15125
|
-
} catch (error) {
|
|
15126
|
-
console.warn(error instanceof Error ? error.message : String(error));
|
|
15127
|
-
}
|
|
15128
|
-
}
|
|
15129
|
-
if (options.agentMode) {
|
|
15130
|
-
console.log(
|
|
15131
|
-
label2 === "demo" ? "VibeRaven demo mode - no login, no API spend. Provider verification is simulated." : `VibeRaven ${label2 === "try" ? "Try" : "showcase run"} - no login, no API spend. Provider verification is simulated.`
|
|
15132
|
-
);
|
|
15133
|
-
const prp = generateProductionProtocol(artifact, { mode: "demo", connectedTools });
|
|
15134
|
-
const mode = currentRenderMode("demo");
|
|
15135
|
-
printOperatorBlock(prp, buildTaskList(artifact), mode);
|
|
15136
|
-
const celebration = renderClearCelebration(prp, mode);
|
|
15137
|
-
if (celebration) {
|
|
15138
|
-
console.log(celebration);
|
|
15139
|
-
}
|
|
15140
|
-
printActionPanelBlock(paths.reportPath);
|
|
15141
|
-
} else if (label2 === "try") {
|
|
15142
|
-
if (canUseInteractiveTryMenu(options)) {
|
|
15143
|
-
await runInteractiveTryMenu({ artifact, reportPath: paths.reportPath, connectedTools });
|
|
15144
|
-
} else {
|
|
15145
|
-
printTryMenuSummary({ artifact, reportPath: paths.reportPath, opened: reportOpened });
|
|
15146
|
-
}
|
|
15147
|
-
} else {
|
|
15148
|
-
console.log(
|
|
15149
|
-
label2 === "demo" ? "VibeRaven demo complete. See .viberaven/prp.json" : "VibeRaven showcase run complete. See .viberaven/prp.json"
|
|
15150
|
-
);
|
|
15151
|
-
}
|
|
15152
|
-
return 0;
|
|
15153
|
-
}
|
|
15154
|
-
|
|
15155
14177
|
// src/cli.ts
|
|
15156
14178
|
function printHelp() {
|
|
15157
14179
|
console.log(`viberaven ${VERSION} \u2014 launch readiness for AI-built apps
|
|
@@ -15181,18 +14203,6 @@ Usage:
|
|
|
15181
14203
|
viberaven --agent-mode [--json|--jsonl] [path]
|
|
15182
14204
|
Agent-first scan; writes tasklist, gate-result, context-map, and per-gap JSON
|
|
15183
14205
|
|
|
15184
|
-
viberaven --demo [path]
|
|
15185
|
-
No-login demo scan over the bundled fixture; writes demo artifacts locally
|
|
15186
|
-
|
|
15187
|
-
viberaven --agent-mode --demo [path]
|
|
15188
|
-
Demo scan with operator output; no login, no managed API spend
|
|
15189
|
-
|
|
15190
|
-
viberaven try [--open|--ui] [path]
|
|
15191
|
-
Free no-login local try run with a simple menu and visual report
|
|
15192
|
-
|
|
15193
|
-
viberaven --showcase --agent-mode [path]
|
|
15194
|
-
Alias for the no-login local try run
|
|
15195
|
-
|
|
15196
14206
|
viberaven --strict[=warning] [path]
|
|
15197
14207
|
Fail when production gate is not clear; warning mode also fails on warnings
|
|
15198
14208
|
|
|
@@ -15231,9 +14241,6 @@ Usage:
|
|
|
15231
14241
|
viberaven audit --vercel-supabase [--json] [path]
|
|
15232
14242
|
Local Vercel/Supabase repo evidence audit (RLS, pooler, secrets)
|
|
15233
14243
|
|
|
15234
|
-
viberaven badge [--svg|--card] [path]
|
|
15235
|
-
Print a README badge, terminal card, or write .viberaven/badge.svg from .viberaven/prp.json
|
|
15236
|
-
|
|
15237
14244
|
|
|
15238
14245
|
|
|
15239
14246
|
Agent workflow (Claude Code / Codex):
|
|
@@ -15293,62 +14300,16 @@ function parseArgs(argv) {
|
|
|
15293
14300
|
continue;
|
|
15294
14301
|
}
|
|
15295
14302
|
if (!command) {
|
|
15296
|
-
|
|
15297
|
-
positional.push(arg);
|
|
15298
|
-
} else {
|
|
15299
|
-
command = arg;
|
|
15300
|
-
}
|
|
14303
|
+
command = arg;
|
|
15301
14304
|
} else {
|
|
15302
14305
|
positional.push(arg);
|
|
15303
14306
|
}
|
|
15304
14307
|
}
|
|
15305
14308
|
return { command, flags, positional };
|
|
15306
14309
|
}
|
|
15307
|
-
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
15308
|
-
"audit",
|
|
15309
|
-
"badge",
|
|
15310
|
-
"connect",
|
|
15311
|
-
"doctor",
|
|
15312
|
-
"guide",
|
|
15313
|
-
"init",
|
|
15314
|
-
"interactive",
|
|
15315
|
-
"login",
|
|
15316
|
-
"logout",
|
|
15317
|
-
"next",
|
|
15318
|
-
"open",
|
|
15319
|
-
"prompt",
|
|
15320
|
-
"provider-verify",
|
|
15321
|
-
"report",
|
|
15322
|
-
"scan",
|
|
15323
|
-
"stack",
|
|
15324
|
-
"status",
|
|
15325
|
-
"tui",
|
|
15326
|
-
"try",
|
|
15327
|
-
"validate-npm-package",
|
|
15328
|
-
"version",
|
|
15329
|
-
"watch"
|
|
15330
|
-
]);
|
|
15331
|
-
function isRootModePositional(flags, token) {
|
|
15332
|
-
if (KNOWN_COMMANDS.has(token)) {
|
|
15333
|
-
return false;
|
|
15334
|
-
}
|
|
15335
|
-
return [
|
|
15336
|
-
"agent-mode",
|
|
15337
|
-
"demo",
|
|
15338
|
-
"showcase",
|
|
15339
|
-
"json",
|
|
15340
|
-
"jsonl",
|
|
15341
|
-
"strict",
|
|
15342
|
-
"condense",
|
|
15343
|
-
"verify",
|
|
15344
|
-
"heal"
|
|
15345
|
-
].some((key) => flags[key] !== void 0);
|
|
15346
|
-
}
|
|
15347
14310
|
function isBooleanFlag(command, key) {
|
|
15348
14311
|
if ([
|
|
15349
14312
|
"agent-mode",
|
|
15350
|
-
"demo",
|
|
15351
|
-
"showcase",
|
|
15352
14313
|
"json",
|
|
15353
14314
|
"jsonl",
|
|
15354
14315
|
"condense",
|
|
@@ -15358,17 +14319,14 @@ function isBooleanFlag(command, key) {
|
|
|
15358
14319
|
"apply",
|
|
15359
14320
|
"yes",
|
|
15360
14321
|
"no-verify",
|
|
15361
|
-
"force-scan"
|
|
15362
|
-
"ui"
|
|
14322
|
+
"force-scan"
|
|
15363
14323
|
].includes(key)) {
|
|
15364
14324
|
return true;
|
|
15365
14325
|
}
|
|
15366
14326
|
if (key === "strict") return true;
|
|
15367
|
-
if (key === "open" && (command === "" || command === "scan" || command === "report"
|
|
14327
|
+
if (key === "open" && (command === "" || command === "scan" || command === "report")) return true;
|
|
15368
14328
|
if (key === "verify" && command === "") return true;
|
|
15369
14329
|
if (key === "vercel-supabase" && command === "audit") return true;
|
|
15370
|
-
if (key === "svg" && command === "badge") return true;
|
|
15371
|
-
if (key === "card" && command === "badge") return true;
|
|
15372
14330
|
if (key === "json" && command === "validate-npm-package") return true;
|
|
15373
14331
|
if (key === "dry-run" && command === "init") return true;
|
|
15374
14332
|
if (key === "agents" && command === "doctor") return true;
|
|
@@ -15380,9 +14338,6 @@ function shouldConsumeLeadingHyphenValue(command, key, value) {
|
|
|
15380
14338
|
function hasFlag(flags, key) {
|
|
15381
14339
|
return flags[key] === true || typeof flags[key] === "string";
|
|
15382
14340
|
}
|
|
15383
|
-
function resolveCliPath(input) {
|
|
15384
|
-
return input ? (0, import_node_path28.resolve)(process.cwd(), input) : process.cwd();
|
|
15385
|
-
}
|
|
15386
14341
|
async function guardEarlyVerifyScan(input) {
|
|
15387
14342
|
if (input.flags["force-scan"] === true) {
|
|
15388
14343
|
return void 0;
|
|
@@ -15391,7 +14346,7 @@ async function guardEarlyVerifyScan(input) {
|
|
|
15391
14346
|
if (!verifyLike) {
|
|
15392
14347
|
return void 0;
|
|
15393
14348
|
}
|
|
15394
|
-
const workspacePath = input.positional[0] ? (0,
|
|
14349
|
+
const workspacePath = input.positional[0] ? (0, import_node_path25.join)(process.cwd(), input.positional[0]) : await resolveWorkspaceRoot(process.cwd());
|
|
15395
14350
|
const loopState = await loadLoopState(workspacePath);
|
|
15396
14351
|
if (loopState.batchApplied <= 0) {
|
|
15397
14352
|
return void 0;
|
|
@@ -15415,19 +14370,7 @@ async function guardEarlyVerifyScan(input) {
|
|
|
15415
14370
|
return void 0;
|
|
15416
14371
|
}
|
|
15417
14372
|
const nextTask = remainingRepoCodeTasks[0];
|
|
15418
|
-
const progress = renderLoopProgress(
|
|
15419
|
-
{
|
|
15420
|
-
applied: loopState.batchApplied,
|
|
15421
|
-
total: batchSize,
|
|
15422
|
-
label: "Healing repo gaps",
|
|
15423
|
-
silentInPlain: true
|
|
15424
|
-
},
|
|
15425
|
-
currentRenderMode("human-command")
|
|
15426
|
-
);
|
|
15427
14373
|
console.error("SCAN_DEFERRED: Local heal batch is not full yet, so VibeRaven is protecting scan quota.");
|
|
15428
|
-
if (progress) {
|
|
15429
|
-
console.error(progress);
|
|
15430
|
-
}
|
|
15431
14374
|
console.error(`Batch progress: ${loopState.batchApplied}/${batchSize} local heals applied since the last scan.`);
|
|
15432
14375
|
console.error(`Next local heal: npx -y viberaven --heal --apply --gap ${nextTask.gapId} --yes`);
|
|
15433
14376
|
console.error("Run verify again after the batch is full, or add --force-scan if the user explicitly wants to spend a scan now.");
|
|
@@ -15442,36 +14385,6 @@ function resolveDefaultEntrypointMode(options) {
|
|
|
15442
14385
|
function formatScanJsonStdout(artifact) {
|
|
15443
14386
|
return JSON.stringify(sanitizeArtifactForDisk(artifact), null, 2);
|
|
15444
14387
|
}
|
|
15445
|
-
function providerFromConnectedToolKey(tool) {
|
|
15446
|
-
if (!tool.endsWith("Mcp")) return void 0;
|
|
15447
|
-
return tool.slice(0, -3).toLowerCase();
|
|
15448
|
-
}
|
|
15449
|
-
function normalizeProviderForConnectedToolHint(provider2) {
|
|
15450
|
-
const lower = provider2.trim().toLowerCase();
|
|
15451
|
-
const tokens = lower.split(/[^a-z0-9]+/).filter(Boolean);
|
|
15452
|
-
for (const knownProvider of ["supabase", "stripe", "vercel", "github"]) {
|
|
15453
|
-
if (lower === knownProvider || tokens.includes(knownProvider)) {
|
|
15454
|
-
return knownProvider;
|
|
15455
|
-
}
|
|
15456
|
-
}
|
|
15457
|
-
return lower;
|
|
15458
|
-
}
|
|
15459
|
-
function printMissingConnectedToolHints(prp) {
|
|
15460
|
-
const relevantProviders = new Set([
|
|
15461
|
-
...prp.detectedStack.auth,
|
|
15462
|
-
...prp.detectedStack.database,
|
|
15463
|
-
...prp.detectedStack.deployment,
|
|
15464
|
-
...prp.detectedStack.payments
|
|
15465
|
-
].map(normalizeProviderForConnectedToolHint));
|
|
15466
|
-
for (const [tool, state] of Object.entries(prp.connectedTools)) {
|
|
15467
|
-
const provider2 = providerFromConnectedToolKey(tool);
|
|
15468
|
-
if (!provider2 || state !== "missing" || !relevantProviders.has(provider2)) {
|
|
15469
|
-
continue;
|
|
15470
|
-
}
|
|
15471
|
-
console.log(`${provider2} MCP is not connected. For stronger verification, add it:`);
|
|
15472
|
-
console.log(` ${installHintFor(provider2)}`);
|
|
15473
|
-
}
|
|
15474
|
-
}
|
|
15475
14388
|
async function cmdLogin(flags) {
|
|
15476
14389
|
const apiBaseUrl = resolveApiBaseUrl(typeof flags["api-url"] === "string" ? flags["api-url"] : void 0);
|
|
15477
14390
|
await runDeviceLogin(apiBaseUrl);
|
|
@@ -15509,7 +14422,7 @@ async function cmdStatus(flags, positional) {
|
|
|
15509
14422
|
console.log("Not signed in. Run: viberaven login");
|
|
15510
14423
|
return 1;
|
|
15511
14424
|
}
|
|
15512
|
-
const startDir = positional[0] ? (0,
|
|
14425
|
+
const startDir = positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd();
|
|
15513
14426
|
let artifact;
|
|
15514
14427
|
try {
|
|
15515
14428
|
artifact = await loadLastArtifact(startDir);
|
|
@@ -15663,7 +14576,7 @@ async function cmdWatch(flags) {
|
|
|
15663
14576
|
}
|
|
15664
14577
|
}
|
|
15665
14578
|
async function runScanCommand(flags, positional, options) {
|
|
15666
|
-
const workspacePath = positional[0] ? (0,
|
|
14579
|
+
const workspacePath = positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : await resolveWorkspaceRoot(process.cwd());
|
|
15667
14580
|
const apiBaseUrl = resolveApiBaseUrl(typeof flags["api-url"] === "string" ? flags["api-url"] : void 0);
|
|
15668
14581
|
let accessToken;
|
|
15669
14582
|
try {
|
|
@@ -15709,8 +14622,7 @@ async function runScanCommand(flags, positional, options) {
|
|
|
15709
14622
|
return { exitCode: 1 };
|
|
15710
14623
|
}
|
|
15711
14624
|
const artifact = await enrichArtifactWithAccount(result.artifact, apiBaseUrl, accessToken);
|
|
15712
|
-
const
|
|
15713
|
-
const paths = await writeScanArtifacts({ artifact, cwd: workspacePath, connectedTools });
|
|
14625
|
+
const paths = await writeScanArtifacts({ artifact, cwd: workspacePath });
|
|
15714
14626
|
if (flags.json && !options?.deferMachineOutput) {
|
|
15715
14627
|
console.log(formatScanJsonStdout(artifact));
|
|
15716
14628
|
return { exitCode: 0, artifacts: paths };
|
|
@@ -15726,15 +14638,6 @@ async function runScanCommand(flags, positional, options) {
|
|
|
15726
14638
|
const openGapCount = artifact.gaps.length;
|
|
15727
14639
|
const updatedState = resetBatch(loopState, openGapCount);
|
|
15728
14640
|
const tasks = buildTaskList(artifact);
|
|
15729
|
-
const prp = generateProductionProtocol(artifact, { connectedTools });
|
|
15730
|
-
const mode = currentRenderMode("agent-mode");
|
|
15731
|
-
printOperatorBlock(prp, tasks, mode);
|
|
15732
|
-
const celebration = renderClearCelebration(prp, mode);
|
|
15733
|
-
if (celebration) {
|
|
15734
|
-
console.log(celebration);
|
|
15735
|
-
}
|
|
15736
|
-
printActionPanelBlock(paths.reportPath);
|
|
15737
|
-
printMissingConnectedToolHints(prp);
|
|
15738
14641
|
const plan = artifact.plan ?? "free";
|
|
15739
14642
|
const block = buildNextActionBlock(tasks, updatedState, plan);
|
|
15740
14643
|
printNextActionBlock(block);
|
|
@@ -15751,7 +14654,7 @@ async function runScanCommand(flags, positional, options) {
|
|
|
15751
14654
|
return { exitCode: 0, artifacts: paths };
|
|
15752
14655
|
}
|
|
15753
14656
|
async function cmdReport(flags, positional) {
|
|
15754
|
-
const startDir = positional[0] ? (0,
|
|
14657
|
+
const startDir = positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd();
|
|
15755
14658
|
try {
|
|
15756
14659
|
const paths = await refreshReportFromDisk(startDir);
|
|
15757
14660
|
console.log(`Report refreshed: ${paths.reportPath}`);
|
|
@@ -15773,7 +14676,7 @@ async function cmdReport(flags, positional) {
|
|
|
15773
14676
|
}
|
|
15774
14677
|
}
|
|
15775
14678
|
async function cmdPrompt(flags, positional) {
|
|
15776
|
-
const startDir = positional[0] ? (0,
|
|
14679
|
+
const startDir = positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd();
|
|
15777
14680
|
let artifact;
|
|
15778
14681
|
try {
|
|
15779
14682
|
artifact = await loadLastArtifact(startDir);
|
|
@@ -15781,26 +14684,26 @@ async function cmdPrompt(flags, positional) {
|
|
|
15781
14684
|
console.error(error instanceof Error ? error.message : "No scan found. Run: viberaven scan");
|
|
15782
14685
|
return 1;
|
|
15783
14686
|
}
|
|
15784
|
-
const
|
|
14687
|
+
const gap = pickGap(artifact, {
|
|
15785
14688
|
gapId: typeof flags.gap === "string" ? flags.gap : void 0,
|
|
15786
14689
|
provider: typeof flags.provider === "string" ? flags.provider : void 0,
|
|
15787
14690
|
area: typeof flags.area === "string" ? flags.area : void 0
|
|
15788
14691
|
});
|
|
15789
|
-
if (!
|
|
14692
|
+
if (!gap) {
|
|
15790
14693
|
console.error("No matching gap. Run `viberaven scan` or pass --gap <id>.");
|
|
15791
14694
|
return 1;
|
|
15792
14695
|
}
|
|
15793
14696
|
const skipCopy = flags["no-copy"] === true;
|
|
15794
14697
|
if (!skipCopy) {
|
|
15795
14698
|
try {
|
|
15796
|
-
await copyToClipboard(
|
|
15797
|
-
console.log(`Copied to clipboard: ${
|
|
14699
|
+
await copyToClipboard(gap.copyPrompt);
|
|
14700
|
+
console.log(`Copied to clipboard: ${gap.title}`);
|
|
15798
14701
|
return 0;
|
|
15799
14702
|
} catch (error) {
|
|
15800
14703
|
console.warn(error instanceof Error ? error.message : String(error));
|
|
15801
14704
|
}
|
|
15802
14705
|
}
|
|
15803
|
-
console.log(
|
|
14706
|
+
console.log(gap.copyPrompt);
|
|
15804
14707
|
return 0;
|
|
15805
14708
|
}
|
|
15806
14709
|
var STACK_AREAS = /* @__PURE__ */ new Set(["database", "auth", "payments", "deployment", "monitoring", "security"]);
|
|
@@ -15871,7 +14774,7 @@ async function main() {
|
|
|
15871
14774
|
const wantsJsonl = hasFlag(flags, "jsonl");
|
|
15872
14775
|
const wantsStrict = hasFlag(flags, "strict");
|
|
15873
14776
|
if (flags.condense) {
|
|
15874
|
-
const cwd = positional[0] ? (0,
|
|
14777
|
+
const cwd = positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd();
|
|
15875
14778
|
const result = await runCondenseCommand({ cwd });
|
|
15876
14779
|
console.log(`VibeRaven context map refreshed: ${result.contextMapPath}`);
|
|
15877
14780
|
return 0;
|
|
@@ -15889,19 +14792,6 @@ async function main() {
|
|
|
15889
14792
|
console.log(JSON.stringify(result, null, 2));
|
|
15890
14793
|
return result.status.startsWith("refused") || result.status === "failed" ? 1 : 0;
|
|
15891
14794
|
}
|
|
15892
|
-
if (command === "try") {
|
|
15893
|
-
const cwd = resolveCliPath(positional[0]);
|
|
15894
|
-
return runDemo({
|
|
15895
|
-
cwd,
|
|
15896
|
-
agentMode: isAgentMode,
|
|
15897
|
-
label: "try",
|
|
15898
|
-
openReport: flags.open === true || flags.ui === true
|
|
15899
|
-
});
|
|
15900
|
-
}
|
|
15901
|
-
if (hasFlag(flags, "demo") || hasFlag(flags, "showcase")) {
|
|
15902
|
-
const cwd = resolveCliPath(positional[0]);
|
|
15903
|
-
return runDemo({ cwd, agentMode: isAgentMode, label: hasFlag(flags, "showcase") ? "showcase" : "demo" });
|
|
15904
|
-
}
|
|
15905
14795
|
if (!command && (isAgentMode || flags.verify === true || wantsJson || wantsJsonl || wantsStrict)) {
|
|
15906
14796
|
const guardedExitCode = await guardEarlyVerifyScan({ flags, positional, wantsStrict });
|
|
15907
14797
|
if (guardedExitCode !== void 0) {
|
|
@@ -15913,7 +14803,7 @@ async function main() {
|
|
|
15913
14803
|
console.error("VibeRaven could not produce machine output because scan artifacts were not written.");
|
|
15914
14804
|
return 3;
|
|
15915
14805
|
}
|
|
15916
|
-
const gateResult = scanResult.artifacts && (wantsJson || wantsJsonl || wantsStrict) ? JSON.parse(await (0,
|
|
14806
|
+
const gateResult = scanResult.artifacts && (wantsJson || wantsJsonl || wantsStrict) ? JSON.parse(await (0, import_promises19.readFile)(scanResult.artifacts.gateResultPath, "utf8")) : void 0;
|
|
15917
14807
|
const strictExitCode = wantsStrict && gateResult ? exitCodeForStrictGate(gateResult, { failOnWarnings: flags.strict === "warning" }) : scanResult.exitCode;
|
|
15918
14808
|
if (wantsJson && gateResult) {
|
|
15919
14809
|
process.stdout.write(renderGateResultJson(gateResult));
|
|
@@ -15948,7 +14838,7 @@ async function main() {
|
|
|
15948
14838
|
case "next":
|
|
15949
14839
|
return runNextCommand({
|
|
15950
14840
|
json: Boolean(flags.json),
|
|
15951
|
-
cwd: positional[0] ? (0,
|
|
14841
|
+
cwd: positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd()
|
|
15952
14842
|
});
|
|
15953
14843
|
case "guide": {
|
|
15954
14844
|
const provider2 = positional[0];
|
|
@@ -15986,7 +14876,7 @@ async function main() {
|
|
|
15986
14876
|
case "provider-verify":
|
|
15987
14877
|
return cmdProviderVerify(flags, positional);
|
|
15988
14878
|
case "init": {
|
|
15989
|
-
const cwd = positional[0] ? (0,
|
|
14879
|
+
const cwd = positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd();
|
|
15990
14880
|
const agents = typeof flags.agents === "string" ? flags.agents : void 0;
|
|
15991
14881
|
return runInitCommand({
|
|
15992
14882
|
cwd,
|
|
@@ -16000,7 +14890,7 @@ async function main() {
|
|
|
16000
14890
|
return 1;
|
|
16001
14891
|
}
|
|
16002
14892
|
return runDoctorAgentsCommand({
|
|
16003
|
-
cwd: positional[0] ? (0,
|
|
14893
|
+
cwd: positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd()
|
|
16004
14894
|
});
|
|
16005
14895
|
case "validate-npm-package":
|
|
16006
14896
|
return runValidateNpmPackageCommand({
|
|
@@ -16013,15 +14903,9 @@ async function main() {
|
|
|
16013
14903
|
return 1;
|
|
16014
14904
|
}
|
|
16015
14905
|
return runAuditCommand({
|
|
16016
|
-
cwd: positional[0] ? (0,
|
|
14906
|
+
cwd: positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd(),
|
|
16017
14907
|
json: Boolean(flags.json)
|
|
16018
14908
|
});
|
|
16019
|
-
case "badge":
|
|
16020
|
-
return runBadgeCommand({
|
|
16021
|
-
cwd: positional[0] ? (0, import_node_path28.join)(process.cwd(), positional[0]) : process.cwd(),
|
|
16022
|
-
svg: flags.svg === true,
|
|
16023
|
-
card: flags.card === true
|
|
16024
|
-
});
|
|
16025
14909
|
default:
|
|
16026
14910
|
console.error(`Unknown command: ${command}`);
|
|
16027
14911
|
printHelp();
|