@viberaven/cli 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -23
- package/assets/report/station.css +146 -9
- package/assets/report/station.js +106 -22
- package/dist/cli.js +1920 -1077
- package/dist/cli.js.map +4 -4
- package/dist/report/station.css +146 -9
- package/dist/report/station.js +106 -22
- package/fixtures/demo-saas/.env.example +3 -0
- package/fixtures/demo-saas/app/api/stripe/webhook/route.ts +12 -0
- package/fixtures/demo-saas/package.json +11 -0
- package/fixtures/demo-saas/supabase/migrations/0001_init.sql +6 -0
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -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_promises21 = require("node:fs/promises");
|
|
174
|
+
var import_node_path28 = 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(label2, message) {
|
|
532
|
+
return `${label2}: ${message}`;
|
|
533
533
|
}
|
|
534
534
|
|
|
535
535
|
// src/account.ts
|
|
@@ -661,6 +661,9 @@ 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
|
+
}
|
|
664
667
|
|
|
665
668
|
// src/auth.ts
|
|
666
669
|
var PUBLIC_LOGIN_COMMAND = `${PUBLIC_COMMAND} login`;
|
|
@@ -1094,7 +1097,7 @@ function createGitignoreMatcher(gitignoreContent) {
|
|
|
1094
1097
|
const rules = gitignoreContent.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("!")).map(parseGitignoreRule);
|
|
1095
1098
|
return (relPath) => {
|
|
1096
1099
|
const normalized = relPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
1097
|
-
return rules.some((
|
|
1100
|
+
return rules.some((rule2) => ruleMatchesPath(rule2, normalized));
|
|
1098
1101
|
};
|
|
1099
1102
|
}
|
|
1100
1103
|
function parseGitignoreRule(raw) {
|
|
@@ -1111,22 +1114,22 @@ function parseGitignoreRule(raw) {
|
|
|
1111
1114
|
regex: new RegExp(`^${gitignoreGlobToRegex(pattern)}$`)
|
|
1112
1115
|
};
|
|
1113
1116
|
}
|
|
1114
|
-
function ruleMatchesPath(
|
|
1115
|
-
const candidates =
|
|
1116
|
-
const directMatch = candidates.some((candidate) =>
|
|
1117
|
-
if (directMatch && !
|
|
1117
|
+
function ruleMatchesPath(rule2, relPath) {
|
|
1118
|
+
const candidates = rule2.anchored || rule2.hasSlash ? [relPath] : relPath.split("/");
|
|
1119
|
+
const directMatch = candidates.some((candidate) => rule2.regex.test(candidate));
|
|
1120
|
+
if (directMatch && !rule2.directoryOnly) {
|
|
1118
1121
|
return true;
|
|
1119
1122
|
}
|
|
1120
|
-
if (directMatch &&
|
|
1123
|
+
if (directMatch && rule2.directoryOnly) {
|
|
1121
1124
|
return true;
|
|
1122
1125
|
}
|
|
1123
|
-
if (!
|
|
1126
|
+
if (!rule2.directoryOnly) {
|
|
1124
1127
|
return false;
|
|
1125
1128
|
}
|
|
1126
|
-
if (
|
|
1127
|
-
return relPath ===
|
|
1129
|
+
if (rule2.anchored || rule2.hasSlash) {
|
|
1130
|
+
return relPath === rule2.pattern || relPath.startsWith(`${rule2.pattern}/`);
|
|
1128
1131
|
}
|
|
1129
|
-
return relPath.split("/").includes(
|
|
1132
|
+
return relPath.split("/").includes(rule2.pattern);
|
|
1130
1133
|
}
|
|
1131
1134
|
function gitignoreGlobToRegex(pattern) {
|
|
1132
1135
|
let out = "";
|
|
@@ -1246,7 +1249,7 @@ function fallbackMapCategoryForGap(category, text) {
|
|
|
1246
1249
|
function inferMapCategories(category, title, detail, copyPrompt, explicitPrimary, explicitAffected) {
|
|
1247
1250
|
const text = [category, title, detail, copyPrompt].filter(Boolean).join(" ");
|
|
1248
1251
|
const fallback = fallbackMapCategoryForGap(category, text);
|
|
1249
|
-
const matched = MAP_CATEGORY_RULES.filter((
|
|
1252
|
+
const matched = MAP_CATEGORY_RULES.filter((rule2) => rule2.match.test(text)).map((rule2) => rule2.key);
|
|
1250
1253
|
const explicit = isProductionMapCategoryKey(explicitPrimary) ? [explicitPrimary] : [];
|
|
1251
1254
|
const affected = Array.isArray(explicitAffected) ? explicitAffected.filter(isProductionMapCategoryKey) : [];
|
|
1252
1255
|
const primarySeed = matched[0] ? [matched[0], fallback] : [fallback];
|
|
@@ -1291,12 +1294,12 @@ function normalizeGap(v) {
|
|
|
1291
1294
|
affectedMapCategories
|
|
1292
1295
|
};
|
|
1293
1296
|
}
|
|
1294
|
-
function rootGapKey(
|
|
1295
|
-
const titleKey = slugify(
|
|
1297
|
+
function rootGapKey(gap2) {
|
|
1298
|
+
const titleKey = slugify(gap2.title);
|
|
1296
1299
|
if (titleKey) {
|
|
1297
1300
|
return titleKey;
|
|
1298
1301
|
}
|
|
1299
|
-
return slugify([
|
|
1302
|
+
return slugify([gap2.category, gap2.copyPrompt].join(" ")) || gap2.id;
|
|
1300
1303
|
}
|
|
1301
1304
|
function mergeToolSuggestions(a, b3) {
|
|
1302
1305
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1326,17 +1329,17 @@ function mergeRootGaps(a, b3) {
|
|
|
1326
1329
|
function dedupeRootGaps(gaps) {
|
|
1327
1330
|
const byKey = /* @__PURE__ */ new Map();
|
|
1328
1331
|
const order = [];
|
|
1329
|
-
for (const
|
|
1330
|
-
const key = rootGapKey(
|
|
1332
|
+
for (const gap2 of gaps) {
|
|
1333
|
+
const key = rootGapKey(gap2);
|
|
1331
1334
|
const existing = byKey.get(key);
|
|
1332
1335
|
if (!existing) {
|
|
1333
|
-
byKey.set(key,
|
|
1336
|
+
byKey.set(key, gap2);
|
|
1334
1337
|
order.push(key);
|
|
1335
1338
|
continue;
|
|
1336
1339
|
}
|
|
1337
|
-
byKey.set(key, mergeRootGaps(existing,
|
|
1340
|
+
byKey.set(key, mergeRootGaps(existing, gap2));
|
|
1338
1341
|
}
|
|
1339
|
-
return order.map((key) => byKey.get(key)).filter((
|
|
1342
|
+
return order.map((key) => byKey.get(key)).filter((gap2) => Boolean(gap2));
|
|
1340
1343
|
}
|
|
1341
1344
|
function normalizeChecklist(v) {
|
|
1342
1345
|
const defaults = {
|
|
@@ -1480,15 +1483,15 @@ function buildLaunchValidationReport(input) {
|
|
|
1480
1483
|
promptTemplate: conflict.recommendedAction.promptTemplate
|
|
1481
1484
|
}))
|
|
1482
1485
|
);
|
|
1483
|
-
const gapIssues = (input.gaps ?? []).filter((
|
|
1484
|
-
const area = gapArea(
|
|
1486
|
+
const gapIssues = (input.gaps ?? []).filter((gap2) => gap2.severity !== "info").map((gap2) => {
|
|
1487
|
+
const area = gapArea(gap2);
|
|
1485
1488
|
return {
|
|
1486
|
-
id: `gap-${
|
|
1489
|
+
id: `gap-${gap2.id}`,
|
|
1487
1490
|
area,
|
|
1488
|
-
severity:
|
|
1489
|
-
title:
|
|
1490
|
-
detail:
|
|
1491
|
-
promptTemplate:
|
|
1491
|
+
severity: gap2.severity,
|
|
1492
|
+
title: gap2.title,
|
|
1493
|
+
detail: gap2.detail,
|
|
1494
|
+
promptTemplate: gap2.severity === "critical" && isLaunchCriticalArea(area) ? "launch-blocker" : "repo-fix"
|
|
1492
1495
|
};
|
|
1493
1496
|
});
|
|
1494
1497
|
const providerCoverageWarnings = providerCoverageIssues(input.providerTruth);
|
|
@@ -1603,8 +1606,8 @@ function isActionableProviderCheckRow(row) {
|
|
|
1603
1606
|
function hasCompletedManualConfirmation(row) {
|
|
1604
1607
|
return row.manualProof.status === "manual-confirmed" || row.roles.some((role) => role === "manual-confirmed");
|
|
1605
1608
|
}
|
|
1606
|
-
function gapArea(
|
|
1607
|
-
return VERIFICATION_AREAS.includes(
|
|
1609
|
+
function gapArea(gap2) {
|
|
1610
|
+
return VERIFICATION_AREAS.includes(gap2.primaryMapCategory) ? gap2.primaryMapCategory : "appFlow";
|
|
1608
1611
|
}
|
|
1609
1612
|
function isLaunchCriticalArea(area) {
|
|
1610
1613
|
return LAUNCH_CRITICAL_AREAS.includes(area);
|
|
@@ -2073,11 +2076,11 @@ function buildProviderRegistrySnapshot(now = /* @__PURE__ */ new Date()) {
|
|
|
2073
2076
|
providers
|
|
2074
2077
|
};
|
|
2075
2078
|
}
|
|
2076
|
-
function provider(providerKey,
|
|
2079
|
+
function provider(providerKey, label2, aliases, areas, productionAreas, iconKey, extras = {}) {
|
|
2077
2080
|
const sifgTemplateIds = areas.flatMap((area) => sifgTemplatesForRegistryProviderArea(providerKey, area)).map((template) => template.id);
|
|
2078
2081
|
return {
|
|
2079
2082
|
provider: providerKey,
|
|
2080
|
-
label,
|
|
2083
|
+
label: label2,
|
|
2081
2084
|
aliases,
|
|
2082
2085
|
areas,
|
|
2083
2086
|
productionAreas,
|
|
@@ -2935,14 +2938,14 @@ function detectProductionConnectionEvidence(scan) {
|
|
|
2935
2938
|
content: file.isSecret || typeof file.content !== "string" ? "" : file.content,
|
|
2936
2939
|
lowerContent: file.isSecret || typeof file.content !== "string" ? "" : file.content.toLowerCase()
|
|
2937
2940
|
}));
|
|
2938
|
-
const secretPathBlob = scan.secretsFound.map((
|
|
2941
|
+
const secretPathBlob = scan.secretsFound.map((path3) => normalizePath(path3)).join("\n");
|
|
2939
2942
|
const pathBlob = `${scan.fileTree}
|
|
2940
2943
|
${files.map((file) => file.path).join("\n")}`.toLowerCase();
|
|
2941
2944
|
const contentBlob = files.map((file) => file.lowerContent).join("\n").slice(0, 12e4);
|
|
2942
2945
|
const secretsHygieneBlob = `${pathBlob}
|
|
2943
2946
|
${secretPathBlob}`;
|
|
2944
|
-
for (const
|
|
2945
|
-
detectProvider(evidence,
|
|
2947
|
+
for (const rule2 of PROVIDER_RULES) {
|
|
2948
|
+
detectProvider(evidence, rule2, deps, files, pathBlob);
|
|
2946
2949
|
}
|
|
2947
2950
|
detectSecretsHygiene(evidence, secretsHygieneBlob);
|
|
2948
2951
|
for (const item3 of Object.values(evidence)) {
|
|
@@ -2996,55 +2999,55 @@ function buildProductionConnectionContext(choices, evidence) {
|
|
|
2996
2999
|
}
|
|
2997
3000
|
return lines.length > 0 ? lines.join("\n") : "production connections: no selected or detected providers";
|
|
2998
3001
|
}
|
|
2999
|
-
function detectProvider(evidence,
|
|
3000
|
-
if (evidence[
|
|
3002
|
+
function detectProvider(evidence, rule2, deps, files, pathBlob) {
|
|
3003
|
+
if (evidence[rule2.area]) {
|
|
3001
3004
|
return;
|
|
3002
3005
|
}
|
|
3003
|
-
const signals = collectSignals(
|
|
3006
|
+
const signals = collectSignals(rule2, deps, files, pathBlob);
|
|
3004
3007
|
if (signals.length === 0) {
|
|
3005
3008
|
return;
|
|
3006
3009
|
}
|
|
3007
|
-
evidence[
|
|
3008
|
-
area:
|
|
3009
|
-
provider:
|
|
3010
|
+
evidence[rule2.area] = {
|
|
3011
|
+
area: rule2.area,
|
|
3012
|
+
provider: rule2.provider,
|
|
3010
3013
|
status: ["detected"],
|
|
3011
3014
|
signals
|
|
3012
3015
|
};
|
|
3013
3016
|
}
|
|
3014
|
-
function collectSignals(
|
|
3017
|
+
function collectSignals(rule2, deps, files, pathBlob) {
|
|
3015
3018
|
const signals = [];
|
|
3016
3019
|
for (const dep of deps) {
|
|
3017
|
-
if ((
|
|
3020
|
+
if ((rule2.packages ?? []).some((pattern) => testRegex(pattern, dep))) {
|
|
3018
3021
|
addSignal(signals, `package: ${dep}`);
|
|
3019
3022
|
}
|
|
3020
3023
|
}
|
|
3021
3024
|
const upperContents = files.map((file) => file.content).join("\n").toUpperCase();
|
|
3022
|
-
for (const envName of
|
|
3025
|
+
for (const envName of rule2.env ?? []) {
|
|
3023
3026
|
if (upperContents.includes(envName.toUpperCase())) {
|
|
3024
3027
|
addSignal(signals, `env: ${envName}`);
|
|
3025
3028
|
}
|
|
3026
3029
|
}
|
|
3027
|
-
const pathLines = pathBlob.split(/\r?\n/).map((
|
|
3028
|
-
for (const
|
|
3029
|
-
if ((
|
|
3030
|
-
addSignal(signals, `${pathSignalPrefix(
|
|
3030
|
+
const pathLines = pathBlob.split(/\r?\n/).map((path3) => path3.trim()).filter(Boolean);
|
|
3031
|
+
for (const path3 of pathLines) {
|
|
3032
|
+
if ((rule2.paths ?? []).some((pattern) => testRegex(pattern, path3))) {
|
|
3033
|
+
addSignal(signals, `${pathSignalPrefix(path3)}: ${path3}`);
|
|
3031
3034
|
}
|
|
3032
3035
|
}
|
|
3033
3036
|
for (const file of files) {
|
|
3034
|
-
for (const importName of
|
|
3037
|
+
for (const importName of rule2.imports ?? []) {
|
|
3035
3038
|
if (containsImport(file.lowerContent, importName)) {
|
|
3036
3039
|
addSignal(signals, `import: ${importName}`);
|
|
3037
3040
|
}
|
|
3038
3041
|
}
|
|
3039
|
-
for (const item3 of
|
|
3042
|
+
for (const item3 of rule2.content ?? []) {
|
|
3040
3043
|
if (testRegex(item3.pattern, file.content)) {
|
|
3041
3044
|
addSignal(signals, item3.signal);
|
|
3042
3045
|
}
|
|
3043
3046
|
}
|
|
3044
3047
|
if (isDocsPath(file.path)) {
|
|
3045
|
-
for (const docsTerm of
|
|
3048
|
+
for (const docsTerm of rule2.docs ?? []) {
|
|
3046
3049
|
if (file.lowerContent.includes(docsTerm.toLowerCase())) {
|
|
3047
|
-
addSignal(signals, `docs: ${file.displayPath} mentions ${
|
|
3050
|
+
addSignal(signals, `docs: ${file.displayPath} mentions ${rule2.label}`);
|
|
3048
3051
|
break;
|
|
3049
3052
|
}
|
|
3050
3053
|
}
|
|
@@ -3056,14 +3059,14 @@ function containsImport(content, importName) {
|
|
|
3056
3059
|
const escaped = importName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").toLowerCase();
|
|
3057
3060
|
return new RegExp(`(?:from\\s+['"]${escaped}['"]|import\\s*\\(\\s*['"]${escaped}['"]|require\\s*\\(\\s*['"]${escaped}['"])`).test(content);
|
|
3058
3061
|
}
|
|
3059
|
-
function isDocsPath(
|
|
3060
|
-
return /(^|\/)(readme|product|spec)\.md$/.test(
|
|
3062
|
+
function isDocsPath(path3) {
|
|
3063
|
+
return /(^|\/)(readme|product|spec)\.md$/.test(path3) || /(^|\/)docs\/.*\.md$/.test(path3);
|
|
3061
3064
|
}
|
|
3062
|
-
function pathSignalPrefix(
|
|
3063
|
-
if (/webhook|checkout|billing|api\/|route\.[jt]s$/.test(
|
|
3065
|
+
function pathSignalPrefix(path3) {
|
|
3066
|
+
if (/webhook|checkout|billing|api\/|route\.[jt]s$/.test(path3)) {
|
|
3064
3067
|
return "route";
|
|
3065
3068
|
}
|
|
3066
|
-
if (/config|\.json$|\.toml$|\.ya?ml$/.test(
|
|
3069
|
+
if (/config|\.json$|\.toml$|\.ya?ml$/.test(path3)) {
|
|
3067
3070
|
return "config";
|
|
3068
3071
|
}
|
|
3069
3072
|
return "file";
|
|
@@ -3074,19 +3077,19 @@ function addSignal(signals, signal) {
|
|
|
3074
3077
|
}
|
|
3075
3078
|
}
|
|
3076
3079
|
function freezeProviderRules(rules) {
|
|
3077
|
-
for (const
|
|
3078
|
-
Object.freeze(
|
|
3079
|
-
Object.freeze(
|
|
3080
|
-
Object.freeze(
|
|
3081
|
-
Object.freeze(
|
|
3082
|
-
Object.freeze(
|
|
3083
|
-
if (
|
|
3084
|
-
for (const item3 of
|
|
3080
|
+
for (const rule2 of rules) {
|
|
3081
|
+
Object.freeze(rule2.packages ?? []);
|
|
3082
|
+
Object.freeze(rule2.env ?? []);
|
|
3083
|
+
Object.freeze(rule2.paths ?? []);
|
|
3084
|
+
Object.freeze(rule2.imports ?? []);
|
|
3085
|
+
Object.freeze(rule2.docs ?? []);
|
|
3086
|
+
if (rule2.content) {
|
|
3087
|
+
for (const item3 of rule2.content) {
|
|
3085
3088
|
Object.freeze(item3);
|
|
3086
3089
|
}
|
|
3087
|
-
Object.freeze(
|
|
3090
|
+
Object.freeze(rule2.content);
|
|
3088
3091
|
}
|
|
3089
|
-
Object.freeze(
|
|
3092
|
+
Object.freeze(rule2);
|
|
3090
3093
|
}
|
|
3091
3094
|
return Object.freeze(rules);
|
|
3092
3095
|
}
|
|
@@ -3214,8 +3217,8 @@ function normalizeSelectedAt(value) {
|
|
|
3214
3217
|
}
|
|
3215
3218
|
return null;
|
|
3216
3219
|
}
|
|
3217
|
-
function normalizePath(
|
|
3218
|
-
return
|
|
3220
|
+
function normalizePath(path3) {
|
|
3221
|
+
return path3.replace(/\\/g, "/").toLowerCase();
|
|
3219
3222
|
}
|
|
3220
3223
|
function isObject(value) {
|
|
3221
3224
|
return typeof value === "object" && value !== null;
|
|
@@ -3243,8 +3246,8 @@ var WEAK_PATH_SEGMENTS = /* @__PURE__ */ new Set([
|
|
|
3243
3246
|
"demo"
|
|
3244
3247
|
]);
|
|
3245
3248
|
var TEST_FILE_PATTERN = /\.(test|spec)\.[jt]sx?$/;
|
|
3246
|
-
function classifyProviderEvidencePath(
|
|
3247
|
-
const normalized = normalizePath2(
|
|
3249
|
+
function classifyProviderEvidencePath(path3) {
|
|
3250
|
+
const normalized = normalizePath2(path3);
|
|
3248
3251
|
const segments = normalized.split("/").filter(Boolean);
|
|
3249
3252
|
if (isDocsLikePath(normalized) || segments.some((segment) => WEAK_PATH_SEGMENTS.has(segment)) || TEST_FILE_PATTERN.test(normalized)) {
|
|
3250
3253
|
return "weak";
|
|
@@ -3259,14 +3262,14 @@ function buildProviderTruthSnapshot(input) {
|
|
|
3259
3262
|
const rowsByArea = {};
|
|
3260
3263
|
void input.mcpVerifierState;
|
|
3261
3264
|
void input.verificationLayer;
|
|
3262
|
-
for (const
|
|
3263
|
-
const evidence = collectProviderTruthEvidence(
|
|
3264
|
-
const selected = choices.choices[
|
|
3265
|
+
for (const rule2 of PROVIDER_RULES) {
|
|
3266
|
+
const evidence = collectProviderTruthEvidence(rule2, deps, files, pathLines);
|
|
3267
|
+
const selected = choices.choices[rule2.area]?.provider === rule2.provider;
|
|
3265
3268
|
if (evidence.length === 0 && !selected) {
|
|
3266
3269
|
continue;
|
|
3267
3270
|
}
|
|
3268
|
-
const row = buildRow(
|
|
3269
|
-
rowsByArea[
|
|
3271
|
+
const row = buildRow(rule2, evidence, selected);
|
|
3272
|
+
rowsByArea[rule2.area] = [...rowsByArea[rule2.area] ?? [], row];
|
|
3270
3273
|
}
|
|
3271
3274
|
for (const [area, choice] of Object.entries(choices.choices)) {
|
|
3272
3275
|
if (!choice) {
|
|
@@ -3298,32 +3301,32 @@ function buildProviderTruthSnapshot(input) {
|
|
|
3298
3301
|
summary
|
|
3299
3302
|
};
|
|
3300
3303
|
}
|
|
3301
|
-
function collectProviderTruthEvidence(
|
|
3304
|
+
function collectProviderTruthEvidence(rule2, deps, files, pathLines) {
|
|
3302
3305
|
const evidence = [];
|
|
3303
3306
|
for (const dep of deps) {
|
|
3304
|
-
if ((
|
|
3305
|
-
addEvidence(evidence,
|
|
3307
|
+
if ((rule2.packages ?? []).some((pattern) => testRegex2(pattern, dep))) {
|
|
3308
|
+
addEvidence(evidence, rule2, {
|
|
3306
3309
|
kind: "package-installed",
|
|
3307
3310
|
strength: "medium",
|
|
3308
|
-
label: `${
|
|
3311
|
+
label: `${rule2.label} package installed`,
|
|
3309
3312
|
detail: dep,
|
|
3310
3313
|
points: 20,
|
|
3311
3314
|
isRuntimeEvidence: false
|
|
3312
3315
|
});
|
|
3313
3316
|
}
|
|
3314
3317
|
}
|
|
3315
|
-
for (const
|
|
3316
|
-
if (!(
|
|
3318
|
+
for (const path3 of pathLines) {
|
|
3319
|
+
if (!(rule2.paths ?? []).some((pattern) => testRegex2(pattern, path3))) {
|
|
3317
3320
|
continue;
|
|
3318
3321
|
}
|
|
3319
|
-
const pathClass = classifyProviderEvidencePath(
|
|
3320
|
-
const kind = weakPathKind(
|
|
3321
|
-
addEvidence(evidence,
|
|
3322
|
+
const pathClass = classifyProviderEvidencePath(path3);
|
|
3323
|
+
const kind = weakPathKind(path3, "active-runtime-route") ?? routeKind(path3);
|
|
3324
|
+
addEvidence(evidence, rule2, {
|
|
3322
3325
|
kind,
|
|
3323
3326
|
strength: pathClass === "runtime" ? "strong" : "weak",
|
|
3324
|
-
label: `${
|
|
3325
|
-
file:
|
|
3326
|
-
points: pathClass === "runtime" ? 35 : weakEvidencePoints(
|
|
3327
|
+
label: `${rule2.label} ${pathClass === "runtime" ? "runtime route or path" : "weak path reference"}`,
|
|
3328
|
+
file: path3,
|
|
3329
|
+
points: pathClass === "runtime" ? 35 : weakEvidencePoints(path3),
|
|
3327
3330
|
isRuntimeEvidence: pathClass === "runtime"
|
|
3328
3331
|
});
|
|
3329
3332
|
}
|
|
@@ -3331,45 +3334,45 @@ function collectProviderTruthEvidence(rule, deps, files, pathLines) {
|
|
|
3331
3334
|
const pathClass = classifyProviderEvidencePath(file.path);
|
|
3332
3335
|
const sourceContent = pathClass === "runtime" ? file.executableContent : file.content;
|
|
3333
3336
|
const sourceLowerContent = pathClass === "runtime" ? file.lowerExecutableContent : file.lowerContent;
|
|
3334
|
-
for (const envName of
|
|
3337
|
+
for (const envName of rule2.env ?? []) {
|
|
3335
3338
|
if (!sourceContent.toUpperCase().includes(envName.toUpperCase())) {
|
|
3336
3339
|
continue;
|
|
3337
3340
|
}
|
|
3338
|
-
addEvidence(evidence,
|
|
3341
|
+
addEvidence(evidence, rule2, {
|
|
3339
3342
|
kind: pathClass === "runtime" ? "runtime-env-usage" : "env-name-only",
|
|
3340
3343
|
strength: pathClass === "runtime" ? "medium" : "weak",
|
|
3341
|
-
label: `${
|
|
3344
|
+
label: `${rule2.label} env name ${pathClass === "runtime" ? "used in runtime source" : "mentioned outside runtime source"}`,
|
|
3342
3345
|
file: file.displayPath,
|
|
3343
3346
|
detail: envName,
|
|
3344
3347
|
points: pathClass === "runtime" ? 20 : weakEvidencePoints(file.path),
|
|
3345
3348
|
isRuntimeEvidence: pathClass === "runtime"
|
|
3346
3349
|
});
|
|
3347
3350
|
}
|
|
3348
|
-
for (const importName of
|
|
3351
|
+
for (const importName of rule2.imports ?? []) {
|
|
3349
3352
|
if (!containsImport2(sourceLowerContent, importName)) {
|
|
3350
3353
|
continue;
|
|
3351
3354
|
}
|
|
3352
3355
|
const weakKind = weakPathKind(file.path, "sdk-import-source");
|
|
3353
|
-
addEvidence(evidence,
|
|
3356
|
+
addEvidence(evidence, rule2, {
|
|
3354
3357
|
kind: weakKind ?? "sdk-import-source",
|
|
3355
3358
|
strength: pathClass === "runtime" ? "strong" : "weak",
|
|
3356
|
-
label: `${
|
|
3359
|
+
label: `${rule2.label} SDK import ${pathClass === "runtime" ? "in runtime source" : "outside runtime source"}`,
|
|
3357
3360
|
file: file.displayPath,
|
|
3358
3361
|
detail: importName,
|
|
3359
3362
|
points: pathClass === "runtime" ? 35 : weakEvidencePoints(file.path),
|
|
3360
3363
|
isRuntimeEvidence: pathClass === "runtime"
|
|
3361
3364
|
});
|
|
3362
3365
|
}
|
|
3363
|
-
for (const item3 of
|
|
3366
|
+
for (const item3 of rule2.content ?? []) {
|
|
3364
3367
|
if (!testRegex2(item3.pattern, sourceContent)) {
|
|
3365
3368
|
continue;
|
|
3366
3369
|
}
|
|
3367
3370
|
const strongKind = contentSignalKind(item3.signal);
|
|
3368
3371
|
const weakKind = weakPathKind(file.path, strongKind);
|
|
3369
|
-
addEvidence(evidence,
|
|
3372
|
+
addEvidence(evidence, rule2, {
|
|
3370
3373
|
kind: weakKind ?? strongKind,
|
|
3371
3374
|
strength: pathClass === "runtime" ? "strong" : "weak",
|
|
3372
|
-
label: `${
|
|
3375
|
+
label: `${rule2.label} ${pathClass === "runtime" ? "runtime content signal" : "weak content reference"}`,
|
|
3373
3376
|
file: file.displayPath,
|
|
3374
3377
|
detail: item3.signal,
|
|
3375
3378
|
points: pathClass === "runtime" ? 35 : weakEvidencePoints(file.path),
|
|
@@ -3377,14 +3380,14 @@ function collectProviderTruthEvidence(rule, deps, files, pathLines) {
|
|
|
3377
3380
|
});
|
|
3378
3381
|
}
|
|
3379
3382
|
if (isDocsLikePath(file.path)) {
|
|
3380
|
-
for (const docsTerm of
|
|
3383
|
+
for (const docsTerm of rule2.docs ?? []) {
|
|
3381
3384
|
if (!file.lowerContent.includes(docsTerm.toLowerCase())) {
|
|
3382
3385
|
continue;
|
|
3383
3386
|
}
|
|
3384
|
-
addEvidence(evidence,
|
|
3387
|
+
addEvidence(evidence, rule2, {
|
|
3385
3388
|
kind: "docs-mention",
|
|
3386
3389
|
strength: "weak",
|
|
3387
|
-
label: `${
|
|
3390
|
+
label: `${rule2.label} mentioned in docs`,
|
|
3388
3391
|
file: file.displayPath,
|
|
3389
3392
|
detail: docsTerm,
|
|
3390
3393
|
points: 4,
|
|
@@ -3396,16 +3399,16 @@ function collectProviderTruthEvidence(rule, deps, files, pathLines) {
|
|
|
3396
3399
|
}
|
|
3397
3400
|
return evidence.sort(compareEvidence);
|
|
3398
3401
|
}
|
|
3399
|
-
function buildRow(
|
|
3402
|
+
function buildRow(rule2, evidence, selected) {
|
|
3400
3403
|
const score = evidence.reduce((total, item3) => total + item3.points, 0);
|
|
3401
3404
|
const roles = rolesForEvidence(evidence, selected);
|
|
3402
|
-
applyMcpSupportRoles(
|
|
3405
|
+
applyMcpSupportRoles(rule2.provider, roles);
|
|
3403
3406
|
const confidence = confidenceForEvidence(evidence, roles);
|
|
3404
|
-
const mcpProof = mcpProofForProvider(
|
|
3407
|
+
const mcpProof = mcpProofForProvider(rule2.provider);
|
|
3405
3408
|
return {
|
|
3406
|
-
area:
|
|
3407
|
-
provider:
|
|
3408
|
-
providerLabel: providerLabel(
|
|
3409
|
+
area: rule2.area,
|
|
3410
|
+
provider: rule2.provider,
|
|
3411
|
+
providerLabel: providerLabel(rule2.provider),
|
|
3409
3412
|
roles,
|
|
3410
3413
|
confidence,
|
|
3411
3414
|
score,
|
|
@@ -3662,10 +3665,10 @@ function statusBadgesForRoles(roles, confidence) {
|
|
|
3662
3665
|
badges.push(confidence.toUpperCase());
|
|
3663
3666
|
return badges;
|
|
3664
3667
|
}
|
|
3665
|
-
function addEvidence(evidence,
|
|
3668
|
+
function addEvidence(evidence, rule2, item3) {
|
|
3666
3669
|
const id = [
|
|
3667
|
-
|
|
3668
|
-
|
|
3670
|
+
rule2.area,
|
|
3671
|
+
rule2.provider,
|
|
3669
3672
|
item3.kind,
|
|
3670
3673
|
item3.file ?? "",
|
|
3671
3674
|
item3.detail ?? item3.label
|
|
@@ -3680,29 +3683,29 @@ function addEvidence(evidence, rule, item3) {
|
|
|
3680
3683
|
isManualProof: false
|
|
3681
3684
|
});
|
|
3682
3685
|
}
|
|
3683
|
-
function weakPathKind(
|
|
3684
|
-
if (classifyProviderEvidencePath(
|
|
3686
|
+
function weakPathKind(path3, fallback) {
|
|
3687
|
+
if (classifyProviderEvidencePath(path3) === "runtime") {
|
|
3685
3688
|
return null;
|
|
3686
3689
|
}
|
|
3687
|
-
if (isDocsLikePath(
|
|
3690
|
+
if (isDocsLikePath(path3)) {
|
|
3688
3691
|
return "docs-mention";
|
|
3689
3692
|
}
|
|
3690
|
-
if (/(^|\/)(__tests__|tests)(\/|$)|\.(test|spec)\.[jt]sx?$/.test(normalizePath2(
|
|
3693
|
+
if (/(^|\/)(__tests__|tests)(\/|$)|\.(test|spec)\.[jt]sx?$/.test(normalizePath2(path3))) {
|
|
3691
3694
|
return "test-reference";
|
|
3692
3695
|
}
|
|
3693
|
-
if (/(^|\/)(tmp|out|outputs|videos|marketing|examples|demo)(\/|$)/.test(normalizePath2(
|
|
3696
|
+
if (/(^|\/)(tmp|out|outputs|videos|marketing|examples|demo)(\/|$)/.test(normalizePath2(path3))) {
|
|
3694
3697
|
return "tmp-demo-example";
|
|
3695
3698
|
}
|
|
3696
3699
|
return fallback;
|
|
3697
3700
|
}
|
|
3698
|
-
function routeKind(
|
|
3699
|
-
if (/webhook/.test(
|
|
3701
|
+
function routeKind(path3) {
|
|
3702
|
+
if (/webhook/.test(path3)) {
|
|
3700
3703
|
return "webhook-handler";
|
|
3701
3704
|
}
|
|
3702
|
-
if (/checkout|billing/.test(
|
|
3705
|
+
if (/checkout|billing/.test(path3)) {
|
|
3703
3706
|
return "checkout-handler";
|
|
3704
3707
|
}
|
|
3705
|
-
if (/config|\.json$|\.toml$|\.ya?ml$/.test(
|
|
3708
|
+
if (/config|\.json$|\.toml$|\.ya?ml$/.test(path3)) {
|
|
3706
3709
|
return "deployment-config";
|
|
3707
3710
|
}
|
|
3708
3711
|
return "active-runtime-route";
|
|
@@ -3719,11 +3722,11 @@ function contentSignalKind(signal) {
|
|
|
3719
3722
|
}
|
|
3720
3723
|
return "active-runtime-route";
|
|
3721
3724
|
}
|
|
3722
|
-
function weakEvidencePoints(
|
|
3723
|
-
if (isDocsLikePath(
|
|
3725
|
+
function weakEvidencePoints(path3) {
|
|
3726
|
+
if (isDocsLikePath(path3)) {
|
|
3724
3727
|
return 4;
|
|
3725
3728
|
}
|
|
3726
|
-
if (/(^|\/)(__tests__|tests)(\/|$)|\.(test|spec)\.[jt]sx?$/.test(normalizePath2(
|
|
3729
|
+
if (/(^|\/)(__tests__|tests)(\/|$)|\.(test|spec)\.[jt]sx?$/.test(normalizePath2(path3))) {
|
|
3727
3730
|
return 6;
|
|
3728
3731
|
}
|
|
3729
3732
|
return 8;
|
|
@@ -3742,8 +3745,8 @@ function toScannableFile(file) {
|
|
|
3742
3745
|
}
|
|
3743
3746
|
function collectPathLines(scan, files) {
|
|
3744
3747
|
const paths = /* @__PURE__ */ new Set();
|
|
3745
|
-
for (const
|
|
3746
|
-
const normalized = normalizePath2(
|
|
3748
|
+
for (const path3 of scan.fileTree.split(/\r?\n/)) {
|
|
3749
|
+
const normalized = normalizePath2(path3.trim());
|
|
3747
3750
|
if (normalized) {
|
|
3748
3751
|
paths.add(normalized);
|
|
3749
3752
|
}
|
|
@@ -3757,8 +3760,8 @@ function containsImport2(content, importName) {
|
|
|
3757
3760
|
const escaped = importName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").toLowerCase();
|
|
3758
3761
|
return new RegExp(`(?:from\\s+['"]${escaped}['"]|import\\s*\\(\\s*['"]${escaped}['"]|require\\s*\\(\\s*['"]${escaped}['"])`).test(content);
|
|
3759
3762
|
}
|
|
3760
|
-
function isDocsLikePath(
|
|
3761
|
-
const normalized = normalizePath2(
|
|
3763
|
+
function isDocsLikePath(path3) {
|
|
3764
|
+
const normalized = normalizePath2(path3);
|
|
3762
3765
|
return /(^|\/)(readme|product|spec)\.mdx?$/.test(normalized) || /(^|\/)docs\/.*\.mdx?$/.test(normalized);
|
|
3763
3766
|
}
|
|
3764
3767
|
function stripComments(content) {
|
|
@@ -3770,8 +3773,8 @@ function compareRows(a, b3) {
|
|
|
3770
3773
|
function compareEvidence(a, b3) {
|
|
3771
3774
|
return b3.points - a.points || a.kind.localeCompare(b3.kind) || (a.file ?? "").localeCompare(b3.file ?? "");
|
|
3772
3775
|
}
|
|
3773
|
-
function normalizePath2(
|
|
3774
|
-
return
|
|
3776
|
+
function normalizePath2(path3) {
|
|
3777
|
+
return path3.replace(/\\/g, "/").toLowerCase();
|
|
3775
3778
|
}
|
|
3776
3779
|
function rowPriority(row) {
|
|
3777
3780
|
if (row.roles.includes("live-verified")) {
|
|
@@ -3966,8 +3969,8 @@ Return ONLY the JSON object \u2014 no markdown, no explanation.
|
|
|
3966
3969
|
}
|
|
3967
3970
|
|
|
3968
3971
|
// ../../src/station/envEvidence.ts
|
|
3969
|
-
function normalizeEvidencePath(
|
|
3970
|
-
return
|
|
3972
|
+
function normalizeEvidencePath(path3) {
|
|
3973
|
+
return path3.replace(/\\/g, "/");
|
|
3971
3974
|
}
|
|
3972
3975
|
function uniquePaths(paths) {
|
|
3973
3976
|
return Array.from(new Set(paths.map(normalizeEvidencePath)));
|
|
@@ -4035,7 +4038,7 @@ function collectEnvVarEvidence(scan, names) {
|
|
|
4035
4038
|
present: true,
|
|
4036
4039
|
mode,
|
|
4037
4040
|
source: "non-secret-content",
|
|
4038
|
-
evidence: uniquePaths(contentEvidence).map((
|
|
4041
|
+
evidence: uniquePaths(contentEvidence).map((path3) => `file: ${path3}`)
|
|
4039
4042
|
};
|
|
4040
4043
|
}
|
|
4041
4044
|
if (nonEmptyAssignmentEvidence.length > 0) {
|
|
@@ -4044,7 +4047,7 @@ function collectEnvVarEvidence(scan, names) {
|
|
|
4044
4047
|
present: true,
|
|
4045
4048
|
mode: "unknown",
|
|
4046
4049
|
source: "non-secret-content",
|
|
4047
|
-
evidence: uniquePaths(nonEmptyAssignmentEvidence).map((
|
|
4050
|
+
evidence: uniquePaths(nonEmptyAssignmentEvidence).map((path3) => `file: ${path3}`)
|
|
4048
4051
|
};
|
|
4049
4052
|
}
|
|
4050
4053
|
if (nameOnlyEvidence.length > 0) {
|
|
@@ -4053,7 +4056,7 @@ function collectEnvVarEvidence(scan, names) {
|
|
|
4053
4056
|
present: true,
|
|
4054
4057
|
mode: "unknown",
|
|
4055
4058
|
source: "variable-name-only",
|
|
4056
|
-
evidence: uniquePaths(nameOnlyEvidence).map((
|
|
4059
|
+
evidence: uniquePaths(nameOnlyEvidence).map((path3) => `file: ${path3}`)
|
|
4057
4060
|
};
|
|
4058
4061
|
}
|
|
4059
4062
|
if (secretEvidence.length > 0) {
|
|
@@ -4062,7 +4065,7 @@ function collectEnvVarEvidence(scan, names) {
|
|
|
4062
4065
|
present: false,
|
|
4063
4066
|
mode: "unknown",
|
|
4064
4067
|
source: "secret-file-path",
|
|
4065
|
-
evidence: secretEvidence.map((
|
|
4068
|
+
evidence: secretEvidence.map((path3) => `secret file: ${path3}`)
|
|
4066
4069
|
};
|
|
4067
4070
|
}
|
|
4068
4071
|
return {
|
|
@@ -4157,10 +4160,10 @@ ${pathBlob}`),
|
|
|
4157
4160
|
function visibleFiles(scan) {
|
|
4158
4161
|
return scan.files.filter((file) => !file.isSecret && typeof file.content === "string");
|
|
4159
4162
|
}
|
|
4160
|
-
function foundOrMissing(id,
|
|
4163
|
+
function foundOrMissing(id, label2, found, evidence) {
|
|
4161
4164
|
return {
|
|
4162
4165
|
id,
|
|
4163
|
-
label,
|
|
4166
|
+
label: label2,
|
|
4164
4167
|
status: found ? "found" : "missing",
|
|
4165
4168
|
evidence: unique(evidence).slice(0, 6)
|
|
4166
4169
|
};
|
|
@@ -4197,7 +4200,7 @@ function evidenceFor(files, pattern) {
|
|
|
4197
4200
|
return files.filter((file) => pattern.test(file.content)).map((file) => `file: ${normalizePath3(file.path)}`).slice(0, 6);
|
|
4198
4201
|
}
|
|
4199
4202
|
function pathEvidence(scan, pattern) {
|
|
4200
|
-
return scan.files.map((file) => normalizePath3(file.path)).filter((
|
|
4203
|
+
return scan.files.map((file) => normalizePath3(file.path)).filter((path3) => pattern.test(path3)).map((path3) => `file: ${path3}`).slice(0, 6);
|
|
4201
4204
|
}
|
|
4202
4205
|
function isClientReachableFile(file) {
|
|
4203
4206
|
const content = file.content;
|
|
@@ -4213,22 +4216,22 @@ function isClientReachableFile(file) {
|
|
|
4213
4216
|
function hasUseClientDirective(content) {
|
|
4214
4217
|
return /^(?:\s|;|\/\/[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/)*['"]use client['"]/.test(content);
|
|
4215
4218
|
}
|
|
4216
|
-
function isBrowserOnlyPath(
|
|
4217
|
-
const normalized = normalizePath3(
|
|
4218
|
-
if (isServerOnlyPath(
|
|
4219
|
+
function isBrowserOnlyPath(path3) {
|
|
4220
|
+
const normalized = normalizePath3(path3).toLowerCase();
|
|
4221
|
+
if (isServerOnlyPath(path3)) {
|
|
4219
4222
|
return false;
|
|
4220
4223
|
}
|
|
4221
4224
|
return /(^|\/)(components|hooks|contexts|providers)\//.test(normalized) || /\.(jsx|tsx)$/.test(normalized);
|
|
4222
4225
|
}
|
|
4223
|
-
function isServerOnlyPath(
|
|
4224
|
-
const normalized = normalizePath3(
|
|
4226
|
+
function isServerOnlyPath(path3) {
|
|
4227
|
+
const normalized = normalizePath3(path3).toLowerCase();
|
|
4225
4228
|
return /\/api\/|app\/api\/|pages\/api\/|route\.[jt]s$|\.server\.[jt]sx?$|\/server\//.test(normalized);
|
|
4226
4229
|
}
|
|
4227
|
-
function isEnvOrDocPath(
|
|
4228
|
-
return ENV_OR_DOC_PATH.test(normalizePath3(
|
|
4230
|
+
function isEnvOrDocPath(path3) {
|
|
4231
|
+
return ENV_OR_DOC_PATH.test(normalizePath3(path3).toLowerCase());
|
|
4229
4232
|
}
|
|
4230
|
-
function normalizePath3(
|
|
4231
|
-
return
|
|
4233
|
+
function normalizePath3(path3) {
|
|
4234
|
+
return path3.replace(/\\/g, "/");
|
|
4232
4235
|
}
|
|
4233
4236
|
function unique(values) {
|
|
4234
4237
|
return [...new Set(values)];
|
|
@@ -5339,7 +5342,7 @@ ${scan.files.map((file) => file.path).join("\n")}`.replace(/\\/g, "/").toLowerCa
|
|
|
5339
5342
|
];
|
|
5340
5343
|
const passedCount = items.filter((entry) => entry.status === "passed").length;
|
|
5341
5344
|
const totalCount = items.length;
|
|
5342
|
-
const
|
|
5345
|
+
const readinessPercent2 = Math.round(passedCount / Math.max(totalCount, 1) * 100);
|
|
5343
5346
|
return {
|
|
5344
5347
|
key: "supabase-database",
|
|
5345
5348
|
provider: "supabase",
|
|
@@ -5350,7 +5353,7 @@ ${scan.files.map((file) => file.path).join("\n")}`.replace(/\\/g, "/").toLowerCa
|
|
|
5350
5353
|
items,
|
|
5351
5354
|
passedCount,
|
|
5352
5355
|
totalCount,
|
|
5353
|
-
readinessPercent
|
|
5356
|
+
readinessPercent: readinessPercent2
|
|
5354
5357
|
};
|
|
5355
5358
|
}
|
|
5356
5359
|
function visibleFiles2(scan) {
|
|
@@ -5361,10 +5364,10 @@ function visibleFiles2(scan) {
|
|
|
5361
5364
|
lowerContent: file.content.toLowerCase()
|
|
5362
5365
|
}));
|
|
5363
5366
|
}
|
|
5364
|
-
function item(id,
|
|
5367
|
+
function item(id, label2, passed, evidence, promptHint) {
|
|
5365
5368
|
return {
|
|
5366
5369
|
id,
|
|
5367
|
-
label,
|
|
5370
|
+
label: label2,
|
|
5368
5371
|
status: passed ? "passed" : "missing",
|
|
5369
5372
|
evidence,
|
|
5370
5373
|
promptHint
|
|
@@ -5394,7 +5397,7 @@ function hasSupabaseClient(files, pathBlob) {
|
|
|
5394
5397
|
}
|
|
5395
5398
|
function clientEvidence(files, pathBlob) {
|
|
5396
5399
|
const evidence = [];
|
|
5397
|
-
const pathMatch = pathBlob.split(/\r?\n/).find((
|
|
5400
|
+
const pathMatch = pathBlob.split(/\r?\n/).find((path3) => /(^|\/)(lib|utils|src\/lib|src\/utils)\/supabase\.[jt]s\b/i.test(path3));
|
|
5398
5401
|
if (pathMatch) {
|
|
5399
5402
|
evidence.push(`file: ${pathMatch}`);
|
|
5400
5403
|
}
|
|
@@ -5411,11 +5414,11 @@ function hasSchemaOrMigration(files, pathBlob) {
|
|
|
5411
5414
|
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));
|
|
5412
5415
|
}
|
|
5413
5416
|
function schemaEvidence(files, pathBlob) {
|
|
5414
|
-
const
|
|
5417
|
+
const path3 = pathBlob.split(/\r?\n/).find(
|
|
5415
5418
|
(entry) => /(^|\/)supabase\/migrations\/[^/]+\.sql\b/i.test(entry) || /(^|\/)(migrations?|schema)\/[^/]+\.(sql|ts|js)\b/i.test(entry)
|
|
5416
5419
|
);
|
|
5417
|
-
if (
|
|
5418
|
-
return [`schema: ${
|
|
5420
|
+
if (path3) {
|
|
5421
|
+
return [`schema: ${path3}`];
|
|
5419
5422
|
}
|
|
5420
5423
|
const file = files.find((entry) => /create\s+table|alter\s+table/i.test(entry.content));
|
|
5421
5424
|
return file ? [`schema: ${file.path}`] : [];
|
|
@@ -5424,9 +5427,9 @@ function hasRlsEvidence(files, pathBlob) {
|
|
|
5424
5427
|
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));
|
|
5425
5428
|
}
|
|
5426
5429
|
function rlsEvidence(files, pathBlob) {
|
|
5427
|
-
const
|
|
5428
|
-
if (
|
|
5429
|
-
return [`rls: ${
|
|
5430
|
+
const path3 = pathBlob.split(/\r?\n/).find((entry) => /\/policies\/|_rls\.sql|\brls\b/i.test(entry));
|
|
5431
|
+
if (path3) {
|
|
5432
|
+
return [`rls: ${path3}`];
|
|
5430
5433
|
}
|
|
5431
5434
|
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));
|
|
5432
5435
|
return file ? [`rls: ${file.path}`] : [];
|
|
@@ -5435,11 +5438,11 @@ function hasGeneratedTypes(files, pathBlob) {
|
|
|
5435
5438
|
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));
|
|
5436
5439
|
}
|
|
5437
5440
|
function generatedTypeEvidence(files, pathBlob) {
|
|
5438
|
-
const
|
|
5441
|
+
const path3 = pathBlob.split(/\r?\n/).find(
|
|
5439
5442
|
(entry) => /database\.types\.[jt]s\b|supabase.*types\.[jt]s\b|types\/database\.[jt]s\b/i.test(entry)
|
|
5440
5443
|
);
|
|
5441
|
-
if (
|
|
5442
|
-
return [`types: ${
|
|
5444
|
+
if (path3) {
|
|
5445
|
+
return [`types: ${path3}`];
|
|
5443
5446
|
}
|
|
5444
5447
|
const file = files.find((entry) => /export\s+type\s+database\b|export\s+interface\s+database\b/i.test(entry.content));
|
|
5445
5448
|
return file ? [`types: ${file.path}`] : [];
|
|
@@ -5456,8 +5459,8 @@ function serviceRoleSafetyItem(files) {
|
|
|
5456
5459
|
promptHint: "Move service-role usage to server-only code and use public anon keys in frontend clients."
|
|
5457
5460
|
};
|
|
5458
5461
|
}
|
|
5459
|
-
function isClientExecutedPath(
|
|
5460
|
-
return /\.(tsx|jsx)$/.test(
|
|
5462
|
+
function isClientExecutedPath(path3) {
|
|
5463
|
+
return /\.(tsx|jsx)$/.test(path3) || /(^|\/)(components|pages|app|client|frontend|web)\//.test(path3) || /\.client\.[jt]sx?$/.test(path3);
|
|
5461
5464
|
}
|
|
5462
5465
|
|
|
5463
5466
|
// ../../src/station/stackWiring.ts
|
|
@@ -6282,7 +6285,7 @@ function analyzeSecretsHygiene(ctx) {
|
|
|
6282
6285
|
promptSubject: "secrets hygiene",
|
|
6283
6286
|
items: [
|
|
6284
6287
|
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."),
|
|
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((
|
|
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((path3) => `secret file: ${path3}`), "Keep real .env files private and out of copied prompts or docs."),
|
|
6286
6289
|
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."),
|
|
6287
6290
|
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."),
|
|
6288
6291
|
manualItem("production-secret-rotation-checked", "Production secret rotation checked", "Confirm production secrets can be rotated and revoked in provider dashboards.")
|
|
@@ -6358,29 +6361,29 @@ function summarize(summary) {
|
|
|
6358
6361
|
readinessPercent: Math.round(passedCount / Math.max(totalCount, 1) * 100)
|
|
6359
6362
|
};
|
|
6360
6363
|
}
|
|
6361
|
-
function item2(id,
|
|
6364
|
+
function item2(id, label2, passed, evidence, promptHint) {
|
|
6362
6365
|
return {
|
|
6363
6366
|
id,
|
|
6364
|
-
label,
|
|
6367
|
+
label: label2,
|
|
6365
6368
|
status: passed ? "passed" : "missing",
|
|
6366
6369
|
evidence,
|
|
6367
6370
|
promptHint
|
|
6368
6371
|
};
|
|
6369
6372
|
}
|
|
6370
|
-
function manualItem(id,
|
|
6373
|
+
function manualItem(id, label2, promptHint) {
|
|
6371
6374
|
return {
|
|
6372
6375
|
id,
|
|
6373
|
-
label,
|
|
6376
|
+
label: label2,
|
|
6374
6377
|
status: "manual",
|
|
6375
6378
|
evidence: [],
|
|
6376
6379
|
promptHint
|
|
6377
6380
|
};
|
|
6378
6381
|
}
|
|
6379
|
-
function secretSafetyItem(ctx, id,
|
|
6382
|
+
function secretSafetyItem(ctx, id, label2, pattern, promptHint) {
|
|
6380
6383
|
const exposed = ctx.files.filter((file) => isClientExecutedPath2(file.normalizedPath) && pattern.test(file.content));
|
|
6381
6384
|
return {
|
|
6382
6385
|
id,
|
|
6383
|
-
label,
|
|
6386
|
+
label: label2,
|
|
6384
6387
|
status: exposed.length > 0 ? "missing" : "passed",
|
|
6385
6388
|
evidence: exposed.slice(0, 4).map((file) => `unsafe reference: ${file.path}`),
|
|
6386
6389
|
promptHint
|
|
@@ -6405,13 +6408,13 @@ function envEvidence2(ctx, patterns) {
|
|
|
6405
6408
|
return evidence.slice(0, 4);
|
|
6406
6409
|
}
|
|
6407
6410
|
function pathEvidence2(ctx, pattern) {
|
|
6408
|
-
return ctx.pathBlob.split(/\r?\n/).filter((
|
|
6411
|
+
return ctx.pathBlob.split(/\r?\n/).filter((path3) => pattern.test(path3)).slice(0, 4).map((path3) => `file: ${path3}`);
|
|
6409
6412
|
}
|
|
6410
6413
|
function fileEvidence(ctx, pattern) {
|
|
6411
6414
|
return ctx.files.filter((file) => pattern.test(file.path) || pattern.test(file.content)).slice(0, 4).map((file) => `file: ${file.path}`);
|
|
6412
6415
|
}
|
|
6413
|
-
function isClientExecutedPath2(
|
|
6414
|
-
return /\.(tsx|jsx)$/.test(
|
|
6416
|
+
function isClientExecutedPath2(path3) {
|
|
6417
|
+
return /\.(tsx|jsx)$/.test(path3) || /(^|\/)(components|pages|app|client|frontend|web)\//.test(path3) || /\.client\.[jt]sx?$/.test(path3);
|
|
6415
6418
|
}
|
|
6416
6419
|
|
|
6417
6420
|
// ../../src/station/verification.ts
|
|
@@ -6540,27 +6543,27 @@ function emptyArea(area) {
|
|
|
6540
6543
|
manual: []
|
|
6541
6544
|
};
|
|
6542
6545
|
}
|
|
6543
|
-
function normalizePath4(
|
|
6544
|
-
return
|
|
6546
|
+
function normalizePath4(path3) {
|
|
6547
|
+
return path3.replace(/\\/g, "/").replace(/^[\s\u2500\u2502\u2514\u251c>*+-]+/u, "").trim().toLowerCase();
|
|
6545
6548
|
}
|
|
6546
|
-
function addFound(area,
|
|
6547
|
-
addItem(area, "found",
|
|
6549
|
+
function addFound(area, label2, source, detail) {
|
|
6550
|
+
addItem(area, "found", label2, source, detail);
|
|
6548
6551
|
}
|
|
6549
|
-
function addMissing(area,
|
|
6550
|
-
addItem(area, "missing",
|
|
6552
|
+
function addMissing(area, label2, source, detail) {
|
|
6553
|
+
addItem(area, "missing", label2, source, detail);
|
|
6551
6554
|
}
|
|
6552
|
-
function addManual(area,
|
|
6553
|
-
addItem(area, "manual",
|
|
6555
|
+
function addManual(area, label2, source, detail) {
|
|
6556
|
+
addItem(area, "manual", label2, source, detail);
|
|
6554
6557
|
}
|
|
6555
|
-
function addItem(area, status,
|
|
6556
|
-
const item3 = compactItem(
|
|
6558
|
+
function addItem(area, status, label2, source, detail) {
|
|
6559
|
+
const item3 = compactItem(label2, status, source, detail);
|
|
6557
6560
|
const bucket = area[status];
|
|
6558
6561
|
if (!bucket.some((existing) => existing.label === item3.label)) {
|
|
6559
6562
|
bucket.push(item3);
|
|
6560
6563
|
}
|
|
6561
6564
|
}
|
|
6562
|
-
function compactItem(
|
|
6563
|
-
return detail ? { label, status, source, detail } : { label, status, source };
|
|
6565
|
+
function compactItem(label2, status, source, detail) {
|
|
6566
|
+
return detail ? { label: label2, status, source, detail } : { label: label2, status, source };
|
|
6564
6567
|
}
|
|
6565
6568
|
function hasDep(ctx, patterns) {
|
|
6566
6569
|
return patterns.some((pattern) => {
|
|
@@ -6569,7 +6572,7 @@ function hasDep(ctx, patterns) {
|
|
|
6569
6572
|
});
|
|
6570
6573
|
}
|
|
6571
6574
|
function hasPath(ctx, patterns) {
|
|
6572
|
-
return [...ctx.paths].some((
|
|
6575
|
+
return [...ctx.paths].some((path3) => !ctx.secretPaths.has(path3) && patterns.some((pattern) => pattern.test(path3)));
|
|
6573
6576
|
}
|
|
6574
6577
|
function hasContent(ctx, patterns) {
|
|
6575
6578
|
return patterns.some((pattern) => pattern.test(ctx.contents));
|
|
@@ -7138,7 +7141,7 @@ var mockGitHubVerifier = {
|
|
|
7138
7141
|
providerObservationMet: false,
|
|
7139
7142
|
fixType: "mcp-connect",
|
|
7140
7143
|
severity: "warning",
|
|
7141
|
-
repoSignals: workflows.map((
|
|
7144
|
+
repoSignals: workflows.map((path3) => `workflow: ${path3}`),
|
|
7142
7145
|
providerSignals: ["GitHub Actions API or MCP"],
|
|
7143
7146
|
requiredEvidence: ["Latest workflow run status on default branch"]
|
|
7144
7147
|
}),
|
|
@@ -7193,7 +7196,7 @@ var mockGitHubVerifier = {
|
|
|
7193
7196
|
providerActual: "Not verified (mock \u2014 connect GitHub MCP read-only)",
|
|
7194
7197
|
severity: "warning",
|
|
7195
7198
|
suggestedFix: "mcp-connect",
|
|
7196
|
-
evidenceRefs: workflows.map((
|
|
7199
|
+
evidenceRefs: workflows.map((path3) => `workflow: ${path3}`)
|
|
7197
7200
|
}
|
|
7198
7201
|
];
|
|
7199
7202
|
}
|
|
@@ -7992,15 +7995,15 @@ async function runProjectScan(options) {
|
|
|
7992
7995
|
|
|
7993
7996
|
// src/artifacts.ts
|
|
7994
7997
|
var import_promises5 = require("node:fs/promises");
|
|
7995
|
-
var
|
|
7998
|
+
var import_node_path8 = require("node:path");
|
|
7996
7999
|
|
|
7997
8000
|
// src/capabilities/classify.ts
|
|
7998
|
-
function gapText(
|
|
7999
|
-
return `${
|
|
8001
|
+
function gapText(gap2) {
|
|
8002
|
+
return `${gap2.id} ${gap2.title} ${gap2.detail} ${gap2.primaryMapCategory}`.toLowerCase();
|
|
8000
8003
|
}
|
|
8001
8004
|
function statusForGaps(gaps) {
|
|
8002
|
-
if (gaps.some((
|
|
8003
|
-
if (gaps.some((
|
|
8005
|
+
if (gaps.some((gap2) => gap2.severity === "critical")) return "critical";
|
|
8006
|
+
if (gaps.some((gap2) => gap2.severity === "warning")) return "warning";
|
|
8004
8007
|
if (gaps.length > 0) return "warning";
|
|
8005
8008
|
return "unknown";
|
|
8006
8009
|
}
|
|
@@ -8020,12 +8023,12 @@ function capabilityFromArea(area) {
|
|
|
8020
8023
|
// src/capabilities/database.ts
|
|
8021
8024
|
var databasePack = {
|
|
8022
8025
|
key: "database",
|
|
8023
|
-
classify(
|
|
8024
|
-
const text = gapText(
|
|
8026
|
+
classify(gap2) {
|
|
8027
|
+
const text = gapText(gap2);
|
|
8025
8028
|
return /database|supabase|rls|migration|postgres|pooler|query/.test(text);
|
|
8026
8029
|
},
|
|
8027
|
-
riskTags(
|
|
8028
|
-
const text = gapText(
|
|
8030
|
+
riskTags(gap2) {
|
|
8031
|
+
const text = gapText(gap2);
|
|
8029
8032
|
return [
|
|
8030
8033
|
text.includes("rls") ? "rls" : "",
|
|
8031
8034
|
text.includes("migration") ? "migration" : "",
|
|
@@ -8037,12 +8040,12 @@ var databasePack = {
|
|
|
8037
8040
|
// src/capabilities/payments.ts
|
|
8038
8041
|
var paymentsPack = {
|
|
8039
8042
|
key: "payments",
|
|
8040
|
-
classify(
|
|
8041
|
-
const text = gapText(
|
|
8043
|
+
classify(gap2) {
|
|
8044
|
+
const text = gapText(gap2);
|
|
8042
8045
|
return /payment|stripe|checkout|billing|entitlement|subscription|refund|cancel/.test(text);
|
|
8043
8046
|
},
|
|
8044
|
-
riskTags(
|
|
8045
|
-
const text = gapText(
|
|
8047
|
+
riskTags(gap2) {
|
|
8048
|
+
const text = gapText(gap2);
|
|
8046
8049
|
return [
|
|
8047
8050
|
text.includes("entitlement") ? "entitlement-source" : "",
|
|
8048
8051
|
text.includes("checkout") ? "checkout-flow" : "",
|
|
@@ -8054,12 +8057,12 @@ var paymentsPack = {
|
|
|
8054
8057
|
// src/capabilities/scaling.ts
|
|
8055
8058
|
var scalingPack = {
|
|
8056
8059
|
key: "scaling",
|
|
8057
|
-
classify(
|
|
8058
|
-
const text = gapText(
|
|
8060
|
+
classify(gap2) {
|
|
8061
|
+
const text = gapText(gap2);
|
|
8059
8062
|
return /serverless|vercel|pooler|rate limit|rate-limit|cache|queue|cron|worker|runtime|connection/.test(text);
|
|
8060
8063
|
},
|
|
8061
|
-
riskTags(
|
|
8062
|
-
const text = gapText(
|
|
8064
|
+
riskTags(gap2) {
|
|
8065
|
+
const text = gapText(gap2);
|
|
8063
8066
|
return [
|
|
8064
8067
|
text.includes("serverless") || text.includes("vercel") ? "serverless" : "",
|
|
8065
8068
|
text.includes("pooler") || text.includes("connection") ? "db-connection" : "",
|
|
@@ -8071,12 +8074,12 @@ var scalingPack = {
|
|
|
8071
8074
|
// src/capabilities/security.ts
|
|
8072
8075
|
var securityPack = {
|
|
8073
8076
|
key: "security",
|
|
8074
|
-
classify(
|
|
8075
|
-
const text = gapText(
|
|
8077
|
+
classify(gap2) {
|
|
8078
|
+
const text = gapText(gap2);
|
|
8076
8079
|
return /secret|service role|token|api key|auth|authorization|browser-exposed|cors|csrf|session/.test(text);
|
|
8077
8080
|
},
|
|
8078
|
-
riskTags(
|
|
8079
|
-
const text = gapText(
|
|
8081
|
+
riskTags(gap2) {
|
|
8082
|
+
const text = gapText(gap2);
|
|
8080
8083
|
return [
|
|
8081
8084
|
text.includes("secret") || text.includes("api key") || text.includes("service role") ? "secret-boundary" : "",
|
|
8082
8085
|
text.includes("auth") || text.includes("authorization") ? "auth-boundary" : "",
|
|
@@ -8088,12 +8091,12 @@ var securityPack = {
|
|
|
8088
8091
|
// src/capabilities/webhooks.ts
|
|
8089
8092
|
var webhooksPack = {
|
|
8090
8093
|
key: "webhooks",
|
|
8091
|
-
classify(
|
|
8092
|
-
const text = gapText(
|
|
8094
|
+
classify(gap2) {
|
|
8095
|
+
const text = gapText(gap2);
|
|
8093
8096
|
return /webhook|signature|idempotency|retry|replay|dead-letter/.test(text);
|
|
8094
8097
|
},
|
|
8095
|
-
riskTags(
|
|
8096
|
-
const text = gapText(
|
|
8098
|
+
riskTags(gap2) {
|
|
8099
|
+
const text = gapText(gap2);
|
|
8097
8100
|
return [
|
|
8098
8101
|
text.includes("signature") ? "signature" : "",
|
|
8099
8102
|
text.includes("idempotency") ? "idempotency" : "",
|
|
@@ -8104,10 +8107,10 @@ var webhooksPack = {
|
|
|
8104
8107
|
|
|
8105
8108
|
// src/capabilities/index.ts
|
|
8106
8109
|
var PACKS = [scalingPack, securityPack, webhooksPack, paymentsPack, databasePack];
|
|
8107
|
-
function classifyGapCapability(
|
|
8108
|
-
const direct = PACKS.find((pack) => pack.classify(
|
|
8110
|
+
function classifyGapCapability(gap2) {
|
|
8111
|
+
const direct = PACKS.find((pack) => pack.classify(gap2));
|
|
8109
8112
|
if (direct) return direct.key;
|
|
8110
|
-
return capabilityFromArea(String(
|
|
8113
|
+
return capabilityFromArea(String(gap2.primaryMapCategory)) ?? "security";
|
|
8111
8114
|
}
|
|
8112
8115
|
function summarizeCapabilities(gaps) {
|
|
8113
8116
|
const result = Object.fromEntries(
|
|
@@ -8117,13 +8120,13 @@ function summarizeCapabilities(gaps) {
|
|
|
8117
8120
|
])
|
|
8118
8121
|
);
|
|
8119
8122
|
for (const pack of PACKS) {
|
|
8120
|
-
const matching = gaps.filter((
|
|
8123
|
+
const matching = gaps.filter((gap2) => classifyGapCapability(gap2) === pack.key);
|
|
8121
8124
|
result[pack.key] = {
|
|
8122
8125
|
key: pack.key,
|
|
8123
8126
|
status: statusForGaps(matching),
|
|
8124
|
-
topGapIds: matching.slice(0, 5).map((
|
|
8127
|
+
topGapIds: matching.slice(0, 5).map((gap2) => gap2.id),
|
|
8125
8128
|
evidenceCount: matching.length,
|
|
8126
|
-
riskTags: unique2(matching.flatMap((
|
|
8129
|
+
riskTags: unique2(matching.flatMap((gap2) => pack.riskTags(gap2)))
|
|
8127
8130
|
};
|
|
8128
8131
|
}
|
|
8129
8132
|
return result;
|
|
@@ -8131,8 +8134,8 @@ function summarizeCapabilities(gaps) {
|
|
|
8131
8134
|
|
|
8132
8135
|
// src/contracts/contextMap.ts
|
|
8133
8136
|
function generateContextMap(artifact) {
|
|
8134
|
-
const criticalCount = artifact.gaps.filter((
|
|
8135
|
-
const warningCount = artifact.gaps.filter((
|
|
8137
|
+
const criticalCount = artifact.gaps.filter((gap2) => gap2.severity === "critical").length;
|
|
8138
|
+
const warningCount = artifact.gaps.filter((gap2) => gap2.severity === "warning").length;
|
|
8136
8139
|
return {
|
|
8137
8140
|
$schema: "https://viberaven.dev/schemas/context-map.schema.json",
|
|
8138
8141
|
schemaVersion: "v1",
|
|
@@ -8165,12 +8168,12 @@ function generateContextMap(artifact) {
|
|
|
8165
8168
|
warningCount,
|
|
8166
8169
|
providerDashboardChecksRequired: true
|
|
8167
8170
|
},
|
|
8168
|
-
topGaps: artifact.gaps.slice(0, 10).map((
|
|
8169
|
-
id:
|
|
8170
|
-
severity:
|
|
8171
|
-
title:
|
|
8172
|
-
area: String(
|
|
8173
|
-
promptCommand: promptGapCommand(
|
|
8171
|
+
topGaps: artifact.gaps.slice(0, 10).map((gap2) => ({
|
|
8172
|
+
id: gap2.id,
|
|
8173
|
+
severity: gap2.severity,
|
|
8174
|
+
title: gap2.title,
|
|
8175
|
+
area: String(gap2.primaryMapCategory),
|
|
8176
|
+
promptCommand: promptGapCommand(gap2.id)
|
|
8174
8177
|
}))
|
|
8175
8178
|
};
|
|
8176
8179
|
}
|
|
@@ -8185,10 +8188,10 @@ function runIdFrom(scannedAt) {
|
|
|
8185
8188
|
return `vr_${scannedAt.replace(/\D/g, "").slice(0, 14) || "scan"}`;
|
|
8186
8189
|
}
|
|
8187
8190
|
function generateGateResult(artifact, options = {}) {
|
|
8188
|
-
const criticalCount = artifact.gaps.filter((
|
|
8189
|
-
const warningCount = artifact.gaps.filter((
|
|
8191
|
+
const criticalCount = artifact.gaps.filter((gap2) => gap2.severity === "critical").length;
|
|
8192
|
+
const warningCount = artifact.gaps.filter((gap2) => gap2.severity === "warning").length;
|
|
8190
8193
|
const capabilities = summarizeCapabilities(artifact.gaps);
|
|
8191
|
-
const topGapIds = artifact.gaps.slice(0, 10).map((
|
|
8194
|
+
const topGapIds = artifact.gaps.slice(0, 10).map((gap2) => gap2.id);
|
|
8192
8195
|
const firstGapId = topGapIds[0];
|
|
8193
8196
|
return {
|
|
8194
8197
|
$schema: "https://viberaven.dev/schemas/gate-result.schema.json",
|
|
@@ -8237,24 +8240,24 @@ function generateGateResult(artifact, options = {}) {
|
|
|
8237
8240
|
}
|
|
8238
8241
|
|
|
8239
8242
|
// src/contracts/gapEvidence.ts
|
|
8240
|
-
function summarizeGap(
|
|
8241
|
-
return `${
|
|
8243
|
+
function summarizeGap(gap2) {
|
|
8244
|
+
return `${gap2.title}. See the tasklist and original scan for redacted detail.`;
|
|
8242
8245
|
}
|
|
8243
8246
|
function generateGapEvidenceFiles(artifact) {
|
|
8244
|
-
return artifact.gaps.slice(0, 50).map((
|
|
8245
|
-
path: `.viberaven/gaps/${
|
|
8247
|
+
return artifact.gaps.slice(0, 50).map((gap2) => ({
|
|
8248
|
+
path: `.viberaven/gaps/${gap2.id}.json`,
|
|
8246
8249
|
content: {
|
|
8247
8250
|
$schema: "https://viberaven.dev/schemas/gap.schema.json",
|
|
8248
8251
|
schemaVersion: "v1",
|
|
8249
|
-
id:
|
|
8250
|
-
severity:
|
|
8251
|
-
capability: classifyGapCapability(
|
|
8252
|
-
title:
|
|
8253
|
-
summary: summarizeGap(
|
|
8254
|
-
evidence: [{ kind: String(
|
|
8252
|
+
id: gap2.id,
|
|
8253
|
+
severity: gap2.severity,
|
|
8254
|
+
capability: classifyGapCapability(gap2),
|
|
8255
|
+
title: gap2.title,
|
|
8256
|
+
summary: summarizeGap(gap2),
|
|
8257
|
+
evidence: [{ kind: String(gap2.primaryMapCategory), redacted: true }],
|
|
8255
8258
|
commands: {
|
|
8256
|
-
prompt: promptGapCommand(
|
|
8257
|
-
healPlan: healPlanGapCommand(
|
|
8259
|
+
prompt: promptGapCommand(gap2.id),
|
|
8260
|
+
healPlan: healPlanGapCommand(gap2.id),
|
|
8258
8261
|
verify: PUBLIC_VERIFY_COMMAND
|
|
8259
8262
|
},
|
|
8260
8263
|
providerBoundary: {
|
|
@@ -8303,39 +8306,6 @@ var PRODUCTION_MAP_CATEGORY_KEYS_ALL = PRODUCTION_MAP_LANES.map(
|
|
|
8303
8306
|
(lane) => lane.extensionKey
|
|
8304
8307
|
);
|
|
8305
8308
|
|
|
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
|
-
|
|
8339
8309
|
// src/playbooks/loadPlaybook.ts
|
|
8340
8310
|
var import_node_fs5 = require("node:fs");
|
|
8341
8311
|
var import_promises3 = require("node:fs/promises");
|
|
@@ -8446,8 +8416,8 @@ async function loadPlaybook(provider2) {
|
|
|
8446
8416
|
`Unknown provider "${provider2}". Available: ${PLAYBOOK_PROVIDERS.join(", ")}`
|
|
8447
8417
|
);
|
|
8448
8418
|
}
|
|
8449
|
-
const
|
|
8450
|
-
const raw = JSON.parse(await (0, import_promises3.readFile)(
|
|
8419
|
+
const path3 = (0, import_node_path5.join)(playbooksRoot(), `${normalized}.json`);
|
|
8420
|
+
const raw = JSON.parse(await (0, import_promises3.readFile)(path3, "utf-8"));
|
|
8451
8421
|
const playbook = parsePlaybook(raw);
|
|
8452
8422
|
if (playbook.provider !== normalized) {
|
|
8453
8423
|
throw new Error(`Playbook file ${normalized}.json has mismatched provider field`);
|
|
@@ -8461,8 +8431,8 @@ function loadPlaybookSync(provider2) {
|
|
|
8461
8431
|
`Unknown provider "${provider2}". Available: ${PLAYBOOK_PROVIDERS.join(", ")}`
|
|
8462
8432
|
);
|
|
8463
8433
|
}
|
|
8464
|
-
const
|
|
8465
|
-
const raw = JSON.parse((0, import_node_fs5.readFileSync)(
|
|
8434
|
+
const path3 = (0, import_node_path5.join)(playbooksRoot(), `${normalized}.json`);
|
|
8435
|
+
const raw = JSON.parse((0, import_node_fs5.readFileSync)(path3, "utf-8"));
|
|
8466
8436
|
const playbook = parsePlaybook(raw);
|
|
8467
8437
|
if (playbook.provider !== normalized) {
|
|
8468
8438
|
throw new Error(`Playbook file ${normalized}.json has mismatched provider field`);
|
|
@@ -8470,28 +8440,35 @@ function loadPlaybookSync(provider2) {
|
|
|
8470
8440
|
return playbook;
|
|
8471
8441
|
}
|
|
8472
8442
|
|
|
8473
|
-
// src/
|
|
8474
|
-
|
|
8475
|
-
|
|
8443
|
+
// src/providers/envKeys.ts
|
|
8444
|
+
var PROVIDER_DEFAULT_KEYS = {
|
|
8445
|
+
stripe: ["STRIPE_SECRET_KEY", "STRIPE_WEBHOOK_SECRET"],
|
|
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;
|
|
8476
8464
|
}
|
|
8477
|
-
function
|
|
8478
|
-
const
|
|
8479
|
-
|
|
8480
|
-
|
|
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
|
-
}
|
|
8465
|
+
function envKeysForGap(gapId, provider2) {
|
|
8466
|
+
const gapKeys = GAP_KEYS[gapId];
|
|
8467
|
+
if (gapKeys) {
|
|
8468
|
+
return [...gapKeys];
|
|
8493
8469
|
}
|
|
8494
|
-
|
|
8470
|
+
const providerKeys = PROVIDER_DEFAULT_KEYS[normalizeProvider2(provider2)];
|
|
8471
|
+
return providerKeys ? [...providerKeys] : [];
|
|
8495
8472
|
}
|
|
8496
8473
|
|
|
8497
8474
|
// src/tui/menu.ts
|
|
@@ -8545,10 +8522,10 @@ function formatTopGapsList(artifact, limit = 10) {
|
|
|
8545
8522
|
if (sorted.length === 0) {
|
|
8546
8523
|
return "No gaps found \u2014 production core looks solid.";
|
|
8547
8524
|
}
|
|
8548
|
-
return sorted.slice(0, limit).map((
|
|
8549
|
-
const severity =
|
|
8550
|
-
const area =
|
|
8551
|
-
return `${index + 1}. [${severity}] ${area} ${
|
|
8525
|
+
return sorted.slice(0, limit).map((gap2, index) => {
|
|
8526
|
+
const severity = gap2.severity.toUpperCase().padEnd(8);
|
|
8527
|
+
const area = gap2.primaryMapCategory.padEnd(12);
|
|
8528
|
+
return `${index + 1}. [${severity}] ${area} ${gap2.title}`;
|
|
8552
8529
|
}).join("\n");
|
|
8553
8530
|
}
|
|
8554
8531
|
async function loadLastArtifact(startDir) {
|
|
@@ -8556,405 +8533,673 @@ async function loadLastArtifact(startDir) {
|
|
|
8556
8533
|
if (!workspace) {
|
|
8557
8534
|
throw new ScanNotFoundError(needsScanMessage(startDir));
|
|
8558
8535
|
}
|
|
8559
|
-
const
|
|
8536
|
+
const path3 = (0, import_node_path6.join)(getProjectArtifactsDir(workspace), "last-scan.json");
|
|
8560
8537
|
try {
|
|
8561
|
-
const raw = await (0, import_promises4.readFile)(
|
|
8538
|
+
const raw = await (0, import_promises4.readFile)(path3, "utf-8");
|
|
8562
8539
|
return JSON.parse(raw);
|
|
8563
8540
|
} catch {
|
|
8564
8541
|
throw new ScanNotFoundError(needsScanMessage(startDir));
|
|
8565
8542
|
}
|
|
8566
8543
|
}
|
|
8567
8544
|
|
|
8568
|
-
// src/
|
|
8569
|
-
var
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8545
|
+
// src/buildTaskList.ts
|
|
8546
|
+
var KNOWN_RECIPE_GAP_IDS = /* @__PURE__ */ new Set([
|
|
8547
|
+
// emptyCatch (original recipe)
|
|
8548
|
+
"empty-catch",
|
|
8549
|
+
"empty_catch",
|
|
8550
|
+
"emptyCatch",
|
|
8551
|
+
// W3 env-add recipes
|
|
8552
|
+
"auth_secret_missing",
|
|
8553
|
+
"node_env_not_set",
|
|
8554
|
+
"database_url_missing",
|
|
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");
|
|
8581
8571
|
}
|
|
8582
|
-
function
|
|
8583
|
-
const
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
);
|
|
8587
|
-
if (
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
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;
|
|
8594
8592
|
}
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
openUrl = playbook.steps[0]?.openUrl;
|
|
8602
|
-
} catch {
|
|
8603
|
-
openUrl = void 0;
|
|
8604
|
-
}
|
|
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;
|
|
8605
8599
|
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
8600
|
provider: provider2,
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
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
|
|
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
|
|
8645
8609
|
};
|
|
8610
|
+
} catch {
|
|
8611
|
+
return void 0;
|
|
8646
8612
|
}
|
|
8647
|
-
return {
|
|
8648
|
-
type: "done",
|
|
8649
|
-
title: "No blockers found",
|
|
8650
|
-
detail: "Re-scan after changes to confirm production readiness."
|
|
8651
|
-
};
|
|
8652
8613
|
}
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
}
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
if (sev !== 0) {
|
|
8673
|
-
return sev;
|
|
8614
|
+
function unlockedKeys(artifact) {
|
|
8615
|
+
const keys = artifact.usage?.unlockedMapCategoryKeys ?? FREE_TRIAL_UNLOCKED_MAP_CATEGORY_KEYS;
|
|
8616
|
+
return new Set(keys);
|
|
8617
|
+
}
|
|
8618
|
+
function buildTaskList(artifact) {
|
|
8619
|
+
const unlocked = unlockedKeys(artifact);
|
|
8620
|
+
const sorted = sortGapsByPriority(artifact.gaps);
|
|
8621
|
+
return sorted.map((gap2, index) => {
|
|
8622
|
+
const id = `TASK-${String(index + 1).padStart(3, "0")}`;
|
|
8623
|
+
const isLocked = !unlocked.has(gap2.primaryMapCategory);
|
|
8624
|
+
let fixType;
|
|
8625
|
+
if (isLocked) {
|
|
8626
|
+
fixType = "upgrade-required";
|
|
8627
|
+
} else if (hasRecipe(gap2.id)) {
|
|
8628
|
+
fixType = "repo-code";
|
|
8629
|
+
} else if (hasPlaybook(gap2)) {
|
|
8630
|
+
fixType = "provider-action";
|
|
8631
|
+
} else {
|
|
8632
|
+
fixType = "manual-verify";
|
|
8674
8633
|
}
|
|
8675
|
-
|
|
8634
|
+
const base = {
|
|
8635
|
+
id,
|
|
8636
|
+
gapId: gap2.id,
|
|
8637
|
+
severity: gap2.severity,
|
|
8638
|
+
fixType,
|
|
8639
|
+
title: gap2.title,
|
|
8640
|
+
verifyCommand: PUBLIC_VERIFY_COMMAND,
|
|
8641
|
+
requiresUserAction: fixType !== "repo-code"
|
|
8642
|
+
};
|
|
8643
|
+
if (fixType === "repo-code") {
|
|
8644
|
+
base.mcpTool = "viberaven_heal_apply";
|
|
8645
|
+
base.mcpArgs = { gap: gap2.id, yes: true };
|
|
8646
|
+
base.requiresUserAction = false;
|
|
8647
|
+
if (gap2.copyPrompt) {
|
|
8648
|
+
base.exactFix = gap2.copyPrompt.trim();
|
|
8649
|
+
}
|
|
8650
|
+
} else if (fixType === "provider-action") {
|
|
8651
|
+
try {
|
|
8652
|
+
const provider2 = gapToPlaybookProvider(gap2);
|
|
8653
|
+
if (provider2) {
|
|
8654
|
+
const pa = buildProviderAction(gap2, provider2);
|
|
8655
|
+
if (pa) {
|
|
8656
|
+
base.providerAction = pa;
|
|
8657
|
+
base.action = pa.exactStep;
|
|
8658
|
+
}
|
|
8659
|
+
}
|
|
8660
|
+
} catch {
|
|
8661
|
+
base.fixType = "manual-verify";
|
|
8662
|
+
}
|
|
8663
|
+
base.requiresUserAction = true;
|
|
8664
|
+
}
|
|
8665
|
+
return base;
|
|
8676
8666
|
});
|
|
8677
8667
|
}
|
|
8678
|
-
function
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
lines.push("# VibeRaven agent summary", "");
|
|
8682
|
-
lines.push(`Scanned: \`${artifact.workspacePath}\``);
|
|
8683
|
-
lines.push(`At: ${artifact.scannedAt}`);
|
|
8684
|
-
lines.push(
|
|
8685
|
-
`Production core: **${artifact.productionCorePercent}%** \xB7 Model score: **${artifact.score}** (${artifact.scoreLabel})`
|
|
8686
|
-
);
|
|
8687
|
-
lines.push("");
|
|
8688
|
-
const next = resolveNextAction(artifact);
|
|
8689
|
-
lines.push("## Next action", "");
|
|
8690
|
-
lines.push(`**${next.title}** \u2014 ${next.detail}`);
|
|
8691
|
-
if (next.command) {
|
|
8692
|
-
lines.push(`Command: \`${next.command}\``);
|
|
8693
|
-
}
|
|
8694
|
-
if (next.upgradeUrl) {
|
|
8695
|
-
lines.push(`Upgrade: ${next.upgradeUrl}`);
|
|
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";
|
|
8696
8671
|
}
|
|
8697
|
-
lines
|
|
8698
|
-
const
|
|
8699
|
-
|
|
8700
|
-
lines.push(
|
|
8701
|
-
lines.push(
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
lines.push("");
|
|
8705
|
-
for (const key of LOCKED_LANE_KEYS) {
|
|
8706
|
-
lines.push(`- ${key}`);
|
|
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}\` `);
|
|
8707
8679
|
}
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
lines.push("");
|
|
8711
|
-
}
|
|
8712
|
-
lines.push("## Suggested stack", "");
|
|
8713
|
-
lines.push(
|
|
8714
|
-
"React + Tailwind + shadcn/ui + Supabase + Vercel (agent-default stack for lowest launch friction)"
|
|
8715
|
-
);
|
|
8716
|
-
lines.push("");
|
|
8717
|
-
lines.push("## Summary");
|
|
8718
|
-
lines.push(artifact.summary || "_No summary returned._");
|
|
8719
|
-
lines.push("");
|
|
8720
|
-
lines.push("## Mission map (repo wiring)");
|
|
8721
|
-
lines.push("");
|
|
8722
|
-
lines.push("| Area | Provider | Readiness | Notes |");
|
|
8723
|
-
lines.push("|------|----------|-----------|-------|");
|
|
8724
|
-
for (const area of artifact.missionGraph.areas ?? []) {
|
|
8725
|
-
for (const mission of area.providerMissions) {
|
|
8726
|
-
const failed = mission.checks.filter(
|
|
8727
|
-
(c) => c.status === "missing" || c.status === "failed" || c.status === "needs-connection"
|
|
8728
|
-
).length;
|
|
8729
|
-
const notes = failed > 0 ? `${failed} open check${failed === 1 ? "" : "s"}` : `${mission.readinessPercent}% repo checks`;
|
|
8730
|
-
lines.push(
|
|
8731
|
-
`| ${area.label} | ${mission.providerLabel} | ${mission.readinessPercent}% | ${notes} |`
|
|
8732
|
-
);
|
|
8680
|
+
if (task.action) {
|
|
8681
|
+
lines.push(`**Action:** ${task.action} `);
|
|
8733
8682
|
}
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
lines.push(
|
|
8742
|
-
`${index + 1}. **${gap.title}** (\`${gap.id}\`, ${gap.severity}, map: \`${gap.primaryMapCategory}\`)`
|
|
8743
|
-
);
|
|
8744
|
-
lines.push(` - ${gap.detail}`);
|
|
8745
|
-
lines.push(` - Command: \`viberaven prompt --gap ${gap.id}\``);
|
|
8746
|
-
if (gap.copyPrompt) {
|
|
8747
|
-
lines.push(` - Prompt: ${gap.copyPrompt}`);
|
|
8748
|
-
}
|
|
8749
|
-
});
|
|
8750
|
-
}
|
|
8751
|
-
const manualChecks = (artifact.missionGraph.areas ?? []).flatMap(
|
|
8752
|
-
(area) => area.providerMissions.flatMap(
|
|
8753
|
-
(mission) => mission.checks.filter(
|
|
8754
|
-
(check) => check.evidenceClass === "manual-dashboard" || check.evidenceClass === "mcp-verifier" || check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.status === "needs-connection" || check.status === "unknown"
|
|
8755
|
-
).map((check) => ({ area: area.label, provider: mission.providerLabel, check }))
|
|
8756
|
-
)
|
|
8757
|
-
);
|
|
8758
|
-
lines.push("");
|
|
8759
|
-
lines.push("## Human-provider actions");
|
|
8760
|
-
if (manualChecks.length === 0) {
|
|
8761
|
-
lines.push("_No manual provider actions were identified in this scan._");
|
|
8762
|
-
} else {
|
|
8763
|
-
manualChecks.slice(0, 8).forEach((item3, index) => {
|
|
8764
|
-
lines.push(`${index + 1}. **${item3.check.label}** (${item3.area} / ${item3.provider})`);
|
|
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) {
|
|
8765
8690
|
lines.push(
|
|
8766
|
-
|
|
8691
|
+
`**MCP:** \`${task.mcpTool} ${JSON.stringify(task.mcpArgs)}\` `
|
|
8767
8692
|
);
|
|
8768
|
-
}
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
if (artifact.usage) {
|
|
8782
|
-
lines.push("## Account usage");
|
|
8783
|
-
lines.push(
|
|
8784
|
-
`- Plan: ${artifact.usage.plan} \xB7 Scans used: ${artifact.usage.used}/${artifact.usage.limit} (${artifact.usage.period})`
|
|
8785
|
-
);
|
|
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
|
+
}
|
|
8786
8706
|
lines.push("");
|
|
8787
8707
|
}
|
|
8788
8708
|
return `${lines.join("\n")}
|
|
8789
8709
|
`;
|
|
8790
8710
|
}
|
|
8791
8711
|
|
|
8792
|
-
// src/
|
|
8793
|
-
var
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
"
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
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");
|
|
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
|
+
);
|
|
8818
8734
|
}
|
|
8819
|
-
function
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
if (
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
if (
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
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;
|
|
8831
8755
|
}
|
|
8832
|
-
function
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
|
|
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) {
|
|
8838
8782
|
return false;
|
|
8839
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 };
|
|
8840
8836
|
}
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
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
8846
|
return {
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
8855
|
-
doneSignal: `${step.title} step completed`,
|
|
8856
|
-
verifyCommand: PUBLIC_VERIFY_COMMAND,
|
|
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",
|
|
8857
8855
|
actionClass: "manual_provider_step",
|
|
8858
|
-
|
|
8859
|
-
manualFallback: `Open ${step.openUrl ?? `https://${provider2}.com`} and complete: ${step.instruction}`,
|
|
8860
|
-
copyValues: []
|
|
8856
|
+
command: promptGapCommand(task.gapId)
|
|
8861
8857
|
};
|
|
8862
|
-
} catch {
|
|
8863
|
-
return void 0;
|
|
8864
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";
|
|
8865
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
|
+
};
|
|
8866
8995
|
function unlockedKeys2(artifact) {
|
|
8867
8996
|
const keys = artifact.usage?.unlockedMapCategoryKeys ?? FREE_TRIAL_UNLOCKED_MAP_CATEGORY_KEYS;
|
|
8868
8997
|
return new Set(keys);
|
|
8869
8998
|
}
|
|
8870
|
-
function
|
|
8999
|
+
function resolveNextAction(artifact) {
|
|
8871
9000
|
const unlocked = unlockedKeys2(artifact);
|
|
8872
|
-
const
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
}
|
|
8882
|
-
|
|
8883
|
-
|
|
8884
|
-
|
|
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;
|
|
8885
9021
|
}
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
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
|
|
8894
9030
|
};
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
|
|
8915
|
-
|
|
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;
|
|
8916
9091
|
}
|
|
8917
|
-
return
|
|
9092
|
+
return a.title.localeCompare(b3.title);
|
|
8918
9093
|
});
|
|
8919
9094
|
}
|
|
8920
|
-
function
|
|
8921
|
-
|
|
8922
|
-
|
|
9095
|
+
function generateAgentSummary(artifact) {
|
|
9096
|
+
const lines = [];
|
|
9097
|
+
const topGaps = sortGaps(artifact.gaps).slice(0, 8);
|
|
9098
|
+
lines.push("# VibeRaven agent summary", "");
|
|
9099
|
+
lines.push(`Scanned: \`${artifact.workspacePath}\``);
|
|
9100
|
+
lines.push(`At: ${artifact.scannedAt}`);
|
|
9101
|
+
lines.push(
|
|
9102
|
+
`Production core: **${artifact.productionCorePercent}%** \xB7 Model score: **${artifact.score}** (${artifact.scoreLabel})`
|
|
9103
|
+
);
|
|
9104
|
+
lines.push("");
|
|
9105
|
+
const next = resolveNextAction(artifact);
|
|
9106
|
+
lines.push("## Next action", "");
|
|
9107
|
+
lines.push(`**${next.title}** \u2014 ${next.detail}`);
|
|
9108
|
+
if (next.command) {
|
|
9109
|
+
lines.push(`Command: \`${next.command}\``);
|
|
8923
9110
|
}
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
lines.push(
|
|
8937
|
-
} else {
|
|
8938
|
-
lines.push(`**Exact fix:** No automated recipe \u2014 see scanner hint. `);
|
|
9111
|
+
if (next.upgradeUrl) {
|
|
9112
|
+
lines.push(`Upgrade: ${next.upgradeUrl}`);
|
|
9113
|
+
}
|
|
9114
|
+
lines.push("");
|
|
9115
|
+
const unlockedCount = artifact.usage?.unlockedMapCategoryKeys.length ?? 6;
|
|
9116
|
+
if (unlockedCount < PRODUCTION_MAP_CATEGORY_KEYS_ALL.length) {
|
|
9117
|
+
lines.push("## Locked lanes (Pro)", "");
|
|
9118
|
+
lines.push(
|
|
9119
|
+
"Free plan unlocks 6/12 mission map lanes. Pro unlocks deployment, monitoring, security, testing, onboarding, and error handling."
|
|
9120
|
+
);
|
|
9121
|
+
lines.push("");
|
|
9122
|
+
for (const key of LOCKED_LANE_KEYS) {
|
|
9123
|
+
lines.push(`- ${key}`);
|
|
8939
9124
|
}
|
|
8940
|
-
lines.push(
|
|
8941
|
-
|
|
9125
|
+
lines.push("");
|
|
9126
|
+
lines.push(`Upgrade: ${UPGRADE_URL2}`);
|
|
9127
|
+
lines.push("");
|
|
9128
|
+
}
|
|
9129
|
+
lines.push("## Suggested stack", "");
|
|
9130
|
+
lines.push(
|
|
9131
|
+
"React + Tailwind + shadcn/ui + Supabase + Vercel (agent-default stack for lowest launch friction)"
|
|
9132
|
+
);
|
|
9133
|
+
lines.push("");
|
|
9134
|
+
lines.push("## Summary");
|
|
9135
|
+
lines.push(artifact.summary || "_No summary returned._");
|
|
9136
|
+
lines.push("");
|
|
9137
|
+
lines.push("## Mission map (repo wiring)");
|
|
9138
|
+
lines.push("");
|
|
9139
|
+
lines.push("| Area | Provider | Readiness | Notes |");
|
|
9140
|
+
lines.push("|------|----------|-----------|-------|");
|
|
9141
|
+
for (const area of artifact.missionGraph.areas ?? []) {
|
|
9142
|
+
for (const mission of area.providerMissions) {
|
|
9143
|
+
const failed = mission.checks.filter(
|
|
9144
|
+
(c) => c.status === "missing" || c.status === "failed" || c.status === "needs-connection"
|
|
9145
|
+
).length;
|
|
9146
|
+
const notes = failed > 0 ? `${failed} open check${failed === 1 ? "" : "s"}` : `${mission.readinessPercent}% repo checks`;
|
|
8942
9147
|
lines.push(
|
|
8943
|
-
|
|
9148
|
+
`| ${area.label} | ${mission.providerLabel} | ${mission.readinessPercent}% | ${notes} |`
|
|
8944
9149
|
);
|
|
8945
9150
|
}
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
lines.push(
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
9151
|
+
}
|
|
9152
|
+
lines.push("");
|
|
9153
|
+
lines.push("## Agent-code actions");
|
|
9154
|
+
if (topGaps.length === 0) {
|
|
9155
|
+
lines.push("_No model gaps returned. Review mission map checks before changing code._");
|
|
9156
|
+
} else {
|
|
9157
|
+
topGaps.forEach((gap2, index) => {
|
|
9158
|
+
lines.push(
|
|
9159
|
+
`${index + 1}. **${gap2.title}** (\`${gap2.id}\`, ${gap2.severity}, map: \`${gap2.primaryMapCategory}\`)`
|
|
9160
|
+
);
|
|
9161
|
+
lines.push(` - ${gap2.detail}`);
|
|
9162
|
+
lines.push(` - Command: \`viberaven prompt --gap ${gap2.id}\``);
|
|
9163
|
+
if (gap2.copyPrompt) {
|
|
9164
|
+
lines.push(` - Prompt: ${gap2.copyPrompt}`);
|
|
9165
|
+
}
|
|
9166
|
+
});
|
|
9167
|
+
}
|
|
9168
|
+
const manualChecks = (artifact.missionGraph.areas ?? []).flatMap(
|
|
9169
|
+
(area) => area.providerMissions.flatMap(
|
|
9170
|
+
(mission) => mission.checks.filter(
|
|
9171
|
+
(check) => check.evidenceClass === "manual-dashboard" || check.evidenceClass === "mcp-verifier" || check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.status === "needs-connection" || check.status === "unknown"
|
|
9172
|
+
).map((check) => ({ area: area.label, provider: mission.providerLabel, check }))
|
|
9173
|
+
)
|
|
9174
|
+
);
|
|
9175
|
+
lines.push("");
|
|
9176
|
+
lines.push("## Human-provider actions");
|
|
9177
|
+
if (manualChecks.length === 0) {
|
|
9178
|
+
lines.push("_No manual provider actions were identified in this scan._");
|
|
9179
|
+
} else {
|
|
9180
|
+
manualChecks.slice(0, 8).forEach((item3, index) => {
|
|
9181
|
+
lines.push(`${index + 1}. **${item3.check.label}** (${item3.area} / ${item3.provider})`);
|
|
9182
|
+
lines.push(
|
|
9183
|
+
` - ${item3.check.promptHint || "Ask the user to confirm this in the provider dashboard or through read-only MCP."}`
|
|
9184
|
+
);
|
|
9185
|
+
});
|
|
9186
|
+
}
|
|
9187
|
+
lines.push("");
|
|
9188
|
+
lines.push("Do not claim human-provider actions as repo-code fixes.");
|
|
9189
|
+
lines.push("");
|
|
9190
|
+
lines.push("## Agent workflow");
|
|
9191
|
+
lines.push("1. Read `.viberaven/agent-tasklist.md` first for the production gate.");
|
|
9192
|
+
lines.push("2. Read `.viberaven/launch-playbook.md` for the full checklist.");
|
|
9193
|
+
lines.push("3. Run `viberaven next --json` - one action at a time.");
|
|
9194
|
+
lines.push("4. Repo fix: `viberaven prompt --gap <id>` then implement.");
|
|
9195
|
+
lines.push("5. Provider: `viberaven guide <provider> --step N` and `viberaven open <provider>`.");
|
|
9196
|
+
lines.push(`6. Run \`${PUBLIC_VERIFY_COMMAND}\` to rescan and refresh \`.viberaven/agent-tasklist.md\`.`);
|
|
9197
|
+
lines.push("");
|
|
9198
|
+
if (artifact.usage) {
|
|
9199
|
+
lines.push("## Account usage");
|
|
9200
|
+
lines.push(
|
|
9201
|
+
`- Plan: ${artifact.usage.plan} \xB7 Scans used: ${artifact.usage.used}/${artifact.usage.limit} (${artifact.usage.period})`
|
|
9202
|
+
);
|
|
8958
9203
|
lines.push("");
|
|
8959
9204
|
}
|
|
8960
9205
|
return `${lines.join("\n")}
|
|
@@ -8974,13 +9219,13 @@ function generateLaunchPlaybook(artifact) {
|
|
|
8974
9219
|
lines.push(`Generated from scan at ${artifact.scannedAt}. Work top to bottom.`, "");
|
|
8975
9220
|
lines.push("## Repo fixes (agent code)", "");
|
|
8976
9221
|
const repoGaps = sortGapsByPriority(artifact.gaps).filter(
|
|
8977
|
-
(
|
|
9222
|
+
(gap2) => unlocked.has(gap2.primaryMapCategory)
|
|
8978
9223
|
);
|
|
8979
9224
|
if (repoGaps.length === 0) {
|
|
8980
9225
|
lines.push("_No repo-code gaps in unlocked lanes._", "");
|
|
8981
9226
|
} else {
|
|
8982
|
-
for (const
|
|
8983
|
-
lines.push(`- [ ] ${
|
|
9227
|
+
for (const gap2 of repoGaps) {
|
|
9228
|
+
lines.push(`- [ ] ${gap2.title} \u2014 \`viberaven prompt --gap ${gap2.id}\``);
|
|
8984
9229
|
}
|
|
8985
9230
|
lines.push("");
|
|
8986
9231
|
}
|
|
@@ -9000,14 +9245,14 @@ function generateLaunchPlaybook(artifact) {
|
|
|
9000
9245
|
if (!isPro) {
|
|
9001
9246
|
lines.push("## [Pro] Deployment, monitoring, and full map", "");
|
|
9002
9247
|
const proGaps = sortGapsByPriority(artifact.gaps).filter(
|
|
9003
|
-
(
|
|
9248
|
+
(gap2) => !unlocked.has(gap2.primaryMapCategory)
|
|
9004
9249
|
);
|
|
9005
9250
|
const proManuals = collectManualChecks(artifact).filter((item3) => !unlocked.has(item3.mapCategory));
|
|
9006
9251
|
if (proGaps.length === 0 && proManuals.length === 0) {
|
|
9007
9252
|
lines.push("_No Pro-only blockers detected._", "");
|
|
9008
9253
|
} else {
|
|
9009
|
-
for (const
|
|
9010
|
-
lines.push(`- [ ] ${
|
|
9254
|
+
for (const gap2 of proGaps.slice(0, 6)) {
|
|
9255
|
+
lines.push(`- [ ] ${gap2.title} [Pro] \u2014 upgrade at ${UPGRADE_URL3}`);
|
|
9011
9256
|
}
|
|
9012
9257
|
for (const item3 of proManuals.slice(0, 6)) {
|
|
9013
9258
|
lines.push(`- [ ] ${item3.check.label} [Pro] \u2014 upgrade at ${UPGRADE_URL3}`);
|
|
@@ -9132,6 +9377,21 @@ function getStationHtml(nonce, cssUri, jsUri, options) {
|
|
|
9132
9377
|
</div>
|
|
9133
9378
|
</header>
|
|
9134
9379
|
|
|
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
|
+
|
|
9135
9395
|
<div class="studio-workspace">
|
|
9136
9396
|
<nav class="studio-nav-rail" aria-label="Studio areas">
|
|
9137
9397
|
<span class="studio-nav-rail__item studio-nav-rail__item--active" aria-label="Architecture">ARCH</span>
|
|
@@ -9363,227 +9623,28 @@ var REPORT_ASSET_FILES = [
|
|
|
9363
9623
|
"assets/provider-logrocket.svg"
|
|
9364
9624
|
];
|
|
9365
9625
|
|
|
9366
|
-
// src/sanitizeArtifact.ts
|
|
9367
|
-
var INLINE_SECRET_PATTERNS = [
|
|
9368
|
-
/\b(sk_(?:live|test)_[A-Za-z0-9]{12,}|sk-proj-[A-Za-z0-9_-]{16,}|sk-[A-Za-z0-9_-]{20,})\b/g,
|
|
9369
|
-
/\b(whsec_[A-Za-z0-9]{12,}|rk_(?:live|test)_[A-Za-z0-9]{12,})\b/g,
|
|
9370
|
-
/\bghp_[A-Za-z0-9]{36,}\b/g,
|
|
9371
|
-
/\bgithub_pat_[A-Za-z0-9_]{50,}\b/g,
|
|
9372
|
-
/\bxox[bp]-[A-Za-z0-9-]{20,}\b/g,
|
|
9373
|
-
/\bxapp-[A-Za-z0-9-]{20,}\b/g,
|
|
9374
|
-
/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,
|
|
9375
|
-
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g
|
|
9376
|
-
];
|
|
9377
|
-
var SENSITIVE_ENV_KEYS = /(?:^|_)(?:ACCESS_TOKEN|AUTHORIZATION|API_KEY|SECRET|SECRET_KEY|SERVICE_ROLE_KEY|TOKEN|PASSWORD|PRIVATE_KEY|CREDENTIALS?)(?:$|_)/i;
|
|
9378
|
-
function redactString(value) {
|
|
9379
|
-
let out = value;
|
|
9380
|
-
for (const pattern of INLINE_SECRET_PATTERNS) {
|
|
9381
|
-
out = out.replace(pattern, pattern.source.includes("PRIVATE KEY") ? "[REDACTED_PRIVATE_KEY]" : "[REDACTED_SECRET]");
|
|
9382
|
-
}
|
|
9383
|
-
out = out.replace(/\bAuthorization\s*:\s*([A-Za-z][A-Za-z0-9._-]*)\s+[^\s;,]+/gi, "Authorization: $1 [REDACTED]");
|
|
9384
|
-
return out.replace(
|
|
9385
|
-
/\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,
|
|
9386
|
-
"$1=[REDACTED]"
|
|
9387
|
-
);
|
|
9388
|
-
}
|
|
9389
|
-
function redactUnknown(value) {
|
|
9390
|
-
if (typeof value === "string") {
|
|
9391
|
-
return redactString(value);
|
|
9392
|
-
}
|
|
9393
|
-
if (Array.isArray(value)) {
|
|
9394
|
-
return value.map(redactUnknown);
|
|
9395
|
-
}
|
|
9396
|
-
if (value && typeof value === "object") {
|
|
9397
|
-
const record = value;
|
|
9398
|
-
const next = {};
|
|
9399
|
-
for (const [key, entry] of Object.entries(record)) {
|
|
9400
|
-
if (SENSITIVE_ENV_KEYS.test(key)) {
|
|
9401
|
-
next[key] = "[REDACTED]";
|
|
9402
|
-
} else {
|
|
9403
|
-
next[key] = redactUnknown(entry);
|
|
9404
|
-
}
|
|
9405
|
-
}
|
|
9406
|
-
return next;
|
|
9407
|
-
}
|
|
9408
|
-
return value;
|
|
9409
|
-
}
|
|
9410
|
-
function sanitizeArtifactForDisk(artifact) {
|
|
9411
|
-
return redactUnknown(artifact);
|
|
9412
|
-
}
|
|
9413
|
-
|
|
9414
|
-
// src/version.ts
|
|
9415
|
-
var VERSION = "1.1.0";
|
|
9416
|
-
|
|
9417
|
-
// src/prp/buildPrp.ts
|
|
9418
|
-
var import_node_path8 = require("node:path");
|
|
9419
|
-
|
|
9420
|
-
// src/prp/types.ts
|
|
9421
|
-
var PRP_SCHEMA_URL = "https://schemas.viberaven.dev/prp/v0.1/schema.json";
|
|
9422
|
-
var PRP_PROTOCOL = "viberaven-production-protocol";
|
|
9423
|
-
var PRP_PROTOCOL_VERSION = "0.1.0";
|
|
9424
|
-
|
|
9425
|
-
// src/prp/buildPrp.ts
|
|
9426
|
-
var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
|
|
9427
|
-
var REDACTED = "<redacted>";
|
|
9428
|
-
var SENSITIVE_JSON_KEY_PATTERN = /secret|token|api[_-]?key|apikey|password|private[_-]?key|database[_-]?url|connection[_-]?string|service[_-]?role|role[_-]?key/i;
|
|
9429
|
-
function buildProductionProtocolReport(options) {
|
|
9430
|
-
const profile = options.profile ?? "launch";
|
|
9431
|
-
const generatedAt = options.artifact.scannedAt;
|
|
9432
|
-
const expiresAt = new Date(new Date(generatedAt).getTime() + ONE_DAY_MS).toISOString();
|
|
9433
|
-
const evidence = buildEvidence(options.artifact);
|
|
9434
|
-
const nextActions = buildNextActions(options.tasks);
|
|
9435
|
-
const findings = buildFindings(options.artifact, nextActions);
|
|
9436
|
-
return {
|
|
9437
|
-
$schema: PRP_SCHEMA_URL,
|
|
9438
|
-
protocol: PRP_PROTOCOL,
|
|
9439
|
-
protocolVersion: PRP_PROTOCOL_VERSION,
|
|
9440
|
-
producer: {
|
|
9441
|
-
name: "viberaven",
|
|
9442
|
-
version: options.producerVersion
|
|
9443
|
-
},
|
|
9444
|
-
subject: {
|
|
9445
|
-
projectName: sanitizePrpText((0, import_node_path8.basename)(options.artifact.workspacePath) || "workspace"),
|
|
9446
|
-
framework: sanitizePrpText(options.artifact.archetype || "unknown"),
|
|
9447
|
-
packageManager: "unknown",
|
|
9448
|
-
deploymentTarget: sanitizePrpText(options.artifact.selectedProviders?.deployment ?? "unknown"),
|
|
9449
|
-
environment: "production"
|
|
9450
|
-
},
|
|
9451
|
-
profile,
|
|
9452
|
-
decision: {
|
|
9453
|
-
status: resolveDecisionStatus(options.artifact),
|
|
9454
|
-
generatedAt,
|
|
9455
|
-
expiresAt,
|
|
9456
|
-
summary: summarizeDecision(options.artifact)
|
|
9457
|
-
},
|
|
9458
|
-
findings,
|
|
9459
|
-
evidence,
|
|
9460
|
-
nextActions,
|
|
9461
|
-
agentInstructions: {
|
|
9462
|
-
mustRead: [".viberaven/prp.json", ".viberaven/mission-map.md", ".viberaven/context-map.json"],
|
|
9463
|
-
doNotDeployUntil: [
|
|
9464
|
-
"decision.status is not blocked",
|
|
9465
|
-
"critical nextActions are resolved or explicitly accepted"
|
|
9466
|
-
]
|
|
9467
|
-
}
|
|
9468
|
-
};
|
|
9469
|
-
}
|
|
9470
|
-
function resolveDecisionStatus(artifact) {
|
|
9471
|
-
if (artifact.gaps.some((gap) => gap.severity === "critical")) return "blocked";
|
|
9472
|
-
if (artifact.gaps.some((gap) => gap.severity === "warning")) return "warning";
|
|
9473
|
-
return "clear";
|
|
9474
|
-
}
|
|
9475
|
-
function summarizeDecision(artifact) {
|
|
9476
|
-
const critical = artifact.gaps.filter((gap) => gap.severity === "critical").length;
|
|
9477
|
-
const warning = artifact.gaps.filter((gap) => gap.severity === "warning").length;
|
|
9478
|
-
if (critical > 0) return `${critical} production blocker${critical === 1 ? "" : "s"} found`;
|
|
9479
|
-
if (warning > 0) return `${warning} production warning${warning === 1 ? "" : "s"} found`;
|
|
9480
|
-
return "No production blockers found";
|
|
9481
|
-
}
|
|
9482
|
-
function buildEvidence(artifact) {
|
|
9483
|
-
return artifact.gaps.map((gap) => {
|
|
9484
|
-
const gapId = safePrpId(gap.id);
|
|
9485
|
-
return {
|
|
9486
|
-
id: `evidence.${gapId}.repo`,
|
|
9487
|
-
kind: "repo",
|
|
9488
|
-
status: evidenceStatusForGapSeverity(gap.severity),
|
|
9489
|
-
source: sanitizePrpText(String(gap.file ?? gap.primaryMapCategory ?? "repo")),
|
|
9490
|
-
summary: sanitizePrpText(gap.detail || gap.title || gap.id)
|
|
9491
|
-
};
|
|
9492
|
-
});
|
|
9493
|
-
}
|
|
9494
|
-
function evidenceStatusForGapSeverity(severity) {
|
|
9495
|
-
if (severity === "critical" || severity === "warning") return "missing";
|
|
9496
|
-
return "inconclusive";
|
|
9497
|
-
}
|
|
9498
|
-
function buildFindings(artifact, nextActions) {
|
|
9499
|
-
return artifact.gaps.map((gap) => {
|
|
9500
|
-
const gapId = safePrpId(gap.id);
|
|
9501
|
-
return {
|
|
9502
|
-
id: gapId,
|
|
9503
|
-
severity: gap.severity,
|
|
9504
|
-
category: sanitizePrpText(String(gap.primaryMapCategory ?? "unknown")),
|
|
9505
|
-
summary: sanitizePrpText(gap.title || gap.id),
|
|
9506
|
-
evidenceRefs: [`evidence.${gapId}.repo`],
|
|
9507
|
-
nextActionRefs: nextActions.some((action) => action.gapId === sanitizePrpText(gap.id)) ? [`action.${gapId}`] : []
|
|
9508
|
-
};
|
|
9509
|
-
});
|
|
9510
|
-
}
|
|
9511
|
-
function buildNextActions(tasks) {
|
|
9512
|
-
return tasks.map((task) => ({
|
|
9513
|
-
id: `action.${safePrpId(task.gapId)}`,
|
|
9514
|
-
title: sanitizePrpText(task.title),
|
|
9515
|
-
actionClass: actionClassForTask(task),
|
|
9516
|
-
requiresUserAction: task.requiresUserAction,
|
|
9517
|
-
approvalRequired: task.requiresUserAction,
|
|
9518
|
-
verifyCommand: sanitizePrpText(task.verifyCommand || PUBLIC_VERIFY_COMMAND),
|
|
9519
|
-
gapId: sanitizePrpText(task.gapId),
|
|
9520
|
-
provider: sanitizeOptionalPrpText(task.providerAction?.provider),
|
|
9521
|
-
dashboardUrl: sanitizeOptionalPrpText(task.providerAction?.dashboardUrl),
|
|
9522
|
-
manualFallback: sanitizeOptionalPrpText(task.providerAction?.exactStep ?? task.action),
|
|
9523
|
-
mcpTool: sanitizeOptionalPrpText(task.mcpTool),
|
|
9524
|
-
mcpArgs: sanitizePrpJsonObject(task.mcpArgs)
|
|
9525
|
-
}));
|
|
9526
|
-
}
|
|
9527
|
-
function actionClassForTask(task) {
|
|
9528
|
-
if (task.fixType === "repo-code") return "local_repo_write";
|
|
9529
|
-
if (task.fixType === "provider-action") return "manual_provider_step";
|
|
9530
|
-
if (task.fixType === "manual-verify") return "local_repo_read";
|
|
9531
|
-
return "manual_provider_step";
|
|
9532
|
-
}
|
|
9533
|
-
function safeId(value) {
|
|
9534
|
-
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "unknown";
|
|
9535
|
-
}
|
|
9536
|
-
function safePrpId(value) {
|
|
9537
|
-
return safeId(sanitizePrpText(value));
|
|
9538
|
-
}
|
|
9539
|
-
function sanitizeOptionalPrpText(value) {
|
|
9540
|
-
return value === void 0 ? void 0 : sanitizePrpText(value);
|
|
9541
|
-
}
|
|
9542
|
-
function sanitizePrpJsonObject(value) {
|
|
9543
|
-
return value === void 0 ? void 0 : sanitizePrpJsonValue(value);
|
|
9544
|
-
}
|
|
9545
|
-
function sanitizePrpJsonValue(value) {
|
|
9546
|
-
if (typeof value === "string") return sanitizePrpText(value);
|
|
9547
|
-
if (Array.isArray(value)) return value.map((item3) => sanitizePrpJsonValue(item3));
|
|
9548
|
-
if (value && typeof value === "object") {
|
|
9549
|
-
return Object.fromEntries(
|
|
9550
|
-
Object.entries(value).map(([key, item3]) => [
|
|
9551
|
-
sanitizePrpText(key),
|
|
9552
|
-
SENSITIVE_JSON_KEY_PATTERN.test(key) ? REDACTED : sanitizePrpJsonValue(item3)
|
|
9553
|
-
])
|
|
9554
|
-
);
|
|
9555
|
-
}
|
|
9556
|
-
return value;
|
|
9557
|
-
}
|
|
9558
|
-
function sanitizePrpText(value) {
|
|
9559
|
-
return value.replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, REDACTED).replace(/\b[a-z][a-z0-9+.-]*:\/\/[^\s/@]+:[^\s/@]+@[^\s,;)]+/gi, REDACTED).replace(/\b(?:postgres(?:ql)?|mongodb(?:\+srv)?|redis(?:s)?|mysql|mariadb):\/\/[^\s/@:]+:[^\s/@]+@[^\s,;)]+/gi, REDACTED).replace(/\b(?:redis(?:s)?):\/\/:[^\s/@]+@[^\s,;)]+/gi, REDACTED).replace(
|
|
9560
|
-
/\b([A-Za-z_][A-Za-z0-9_]*(?:SECRET|TOKEN|PASSWORD|PRIVATE[_-]?KEY|API[_-]?KEY|APIKEY|SERVICE[_-]?ROLE|DATABASE[_-]?URL|CONNECTION[_-]?STRING)[A-Za-z0-9_]*)\s*=\s*(?:"[^"]*"|'[^']*'|[^\s,;]+)/gi,
|
|
9561
|
-
`$1=${REDACTED}`
|
|
9562
|
-
).replace(/\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/gi, `Bearer ${REDACTED}`).replace(/(^|[^A-Za-z0-9])whsec_[A-Za-z0-9_]+/g, `$1${REDACTED}`).replace(/(^|[^A-Za-z0-9])sk_(live|test)_[A-Za-z0-9_]+/g, `$1${REDACTED}`).replace(/(^|[^A-Za-z0-9])pk_(live|test)_[A-Za-z0-9_]+/g, `$1${REDACTED}`).replace(/(^|[^A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{8,}/g, `$1${REDACTED}`).replace(/(^|[^A-Za-z0-9])gh[oprsu]_[A-Za-z0-9_]{16,}/g, `$1${REDACTED}`).replace(/(^|[^A-Za-z0-9])github_pat_[A-Za-z0-9_]{16,}/g, `$1${REDACTED}`).replace(/(^|[^A-Za-z0-9])(sb|supabase|vercel|stripe)_[A-Za-z0-9_]*\d[A-Za-z0-9_]{15,}/gi, `$1${REDACTED}`).replace(/(^|[^A-Za-z0-9_-])[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(?=$|[^A-Za-z0-9_-])/g, `$1${REDACTED}`).replace(/\b[A-Za-z0-9+/]{32,}={0,2}\b/g, REDACTED);
|
|
9563
|
-
}
|
|
9564
|
-
|
|
9565
9626
|
// src/artifacts.ts
|
|
9566
9627
|
async function copyReportAssets(reportAssetsDir) {
|
|
9567
9628
|
const sourceDir = getBundledReportAssetsDir();
|
|
9568
|
-
await (0, import_promises5.mkdir)((0,
|
|
9629
|
+
await (0, import_promises5.mkdir)((0, import_node_path8.join)(reportAssetsDir, "assets"), { recursive: true });
|
|
9569
9630
|
for (const rel of REPORT_ASSET_FILES) {
|
|
9570
|
-
await (0, import_promises5.copyFile)((0,
|
|
9631
|
+
await (0, import_promises5.copyFile)((0, import_node_path8.join)(sourceDir, rel), (0, import_node_path8.join)(reportAssetsDir, rel));
|
|
9571
9632
|
}
|
|
9572
9633
|
}
|
|
9573
9634
|
async function writeScanArtifacts(options) {
|
|
9574
9635
|
const cwd = options.cwd ?? options.artifact.workspacePath;
|
|
9575
9636
|
const dir = getProjectArtifactsDir(cwd);
|
|
9576
9637
|
await (0, import_promises5.mkdir)(dir, { recursive: true });
|
|
9577
|
-
const jsonPath = (0,
|
|
9578
|
-
const gateResultPath = (0,
|
|
9579
|
-
const contextMapPath = (0,
|
|
9580
|
-
const
|
|
9581
|
-
const
|
|
9582
|
-
const tasklistPath = (0,
|
|
9583
|
-
const summaryPath = (0,
|
|
9584
|
-
const playbookPath = (0,
|
|
9585
|
-
const reportPath = (0,
|
|
9586
|
-
const reportAssetsDir = (0,
|
|
9638
|
+
const jsonPath = (0, import_node_path8.join)(dir, "last-scan.json");
|
|
9639
|
+
const gateResultPath = (0, import_node_path8.join)(dir, "gate-result.json");
|
|
9640
|
+
const contextMapPath = (0, import_node_path8.join)(dir, "context-map.json");
|
|
9641
|
+
const prpPath = (0, import_node_path8.join)(dir, "prp.json");
|
|
9642
|
+
const gapsDir = (0, import_node_path8.join)(dir, "gaps");
|
|
9643
|
+
const tasklistPath = (0, import_node_path8.join)(dir, "agent-tasklist.md");
|
|
9644
|
+
const summaryPath = (0, import_node_path8.join)(dir, "agent-summary.md");
|
|
9645
|
+
const playbookPath = (0, import_node_path8.join)(dir, "launch-playbook.md");
|
|
9646
|
+
const reportPath = (0, import_node_path8.join)(dir, "report.html");
|
|
9647
|
+
const reportAssetsDir = (0, import_node_path8.join)(dir, "report");
|
|
9587
9648
|
await (0, import_promises5.mkdir)(gapsDir, { recursive: true });
|
|
9588
9649
|
const safe = sanitizeArtifactForDisk(options.artifact);
|
|
9589
9650
|
const json = `${JSON.stringify(safe, null, 2)}
|
|
@@ -9593,25 +9654,25 @@ async function writeScanArtifacts(options) {
|
|
|
9593
9654
|
const html = generateReportHtml(safe);
|
|
9594
9655
|
const tasks = buildTaskList(safe);
|
|
9595
9656
|
const tasklist = buildTaskListMarkdown(tasks);
|
|
9596
|
-
const prp = `${JSON.stringify(buildProductionProtocolReport({
|
|
9597
|
-
artifact: safe,
|
|
9598
|
-
tasks,
|
|
9599
|
-
producerVersion: VERSION
|
|
9600
|
-
}), null, 2)}
|
|
9601
|
-
`;
|
|
9602
9657
|
const gateResult = `${JSON.stringify(generateGateResult(safe), null, 2)}
|
|
9603
9658
|
`;
|
|
9604
9659
|
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
|
+
)}
|
|
9605
9666
|
`;
|
|
9606
9667
|
const gapEvidenceFiles = generateGapEvidenceFiles(safe);
|
|
9607
9668
|
await copyReportAssets(reportAssetsDir);
|
|
9608
9669
|
await (0, import_promises5.writeFile)(gateResultPath, gateResult, "utf-8");
|
|
9609
9670
|
await (0, import_promises5.writeFile)(contextMapPath, contextMap, "utf-8");
|
|
9610
|
-
|
|
9611
|
-
|
|
9671
|
+
await (0, import_promises5.writeFile)(prpPath, prp, "utf-8");
|
|
9672
|
+
for (const gap2 of gapEvidenceFiles) {
|
|
9673
|
+
await (0, import_promises5.writeFile)((0, import_node_path8.join)(dir, "gaps", `${gap2.content.id}.json`), `${JSON.stringify(gap2.content, null, 2)}
|
|
9612
9674
|
`, "utf-8");
|
|
9613
9675
|
}
|
|
9614
|
-
await (0, import_promises5.writeFile)(prpPath, prp, "utf-8");
|
|
9615
9676
|
await (0, import_promises5.writeFile)(tasklistPath, tasklist, "utf-8");
|
|
9616
9677
|
await (0, import_promises5.writeFile)(jsonPath, json, "utf-8");
|
|
9617
9678
|
await (0, import_promises5.writeFile)(summaryPath, summary, "utf-8");
|
|
@@ -9622,8 +9683,8 @@ async function writeScanArtifacts(options) {
|
|
|
9622
9683
|
jsonPath,
|
|
9623
9684
|
gateResultPath,
|
|
9624
9685
|
contextMapPath,
|
|
9625
|
-
gapsDir,
|
|
9626
9686
|
prpPath,
|
|
9687
|
+
gapsDir,
|
|
9627
9688
|
tasklistPath,
|
|
9628
9689
|
summaryPath,
|
|
9629
9690
|
playbookPath,
|
|
@@ -9659,28 +9720,6 @@ function renderJsonlEvents(result) {
|
|
|
9659
9720
|
`;
|
|
9660
9721
|
}
|
|
9661
9722
|
|
|
9662
|
-
// src/output/prpSummary.ts
|
|
9663
|
-
function renderProductionProtocolSummary(prp) {
|
|
9664
|
-
const nextAction = prp.nextActions[0];
|
|
9665
|
-
const lines = [
|
|
9666
|
-
"",
|
|
9667
|
-
"VibeRaven Production Protocol",
|
|
9668
|
-
"",
|
|
9669
|
-
`Profile: ${prp.profile}`,
|
|
9670
|
-
`Decision: ${prp.decision.status.toUpperCase()}`,
|
|
9671
|
-
"Canonical artifact: .viberaven/prp.json",
|
|
9672
|
-
""
|
|
9673
|
-
];
|
|
9674
|
-
if (nextAction) {
|
|
9675
|
-
lines.push("Next action:", nextAction.title, "");
|
|
9676
|
-
lines.push("Why:", prp.decision.summary);
|
|
9677
|
-
} else {
|
|
9678
|
-
lines.push("Next action:", "No production blockers found.", "");
|
|
9679
|
-
lines.push("Why:", prp.decision.summary);
|
|
9680
|
-
}
|
|
9681
|
-
return lines.join("\n");
|
|
9682
|
-
}
|
|
9683
|
-
|
|
9684
9723
|
// src/commands/strictGate.ts
|
|
9685
9724
|
function exitCodeForStrictGate(result, options = {}) {
|
|
9686
9725
|
if (result.gate.status === "error") return 2;
|
|
@@ -9840,10 +9879,10 @@ function printScanSummary(artifact, paths) {
|
|
|
9840
9879
|
const modelGaps = artifact.gaps.filter((g2) => g2.primaryMapCategory === area.key).length;
|
|
9841
9880
|
const tag = modelGaps > 0 ? ` GAP ${modelGaps}` : open > 0 ? ` ${open} fix` : "";
|
|
9842
9881
|
const tagColored = tag ? gapTagColor(modelGaps, open)(tag) : "";
|
|
9843
|
-
const
|
|
9882
|
+
const label2 = area.label.padEnd(18);
|
|
9844
9883
|
const provider2 = mission.providerLabel.padEnd(14);
|
|
9845
9884
|
const readiness = readinessColor(mission.readinessPercent)(`${mission.readinessPercent}%`);
|
|
9846
|
-
console.log(` ${import_picocolors.default.dim(
|
|
9885
|
+
console.log(` ${import_picocolors.default.dim(label2)} ${provider2} ${readiness}${tagColored}`);
|
|
9847
9886
|
}
|
|
9848
9887
|
console.log("");
|
|
9849
9888
|
console.log(import_picocolors.default.bold("Artifacts:"));
|
|
@@ -9891,7 +9930,7 @@ function printScanSummary(artifact, paths) {
|
|
|
9891
9930
|
|
|
9892
9931
|
// src/runnerConnect.ts
|
|
9893
9932
|
var import_promises6 = require("node:fs/promises");
|
|
9894
|
-
var
|
|
9933
|
+
var import_node_path9 = require("node:path");
|
|
9895
9934
|
var import_node_child_process3 = require("node:child_process");
|
|
9896
9935
|
var import_node_crypto = require("node:crypto");
|
|
9897
9936
|
|
|
@@ -9970,8 +10009,8 @@ function validateSafeFixJobInput(kind, rawInput) {
|
|
|
9970
10009
|
}
|
|
9971
10010
|
};
|
|
9972
10011
|
}
|
|
9973
|
-
function validateSafeFixRelativePath(
|
|
9974
|
-
const trimmed =
|
|
10012
|
+
function validateSafeFixRelativePath(path3) {
|
|
10013
|
+
const trimmed = path3.trim();
|
|
9975
10014
|
if (!trimmed) {
|
|
9976
10015
|
return { ok: false, reason: "Safe fix path must not be empty." };
|
|
9977
10016
|
}
|
|
@@ -9986,11 +10025,11 @@ function validateSafeFixRelativePath(path) {
|
|
|
9986
10025
|
if (segments.some((segment) => segment === ".git" || segment === "node_modules")) {
|
|
9987
10026
|
return { ok: false, reason: "Safe fix path targets a blocked directory." };
|
|
9988
10027
|
}
|
|
9989
|
-
const
|
|
9990
|
-
if (
|
|
10028
|
+
const basename3 = segments.at(-1)?.toLowerCase() ?? "";
|
|
10029
|
+
if (basename3 !== ".env.example" && (basename3 === ".env" || basename3.startsWith(".env."))) {
|
|
9991
10030
|
return { ok: false, reason: "Safe fix path targets an environment secret file." };
|
|
9992
10031
|
}
|
|
9993
|
-
if (isSecretLikeFilename(
|
|
10032
|
+
if (isSecretLikeFilename(basename3)) {
|
|
9994
10033
|
return { ok: false, reason: "Safe fix path targets a secret-like file." };
|
|
9995
10034
|
}
|
|
9996
10035
|
if (!isAllowedSafeFixPath(normalized)) {
|
|
@@ -10013,11 +10052,11 @@ var SECRET_VALUE_PATTERNS = [
|
|
|
10013
10052
|
/\bwhsec_[A-Za-z0-9]{12,}\b/,
|
|
10014
10053
|
/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/
|
|
10015
10054
|
];
|
|
10016
|
-
function isAllowedSafeFixPath(
|
|
10017
|
-
if (
|
|
10055
|
+
function isAllowedSafeFixPath(path3) {
|
|
10056
|
+
if (path3 === ".env.example") {
|
|
10018
10057
|
return true;
|
|
10019
10058
|
}
|
|
10020
|
-
const segments =
|
|
10059
|
+
const segments = path3.split("/");
|
|
10021
10060
|
if (segments.length !== 2) {
|
|
10022
10061
|
return false;
|
|
10023
10062
|
}
|
|
@@ -10239,7 +10278,7 @@ async function createSafeFixFile(job, input, targetPath) {
|
|
|
10239
10278
|
} catch {
|
|
10240
10279
|
}
|
|
10241
10280
|
try {
|
|
10242
|
-
await (0, import_promises6.mkdir)((0,
|
|
10281
|
+
await (0, import_promises6.mkdir)((0, import_node_path9.dirname)(targetPath), { recursive: true });
|
|
10243
10282
|
await writeNewFileAtomically(targetPath, input.content);
|
|
10244
10283
|
} catch {
|
|
10245
10284
|
return safeFixNeedsUser(job, "SAFE_FIX_WRITE_FAILED", `Could not create ${input.path}.`);
|
|
@@ -10307,12 +10346,12 @@ function safeFixNeedsUser(job, code, message) {
|
|
|
10307
10346
|
};
|
|
10308
10347
|
}
|
|
10309
10348
|
async function resolveSafeFixTarget(workspaceRoot, relativePath) {
|
|
10310
|
-
const root = await (0, import_promises6.realpath)(workspaceRoot).catch(() => (0,
|
|
10311
|
-
const target = (0,
|
|
10349
|
+
const root = await (0, import_promises6.realpath)(workspaceRoot).catch(() => (0, import_node_path9.resolve)(workspaceRoot));
|
|
10350
|
+
const target = (0, import_node_path9.resolve)(root, relativePath);
|
|
10312
10351
|
if (!isInsideRoot(root, target)) {
|
|
10313
10352
|
return { ok: false, reason: "Safe fix target escaped the workspace root." };
|
|
10314
10353
|
}
|
|
10315
|
-
const parent = (0,
|
|
10354
|
+
const parent = (0, import_node_path9.dirname)(target);
|
|
10316
10355
|
const parentReal = await realpathNearestExisting(parent, root);
|
|
10317
10356
|
if (!isInsideRoot(root, parentReal)) {
|
|
10318
10357
|
return { ok: false, reason: "Safe fix parent path escaped the workspace root." };
|
|
@@ -10323,14 +10362,14 @@ async function resolveSafeFixTarget(workspaceRoot, relativePath) {
|
|
|
10323
10362
|
}
|
|
10324
10363
|
return { ok: true, path: target };
|
|
10325
10364
|
}
|
|
10326
|
-
async function realpathNearestExisting(
|
|
10327
|
-
let candidate =
|
|
10365
|
+
async function realpathNearestExisting(path3, root) {
|
|
10366
|
+
let candidate = path3;
|
|
10328
10367
|
while (isInsideRoot(root, candidate)) {
|
|
10329
10368
|
try {
|
|
10330
10369
|
await (0, import_promises6.lstat)(candidate);
|
|
10331
10370
|
return (0, import_promises6.realpath)(candidate);
|
|
10332
10371
|
} catch {
|
|
10333
|
-
const next = (0,
|
|
10372
|
+
const next = (0, import_node_path9.dirname)(candidate);
|
|
10334
10373
|
if (next === candidate) {
|
|
10335
10374
|
break;
|
|
10336
10375
|
}
|
|
@@ -10340,8 +10379,8 @@ async function realpathNearestExisting(path, root) {
|
|
|
10340
10379
|
return root;
|
|
10341
10380
|
}
|
|
10342
10381
|
function isInsideRoot(root, candidate) {
|
|
10343
|
-
const rel = (0,
|
|
10344
|
-
return rel === "" || !rel.startsWith("..") && !(0,
|
|
10382
|
+
const rel = (0, import_node_path9.relative)(root, candidate);
|
|
10383
|
+
return rel === "" || !rel.startsWith("..") && !(0, import_node_path9.isAbsolute)(rel);
|
|
10345
10384
|
}
|
|
10346
10385
|
async function writeNewFileAtomically(targetPath, content) {
|
|
10347
10386
|
const tempPath = tempPathFor(targetPath);
|
|
@@ -10362,7 +10401,7 @@ async function replaceFileAtomically(targetPath, content) {
|
|
|
10362
10401
|
}
|
|
10363
10402
|
}
|
|
10364
10403
|
function tempPathFor(targetPath) {
|
|
10365
|
-
return (0,
|
|
10404
|
+
return (0, import_node_path9.join)((0, import_node_path9.dirname)(targetPath), `.${(0, import_node_path9.basename)(targetPath)}.${(0, import_node_crypto.randomUUID)()}.tmp`);
|
|
10366
10405
|
}
|
|
10367
10406
|
async function executePackageScriptJob(job, scriptName, options) {
|
|
10368
10407
|
const packageJson = await readPackageJson(options.workspaceRoot);
|
|
@@ -10455,8 +10494,8 @@ function buildStationScanProof(kind, artifact) {
|
|
|
10455
10494
|
`gaps: ${artifact.gaps.length}`,
|
|
10456
10495
|
`summary: ${artifact.summary || "No summary returned."}`
|
|
10457
10496
|
];
|
|
10458
|
-
for (const
|
|
10459
|
-
evidence.push(`gap: ${
|
|
10497
|
+
for (const gap2 of artifact.gaps.slice(0, 5)) {
|
|
10498
|
+
evidence.push(`gap: ${gap2.title} (${gap2.severity})`);
|
|
10460
10499
|
}
|
|
10461
10500
|
for (const area of (artifact.missionGraph.areas ?? []).slice(0, 6)) {
|
|
10462
10501
|
for (const mission of area.providerMissions.slice(0, 1)) {
|
|
@@ -10522,13 +10561,13 @@ function summarizeSafeNoopJob(job) {
|
|
|
10522
10561
|
proofItems: [proofItemForJob(job, "runner_summary", "Runner summary", summary, [])]
|
|
10523
10562
|
};
|
|
10524
10563
|
}
|
|
10525
|
-
function proofItemForJob(job, kind,
|
|
10564
|
+
function proofItemForJob(job, kind, label2, summary, evidence, redactionSecrets = []) {
|
|
10526
10565
|
return {
|
|
10527
10566
|
deploySessionId: job.deploySessionId,
|
|
10528
10567
|
runnerSessionId: job.runnerSessionId,
|
|
10529
10568
|
jobId: job.id,
|
|
10530
10569
|
kind,
|
|
10531
|
-
label,
|
|
10570
|
+
label: label2,
|
|
10532
10571
|
summary: redactRunnerProofText(summary, redactionSecrets),
|
|
10533
10572
|
evidence: evidence.map((value) => redactRunnerProofText(value, redactionSecrets)),
|
|
10534
10573
|
redacted: true
|
|
@@ -10536,7 +10575,7 @@ function proofItemForJob(job, kind, label, summary, evidence, redactionSecrets =
|
|
|
10536
10575
|
}
|
|
10537
10576
|
async function readPackageJson(workspaceRoot) {
|
|
10538
10577
|
try {
|
|
10539
|
-
const raw = await (0, import_promises6.readFile)((0,
|
|
10578
|
+
const raw = await (0, import_promises6.readFile)((0, import_node_path9.join)(workspaceRoot, "package.json"), "utf-8");
|
|
10540
10579
|
const parsed = JSON.parse(raw);
|
|
10541
10580
|
if (isRecord6(parsed) && isRecord6(parsed.scripts)) {
|
|
10542
10581
|
return { scripts: Object.fromEntries(Object.entries(parsed.scripts).filter(([, value]) => typeof value === "string")) };
|
|
@@ -10551,10 +10590,10 @@ function packageScriptArgs(command, scriptName) {
|
|
|
10551
10590
|
}
|
|
10552
10591
|
return ["run", scriptName];
|
|
10553
10592
|
}
|
|
10554
|
-
function summarizeCommandOutput(
|
|
10593
|
+
function summarizeCommandOutput(label2, result, redactionSecrets = []) {
|
|
10555
10594
|
const lines = `${result.stdout}
|
|
10556
10595
|
${result.stderr ?? ""}`.split(/\r?\n/).map((line) => redactRunnerProofText(line.trim(), redactionSecrets)).filter(Boolean).slice(0, 8);
|
|
10557
|
-
return [`${
|
|
10596
|
+
return [`${label2} ${result.ok ? "succeeded" : "failed"}`, ...lines];
|
|
10558
10597
|
}
|
|
10559
10598
|
function redactRunnerProofText(value, additionalSecrets = []) {
|
|
10560
10599
|
let out = redactAdditionalSecrets(value, additionalSecrets);
|
|
@@ -10619,7 +10658,7 @@ async function collectLocalRepoMetadata(workspaceRoot, commandRunner = runComman
|
|
|
10619
10658
|
const headSha = headResult.ok ? normalizeSha(headResult.stdout) : null;
|
|
10620
10659
|
const dirty = statusResult.ok ? statusResult.stdout.trim().length > 0 : void 0;
|
|
10621
10660
|
return {
|
|
10622
|
-
rootName: (0,
|
|
10661
|
+
rootName: (0, import_node_path9.basename)(workspaceRoot) || "workspace",
|
|
10623
10662
|
remotes: remoteResult.ok ? parseGitRemotes(remoteResult.stdout) : [],
|
|
10624
10663
|
branch,
|
|
10625
10664
|
headSha,
|
|
@@ -10637,7 +10676,7 @@ async function detectPackageManager(workspaceRoot) {
|
|
|
10637
10676
|
];
|
|
10638
10677
|
for (const [manager, file] of checks) {
|
|
10639
10678
|
try {
|
|
10640
|
-
await (0, import_promises6.access)((0,
|
|
10679
|
+
await (0, import_promises6.access)((0, import_node_path9.join)(workspaceRoot, file));
|
|
10641
10680
|
return manager;
|
|
10642
10681
|
} catch {
|
|
10643
10682
|
}
|
|
@@ -10828,7 +10867,7 @@ function isRecord6(value) {
|
|
|
10828
10867
|
}
|
|
10829
10868
|
|
|
10830
10869
|
// src/tui/runInteractive.ts
|
|
10831
|
-
var
|
|
10870
|
+
var import_node_path14 = require("node:path");
|
|
10832
10871
|
|
|
10833
10872
|
// ../../../../node_modules/@clack/core/dist/index.mjs
|
|
10834
10873
|
var import_sisteransi = __toESM(require_src(), 1);
|
|
@@ -11367,12 +11406,12 @@ var import_picocolors4 = __toESM(require_picocolors());
|
|
|
11367
11406
|
function compact(value) {
|
|
11368
11407
|
return String(value ?? "").replace(/\s+/g, " ").trim();
|
|
11369
11408
|
}
|
|
11370
|
-
function originalHint(
|
|
11371
|
-
const hint = compact(
|
|
11409
|
+
function originalHint(gap2) {
|
|
11410
|
+
const hint = compact(gap2.copyPrompt);
|
|
11372
11411
|
return hint ? hint : "No scanner hint was returned for this gap.";
|
|
11373
11412
|
}
|
|
11374
|
-
function buildAgentFixPrompt(artifact,
|
|
11375
|
-
const affected = [
|
|
11413
|
+
function buildAgentFixPrompt(artifact, gap2) {
|
|
11414
|
+
const affected = [gap2.primaryMapCategory, ...gap2.affectedMapCategories ?? []].filter(Boolean).filter((value, index, all) => all.indexOf(value) === index).join(", ");
|
|
11376
11415
|
return [
|
|
11377
11416
|
"You are an AI coding agent using VibeRaven as the production-readiness map.",
|
|
11378
11417
|
"",
|
|
@@ -11380,11 +11419,11 @@ function buildAgentFixPrompt(artifact, gap) {
|
|
|
11380
11419
|
"",
|
|
11381
11420
|
`Project: ${artifact.workspacePath}`,
|
|
11382
11421
|
`Current VibeRaven state: production core ${artifact.productionCorePercent}% | score ${artifact.score} (${artifact.scoreLabel})`,
|
|
11383
|
-
`Gap: ${
|
|
11384
|
-
`Gap id: ${
|
|
11385
|
-
`Severity: ${
|
|
11386
|
-
`Production area: ${
|
|
11387
|
-
`Scanner detail: ${compact(
|
|
11422
|
+
`Gap: ${gap2.title}`,
|
|
11423
|
+
`Gap id: ${gap2.id}`,
|
|
11424
|
+
`Severity: ${gap2.severity}`,
|
|
11425
|
+
`Production area: ${gap2.primaryMapCategory}${affected ? ` | affected: ${affected}` : ""}`,
|
|
11426
|
+
`Scanner detail: ${compact(gap2.detail)}`,
|
|
11388
11427
|
"",
|
|
11389
11428
|
"Required workflow:",
|
|
11390
11429
|
"1. Read `.viberaven/agent-summary.md` and `.viberaven/launch-playbook.md` before changing code.",
|
|
@@ -11410,10 +11449,13 @@ function buildAgentFixPrompt(artifact, gap) {
|
|
|
11410
11449
|
"- Report what changed, what verification passed, and which VibeRaven gap remains next.",
|
|
11411
11450
|
"",
|
|
11412
11451
|
"Original scanner hint:",
|
|
11413
|
-
originalHint(
|
|
11452
|
+
originalHint(gap2)
|
|
11414
11453
|
].join("\n");
|
|
11415
11454
|
}
|
|
11416
11455
|
|
|
11456
|
+
// src/version.ts
|
|
11457
|
+
var VERSION = "1.1.1";
|
|
11458
|
+
|
|
11417
11459
|
// src/commands/guide.ts
|
|
11418
11460
|
var import_picocolors3 = __toESM(require_picocolors());
|
|
11419
11461
|
function formatPasteTarget(step) {
|
|
@@ -11482,7 +11524,7 @@ async function runGuideCommand(options) {
|
|
|
11482
11524
|
|
|
11483
11525
|
// src/commands/audit.ts
|
|
11484
11526
|
var import_promises7 = require("node:fs/promises");
|
|
11485
|
-
var
|
|
11527
|
+
var import_node_path10 = require("node:path");
|
|
11486
11528
|
var ENV_FILES = [
|
|
11487
11529
|
".env",
|
|
11488
11530
|
".env.local",
|
|
@@ -11582,7 +11624,7 @@ function buildVercelSupabaseAudit(input) {
|
|
|
11582
11624
|
}
|
|
11583
11625
|
async function readIfExists(projectRoot, relativePath) {
|
|
11584
11626
|
try {
|
|
11585
|
-
const absolutePath = (0,
|
|
11627
|
+
const absolutePath = (0, import_node_path10.join)(projectRoot, relativePath);
|
|
11586
11628
|
const fileStat = await (0, import_promises7.stat)(absolutePath);
|
|
11587
11629
|
if (!fileStat.isFile()) {
|
|
11588
11630
|
return void 0;
|
|
@@ -11596,7 +11638,7 @@ async function readIfExists(projectRoot, relativePath) {
|
|
|
11596
11638
|
}
|
|
11597
11639
|
}
|
|
11598
11640
|
async function collectSqlFiles(projectRoot, root) {
|
|
11599
|
-
const base = (0,
|
|
11641
|
+
const base = (0, import_node_path10.join)(projectRoot, root);
|
|
11600
11642
|
try {
|
|
11601
11643
|
const rootStat = await (0, import_promises7.stat)(base);
|
|
11602
11644
|
if (!rootStat.isDirectory()) {
|
|
@@ -11616,17 +11658,17 @@ async function collectSqlFiles(projectRoot, root) {
|
|
|
11616
11658
|
for (const entry of entries) {
|
|
11617
11659
|
if (entry.isDirectory()) {
|
|
11618
11660
|
if (!SKIP_DIRS.has(entry.name)) {
|
|
11619
|
-
await visit((0,
|
|
11661
|
+
await visit((0, import_node_path10.join)(dir, entry.name));
|
|
11620
11662
|
}
|
|
11621
11663
|
continue;
|
|
11622
11664
|
}
|
|
11623
11665
|
if (!entry.isFile() || !entry.name.toLowerCase().endsWith(".sql")) {
|
|
11624
11666
|
continue;
|
|
11625
11667
|
}
|
|
11626
|
-
const absolutePath = (0,
|
|
11668
|
+
const absolutePath = (0, import_node_path10.join)(dir, entry.name);
|
|
11627
11669
|
try {
|
|
11628
11670
|
files.push({
|
|
11629
|
-
path: (0,
|
|
11671
|
+
path: (0, import_node_path10.relative)(projectRoot, absolutePath).replace(/\\/g, "/"),
|
|
11630
11672
|
content: await (0, import_promises7.readFile)(absolutePath, "utf8")
|
|
11631
11673
|
});
|
|
11632
11674
|
} catch {
|
|
@@ -11676,7 +11718,7 @@ function renderVercelSupabaseAudit(result) {
|
|
|
11676
11718
|
|
|
11677
11719
|
// src/commands/initRules.ts
|
|
11678
11720
|
var import_promises10 = require("node:fs/promises");
|
|
11679
|
-
var
|
|
11721
|
+
var import_node_path13 = require("node:path");
|
|
11680
11722
|
|
|
11681
11723
|
// src/commands/agentRulesBlock.ts
|
|
11682
11724
|
var VIBERAVEN_BLOCK_START = "<!-- VIBERAVEN:START -->";
|
|
@@ -11698,15 +11740,6 @@ Maps repo evidence to OWASP LLM Top 10 launch risks. Supports SAFECode-style dep
|
|
|
11698
11740
|
var STACK_FRAMING = `Stack context for this repo: Next.js, Supabase, Vercel, VibeRaven.
|
|
11699
11741
|
Do NOT deploy to Vercel or ship to production users without running \`${PUBLIC_AGENT_MODE_COMMAND}\`.
|
|
11700
11742
|
Do NOT rely on manual production checklists when VibeRaven gate artifacts exist in \`.viberaven/\`.`;
|
|
11701
|
-
var PRODUCTION_PROTOCOL_RULES = `## VibeRaven Production Protocol
|
|
11702
|
-
|
|
11703
|
-
Before making deploy, auth, database, payment, API, or scaling changes:
|
|
11704
|
-
|
|
11705
|
-
1. Read \`.viberaven/prp.json\`.
|
|
11706
|
-
2. Follow \`.viberaven/mission-map.md\`.
|
|
11707
|
-
3. Do not deploy if \`decision.status\` is \`blocked\`.
|
|
11708
|
-
4. Fix \`nextActions\` in order.
|
|
11709
|
-
5. Re-run \`${PUBLIC_AGENT_MODE_COMMAND}\`.`;
|
|
11710
11743
|
var AGENT_NEGATIVE_CONSTRAINTS = `## Event-Triggered Production Verification
|
|
11711
11744
|
|
|
11712
11745
|
Trigger: modifications to \`package.json\`, \`vercel.json\`, \`supabase/migrations/**/*\`, \`.env.example\`, auth/billing/webhook paths, or deploy workflow files.
|
|
@@ -11732,8 +11765,6 @@ var AGENT_RULES_BODY = `${AGENT_RULES_PREAMBLE}
|
|
|
11732
11765
|
|
|
11733
11766
|
${STACK_FRAMING}
|
|
11734
11767
|
|
|
11735
|
-
${PRODUCTION_PROTOCOL_RULES}
|
|
11736
|
-
|
|
11737
11768
|
${AGENT_NEGATIVE_CONSTRAINTS}
|
|
11738
11769
|
|
|
11739
11770
|
## VibeRaven Production-Readiness Gate
|
|
@@ -11783,11 +11814,10 @@ var AGENT_CONTEXT_BODY = `${AGENT_RULES_PREAMBLE}
|
|
|
11783
11814
|
|
|
11784
11815
|
After \`--agent-mode\`, read these artifacts in order:
|
|
11785
11816
|
|
|
11786
|
-
1. \`.viberaven/
|
|
11787
|
-
2. \`.viberaven/
|
|
11788
|
-
3. \`.viberaven/
|
|
11789
|
-
4. \`.viberaven/
|
|
11790
|
-
5. \`.viberaven/context-map.json\``;
|
|
11817
|
+
1. \`.viberaven/mission-map.md\`
|
|
11818
|
+
2. \`.viberaven/agent-tasklist.md\`
|
|
11819
|
+
3. \`.viberaven/gate-result.json\`
|
|
11820
|
+
4. \`.viberaven/context-map.json\``;
|
|
11791
11821
|
var MISSION_MAP_BODY = `${AGENT_RULES_PREAMBLE}
|
|
11792
11822
|
|
|
11793
11823
|
## Mission Map loop
|
|
@@ -11926,7 +11956,7 @@ function escapeRegExp3(value) {
|
|
|
11926
11956
|
|
|
11927
11957
|
// src/commands/cursorRulesPack.ts
|
|
11928
11958
|
var import_promises8 = require("node:fs/promises");
|
|
11929
|
-
var
|
|
11959
|
+
var import_node_path11 = require("node:path");
|
|
11930
11960
|
var CURSOR_RULES_DIR = ".cursor/rules";
|
|
11931
11961
|
var LEGACY_CURSOR_RULE_FILE = `${CURSOR_RULES_DIR}/viberaven.mdc`;
|
|
11932
11962
|
var DOMAIN_PATH_POINTER = "Before editing these files, read `.viberaven/agent-context.md` and `.viberaven/mission-map.md`.";
|
|
@@ -12008,27 +12038,27 @@ function renderCursorCoreRulePreview() {
|
|
|
12008
12038
|
async function initCursorRulesPack(options) {
|
|
12009
12039
|
const results = [];
|
|
12010
12040
|
const pack = buildCursorRulesPack();
|
|
12011
|
-
for (const
|
|
12012
|
-
const file = `${CURSOR_RULES_DIR}/${
|
|
12013
|
-
const
|
|
12014
|
-
const existing = await readExistingFile(
|
|
12015
|
-
const changed = !existing.exists || existing.content !==
|
|
12041
|
+
for (const rule2 of pack) {
|
|
12042
|
+
const file = `${CURSOR_RULES_DIR}/${rule2.filename}`;
|
|
12043
|
+
const path3 = (0, import_node_path11.join)(options.cwd, file);
|
|
12044
|
+
const existing = await readExistingFile(path3);
|
|
12045
|
+
const changed = !existing.exists || existing.content !== rule2.content;
|
|
12016
12046
|
const action = !existing.exists ? "created" : changed ? "updated" : "unchanged";
|
|
12017
12047
|
if (!options.dryRun && changed) {
|
|
12018
|
-
await (0, import_promises8.mkdir)((0,
|
|
12019
|
-
await (0, import_promises8.writeFile)(
|
|
12048
|
+
await (0, import_promises8.mkdir)((0, import_node_path11.join)(options.cwd, CURSOR_RULES_DIR), { recursive: true });
|
|
12049
|
+
await (0, import_promises8.writeFile)(path3, rule2.content, "utf-8");
|
|
12020
12050
|
}
|
|
12021
|
-
results.push({ target: "cursor", file, path, action });
|
|
12051
|
+
results.push({ target: "cursor", file, path: path3, action });
|
|
12022
12052
|
}
|
|
12023
|
-
const legacyPath = (0,
|
|
12053
|
+
const legacyPath = (0, import_node_path11.join)(options.cwd, LEGACY_CURSOR_RULE_FILE);
|
|
12024
12054
|
if (!options.dryRun && await fileExists(legacyPath)) {
|
|
12025
12055
|
await (0, import_promises8.rm)(legacyPath, { force: true });
|
|
12026
12056
|
}
|
|
12027
12057
|
return results;
|
|
12028
12058
|
}
|
|
12029
|
-
async function readExistingFile(
|
|
12059
|
+
async function readExistingFile(path3) {
|
|
12030
12060
|
try {
|
|
12031
|
-
return { exists: true, content: await (0, import_promises8.readFile)(
|
|
12061
|
+
return { exists: true, content: await (0, import_promises8.readFile)(path3, "utf-8") };
|
|
12032
12062
|
} catch (error) {
|
|
12033
12063
|
if (isFileNotFoundError(error)) {
|
|
12034
12064
|
return { exists: false, content: "" };
|
|
@@ -12036,9 +12066,9 @@ async function readExistingFile(path) {
|
|
|
12036
12066
|
throw error;
|
|
12037
12067
|
}
|
|
12038
12068
|
}
|
|
12039
|
-
async function fileExists(
|
|
12069
|
+
async function fileExists(path3) {
|
|
12040
12070
|
try {
|
|
12041
|
-
await (0, import_promises8.access)(
|
|
12071
|
+
await (0, import_promises8.access)(path3);
|
|
12042
12072
|
return true;
|
|
12043
12073
|
} catch {
|
|
12044
12074
|
return false;
|
|
@@ -12133,14 +12163,14 @@ function getAgentRulesTargets(value) {
|
|
|
12133
12163
|
// src/commands/seedPackageJsonScripts.ts
|
|
12134
12164
|
var import_node_fs7 = require("node:fs");
|
|
12135
12165
|
var import_promises9 = require("node:fs/promises");
|
|
12136
|
-
var
|
|
12166
|
+
var import_node_path12 = require("node:path");
|
|
12137
12167
|
var VIBERAVEN_PACKAGE_JSON_SCRIPTS = {
|
|
12138
12168
|
"viberaven:gate": PUBLIC_AGENT_MODE_COMMAND,
|
|
12139
12169
|
"viberaven:verify": PUBLIC_VERIFY_COMMAND,
|
|
12140
12170
|
"viberaven:strict": PUBLIC_STRICT_COMMAND
|
|
12141
12171
|
};
|
|
12142
12172
|
async function seedPackageJsonScripts(options) {
|
|
12143
|
-
const packageJsonPath = (0,
|
|
12173
|
+
const packageJsonPath = (0, import_node_path12.join)(options.cwd, "package.json");
|
|
12144
12174
|
if (!(0, import_node_fs7.existsSync)(packageJsonPath)) {
|
|
12145
12175
|
return null;
|
|
12146
12176
|
}
|
|
@@ -12189,15 +12219,15 @@ async function initAgentRules(options) {
|
|
|
12189
12219
|
continue;
|
|
12190
12220
|
}
|
|
12191
12221
|
const file = AGENT_RULE_TARGETS[target].file;
|
|
12192
|
-
const
|
|
12193
|
-
const existing = await readExistingFile2(
|
|
12222
|
+
const path3 = (0, import_node_path13.join)(options.cwd, file);
|
|
12223
|
+
const existing = await readExistingFile2(path3);
|
|
12194
12224
|
const injected = injectAgentRulesBlock(existing.content, renderAgentRulesForTarget(target));
|
|
12195
12225
|
const action = !existing.exists ? "created" : injected.changed ? "updated" : "unchanged";
|
|
12196
12226
|
if (!options.dryRun && injected.changed) {
|
|
12197
|
-
await (0, import_promises10.mkdir)((0,
|
|
12198
|
-
await (0, import_promises10.writeFile)(
|
|
12227
|
+
await (0, import_promises10.mkdir)((0, import_node_path13.dirname)(path3), { recursive: true });
|
|
12228
|
+
await (0, import_promises10.writeFile)(path3, injected.content, "utf-8");
|
|
12199
12229
|
}
|
|
12200
|
-
results.push({ target, file, path, action });
|
|
12230
|
+
results.push({ target, file, path: path3, action });
|
|
12201
12231
|
}
|
|
12202
12232
|
const packageJsonScripts = await seedPackageJsonScripts({
|
|
12203
12233
|
cwd: options.cwd,
|
|
@@ -12208,7 +12238,7 @@ async function initAgentRules(options) {
|
|
|
12208
12238
|
function renderAgentRulesDryRun(targets) {
|
|
12209
12239
|
const files = targets.flatMap((target) => {
|
|
12210
12240
|
if (target === "cursor") {
|
|
12211
|
-
return buildCursorRulesPack().map((
|
|
12241
|
+
return buildCursorRulesPack().map((rule2) => `- cursor: .cursor/rules/${rule2.filename}`);
|
|
12212
12242
|
}
|
|
12213
12243
|
return [`- ${target}: ${AGENT_RULE_TARGETS[target].file}`];
|
|
12214
12244
|
}).join("\n");
|
|
@@ -12261,9 +12291,9 @@ function formatAgentRulesInitSummary(output) {
|
|
|
12261
12291
|
);
|
|
12262
12292
|
return lines.join("\n");
|
|
12263
12293
|
}
|
|
12264
|
-
async function readExistingFile2(
|
|
12294
|
+
async function readExistingFile2(path3) {
|
|
12265
12295
|
try {
|
|
12266
|
-
return { exists: true, content: await (0, import_promises10.readFile)(
|
|
12296
|
+
return { exists: true, content: await (0, import_promises10.readFile)(path3, "utf-8") };
|
|
12267
12297
|
} catch (error) {
|
|
12268
12298
|
if (isFileNotFoundError2(error)) {
|
|
12269
12299
|
return { exists: false, content: "" };
|
|
@@ -12431,15 +12461,15 @@ async function handleViewGaps(cwd) {
|
|
|
12431
12461
|
async function handlePrompt(cwd) {
|
|
12432
12462
|
try {
|
|
12433
12463
|
const artifact = await loadLastArtifact(cwd);
|
|
12434
|
-
const
|
|
12435
|
-
if (!
|
|
12464
|
+
const gap2 = pickGap(artifact);
|
|
12465
|
+
if (!gap2) {
|
|
12436
12466
|
M2.warn("No gaps to fix. Run a scan or pick a different project.");
|
|
12437
12467
|
return;
|
|
12438
12468
|
}
|
|
12439
|
-
const prompt = buildAgentFixPrompt(artifact,
|
|
12469
|
+
const prompt = buildAgentFixPrompt(artifact, gap2);
|
|
12440
12470
|
try {
|
|
12441
12471
|
await copyToClipboard(prompt);
|
|
12442
|
-
M2.success(import_picocolors4.default.green(`Copied top prompt to clipboard \u2014 ${
|
|
12472
|
+
M2.success(import_picocolors4.default.green(`Copied top prompt to clipboard \u2014 ${gap2.title}`));
|
|
12443
12473
|
} catch (error) {
|
|
12444
12474
|
M2.warn(error instanceof Error ? error.message : String(error));
|
|
12445
12475
|
console.log("");
|
|
@@ -12571,7 +12601,7 @@ async function runInteractiveSession(startDir = process.cwd()) {
|
|
|
12571
12601
|
Ie(`${import_picocolors4.default.bold("VibeRaven")} ${import_picocolors4.default.dim(VERSION)}`);
|
|
12572
12602
|
const cwd = await resolveWorkspaceRoot(startDir);
|
|
12573
12603
|
const artifactsAt = await findArtifactsWorkspace(startDir);
|
|
12574
|
-
if (artifactsAt && (0,
|
|
12604
|
+
if (artifactsAt && (0, import_node_path14.resolve)(artifactsAt) !== (0, import_node_path14.resolve)(startDir)) {
|
|
12575
12605
|
M2.message(import_picocolors4.default.dim(`Using scan from: ${artifactsAt}`));
|
|
12576
12606
|
} else {
|
|
12577
12607
|
M2.message(import_picocolors4.default.dim(`Project folder: ${cwd}`));
|
|
@@ -12639,11 +12669,11 @@ async function runInteractiveSession(startDir = process.cwd()) {
|
|
|
12639
12669
|
|
|
12640
12670
|
// src/commands/condense.ts
|
|
12641
12671
|
var import_promises11 = require("node:fs/promises");
|
|
12642
|
-
var
|
|
12672
|
+
var import_node_path15 = require("node:path");
|
|
12643
12673
|
async function runCondenseCommand(options) {
|
|
12644
12674
|
const dir = getProjectArtifactsDir(options.cwd);
|
|
12645
|
-
const artifact = JSON.parse(await (0, import_promises11.readFile)((0,
|
|
12646
|
-
const contextMapPath = (0,
|
|
12675
|
+
const artifact = JSON.parse(await (0, import_promises11.readFile)((0, import_node_path15.join)(dir, "last-scan.json"), "utf8"));
|
|
12676
|
+
const contextMapPath = (0, import_node_path15.join)(dir, "context-map.json");
|
|
12647
12677
|
await (0, import_promises11.writeFile)(contextMapPath, `${JSON.stringify(generateContextMap(artifact), null, 2)}
|
|
12648
12678
|
`, "utf8");
|
|
12649
12679
|
return { contextMapPath };
|
|
@@ -12652,15 +12682,15 @@ async function runCondenseCommand(options) {
|
|
|
12652
12682
|
// src/heal/apply.ts
|
|
12653
12683
|
var import_promises13 = require("node:fs/promises");
|
|
12654
12684
|
var import_node_fs10 = require("node:fs");
|
|
12655
|
-
var
|
|
12685
|
+
var import_node_path19 = require("node:path");
|
|
12656
12686
|
|
|
12657
12687
|
// src/heal/pathSafety.ts
|
|
12658
|
-
var
|
|
12688
|
+
var import_node_path16 = require("node:path");
|
|
12659
12689
|
var BLOCKED_SEGMENTS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".viberaven"]);
|
|
12660
12690
|
function assertSafeHealTarget(cwd, target) {
|
|
12661
|
-
const root = (0,
|
|
12662
|
-
const absolute = (0,
|
|
12663
|
-
const rel = (0,
|
|
12691
|
+
const root = (0, import_node_path16.resolve)(cwd);
|
|
12692
|
+
const absolute = (0, import_node_path16.resolve)(root, target);
|
|
12693
|
+
const rel = (0, import_node_path16.relative)(root, absolute);
|
|
12664
12694
|
if (rel.startsWith("..") || rel === "" || /^[A-Za-z]:/.test(rel)) {
|
|
12665
12695
|
throw new Error("Heal target must stay inside the workspace");
|
|
12666
12696
|
}
|
|
@@ -12685,7 +12715,7 @@ function applyEmptyCatchRecipe(source) {
|
|
|
12685
12715
|
// src/heal/recipes/index.ts
|
|
12686
12716
|
var import_node_fs9 = require("node:fs");
|
|
12687
12717
|
var import_promises12 = require("node:fs/promises");
|
|
12688
|
-
var
|
|
12718
|
+
var import_node_path18 = require("node:path");
|
|
12689
12719
|
|
|
12690
12720
|
// src/heal/recipes/envAuthSecret.ts
|
|
12691
12721
|
function applyAuthSecretRecipe(source) {
|
|
@@ -13067,7 +13097,7 @@ function applyRateLimitRecipe(source, hasUpstash) {
|
|
|
13067
13097
|
|
|
13068
13098
|
// src/heal/recipes/eslintRestrictedImports.ts
|
|
13069
13099
|
var import_node_fs8 = require("node:fs");
|
|
13070
|
-
var
|
|
13100
|
+
var import_node_path17 = require("node:path");
|
|
13071
13101
|
var VIBERAVEN_ESLINT_MARKER = "VibeRaven heal: eslint_restricted_imports";
|
|
13072
13102
|
var RESTRICTED_IMPORTS_MESSAGE = `Restricted import. Run ${PUBLIC_AGENT_MODE_COMMAND} before substituting packages.`;
|
|
13073
13103
|
var RESTRICTED_PATHS = [
|
|
@@ -13097,7 +13127,7 @@ var ESLINT_CONFIG_CANDIDATES = [
|
|
|
13097
13127
|
];
|
|
13098
13128
|
function detectEslintConfigFile(cwd) {
|
|
13099
13129
|
for (const candidate of ESLINT_CONFIG_CANDIDATES) {
|
|
13100
|
-
if ((0, import_node_fs8.existsSync)((0,
|
|
13130
|
+
if ((0, import_node_fs8.existsSync)((0, import_node_path17.join)(cwd, candidate))) {
|
|
13101
13131
|
return candidate;
|
|
13102
13132
|
}
|
|
13103
13133
|
}
|
|
@@ -13277,7 +13307,7 @@ async function readSourceOrEmpty(absolutePath) {
|
|
|
13277
13307
|
}
|
|
13278
13308
|
async function detectUpstash(cwd) {
|
|
13279
13309
|
try {
|
|
13280
|
-
const pkgPath = (0,
|
|
13310
|
+
const pkgPath = (0, import_node_path18.join)(cwd, "package.json");
|
|
13281
13311
|
if (!(0, import_node_fs9.existsSync)(pkgPath)) return false;
|
|
13282
13312
|
const raw = await (0, import_promises12.readFile)(pkgPath, "utf8");
|
|
13283
13313
|
const pkg = JSON.parse(raw);
|
|
@@ -13294,7 +13324,7 @@ async function dispatchRecipeByGapId(gapId, cwd, explicitTarget) {
|
|
|
13294
13324
|
const targetFile = explicitTarget ?? defaultTargetFile(gapId);
|
|
13295
13325
|
if (targetFile === void 0) return null;
|
|
13296
13326
|
if (gapId === "auth_secret_missing" || gapId === "node_env_not_set" || gapId === "database_url_missing") {
|
|
13297
|
-
const absolutePath = (0,
|
|
13327
|
+
const absolutePath = (0, import_node_path18.join)(cwd, targetFile);
|
|
13298
13328
|
const source = await readSourceOrEmpty(absolutePath);
|
|
13299
13329
|
let result;
|
|
13300
13330
|
if (gapId === "auth_secret_missing") result = applyAuthSecretRecipe(source);
|
|
@@ -13308,25 +13338,25 @@ async function dispatchRecipeByGapId(gapId, cwd, explicitTarget) {
|
|
|
13308
13338
|
};
|
|
13309
13339
|
}
|
|
13310
13340
|
if (gapId === "missing_error_boundary") {
|
|
13311
|
-
const absolutePath = (0,
|
|
13341
|
+
const absolutePath = (0, import_node_path18.join)(cwd, "app/error.tsx");
|
|
13312
13342
|
const source = await readSourceOrEmpty(absolutePath);
|
|
13313
13343
|
const result = applyErrorBoundaryRecipe(source);
|
|
13314
13344
|
return { ...result, canAutoApply: true, recipeName: gapId };
|
|
13315
13345
|
}
|
|
13316
13346
|
if (gapId === "missing_health_route") {
|
|
13317
|
-
const absolutePath = (0,
|
|
13347
|
+
const absolutePath = (0, import_node_path18.join)(cwd, "app/api/health/route.ts");
|
|
13318
13348
|
const source = await readSourceOrEmpty(absolutePath);
|
|
13319
13349
|
const result = applyHealthRouteRecipe(source);
|
|
13320
13350
|
return { ...result, canAutoApply: true, recipeName: gapId };
|
|
13321
13351
|
}
|
|
13322
13352
|
if (gapId === "missing_loading_state") {
|
|
13323
|
-
const absolutePath = (0,
|
|
13353
|
+
const absolutePath = (0, import_node_path18.join)(cwd, "app/loading.tsx");
|
|
13324
13354
|
const source = await readSourceOrEmpty(absolutePath);
|
|
13325
13355
|
const result = applyLoadingStateRecipe(source);
|
|
13326
13356
|
return { ...result, canAutoApply: true, recipeName: gapId };
|
|
13327
13357
|
}
|
|
13328
13358
|
if (gapId === "missing_404_page") {
|
|
13329
|
-
const absolutePath = (0,
|
|
13359
|
+
const absolutePath = (0, import_node_path18.join)(cwd, "app/not-found.tsx");
|
|
13330
13360
|
const source = await readSourceOrEmpty(absolutePath);
|
|
13331
13361
|
const result = applyNotFoundRecipe(source);
|
|
13332
13362
|
return { ...result, canAutoApply: true, recipeName: gapId };
|
|
@@ -13344,10 +13374,10 @@ async function dispatchRecipeByGapId(gapId, cwd, explicitTarget) {
|
|
|
13344
13374
|
}
|
|
13345
13375
|
if (gapId === "missing_csp_header") {
|
|
13346
13376
|
let configFile = "next.config.js";
|
|
13347
|
-
let absolutePath = (0,
|
|
13377
|
+
let absolutePath = (0, import_node_path18.join)(cwd, configFile);
|
|
13348
13378
|
if (!(0, import_node_fs9.existsSync)(absolutePath)) {
|
|
13349
13379
|
configFile = "next.config.mjs";
|
|
13350
|
-
absolutePath = (0,
|
|
13380
|
+
absolutePath = (0, import_node_path18.join)(cwd, configFile);
|
|
13351
13381
|
}
|
|
13352
13382
|
const source = await readSourceOrEmpty(absolutePath);
|
|
13353
13383
|
const result = applyCspHeaderRecipe(source);
|
|
@@ -13359,7 +13389,7 @@ async function dispatchRecipeByGapId(gapId, cwd, explicitTarget) {
|
|
|
13359
13389
|
}
|
|
13360
13390
|
if (gapId === "missing_rate_limit") {
|
|
13361
13391
|
const hasUpstash = await detectUpstash(cwd);
|
|
13362
|
-
const middlewarePath = (0,
|
|
13392
|
+
const middlewarePath = (0, import_node_path18.join)(cwd, "middleware.ts");
|
|
13363
13393
|
const source = await readSourceOrEmpty(middlewarePath);
|
|
13364
13394
|
const result = applyRateLimitRecipe(source, hasUpstash);
|
|
13365
13395
|
return {
|
|
@@ -13381,7 +13411,7 @@ async function dispatchRecipeByGapId(gapId, cwd, explicitTarget) {
|
|
|
13381
13411
|
recipeName: gapId
|
|
13382
13412
|
};
|
|
13383
13413
|
}
|
|
13384
|
-
const absolutePath = (0,
|
|
13414
|
+
const absolutePath = (0, import_node_path18.join)(cwd, configFile);
|
|
13385
13415
|
const source = await readSourceOrEmpty(absolutePath);
|
|
13386
13416
|
const result = applyEslintRestrictedImportsRecipe(source, configFile);
|
|
13387
13417
|
return {
|
|
@@ -13456,13 +13486,13 @@ async function applyHeal(options) {
|
|
|
13456
13486
|
rollback: { available: false, instructions: "Recipe matched but no change was needed (already applied or file already exists)." }
|
|
13457
13487
|
};
|
|
13458
13488
|
}
|
|
13459
|
-
const absoluteTarget = (0,
|
|
13489
|
+
const absoluteTarget = (0, import_node_path19.join)(options.cwd, dispatched.targetFile);
|
|
13460
13490
|
assertSafeHealTarget(options.cwd, dispatched.targetFile);
|
|
13461
|
-
await (0, import_promises13.mkdir)((0,
|
|
13462
|
-
const healDir2 = (0,
|
|
13463
|
-
await (0, import_promises13.mkdir)((0,
|
|
13491
|
+
await (0, import_promises13.mkdir)((0, import_node_path19.dirname)(absoluteTarget), { recursive: true });
|
|
13492
|
+
const healDir2 = (0, import_node_path19.join)(options.cwd, ".viberaven", "heal", id);
|
|
13493
|
+
await (0, import_promises13.mkdir)((0, import_node_path19.join)(healDir2, "before"), { recursive: true });
|
|
13464
13494
|
const beforeContent = (0, import_node_fs10.existsSync)(absoluteTarget) ? await (0, import_promises13.readFile)(absoluteTarget, "utf8") : "";
|
|
13465
|
-
await (0, import_promises13.writeFile)((0,
|
|
13495
|
+
await (0, import_promises13.writeFile)((0, import_node_path19.join)(healDir2, "before", "target.txt"), beforeContent, "utf8");
|
|
13466
13496
|
await (0, import_promises13.writeFile)(absoluteTarget, dispatched.output, "utf8");
|
|
13467
13497
|
const patch2 = [
|
|
13468
13498
|
`--- ${dispatched.targetFile}`,
|
|
@@ -13473,7 +13503,7 @@ async function applyHeal(options) {
|
|
|
13473
13503
|
dispatched.output,
|
|
13474
13504
|
""
|
|
13475
13505
|
].join("\n");
|
|
13476
|
-
await (0, import_promises13.writeFile)((0,
|
|
13506
|
+
await (0, import_promises13.writeFile)((0, import_node_path19.join)(healDir2, "patch.diff"), patch2, "utf8");
|
|
13477
13507
|
const result2 = {
|
|
13478
13508
|
$schema: "https://viberaven.dev/schemas/heal-result.schema.json",
|
|
13479
13509
|
schemaVersion: "v1",
|
|
@@ -13484,7 +13514,7 @@ async function applyHeal(options) {
|
|
|
13484
13514
|
gapId: options.gapId,
|
|
13485
13515
|
recipe: dispatched.recipeName,
|
|
13486
13516
|
target: dispatched.targetFile,
|
|
13487
|
-
changedFiles: [(0,
|
|
13517
|
+
changedFiles: [(0, import_node_path19.relative)(options.cwd, absoluteTarget).replace(/\\/g, "/")],
|
|
13488
13518
|
artifacts: {
|
|
13489
13519
|
patch: `.viberaven/heal/${id}/patch.diff`,
|
|
13490
13520
|
result: `.viberaven/heal/${id}/result.json`
|
|
@@ -13494,7 +13524,7 @@ async function applyHeal(options) {
|
|
|
13494
13524
|
instructions: "Restore .viberaven/heal/<healId>/before/target.txt to the target file or apply the reverse patch."
|
|
13495
13525
|
}
|
|
13496
13526
|
};
|
|
13497
|
-
await (0, import_promises13.writeFile)((0,
|
|
13527
|
+
await (0, import_promises13.writeFile)((0, import_node_path19.join)(healDir2, "result.json"), `${JSON.stringify(result2, null, 2)}
|
|
13498
13528
|
`, "utf8");
|
|
13499
13529
|
return result2;
|
|
13500
13530
|
}
|
|
@@ -13531,9 +13561,9 @@ async function applyHeal(options) {
|
|
|
13531
13561
|
rollback: { available: false, instructions: "No supported heal recipe matched this file." }
|
|
13532
13562
|
};
|
|
13533
13563
|
}
|
|
13534
|
-
const healDir = (0,
|
|
13535
|
-
await (0, import_promises13.mkdir)((0,
|
|
13536
|
-
await (0, import_promises13.writeFile)((0,
|
|
13564
|
+
const healDir = (0, import_node_path19.join)(options.cwd, ".viberaven", "heal", id);
|
|
13565
|
+
await (0, import_promises13.mkdir)((0, import_node_path19.join)(healDir, "before"), { recursive: true });
|
|
13566
|
+
await (0, import_promises13.writeFile)((0, import_node_path19.join)(healDir, "before", "target.txt"), before, "utf8");
|
|
13537
13567
|
await (0, import_promises13.writeFile)(absolute, recipe.output, "utf8");
|
|
13538
13568
|
const patch = [
|
|
13539
13569
|
`--- ${options.target}`,
|
|
@@ -13544,7 +13574,7 @@ async function applyHeal(options) {
|
|
|
13544
13574
|
recipe.output,
|
|
13545
13575
|
""
|
|
13546
13576
|
].join("\n");
|
|
13547
|
-
await (0, import_promises13.writeFile)((0,
|
|
13577
|
+
await (0, import_promises13.writeFile)((0, import_node_path19.join)(healDir, "patch.diff"), patch, "utf8");
|
|
13548
13578
|
const result = {
|
|
13549
13579
|
$schema: "https://viberaven.dev/schemas/heal-result.schema.json",
|
|
13550
13580
|
schemaVersion: "v1",
|
|
@@ -13555,7 +13585,7 @@ async function applyHeal(options) {
|
|
|
13555
13585
|
gapId: options.gapId,
|
|
13556
13586
|
recipe: "empty-catch-safe-response",
|
|
13557
13587
|
target: options.target,
|
|
13558
|
-
changedFiles: [(0,
|
|
13588
|
+
changedFiles: [(0, import_node_path19.relative)(options.cwd, absolute).replace(/\\/g, "/")],
|
|
13559
13589
|
artifacts: {
|
|
13560
13590
|
patch: `.viberaven/heal/${id}/patch.diff`,
|
|
13561
13591
|
result: `.viberaven/heal/${id}/result.json`
|
|
@@ -13565,19 +13595,19 @@ async function applyHeal(options) {
|
|
|
13565
13595
|
instructions: "Restore .viberaven/heal/<healId>/before/target.txt to the target file or apply the reverse patch."
|
|
13566
13596
|
}
|
|
13567
13597
|
};
|
|
13568
|
-
await (0, import_promises13.writeFile)((0,
|
|
13598
|
+
await (0, import_promises13.writeFile)((0, import_node_path19.join)(healDir, "result.json"), `${JSON.stringify(result, null, 2)}
|
|
13569
13599
|
`, "utf8");
|
|
13570
13600
|
return result;
|
|
13571
13601
|
}
|
|
13572
13602
|
|
|
13573
13603
|
// src/heal/plan.ts
|
|
13574
13604
|
var import_promises14 = require("node:fs/promises");
|
|
13575
|
-
var
|
|
13605
|
+
var import_node_path20 = require("node:path");
|
|
13576
13606
|
function healId2() {
|
|
13577
13607
|
return `heal_${(/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14)}`;
|
|
13578
13608
|
}
|
|
13579
13609
|
async function writeHealPlan(options) {
|
|
13580
|
-
const dir = (0,
|
|
13610
|
+
const dir = (0, import_node_path20.join)(options.cwd, ".viberaven");
|
|
13581
13611
|
await (0, import_promises14.mkdir)(dir, { recursive: true });
|
|
13582
13612
|
const id = healId2();
|
|
13583
13613
|
const target = options.target ?? `gap:${options.gapId}`;
|
|
@@ -13605,20 +13635,20 @@ async function writeHealPlan(options) {
|
|
|
13605
13635
|
artifacts: { plan: ".viberaven/heal-plan.md" },
|
|
13606
13636
|
rollback: { available: false, instructions: "No source files were changed." }
|
|
13607
13637
|
};
|
|
13608
|
-
await (0, import_promises14.writeFile)((0,
|
|
13609
|
-
await (0, import_promises14.writeFile)((0,
|
|
13638
|
+
await (0, import_promises14.writeFile)((0, import_node_path20.join)(dir, "heal-plan.md"), markdown, "utf8");
|
|
13639
|
+
await (0, import_promises14.writeFile)((0, import_node_path20.join)(dir, "heal-plan.json"), `${JSON.stringify(result, null, 2)}
|
|
13610
13640
|
`, "utf8");
|
|
13611
13641
|
return result;
|
|
13612
13642
|
}
|
|
13613
13643
|
|
|
13614
13644
|
// src/heal/prompt.ts
|
|
13615
13645
|
var import_promises15 = require("node:fs/promises");
|
|
13616
|
-
var
|
|
13646
|
+
var import_node_path21 = require("node:path");
|
|
13617
13647
|
function healId3() {
|
|
13618
13648
|
return `heal_${(/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14)}`;
|
|
13619
13649
|
}
|
|
13620
13650
|
async function writeHealPrompt(options) {
|
|
13621
|
-
const dir = (0,
|
|
13651
|
+
const dir = (0, import_node_path21.join)(options.cwd, ".viberaven");
|
|
13622
13652
|
await (0, import_promises15.mkdir)(dir, { recursive: true });
|
|
13623
13653
|
const id = healId3();
|
|
13624
13654
|
const target = options.target ?? `gap:${options.gapId}`;
|
|
@@ -13646,7 +13676,7 @@ async function writeHealPrompt(options) {
|
|
|
13646
13676
|
artifacts: { prompt: ".viberaven/heal-prompt.md" },
|
|
13647
13677
|
rollback: { available: false, instructions: "No source files were changed." }
|
|
13648
13678
|
};
|
|
13649
|
-
await (0, import_promises15.writeFile)((0,
|
|
13679
|
+
await (0, import_promises15.writeFile)((0, import_node_path21.join)(dir, "heal-prompt.md"), prompt, "utf8");
|
|
13650
13680
|
return result;
|
|
13651
13681
|
}
|
|
13652
13682
|
|
|
@@ -13733,7 +13763,7 @@ async function runHealCommand(options) {
|
|
|
13733
13763
|
// src/stackRecommend.ts
|
|
13734
13764
|
var import_promises17 = require("node:fs/promises");
|
|
13735
13765
|
var import_node_fs11 = require("node:fs");
|
|
13736
|
-
var
|
|
13766
|
+
var import_node_path22 = require("node:path");
|
|
13737
13767
|
var DEFAULT_STACK = {
|
|
13738
13768
|
frontend: "react",
|
|
13739
13769
|
ui: "tailwind + shadcn/ui",
|
|
@@ -13744,7 +13774,7 @@ var DEFAULT_STACK = {
|
|
|
13744
13774
|
reason: "Agent-default stack for lowest launch friction when repo signals are ambiguous"
|
|
13745
13775
|
};
|
|
13746
13776
|
async function recommendStack(cwd = process.cwd()) {
|
|
13747
|
-
const pkgPath = (0,
|
|
13777
|
+
const pkgPath = (0, import_node_path22.join)(cwd, "package.json");
|
|
13748
13778
|
if (!(0, import_node_fs11.existsSync)(pkgPath)) {
|
|
13749
13779
|
return DEFAULT_STACK;
|
|
13750
13780
|
}
|
|
@@ -13801,7 +13831,7 @@ async function runInitCommand(options) {
|
|
|
13801
13831
|
|
|
13802
13832
|
// src/commands/doctorAgents.ts
|
|
13803
13833
|
var import_promises18 = require("node:fs/promises");
|
|
13804
|
-
var
|
|
13834
|
+
var import_node_path23 = require("node:path");
|
|
13805
13835
|
var REQUIRED_EXISTENCE_CHECKS = [
|
|
13806
13836
|
{ id: "agents-md", file: "AGENTS.md" },
|
|
13807
13837
|
{ id: "claude-md", file: "CLAUDE.md" },
|
|
@@ -13821,7 +13851,7 @@ var STALE_PATTERNS = [
|
|
|
13821
13851
|
async function checkAgentInjection(cwd) {
|
|
13822
13852
|
const checks = [];
|
|
13823
13853
|
for (const item3 of REQUIRED_EXISTENCE_CHECKS) {
|
|
13824
|
-
const exists = await fileExists2((0,
|
|
13854
|
+
const exists = await fileExists2((0, import_node_path23.join)(cwd, item3.file));
|
|
13825
13855
|
checks.push({
|
|
13826
13856
|
id: item3.id,
|
|
13827
13857
|
status: exists ? "pass" : "fail",
|
|
@@ -13829,17 +13859,17 @@ async function checkAgentInjection(cwd) {
|
|
|
13829
13859
|
});
|
|
13830
13860
|
}
|
|
13831
13861
|
for (const item3 of OPTIONAL_CURSOR_PACK_CHECKS) {
|
|
13832
|
-
const exists = await fileExists2((0,
|
|
13862
|
+
const exists = await fileExists2((0, import_node_path23.join)(cwd, item3.file));
|
|
13833
13863
|
checks.push({
|
|
13834
13864
|
id: item3.id,
|
|
13835
13865
|
status: exists ? "pass" : "fail",
|
|
13836
13866
|
message: exists ? `${item3.file} exists` : `Missing ${item3.file} \u2014 run npx -y viberaven init --agents all`
|
|
13837
13867
|
});
|
|
13838
13868
|
}
|
|
13839
|
-
const legacyCursorPath = (0,
|
|
13869
|
+
const legacyCursorPath = (0, import_node_path23.join)(cwd, ".cursor/rules/viberaven.mdc");
|
|
13840
13870
|
if (await fileExists2(legacyCursorPath)) {
|
|
13841
13871
|
const legacyContent = await (0, import_promises18.readFile)(legacyCursorPath, "utf-8");
|
|
13842
|
-
const hasCoreSplit = await fileExists2((0,
|
|
13872
|
+
const hasCoreSplit = await fileExists2((0, import_node_path23.join)(cwd, ".cursor/rules/viberaven-core.mdc"));
|
|
13843
13873
|
if (!hasCoreSplit || legacyContent.includes("alwaysApply: true")) {
|
|
13844
13874
|
checks.push({
|
|
13845
13875
|
id: "cursor-legacy-mdc",
|
|
@@ -13850,8 +13880,8 @@ async function checkAgentInjection(cwd) {
|
|
|
13850
13880
|
}
|
|
13851
13881
|
for (const target of CORE_AGENT_INJECTION_TARGETS) {
|
|
13852
13882
|
const file = target === "cursor" ? ".cursor/rules/viberaven-core.mdc" : AGENT_RULE_TARGETS[target].file;
|
|
13853
|
-
const
|
|
13854
|
-
const exists = await fileExists2(
|
|
13883
|
+
const path3 = (0, import_node_path23.join)(cwd, file);
|
|
13884
|
+
const exists = await fileExists2(path3);
|
|
13855
13885
|
if (!exists) {
|
|
13856
13886
|
checks.push({
|
|
13857
13887
|
id: `canonical-${target}`,
|
|
@@ -13860,7 +13890,7 @@ async function checkAgentInjection(cwd) {
|
|
|
13860
13890
|
});
|
|
13861
13891
|
continue;
|
|
13862
13892
|
}
|
|
13863
|
-
const content = await (0, import_promises18.readFile)(
|
|
13893
|
+
const content = await (0, import_promises18.readFile)(path3, "utf-8");
|
|
13864
13894
|
const hasCommand = content.includes(PUBLIC_AGENT_MODE_COMMAND);
|
|
13865
13895
|
checks.push({
|
|
13866
13896
|
id: `canonical-${target}`,
|
|
@@ -13892,9 +13922,9 @@ function formatDoctorAgentsReport(report) {
|
|
|
13892
13922
|
lines.push(report.ok ? "All agent injection checks passed." : "Agent injection checks failed.");
|
|
13893
13923
|
return lines.join("\n");
|
|
13894
13924
|
}
|
|
13895
|
-
async function fileExists2(
|
|
13925
|
+
async function fileExists2(path3) {
|
|
13896
13926
|
try {
|
|
13897
|
-
await (0, import_promises18.access)(
|
|
13927
|
+
await (0, import_promises18.access)(path3);
|
|
13898
13928
|
return true;
|
|
13899
13929
|
} catch {
|
|
13900
13930
|
return false;
|
|
@@ -14107,6 +14137,220 @@ async function runAuditCommand(input) {
|
|
|
14107
14137
|
return result.status === "pass" ? 0 : 1;
|
|
14108
14138
|
}
|
|
14109
14139
|
|
|
14140
|
+
// src/commands/badge.ts
|
|
14141
|
+
var import_promises19 = require("node:fs/promises");
|
|
14142
|
+
var import_node_path24 = require("node:path");
|
|
14143
|
+
|
|
14144
|
+
// src/brand/palette.ts
|
|
14145
|
+
var import_picocolors5 = __toESM(require_picocolors());
|
|
14146
|
+
var STATUS_GLYPH = {
|
|
14147
|
+
clear: "\u2713",
|
|
14148
|
+
warning: "\u25C8",
|
|
14149
|
+
blocked: "\u25C6",
|
|
14150
|
+
fixable: "\u25B8",
|
|
14151
|
+
manual: "\u25C7",
|
|
14152
|
+
done: "\u2713",
|
|
14153
|
+
pending: "\xB7"
|
|
14154
|
+
};
|
|
14155
|
+
var DECISION_GLYPH = {
|
|
14156
|
+
CLEAR: STATUS_GLYPH.clear,
|
|
14157
|
+
WARNING: STATUS_GLYPH.warning,
|
|
14158
|
+
BLOCKED: STATUS_GLYPH.blocked
|
|
14159
|
+
};
|
|
14160
|
+
function identity(text) {
|
|
14161
|
+
return text;
|
|
14162
|
+
}
|
|
14163
|
+
function colorsFor(mode) {
|
|
14164
|
+
return import_picocolors5.default.createColors(mode === "rich");
|
|
14165
|
+
}
|
|
14166
|
+
function decisionColor(decision, mode) {
|
|
14167
|
+
const colors = colorsFor(mode);
|
|
14168
|
+
if (decision === "CLEAR") return colors.green;
|
|
14169
|
+
if (decision === "WARNING") return colors.yellow;
|
|
14170
|
+
return colors.red;
|
|
14171
|
+
}
|
|
14172
|
+
function decisionStyle(decision, mode) {
|
|
14173
|
+
const colorize = mode === "rich" ? decisionColor(decision, mode) : identity;
|
|
14174
|
+
return {
|
|
14175
|
+
glyph: DECISION_GLYPH[decision],
|
|
14176
|
+
label: colorize(decision),
|
|
14177
|
+
colorize
|
|
14178
|
+
};
|
|
14179
|
+
}
|
|
14180
|
+
function richOrPlain(text, mode, colorize) {
|
|
14181
|
+
return mode === "rich" ? colorize(text) : text;
|
|
14182
|
+
}
|
|
14183
|
+
var accent = {
|
|
14184
|
+
brand(text, mode) {
|
|
14185
|
+
return richOrPlain(text, mode, colorsFor(mode).cyan);
|
|
14186
|
+
},
|
|
14187
|
+
dim(text, mode) {
|
|
14188
|
+
return richOrPlain(text, mode, colorsFor(mode).dim);
|
|
14189
|
+
},
|
|
14190
|
+
bold(text, mode) {
|
|
14191
|
+
return richOrPlain(text, mode, colorsFor(mode).bold);
|
|
14192
|
+
}
|
|
14193
|
+
};
|
|
14194
|
+
|
|
14195
|
+
// src/brand/ui.ts
|
|
14196
|
+
var import_picocolors6 = __toESM(require_picocolors());
|
|
14197
|
+
var ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
|
|
14198
|
+
function colorsFor2(mode) {
|
|
14199
|
+
return import_picocolors6.default.createColors(mode === "rich");
|
|
14200
|
+
}
|
|
14201
|
+
function stripAnsi(value) {
|
|
14202
|
+
return value.replace(ANSI_PATTERN, "");
|
|
14203
|
+
}
|
|
14204
|
+
function visibleWidth(value) {
|
|
14205
|
+
return stripAnsi(value).length;
|
|
14206
|
+
}
|
|
14207
|
+
function padVisible(value, width) {
|
|
14208
|
+
return `${value}${" ".repeat(Math.max(0, width - visibleWidth(value)))}`;
|
|
14209
|
+
}
|
|
14210
|
+
function safeWidth(width) {
|
|
14211
|
+
if (!Number.isFinite(width)) return 1;
|
|
14212
|
+
return Math.max(1, Math.floor(width));
|
|
14213
|
+
}
|
|
14214
|
+
function clampPercent(percent) {
|
|
14215
|
+
if (Number.isNaN(percent)) return 0;
|
|
14216
|
+
return Math.max(0, Math.min(100, percent));
|
|
14217
|
+
}
|
|
14218
|
+
function colorizeGaugeFill(text, percent, mode) {
|
|
14219
|
+
const colors = colorsFor2(mode);
|
|
14220
|
+
if (mode !== "rich") return text;
|
|
14221
|
+
if (percent >= 80) return colors.green(text);
|
|
14222
|
+
if (percent >= 50) return colors.yellow(text);
|
|
14223
|
+
return colors.red(text);
|
|
14224
|
+
}
|
|
14225
|
+
function rule(width, options) {
|
|
14226
|
+
const line = "\u2500".repeat(safeWidth(width));
|
|
14227
|
+
return accent.dim(line, options.mode);
|
|
14228
|
+
}
|
|
14229
|
+
function box(lines, options) {
|
|
14230
|
+
const content = lines.length > 0 ? lines : [""];
|
|
14231
|
+
const innerWidth = Math.max(1, ...content.map(visibleWidth));
|
|
14232
|
+
const horizontal = "\u2500".repeat(innerWidth + 2);
|
|
14233
|
+
const colors = colorsFor2(options.mode);
|
|
14234
|
+
const border = (value) => options.mode === "rich" ? colors.cyan(value) : value;
|
|
14235
|
+
const body = content.map((line) => border("\u2502") + ` ${padVisible(line, innerWidth)} ` + border("\u2502"));
|
|
14236
|
+
return [border(`\u250C${horizontal}\u2510`), ...body, border(`\u2514${horizontal}\u2518`)].join("\n");
|
|
14237
|
+
}
|
|
14238
|
+
function gauge(percent, options) {
|
|
14239
|
+
const width = safeWidth(options.width);
|
|
14240
|
+
const clamped = clampPercent(percent);
|
|
14241
|
+
const label2 = `${Math.round(clamped)}%`;
|
|
14242
|
+
const filledWidth = Math.round(clamped / 100 * width);
|
|
14243
|
+
const emptyWidth = width - filledWidth;
|
|
14244
|
+
const filled = colorizeGaugeFill("\u2588".repeat(filledWidth), clamped, options.mode);
|
|
14245
|
+
const empty = accent.dim("\u2591".repeat(emptyWidth), options.mode);
|
|
14246
|
+
return `[${filled}${empty}] ${label2}`;
|
|
14247
|
+
}
|
|
14248
|
+
function banner(decision, options) {
|
|
14249
|
+
const style = decisionStyle(decision, options.mode);
|
|
14250
|
+
return `${style.glyph} ${style.label}`;
|
|
14251
|
+
}
|
|
14252
|
+
function kv(key, value, options) {
|
|
14253
|
+
return `${accent.dim(key, options.mode)}: ${value}`;
|
|
14254
|
+
}
|
|
14255
|
+
|
|
14256
|
+
// src/brand/wordmark.ts
|
|
14257
|
+
var DEFAULT_TAGLINE = "production operator for AI-built apps";
|
|
14258
|
+
function wordmark(options) {
|
|
14259
|
+
const tagline = options.tagline ?? DEFAULT_TAGLINE;
|
|
14260
|
+
const brand = accent.brand("VibeRaven", options.mode);
|
|
14261
|
+
const mutedTagline = accent.dim(tagline, options.mode);
|
|
14262
|
+
if (options.variant === "compact") {
|
|
14263
|
+
return `${brand} - ${mutedTagline}`;
|
|
14264
|
+
}
|
|
14265
|
+
return [accent.bold(brand, options.mode), mutedTagline].join("\n");
|
|
14266
|
+
}
|
|
14267
|
+
|
|
14268
|
+
// src/badge/renderBadge.ts
|
|
14269
|
+
var COLORS = {
|
|
14270
|
+
CLEAR: "#2ea44f",
|
|
14271
|
+
WARNING: "#dbab09",
|
|
14272
|
+
BLOCKED: "#d1242f"
|
|
14273
|
+
};
|
|
14274
|
+
function label(prp) {
|
|
14275
|
+
return prp.decision.toLowerCase();
|
|
14276
|
+
}
|
|
14277
|
+
function renderBadgeSvg(prp) {
|
|
14278
|
+
const text = label(prp);
|
|
14279
|
+
const color = COLORS[prp.decision];
|
|
14280
|
+
const width = 132 + text.length * 7;
|
|
14281
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="20" role="img" aria-label="VibeRaven: ${text}">
|
|
14282
|
+
<rect width="${width}" height="20" fill="#24292f"/>
|
|
14283
|
+
<rect x="86" width="${width - 86}" height="20" fill="${color}"/>
|
|
14284
|
+
<g fill="#fff" font-family="Verdana,Geneva,sans-serif" font-size="11">
|
|
14285
|
+
<text x="8" y="14">VibeRaven</text>
|
|
14286
|
+
<text x="94" y="14">${text}</text>
|
|
14287
|
+
</g>
|
|
14288
|
+
</svg>`;
|
|
14289
|
+
}
|
|
14290
|
+
function renderBadgeMarkdown(prp) {
|
|
14291
|
+
const text = label(prp);
|
|
14292
|
+
const date = prp.generatedAt.slice(0, 10);
|
|
14293
|
+
return `[](https://viberaven.dev/production-protocol.md?utm_source=badge) \`gate: ${text} - ${date}\``;
|
|
14294
|
+
}
|
|
14295
|
+
function renderBadgeCard(prp, mode) {
|
|
14296
|
+
const text = label(prp);
|
|
14297
|
+
const date = prp.generatedAt.slice(0, 10);
|
|
14298
|
+
const style = decisionStyle(prp.decision, mode);
|
|
14299
|
+
return box(
|
|
14300
|
+
[
|
|
14301
|
+
wordmark({ variant: "compact", mode, tagline: "launch gate proof" }),
|
|
14302
|
+
`${accent.dim("gate", mode)}: ${style.colorize(text)}`,
|
|
14303
|
+
`${accent.dim("date", mode)}: ${date}`,
|
|
14304
|
+
accent.brand("viberaven.dev", mode)
|
|
14305
|
+
],
|
|
14306
|
+
{ mode }
|
|
14307
|
+
);
|
|
14308
|
+
}
|
|
14309
|
+
|
|
14310
|
+
// src/brand/renderMode.ts
|
|
14311
|
+
function resolveRenderMode(input) {
|
|
14312
|
+
if (input.env.VIBERAVEN_FORCE_RICH === "1") return "rich";
|
|
14313
|
+
if (input.env.VIBERAVEN_PLAIN === "1") return "plain";
|
|
14314
|
+
if (input.env.NO_COLOR !== void 0 && input.env.NO_COLOR !== "") return "plain";
|
|
14315
|
+
if (input.surface === "agent-mode") return "plain";
|
|
14316
|
+
return input.isTTY ? "rich" : "plain";
|
|
14317
|
+
}
|
|
14318
|
+
function currentRenderMode(surface) {
|
|
14319
|
+
return resolveRenderMode({
|
|
14320
|
+
surface,
|
|
14321
|
+
isTTY: Boolean(process.stdout.isTTY),
|
|
14322
|
+
env: process.env
|
|
14323
|
+
});
|
|
14324
|
+
}
|
|
14325
|
+
|
|
14326
|
+
// src/commands/badge.ts
|
|
14327
|
+
async function runBadgeCommand(options) {
|
|
14328
|
+
const log = options.log ?? ((message) => console.log(message));
|
|
14329
|
+
const artifactDir = (0, import_node_path24.join)(options.cwd, ".viberaven");
|
|
14330
|
+
const prpPath = (0, import_node_path24.join)(artifactDir, "prp.json");
|
|
14331
|
+
let prp;
|
|
14332
|
+
try {
|
|
14333
|
+
prp = JSON.parse(await (0, import_promises19.readFile)(prpPath, "utf8"));
|
|
14334
|
+
} catch {
|
|
14335
|
+
log("No .viberaven/prp.json found. Run: npx -y viberaven --agent-mode");
|
|
14336
|
+
return 1;
|
|
14337
|
+
}
|
|
14338
|
+
if (options.svg) {
|
|
14339
|
+
const outputPath = (0, import_node_path24.join)(artifactDir, "badge.svg");
|
|
14340
|
+
await (0, import_promises19.mkdir)(artifactDir, { recursive: true });
|
|
14341
|
+
await (0, import_promises19.writeFile)(outputPath, renderBadgeSvg(prp), "utf8");
|
|
14342
|
+
log(`Wrote ${outputPath}`);
|
|
14343
|
+
return 0;
|
|
14344
|
+
}
|
|
14345
|
+
if (options.card) {
|
|
14346
|
+
log(renderBadgeCard(prp, currentRenderMode("human-command")));
|
|
14347
|
+
return 0;
|
|
14348
|
+
}
|
|
14349
|
+
log("Add this to your README:");
|
|
14350
|
+
log(renderBadgeMarkdown(prp));
|
|
14351
|
+
return 0;
|
|
14352
|
+
}
|
|
14353
|
+
|
|
14110
14354
|
// src/output/nextActionBlock.ts
|
|
14111
14355
|
function buildNextActionBlock(tasks, loopState, plan) {
|
|
14112
14356
|
const batchSize = plan === "pro" ? 10 : 3;
|
|
@@ -14201,14 +14445,9 @@ function buildProviderActionBlock(task) {
|
|
|
14201
14445
|
dashboardUrl: pa.dashboardUrl,
|
|
14202
14446
|
exactStep: pa.exactStep,
|
|
14203
14447
|
envKeyName: pa.envKeyName ?? null,
|
|
14204
|
-
envKeyExample: pa.envKeyExample ?? null,
|
|
14205
14448
|
doneSignal: pa.doneSignal,
|
|
14206
14449
|
verifyCommand: task.verifyCommand,
|
|
14207
|
-
mcpAlternative: task.mcpTool ?? pa.mcpAlternative ?? null
|
|
14208
|
-
actionClass: pa.actionClass ?? "manual_provider_step",
|
|
14209
|
-
approvalRequired: pa.approvalRequired ?? true,
|
|
14210
|
-
manualFallback: pa.manualFallback ?? pa.exactStep,
|
|
14211
|
-
copyValues: pa.copyValues ?? []
|
|
14450
|
+
mcpAlternative: task.mcpTool ?? pa.mcpAlternative ?? null
|
|
14212
14451
|
}
|
|
14213
14452
|
};
|
|
14214
14453
|
}
|
|
@@ -14229,6 +14468,225 @@ function printNextActionBlock(block) {
|
|
|
14229
14468
|
console.log(NEXT_ACTION_END);
|
|
14230
14469
|
}
|
|
14231
14470
|
|
|
14471
|
+
// src/output/operatorBlock.ts
|
|
14472
|
+
var OPERATOR_START = "VIBERAVEN_OPERATOR_START";
|
|
14473
|
+
var OPERATOR_END = "VIBERAVEN_OPERATOR_END";
|
|
14474
|
+
function stackLine(prp) {
|
|
14475
|
+
const parts = Array.from(
|
|
14476
|
+
/* @__PURE__ */ new Set([
|
|
14477
|
+
...prp.detectedStack.deployment,
|
|
14478
|
+
...prp.detectedStack.database,
|
|
14479
|
+
...prp.detectedStack.auth,
|
|
14480
|
+
...prp.detectedStack.payments,
|
|
14481
|
+
...prp.detectedStack.monitoring
|
|
14482
|
+
])
|
|
14483
|
+
);
|
|
14484
|
+
if (parts.length > 0) {
|
|
14485
|
+
return parts.join(" + ");
|
|
14486
|
+
}
|
|
14487
|
+
return prp.detectedStack.archetype ?? "unknown";
|
|
14488
|
+
}
|
|
14489
|
+
function uniqueEnvNames(tasks) {
|
|
14490
|
+
return Array.from(
|
|
14491
|
+
new Set(
|
|
14492
|
+
tasks.map((task) => task.providerAction?.envKeyName).filter((name) => Boolean(name))
|
|
14493
|
+
)
|
|
14494
|
+
);
|
|
14495
|
+
}
|
|
14496
|
+
function humanTaskText(task) {
|
|
14497
|
+
if (task.fixType === "provider-action") {
|
|
14498
|
+
return task.providerAction?.exactStep ?? task.title;
|
|
14499
|
+
}
|
|
14500
|
+
if (task.fixType === "upgrade-required") {
|
|
14501
|
+
return `Upgrade required: ${task.action ?? task.exactFix ?? task.title}`;
|
|
14502
|
+
}
|
|
14503
|
+
if (task.fixType === "manual-verify") {
|
|
14504
|
+
return `Manual verification required: ${task.action ?? task.exactFix ?? task.title}`;
|
|
14505
|
+
}
|
|
14506
|
+
return task.title;
|
|
14507
|
+
}
|
|
14508
|
+
function riskSuffixFor(task, prp) {
|
|
14509
|
+
const topRisk = prp.findings.find((finding) => finding.id === task.gapId)?.riskMapping?.owaspLlm?.[0];
|
|
14510
|
+
return topRisk ? ` (risk: ${topRisk})` : "";
|
|
14511
|
+
}
|
|
14512
|
+
function readinessPercent(prp) {
|
|
14513
|
+
if (prp.decision === "CLEAR") return 100;
|
|
14514
|
+
if (prp.decision === "WARNING") return 65;
|
|
14515
|
+
return 25;
|
|
14516
|
+
}
|
|
14517
|
+
function buildPlainOperatorBlock(prp, tasks) {
|
|
14518
|
+
const repoTasks = tasks.filter((task) => task.fixType === "repo-code");
|
|
14519
|
+
const humanTasks = tasks.filter((task) => task.fixType !== "repo-code");
|
|
14520
|
+
const providerTasks = tasks.filter((task) => task.fixType === "provider-action");
|
|
14521
|
+
const nextRepoTask = repoTasks[0];
|
|
14522
|
+
const envNames = uniqueEnvNames(providerTasks);
|
|
14523
|
+
const lines = [];
|
|
14524
|
+
lines.push(`Decision: ${prp.decision}`);
|
|
14525
|
+
lines.push(`Detected stack: ${stackLine(prp)}`);
|
|
14526
|
+
lines.push("");
|
|
14527
|
+
lines.push("What I can fix:");
|
|
14528
|
+
if (repoTasks.length === 0) {
|
|
14529
|
+
lines.push(" (none - no automated repo-code recipes apply)");
|
|
14530
|
+
} else {
|
|
14531
|
+
repoTasks.forEach((task, index) => {
|
|
14532
|
+
lines.push(` ${index + 1}. ${task.title}${riskSuffixFor(task, prp)}`);
|
|
14533
|
+
});
|
|
14534
|
+
}
|
|
14535
|
+
lines.push("");
|
|
14536
|
+
lines.push("What you must do:");
|
|
14537
|
+
if (humanTasks.length === 0) {
|
|
14538
|
+
lines.push(" (none - no human-owned steps required)");
|
|
14539
|
+
} else {
|
|
14540
|
+
humanTasks.forEach((task, index) => {
|
|
14541
|
+
lines.push(` ${index + 1}. ${humanTaskText(task)}${riskSuffixFor(task, prp)}`);
|
|
14542
|
+
});
|
|
14543
|
+
}
|
|
14544
|
+
lines.push("");
|
|
14545
|
+
lines.push("Next repo-code fix:");
|
|
14546
|
+
lines.push(
|
|
14547
|
+
nextRepoTask ? ` ${nextRepoTask.title}${riskSuffixFor(nextRepoTask, prp)} -> npx -y viberaven --heal --apply --gap ${nextRepoTask.gapId} --yes` : " (none)"
|
|
14548
|
+
);
|
|
14549
|
+
lines.push("");
|
|
14550
|
+
lines.push("Provider actions:");
|
|
14551
|
+
if (providerTasks.length === 0) {
|
|
14552
|
+
lines.push(" (none)");
|
|
14553
|
+
} else {
|
|
14554
|
+
providerTasks.forEach((task) => {
|
|
14555
|
+
const providerAction = task.providerAction;
|
|
14556
|
+
if (!providerAction) {
|
|
14557
|
+
lines.push(` - ${task.title}${riskSuffixFor(task, prp)}`);
|
|
14558
|
+
return;
|
|
14559
|
+
}
|
|
14560
|
+
lines.push(` - ${providerAction.provider}: ${providerAction.dashboardUrl}`);
|
|
14561
|
+
lines.push(` Step: ${providerAction.exactStep}${riskSuffixFor(task, prp)}`);
|
|
14562
|
+
lines.push(` Done when: ${providerAction.doneSignal}`);
|
|
14563
|
+
});
|
|
14564
|
+
}
|
|
14565
|
+
lines.push("");
|
|
14566
|
+
lines.push("Copy these env var names:");
|
|
14567
|
+
lines.push(envNames.length > 0 ? ` ${envNames.join(", ")}` : " (none)");
|
|
14568
|
+
lines.push("");
|
|
14569
|
+
lines.push(`Verify command: ${prp.verifyCommand}`);
|
|
14570
|
+
lines.push("");
|
|
14571
|
+
lines.push(`Do not deploy until: decision is CLEAR (current: ${prp.decision}). Run the verify command after fixes.`);
|
|
14572
|
+
if (prp.decision === "CLEAR") {
|
|
14573
|
+
lines.push("Share your launch proof: npx -y viberaven badge");
|
|
14574
|
+
}
|
|
14575
|
+
return lines.join("\n");
|
|
14576
|
+
}
|
|
14577
|
+
function buildRichOperatorBlock(prp, tasks) {
|
|
14578
|
+
const repoTasks = tasks.filter((task) => task.fixType === "repo-code");
|
|
14579
|
+
const humanTasks = tasks.filter((task) => task.fixType !== "repo-code");
|
|
14580
|
+
const providerTasks = tasks.filter((task) => task.fixType === "provider-action");
|
|
14581
|
+
const nextRepoTask = repoTasks[0];
|
|
14582
|
+
const envNames = uniqueEnvNames(providerTasks);
|
|
14583
|
+
const mode = "rich";
|
|
14584
|
+
const lines = [];
|
|
14585
|
+
lines.push(wordmark({ variant: "compact", mode }));
|
|
14586
|
+
lines.push(rule(64, { mode }));
|
|
14587
|
+
lines.push(
|
|
14588
|
+
`${accent.bold("Decision", mode)}: ${banner(prp.decision, { mode })} ${gauge(readinessPercent(prp), {
|
|
14589
|
+
width: 18,
|
|
14590
|
+
mode
|
|
14591
|
+
})}`
|
|
14592
|
+
);
|
|
14593
|
+
lines.push(kv("Detected stack", stackLine(prp), { mode }));
|
|
14594
|
+
lines.push("");
|
|
14595
|
+
lines.push(`${accent.bold("What I can fix", mode)}:`);
|
|
14596
|
+
if (repoTasks.length === 0) {
|
|
14597
|
+
lines.push(` ${accent.dim("(none - no automated repo-code recipes apply)", mode)}`);
|
|
14598
|
+
} else {
|
|
14599
|
+
repoTasks.forEach((task, index) => {
|
|
14600
|
+
lines.push(` ${index + 1}. ${task.title}${riskSuffixFor(task, prp)}`);
|
|
14601
|
+
});
|
|
14602
|
+
}
|
|
14603
|
+
lines.push("");
|
|
14604
|
+
lines.push(`${accent.bold("What you must do", mode)}:`);
|
|
14605
|
+
if (humanTasks.length === 0) {
|
|
14606
|
+
lines.push(` ${accent.dim("(none - no human-owned steps required)", mode)}`);
|
|
14607
|
+
} else {
|
|
14608
|
+
humanTasks.forEach((task, index) => {
|
|
14609
|
+
lines.push(` ${index + 1}. ${humanTaskText(task)}${riskSuffixFor(task, prp)}`);
|
|
14610
|
+
});
|
|
14611
|
+
}
|
|
14612
|
+
lines.push("");
|
|
14613
|
+
lines.push(`${accent.bold("Next repo-code fix", mode)}:`);
|
|
14614
|
+
lines.push(
|
|
14615
|
+
nextRepoTask ? ` ${nextRepoTask.title}${riskSuffixFor(nextRepoTask, prp)} -> npx -y viberaven --heal --apply --gap ${nextRepoTask.gapId} --yes` : ` ${accent.dim("(none)", mode)}`
|
|
14616
|
+
);
|
|
14617
|
+
lines.push("");
|
|
14618
|
+
lines.push(`${accent.bold("Provider actions", mode)}:`);
|
|
14619
|
+
if (providerTasks.length === 0) {
|
|
14620
|
+
lines.push(` ${accent.dim("(none)", mode)}`);
|
|
14621
|
+
} else {
|
|
14622
|
+
providerTasks.forEach((task) => {
|
|
14623
|
+
const providerAction = task.providerAction;
|
|
14624
|
+
if (!providerAction) {
|
|
14625
|
+
lines.push(` - ${task.title}${riskSuffixFor(task, prp)}`);
|
|
14626
|
+
return;
|
|
14627
|
+
}
|
|
14628
|
+
lines.push(` - ${accent.brand(providerAction.provider, mode)}: ${providerAction.dashboardUrl}`);
|
|
14629
|
+
lines.push(` ${kv("Step", `${providerAction.exactStep}${riskSuffixFor(task, prp)}`, { mode })}`);
|
|
14630
|
+
lines.push(` ${kv("Done when", providerAction.doneSignal, { mode })}`);
|
|
14631
|
+
});
|
|
14632
|
+
}
|
|
14633
|
+
lines.push("");
|
|
14634
|
+
lines.push(`${accent.bold("Copy these env var names", mode)}:`);
|
|
14635
|
+
lines.push(envNames.length > 0 ? ` ${envNames.join(", ")}` : ` ${accent.dim("(none)", mode)}`);
|
|
14636
|
+
lines.push("");
|
|
14637
|
+
lines.push(kv("Verify command", prp.verifyCommand, { mode }));
|
|
14638
|
+
lines.push("");
|
|
14639
|
+
lines.push(
|
|
14640
|
+
`${accent.bold("Do not deploy until", mode)}: decision is CLEAR (current: ${banner(
|
|
14641
|
+
prp.decision,
|
|
14642
|
+
{ mode }
|
|
14643
|
+
)}). Run the verify command after fixes.`
|
|
14644
|
+
);
|
|
14645
|
+
if (prp.decision === "CLEAR") {
|
|
14646
|
+
lines.push(kv("Share your launch proof", "npx -y viberaven badge", { mode }));
|
|
14647
|
+
}
|
|
14648
|
+
return lines.join("\n");
|
|
14649
|
+
}
|
|
14650
|
+
function buildOperatorBlock(prp, tasks, mode = "plain") {
|
|
14651
|
+
return mode === "rich" ? buildRichOperatorBlock(prp, tasks) : buildPlainOperatorBlock(prp, tasks);
|
|
14652
|
+
}
|
|
14653
|
+
function printOperatorBlock(prp, tasks, mode = "plain") {
|
|
14654
|
+
if (mode === "rich") {
|
|
14655
|
+
console.log(buildOperatorBlock(prp, tasks, "rich"));
|
|
14656
|
+
}
|
|
14657
|
+
console.log(OPERATOR_START);
|
|
14658
|
+
console.log(buildOperatorBlock(prp, tasks, "plain"));
|
|
14659
|
+
console.log(OPERATOR_END);
|
|
14660
|
+
}
|
|
14661
|
+
|
|
14662
|
+
// src/output/celebration.ts
|
|
14663
|
+
function renderClearCelebration(prp, mode) {
|
|
14664
|
+
if (prp.decision !== "CLEAR") return "";
|
|
14665
|
+
const verifiedDate = prp.generatedAt.slice(0, 10);
|
|
14666
|
+
const style = decisionStyle("CLEAR", mode);
|
|
14667
|
+
const lines = [
|
|
14668
|
+
`${style.glyph} ${style.label} production gate is clear`,
|
|
14669
|
+
accent.dim(`Verified ${verifiedDate}`, mode),
|
|
14670
|
+
"",
|
|
14671
|
+
`Share your launch proof: ${accent.bold("npx -y viberaven badge", mode)}`
|
|
14672
|
+
];
|
|
14673
|
+
return [wordmark({ variant: "compact", mode }), box(lines, { mode }), rule(40, { mode })].join("\n");
|
|
14674
|
+
}
|
|
14675
|
+
|
|
14676
|
+
// src/output/loopProgress.ts
|
|
14677
|
+
function safeCount(value) {
|
|
14678
|
+
if (!Number.isFinite(value)) return 0;
|
|
14679
|
+
return Math.max(0, Math.floor(value));
|
|
14680
|
+
}
|
|
14681
|
+
function renderLoopProgress(input, mode) {
|
|
14682
|
+
if (mode === "plain" && input.silentInPlain) return "";
|
|
14683
|
+
const total = safeCount(input.total);
|
|
14684
|
+
const applied = total > 0 ? Math.min(safeCount(input.applied), total) : safeCount(input.applied);
|
|
14685
|
+
const percent = total === 0 ? 100 : applied / total * 100;
|
|
14686
|
+
const label2 = `${STATUS_GLYPH.fixable} ${input.label}`;
|
|
14687
|
+
return `${accent.bold(label2, mode)} ${gauge(percent, { width: 12, mode })} ${applied}/${total}`;
|
|
14688
|
+
}
|
|
14689
|
+
|
|
14232
14690
|
// src/providerMcpBridge.ts
|
|
14233
14691
|
var import_node_fs12 = require("node:fs");
|
|
14234
14692
|
var import_node_os2 = require("node:os");
|
|
@@ -14270,12 +14728,12 @@ function findServerEntry(servers, provider2) {
|
|
|
14270
14728
|
}
|
|
14271
14729
|
function findProviderMcpConfig(provider2, configPaths) {
|
|
14272
14730
|
const paths = configPaths ?? resolveConfigPaths();
|
|
14273
|
-
for (const
|
|
14274
|
-
if (!(0, import_node_fs12.existsSync)(
|
|
14731
|
+
for (const path3 of paths) {
|
|
14732
|
+
if (!(0, import_node_fs12.existsSync)(path3)) {
|
|
14275
14733
|
continue;
|
|
14276
14734
|
}
|
|
14277
14735
|
try {
|
|
14278
|
-
const raw = JSON.parse((0, import_node_fs12.readFileSync)(
|
|
14736
|
+
const raw = JSON.parse((0, import_node_fs12.readFileSync)(path3, "utf8"));
|
|
14279
14737
|
const servers = parseMcpServers(raw);
|
|
14280
14738
|
if (!servers) {
|
|
14281
14739
|
continue;
|
|
@@ -14289,7 +14747,7 @@ function findProviderMcpConfig(provider2, configPaths) {
|
|
|
14289
14747
|
command: typeof server.command === "string" ? server.command : void 0,
|
|
14290
14748
|
args: Array.isArray(server.args) ? server.args.filter((arg) => typeof arg === "string") : void 0,
|
|
14291
14749
|
url: typeof server.url === "string" ? server.url : void 0,
|
|
14292
|
-
source:
|
|
14750
|
+
source: path3
|
|
14293
14751
|
};
|
|
14294
14752
|
} catch {
|
|
14295
14753
|
continue;
|
|
@@ -14297,6 +14755,19 @@ function findProviderMcpConfig(provider2, configPaths) {
|
|
|
14297
14755
|
}
|
|
14298
14756
|
return void 0;
|
|
14299
14757
|
}
|
|
14758
|
+
function hasUsableProviderMcpConfig(config) {
|
|
14759
|
+
return Boolean(config.command?.trim() || config.url?.trim());
|
|
14760
|
+
}
|
|
14761
|
+
function findUsableProviderMcpConfig(provider2, configPaths) {
|
|
14762
|
+
const paths = configPaths ?? resolveConfigPaths();
|
|
14763
|
+
for (const path3 of paths) {
|
|
14764
|
+
const config = findProviderMcpConfig(provider2, [path3]);
|
|
14765
|
+
if (config && hasUsableProviderMcpConfig(config)) {
|
|
14766
|
+
return config;
|
|
14767
|
+
}
|
|
14768
|
+
}
|
|
14769
|
+
return void 0;
|
|
14770
|
+
}
|
|
14300
14771
|
async function verifyProviderGap(options) {
|
|
14301
14772
|
if (options.plan !== "pro") {
|
|
14302
14773
|
return {
|
|
@@ -14327,6 +14798,254 @@ async function verifyProviderGap(options) {
|
|
|
14327
14798
|
};
|
|
14328
14799
|
}
|
|
14329
14800
|
|
|
14801
|
+
// src/connectedTools.ts
|
|
14802
|
+
var DETECTED_PROVIDERS = ["supabase", "vercel", "stripe", "github"];
|
|
14803
|
+
var INSTALL_HINTS = {
|
|
14804
|
+
supabase: "claude mcp add --transport http supabase https://mcp.supabase.com/",
|
|
14805
|
+
vercel: "claude mcp add --transport http vercel https://mcp.vercel.com/",
|
|
14806
|
+
stripe: "claude mcp add --transport http stripe https://mcp.stripe.com/",
|
|
14807
|
+
github: "claude mcp add --transport http github https://api.githubcopilot.com/mcp/"
|
|
14808
|
+
};
|
|
14809
|
+
var READONLY_PROVIDERS = /* @__PURE__ */ new Set(["vercel"]);
|
|
14810
|
+
function isDetectedProvider(provider2) {
|
|
14811
|
+
return DETECTED_PROVIDERS.includes(provider2);
|
|
14812
|
+
}
|
|
14813
|
+
function installHintFor(provider2) {
|
|
14814
|
+
const normalizedProvider = provider2.toLowerCase().trim();
|
|
14815
|
+
if (isDetectedProvider(normalizedProvider)) {
|
|
14816
|
+
return INSTALL_HINTS[normalizedProvider];
|
|
14817
|
+
}
|
|
14818
|
+
return `Add the ${provider2} MCP server to your agent config.`;
|
|
14819
|
+
}
|
|
14820
|
+
function detectConnectedTools() {
|
|
14821
|
+
const tools = {};
|
|
14822
|
+
for (const provider2 of DETECTED_PROVIDERS) {
|
|
14823
|
+
const config = findUsableProviderMcpConfig(provider2);
|
|
14824
|
+
if (!config) {
|
|
14825
|
+
tools[`${provider2}Mcp`] = "missing";
|
|
14826
|
+
continue;
|
|
14827
|
+
}
|
|
14828
|
+
tools[`${provider2}Mcp`] = READONLY_PROVIDERS.has(provider2) ? "available_readonly" : "available";
|
|
14829
|
+
}
|
|
14830
|
+
tools.browser = "available";
|
|
14831
|
+
return tools;
|
|
14832
|
+
}
|
|
14833
|
+
|
|
14834
|
+
// src/demo/runDemo.ts
|
|
14835
|
+
var import_node_fs13 = require("node:fs");
|
|
14836
|
+
var import_node_path27 = __toESM(require("node:path"));
|
|
14837
|
+
|
|
14838
|
+
// src/demo/localRules.ts
|
|
14839
|
+
var import_promises20 = require("node:fs/promises");
|
|
14840
|
+
var import_node_path26 = __toESM(require("node:path"));
|
|
14841
|
+
var DEMO_SCANNED_AT = "2026-06-14T00:00:00.000Z";
|
|
14842
|
+
async function readKnownFile(root, relativePath) {
|
|
14843
|
+
try {
|
|
14844
|
+
return await (0, import_promises20.readFile)(import_node_path26.default.join(root, relativePath), "utf8");
|
|
14845
|
+
} catch (error) {
|
|
14846
|
+
const code = error.code;
|
|
14847
|
+
if (code === "ENOENT") return "";
|
|
14848
|
+
throw error;
|
|
14849
|
+
}
|
|
14850
|
+
}
|
|
14851
|
+
function dependencyNames(packageJson) {
|
|
14852
|
+
if (!packageJson.trim()) return /* @__PURE__ */ new Set();
|
|
14853
|
+
const parsed = JSON.parse(packageJson);
|
|
14854
|
+
return /* @__PURE__ */ new Set([
|
|
14855
|
+
...Object.keys(parsed.dependencies ?? {}),
|
|
14856
|
+
...Object.keys(parsed.devDependencies ?? {})
|
|
14857
|
+
]);
|
|
14858
|
+
}
|
|
14859
|
+
function stripTypeScriptComments(source) {
|
|
14860
|
+
return source.replace(/\/\*[\s\S]*?\*\//g, "").split("\n").map((line) => {
|
|
14861
|
+
let quote = null;
|
|
14862
|
+
let escaped = false;
|
|
14863
|
+
for (let index = 0; index < line.length - 1; index += 1) {
|
|
14864
|
+
const char = line[index];
|
|
14865
|
+
const next = line[index + 1];
|
|
14866
|
+
if (quote) {
|
|
14867
|
+
if (escaped) {
|
|
14868
|
+
escaped = false;
|
|
14869
|
+
} else if (char === "\\") {
|
|
14870
|
+
escaped = true;
|
|
14871
|
+
} else if (char === quote) {
|
|
14872
|
+
quote = null;
|
|
14873
|
+
}
|
|
14874
|
+
continue;
|
|
14875
|
+
}
|
|
14876
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
14877
|
+
quote = char;
|
|
14878
|
+
continue;
|
|
14879
|
+
}
|
|
14880
|
+
if (char === "/" && next === "/") {
|
|
14881
|
+
return line.slice(0, index);
|
|
14882
|
+
}
|
|
14883
|
+
}
|
|
14884
|
+
return line;
|
|
14885
|
+
}).join("\n");
|
|
14886
|
+
}
|
|
14887
|
+
function stripSqlComments(source) {
|
|
14888
|
+
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/--.*$/gm, "");
|
|
14889
|
+
}
|
|
14890
|
+
function hasStripeWebhookSignatureVerification(source) {
|
|
14891
|
+
const executable = stripTypeScriptComments(source);
|
|
14892
|
+
return /webhooks\s*\.\s*constructEvent\s*\(/.test(executable) && /Stripe-Signature/.test(executable);
|
|
14893
|
+
}
|
|
14894
|
+
function hasEnabledRowLevelSecurity(source) {
|
|
14895
|
+
return /enable\s+row\s+level\s+security/i.test(stripSqlComments(source));
|
|
14896
|
+
}
|
|
14897
|
+
function hasEnvAssignment(source, name) {
|
|
14898
|
+
const pattern = new RegExp(`^\\s*${name}\\s*=`, "m");
|
|
14899
|
+
return pattern.test(source);
|
|
14900
|
+
}
|
|
14901
|
+
function gap(input) {
|
|
14902
|
+
return {
|
|
14903
|
+
...input,
|
|
14904
|
+
toolSuggestions: [],
|
|
14905
|
+
mcpSuggestion: null,
|
|
14906
|
+
affectedMapCategories: input.affectedMapCategories ?? []
|
|
14907
|
+
};
|
|
14908
|
+
}
|
|
14909
|
+
function emptyMissionGraph() {
|
|
14910
|
+
return {
|
|
14911
|
+
areas: [],
|
|
14912
|
+
byArea: {},
|
|
14913
|
+
byProvider: {},
|
|
14914
|
+
repositoryEvidence: {
|
|
14915
|
+
env: [],
|
|
14916
|
+
security: []
|
|
14917
|
+
}
|
|
14918
|
+
};
|
|
14919
|
+
}
|
|
14920
|
+
async function scanDemoFixture(root) {
|
|
14921
|
+
const [packageJson, stripeWebhook, migration, envExample] = await Promise.all([
|
|
14922
|
+
readKnownFile(root, "package.json"),
|
|
14923
|
+
readKnownFile(root, "app/api/stripe/webhook/route.ts"),
|
|
14924
|
+
readKnownFile(root, "supabase/migrations/0001_init.sql"),
|
|
14925
|
+
readKnownFile(root, ".env.example")
|
|
14926
|
+
]);
|
|
14927
|
+
const dependencies = dependencyNames(packageJson);
|
|
14928
|
+
const hasNext = dependencies.has("next");
|
|
14929
|
+
const hasSupabase = dependencies.has("@supabase/supabase-js");
|
|
14930
|
+
const hasStripe = dependencies.has("stripe");
|
|
14931
|
+
const selectedProviders = {};
|
|
14932
|
+
if (hasNext) selectedProviders.deployment = "vercel";
|
|
14933
|
+
if (hasSupabase) {
|
|
14934
|
+
selectedProviders.database = "supabase";
|
|
14935
|
+
selectedProviders.auth = "supabase";
|
|
14936
|
+
}
|
|
14937
|
+
if (hasStripe) selectedProviders.payments = "stripe";
|
|
14938
|
+
const gaps = [];
|
|
14939
|
+
if (hasStripe && !hasStripeWebhookSignatureVerification(stripeWebhook)) {
|
|
14940
|
+
gaps.push(
|
|
14941
|
+
gap({
|
|
14942
|
+
id: "stripe_webhook_signature_missing",
|
|
14943
|
+
category: "SECURITY & AUTH",
|
|
14944
|
+
severity: "critical",
|
|
14945
|
+
title: "Verify Stripe webhook signatures",
|
|
14946
|
+
detail: "The demo Stripe webhook handler does not verify the Stripe-Signature header with stripe.webhooks.constructEvent.",
|
|
14947
|
+
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.",
|
|
14948
|
+
primaryMapCategory: "payments",
|
|
14949
|
+
affectedMapCategories: ["security"]
|
|
14950
|
+
})
|
|
14951
|
+
);
|
|
14952
|
+
}
|
|
14953
|
+
if (hasSupabase && !hasEnabledRowLevelSecurity(migration)) {
|
|
14954
|
+
gaps.push(
|
|
14955
|
+
gap({
|
|
14956
|
+
id: "rls_disabled",
|
|
14957
|
+
category: "DATABASE & DATA",
|
|
14958
|
+
severity: "critical",
|
|
14959
|
+
title: "Enable Supabase row level security",
|
|
14960
|
+
detail: "The demo migration creates application data without enabling row level security.",
|
|
14961
|
+
copyPrompt: "Add alter table statements that enable row level security and add least-privilege policies for the Supabase tables in the demo migration.",
|
|
14962
|
+
primaryMapCategory: "database",
|
|
14963
|
+
affectedMapCategories: ["auth", "security"]
|
|
14964
|
+
})
|
|
14965
|
+
);
|
|
14966
|
+
}
|
|
14967
|
+
if (hasStripe && !hasEnvAssignment(envExample, "STRIPE_WEBHOOK_SECRET")) {
|
|
14968
|
+
gaps.push(
|
|
14969
|
+
gap({
|
|
14970
|
+
id: "missing_prod_env_stripe_webhook_secret",
|
|
14971
|
+
category: "DEPLOYMENT",
|
|
14972
|
+
severity: "warning",
|
|
14973
|
+
title: "Document STRIPE_WEBHOOK_SECRET",
|
|
14974
|
+
detail: "The demo .env.example does not include a STRIPE_WEBHOOK_SECRET assignment.",
|
|
14975
|
+
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.",
|
|
14976
|
+
primaryMapCategory: "deployment",
|
|
14977
|
+
affectedMapCategories: ["payments"]
|
|
14978
|
+
})
|
|
14979
|
+
);
|
|
14980
|
+
}
|
|
14981
|
+
const hasCriticalGaps = gaps.some((item3) => item3.severity === "critical");
|
|
14982
|
+
return {
|
|
14983
|
+
version: 1,
|
|
14984
|
+
scannedAt: DEMO_SCANNED_AT,
|
|
14985
|
+
workspacePath: root,
|
|
14986
|
+
score: hasCriticalGaps ? 55 : 90,
|
|
14987
|
+
scoreLabel: hasCriticalGaps ? "Needs work" : "Looks solid",
|
|
14988
|
+
summary: `Demo scan: ${gaps.length} launch gap(s) detected by local rules.`,
|
|
14989
|
+
archetype: hasNext && hasSupabase && hasStripe ? "nextjs-supabase-stripe" : "demo-saas",
|
|
14990
|
+
gaps,
|
|
14991
|
+
missionGraph: emptyMissionGraph(),
|
|
14992
|
+
stackWiring: { items: [], byKey: {} },
|
|
14993
|
+
providerRegistry: {
|
|
14994
|
+
version: 1,
|
|
14995
|
+
source: "bundled",
|
|
14996
|
+
generatedAt: DEMO_SCANNED_AT,
|
|
14997
|
+
staleAfterDays: 30,
|
|
14998
|
+
status: "fresh",
|
|
14999
|
+
providers: []
|
|
15000
|
+
},
|
|
15001
|
+
verificationSummary: { byArea: {} },
|
|
15002
|
+
productionCorePercent: hasCriticalGaps ? 40 : 95,
|
|
15003
|
+
selectedProviders
|
|
15004
|
+
};
|
|
15005
|
+
}
|
|
15006
|
+
|
|
15007
|
+
// src/demo/runDemo.ts
|
|
15008
|
+
function bundledFixtureRoot() {
|
|
15009
|
+
const candidates = [
|
|
15010
|
+
import_node_path27.default.join(__dirname, "..", "fixtures", "demo-saas"),
|
|
15011
|
+
import_node_path27.default.join(__dirname, "..", "..", "fixtures", "demo-saas")
|
|
15012
|
+
];
|
|
15013
|
+
return candidates.find((candidate) => (0, import_node_fs13.existsSync)(import_node_path27.default.join(candidate, "package.json"))) ?? candidates[0];
|
|
15014
|
+
}
|
|
15015
|
+
async function runDemo(options) {
|
|
15016
|
+
const label2 = options.label ?? "demo";
|
|
15017
|
+
const connectedTools = detectConnectedTools();
|
|
15018
|
+
const fixtureRoot = options.fixtureRoot ?? bundledFixtureRoot();
|
|
15019
|
+
const scannedArtifact = await scanDemoFixture(fixtureRoot);
|
|
15020
|
+
const artifact = {
|
|
15021
|
+
...scannedArtifact,
|
|
15022
|
+
workspacePath: options.cwd
|
|
15023
|
+
};
|
|
15024
|
+
await writeScanArtifacts({
|
|
15025
|
+
artifact,
|
|
15026
|
+
cwd: options.cwd,
|
|
15027
|
+
connectedTools,
|
|
15028
|
+
prpMode: "demo"
|
|
15029
|
+
});
|
|
15030
|
+
if (options.agentMode) {
|
|
15031
|
+
console.log(
|
|
15032
|
+
label2 === "demo" ? "VibeRaven demo mode - no login, no API spend. Provider verification is simulated." : `VibeRaven ${label2 === "try" ? "Try" : "showcase run"} - no login, no API spend. Provider verification is simulated.`
|
|
15033
|
+
);
|
|
15034
|
+
const prp = generateProductionProtocol(artifact, { mode: "demo", connectedTools });
|
|
15035
|
+
const mode = currentRenderMode("demo");
|
|
15036
|
+
printOperatorBlock(prp, buildTaskList(artifact), mode);
|
|
15037
|
+
const celebration = renderClearCelebration(prp, mode);
|
|
15038
|
+
if (celebration) {
|
|
15039
|
+
console.log(celebration);
|
|
15040
|
+
}
|
|
15041
|
+
} else {
|
|
15042
|
+
console.log(
|
|
15043
|
+
label2 === "demo" ? "VibeRaven demo complete. See .viberaven/prp.json" : `VibeRaven ${label2 === "try" ? "Try" : "showcase run"} complete. See .viberaven/prp.json`
|
|
15044
|
+
);
|
|
15045
|
+
}
|
|
15046
|
+
return 0;
|
|
15047
|
+
}
|
|
15048
|
+
|
|
14330
15049
|
// src/cli.ts
|
|
14331
15050
|
function printHelp() {
|
|
14332
15051
|
console.log(`viberaven ${VERSION} \u2014 launch readiness for AI-built apps
|
|
@@ -14356,6 +15075,18 @@ Usage:
|
|
|
14356
15075
|
viberaven --agent-mode [--json|--jsonl] [path]
|
|
14357
15076
|
Agent-first scan; writes tasklist, gate-result, context-map, and per-gap JSON
|
|
14358
15077
|
|
|
15078
|
+
viberaven --demo [path]
|
|
15079
|
+
No-login demo scan over the bundled fixture; writes demo artifacts locally
|
|
15080
|
+
|
|
15081
|
+
viberaven --agent-mode --demo [path]
|
|
15082
|
+
Demo scan with operator output; no login, no managed API spend
|
|
15083
|
+
|
|
15084
|
+
viberaven try [path]
|
|
15085
|
+
Free no-login local try run over a bundled fixture; no OpenAI key or managed API spend
|
|
15086
|
+
|
|
15087
|
+
viberaven --showcase --agent-mode [path]
|
|
15088
|
+
Alias for the no-login local try run
|
|
15089
|
+
|
|
14359
15090
|
viberaven --strict[=warning] [path]
|
|
14360
15091
|
Fail when production gate is not clear; warning mode also fails on warnings
|
|
14361
15092
|
|
|
@@ -14394,6 +15125,9 @@ Usage:
|
|
|
14394
15125
|
viberaven audit --vercel-supabase [--json] [path]
|
|
14395
15126
|
Local Vercel/Supabase repo evidence audit (RLS, pooler, secrets)
|
|
14396
15127
|
|
|
15128
|
+
viberaven badge [--svg|--card] [path]
|
|
15129
|
+
Print a README badge, terminal card, or write .viberaven/badge.svg from .viberaven/prp.json
|
|
15130
|
+
|
|
14397
15131
|
|
|
14398
15132
|
|
|
14399
15133
|
Agent workflow (Claude Code / Codex):
|
|
@@ -14453,16 +15187,62 @@ function parseArgs(argv) {
|
|
|
14453
15187
|
continue;
|
|
14454
15188
|
}
|
|
14455
15189
|
if (!command) {
|
|
14456
|
-
|
|
15190
|
+
if (isRootModePositional(flags, arg)) {
|
|
15191
|
+
positional.push(arg);
|
|
15192
|
+
} else {
|
|
15193
|
+
command = arg;
|
|
15194
|
+
}
|
|
14457
15195
|
} else {
|
|
14458
15196
|
positional.push(arg);
|
|
14459
15197
|
}
|
|
14460
15198
|
}
|
|
14461
15199
|
return { command, flags, positional };
|
|
14462
15200
|
}
|
|
15201
|
+
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
15202
|
+
"audit",
|
|
15203
|
+
"badge",
|
|
15204
|
+
"connect",
|
|
15205
|
+
"doctor",
|
|
15206
|
+
"guide",
|
|
15207
|
+
"init",
|
|
15208
|
+
"interactive",
|
|
15209
|
+
"login",
|
|
15210
|
+
"logout",
|
|
15211
|
+
"next",
|
|
15212
|
+
"open",
|
|
15213
|
+
"prompt",
|
|
15214
|
+
"provider-verify",
|
|
15215
|
+
"report",
|
|
15216
|
+
"scan",
|
|
15217
|
+
"stack",
|
|
15218
|
+
"status",
|
|
15219
|
+
"tui",
|
|
15220
|
+
"try",
|
|
15221
|
+
"validate-npm-package",
|
|
15222
|
+
"version",
|
|
15223
|
+
"watch"
|
|
15224
|
+
]);
|
|
15225
|
+
function isRootModePositional(flags, token) {
|
|
15226
|
+
if (KNOWN_COMMANDS.has(token)) {
|
|
15227
|
+
return false;
|
|
15228
|
+
}
|
|
15229
|
+
return [
|
|
15230
|
+
"agent-mode",
|
|
15231
|
+
"demo",
|
|
15232
|
+
"showcase",
|
|
15233
|
+
"json",
|
|
15234
|
+
"jsonl",
|
|
15235
|
+
"strict",
|
|
15236
|
+
"condense",
|
|
15237
|
+
"verify",
|
|
15238
|
+
"heal"
|
|
15239
|
+
].some((key) => flags[key] !== void 0);
|
|
15240
|
+
}
|
|
14463
15241
|
function isBooleanFlag(command, key) {
|
|
14464
15242
|
if ([
|
|
14465
15243
|
"agent-mode",
|
|
15244
|
+
"demo",
|
|
15245
|
+
"showcase",
|
|
14466
15246
|
"json",
|
|
14467
15247
|
"jsonl",
|
|
14468
15248
|
"condense",
|
|
@@ -14480,6 +15260,8 @@ function isBooleanFlag(command, key) {
|
|
|
14480
15260
|
if (key === "open" && (command === "" || command === "scan" || command === "report")) return true;
|
|
14481
15261
|
if (key === "verify" && command === "") return true;
|
|
14482
15262
|
if (key === "vercel-supabase" && command === "audit") return true;
|
|
15263
|
+
if (key === "svg" && command === "badge") return true;
|
|
15264
|
+
if (key === "card" && command === "badge") return true;
|
|
14483
15265
|
if (key === "json" && command === "validate-npm-package") return true;
|
|
14484
15266
|
if (key === "dry-run" && command === "init") return true;
|
|
14485
15267
|
if (key === "agents" && command === "doctor") return true;
|
|
@@ -14499,7 +15281,7 @@ async function guardEarlyVerifyScan(input) {
|
|
|
14499
15281
|
if (!verifyLike) {
|
|
14500
15282
|
return void 0;
|
|
14501
15283
|
}
|
|
14502
|
-
const workspacePath = input.positional[0] ? (0,
|
|
15284
|
+
const workspacePath = input.positional[0] ? (0, import_node_path28.join)(process.cwd(), input.positional[0]) : await resolveWorkspaceRoot(process.cwd());
|
|
14503
15285
|
const loopState = await loadLoopState(workspacePath);
|
|
14504
15286
|
if (loopState.batchApplied <= 0) {
|
|
14505
15287
|
return void 0;
|
|
@@ -14523,7 +15305,19 @@ async function guardEarlyVerifyScan(input) {
|
|
|
14523
15305
|
return void 0;
|
|
14524
15306
|
}
|
|
14525
15307
|
const nextTask = remainingRepoCodeTasks[0];
|
|
15308
|
+
const progress = renderLoopProgress(
|
|
15309
|
+
{
|
|
15310
|
+
applied: loopState.batchApplied,
|
|
15311
|
+
total: batchSize,
|
|
15312
|
+
label: "Healing repo gaps",
|
|
15313
|
+
silentInPlain: true
|
|
15314
|
+
},
|
|
15315
|
+
currentRenderMode("human-command")
|
|
15316
|
+
);
|
|
14526
15317
|
console.error("SCAN_DEFERRED: Local heal batch is not full yet, so VibeRaven is protecting scan quota.");
|
|
15318
|
+
if (progress) {
|
|
15319
|
+
console.error(progress);
|
|
15320
|
+
}
|
|
14527
15321
|
console.error(`Batch progress: ${loopState.batchApplied}/${batchSize} local heals applied since the last scan.`);
|
|
14528
15322
|
console.error(`Next local heal: npx -y viberaven --heal --apply --gap ${nextTask.gapId} --yes`);
|
|
14529
15323
|
console.error("Run verify again after the batch is full, or add --force-scan if the user explicitly wants to spend a scan now.");
|
|
@@ -14538,6 +15332,36 @@ function resolveDefaultEntrypointMode(options) {
|
|
|
14538
15332
|
function formatScanJsonStdout(artifact) {
|
|
14539
15333
|
return JSON.stringify(sanitizeArtifactForDisk(artifact), null, 2);
|
|
14540
15334
|
}
|
|
15335
|
+
function providerFromConnectedToolKey(tool) {
|
|
15336
|
+
if (!tool.endsWith("Mcp")) return void 0;
|
|
15337
|
+
return tool.slice(0, -3).toLowerCase();
|
|
15338
|
+
}
|
|
15339
|
+
function normalizeProviderForConnectedToolHint(provider2) {
|
|
15340
|
+
const lower = provider2.trim().toLowerCase();
|
|
15341
|
+
const tokens = lower.split(/[^a-z0-9]+/).filter(Boolean);
|
|
15342
|
+
for (const knownProvider of ["supabase", "stripe", "vercel", "github"]) {
|
|
15343
|
+
if (lower === knownProvider || tokens.includes(knownProvider)) {
|
|
15344
|
+
return knownProvider;
|
|
15345
|
+
}
|
|
15346
|
+
}
|
|
15347
|
+
return lower;
|
|
15348
|
+
}
|
|
15349
|
+
function printMissingConnectedToolHints(prp) {
|
|
15350
|
+
const relevantProviders = new Set([
|
|
15351
|
+
...prp.detectedStack.auth,
|
|
15352
|
+
...prp.detectedStack.database,
|
|
15353
|
+
...prp.detectedStack.deployment,
|
|
15354
|
+
...prp.detectedStack.payments
|
|
15355
|
+
].map(normalizeProviderForConnectedToolHint));
|
|
15356
|
+
for (const [tool, state] of Object.entries(prp.connectedTools)) {
|
|
15357
|
+
const provider2 = providerFromConnectedToolKey(tool);
|
|
15358
|
+
if (!provider2 || state !== "missing" || !relevantProviders.has(provider2)) {
|
|
15359
|
+
continue;
|
|
15360
|
+
}
|
|
15361
|
+
console.log(`${provider2} MCP is not connected. For stronger verification, add it:`);
|
|
15362
|
+
console.log(` ${installHintFor(provider2)}`);
|
|
15363
|
+
}
|
|
15364
|
+
}
|
|
14541
15365
|
async function cmdLogin(flags) {
|
|
14542
15366
|
const apiBaseUrl = resolveApiBaseUrl(typeof flags["api-url"] === "string" ? flags["api-url"] : void 0);
|
|
14543
15367
|
await runDeviceLogin(apiBaseUrl);
|
|
@@ -14575,7 +15399,7 @@ async function cmdStatus(flags, positional) {
|
|
|
14575
15399
|
console.log("Not signed in. Run: viberaven login");
|
|
14576
15400
|
return 1;
|
|
14577
15401
|
}
|
|
14578
|
-
const startDir = positional[0] ? (0,
|
|
15402
|
+
const startDir = positional[0] ? (0, import_node_path28.join)(process.cwd(), positional[0]) : process.cwd();
|
|
14579
15403
|
let artifact;
|
|
14580
15404
|
try {
|
|
14581
15405
|
artifact = await loadLastArtifact(startDir);
|
|
@@ -14729,7 +15553,7 @@ async function cmdWatch(flags) {
|
|
|
14729
15553
|
}
|
|
14730
15554
|
}
|
|
14731
15555
|
async function runScanCommand(flags, positional, options) {
|
|
14732
|
-
const workspacePath = positional[0] ? (0,
|
|
15556
|
+
const workspacePath = positional[0] ? (0, import_node_path28.join)(process.cwd(), positional[0]) : await resolveWorkspaceRoot(process.cwd());
|
|
14733
15557
|
const apiBaseUrl = resolveApiBaseUrl(typeof flags["api-url"] === "string" ? flags["api-url"] : void 0);
|
|
14734
15558
|
let accessToken;
|
|
14735
15559
|
try {
|
|
@@ -14775,17 +15599,14 @@ async function runScanCommand(flags, positional, options) {
|
|
|
14775
15599
|
return { exitCode: 1 };
|
|
14776
15600
|
}
|
|
14777
15601
|
const artifact = await enrichArtifactWithAccount(result.artifact, apiBaseUrl, accessToken);
|
|
14778
|
-
const
|
|
15602
|
+
const connectedTools = detectConnectedTools();
|
|
15603
|
+
const paths = await writeScanArtifacts({ artifact, cwd: workspacePath, connectedTools });
|
|
14779
15604
|
if (flags.json && !options?.deferMachineOutput) {
|
|
14780
15605
|
console.log(formatScanJsonStdout(artifact));
|
|
14781
15606
|
return { exitCode: 0, artifacts: paths };
|
|
14782
15607
|
}
|
|
14783
15608
|
if (!options?.deferMachineOutput) {
|
|
14784
15609
|
printScanSummary(artifact, paths);
|
|
14785
|
-
if (flags["agent-mode"]) {
|
|
14786
|
-
const prp = JSON.parse(await (0, import_promises19.readFile)(paths.prpPath, "utf8"));
|
|
14787
|
-
console.log(renderProductionProtocolSummary(prp));
|
|
14788
|
-
}
|
|
14789
15610
|
}
|
|
14790
15611
|
if (artifact.usage && !options?.deferMachineOutput) {
|
|
14791
15612
|
console.log(formatUsageLine(artifact.usage));
|
|
@@ -14795,6 +15616,14 @@ async function runScanCommand(flags, positional, options) {
|
|
|
14795
15616
|
const openGapCount = artifact.gaps.length;
|
|
14796
15617
|
const updatedState = resetBatch(loopState, openGapCount);
|
|
14797
15618
|
const tasks = buildTaskList(artifact);
|
|
15619
|
+
const prp = generateProductionProtocol(artifact, { connectedTools });
|
|
15620
|
+
const mode = currentRenderMode("agent-mode");
|
|
15621
|
+
printOperatorBlock(prp, tasks, mode);
|
|
15622
|
+
const celebration = renderClearCelebration(prp, mode);
|
|
15623
|
+
if (celebration) {
|
|
15624
|
+
console.log(celebration);
|
|
15625
|
+
}
|
|
15626
|
+
printMissingConnectedToolHints(prp);
|
|
14798
15627
|
const plan = artifact.plan ?? "free";
|
|
14799
15628
|
const block = buildNextActionBlock(tasks, updatedState, plan);
|
|
14800
15629
|
printNextActionBlock(block);
|
|
@@ -14811,7 +15640,7 @@ async function runScanCommand(flags, positional, options) {
|
|
|
14811
15640
|
return { exitCode: 0, artifacts: paths };
|
|
14812
15641
|
}
|
|
14813
15642
|
async function cmdReport(flags, positional) {
|
|
14814
|
-
const startDir = positional[0] ? (0,
|
|
15643
|
+
const startDir = positional[0] ? (0, import_node_path28.join)(process.cwd(), positional[0]) : process.cwd();
|
|
14815
15644
|
try {
|
|
14816
15645
|
const paths = await refreshReportFromDisk(startDir);
|
|
14817
15646
|
console.log(`Report refreshed: ${paths.reportPath}`);
|
|
@@ -14833,7 +15662,7 @@ async function cmdReport(flags, positional) {
|
|
|
14833
15662
|
}
|
|
14834
15663
|
}
|
|
14835
15664
|
async function cmdPrompt(flags, positional) {
|
|
14836
|
-
const startDir = positional[0] ? (0,
|
|
15665
|
+
const startDir = positional[0] ? (0, import_node_path28.join)(process.cwd(), positional[0]) : process.cwd();
|
|
14837
15666
|
let artifact;
|
|
14838
15667
|
try {
|
|
14839
15668
|
artifact = await loadLastArtifact(startDir);
|
|
@@ -14841,26 +15670,26 @@ async function cmdPrompt(flags, positional) {
|
|
|
14841
15670
|
console.error(error instanceof Error ? error.message : "No scan found. Run: viberaven scan");
|
|
14842
15671
|
return 1;
|
|
14843
15672
|
}
|
|
14844
|
-
const
|
|
15673
|
+
const gap2 = pickGap(artifact, {
|
|
14845
15674
|
gapId: typeof flags.gap === "string" ? flags.gap : void 0,
|
|
14846
15675
|
provider: typeof flags.provider === "string" ? flags.provider : void 0,
|
|
14847
15676
|
area: typeof flags.area === "string" ? flags.area : void 0
|
|
14848
15677
|
});
|
|
14849
|
-
if (!
|
|
15678
|
+
if (!gap2) {
|
|
14850
15679
|
console.error("No matching gap. Run `viberaven scan` or pass --gap <id>.");
|
|
14851
15680
|
return 1;
|
|
14852
15681
|
}
|
|
14853
15682
|
const skipCopy = flags["no-copy"] === true;
|
|
14854
15683
|
if (!skipCopy) {
|
|
14855
15684
|
try {
|
|
14856
|
-
await copyToClipboard(
|
|
14857
|
-
console.log(`Copied to clipboard: ${
|
|
15685
|
+
await copyToClipboard(gap2.copyPrompt);
|
|
15686
|
+
console.log(`Copied to clipboard: ${gap2.title}`);
|
|
14858
15687
|
return 0;
|
|
14859
15688
|
} catch (error) {
|
|
14860
15689
|
console.warn(error instanceof Error ? error.message : String(error));
|
|
14861
15690
|
}
|
|
14862
15691
|
}
|
|
14863
|
-
console.log(
|
|
15692
|
+
console.log(gap2.copyPrompt);
|
|
14864
15693
|
return 0;
|
|
14865
15694
|
}
|
|
14866
15695
|
var STACK_AREAS = /* @__PURE__ */ new Set(["database", "auth", "payments", "deployment", "monitoring", "security"]);
|
|
@@ -14931,7 +15760,7 @@ async function main() {
|
|
|
14931
15760
|
const wantsJsonl = hasFlag(flags, "jsonl");
|
|
14932
15761
|
const wantsStrict = hasFlag(flags, "strict");
|
|
14933
15762
|
if (flags.condense) {
|
|
14934
|
-
const cwd = positional[0] ? (0,
|
|
15763
|
+
const cwd = positional[0] ? (0, import_node_path28.join)(process.cwd(), positional[0]) : process.cwd();
|
|
14935
15764
|
const result = await runCondenseCommand({ cwd });
|
|
14936
15765
|
console.log(`VibeRaven context map refreshed: ${result.contextMapPath}`);
|
|
14937
15766
|
return 0;
|
|
@@ -14949,6 +15778,14 @@ async function main() {
|
|
|
14949
15778
|
console.log(JSON.stringify(result, null, 2));
|
|
14950
15779
|
return result.status.startsWith("refused") || result.status === "failed" ? 1 : 0;
|
|
14951
15780
|
}
|
|
15781
|
+
if (command === "try") {
|
|
15782
|
+
const cwd = positional[0] ? (0, import_node_path28.join)(process.cwd(), positional[0]) : process.cwd();
|
|
15783
|
+
return runDemo({ cwd, agentMode: true, label: "try" });
|
|
15784
|
+
}
|
|
15785
|
+
if (hasFlag(flags, "demo") || hasFlag(flags, "showcase")) {
|
|
15786
|
+
const cwd = positional[0] ? (0, import_node_path28.join)(process.cwd(), positional[0]) : process.cwd();
|
|
15787
|
+
return runDemo({ cwd, agentMode: isAgentMode, label: hasFlag(flags, "showcase") ? "showcase" : "demo" });
|
|
15788
|
+
}
|
|
14952
15789
|
if (!command && (isAgentMode || flags.verify === true || wantsJson || wantsJsonl || wantsStrict)) {
|
|
14953
15790
|
const guardedExitCode = await guardEarlyVerifyScan({ flags, positional, wantsStrict });
|
|
14954
15791
|
if (guardedExitCode !== void 0) {
|
|
@@ -14960,7 +15797,7 @@ async function main() {
|
|
|
14960
15797
|
console.error("VibeRaven could not produce machine output because scan artifacts were not written.");
|
|
14961
15798
|
return 3;
|
|
14962
15799
|
}
|
|
14963
|
-
const gateResult = scanResult.artifacts && (wantsJson || wantsJsonl || wantsStrict) ? JSON.parse(await (0,
|
|
15800
|
+
const gateResult = scanResult.artifacts && (wantsJson || wantsJsonl || wantsStrict) ? JSON.parse(await (0, import_promises21.readFile)(scanResult.artifacts.gateResultPath, "utf8")) : void 0;
|
|
14964
15801
|
const strictExitCode = wantsStrict && gateResult ? exitCodeForStrictGate(gateResult, { failOnWarnings: flags.strict === "warning" }) : scanResult.exitCode;
|
|
14965
15802
|
if (wantsJson && gateResult) {
|
|
14966
15803
|
process.stdout.write(renderGateResultJson(gateResult));
|
|
@@ -14995,7 +15832,7 @@ async function main() {
|
|
|
14995
15832
|
case "next":
|
|
14996
15833
|
return runNextCommand({
|
|
14997
15834
|
json: Boolean(flags.json),
|
|
14998
|
-
cwd: positional[0] ? (0,
|
|
15835
|
+
cwd: positional[0] ? (0, import_node_path28.join)(process.cwd(), positional[0]) : process.cwd()
|
|
14999
15836
|
});
|
|
15000
15837
|
case "guide": {
|
|
15001
15838
|
const provider2 = positional[0];
|
|
@@ -15033,7 +15870,7 @@ async function main() {
|
|
|
15033
15870
|
case "provider-verify":
|
|
15034
15871
|
return cmdProviderVerify(flags, positional);
|
|
15035
15872
|
case "init": {
|
|
15036
|
-
const cwd = positional[0] ? (0,
|
|
15873
|
+
const cwd = positional[0] ? (0, import_node_path28.join)(process.cwd(), positional[0]) : process.cwd();
|
|
15037
15874
|
const agents = typeof flags.agents === "string" ? flags.agents : void 0;
|
|
15038
15875
|
return runInitCommand({
|
|
15039
15876
|
cwd,
|
|
@@ -15047,7 +15884,7 @@ async function main() {
|
|
|
15047
15884
|
return 1;
|
|
15048
15885
|
}
|
|
15049
15886
|
return runDoctorAgentsCommand({
|
|
15050
|
-
cwd: positional[0] ? (0,
|
|
15887
|
+
cwd: positional[0] ? (0, import_node_path28.join)(process.cwd(), positional[0]) : process.cwd()
|
|
15051
15888
|
});
|
|
15052
15889
|
case "validate-npm-package":
|
|
15053
15890
|
return runValidateNpmPackageCommand({
|
|
@@ -15060,9 +15897,15 @@ async function main() {
|
|
|
15060
15897
|
return 1;
|
|
15061
15898
|
}
|
|
15062
15899
|
return runAuditCommand({
|
|
15063
|
-
cwd: positional[0] ? (0,
|
|
15900
|
+
cwd: positional[0] ? (0, import_node_path28.join)(process.cwd(), positional[0]) : process.cwd(),
|
|
15064
15901
|
json: Boolean(flags.json)
|
|
15065
15902
|
});
|
|
15903
|
+
case "badge":
|
|
15904
|
+
return runBadgeCommand({
|
|
15905
|
+
cwd: positional[0] ? (0, import_node_path28.join)(process.cwd(), positional[0]) : process.cwd(),
|
|
15906
|
+
svg: flags.svg === true,
|
|
15907
|
+
card: flags.card === true
|
|
15908
|
+
});
|
|
15066
15909
|
default:
|
|
15067
15910
|
console.error(`Unknown command: ${command}`);
|
|
15068
15911
|
printHelp();
|