vibe-splain 3.3.0 → 3.4.0

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.
@@ -2,5 +2,5 @@ import type { DossierViewModel, AnalysisStore } from '@vibe-splain/brain';
2
2
  import type { Renderer } from './Renderer.js';
3
3
  import type { Artifact } from '../ArtifactBundleWriter.js';
4
4
  export declare class ValidationRenderer implements Renderer {
5
- render(viewModel: DossierViewModel, _store: AnalysisStore): Artifact[];
5
+ render(viewModel: DossierViewModel, store: AnalysisStore): Artifact[];
6
6
  }
@@ -1,12 +1,13 @@
1
1
  export class ValidationRenderer {
2
- render(viewModel, _store) {
3
- if (!viewModel.map.validation)
2
+ render(viewModel, store) {
3
+ const report = store.validationReport || viewModel.map.validation;
4
+ if (!report)
4
5
  return [];
5
6
  return [
6
7
  {
7
8
  type: 'validation',
8
9
  path: 'validation_report.json',
9
- content: JSON.stringify(viewModel.map.validation, null, 2),
10
+ content: JSON.stringify(report, null, 2),
10
11
  }
11
12
  ];
12
13
  }
package/dist/index.js CHANGED
@@ -1043,7 +1043,17 @@ function analyzeAst(source, lang, tree) {
1043
1043
  for (const fn of fnNodes) {
1044
1044
  const bodyLOC = nodeLOC(fn);
1045
1045
  const decisions = countDecisions(fn);
1046
- scored.push({ node: fn, decisions, bodyLOC, score: decisions + bodyLOC });
1046
+ let score = decisions + bodyLOC;
1047
+ const text = fn.text;
1048
+ if (lang === "typescript" || lang === "tsx" || lang === "javascript") {
1049
+ if (/stripe|webhook|payload|signature|event/i.test(text) && /switch|case|if/i.test(text)) {
1050
+ score += 25;
1051
+ }
1052
+ if (text.includes("prisma") && (text.includes("create") || text.includes("update"))) {
1053
+ score += 10;
1054
+ }
1055
+ }
1056
+ scored.push({ node: fn, decisions, bodyLOC, score });
1047
1057
  if (bodyLOC > LONG_FN_LOC) {
1048
1058
  longFunctions++;
1049
1059
  smells.push({
@@ -1073,7 +1083,7 @@ function analyzeAst(source, lang, tree) {
1073
1083
  });
1074
1084
  }
1075
1085
  scored.sort((a, b) => b.score - a.score);
1076
- const hotSpans = scored.slice(0, 3).filter((s) => s.bodyLOC >= 4).map((s) => {
1086
+ const hotSpans = scored.slice(0, 3).filter((s) => s.bodyLOC >= 2).map((s) => {
1077
1087
  const rawExcerpt = source.split("\n").slice(s.node.startPosition.row, s.node.endPosition.row + 1).join("\n");
1078
1088
  const snippet = stripLeadingComments(rawExcerpt).slice(0, 2e3);
1079
1089
  return {
@@ -1517,9 +1527,9 @@ function inferSideEffectProfile(source, importSpecs, productDomain, frameworkRol
1517
1527
  }
1518
1528
  if (/createBooking|handleNewBooking|cancelBooking|rescheduleBooking|handleBooking|createRecurring/.test(source) || productDomain === "booking_creation" && /useMutation\b|\.mutate\b|\.mutateAsync\b/.test(source))
1519
1529
  effects.add("booking_mutation");
1520
- if (/stripe\.webhooks\.(constructEvent|constructEventAsync)|webhookSecret|validateWebhook|verifyWebhook|verifySignature/.test(source) || productDomain === "payments_webhooks" && frameworkRole === "pages_api_route")
1530
+ if (/stripe\.webhooks\.(constructEvent|constructEventAsync)|webhookSecret|validateWebhook|verifyWebhook|verifySignature|svix|signature|req\.body|req\.rawBody/.test(source) || productDomain === "payments_webhooks" && frameworkRole === "pages_api_route")
1521
1531
  effects.add("webhook_ingress");
1522
- if (importSpecs.some((s) => /stripe|paypal|btcpay|alby/.test(s.toLowerCase())) || /stripe\.|paymentIntent|createPaymentIntent|confirmPayment|createCharge/.test(source) || productDomain === "payments_webhooks" && effects.has("webhook_ingress"))
1532
+ if (importSpecs.some((s) => /stripe|paypal|btcpay|alby/.test(s.toLowerCase())) || /stripe\.|paymentIntent|createPaymentIntent|confirmPayment|createCharge/.test(source) || productDomain === "payments_webhooks" && (effects.has("webhook_ingress") || source.includes("webhook")))
1523
1533
  effects.add("payment_mutation");
1524
1534
  if (/signIn\b|signOut\b|createSession|destroySession|issueToken|refreshToken|getToken/.test(source)) {
1525
1535
  effects.add("auth_token_mutation");
@@ -1634,19 +1644,17 @@ function findRuntimeEntrypoints(relPath, importedByMap, persisted, maxDepth = 8)
1634
1644
  if (seen.has(current.path))
1635
1645
  continue;
1636
1646
  seen.add(current.path);
1637
- if (current.path !== relPath) {
1638
- const meta = persisted.get(current.path);
1639
- if (meta && ENTRYPOINT_ROLES.has(meta.frameworkRole)) {
1640
- results.push({
1641
- path: current.path,
1642
- frameworkRole: meta.frameworkRole,
1643
- productDomain: meta.productDomain,
1644
- distance: current.depth
1645
- });
1646
- if (results.length >= 8)
1647
- break;
1648
- continue;
1649
- }
1647
+ const meta = persisted.get(current.path);
1648
+ if (meta && ENTRYPOINT_ROLES.has(meta.frameworkRole)) {
1649
+ results.push({
1650
+ path: current.path,
1651
+ frameworkRole: meta.frameworkRole,
1652
+ productDomain: meta.productDomain,
1653
+ distance: current.depth
1654
+ });
1655
+ if (results.length >= 8)
1656
+ break;
1657
+ continue;
1650
1658
  }
1651
1659
  if (current.depth >= maxDepth)
1652
1660
  continue;
@@ -2824,17 +2832,19 @@ async function runScoring(projectRoot, cr, binding) {
2824
2832
  hotSpans: f.hotSpans,
2825
2833
  riskTypes: f.riskTypes,
2826
2834
  writeIntents: f.writeIntents,
2835
+ runtimeEntrypoints: f.runtimeEntrypoints,
2836
+ entrypointTraceStatus: f.entrypointTraceStatus,
2827
2837
  canonicalSeverity: severity,
2828
2838
  canonicalLoadBearing: f.isLoadBearing,
2829
2839
  // STRICT: fanIn >= 10
2830
2840
  isOperationallyCritical: f.isOperationallyCritical,
2831
- confidence
2841
+ confidence,
2842
+ source: f.source
2832
2843
  };
2833
2844
  applyCorrections(pf);
2834
2845
  persisted[f.rel] = pf;
2835
2846
  severityBreakdowns[f.rel] = `severity=${pf.canonicalSeverity} loadBearing=${pf.canonicalLoadBearing} effects=${pf.sideEffectProfile.join(",")} domain=${pf.productDomain}`;
2836
2847
  }
2837
- const store = { files: persisted };
2838
2848
  const deltaTargets = Object.values(persisted).filter((pf) => pf.isRealSource).sort((a, b) => b.gravity - a.gravity).map((pf) => ({
2839
2849
  path: pf.relativePath,
2840
2850
  gravity: Math.round(pf.gravity),
@@ -2843,7 +2853,9 @@ async function runScoring(projectRoot, cr, binding) {
2843
2853
  blastRadius: pf.importedBy,
2844
2854
  pillarHint: pf.pillarHint
2845
2855
  }));
2856
+ const store = { files: persisted };
2846
2857
  const validationReport = await buildValidationReport(store, deltaTargets, projectRoot, cr);
2858
+ store.validationReport = validationReport;
2847
2859
  for (const e of validationReport.errors) {
2848
2860
  console.error(`[vibe-splain] VALIDATION ERROR [${e.rule}] ${e.file}: ${e.detail}`);
2849
2861
  }
@@ -2884,7 +2896,7 @@ async function buildValidationReport(store, deltaTargets, projectRoot, cr) {
2884
2896
  });
2885
2897
  continue;
2886
2898
  }
2887
- if (pf.productDomain === "booking_creation" && classified?.entrypointTraceStatus === "no_runtime_entrypoint_found" && pf.importsUnresolved.length === 0) {
2899
+ if (pf.productDomain === "booking_creation" && classified?.entrypointTraceStatus === "no_runtime_entrypoint_found" && pf.importsUnresolved.length === 0 && !["app_route_layout", "app_loading_boundary", "app_error_boundary"].includes(pf.frameworkRole) && (pf.sideEffectProfile.includes("booking_mutation") || pf.source.includes("createBooking") || pf.source.includes("handleNewBooking"))) {
2888
2900
  errors.push({
2889
2901
  file: pf.relativePath,
2890
2902
  rule: "booking_creation_no_entrypoint_no_blockers",
@@ -2892,7 +2904,7 @@ async function buildValidationReport(store, deltaTargets, projectRoot, cr) {
2892
2904
  });
2893
2905
  continue;
2894
2906
  }
2895
- if (pf.canonicalSeverity >= 4 && pf.hotSpans.length === 0) {
2907
+ if (pf.canonicalSeverity >= 4 && pf.hotSpans.length === 0 && !pf.source.includes("export {") && pf.gravitySignals.loc > 5) {
2896
2908
  errors.push({
2897
2909
  file: pf.relativePath,
2898
2910
  rule: "high_severity_no_evidence",
@@ -2922,9 +2934,9 @@ async function buildValidationReport(store, deltaTargets, projectRoot, cr) {
2922
2934
  if (!pf.isRealSource)
2923
2935
  continue;
2924
2936
  const hasIntent = pf.writeIntents.includes("handle_payment_webhook");
2925
- const hasEffects = pf.sideEffectProfile.includes("webhook_ingress") || pf.sideEffectProfile.includes("payment_mutation");
2937
+ const hasWebhookIngress = pf.sideEffectProfile.includes("webhook_ingress");
2926
2938
  const pathMentionsPayment = PAYMENT_PROVIDER_PATH_TERMS.some((t) => rel.toLowerCase().includes(t));
2927
- if (!hasIntent && !(hasEffects && pathMentionsPayment))
2939
+ if (!hasIntent && !(hasWebhookIngress && pathMentionsPayment && pf.frameworkRole !== "component"))
2928
2940
  continue;
2929
2941
  const webhookChecks = [
2930
2942
  [
@@ -3023,7 +3035,8 @@ async function runPipeline(projectRoot) {
3023
3035
  errors: scoring.validationReport.summary.errorCount,
3024
3036
  warnings: scoring.validationReport.summary.warningCount,
3025
3037
  reportPath: ".vibe-splainer/validation_report.json"
3026
- }
3038
+ },
3039
+ fullValidationReport: scoring.validationReport
3027
3040
  };
3028
3041
  }
3029
3042
 
@@ -3605,14 +3618,15 @@ ${viewModel.map.brief}
3605
3618
 
3606
3619
  // dist/export/renderers/ValidationRenderer.js
3607
3620
  var ValidationRenderer = class {
3608
- render(viewModel, _store) {
3609
- if (!viewModel.map.validation)
3621
+ render(viewModel, store) {
3622
+ const report = store.validationReport || viewModel.map.validation;
3623
+ if (!report)
3610
3624
  return [];
3611
3625
  return [
3612
3626
  {
3613
3627
  type: "validation",
3614
3628
  path: "validation_report.json",
3615
- content: JSON.stringify(viewModel.map.validation, null, 2)
3629
+ content: JSON.stringify(report, null, 2)
3616
3630
  }
3617
3631
  ];
3618
3632
  }
@@ -4394,7 +4408,7 @@ async function handleScanProject(args, options = {}) {
4394
4408
  message: statusMsg,
4395
4409
  scanId,
4396
4410
  manifestPointer,
4397
- validation: {
4411
+ validation: result.fullValidationReport || {
4398
4412
  passed: validation.passed,
4399
4413
  errors: validation.errors,
4400
4414
  warnings: validation.warnings,
@@ -7960,7 +7974,7 @@ async function importBundleCommand(tarballPath, opts = {}) {
7960
7974
 
7961
7975
  // dist/index.js
7962
7976
  var program = new Command();
7963
- program.name("vibe-splain").description("Architectural dossier engine for vibe-coded projects").version("3.2.0");
7977
+ program.name("vibe-splain").description("Architectural dossier engine for vibe-coded projects").version("3.4.0");
7964
7978
  program.command("install").description("Patch coding agent MCP config files to register vibe-splain").action(installCommand);
7965
7979
  program.command("serve").description("Start the MCP server (called by the coding agent, not by you)").option("--format <format>", "Export format (html, markdown, etc.)").option("--budget <budget>", "Token budget for markdown").option("--scope <scope>", "Scope for export").action((options) => serveCommand(options));
7966
7980
  program.command("export [projectRoot]").description("Manually trigger bundle generation").option("--format <format>", "Export format (html, markdown, etc.)").option("--budget <budget>", "Token budget for markdown").option("--scope <scope>", "Scope for export").action(exportCommand);
@@ -67,7 +67,7 @@ export async function handleScanProject(args, options = {}) {
67
67
  message: statusMsg,
68
68
  scanId,
69
69
  manifestPointer,
70
- validation: {
70
+ validation: result.fullValidationReport || {
71
71
  passed: validation.passed,
72
72
  errors: validation.errors,
73
73
  warnings: validation.warnings,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-splain",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "Architectural mapping and behavioral call-chain engine. Built on a language-agnostic foundation with specialized optimization for TypeScript/JavaScript projects.",
5
5
  "type": "module",
6
6
  "license": "MIT",