@viberaven/cli 1.1.4 → 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/AGENTS.md +2 -2
- package/README.md +1 -1
- 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 +817 -1942
- 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 +2 -3
- package/templates/AGENTS.snippet.md +1 -1
- package/templates/CLAUDE.snippet.md +1 -1
- package/templates/CURSOR.snippet.md +1 -1
- 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)];
|
|
@@ -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
|
|
@@ -8493,7 +8516,7 @@ function needsScanMessage(startDir) {
|
|
|
8493
8516
|
return [
|
|
8494
8517
|
"No CLI scan found for this folder.",
|
|
8495
8518
|
`Looking from: ${cwd}`,
|
|
8496
|
-
'Choose "
|
|
8519
|
+
'Choose "Scan project", or run the menu from your repo root (where .viberaven/ lives).',
|
|
8497
8520
|
"VS Code extension scans stay inside the editor \u2014 run a CLI scan once to create .viberaven/ on disk."
|
|
8498
8521
|
].join("\n");
|
|
8499
8522
|
}
|
|
@@ -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,12 +11549,14 @@ 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
|
|
11771
11557
|
|
|
11772
11558
|
- Before running build, compile, cleanup, deployment, vercel, or supabase commands, run \`${PUBLIC_AGENT_MODE_COMMAND}\` first from the project root.
|
|
11773
|
-
- Treat VibeRaven as the
|
|
11559
|
+
- Treat VibeRaven as the Agent Context + Production Gate for AI-built apps, especially apps using Vercel, Supabase, auth, database, payments, webhooks, monitoring, rate limits, or environment variables.
|
|
11774
11560
|
- Read \`.viberaven/agent-tasklist.md\` first. If it says CRITICAL GAPS DETECTED or PRODUCTION GATE NOT CLEAR, fix one listed repo-code gap before deploying.
|
|
11775
11561
|
- Read \`.viberaven/gate-result.json\` for the machine verdict.
|
|
11776
11562
|
- Read \`.viberaven/context-map.json\` for compact agent context.
|
|
@@ -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,53 +12078,14 @@ function formatAgentRulesInitSummary(output) {
|
|
|
12291
12078
|
);
|
|
12292
12079
|
return lines.join("\n");
|
|
12293
12080
|
}
|
|
12294
|
-
async function
|
|
12295
|
-
const existing = await Promise.all([
|
|
12296
|
-
fileExists2((0, import_node_path13.join)(options.cwd, AGENT_RULE_TARGETS.codex.file)),
|
|
12297
|
-
fileExists2((0, import_node_path13.join)(options.cwd, AGENT_RULE_TARGETS.claude.file)),
|
|
12298
|
-
fileExists2((0, import_node_path13.join)(options.cwd, AGENT_RULE_TARGETS.cursor.file))
|
|
12299
|
-
]);
|
|
12300
|
-
if (existing.some(Boolean)) {
|
|
12301
|
-
return { status: "ready" };
|
|
12302
|
-
}
|
|
12303
|
-
try {
|
|
12304
|
-
const output = await initAgentRules({
|
|
12305
|
-
cwd: options.cwd,
|
|
12306
|
-
targets: ["codex", "claude", "cursor", "agentContext", "missionMap"]
|
|
12307
|
-
});
|
|
12308
|
-
return { status: "installed", output };
|
|
12309
|
-
} catch (error) {
|
|
12310
|
-
return { status: "skipped", reason: error instanceof Error ? error.message : String(error) };
|
|
12311
|
-
}
|
|
12312
|
-
}
|
|
12313
|
-
function formatAgentRulesBootstrapSummary(result) {
|
|
12314
|
-
if (result.status === "ready") {
|
|
12315
|
-
return "Agent setup: Codex/Claude/Cursor instructions already connected.";
|
|
12316
|
-
}
|
|
12317
|
-
if (result.status === "skipped") {
|
|
12318
|
-
return `Agent setup: skipped (${result.reason})`;
|
|
12319
|
-
}
|
|
12320
|
-
const created = result.output.results.filter((item3) => item3.action === "created").map((item3) => item3.file);
|
|
12321
|
-
const updated = result.output.results.filter((item3) => item3.action === "updated").map((item3) => item3.file);
|
|
12322
|
-
const changed = [...created, ...updated];
|
|
12323
|
-
return changed.length > 0 ? `Agent setup: connected ${changed.join(", ")}.` : "Agent setup: Codex/Claude/Cursor instructions already connected.";
|
|
12324
|
-
}
|
|
12325
|
-
async function readExistingFile2(path3) {
|
|
12326
|
-
try {
|
|
12327
|
-
return { exists: true, content: await (0, import_promises10.readFile)(path3, "utf-8") };
|
|
12328
|
-
} catch (error) {
|
|
12329
|
-
if (isFileNotFoundError2(error)) {
|
|
12330
|
-
return { exists: false, content: "" };
|
|
12331
|
-
}
|
|
12332
|
-
throw error;
|
|
12333
|
-
}
|
|
12334
|
-
}
|
|
12335
|
-
async function fileExists2(path3) {
|
|
12081
|
+
async function readExistingFile2(path) {
|
|
12336
12082
|
try {
|
|
12337
|
-
await (0, import_promises10.
|
|
12338
|
-
|
|
12339
|
-
|
|
12340
|
-
|
|
12083
|
+
return { exists: true, content: await (0, import_promises10.readFile)(path, "utf-8") };
|
|
12084
|
+
} catch (error) {
|
|
12085
|
+
if (isFileNotFoundError2(error)) {
|
|
12086
|
+
return { exists: false, content: "" };
|
|
12087
|
+
}
|
|
12088
|
+
throw error;
|
|
12341
12089
|
}
|
|
12342
12090
|
}
|
|
12343
12091
|
function isFileNotFoundError2(error) {
|
|
@@ -12500,15 +12248,15 @@ async function handleViewGaps(cwd) {
|
|
|
12500
12248
|
async function handlePrompt(cwd) {
|
|
12501
12249
|
try {
|
|
12502
12250
|
const artifact = await loadLastArtifact(cwd);
|
|
12503
|
-
const
|
|
12504
|
-
if (!
|
|
12251
|
+
const gap = pickGap(artifact);
|
|
12252
|
+
if (!gap) {
|
|
12505
12253
|
M2.warn("No gaps to fix. Run a scan or pick a different project.");
|
|
12506
12254
|
return;
|
|
12507
12255
|
}
|
|
12508
|
-
const prompt = buildAgentFixPrompt(artifact,
|
|
12256
|
+
const prompt = buildAgentFixPrompt(artifact, gap);
|
|
12509
12257
|
try {
|
|
12510
12258
|
await copyToClipboard(prompt);
|
|
12511
|
-
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}`));
|
|
12512
12260
|
} catch (error) {
|
|
12513
12261
|
M2.warn(error instanceof Error ? error.message : String(error));
|
|
12514
12262
|
console.log("");
|
|
@@ -12619,15 +12367,19 @@ async function handleOpenDashboard(cwd) {
|
|
|
12619
12367
|
}
|
|
12620
12368
|
function buildMenuOptions(isSignedIn) {
|
|
12621
12369
|
return [
|
|
12622
|
-
{ value: "
|
|
12623
|
-
{ value: "
|
|
12624
|
-
{ value: "open-
|
|
12625
|
-
{ value: "guide", label: "Provider
|
|
12626
|
-
{ value: "
|
|
12370
|
+
{ value: "next", label: "What's next?", hint: "One action from last scan" },
|
|
12371
|
+
{ value: "scan", label: "Scan project", hint: "Map launch readiness" },
|
|
12372
|
+
{ value: "open-report", label: "Open report in browser", hint: "Rebuilds UI from last scan" },
|
|
12373
|
+
{ value: "guide", label: "Provider guide", hint: "Vercel, Supabase, Stripe steps" },
|
|
12374
|
+
{ value: "open-dashboard", label: "Open dashboard", hint: "Browser link for next step" },
|
|
12375
|
+
{ value: "gaps", label: "View top gaps", hint: "From last scan" },
|
|
12376
|
+
{ value: "prompt", label: "Copy top prompt", hint: "Agent-ready fix prompt" },
|
|
12377
|
+
{ value: "agent-rules", label: "Install agent rules", hint: "AGENTS.md, CLAUDE.md, Cursor" },
|
|
12378
|
+
{ value: "audit", label: "Vercel/Supabase audit", hint: "RLS, service-role, serverless pooler evidence" },
|
|
12627
12379
|
{
|
|
12628
12380
|
value: "auth",
|
|
12629
12381
|
label: isSignedIn ? "Sign out" : "Sign in",
|
|
12630
|
-
hint: isSignedIn ? "Clear local credentials" : "
|
|
12382
|
+
hint: isSignedIn ? "Clear local credentials" : "Device login flow"
|
|
12631
12383
|
},
|
|
12632
12384
|
{ value: "exit", label: "Exit" }
|
|
12633
12385
|
];
|
|
@@ -12643,7 +12395,7 @@ async function runInteractiveSession(startDir = process.cwd()) {
|
|
|
12643
12395
|
if (!artifactsAt) {
|
|
12644
12396
|
M2.message(
|
|
12645
12397
|
import_picocolors4.default.dim(
|
|
12646
|
-
|
|
12398
|
+
"No .viberaven/ here yet. Extension scans are separate \u2014 choose Scan project to write CLI artifacts."
|
|
12647
12399
|
)
|
|
12648
12400
|
);
|
|
12649
12401
|
}
|
|
@@ -13886,7 +13638,7 @@ var STALE_PATTERNS = [
|
|
|
13886
13638
|
async function checkAgentInjection(cwd) {
|
|
13887
13639
|
const checks = [];
|
|
13888
13640
|
for (const item3 of REQUIRED_EXISTENCE_CHECKS) {
|
|
13889
|
-
const exists = await
|
|
13641
|
+
const exists = await fileExists2((0, import_node_path23.join)(cwd, item3.file));
|
|
13890
13642
|
checks.push({
|
|
13891
13643
|
id: item3.id,
|
|
13892
13644
|
status: exists ? "pass" : "fail",
|
|
@@ -13894,7 +13646,7 @@ async function checkAgentInjection(cwd) {
|
|
|
13894
13646
|
});
|
|
13895
13647
|
}
|
|
13896
13648
|
for (const item3 of OPTIONAL_CURSOR_PACK_CHECKS) {
|
|
13897
|
-
const exists = await
|
|
13649
|
+
const exists = await fileExists2((0, import_node_path23.join)(cwd, item3.file));
|
|
13898
13650
|
checks.push({
|
|
13899
13651
|
id: item3.id,
|
|
13900
13652
|
status: exists ? "pass" : "fail",
|
|
@@ -13902,9 +13654,9 @@ async function checkAgentInjection(cwd) {
|
|
|
13902
13654
|
});
|
|
13903
13655
|
}
|
|
13904
13656
|
const legacyCursorPath = (0, import_node_path23.join)(cwd, ".cursor/rules/viberaven.mdc");
|
|
13905
|
-
if (await
|
|
13657
|
+
if (await fileExists2(legacyCursorPath)) {
|
|
13906
13658
|
const legacyContent = await (0, import_promises18.readFile)(legacyCursorPath, "utf-8");
|
|
13907
|
-
const hasCoreSplit = await
|
|
13659
|
+
const hasCoreSplit = await fileExists2((0, import_node_path23.join)(cwd, ".cursor/rules/viberaven-core.mdc"));
|
|
13908
13660
|
if (!hasCoreSplit || legacyContent.includes("alwaysApply: true")) {
|
|
13909
13661
|
checks.push({
|
|
13910
13662
|
id: "cursor-legacy-mdc",
|
|
@@ -13915,8 +13667,8 @@ async function checkAgentInjection(cwd) {
|
|
|
13915
13667
|
}
|
|
13916
13668
|
for (const target of CORE_AGENT_INJECTION_TARGETS) {
|
|
13917
13669
|
const file = target === "cursor" ? ".cursor/rules/viberaven-core.mdc" : AGENT_RULE_TARGETS[target].file;
|
|
13918
|
-
const
|
|
13919
|
-
const exists = await
|
|
13670
|
+
const path = (0, import_node_path23.join)(cwd, file);
|
|
13671
|
+
const exists = await fileExists2(path);
|
|
13920
13672
|
if (!exists) {
|
|
13921
13673
|
checks.push({
|
|
13922
13674
|
id: `canonical-${target}`,
|
|
@@ -13925,7 +13677,7 @@ async function checkAgentInjection(cwd) {
|
|
|
13925
13677
|
});
|
|
13926
13678
|
continue;
|
|
13927
13679
|
}
|
|
13928
|
-
const content = await (0, import_promises18.readFile)(
|
|
13680
|
+
const content = await (0, import_promises18.readFile)(path, "utf-8");
|
|
13929
13681
|
const hasCommand = content.includes(PUBLIC_AGENT_MODE_COMMAND);
|
|
13930
13682
|
checks.push({
|
|
13931
13683
|
id: `canonical-${target}`,
|
|
@@ -13957,9 +13709,9 @@ function formatDoctorAgentsReport(report) {
|
|
|
13957
13709
|
lines.push(report.ok ? "All agent injection checks passed." : "Agent injection checks failed.");
|
|
13958
13710
|
return lines.join("\n");
|
|
13959
13711
|
}
|
|
13960
|
-
async function
|
|
13712
|
+
async function fileExists2(path) {
|
|
13961
13713
|
try {
|
|
13962
|
-
await (0, import_promises18.access)(
|
|
13714
|
+
await (0, import_promises18.access)(path);
|
|
13963
13715
|
return true;
|
|
13964
13716
|
} catch {
|
|
13965
13717
|
return false;
|
|
@@ -14172,217 +13924,6 @@ async function runAuditCommand(input) {
|
|
|
14172
13924
|
return result.status === "pass" ? 0 : 1;
|
|
14173
13925
|
}
|
|
14174
13926
|
|
|
14175
|
-
// src/commands/badge.ts
|
|
14176
|
-
var import_promises19 = require("node:fs/promises");
|
|
14177
|
-
var import_node_path24 = require("node:path");
|
|
14178
|
-
|
|
14179
|
-
// src/brand/palette.ts
|
|
14180
|
-
var import_picocolors5 = __toESM(require_picocolors());
|
|
14181
|
-
var STATUS_GLYPH = {
|
|
14182
|
-
clear: "\u2713",
|
|
14183
|
-
warning: "\u25C8",
|
|
14184
|
-
blocked: "\u25C6",
|
|
14185
|
-
fixable: "\u25B8",
|
|
14186
|
-
manual: "\u25C7",
|
|
14187
|
-
done: "\u2713",
|
|
14188
|
-
pending: "\xB7"
|
|
14189
|
-
};
|
|
14190
|
-
var DECISION_GLYPH = {
|
|
14191
|
-
CLEAR: STATUS_GLYPH.clear,
|
|
14192
|
-
WARNING: STATUS_GLYPH.warning,
|
|
14193
|
-
BLOCKED: STATUS_GLYPH.blocked
|
|
14194
|
-
};
|
|
14195
|
-
function identity(text) {
|
|
14196
|
-
return text;
|
|
14197
|
-
}
|
|
14198
|
-
function colorsFor(mode) {
|
|
14199
|
-
return import_picocolors5.default.createColors(mode === "rich");
|
|
14200
|
-
}
|
|
14201
|
-
function decisionColor(decision, mode) {
|
|
14202
|
-
const colors = colorsFor(mode);
|
|
14203
|
-
if (decision === "CLEAR") return colors.green;
|
|
14204
|
-
if (decision === "WARNING") return colors.yellow;
|
|
14205
|
-
return colors.red;
|
|
14206
|
-
}
|
|
14207
|
-
function decisionStyle(decision, mode) {
|
|
14208
|
-
const colorize = mode === "rich" ? decisionColor(decision, mode) : identity;
|
|
14209
|
-
return {
|
|
14210
|
-
glyph: DECISION_GLYPH[decision],
|
|
14211
|
-
label: colorize(decision),
|
|
14212
|
-
colorize
|
|
14213
|
-
};
|
|
14214
|
-
}
|
|
14215
|
-
function richOrPlain(text, mode, colorize) {
|
|
14216
|
-
return mode === "rich" ? colorize(text) : text;
|
|
14217
|
-
}
|
|
14218
|
-
var accent = {
|
|
14219
|
-
brand(text, mode) {
|
|
14220
|
-
return richOrPlain(text, mode, colorsFor(mode).cyan);
|
|
14221
|
-
},
|
|
14222
|
-
dim(text, mode) {
|
|
14223
|
-
return richOrPlain(text, mode, colorsFor(mode).dim);
|
|
14224
|
-
},
|
|
14225
|
-
bold(text, mode) {
|
|
14226
|
-
return richOrPlain(text, mode, colorsFor(mode).bold);
|
|
14227
|
-
}
|
|
14228
|
-
};
|
|
14229
|
-
|
|
14230
|
-
// src/brand/ui.ts
|
|
14231
|
-
var import_picocolors6 = __toESM(require_picocolors());
|
|
14232
|
-
var ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
|
|
14233
|
-
function colorsFor2(mode) {
|
|
14234
|
-
return import_picocolors6.default.createColors(mode === "rich");
|
|
14235
|
-
}
|
|
14236
|
-
function stripAnsi(value) {
|
|
14237
|
-
return value.replace(ANSI_PATTERN, "");
|
|
14238
|
-
}
|
|
14239
|
-
function visibleWidth(value) {
|
|
14240
|
-
return stripAnsi(value).length;
|
|
14241
|
-
}
|
|
14242
|
-
function padVisible(value, width) {
|
|
14243
|
-
return `${value}${" ".repeat(Math.max(0, width - visibleWidth(value)))}`;
|
|
14244
|
-
}
|
|
14245
|
-
function safeWidth(width) {
|
|
14246
|
-
if (!Number.isFinite(width)) return 1;
|
|
14247
|
-
return Math.max(1, Math.floor(width));
|
|
14248
|
-
}
|
|
14249
|
-
function clampPercent(percent) {
|
|
14250
|
-
if (Number.isNaN(percent)) return 0;
|
|
14251
|
-
return Math.max(0, Math.min(100, percent));
|
|
14252
|
-
}
|
|
14253
|
-
function colorizeGaugeFill(text, percent, mode) {
|
|
14254
|
-
const colors = colorsFor2(mode);
|
|
14255
|
-
if (mode !== "rich") return text;
|
|
14256
|
-
if (percent >= 80) return colors.green(text);
|
|
14257
|
-
if (percent >= 50) return colors.yellow(text);
|
|
14258
|
-
return colors.red(text);
|
|
14259
|
-
}
|
|
14260
|
-
function rule(width, options) {
|
|
14261
|
-
const line = "\u2500".repeat(safeWidth(width));
|
|
14262
|
-
return accent.dim(line, options.mode);
|
|
14263
|
-
}
|
|
14264
|
-
function box(lines, options) {
|
|
14265
|
-
const content = lines.length > 0 ? lines : [""];
|
|
14266
|
-
const innerWidth = Math.max(1, ...content.map(visibleWidth));
|
|
14267
|
-
const horizontal = "\u2500".repeat(innerWidth + 2);
|
|
14268
|
-
const colors = colorsFor2(options.mode);
|
|
14269
|
-
const border = (value) => options.mode === "rich" ? colors.cyan(value) : value;
|
|
14270
|
-
const body = content.map((line) => border("\u2502") + ` ${padVisible(line, innerWidth)} ` + border("\u2502"));
|
|
14271
|
-
return [border(`\u250C${horizontal}\u2510`), ...body, border(`\u2514${horizontal}\u2518`)].join("\n");
|
|
14272
|
-
}
|
|
14273
|
-
function gauge(percent, options) {
|
|
14274
|
-
const width = safeWidth(options.width);
|
|
14275
|
-
const clamped = clampPercent(percent);
|
|
14276
|
-
const label2 = `${Math.round(clamped)}%`;
|
|
14277
|
-
const filledWidth = Math.round(clamped / 100 * width);
|
|
14278
|
-
const emptyWidth = width - filledWidth;
|
|
14279
|
-
const filled = colorizeGaugeFill("\u2588".repeat(filledWidth), clamped, options.mode);
|
|
14280
|
-
const empty = accent.dim("\u2591".repeat(emptyWidth), options.mode);
|
|
14281
|
-
return `[${filled}${empty}] ${label2}`;
|
|
14282
|
-
}
|
|
14283
|
-
function banner(decision, options) {
|
|
14284
|
-
const style = decisionStyle(decision, options.mode);
|
|
14285
|
-
return `${style.glyph} ${style.label}`;
|
|
14286
|
-
}
|
|
14287
|
-
|
|
14288
|
-
// src/brand/wordmark.ts
|
|
14289
|
-
var DEFAULT_TAGLINE = "production operator for AI-built apps";
|
|
14290
|
-
function wordmark(options) {
|
|
14291
|
-
const tagline = options.tagline ?? DEFAULT_TAGLINE;
|
|
14292
|
-
const brand = accent.brand("VibeRaven", options.mode);
|
|
14293
|
-
const mutedTagline = accent.dim(tagline, options.mode);
|
|
14294
|
-
if (options.variant === "compact") {
|
|
14295
|
-
return `${brand} - ${mutedTagline}`;
|
|
14296
|
-
}
|
|
14297
|
-
return [accent.bold(brand, options.mode), mutedTagline].join("\n");
|
|
14298
|
-
}
|
|
14299
|
-
|
|
14300
|
-
// src/badge/renderBadge.ts
|
|
14301
|
-
var COLORS = {
|
|
14302
|
-
CLEAR: "#2ea44f",
|
|
14303
|
-
WARNING: "#dbab09",
|
|
14304
|
-
BLOCKED: "#d1242f"
|
|
14305
|
-
};
|
|
14306
|
-
function label(prp) {
|
|
14307
|
-
return prp.decision.toLowerCase();
|
|
14308
|
-
}
|
|
14309
|
-
function renderBadgeSvg(prp) {
|
|
14310
|
-
const text = label(prp);
|
|
14311
|
-
const color = COLORS[prp.decision];
|
|
14312
|
-
const width = 132 + text.length * 7;
|
|
14313
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="20" role="img" aria-label="VibeRaven: ${text}">
|
|
14314
|
-
<rect width="${width}" height="20" fill="#24292f"/>
|
|
14315
|
-
<rect x="86" width="${width - 86}" height="20" fill="${color}"/>
|
|
14316
|
-
<g fill="#fff" font-family="Verdana,Geneva,sans-serif" font-size="11">
|
|
14317
|
-
<text x="8" y="14">VibeRaven</text>
|
|
14318
|
-
<text x="94" y="14">${text}</text>
|
|
14319
|
-
</g>
|
|
14320
|
-
</svg>`;
|
|
14321
|
-
}
|
|
14322
|
-
function renderBadgeMarkdown(prp) {
|
|
14323
|
-
const text = label(prp);
|
|
14324
|
-
const date = prp.generatedAt.slice(0, 10);
|
|
14325
|
-
return `[](https://viberaven.dev/production-protocol.md?utm_source=badge) \`gate: ${text} - ${date}\``;
|
|
14326
|
-
}
|
|
14327
|
-
function renderBadgeCard(prp, mode) {
|
|
14328
|
-
const text = label(prp);
|
|
14329
|
-
const date = prp.generatedAt.slice(0, 10);
|
|
14330
|
-
const style = decisionStyle(prp.decision, mode);
|
|
14331
|
-
return box(
|
|
14332
|
-
[
|
|
14333
|
-
wordmark({ variant: "compact", mode, tagline: "launch gate proof" }),
|
|
14334
|
-
`${accent.dim("gate", mode)}: ${style.colorize(text)}`,
|
|
14335
|
-
`${accent.dim("date", mode)}: ${date}`,
|
|
14336
|
-
accent.brand("viberaven.dev", mode)
|
|
14337
|
-
],
|
|
14338
|
-
{ mode }
|
|
14339
|
-
);
|
|
14340
|
-
}
|
|
14341
|
-
|
|
14342
|
-
// src/brand/renderMode.ts
|
|
14343
|
-
function resolveRenderMode(input) {
|
|
14344
|
-
if (input.env.VIBERAVEN_FORCE_RICH === "1") return "rich";
|
|
14345
|
-
if (input.env.VIBERAVEN_PLAIN === "1") return "plain";
|
|
14346
|
-
if (input.env.NO_COLOR !== void 0 && input.env.NO_COLOR !== "") return "plain";
|
|
14347
|
-
if (input.surface === "agent-mode") return "plain";
|
|
14348
|
-
return input.isTTY ? "rich" : "plain";
|
|
14349
|
-
}
|
|
14350
|
-
function currentRenderMode(surface) {
|
|
14351
|
-
return resolveRenderMode({
|
|
14352
|
-
surface,
|
|
14353
|
-
isTTY: Boolean(process.stdout.isTTY),
|
|
14354
|
-
env: process.env
|
|
14355
|
-
});
|
|
14356
|
-
}
|
|
14357
|
-
|
|
14358
|
-
// src/commands/badge.ts
|
|
14359
|
-
async function runBadgeCommand(options) {
|
|
14360
|
-
const log = options.log ?? ((message) => console.log(message));
|
|
14361
|
-
const artifactDir = (0, import_node_path24.join)(options.cwd, ".viberaven");
|
|
14362
|
-
const prpPath = (0, import_node_path24.join)(artifactDir, "prp.json");
|
|
14363
|
-
let prp;
|
|
14364
|
-
try {
|
|
14365
|
-
prp = JSON.parse(await (0, import_promises19.readFile)(prpPath, "utf8"));
|
|
14366
|
-
} catch {
|
|
14367
|
-
log("No .viberaven/prp.json found. Run: npx -y viberaven --agent-mode");
|
|
14368
|
-
return 1;
|
|
14369
|
-
}
|
|
14370
|
-
if (options.svg) {
|
|
14371
|
-
const outputPath = (0, import_node_path24.join)(artifactDir, "badge.svg");
|
|
14372
|
-
await (0, import_promises19.mkdir)(artifactDir, { recursive: true });
|
|
14373
|
-
await (0, import_promises19.writeFile)(outputPath, renderBadgeSvg(prp), "utf8");
|
|
14374
|
-
log(`Wrote ${outputPath}`);
|
|
14375
|
-
return 0;
|
|
14376
|
-
}
|
|
14377
|
-
if (options.card) {
|
|
14378
|
-
log(renderBadgeCard(prp, currentRenderMode("human-command")));
|
|
14379
|
-
return 0;
|
|
14380
|
-
}
|
|
14381
|
-
log("Add this to your README:");
|
|
14382
|
-
log(renderBadgeMarkdown(prp));
|
|
14383
|
-
return 0;
|
|
14384
|
-
}
|
|
14385
|
-
|
|
14386
13927
|
// src/output/nextActionBlock.ts
|
|
14387
13928
|
function buildNextActionBlock(tasks, loopState, plan) {
|
|
14388
13929
|
const batchSize = plan === "pro" ? 10 : 3;
|
|
@@ -14397,7 +13938,11 @@ function buildNextActionBlock(tasks, loopState, plan) {
|
|
|
14397
13938
|
return {
|
|
14398
13939
|
...base,
|
|
14399
13940
|
type: "stalled",
|
|
14400
|
-
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
|
+
],
|
|
14401
13946
|
requiresUserAction: true,
|
|
14402
13947
|
stallReason
|
|
14403
13948
|
};
|
|
@@ -14406,42 +13951,67 @@ function buildNextActionBlock(tasks, loopState, plan) {
|
|
|
14406
13951
|
return {
|
|
14407
13952
|
...base,
|
|
14408
13953
|
type: "verify",
|
|
14409
|
-
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
|
+
],
|
|
14410
13960
|
mcpTool: "viberaven_verify",
|
|
14411
13961
|
mcpArgs: {},
|
|
14412
13962
|
requiresUserAction: false
|
|
14413
13963
|
};
|
|
14414
13964
|
}
|
|
14415
|
-
const repoCodeTask = tasks.find((
|
|
13965
|
+
const repoCodeTask = tasks.find((task) => task.fixType === "repo-code" && !task.requiresUserAction);
|
|
14416
13966
|
if (repoCodeTask) {
|
|
13967
|
+
const fallbackCommand = repoCodeTask.mcpArgs ? `npx -y viberaven --heal --apply --gap ${repoCodeTask.gapId} --yes` : void 0;
|
|
14417
13968
|
return {
|
|
14418
13969
|
...base,
|
|
14419
13970
|
type: "repo-code",
|
|
14420
13971
|
gapId: repoCodeTask.gapId,
|
|
14421
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
|
+
],
|
|
14422
13982
|
mcpTool: repoCodeTask.mcpTool,
|
|
14423
13983
|
mcpArgs: repoCodeTask.mcpArgs,
|
|
14424
|
-
fallbackCommand
|
|
13984
|
+
fallbackCommand,
|
|
14425
13985
|
requiresUserAction: false
|
|
14426
13986
|
};
|
|
14427
13987
|
}
|
|
14428
|
-
const providerTask = tasks.find((
|
|
13988
|
+
const providerTask = tasks.find((task) => task.fixType === "provider-action");
|
|
14429
13989
|
if (providerTask) {
|
|
14430
13990
|
return {
|
|
14431
13991
|
...base,
|
|
14432
13992
|
type: "provider-action",
|
|
14433
13993
|
gapId: providerTask.gapId,
|
|
14434
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
|
+
],
|
|
14435
14000
|
requiresUserAction: true
|
|
14436
14001
|
};
|
|
14437
14002
|
}
|
|
14438
|
-
const upgradeTask = tasks.find((
|
|
14003
|
+
const upgradeTask = tasks.find((task) => task.fixType === "upgrade-required");
|
|
14439
14004
|
if (upgradeTask) {
|
|
14440
14005
|
return {
|
|
14441
14006
|
...base,
|
|
14442
14007
|
type: "upgrade-required",
|
|
14443
14008
|
gapId: upgradeTask.gapId,
|
|
14444
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
|
+
],
|
|
14445
14015
|
requiresUserAction: true,
|
|
14446
14016
|
upgradeUrl: "https://viberaven.dev/pricing"
|
|
14447
14017
|
};
|
|
@@ -14449,15 +14019,20 @@ function buildNextActionBlock(tasks, loopState, plan) {
|
|
|
14449
14019
|
return {
|
|
14450
14020
|
...base,
|
|
14451
14021
|
type: "done",
|
|
14452
|
-
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
|
+
],
|
|
14453
14028
|
requiresUserAction: false
|
|
14454
14029
|
};
|
|
14455
14030
|
}
|
|
14456
14031
|
function resolveStallReason(tasks) {
|
|
14457
14032
|
if (tasks.length === 0) return "no-recipes";
|
|
14458
|
-
const allUpgradeOrEmpty = tasks.every((
|
|
14033
|
+
const allUpgradeOrEmpty = tasks.every((task) => task.fixType === "upgrade-required");
|
|
14459
14034
|
if (allUpgradeOrEmpty) return "no-recipes";
|
|
14460
|
-
const allProviderAction = tasks.every((
|
|
14035
|
+
const allProviderAction = tasks.every((task) => task.fixType === "provider-action");
|
|
14461
14036
|
if (allProviderAction) return "provider-action-required";
|
|
14462
14037
|
return "unknown";
|
|
14463
14038
|
}
|
|
@@ -14477,6 +14052,7 @@ function buildProviderActionBlock(task) {
|
|
|
14477
14052
|
dashboardUrl: pa.dashboardUrl,
|
|
14478
14053
|
exactStep: pa.exactStep,
|
|
14479
14054
|
envKeyName: pa.envKeyName ?? null,
|
|
14055
|
+
envKeyExample: pa.envKeyExample ?? null,
|
|
14480
14056
|
doneSignal: pa.doneSignal,
|
|
14481
14057
|
verifyCommand: task.verifyCommand,
|
|
14482
14058
|
mcpAlternative: task.mcpTool ?? pa.mcpAlternative ?? null
|
|
@@ -14485,7 +14061,7 @@ function buildProviderActionBlock(task) {
|
|
|
14485
14061
|
}
|
|
14486
14062
|
function printProviderActionBlock(tasks) {
|
|
14487
14063
|
const task = tasks.find(
|
|
14488
|
-
(
|
|
14064
|
+
(entry) => entry.fixType === "provider-action" && entry.requiresUserAction === true
|
|
14489
14065
|
);
|
|
14490
14066
|
if (!task) return;
|
|
14491
14067
|
const block = buildProviderActionBlock(task);
|
|
@@ -14503,7 +14079,7 @@ function printNextActionBlock(block) {
|
|
|
14503
14079
|
// src/providerMcpBridge.ts
|
|
14504
14080
|
var import_node_fs12 = require("node:fs");
|
|
14505
14081
|
var import_node_os2 = require("node:os");
|
|
14506
|
-
var
|
|
14082
|
+
var import_node_path24 = require("node:path");
|
|
14507
14083
|
var UPGRADE_URL4 = "https://viberaven.dev/pricing";
|
|
14508
14084
|
var FALLBACK_COMMAND = "npx -y viberaven audit --vercel-supabase --json";
|
|
14509
14085
|
var SUPPORTED_PROVIDERS = /* @__PURE__ */ new Set(["supabase", "vercel"]);
|
|
@@ -14511,9 +14087,9 @@ var configPathsOverride;
|
|
|
14511
14087
|
function defaultMcpConfigPaths() {
|
|
14512
14088
|
const home = (0, import_node_os2.homedir)();
|
|
14513
14089
|
return [
|
|
14514
|
-
(0,
|
|
14515
|
-
(0,
|
|
14516
|
-
(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")
|
|
14517
14093
|
];
|
|
14518
14094
|
}
|
|
14519
14095
|
function resolveConfigPaths() {
|
|
@@ -14541,12 +14117,12 @@ function findServerEntry(servers, provider2) {
|
|
|
14541
14117
|
}
|
|
14542
14118
|
function findProviderMcpConfig(provider2, configPaths) {
|
|
14543
14119
|
const paths = configPaths ?? resolveConfigPaths();
|
|
14544
|
-
for (const
|
|
14545
|
-
if (!(0, import_node_fs12.existsSync)(
|
|
14120
|
+
for (const path of paths) {
|
|
14121
|
+
if (!(0, import_node_fs12.existsSync)(path)) {
|
|
14546
14122
|
continue;
|
|
14547
14123
|
}
|
|
14548
14124
|
try {
|
|
14549
|
-
const raw = JSON.parse((0, import_node_fs12.readFileSync)(
|
|
14125
|
+
const raw = JSON.parse((0, import_node_fs12.readFileSync)(path, "utf8"));
|
|
14550
14126
|
const servers = parseMcpServers(raw);
|
|
14551
14127
|
if (!servers) {
|
|
14552
14128
|
continue;
|
|
@@ -14560,7 +14136,7 @@ function findProviderMcpConfig(provider2, configPaths) {
|
|
|
14560
14136
|
command: typeof server.command === "string" ? server.command : void 0,
|
|
14561
14137
|
args: Array.isArray(server.args) ? server.args.filter((arg) => typeof arg === "string") : void 0,
|
|
14562
14138
|
url: typeof server.url === "string" ? server.url : void 0,
|
|
14563
|
-
source:
|
|
14139
|
+
source: path
|
|
14564
14140
|
};
|
|
14565
14141
|
} catch {
|
|
14566
14142
|
continue;
|
|
@@ -14568,19 +14144,6 @@ function findProviderMcpConfig(provider2, configPaths) {
|
|
|
14568
14144
|
}
|
|
14569
14145
|
return void 0;
|
|
14570
14146
|
}
|
|
14571
|
-
function hasUsableProviderMcpConfig(config) {
|
|
14572
|
-
return Boolean(config.command?.trim() || config.url?.trim());
|
|
14573
|
-
}
|
|
14574
|
-
function findUsableProviderMcpConfig(provider2, configPaths) {
|
|
14575
|
-
const paths = configPaths ?? resolveConfigPaths();
|
|
14576
|
-
for (const path3 of paths) {
|
|
14577
|
-
const config = findProviderMcpConfig(provider2, [path3]);
|
|
14578
|
-
if (config && hasUsableProviderMcpConfig(config)) {
|
|
14579
|
-
return config;
|
|
14580
|
-
}
|
|
14581
|
-
}
|
|
14582
|
-
return void 0;
|
|
14583
|
-
}
|
|
14584
14147
|
async function verifyProviderGap(options) {
|
|
14585
14148
|
if (options.plan !== "pro") {
|
|
14586
14149
|
return {
|
|
@@ -14611,581 +14174,6 @@ async function verifyProviderGap(options) {
|
|
|
14611
14174
|
};
|
|
14612
14175
|
}
|
|
14613
14176
|
|
|
14614
|
-
// src/connectedTools.ts
|
|
14615
|
-
var DETECTED_PROVIDERS = ["supabase", "vercel", "stripe", "github"];
|
|
14616
|
-
var INSTALL_HINTS = {
|
|
14617
|
-
supabase: "claude mcp add --transport http supabase https://mcp.supabase.com/",
|
|
14618
|
-
vercel: "claude mcp add --transport http vercel https://mcp.vercel.com/",
|
|
14619
|
-
stripe: "claude mcp add --transport http stripe https://mcp.stripe.com/",
|
|
14620
|
-
github: "claude mcp add --transport http github https://api.githubcopilot.com/mcp/"
|
|
14621
|
-
};
|
|
14622
|
-
var READONLY_PROVIDERS = /* @__PURE__ */ new Set(["vercel"]);
|
|
14623
|
-
function isDetectedProvider(provider2) {
|
|
14624
|
-
return DETECTED_PROVIDERS.includes(provider2);
|
|
14625
|
-
}
|
|
14626
|
-
function installHintFor(provider2) {
|
|
14627
|
-
const normalizedProvider = provider2.toLowerCase().trim();
|
|
14628
|
-
if (isDetectedProvider(normalizedProvider)) {
|
|
14629
|
-
return INSTALL_HINTS[normalizedProvider];
|
|
14630
|
-
}
|
|
14631
|
-
return `Add the ${provider2} MCP server to your agent config.`;
|
|
14632
|
-
}
|
|
14633
|
-
function detectConnectedTools() {
|
|
14634
|
-
const tools = {};
|
|
14635
|
-
for (const provider2 of DETECTED_PROVIDERS) {
|
|
14636
|
-
const config = findUsableProviderMcpConfig(provider2);
|
|
14637
|
-
if (!config) {
|
|
14638
|
-
tools[`${provider2}Mcp`] = "missing";
|
|
14639
|
-
continue;
|
|
14640
|
-
}
|
|
14641
|
-
tools[`${provider2}Mcp`] = READONLY_PROVIDERS.has(provider2) ? "available_readonly" : "available";
|
|
14642
|
-
}
|
|
14643
|
-
tools.browser = "available";
|
|
14644
|
-
return tools;
|
|
14645
|
-
}
|
|
14646
|
-
|
|
14647
|
-
// src/output/actionPanelBlock.ts
|
|
14648
|
-
var ACTION_PANEL_START = "VIBERAVEN_ACTION_PANEL_START";
|
|
14649
|
-
var ACTION_PANEL_END = "VIBERAVEN_ACTION_PANEL_END";
|
|
14650
|
-
function stackLine(prp) {
|
|
14651
|
-
const parts = Array.from(
|
|
14652
|
-
/* @__PURE__ */ new Set([
|
|
14653
|
-
...prp.detectedStack.deployment,
|
|
14654
|
-
...prp.detectedStack.database,
|
|
14655
|
-
...prp.detectedStack.auth,
|
|
14656
|
-
...prp.detectedStack.payments,
|
|
14657
|
-
...prp.detectedStack.monitoring
|
|
14658
|
-
])
|
|
14659
|
-
);
|
|
14660
|
-
return parts.length > 0 ? parts.join(" + ") : prp.detectedStack.archetype ?? "unknown";
|
|
14661
|
-
}
|
|
14662
|
-
function envVarNames(tasks) {
|
|
14663
|
-
return Array.from(
|
|
14664
|
-
new Set(
|
|
14665
|
-
tasks.map((task) => task.providerAction?.envKeyName).filter((name) => Boolean(name))
|
|
14666
|
-
)
|
|
14667
|
-
);
|
|
14668
|
-
}
|
|
14669
|
-
function humanStep(task) {
|
|
14670
|
-
if (task.fixType === "provider-action") {
|
|
14671
|
-
return task.providerAction?.exactStep ?? task.title;
|
|
14672
|
-
}
|
|
14673
|
-
if (task.fixType === "upgrade-required") {
|
|
14674
|
-
return `Upgrade required: ${task.action ?? task.exactFix ?? task.title}`;
|
|
14675
|
-
}
|
|
14676
|
-
if (task.fixType === "manual-verify") {
|
|
14677
|
-
return `Manual verification required: ${task.action ?? task.exactFix ?? task.title}`;
|
|
14678
|
-
}
|
|
14679
|
-
return task.title;
|
|
14680
|
-
}
|
|
14681
|
-
function repoTaskTitle(task) {
|
|
14682
|
-
return task.action ?? task.exactFix ?? task.title;
|
|
14683
|
-
}
|
|
14684
|
-
function parseDecision(value) {
|
|
14685
|
-
if (value === "CLEAR" || value === "WARNING" || value === "BLOCKED") return value;
|
|
14686
|
-
return "BLOCKED";
|
|
14687
|
-
}
|
|
14688
|
-
function providerFromConnectedToolKey(tool) {
|
|
14689
|
-
if (!tool.endsWith("Mcp")) return void 0;
|
|
14690
|
-
return tool.slice(0, -3).toLowerCase();
|
|
14691
|
-
}
|
|
14692
|
-
function normalizeProvider3(provider2) {
|
|
14693
|
-
const lower = provider2.trim().toLowerCase();
|
|
14694
|
-
const tokens = lower.split(/[^a-z0-9]+/).filter(Boolean);
|
|
14695
|
-
for (const knownProvider of ["supabase", "stripe", "vercel", "github"]) {
|
|
14696
|
-
if (lower === knownProvider || tokens.includes(knownProvider)) {
|
|
14697
|
-
return knownProvider;
|
|
14698
|
-
}
|
|
14699
|
-
}
|
|
14700
|
-
return lower;
|
|
14701
|
-
}
|
|
14702
|
-
function toolSetupFor(prp) {
|
|
14703
|
-
const relevantProviders = new Set([
|
|
14704
|
-
...prp.detectedStack.auth,
|
|
14705
|
-
...prp.detectedStack.database,
|
|
14706
|
-
...prp.detectedStack.deployment,
|
|
14707
|
-
...prp.detectedStack.payments
|
|
14708
|
-
].map(normalizeProvider3));
|
|
14709
|
-
return Object.entries(prp.connectedTools).flatMap(([tool, state]) => {
|
|
14710
|
-
const provider2 = providerFromConnectedToolKey(tool);
|
|
14711
|
-
if (!provider2 || state !== "missing" || !relevantProviders.has(provider2)) {
|
|
14712
|
-
return [];
|
|
14713
|
-
}
|
|
14714
|
-
return [provider2];
|
|
14715
|
-
}).map((provider2) => ({
|
|
14716
|
-
provider: provider2,
|
|
14717
|
-
command: installHintFor(provider2),
|
|
14718
|
-
reason: "Connect this provider MCP so the agent can verify live provider state when available."
|
|
14719
|
-
}));
|
|
14720
|
-
}
|
|
14721
|
-
function checklistFor(repoTasks, humanTasks) {
|
|
14722
|
-
return [
|
|
14723
|
-
{ label: "Understand the stack and launch gaps", done: true, kind: "scan" },
|
|
14724
|
-
...repoTasks.slice(0, 3).map((task) => ({
|
|
14725
|
-
label: repoTaskTitle(task),
|
|
14726
|
-
done: false,
|
|
14727
|
-
kind: "repo-code"
|
|
14728
|
-
})),
|
|
14729
|
-
...humanTasks.slice(0, 3).map((task) => ({
|
|
14730
|
-
label: humanStep(task),
|
|
14731
|
-
done: false,
|
|
14732
|
-
kind: "provider"
|
|
14733
|
-
})),
|
|
14734
|
-
{ label: "Verify again before launch", done: false, kind: "verify" }
|
|
14735
|
-
];
|
|
14736
|
-
}
|
|
14737
|
-
function mermaidFor(prp, tasks) {
|
|
14738
|
-
const status = prp.decision === "CLEAR" ? "Clear" : prp.decision === "WARNING" ? "Review" : "Blocked";
|
|
14739
|
-
const hasProvider = tasks.some((task) => task.fixType === "provider-action");
|
|
14740
|
-
const hasRepo = tasks.some((task) => task.fixType === "repo-code" && !task.requiresUserAction);
|
|
14741
|
-
const lines = [
|
|
14742
|
-
"flowchart LR",
|
|
14743
|
-
` app["AI-built app"] --> stack["${stackLine(prp)}"]`,
|
|
14744
|
-
` stack --> gate["VibeRaven: ${status}"]`
|
|
14745
|
-
];
|
|
14746
|
-
if (hasRepo) lines.push(' gate --> repo["Repo fixes"]');
|
|
14747
|
-
if (hasProvider) lines.push(' gate --> provider["Provider setup"]');
|
|
14748
|
-
lines.push(' gate --> verify["Verify before launch"]');
|
|
14749
|
-
return lines.join("\n");
|
|
14750
|
-
}
|
|
14751
|
-
function buildActionPanelBlock(prp, tasks) {
|
|
14752
|
-
const repoTasks = tasks.filter((task) => task.fixType === "repo-code" && !task.requiresUserAction);
|
|
14753
|
-
const humanTasks = tasks.filter((task) => task.fixType !== "repo-code" || task.requiresUserAction);
|
|
14754
|
-
const nextRepoTask = repoTasks[0];
|
|
14755
|
-
const toolSetup = toolSetupFor(prp);
|
|
14756
|
-
const statusLine = [
|
|
14757
|
-
prp.decision,
|
|
14758
|
-
stackLine(prp),
|
|
14759
|
-
`${tasks.length} launch gap${tasks.length === 1 ? "" : "s"}`
|
|
14760
|
-
].join(" | ");
|
|
14761
|
-
return {
|
|
14762
|
-
VIBERAVEN_ACTION_PANEL: {
|
|
14763
|
-
surface: "native-agent-chat",
|
|
14764
|
-
title: "VibeRaven",
|
|
14765
|
-
statusLine,
|
|
14766
|
-
primaryCommand: nextRepoTask ? `npx -y viberaven --heal --apply --gap ${nextRepoTask.gapId} --yes` : "npx -y viberaven next --json",
|
|
14767
|
-
nextCommand: "npx -y viberaven next --json",
|
|
14768
|
-
verifyCommand: prp.verifyCommand,
|
|
14769
|
-
checklist: checklistFor(repoTasks, humanTasks),
|
|
14770
|
-
mermaidDiagram: mermaidFor(prp, tasks),
|
|
14771
|
-
agentCanDo: repoTasks.length > 0 ? repoTasks.map(repoTaskTitle) : ["No safe repo-code fix is available yet. I will guide the provider steps first."],
|
|
14772
|
-
userMustDo: humanTasks.map((task) => ({
|
|
14773
|
-
provider: task.providerAction?.provider,
|
|
14774
|
-
dashboardUrl: task.providerAction?.dashboardUrl,
|
|
14775
|
-
step: humanStep(task),
|
|
14776
|
-
doneWhen: task.providerAction?.doneSignal
|
|
14777
|
-
})),
|
|
14778
|
-
toolSetup,
|
|
14779
|
-
envVarNames: envVarNames(tasks),
|
|
14780
|
-
agentInstruction: "Render this as a short VibeRaven action message in the native chat. Use the checklist as the live execution timeline and update [ ] to [x] as tasks complete. Present toolSetup as optional connection help, not as a blocker for safe repo fixes. Do not describe the output with internal product labels. Keep the user inside Codex, Claude Code, Cursor, or the terminal. Do not open report.html unless the user explicitly asks for the legacy report."
|
|
14781
|
-
}
|
|
14782
|
-
};
|
|
14783
|
-
}
|
|
14784
|
-
function nextHumanStep(panel) {
|
|
14785
|
-
const hasRepoFix = panel.agentCanDo.some((item3) => !item3.startsWith("No safe repo-code fix"));
|
|
14786
|
-
if (hasRepoFix) {
|
|
14787
|
-
return "Next: I will apply the first safe repo fix, then verify the launch state.";
|
|
14788
|
-
}
|
|
14789
|
-
if (panel.userMustDo.length > 0) {
|
|
14790
|
-
return "Next: I will guide the blocked provider setup, then verify when you confirm it is done.";
|
|
14791
|
-
}
|
|
14792
|
-
return "Next: I will verify the launch state before deploy.";
|
|
14793
|
-
}
|
|
14794
|
-
function buildActionPanelCard(block, mode = "plain") {
|
|
14795
|
-
const panel = block.VIBERAVEN_ACTION_PANEL;
|
|
14796
|
-
const agentCanDo = panel.agentCanDo.slice(0, 3);
|
|
14797
|
-
const userMustDo = panel.userMustDo.slice(0, 3);
|
|
14798
|
-
const lines = [];
|
|
14799
|
-
const statusParts = panel.statusLine.split(/\s*\|\s*/);
|
|
14800
|
-
const decision = parseDecision(statusParts[0] ?? "BLOCKED");
|
|
14801
|
-
const rest = statusParts.slice(1).join(" | ");
|
|
14802
|
-
lines.push(accent.bold("VibeRaven", mode));
|
|
14803
|
-
lines.push("Taking this app from demo to production.");
|
|
14804
|
-
lines.push("");
|
|
14805
|
-
lines.push(`Status: ${banner(decision, { mode })}${rest ? ` | ${rest}` : ""}`);
|
|
14806
|
-
lines.push("");
|
|
14807
|
-
lines.push("Progress:");
|
|
14808
|
-
panel.checklist.slice(0, 6).forEach((item3) => {
|
|
14809
|
-
lines.push(` - [${item3.done ? "x" : " "}] ${item3.label}`);
|
|
14810
|
-
});
|
|
14811
|
-
lines.push("");
|
|
14812
|
-
lines.push("I can do now:");
|
|
14813
|
-
agentCanDo.forEach((item3, index) => {
|
|
14814
|
-
lines.push(` ${index + 1}. ${item3}`);
|
|
14815
|
-
});
|
|
14816
|
-
lines.push("");
|
|
14817
|
-
lines.push("I need you for:");
|
|
14818
|
-
if (userMustDo.length === 0) {
|
|
14819
|
-
lines.push(" (none)");
|
|
14820
|
-
} else {
|
|
14821
|
-
userMustDo.forEach((item3, index) => {
|
|
14822
|
-
const provider2 = item3.provider ? `${item3.provider}: ` : "";
|
|
14823
|
-
lines.push(` ${index + 1}. ${provider2}${item3.step}`);
|
|
14824
|
-
});
|
|
14825
|
-
}
|
|
14826
|
-
const links = userMustDo.filter((item3) => item3.dashboardUrl);
|
|
14827
|
-
if (links.length > 0) {
|
|
14828
|
-
lines.push("");
|
|
14829
|
-
lines.push("Open:");
|
|
14830
|
-
links.forEach((item3) => {
|
|
14831
|
-
const label2 = item3.provider ?? "provider";
|
|
14832
|
-
lines.push(` ${label2}: ${item3.dashboardUrl}`);
|
|
14833
|
-
});
|
|
14834
|
-
}
|
|
14835
|
-
if (panel.envVarNames.length > 0) {
|
|
14836
|
-
lines.push("");
|
|
14837
|
-
lines.push(`Copy env names: ${panel.envVarNames.join(", ")}`);
|
|
14838
|
-
}
|
|
14839
|
-
const toolSetup = panel.toolSetup.slice(0, 3);
|
|
14840
|
-
if (toolSetup.length > 0) {
|
|
14841
|
-
lines.push("");
|
|
14842
|
-
lines.push("Connect tools:");
|
|
14843
|
-
toolSetup.forEach((item3) => {
|
|
14844
|
-
lines.push(` ${item3.provider}: ${item3.command}`);
|
|
14845
|
-
});
|
|
14846
|
-
}
|
|
14847
|
-
lines.push("");
|
|
14848
|
-
lines.push(nextHumanStep(panel));
|
|
14849
|
-
lines.push("Verify: I will re-check after the repo fix or provider step.");
|
|
14850
|
-
return lines.join("\n");
|
|
14851
|
-
}
|
|
14852
|
-
function printActionPanelBlock(prp, tasks, mode = "plain") {
|
|
14853
|
-
const block = buildActionPanelBlock(prp, tasks);
|
|
14854
|
-
console.log(buildActionPanelCard(block, mode));
|
|
14855
|
-
console.log(ACTION_PANEL_START);
|
|
14856
|
-
console.log(JSON.stringify(block, null, 2));
|
|
14857
|
-
console.log(ACTION_PANEL_END);
|
|
14858
|
-
}
|
|
14859
|
-
|
|
14860
|
-
// src/output/celebration.ts
|
|
14861
|
-
function renderClearCelebration(prp, mode) {
|
|
14862
|
-
if (prp.decision !== "CLEAR") return "";
|
|
14863
|
-
const verifiedDate = prp.generatedAt.slice(0, 10);
|
|
14864
|
-
const style = decisionStyle("CLEAR", mode);
|
|
14865
|
-
const lines = [
|
|
14866
|
-
`${style.glyph} ${style.label} production gate is clear`,
|
|
14867
|
-
accent.dim(`Verified ${verifiedDate}`, mode),
|
|
14868
|
-
"",
|
|
14869
|
-
`Share your launch proof: ${accent.bold("npx -y viberaven badge", mode)}`
|
|
14870
|
-
];
|
|
14871
|
-
return [wordmark({ variant: "compact", mode }), box(lines, { mode }), rule(40, { mode })].join("\n");
|
|
14872
|
-
}
|
|
14873
|
-
|
|
14874
|
-
// src/output/loopProgress.ts
|
|
14875
|
-
function safeCount(value) {
|
|
14876
|
-
if (!Number.isFinite(value)) return 0;
|
|
14877
|
-
return Math.max(0, Math.floor(value));
|
|
14878
|
-
}
|
|
14879
|
-
function renderLoopProgress(input, mode) {
|
|
14880
|
-
if (mode === "plain" && input.silentInPlain) return "";
|
|
14881
|
-
const total = safeCount(input.total);
|
|
14882
|
-
const applied = total > 0 ? Math.min(safeCount(input.applied), total) : safeCount(input.applied);
|
|
14883
|
-
const percent = total === 0 ? 100 : applied / total * 100;
|
|
14884
|
-
const label2 = `${STATUS_GLYPH.fixable} ${input.label}`;
|
|
14885
|
-
return `${accent.bold(label2, mode)} ${gauge(percent, { width: 12, mode })} ${applied}/${total}`;
|
|
14886
|
-
}
|
|
14887
|
-
|
|
14888
|
-
// src/demo/runDemo.ts
|
|
14889
|
-
var import_node_fs13 = require("node:fs");
|
|
14890
|
-
var import_node_path27 = __toESM(require("node:path"));
|
|
14891
|
-
|
|
14892
|
-
// src/demo/localRules.ts
|
|
14893
|
-
var import_promises20 = require("node:fs/promises");
|
|
14894
|
-
var import_node_path26 = __toESM(require("node:path"));
|
|
14895
|
-
var DEMO_SCANNED_AT = "2026-06-14T00:00:00.000Z";
|
|
14896
|
-
async function readKnownFile(root, relativePath) {
|
|
14897
|
-
try {
|
|
14898
|
-
return await (0, import_promises20.readFile)(import_node_path26.default.join(root, relativePath), "utf8");
|
|
14899
|
-
} catch (error) {
|
|
14900
|
-
const code = error.code;
|
|
14901
|
-
if (code === "ENOENT") return "";
|
|
14902
|
-
throw error;
|
|
14903
|
-
}
|
|
14904
|
-
}
|
|
14905
|
-
function dependencyNames(packageJson) {
|
|
14906
|
-
if (!packageJson.trim()) return /* @__PURE__ */ new Set();
|
|
14907
|
-
const parsed = JSON.parse(packageJson);
|
|
14908
|
-
return /* @__PURE__ */ new Set([
|
|
14909
|
-
...Object.keys(parsed.dependencies ?? {}),
|
|
14910
|
-
...Object.keys(parsed.devDependencies ?? {})
|
|
14911
|
-
]);
|
|
14912
|
-
}
|
|
14913
|
-
function stripTypeScriptComments(source) {
|
|
14914
|
-
return source.replace(/\/\*[\s\S]*?\*\//g, "").split("\n").map((line) => {
|
|
14915
|
-
let quote = null;
|
|
14916
|
-
let escaped = false;
|
|
14917
|
-
for (let index = 0; index < line.length - 1; index += 1) {
|
|
14918
|
-
const char = line[index];
|
|
14919
|
-
const next = line[index + 1];
|
|
14920
|
-
if (quote) {
|
|
14921
|
-
if (escaped) {
|
|
14922
|
-
escaped = false;
|
|
14923
|
-
} else if (char === "\\") {
|
|
14924
|
-
escaped = true;
|
|
14925
|
-
} else if (char === quote) {
|
|
14926
|
-
quote = null;
|
|
14927
|
-
}
|
|
14928
|
-
continue;
|
|
14929
|
-
}
|
|
14930
|
-
if (char === '"' || char === "'" || char === "`") {
|
|
14931
|
-
quote = char;
|
|
14932
|
-
continue;
|
|
14933
|
-
}
|
|
14934
|
-
if (char === "/" && next === "/") {
|
|
14935
|
-
return line.slice(0, index);
|
|
14936
|
-
}
|
|
14937
|
-
}
|
|
14938
|
-
return line;
|
|
14939
|
-
}).join("\n");
|
|
14940
|
-
}
|
|
14941
|
-
function stripSqlComments(source) {
|
|
14942
|
-
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/--.*$/gm, "");
|
|
14943
|
-
}
|
|
14944
|
-
function hasStripeWebhookSignatureVerification(source) {
|
|
14945
|
-
const executable = stripTypeScriptComments(source);
|
|
14946
|
-
return /webhooks\s*\.\s*constructEvent\s*\(/.test(executable) && /Stripe-Signature/.test(executable);
|
|
14947
|
-
}
|
|
14948
|
-
function hasEnabledRowLevelSecurity(source) {
|
|
14949
|
-
return /enable\s+row\s+level\s+security/i.test(stripSqlComments(source));
|
|
14950
|
-
}
|
|
14951
|
-
function hasEnvAssignment(source, name) {
|
|
14952
|
-
const pattern = new RegExp(`^\\s*${name}\\s*=`, "m");
|
|
14953
|
-
return pattern.test(source);
|
|
14954
|
-
}
|
|
14955
|
-
function gap(input) {
|
|
14956
|
-
return {
|
|
14957
|
-
...input,
|
|
14958
|
-
toolSuggestions: [],
|
|
14959
|
-
mcpSuggestion: null,
|
|
14960
|
-
affectedMapCategories: input.affectedMapCategories ?? []
|
|
14961
|
-
};
|
|
14962
|
-
}
|
|
14963
|
-
function emptyMissionGraph() {
|
|
14964
|
-
return {
|
|
14965
|
-
areas: [],
|
|
14966
|
-
byArea: {},
|
|
14967
|
-
byProvider: {},
|
|
14968
|
-
repositoryEvidence: {
|
|
14969
|
-
env: [],
|
|
14970
|
-
security: []
|
|
14971
|
-
}
|
|
14972
|
-
};
|
|
14973
|
-
}
|
|
14974
|
-
async function scanDemoFixture(root) {
|
|
14975
|
-
const [packageJson, stripeWebhook, migration, envExample] = await Promise.all([
|
|
14976
|
-
readKnownFile(root, "package.json"),
|
|
14977
|
-
readKnownFile(root, "app/api/stripe/webhook/route.ts"),
|
|
14978
|
-
readKnownFile(root, "supabase/migrations/0001_init.sql"),
|
|
14979
|
-
readKnownFile(root, ".env.example")
|
|
14980
|
-
]);
|
|
14981
|
-
const dependencies = dependencyNames(packageJson);
|
|
14982
|
-
const hasNext = dependencies.has("next");
|
|
14983
|
-
const hasSupabase = dependencies.has("@supabase/supabase-js");
|
|
14984
|
-
const hasStripe = dependencies.has("stripe");
|
|
14985
|
-
const selectedProviders = {};
|
|
14986
|
-
if (hasNext) selectedProviders.deployment = "vercel";
|
|
14987
|
-
if (hasSupabase) {
|
|
14988
|
-
selectedProviders.database = "supabase";
|
|
14989
|
-
selectedProviders.auth = "supabase";
|
|
14990
|
-
}
|
|
14991
|
-
if (hasStripe) selectedProviders.payments = "stripe";
|
|
14992
|
-
const gaps = [];
|
|
14993
|
-
if (hasStripe && !hasStripeWebhookSignatureVerification(stripeWebhook)) {
|
|
14994
|
-
gaps.push(
|
|
14995
|
-
gap({
|
|
14996
|
-
id: "stripe_webhook_signature_missing",
|
|
14997
|
-
category: "SECURITY & AUTH",
|
|
14998
|
-
severity: "critical",
|
|
14999
|
-
title: "Verify Stripe webhook signatures",
|
|
15000
|
-
detail: "The demo Stripe webhook handler does not verify the Stripe-Signature header with stripe.webhooks.constructEvent.",
|
|
15001
|
-
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.",
|
|
15002
|
-
primaryMapCategory: "payments",
|
|
15003
|
-
affectedMapCategories: ["security"]
|
|
15004
|
-
})
|
|
15005
|
-
);
|
|
15006
|
-
}
|
|
15007
|
-
if (hasSupabase && !hasEnabledRowLevelSecurity(migration)) {
|
|
15008
|
-
gaps.push(
|
|
15009
|
-
gap({
|
|
15010
|
-
id: "rls_disabled",
|
|
15011
|
-
category: "DATABASE & DATA",
|
|
15012
|
-
severity: "critical",
|
|
15013
|
-
title: "Enable Supabase row level security",
|
|
15014
|
-
detail: "The demo migration creates application data without enabling row level security.",
|
|
15015
|
-
copyPrompt: "Add alter table statements that enable row level security and add least-privilege policies for the Supabase tables in the demo migration.",
|
|
15016
|
-
primaryMapCategory: "database",
|
|
15017
|
-
affectedMapCategories: ["auth", "security"]
|
|
15018
|
-
})
|
|
15019
|
-
);
|
|
15020
|
-
}
|
|
15021
|
-
if (hasStripe && !hasEnvAssignment(envExample, "STRIPE_WEBHOOK_SECRET")) {
|
|
15022
|
-
gaps.push(
|
|
15023
|
-
gap({
|
|
15024
|
-
id: "missing_prod_env_stripe_webhook_secret",
|
|
15025
|
-
category: "DEPLOYMENT",
|
|
15026
|
-
severity: "warning",
|
|
15027
|
-
title: "Document STRIPE_WEBHOOK_SECRET",
|
|
15028
|
-
detail: "The demo .env.example does not include a STRIPE_WEBHOOK_SECRET assignment.",
|
|
15029
|
-
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.",
|
|
15030
|
-
primaryMapCategory: "deployment",
|
|
15031
|
-
affectedMapCategories: ["payments"]
|
|
15032
|
-
})
|
|
15033
|
-
);
|
|
15034
|
-
}
|
|
15035
|
-
const hasCriticalGaps = gaps.some((item3) => item3.severity === "critical");
|
|
15036
|
-
return {
|
|
15037
|
-
version: 1,
|
|
15038
|
-
scannedAt: DEMO_SCANNED_AT,
|
|
15039
|
-
workspacePath: root,
|
|
15040
|
-
score: hasCriticalGaps ? 55 : 90,
|
|
15041
|
-
scoreLabel: hasCriticalGaps ? "Needs work" : "Looks solid",
|
|
15042
|
-
summary: `Demo scan: ${gaps.length} launch gap(s) detected by local rules.`,
|
|
15043
|
-
archetype: hasNext && hasSupabase && hasStripe ? "nextjs-supabase-stripe" : "demo-saas",
|
|
15044
|
-
gaps,
|
|
15045
|
-
missionGraph: emptyMissionGraph(),
|
|
15046
|
-
stackWiring: { items: [], byKey: {} },
|
|
15047
|
-
providerRegistry: {
|
|
15048
|
-
version: 1,
|
|
15049
|
-
source: "bundled",
|
|
15050
|
-
generatedAt: DEMO_SCANNED_AT,
|
|
15051
|
-
staleAfterDays: 30,
|
|
15052
|
-
status: "fresh",
|
|
15053
|
-
providers: []
|
|
15054
|
-
},
|
|
15055
|
-
verificationSummary: { byArea: {} },
|
|
15056
|
-
productionCorePercent: hasCriticalGaps ? 40 : 95,
|
|
15057
|
-
selectedProviders
|
|
15058
|
-
};
|
|
15059
|
-
}
|
|
15060
|
-
|
|
15061
|
-
// src/demo/runDemo.ts
|
|
15062
|
-
var import_picocolors7 = __toESM(require_picocolors());
|
|
15063
|
-
function bundledFixtureRoot() {
|
|
15064
|
-
const candidates = [
|
|
15065
|
-
import_node_path27.default.join(__dirname, "..", "fixtures", "demo-saas"),
|
|
15066
|
-
import_node_path27.default.join(__dirname, "..", "..", "fixtures", "demo-saas")
|
|
15067
|
-
];
|
|
15068
|
-
return candidates.find((candidate) => (0, import_node_fs13.existsSync)(import_node_path27.default.join(candidate, "package.json"))) ?? candidates[0];
|
|
15069
|
-
}
|
|
15070
|
-
function canUseInteractiveTryMenu(options) {
|
|
15071
|
-
return isPreviewLabel(options.label) && !options.agentMode && !options.openReport && process.stdin.isTTY === true && process.stdout.isTTY === true && process.env.CI !== "true";
|
|
15072
|
-
}
|
|
15073
|
-
function isPreviewLabel(label2) {
|
|
15074
|
-
return label2 === "preview" || label2 === "try";
|
|
15075
|
-
}
|
|
15076
|
-
function printTryMenuSummary(input) {
|
|
15077
|
-
const providerStack = Array.from(new Set(Object.values(input.artifact.selectedProviders ?? {}))).filter(Boolean).join(" + ");
|
|
15078
|
-
console.log("");
|
|
15079
|
-
console.log("VibeRaven Preview");
|
|
15080
|
-
console.log("Local production rehearsal. No login or API spend.");
|
|
15081
|
-
console.log("");
|
|
15082
|
-
console.log(`Preview stack: ${input.artifact.archetype}${providerStack ? ` (${providerStack})` : ""}`);
|
|
15083
|
-
console.log(
|
|
15084
|
-
`Decision: BLOCKED | Production core: ${input.artifact.productionCorePercent}% | Gaps: ${input.artifact.gaps.length}`
|
|
15085
|
-
);
|
|
15086
|
-
console.log("");
|
|
15087
|
-
console.log("Menu:");
|
|
15088
|
-
console.log(" 1. Show Codex/Claude native agent UI");
|
|
15089
|
-
console.log(" npx -y viberaven preview --agent-mode");
|
|
15090
|
-
console.log(" 2. Run VibeRaven on your real app");
|
|
15091
|
-
console.log(" npx -y viberaven --agent-mode");
|
|
15092
|
-
console.log(" 3. Open legacy HTML report only if you explicitly want it");
|
|
15093
|
-
console.log(" npx -y viberaven preview --open");
|
|
15094
|
-
console.log("");
|
|
15095
|
-
if (input.opened) {
|
|
15096
|
-
console.log("Opened legacy HTML report in your browser.");
|
|
15097
|
-
}
|
|
15098
|
-
console.log("");
|
|
15099
|
-
}
|
|
15100
|
-
async function runInteractiveTryMenu(input) {
|
|
15101
|
-
Ie(`${import_picocolors7.default.bold("VibeRaven Preview")} ${import_picocolors7.default.dim("local production rehearsal")}`);
|
|
15102
|
-
M2.message(
|
|
15103
|
-
`Preview stack: ${input.artifact.archetype} | Production core ${input.artifact.productionCorePercent}% | ${input.artifact.gaps.length} gaps`
|
|
15104
|
-
);
|
|
15105
|
-
const action = await ve({
|
|
15106
|
-
message: "What do you want to see?",
|
|
15107
|
-
options: [
|
|
15108
|
-
{ value: "agent-output", label: "Show Codex/Claude native UI", hint: "Checklist, provider links, and next step" },
|
|
15109
|
-
{ value: "real-app", label: "Run on my real app", hint: "Copy/use the agent command" },
|
|
15110
|
-
{ value: "open-report", label: "Open legacy HTML report", hint: "Optional old browser report" },
|
|
15111
|
-
{ value: "exit", label: "Exit" }
|
|
15112
|
-
]
|
|
15113
|
-
});
|
|
15114
|
-
if (pD(action) || action === "exit") {
|
|
15115
|
-
Se(import_picocolors7.default.dim("Run npx -y viberaven preview anytime."));
|
|
15116
|
-
return;
|
|
15117
|
-
}
|
|
15118
|
-
if (action === "open-report") {
|
|
15119
|
-
await openPathInBrowser(input.reportPath);
|
|
15120
|
-
Se(`Opened ${input.reportPath}`);
|
|
15121
|
-
return;
|
|
15122
|
-
}
|
|
15123
|
-
if (action === "agent-output") {
|
|
15124
|
-
const prp = generateProductionProtocol(input.artifact, {
|
|
15125
|
-
mode: "demo",
|
|
15126
|
-
connectedTools: input.connectedTools
|
|
15127
|
-
});
|
|
15128
|
-
const tasks = buildTaskList(input.artifact);
|
|
15129
|
-
printActionPanelBlock(prp, tasks, currentRenderMode("demo"));
|
|
15130
|
-
Se(import_picocolors7.default.dim("This is the output Codex/Claude/Cursor should reason over."));
|
|
15131
|
-
return;
|
|
15132
|
-
}
|
|
15133
|
-
console.log("");
|
|
15134
|
-
console.log("Run this inside Codex, Claude Code, Cursor, Windsurf, or Gemini CLI:");
|
|
15135
|
-
console.log(" npx -y viberaven --agent-mode");
|
|
15136
|
-
console.log("");
|
|
15137
|
-
Se(import_picocolors7.default.dim("The native agent chat stays the main UX. Use report.html only as a legacy fallback."));
|
|
15138
|
-
}
|
|
15139
|
-
async function runDemo(options) {
|
|
15140
|
-
const label2 = options.label ?? "demo";
|
|
15141
|
-
const connectedTools = detectConnectedTools();
|
|
15142
|
-
const fixtureRoot = options.fixtureRoot ?? bundledFixtureRoot();
|
|
15143
|
-
const scannedArtifact = await scanDemoFixture(fixtureRoot);
|
|
15144
|
-
const artifact = {
|
|
15145
|
-
...scannedArtifact,
|
|
15146
|
-
workspacePath: options.cwd
|
|
15147
|
-
};
|
|
15148
|
-
const paths = await writeScanArtifacts({
|
|
15149
|
-
artifact,
|
|
15150
|
-
cwd: options.cwd,
|
|
15151
|
-
connectedTools,
|
|
15152
|
-
prpMode: "demo"
|
|
15153
|
-
});
|
|
15154
|
-
let reportOpened = false;
|
|
15155
|
-
if (options.openReport) {
|
|
15156
|
-
try {
|
|
15157
|
-
await openPathInBrowser(paths.reportPath);
|
|
15158
|
-
reportOpened = true;
|
|
15159
|
-
} catch (error) {
|
|
15160
|
-
console.warn(error instanceof Error ? error.message : String(error));
|
|
15161
|
-
}
|
|
15162
|
-
}
|
|
15163
|
-
if (options.agentMode) {
|
|
15164
|
-
console.log(
|
|
15165
|
-
label2 === "demo" ? "VibeRaven demo mode - no login, no API spend. Provider verification is simulated." : isPreviewLabel(label2) ? "VibeRaven Preview - local production rehearsal. Provider verification is simulated." : "VibeRaven showcase run - no login, no API spend. Provider verification is simulated."
|
|
15166
|
-
);
|
|
15167
|
-
const prp = generateProductionProtocol(artifact, { mode: "demo", connectedTools });
|
|
15168
|
-
const mode = currentRenderMode("demo");
|
|
15169
|
-
const tasks = buildTaskList(artifact);
|
|
15170
|
-
const celebration = renderClearCelebration(prp, mode);
|
|
15171
|
-
printActionPanelBlock(prp, tasks, mode);
|
|
15172
|
-
if (celebration) {
|
|
15173
|
-
console.log(celebration);
|
|
15174
|
-
}
|
|
15175
|
-
} else if (isPreviewLabel(label2)) {
|
|
15176
|
-
if (canUseInteractiveTryMenu(options)) {
|
|
15177
|
-
await runInteractiveTryMenu({ artifact, reportPath: paths.reportPath, connectedTools });
|
|
15178
|
-
} else {
|
|
15179
|
-
printTryMenuSummary({ artifact, opened: reportOpened });
|
|
15180
|
-
}
|
|
15181
|
-
} else {
|
|
15182
|
-
console.log(
|
|
15183
|
-
label2 === "demo" ? "VibeRaven demo complete. See .viberaven/prp.json" : "VibeRaven showcase run complete. See .viberaven/prp.json"
|
|
15184
|
-
);
|
|
15185
|
-
}
|
|
15186
|
-
return 0;
|
|
15187
|
-
}
|
|
15188
|
-
|
|
15189
14177
|
// src/cli.ts
|
|
15190
14178
|
function printHelp() {
|
|
15191
14179
|
console.log(`viberaven ${VERSION} \u2014 launch readiness for AI-built apps
|
|
@@ -15213,22 +14201,7 @@ Usage:
|
|
|
15213
14201
|
viberaven scan [--open] [--json] [--api-url <url>] [path]
|
|
15214
14202
|
|
|
15215
14203
|
viberaven --agent-mode [--json|--jsonl] [path]
|
|
15216
|
-
Agent-first
|
|
15217
|
-
|
|
15218
|
-
viberaven --demo [path]
|
|
15219
|
-
No-login demo scan over the bundled fixture; writes demo artifacts locally
|
|
15220
|
-
|
|
15221
|
-
viberaven --agent-mode --demo [path]
|
|
15222
|
-
Demo production flow; no login, no managed API spend
|
|
15223
|
-
|
|
15224
|
-
viberaven preview [--agent-mode] [path]
|
|
15225
|
-
Local production flow preview with native chat/terminal UX
|
|
15226
|
-
|
|
15227
|
-
viberaven try [--agent-mode] [path]
|
|
15228
|
-
Alias for preview
|
|
15229
|
-
|
|
15230
|
-
viberaven --showcase --agent-mode [path]
|
|
15231
|
-
Legacy alias for the local preview run
|
|
14204
|
+
Agent-first scan; writes tasklist, gate-result, context-map, and per-gap JSON
|
|
15232
14205
|
|
|
15233
14206
|
viberaven --strict[=warning] [path]
|
|
15234
14207
|
Fail when production gate is not clear; warning mode also fails on warnings
|
|
@@ -15237,7 +14210,7 @@ Usage:
|
|
|
15237
14210
|
Refresh .viberaven/context-map.json from the last scan
|
|
15238
14211
|
|
|
15239
14212
|
viberaven report [--open] [path]
|
|
15240
|
-
|
|
14213
|
+
Rebuild report.html from last scan (no new API scan)
|
|
15241
14214
|
|
|
15242
14215
|
viberaven prompt [--gap <id>] [--provider <key>] [--area <key>] [--no-copy]
|
|
15243
14216
|
|
|
@@ -15268,9 +14241,6 @@ Usage:
|
|
|
15268
14241
|
viberaven audit --vercel-supabase [--json] [path]
|
|
15269
14242
|
Local Vercel/Supabase repo evidence audit (RLS, pooler, secrets)
|
|
15270
14243
|
|
|
15271
|
-
viberaven badge [--svg|--card] [path]
|
|
15272
|
-
Print a README badge, terminal card, or write .viberaven/badge.svg from .viberaven/prp.json
|
|
15273
|
-
|
|
15274
14244
|
|
|
15275
14245
|
|
|
15276
14246
|
Agent workflow (Claude Code / Codex):
|
|
@@ -15330,63 +14300,16 @@ function parseArgs(argv) {
|
|
|
15330
14300
|
continue;
|
|
15331
14301
|
}
|
|
15332
14302
|
if (!command) {
|
|
15333
|
-
|
|
15334
|
-
positional.push(arg);
|
|
15335
|
-
} else {
|
|
15336
|
-
command = arg;
|
|
15337
|
-
}
|
|
14303
|
+
command = arg;
|
|
15338
14304
|
} else {
|
|
15339
14305
|
positional.push(arg);
|
|
15340
14306
|
}
|
|
15341
14307
|
}
|
|
15342
14308
|
return { command, flags, positional };
|
|
15343
14309
|
}
|
|
15344
|
-
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
15345
|
-
"audit",
|
|
15346
|
-
"badge",
|
|
15347
|
-
"connect",
|
|
15348
|
-
"doctor",
|
|
15349
|
-
"guide",
|
|
15350
|
-
"init",
|
|
15351
|
-
"interactive",
|
|
15352
|
-
"login",
|
|
15353
|
-
"logout",
|
|
15354
|
-
"next",
|
|
15355
|
-
"open",
|
|
15356
|
-
"prompt",
|
|
15357
|
-
"preview",
|
|
15358
|
-
"provider-verify",
|
|
15359
|
-
"report",
|
|
15360
|
-
"scan",
|
|
15361
|
-
"stack",
|
|
15362
|
-
"status",
|
|
15363
|
-
"tui",
|
|
15364
|
-
"try",
|
|
15365
|
-
"validate-npm-package",
|
|
15366
|
-
"version",
|
|
15367
|
-
"watch"
|
|
15368
|
-
]);
|
|
15369
|
-
function isRootModePositional(flags, token) {
|
|
15370
|
-
if (KNOWN_COMMANDS.has(token)) {
|
|
15371
|
-
return false;
|
|
15372
|
-
}
|
|
15373
|
-
return [
|
|
15374
|
-
"agent-mode",
|
|
15375
|
-
"demo",
|
|
15376
|
-
"showcase",
|
|
15377
|
-
"json",
|
|
15378
|
-
"jsonl",
|
|
15379
|
-
"strict",
|
|
15380
|
-
"condense",
|
|
15381
|
-
"verify",
|
|
15382
|
-
"heal"
|
|
15383
|
-
].some((key) => flags[key] !== void 0);
|
|
15384
|
-
}
|
|
15385
14310
|
function isBooleanFlag(command, key) {
|
|
15386
14311
|
if ([
|
|
15387
14312
|
"agent-mode",
|
|
15388
|
-
"demo",
|
|
15389
|
-
"showcase",
|
|
15390
14313
|
"json",
|
|
15391
14314
|
"jsonl",
|
|
15392
14315
|
"condense",
|
|
@@ -15396,17 +14319,14 @@ function isBooleanFlag(command, key) {
|
|
|
15396
14319
|
"apply",
|
|
15397
14320
|
"yes",
|
|
15398
14321
|
"no-verify",
|
|
15399
|
-
"force-scan"
|
|
15400
|
-
"ui"
|
|
14322
|
+
"force-scan"
|
|
15401
14323
|
].includes(key)) {
|
|
15402
14324
|
return true;
|
|
15403
14325
|
}
|
|
15404
14326
|
if (key === "strict") return true;
|
|
15405
|
-
if (key === "open" && (command === "" || command === "scan" || command === "report"
|
|
14327
|
+
if (key === "open" && (command === "" || command === "scan" || command === "report")) return true;
|
|
15406
14328
|
if (key === "verify" && command === "") return true;
|
|
15407
14329
|
if (key === "vercel-supabase" && command === "audit") return true;
|
|
15408
|
-
if (key === "svg" && command === "badge") return true;
|
|
15409
|
-
if (key === "card" && command === "badge") return true;
|
|
15410
14330
|
if (key === "json" && command === "validate-npm-package") return true;
|
|
15411
14331
|
if (key === "dry-run" && command === "init") return true;
|
|
15412
14332
|
if (key === "agents" && command === "doctor") return true;
|
|
@@ -15418,9 +14338,6 @@ function shouldConsumeLeadingHyphenValue(command, key, value) {
|
|
|
15418
14338
|
function hasFlag(flags, key) {
|
|
15419
14339
|
return flags[key] === true || typeof flags[key] === "string";
|
|
15420
14340
|
}
|
|
15421
|
-
function resolveCliPath(input) {
|
|
15422
|
-
return input ? (0, import_node_path28.resolve)(process.cwd(), input) : process.cwd();
|
|
15423
|
-
}
|
|
15424
14341
|
async function guardEarlyVerifyScan(input) {
|
|
15425
14342
|
if (input.flags["force-scan"] === true) {
|
|
15426
14343
|
return void 0;
|
|
@@ -15429,7 +14346,7 @@ async function guardEarlyVerifyScan(input) {
|
|
|
15429
14346
|
if (!verifyLike) {
|
|
15430
14347
|
return void 0;
|
|
15431
14348
|
}
|
|
15432
|
-
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());
|
|
15433
14350
|
const loopState = await loadLoopState(workspacePath);
|
|
15434
14351
|
if (loopState.batchApplied <= 0) {
|
|
15435
14352
|
return void 0;
|
|
@@ -15453,19 +14370,7 @@ async function guardEarlyVerifyScan(input) {
|
|
|
15453
14370
|
return void 0;
|
|
15454
14371
|
}
|
|
15455
14372
|
const nextTask = remainingRepoCodeTasks[0];
|
|
15456
|
-
const progress = renderLoopProgress(
|
|
15457
|
-
{
|
|
15458
|
-
applied: loopState.batchApplied,
|
|
15459
|
-
total: batchSize,
|
|
15460
|
-
label: "Healing repo gaps",
|
|
15461
|
-
silentInPlain: true
|
|
15462
|
-
},
|
|
15463
|
-
currentRenderMode("human-command")
|
|
15464
|
-
);
|
|
15465
14373
|
console.error("SCAN_DEFERRED: Local heal batch is not full yet, so VibeRaven is protecting scan quota.");
|
|
15466
|
-
if (progress) {
|
|
15467
|
-
console.error(progress);
|
|
15468
|
-
}
|
|
15469
14374
|
console.error(`Batch progress: ${loopState.batchApplied}/${batchSize} local heals applied since the last scan.`);
|
|
15470
14375
|
console.error(`Next local heal: npx -y viberaven --heal --apply --gap ${nextTask.gapId} --yes`);
|
|
15471
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.");
|
|
@@ -15517,7 +14422,7 @@ async function cmdStatus(flags, positional) {
|
|
|
15517
14422
|
console.log("Not signed in. Run: viberaven login");
|
|
15518
14423
|
return 1;
|
|
15519
14424
|
}
|
|
15520
|
-
const startDir = positional[0] ? (0,
|
|
14425
|
+
const startDir = positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd();
|
|
15521
14426
|
let artifact;
|
|
15522
14427
|
try {
|
|
15523
14428
|
artifact = await loadLastArtifact(startDir);
|
|
@@ -15671,7 +14576,7 @@ async function cmdWatch(flags) {
|
|
|
15671
14576
|
}
|
|
15672
14577
|
}
|
|
15673
14578
|
async function runScanCommand(flags, positional, options) {
|
|
15674
|
-
const workspacePath = positional[0] ? (0,
|
|
14579
|
+
const workspacePath = positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : await resolveWorkspaceRoot(process.cwd());
|
|
15675
14580
|
const apiBaseUrl = resolveApiBaseUrl(typeof flags["api-url"] === "string" ? flags["api-url"] : void 0);
|
|
15676
14581
|
let accessToken;
|
|
15677
14582
|
try {
|
|
@@ -15717,16 +14622,15 @@ async function runScanCommand(flags, positional, options) {
|
|
|
15717
14622
|
return { exitCode: 1 };
|
|
15718
14623
|
}
|
|
15719
14624
|
const artifact = await enrichArtifactWithAccount(result.artifact, apiBaseUrl, accessToken);
|
|
15720
|
-
const
|
|
15721
|
-
const paths = await writeScanArtifacts({ artifact, cwd: workspacePath, connectedTools });
|
|
14625
|
+
const paths = await writeScanArtifacts({ artifact, cwd: workspacePath });
|
|
15722
14626
|
if (flags.json && !options?.deferMachineOutput) {
|
|
15723
14627
|
console.log(formatScanJsonStdout(artifact));
|
|
15724
14628
|
return { exitCode: 0, artifacts: paths };
|
|
15725
14629
|
}
|
|
15726
|
-
if (!options?.deferMachineOutput
|
|
14630
|
+
if (!options?.deferMachineOutput) {
|
|
15727
14631
|
printScanSummary(artifact, paths);
|
|
15728
14632
|
}
|
|
15729
|
-
if (artifact.usage && !options?.deferMachineOutput
|
|
14633
|
+
if (artifact.usage && !options?.deferMachineOutput) {
|
|
15730
14634
|
console.log(formatUsageLine(artifact.usage));
|
|
15731
14635
|
}
|
|
15732
14636
|
if (flags["agent-mode"] && !options?.deferMachineOutput) {
|
|
@@ -15734,16 +14638,6 @@ async function runScanCommand(flags, positional, options) {
|
|
|
15734
14638
|
const openGapCount = artifact.gaps.length;
|
|
15735
14639
|
const updatedState = resetBatch(loopState, openGapCount);
|
|
15736
14640
|
const tasks = buildTaskList(artifact);
|
|
15737
|
-
const prp = generateProductionProtocol(artifact, { connectedTools });
|
|
15738
|
-
const mode = currentRenderMode("agent-mode");
|
|
15739
|
-
const bootstrap = await maybeAutoInstallAgentRules({ cwd: workspacePath });
|
|
15740
|
-
console.log(formatAgentRulesBootstrapSummary(bootstrap));
|
|
15741
|
-
console.log("");
|
|
15742
|
-
printActionPanelBlock(prp, tasks, mode);
|
|
15743
|
-
const celebration = renderClearCelebration(prp, mode);
|
|
15744
|
-
if (celebration) {
|
|
15745
|
-
console.log(celebration);
|
|
15746
|
-
}
|
|
15747
14641
|
const plan = artifact.plan ?? "free";
|
|
15748
14642
|
const block = buildNextActionBlock(tasks, updatedState, plan);
|
|
15749
14643
|
printNextActionBlock(block);
|
|
@@ -15760,7 +14654,7 @@ async function runScanCommand(flags, positional, options) {
|
|
|
15760
14654
|
return { exitCode: 0, artifacts: paths };
|
|
15761
14655
|
}
|
|
15762
14656
|
async function cmdReport(flags, positional) {
|
|
15763
|
-
const startDir = positional[0] ? (0,
|
|
14657
|
+
const startDir = positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd();
|
|
15764
14658
|
try {
|
|
15765
14659
|
const paths = await refreshReportFromDisk(startDir);
|
|
15766
14660
|
console.log(`Report refreshed: ${paths.reportPath}`);
|
|
@@ -15782,7 +14676,7 @@ async function cmdReport(flags, positional) {
|
|
|
15782
14676
|
}
|
|
15783
14677
|
}
|
|
15784
14678
|
async function cmdPrompt(flags, positional) {
|
|
15785
|
-
const startDir = positional[0] ? (0,
|
|
14679
|
+
const startDir = positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd();
|
|
15786
14680
|
let artifact;
|
|
15787
14681
|
try {
|
|
15788
14682
|
artifact = await loadLastArtifact(startDir);
|
|
@@ -15790,26 +14684,26 @@ async function cmdPrompt(flags, positional) {
|
|
|
15790
14684
|
console.error(error instanceof Error ? error.message : "No scan found. Run: viberaven scan");
|
|
15791
14685
|
return 1;
|
|
15792
14686
|
}
|
|
15793
|
-
const
|
|
14687
|
+
const gap = pickGap(artifact, {
|
|
15794
14688
|
gapId: typeof flags.gap === "string" ? flags.gap : void 0,
|
|
15795
14689
|
provider: typeof flags.provider === "string" ? flags.provider : void 0,
|
|
15796
14690
|
area: typeof flags.area === "string" ? flags.area : void 0
|
|
15797
14691
|
});
|
|
15798
|
-
if (!
|
|
14692
|
+
if (!gap) {
|
|
15799
14693
|
console.error("No matching gap. Run `viberaven scan` or pass --gap <id>.");
|
|
15800
14694
|
return 1;
|
|
15801
14695
|
}
|
|
15802
14696
|
const skipCopy = flags["no-copy"] === true;
|
|
15803
14697
|
if (!skipCopy) {
|
|
15804
14698
|
try {
|
|
15805
|
-
await copyToClipboard(
|
|
15806
|
-
console.log(`Copied to clipboard: ${
|
|
14699
|
+
await copyToClipboard(gap.copyPrompt);
|
|
14700
|
+
console.log(`Copied to clipboard: ${gap.title}`);
|
|
15807
14701
|
return 0;
|
|
15808
14702
|
} catch (error) {
|
|
15809
14703
|
console.warn(error instanceof Error ? error.message : String(error));
|
|
15810
14704
|
}
|
|
15811
14705
|
}
|
|
15812
|
-
console.log(
|
|
14706
|
+
console.log(gap.copyPrompt);
|
|
15813
14707
|
return 0;
|
|
15814
14708
|
}
|
|
15815
14709
|
var STACK_AREAS = /* @__PURE__ */ new Set(["database", "auth", "payments", "deployment", "monitoring", "security"]);
|
|
@@ -15880,7 +14774,7 @@ async function main() {
|
|
|
15880
14774
|
const wantsJsonl = hasFlag(flags, "jsonl");
|
|
15881
14775
|
const wantsStrict = hasFlag(flags, "strict");
|
|
15882
14776
|
if (flags.condense) {
|
|
15883
|
-
const cwd = positional[0] ? (0,
|
|
14777
|
+
const cwd = positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd();
|
|
15884
14778
|
const result = await runCondenseCommand({ cwd });
|
|
15885
14779
|
console.log(`VibeRaven context map refreshed: ${result.contextMapPath}`);
|
|
15886
14780
|
return 0;
|
|
@@ -15898,19 +14792,6 @@ async function main() {
|
|
|
15898
14792
|
console.log(JSON.stringify(result, null, 2));
|
|
15899
14793
|
return result.status.startsWith("refused") || result.status === "failed" ? 1 : 0;
|
|
15900
14794
|
}
|
|
15901
|
-
if (command === "preview" || command === "try") {
|
|
15902
|
-
const cwd = resolveCliPath(positional[0]);
|
|
15903
|
-
return runDemo({
|
|
15904
|
-
cwd,
|
|
15905
|
-
agentMode: isAgentMode,
|
|
15906
|
-
label: command === "preview" ? "preview" : "try",
|
|
15907
|
-
openReport: flags.open === true || flags.ui === true
|
|
15908
|
-
});
|
|
15909
|
-
}
|
|
15910
|
-
if (hasFlag(flags, "demo") || hasFlag(flags, "showcase")) {
|
|
15911
|
-
const cwd = resolveCliPath(positional[0]);
|
|
15912
|
-
return runDemo({ cwd, agentMode: isAgentMode, label: hasFlag(flags, "showcase") ? "showcase" : "demo" });
|
|
15913
|
-
}
|
|
15914
14795
|
if (!command && (isAgentMode || flags.verify === true || wantsJson || wantsJsonl || wantsStrict)) {
|
|
15915
14796
|
const guardedExitCode = await guardEarlyVerifyScan({ flags, positional, wantsStrict });
|
|
15916
14797
|
if (guardedExitCode !== void 0) {
|
|
@@ -15922,7 +14803,7 @@ async function main() {
|
|
|
15922
14803
|
console.error("VibeRaven could not produce machine output because scan artifacts were not written.");
|
|
15923
14804
|
return 3;
|
|
15924
14805
|
}
|
|
15925
|
-
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;
|
|
15926
14807
|
const strictExitCode = wantsStrict && gateResult ? exitCodeForStrictGate(gateResult, { failOnWarnings: flags.strict === "warning" }) : scanResult.exitCode;
|
|
15927
14808
|
if (wantsJson && gateResult) {
|
|
15928
14809
|
process.stdout.write(renderGateResultJson(gateResult));
|
|
@@ -15957,7 +14838,7 @@ async function main() {
|
|
|
15957
14838
|
case "next":
|
|
15958
14839
|
return runNextCommand({
|
|
15959
14840
|
json: Boolean(flags.json),
|
|
15960
|
-
cwd: positional[0] ? (0,
|
|
14841
|
+
cwd: positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd()
|
|
15961
14842
|
});
|
|
15962
14843
|
case "guide": {
|
|
15963
14844
|
const provider2 = positional[0];
|
|
@@ -15995,7 +14876,7 @@ async function main() {
|
|
|
15995
14876
|
case "provider-verify":
|
|
15996
14877
|
return cmdProviderVerify(flags, positional);
|
|
15997
14878
|
case "init": {
|
|
15998
|
-
const cwd = positional[0] ? (0,
|
|
14879
|
+
const cwd = positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd();
|
|
15999
14880
|
const agents = typeof flags.agents === "string" ? flags.agents : void 0;
|
|
16000
14881
|
return runInitCommand({
|
|
16001
14882
|
cwd,
|
|
@@ -16009,7 +14890,7 @@ async function main() {
|
|
|
16009
14890
|
return 1;
|
|
16010
14891
|
}
|
|
16011
14892
|
return runDoctorAgentsCommand({
|
|
16012
|
-
cwd: positional[0] ? (0,
|
|
14893
|
+
cwd: positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd()
|
|
16013
14894
|
});
|
|
16014
14895
|
case "validate-npm-package":
|
|
16015
14896
|
return runValidateNpmPackageCommand({
|
|
@@ -16022,15 +14903,9 @@ async function main() {
|
|
|
16022
14903
|
return 1;
|
|
16023
14904
|
}
|
|
16024
14905
|
return runAuditCommand({
|
|
16025
|
-
cwd: positional[0] ? (0,
|
|
14906
|
+
cwd: positional[0] ? (0, import_node_path25.join)(process.cwd(), positional[0]) : process.cwd(),
|
|
16026
14907
|
json: Boolean(flags.json)
|
|
16027
14908
|
});
|
|
16028
|
-
case "badge":
|
|
16029
|
-
return runBadgeCommand({
|
|
16030
|
-
cwd: positional[0] ? (0, import_node_path28.join)(process.cwd(), positional[0]) : process.cwd(),
|
|
16031
|
-
svg: flags.svg === true,
|
|
16032
|
-
card: flags.card === true
|
|
16033
|
-
});
|
|
16034
14909
|
default:
|
|
16035
14910
|
console.error(`Unknown command: ${command}`);
|
|
16036
14911
|
printHelp();
|