vibe-splain 2.3.1 → 2.4.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.
Files changed (2) hide show
  1. package/dist/index.js +714 -59
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -106,6 +106,7 @@ async function writeGraph(projectRoot, graph) {
106
106
 
107
107
  // ../brain/dist/analysis.js
108
108
  import { join as join3 } from "path";
109
+ import { createHash } from "crypto";
109
110
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
110
111
  async function readAnalysis(projectRoot) {
111
112
  const p = join3(projectRoot, ".vibe-splainer", "analysis.json");
@@ -125,16 +126,437 @@ async function writeAnalysis(projectRoot, store) {
125
126
  const { rename } = await import("fs/promises");
126
127
  await rename(tmp, dest);
127
128
  }
128
- var LOAD_BEARING_FAN_IN_THRESHOLD = 10;
129
- async function writeDeltaTargets(projectRoot, store) {
130
- const targets = Object.values(store.files).filter((f) => f.isRealSource).sort((a, b) => b.gravity - a.gravity).map((f) => ({
131
- path: f.relativePath,
132
- gravity: f.gravity,
133
- isLoadBearing: f.gravitySignals.fanIn >= LOAD_BEARING_FAN_IN_THRESHOLD,
134
- blastRadius: f.importedBy,
135
- // community-{N} labels are internal graph IDs — not useful to Delta Engine
136
- pillarHint: f.pillarHint && !f.pillarHint.startsWith("community-") ? f.pillarHint : null
129
+ var ENTRYPOINT_ROLES = /* @__PURE__ */ new Set([
130
+ "app_route_page",
131
+ "app_route_handler",
132
+ "pages_route",
133
+ "pages_api_route",
134
+ "trpc_api_route"
135
+ ]);
136
+ function findRuntimeEntrypoints(relPath, files, maxDepth = 8) {
137
+ const results = [];
138
+ const seen = /* @__PURE__ */ new Set();
139
+ const queue = [{ path: relPath, depth: 0 }];
140
+ while (queue.length > 0) {
141
+ const current = queue.shift();
142
+ if (seen.has(current.path))
143
+ continue;
144
+ seen.add(current.path);
145
+ if (current.path !== relPath) {
146
+ const meta2 = files[current.path];
147
+ if (meta2 && ENTRYPOINT_ROLES.has(meta2.frameworkRole)) {
148
+ results.push({
149
+ path: current.path,
150
+ frameworkRole: meta2.frameworkRole,
151
+ productDomain: meta2.productDomain,
152
+ distance: current.depth
153
+ });
154
+ if (results.length >= 8)
155
+ break;
156
+ continue;
157
+ }
158
+ }
159
+ if (current.depth >= maxDepth)
160
+ continue;
161
+ const meta = files[current.path];
162
+ if (!meta)
163
+ continue;
164
+ for (const importer of meta.importedBy) {
165
+ if (!seen.has(importer)) {
166
+ queue.push({ path: importer, depth: current.depth + 1 });
167
+ }
168
+ }
169
+ }
170
+ const byPath = /* @__PURE__ */ new Map();
171
+ for (const r of results) {
172
+ const existing = byPath.get(r.path);
173
+ if (!existing || r.distance < existing.distance)
174
+ byPath.set(r.path, r);
175
+ }
176
+ return [...byPath.values()].sort((a, b) => a.distance - b.distance);
177
+ }
178
+ function deriveEntrypointTraceStatus(entrypoints, unresolved) {
179
+ if (entrypoints.length > 0 && unresolved.length === 0)
180
+ return "complete";
181
+ if (entrypoints.length > 0)
182
+ return "partial";
183
+ if (unresolved.length > 0)
184
+ return "blocked_by_alias_resolution";
185
+ return "no_runtime_entrypoint_found";
186
+ }
187
+ function computeLoadBearingScore(f, entrypoints) {
188
+ let score = 0;
189
+ if (f.gravity >= 85)
190
+ score += 2;
191
+ if (f.heat >= 60)
192
+ score += 1;
193
+ if (entrypoints.length >= 2)
194
+ score += 2;
195
+ if (f.importedBy.length >= 3)
196
+ score += 1;
197
+ if (f.sideEffectProfile.includes("database_write"))
198
+ score += 3;
199
+ if (f.sideEffectProfile.includes("booking_mutation"))
200
+ score += 3;
201
+ if (f.sideEffectProfile.includes("payment_mutation"))
202
+ score += 3;
203
+ if (f.sideEffectProfile.includes("auth_token_mutation"))
204
+ score += 3;
205
+ if (f.sideEffectProfile.includes("webhook_delivery"))
206
+ score += 2;
207
+ if (f.sideEffectProfile.includes("webhook_ingress"))
208
+ score += 2;
209
+ if (f.sideEffectProfile.includes("calendar_mutation"))
210
+ score += 2;
211
+ if (f.sideEffectProfile.includes("redirect"))
212
+ score += 1;
213
+ if (f.sideEffectProfile.includes("analytics_event"))
214
+ score += 1;
215
+ const highImpactDomains = [
216
+ "booking_creation",
217
+ "payments",
218
+ "auth_oauth",
219
+ "webhooks",
220
+ "payments_webhooks"
221
+ ];
222
+ if (highImpactDomains.includes(f.productDomain))
223
+ score += 2;
224
+ const maxSeverity = f.smells.length > 0 ? Math.max(...f.smells.map((s) => s.severity)) : 0;
225
+ if (maxSeverity === 5)
226
+ score += 3;
227
+ return score;
228
+ }
229
+ function computeSeverity(f, entrypoints) {
230
+ let score = 0;
231
+ if (f.sideEffectProfile.includes("database_write"))
232
+ score += 3;
233
+ if (f.sideEffectProfile.includes("booking_mutation"))
234
+ score += 4;
235
+ if (f.sideEffectProfile.includes("payment_mutation"))
236
+ score += 4;
237
+ if (f.sideEffectProfile.includes("auth_token_mutation"))
238
+ score += 4;
239
+ if (f.sideEffectProfile.includes("webhook_delivery"))
240
+ score += 3;
241
+ if (f.sideEffectProfile.includes("webhook_ingress"))
242
+ score += 3;
243
+ if (f.sideEffectProfile.includes("calendar_mutation"))
244
+ score += 3;
245
+ if (f.productDomain === "booking_creation")
246
+ score += 3;
247
+ if (f.productDomain === "payments" || f.productDomain === "payments_webhooks")
248
+ score += 3;
249
+ if (f.productDomain === "auth_oauth")
250
+ score += 3;
251
+ if (f.productDomain === "webhooks")
252
+ score += 2;
253
+ if (f.gravity >= 85)
254
+ score += 2;
255
+ if (f.heat >= 70)
256
+ score += 2;
257
+ if (f.heatSignals.maxNesting >= 4)
258
+ score += 1;
259
+ if (f.heatSignals.longFunctions >= 1)
260
+ score += 1;
261
+ if (f.heatSignals.swallowedCatches >= 1)
262
+ score += 1;
263
+ if (entrypoints.length >= 2)
264
+ score += 2;
265
+ if (score >= 10)
266
+ return 5;
267
+ if (score >= 7)
268
+ return 4;
269
+ if (score >= 4)
270
+ return 3;
271
+ if (score >= 2)
272
+ return 2;
273
+ return 1;
274
+ }
275
+ function inferRiskTypes(f) {
276
+ const types = [];
277
+ const kinds = new Set(f.smells.map((s) => s.kind));
278
+ if (f.gravitySignals.cyclomatic > 20)
279
+ types.push("state_machine");
280
+ if (kinds.has("god-file")) {
281
+ if (f.frameworkRole === "hook")
282
+ types.push("god_hook");
283
+ else
284
+ types.push("god_component");
285
+ }
286
+ if (f.sideEffectProfile.length > 3 && !f.sideEffectProfile.includes("none_detected")) {
287
+ types.push("side_effect_coupling");
288
+ }
289
+ if (f.productDomain === "forms" && f.gravitySignals.fanIn > 5 && f.gravitySignals.publicSurface > 8)
290
+ types.push("registry_bottleneck");
291
+ if (f.sideEffectProfile.some((s) => ["booking_mutation", "payment_mutation", "auth_token_mutation"].includes(s)) && f.gravitySignals.cyclomatic > 10)
292
+ types.push("mutation_orchestration");
293
+ if (ENTRYPOINT_ROLES.has(f.frameworkRole) && f.sideEffectProfile.includes("database_write"))
294
+ types.push("route_handler_write_path");
295
+ if (kinds.has("swallowed-catch"))
296
+ types.push("error_swallowing");
297
+ if (f.sideEffectProfile.includes("local_storage") || f.sideEffectProfile.includes("indexed_db"))
298
+ types.push("storage_persistence_risk");
299
+ if (types.length === 0)
300
+ types.push("complexity_hotspot");
301
+ return types;
302
+ }
303
+ function inferObservableOutputs(f) {
304
+ const outputs = [];
305
+ if (f.sideEffectProfile.includes("redirect"))
306
+ outputs.push("redirect_url");
307
+ if (ENTRYPOINT_ROLES.has(f.frameworkRole))
308
+ outputs.push("http_status");
309
+ if (f.frameworkRole === "app_route_handler" || f.frameworkRole === "pages_api_route") {
310
+ outputs.push("json_response_shape");
311
+ }
312
+ if (f.productDomain === "booking_creation" || f.productDomain === "booking_management") {
313
+ outputs.push("booking_uid");
314
+ }
315
+ if (f.productDomain === "payments" || f.productDomain === "payments_webhooks") {
316
+ outputs.push("payment_status");
317
+ }
318
+ if (f.productDomain === "auth_oauth") {
319
+ outputs.push("auth_token");
320
+ }
321
+ if (f.sideEffectProfile.includes("webhook_delivery") || f.sideEffectProfile.includes("webhook_ingress")) {
322
+ outputs.push("webhook_payload");
323
+ }
324
+ if (f.sideEffectProfile.includes("calendar_mutation")) {
325
+ outputs.push("calendar_event_id");
326
+ }
327
+ if (f.sideEffectProfile.includes("email_send")) {
328
+ outputs.push("email_payload");
329
+ }
330
+ if (f.sideEffectProfile.includes("analytics_event")) {
331
+ outputs.push("sdk_event_name");
332
+ }
333
+ if (f.frameworkRole === "hook" || f.frameworkRole === "store") {
334
+ outputs.push("ui_state_transition");
335
+ }
336
+ return [...new Set(outputs)];
337
+ }
338
+ function inferWriteIntents(f) {
339
+ const intents = [];
340
+ if (f.productDomain === "booking_creation") {
341
+ intents.push("create_booking");
342
+ if (f.relativePath.includes("reschedule") || f.relativePath.includes("Reschedule"))
343
+ intents.push("reschedule_booking");
344
+ if (f.relativePath.includes("recurring") || f.relativePath.includes("Recurring")) {
345
+ intents.push("create_recurring_booking");
346
+ }
347
+ }
348
+ if (f.productDomain === "booking_management") {
349
+ intents.push("cancel_booking");
350
+ }
351
+ if (f.productDomain === "event_type_configuration") {
352
+ intents.push("update_event_type");
353
+ }
354
+ if (f.productDomain === "availability") {
355
+ intents.push("update_availability");
356
+ }
357
+ if (f.productDomain === "payments") {
358
+ intents.push("create_payment");
359
+ }
360
+ if (f.productDomain === "payments_webhooks") {
361
+ intents.push("handle_payment_webhook");
362
+ }
363
+ if (f.productDomain === "auth_oauth") {
364
+ intents.push("issue_auth_token");
365
+ intents.push("refresh_auth_token");
366
+ }
367
+ if (f.sideEffectProfile.includes("webhook_delivery")) {
368
+ intents.push("send_webhook");
369
+ }
370
+ if (f.productDomain === "settings") {
371
+ intents.push("update_user_settings");
372
+ }
373
+ if (f.sideEffectProfile.includes("local_storage") || f.sideEffectProfile.includes("indexed_db")) {
374
+ intents.push("persist_local_state");
375
+ }
376
+ return intents.length > 0 ? intents : ["none_detected"];
377
+ }
378
+ function inferPatchRisk(f, score, riskTypes) {
379
+ if (score >= 12 || f.productDomain === "booking_creation" && riskTypes.includes("mutation_orchestration")) {
380
+ return {
381
+ level: "critical",
382
+ reason: `${f.productDomain} domain with ${riskTypes.join(", ")} \u2014 any patch risks breaking live booking, payment, or auth flows.`
383
+ };
384
+ }
385
+ if (score >= 8 || f.sideEffectProfile.includes("payment_mutation") || f.sideEffectProfile.includes("auth_token_mutation")) {
386
+ return {
387
+ level: "high",
388
+ reason: `${f.productDomain} writes to external state (${f.sideEffectProfile.filter((s) => ["payment_mutation", "auth_token_mutation", "database_write", "webhook_delivery"].includes(s)).join(", ") || "database"}). Changes require integration testing.`
389
+ };
390
+ }
391
+ if (score >= 5 || f.importedBy.length >= 5) {
392
+ return {
393
+ level: "medium",
394
+ reason: `Imported by ${f.importedBy.length} files. Interface changes will cascade.`
395
+ };
396
+ }
397
+ return {
398
+ level: "low",
399
+ reason: "Locally contained \u2014 limited blast radius."
400
+ };
401
+ }
402
+ function inferSafePatchStrategy(f, riskTypes) {
403
+ if (riskTypes.includes("mutation_orchestration")) {
404
+ return "Do not rewrite inline. Extract pure decision logic into a tested reducer or state machine first. Preserve all side-effect call sites (redirect URLs, SDK event names, response shapes) as invariants.";
405
+ }
406
+ if (riskTypes.includes("registry_bottleneck")) {
407
+ return "Add new entries without removing existing keys. Treat the registry map as append-only until all consumers are verified.";
408
+ }
409
+ if (riskTypes.includes("route_handler_write_path")) {
410
+ return "Add integration tests covering success and failure paths before modifying. Verify HTTP status codes and response shapes are preserved.";
411
+ }
412
+ if (riskTypes.includes("god_component") || riskTypes.includes("god_hook")) {
413
+ return "Extract sub-concerns into separate modules first. Only refactor the extraction points after tests confirm equivalence.";
414
+ }
415
+ if (f.sideEffectProfile.includes("database_write")) {
416
+ return "Wrap changes in a transaction or use a feature flag. Run against a staging database before production.";
417
+ }
418
+ return "Review importedBy before patching. Run affected integration tests.";
419
+ }
420
+ function inferDoNotTouch(f) {
421
+ const items = [];
422
+ if (f.sideEffectProfile.includes("payment_mutation"))
423
+ items.push("payment flow branch");
424
+ if (f.sideEffectProfile.includes("auth_token_mutation"))
425
+ items.push("token issuance / refresh branch");
426
+ if (f.sideEffectProfile.includes("webhook_delivery") || f.sideEffectProfile.includes("webhook_ingress")) {
427
+ items.push("webhook payload shape");
428
+ }
429
+ if (f.sideEffectProfile.includes("redirect"))
430
+ items.push("redirect URL strings");
431
+ if (f.sideEffectProfile.includes("analytics_event"))
432
+ items.push("SDK event names");
433
+ if (f.sideEffectProfile.includes("booking_mutation")) {
434
+ items.push("booking success response shape", "recurring booking branch");
435
+ }
436
+ if (f.productDomain === "auth_oauth")
437
+ items.push("OAuth callback URLs", "token scopes");
438
+ return items;
439
+ }
440
+ function inferTestProbes(f, writeIntents, observableOutputs) {
441
+ const probes = [];
442
+ if (writeIntents.includes("create_booking")) {
443
+ probes.push({
444
+ name: "standard booking success",
445
+ scenario: "create a standard booking and assert success redirect and booking uid",
446
+ expectedObservable: ["booking_uid", "redirect_url", "sdk_event_name"].filter((o) => observableOutputs.includes(o))
447
+ });
448
+ }
449
+ if (writeIntents.includes("reschedule_booking")) {
450
+ probes.push({
451
+ name: "reschedule booking",
452
+ scenario: "reschedule an existing booking and assert reschedule event path",
453
+ expectedObservable: ["booking_uid", "redirect_url"].filter((o) => observableOutputs.includes(o))
454
+ });
455
+ }
456
+ if (writeIntents.includes("create_recurring_booking")) {
457
+ probes.push({
458
+ name: "recurring booking",
459
+ scenario: "create recurring booking and assert recurring success behavior",
460
+ expectedObservable: ["booking_uid", "redirect_url"].filter((o) => observableOutputs.includes(o))
461
+ });
462
+ }
463
+ if (writeIntents.includes("handle_payment_webhook")) {
464
+ probes.push({
465
+ name: "payment webhook ingestion",
466
+ scenario: "send a valid payment webhook and assert booking/payment state updated",
467
+ expectedObservable: ["payment_status", "booking_uid", "http_status"].filter((o) => observableOutputs.includes(o))
468
+ });
469
+ }
470
+ if (writeIntents.includes("issue_auth_token")) {
471
+ probes.push({
472
+ name: "token issuance",
473
+ scenario: "complete OAuth flow and assert access token issued with correct scopes",
474
+ expectedObservable: ["auth_token", "http_status"].filter((o) => observableOutputs.includes(o))
475
+ });
476
+ }
477
+ return probes;
478
+ }
479
+ function buildRawEvidence(f) {
480
+ return f.hotSpans.map((span) => ({
481
+ file: f.relativePath,
482
+ startLine: span.startLine,
483
+ endLine: span.endLine,
484
+ rawSourceExcerpt: span.snippet,
485
+ evidenceHash: createHash("sha256").update(span.snippet).digest("hex").slice(0, 12)
137
486
  }));
487
+ }
488
+ function deriveConfidence(f) {
489
+ if (f.gravitySignals.fanIn >= 10 && f.gravity >= 40)
490
+ return "high";
491
+ if (f.gravitySignals.fanIn >= 5 || f.gravity >= 25)
492
+ return "medium";
493
+ return "low";
494
+ }
495
+ function validateTarget(target) {
496
+ const warn = (msg) => console.error(`[vibe-splain] WARN ${target.path}: ${msg}`);
497
+ const err = (msg) => console.error(`[vibe-splain] ERR ${target.path}: ${msg}`);
498
+ if (target.severity >= 4 && target.runtimeEntrypoints.length === 0) {
499
+ warn("high severity target has no runtime entrypoints \u2014 check alias resolution");
500
+ }
501
+ if (target.severity === 5 && !target.isLoadBearing) {
502
+ err("severity 5 target must be load bearing");
503
+ }
504
+ if (target.productDomain === "routing_infrastructure" && !target.path.includes("middleware") && !target.path.includes("router") && !target.path.includes("Navigation")) {
505
+ warn("possible over-classification as routing_infrastructure");
506
+ }
507
+ if ((target.path.includes("payment") || target.path.includes("stripe") || target.path.includes("paypal")) && !target.sideEffectProfile.includes("payment_mutation") && !target.sideEffectProfile.includes("webhook_ingress")) {
508
+ warn("payment file missing payment side effect classification");
509
+ }
510
+ if (target.rawEvidence.some((e) => e.rawSourceExcerpt.includes("// ..") || e.rawSourceExcerpt.includes("/* ..."))) {
511
+ err("raw evidence appears summarized or annotated");
512
+ }
513
+ }
514
+ async function writeDeltaTargets(projectRoot, store, _entrypoints = /* @__PURE__ */ new Set()) {
515
+ const targets = Object.values(store.files).filter((f) => f.isRealSource).sort((a, b) => b.gravity - a.gravity).map((f) => {
516
+ const runtimeEntrypoints = findRuntimeEntrypoints(f.relativePath, store.files);
517
+ const entrypointTraceStatus = deriveEntrypointTraceStatus(runtimeEntrypoints, f.importsUnresolved);
518
+ const loadBearingScore = computeLoadBearingScore(f, runtimeEntrypoints);
519
+ const severity = computeSeverity(f, runtimeEntrypoints);
520
+ const riskTypes = inferRiskTypes(f);
521
+ const observableOutputs = inferObservableOutputs(f);
522
+ const writeIntents = inferWriteIntents(f);
523
+ const patchRisk = inferPatchRisk(f, loadBearingScore, riskTypes);
524
+ const rawEvidence = buildRawEvidence(f);
525
+ const confidence = deriveConfidence(f);
526
+ const fileHashInput = f.hotSpans.map((h) => h.snippet).join("");
527
+ const fileHash = createHash("sha256").update(fileHashInput || f.relativePath).digest("hex").slice(0, 12);
528
+ const target = {
529
+ path: f.relativePath,
530
+ frameworkRole: f.frameworkRole,
531
+ productDomain: f.productDomain,
532
+ gravity: Math.round(f.gravity),
533
+ heat: Math.round(f.heat),
534
+ severity,
535
+ confidence,
536
+ isLoadBearing: loadBearingScore >= 5,
537
+ loadBearingScore,
538
+ riskTypes,
539
+ sideEffectProfile: f.sideEffectProfile,
540
+ blastRadius: f.importedBy,
541
+ runtimeEntrypoints,
542
+ entrypointTraceStatus,
543
+ blockedImports: f.importsUnresolved,
544
+ observableOutputs,
545
+ writeIntents,
546
+ patchRisk,
547
+ safePatchStrategy: inferSafePatchStrategy(f, riskTypes),
548
+ doNotTouch: inferDoNotTouch(f),
549
+ testProbes: inferTestProbes(f, writeIntents, observableOutputs),
550
+ rawEvidence,
551
+ analysisAnnotation: `${f.frameworkRole} in ${f.productDomain} domain. fanIn=${f.gravitySignals.fanIn} cyclomatic=${f.gravitySignals.cyclomatic} loc=${f.gravitySignals.loc}`,
552
+ hashes: {
553
+ fileHash,
554
+ evidenceHash: rawEvidence.map((e) => e.evidenceHash).join("-")
555
+ }
556
+ };
557
+ validateTarget(target);
558
+ return target;
559
+ });
138
560
  const dir = join3(projectRoot, ".vibe-splainer");
139
561
  await mkdir2(dir, { recursive: true });
140
562
  const dest = join3(dir, "delta_targets.json");
@@ -256,7 +678,14 @@ var DEMOTE_SEGMENTS = /* @__PURE__ */ new Set([
256
678
  "fixtures",
257
679
  "fixture",
258
680
  "__generated__",
259
- "__mocks__"
681
+ "__mocks__",
682
+ "playwright",
683
+ "e2e",
684
+ "__tests__",
685
+ "cypress",
686
+ "storybook",
687
+ "stories",
688
+ ".storybook"
260
689
  ]);
261
690
  var VENDOR_SEGMENTS = /* @__PURE__ */ new Set([
262
691
  "node_modules",
@@ -318,20 +747,6 @@ var PILLAR_KEYWORDS = {
318
747
  "paddle",
319
748
  "lemon-squeezy"
320
749
  ],
321
- "Routing": [
322
- "express",
323
- "fastify",
324
- "koa",
325
- "koa-router",
326
- "next/router",
327
- "next/navigation",
328
- "react-router",
329
- "@remix-run/",
330
- "hono",
331
- "express-rate-limit",
332
- "cors",
333
- "helmet"
334
- ],
335
750
  "Queue": [
336
751
  "bull",
337
752
  "bullmq",
@@ -382,7 +797,6 @@ var PILLAR_PATH_PATTERNS = {
382
797
  "Auth": /(?:^|[\/\\])(?:auth|login|signup|register|session|oauth)(?:[\/\\]|$)/i,
383
798
  "Database": /(?:^|[\/\\])(?:db|database|models?|schema|migrations?|seeds?)(?:[\/\\]|$)/i,
384
799
  "Payments": /(?:^|[\/\\])(?:pay|payments?|billing|checkout|subscriptions?|stripe)(?:[\/\\]|$)/i,
385
- "Routing": /(?:^|[\/\\])(?:routes?|router|middleware|api)(?:[\/\\]|$)/i,
386
800
  "Queue": /(?:^|[\/\\])(?:queues?|workers?|jobs?|consumers?|producers?)(?:[\/\\]|$)/i,
387
801
  "Storage": /(?:^|[\/\\])(?:storage|uploads?|s3|blobs?|media)(?:[\/\\]|$)/i,
388
802
  "Config": /(?:^|[\/\\])(?:config|env|settings?)(?:[\/\\]|$)/i,
@@ -423,6 +837,144 @@ function matchPillarByPath(relPath) {
423
837
  }
424
838
  return null;
425
839
  }
840
+ function inferFrameworkRole(relPath) {
841
+ const p = relPath.replace(/\\/g, "/");
842
+ if (/\.test\.|\.spec\./.test(p))
843
+ return "test";
844
+ if (/\.generated\.|__generated__|\.prisma\//.test(p))
845
+ return "generated";
846
+ if (/(?:^|\/)app\/.*\/page\.tsx?$/.test(p))
847
+ return "app_route_page";
848
+ if (/(?:^|\/)app\/.*\/layout\.tsx?$/.test(p))
849
+ return "app_route_layout";
850
+ if (/(?:^|\/)app\/.*\/route\.tsx?$/.test(p))
851
+ return "app_route_handler";
852
+ if (/(?:^|\/)app\/.*\/loading\.tsx?$/.test(p))
853
+ return "app_loading_boundary";
854
+ if (/(?:^|\/)app\/.*\/error\.tsx?$/.test(p))
855
+ return "app_error_boundary";
856
+ if (/(?:^|\/)pages\/api\/trpc\//.test(p))
857
+ return "trpc_api_route";
858
+ if (/(?:^|\/)pages\/api\//.test(p))
859
+ return "pages_api_route";
860
+ if (/(?:^|\/)pages\//.test(p))
861
+ return "pages_route";
862
+ if (/\/hooks\/|\/use[A-Z][^/]*\.(ts|tsx)$/.test(p))
863
+ return "hook";
864
+ if (/\/stores?\/|[Ss]tore\.(ts|tsx)$/.test(p))
865
+ return "store";
866
+ if (/[Pp]rovider\.(tsx?|jsx?)$|\/providers?\//.test(p))
867
+ return "provider";
868
+ if (/\.types\.ts$|\/types\.ts$|\/types\/[^/]+\.ts$/.test(p))
869
+ return "type_definition";
870
+ if (/\.(tsx|jsx)$/.test(p))
871
+ return "component";
872
+ if (/\.(ts|js|mjs|cjs)$/.test(p))
873
+ return "utility";
874
+ return "unknown";
875
+ }
876
+ function inferProductDomain(relPath, importSpecs) {
877
+ const p = relPath.toLowerCase().replace(/\\/g, "/");
878
+ if (/\.test\.|\.spec\.|__tests__|\/e2e\/|\/playwright\/|\/cypress\//.test(p)) {
879
+ return "test_infrastructure";
880
+ }
881
+ if (/\.generated\.|__generated__|\.prisma\//.test(p)) {
882
+ return "generated_noise";
883
+ }
884
+ if (p.includes("booking-audit") || p.includes("bookingaudit"))
885
+ return "booking_audit";
886
+ if (p.includes("bookeventform") || p.includes("availabletimes") || p.includes("availabletimeslots") || p.includes("usebookings") || p.includes("/pages/api/book/") || p.includes("/api/book/") || p.includes("/booking-successful/") || p.includes("/reschedule/") || p.includes("booking-page-wrapper") || p.includes("/book/") && !p.includes("booking-audit"))
887
+ return "booking_creation";
888
+ if (p.includes("modules/bookings") || p.includes("components/booking/actions") || p.includes("/bookings/[status]") || p.includes("/booking/[uid]") || p.includes("/bookings/"))
889
+ return "booking_management";
890
+ if (p.includes("event-types") || p.includes("eventtypes") || p.includes("eventavailabilitytab") || p.includes("eventadvancedtab") || p.includes("eventlimits") || p.includes("eventrecurring"))
891
+ return "event_type_configuration";
892
+ if (p.includes("availability") || p.includes("/schedules/") || p.includes("/slots/"))
893
+ return "availability";
894
+ if (p.includes("oauth") || p.includes("nextauth") || p.includes("/auth/oauth") || p.includes("/api/auth/") || importSpecs.some((s) => s.includes("arctic") || s.includes("@auth/core")))
895
+ return "auth_oauth";
896
+ if (p.includes("/auth/") || p.includes("signup") || p.includes("login") || p.includes("forgot-password") || p.includes("reset-password") || p.includes("two-factor") || p.includes("verify-email") || importSpecs.some((s) => s.includes("next-auth") || s.includes("@clerk/")))
897
+ return "auth";
898
+ if ((p.includes("stripe") || p.includes("paypal") || p.includes("btcpay") || p.includes("alby") || p.includes("payment")) && (p.includes("webhook") || p.includes("hook")))
899
+ return "payments_webhooks";
900
+ if (p.includes("stripe") || p.includes("paypal") || p.includes("btcpay") || p.includes("alby") || p.includes("payment") || p.includes("billing") || p.includes("checkout") || p.includes("subscription") || importSpecs.some((s) => s.includes("stripe") || s.includes("@stripe/")))
901
+ return "payments";
902
+ if (p.includes("webhook"))
903
+ return "webhooks";
904
+ if (p.includes("app-store") || p.includes("appstore") || p.includes("/apps/") || p.includes("modules/apps"))
905
+ return "apps_marketplace";
906
+ if (p.includes("calendar") || p.includes("selected-calendars") || importSpecs.some((s) => s.includes("googleapis") || s.includes("@google-cloud/")))
907
+ return "calendar_integrations";
908
+ if (p.includes("video") || p.includes("calvideo") || p.includes("daily.co"))
909
+ return "video";
910
+ if (p.includes("onboarding") || p.includes("getting-started"))
911
+ return "onboarding";
912
+ if (p.includes("/settings/") || p.includes("/settings."))
913
+ return "settings";
914
+ if (p.includes("/admin/") || p.includes("/admin."))
915
+ return "admin";
916
+ if (p.includes("data-table") || p.includes("datatable") || p.includes("datasegment") || p.includes("segment"))
917
+ return "data_table";
918
+ if (p.includes("shell/navigation") || p.includes("navigationitem") || p.includes("/shell/") || p.includes("sidebar") || p.includes("topnav") || p.includes("mainnav"))
919
+ return "shell_navigation";
920
+ if (p.includes("form-builder") || p.includes("formbuilder") || p.includes("/forms/") || p.includes("routingforms"))
921
+ return "forms";
922
+ if (p.includes("embed"))
923
+ return "embed";
924
+ if (p.includes("notification") || p.includes("/email/") || p.includes("/emails/") || importSpecs.some((s) => s.includes("nodemailer") || s.includes("resend") || s.includes("@sendgrid/")))
925
+ return "notifications";
926
+ if (p.includes("middleware") && !p.includes("pages/api/") || p.includes("/router.") || p.includes("routerconfig"))
927
+ return "routing_infrastructure";
928
+ return "unknown";
929
+ }
930
+ function inferSideEffectProfile(source, importSpecs) {
931
+ const effects = /* @__PURE__ */ new Set();
932
+ if (/router\.(push|replace|back)\(|redirect\(|notFound\(|permanentRedirect\(/.test(source)) {
933
+ effects.add("redirect");
934
+ }
935
+ if (/["']use server["']/.test(source))
936
+ effects.add("server_action");
937
+ if (/useMutation\b|\.mutate\b|\.mutateAsync\b/.test(source))
938
+ effects.add("trpc_mutation");
939
+ if (/sdkActionManager\.fire|telemetry\.|posthog\.|mixpanel\.|amplitude\.|ga\(/.test(source) || importSpecs.some((s) => /analytics|telemetry|posthog|mixpanel|amplitude/.test(s)))
940
+ effects.add("analytics_event");
941
+ if (/prisma\s*[.?]\s*\w+\s*[.?]\s*(create|update|upsert|delete|deleteMany|updateMany|createMany|transaction|executeRaw|queryRaw)\b/.test(source)) {
942
+ effects.add("database_write");
943
+ }
944
+ if (/prisma\s*[.?]\s*\w+\s*[.?]\s*(findMany|findUnique|findFirst|findFirstOrThrow|findUniqueOrThrow|count|aggregate|groupBy)\b/.test(source)) {
945
+ effects.add("database_read");
946
+ }
947
+ if (/createBooking|handleNewBooking|cancelBooking|rescheduleBooking|handleBooking|createRecurring/.test(source)) {
948
+ effects.add("booking_mutation");
949
+ }
950
+ if (importSpecs.some((s) => /stripe|paypal|btcpay|alby/.test(s.toLowerCase())) || /stripe\.|paymentIntent|createPaymentIntent|confirmPayment|createCharge/.test(source))
951
+ effects.add("payment_mutation");
952
+ if (/signIn\b|signOut\b|createSession|destroySession|issueToken|refreshToken|getToken/.test(source)) {
953
+ effects.add("auth_token_mutation");
954
+ }
955
+ if (/triggerWebhook|sendWebhook|webhook\.send\b/.test(source))
956
+ effects.add("webhook_delivery");
957
+ if (/webhookSecret|stripe\.webhooks\.constructEvent|validateWebhook|verifyWebhook/.test(source)) {
958
+ effects.add("webhook_ingress");
959
+ }
960
+ if (/sendEmail|sendMail\b|mailer\./.test(source) || importSpecs.some((s) => /nodemailer|resend|sendgrid|postmark|mailgun/.test(s)))
961
+ effects.add("email_send");
962
+ if (/createCalendarEvent|updateCalendarEvent|deleteCalendarEvent|calendar\.events\.(insert|update|delete|patch)/.test(source)) {
963
+ effects.add("calendar_mutation");
964
+ }
965
+ if (/revalidatePath\b|revalidateTag\b/.test(source))
966
+ effects.add("cache_revalidation");
967
+ if (/localStorage\.|sessionStorage\./.test(source))
968
+ effects.add("local_storage");
969
+ if (/indexedDB\b|new Dexie|idb\./.test(source))
970
+ effects.add("indexed_db");
971
+ if (/\bfetch\s*\(|axios\.(get|post|put|patch|delete)\b/.test(source)) {
972
+ effects.add("external_api_call");
973
+ }
974
+ if (effects.size === 0)
975
+ effects.add("none_detected");
976
+ return [...effects];
977
+ }
426
978
  async function collectFiles(dir, projectRoot, acc) {
427
979
  let entries;
428
980
  try {
@@ -522,18 +1074,46 @@ function extractImports(source, lang) {
522
1074
  }
523
1075
  return specs;
524
1076
  }
1077
+ var MONOREPO_ALIASES = [
1078
+ { prefix: "~/", replacement: "" },
1079
+ { prefix: "@components/", replacement: "components/" },
1080
+ { prefix: "@lib/", replacement: "lib/" },
1081
+ { prefix: "@server/", replacement: "server/" },
1082
+ { prefix: "@calcom/web/", replacement: "" },
1083
+ { prefix: "@calcom/features/", replacement: "../packages/features/" },
1084
+ { prefix: "@calcom/lib/", replacement: "../packages/lib/" },
1085
+ { prefix: "@calcom/prisma/", replacement: "../packages/prisma/" },
1086
+ { prefix: "@calcom/trpc/", replacement: "../packages/trpc/" },
1087
+ { prefix: "@calcom/ui/", replacement: "../packages/ui/" },
1088
+ { prefix: "@calcom/emails/", replacement: "../packages/emails/" }
1089
+ ];
1090
+ function resolveAlias(spec) {
1091
+ for (const { prefix, replacement } of MONOREPO_ALIASES) {
1092
+ if (spec.startsWith(prefix)) {
1093
+ return replacement + spec.slice(prefix.length);
1094
+ }
1095
+ }
1096
+ return null;
1097
+ }
525
1098
  var JS_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
526
1099
  function resolveImport(spec, fromAbs, lang, projectRoot, fileSet, basenameIndex) {
527
1100
  if (lang === "python") {
528
- return resolvePython(spec, fromAbs, projectRoot, fileSet);
1101
+ return { resolved: resolvePython(spec, fromAbs, projectRoot, fileSet), isAlias: false };
529
1102
  }
530
1103
  if (lang === "typescript" || lang === "tsx" || lang === "javascript") {
531
- if (!spec.startsWith("."))
532
- return null;
533
- const base = join4(dirname(fromAbs), spec);
534
- return tryJsCandidates(base, projectRoot, fileSet);
1104
+ if (spec.startsWith(".")) {
1105
+ const base = join4(dirname(fromAbs), spec);
1106
+ return { resolved: tryJsCandidates(base, projectRoot, fileSet), isAlias: false };
1107
+ }
1108
+ const aliasResolved = resolveAlias(spec);
1109
+ if (aliasResolved !== null) {
1110
+ const base = join4(projectRoot, aliasResolved);
1111
+ const resolved = tryJsCandidates(base, projectRoot, fileSet);
1112
+ return { resolved, isAlias: true };
1113
+ }
1114
+ return { resolved: null, isAlias: false };
535
1115
  }
536
- return resolveGeneric(spec, projectRoot, fileSet, basenameIndex);
1116
+ return { resolved: resolveGeneric(spec, projectRoot, fileSet, basenameIndex), isAlias: false };
537
1117
  }
538
1118
  function tryJsCandidates(base, projectRoot, fileSet) {
539
1119
  const candidates = [];
@@ -567,10 +1147,6 @@ function resolvePython(spec, fromAbs, projectRoot, fileSet) {
567
1147
  if (fileSet.has(rel))
568
1148
  return rel;
569
1149
  }
570
- if (!spec.startsWith(".")) {
571
- const last = spec.split(".").pop();
572
- void last;
573
- }
574
1150
  return null;
575
1151
  }
576
1152
  function resolveGeneric(spec, projectRoot, fileSet, basenameIndex) {
@@ -748,7 +1324,7 @@ function collectFunctionNodes(root) {
748
1324
  walk(root);
749
1325
  return out;
750
1326
  }
751
- function catchIsSwallowed(node, lang) {
1327
+ function catchIsSwallowed(node, _lang) {
752
1328
  const bodyText = node.text;
753
1329
  const inner = bodyText.replace(/^[^{:]*[{:]/, "");
754
1330
  const meaningful = inner.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("//") && !l.startsWith("#") && l !== "}" && l !== "pass");
@@ -1094,6 +1670,19 @@ async function detectStackAndEntrypoints(projectRoot, files) {
1094
1670
  if (base === "main.rs" || base === "lib.rs")
1095
1671
  entrypoints.add(r);
1096
1672
  }
1673
+ if (stack.has("Next.js")) {
1674
+ const appRouterNames = /* @__PURE__ */ new Set(["page", "layout", "route", "loading", "error", "not-found", "template", "default"]);
1675
+ for (const abs of files) {
1676
+ const r = rel(abs);
1677
+ const stem = basename(r, extname(r));
1678
+ const underApp = /(?:^|[/\\])app[/\\]/.test(r);
1679
+ const underPages = /(?:^|[/\\])pages[/\\]/.test(r);
1680
+ if (underApp && appRouterNames.has(stem))
1681
+ entrypoints.add(r);
1682
+ if (underPages && !stem.startsWith("_"))
1683
+ entrypoints.add(r);
1684
+ }
1685
+ }
1097
1686
  return { stack: [...stack], entrypoints };
1098
1687
  }
1099
1688
  var SMELL_WEIGHT = {
@@ -1147,24 +1736,42 @@ async function scanProject(projectRoot) {
1147
1736
  const ast = analyzeAst(source, lang, tree);
1148
1737
  const importSpecs = extractImports(source, lang);
1149
1738
  graph.nodes[rel] = { imports: importSpecs };
1150
- work.push({ abs: file, rel, lang, source, ast, importSpecs, pathDemote: pathDemoteReason(rel) });
1739
+ const frameworkRole = inferFrameworkRole(rel);
1740
+ const productDomain = inferProductDomain(rel, importSpecs);
1741
+ const sideEffectProfile = inferSideEffectProfile(source, importSpecs);
1742
+ work.push({
1743
+ abs: file,
1744
+ rel,
1745
+ lang,
1746
+ source,
1747
+ ast,
1748
+ importSpecs,
1749
+ pathDemote: pathDemoteReason(rel),
1750
+ frameworkRole,
1751
+ productDomain,
1752
+ sideEffectProfile
1753
+ });
1151
1754
  }
1152
1755
  const importedBy = /* @__PURE__ */ new Map();
1153
1756
  const importsResolved = /* @__PURE__ */ new Map();
1757
+ const importsUnresolved = /* @__PURE__ */ new Map();
1154
1758
  const fanOut = /* @__PURE__ */ new Map();
1155
1759
  for (const w of work) {
1156
1760
  importedBy.set(w.rel, /* @__PURE__ */ new Set());
1157
1761
  importsResolved.set(w.rel, /* @__PURE__ */ new Set());
1762
+ importsUnresolved.set(w.rel, /* @__PURE__ */ new Set());
1158
1763
  }
1159
1764
  for (const w of work) {
1160
1765
  const distinctModules = /* @__PURE__ */ new Set();
1161
1766
  for (const spec of w.importSpecs) {
1162
1767
  distinctModules.add(spec);
1163
- const target = resolveImport(spec, w.abs, w.lang, projectRoot, fileSet, basenameIndex);
1164
- if (target && target !== w.rel && importedBy.has(target)) {
1165
- importedBy.get(target).add(w.rel);
1166
- importsResolved.get(w.rel).add(target);
1167
- graph.edges.push({ from: w.rel, to: target });
1768
+ const { resolved, isAlias } = resolveImport(spec, w.abs, w.lang, projectRoot, fileSet, basenameIndex);
1769
+ if (resolved && resolved !== w.rel && importedBy.has(resolved)) {
1770
+ importedBy.get(resolved).add(w.rel);
1771
+ importsResolved.get(w.rel).add(resolved);
1772
+ graph.edges.push({ from: w.rel, to: resolved });
1773
+ } else if (resolved === null && isAlias) {
1774
+ importsUnresolved.get(w.rel).add(spec);
1168
1775
  }
1169
1776
  }
1170
1777
  fanOut.set(w.rel, distinctModules.size);
@@ -1259,7 +1866,10 @@ async function scanProject(projectRoot) {
1259
1866
  gravitySignals,
1260
1867
  heatSignals,
1261
1868
  smells: w.ast.smells,
1262
- pillarHint
1869
+ pillarHint,
1870
+ frameworkRole: w.frameworkRole,
1871
+ productDomain: w.productDomain,
1872
+ sideEffectProfile: w.sideEffectProfile
1263
1873
  };
1264
1874
  analyses.push(fa);
1265
1875
  persisted[w.rel] = {
@@ -1274,12 +1884,17 @@ async function scanProject(projectRoot) {
1274
1884
  smells: w.ast.smells,
1275
1885
  pillarHint,
1276
1886
  importedBy: [...importedBy.get(w.rel)].filter((src) => isRealSource.get(src)),
1277
- imports: [...importsResolved.get(w.rel)]
1887
+ imports: [...importsResolved.get(w.rel)],
1888
+ importsUnresolved: [...importsUnresolved.get(w.rel)],
1889
+ frameworkRole: w.frameworkRole,
1890
+ productDomain: w.productDomain,
1891
+ sideEffectProfile: w.sideEffectProfile,
1892
+ hotSpans: w.ast.hotSpans
1278
1893
  };
1279
1894
  }
1280
1895
  const realAnalyses = analyses.filter((a) => a.isRealSource).sort((a, b) => b.gravity - a.gravity);
1281
1896
  const wildCandidates = realAnalyses.filter((a) => a.heat >= 60 || a.smells.some((s) => s.severity >= 4)).sort((a, b) => b.heat - a.heat);
1282
- const pillars = buildPillars(realAnalyses, communities, stack);
1897
+ const pillars = buildPillars(realAnalyses, persisted, communities, stack);
1283
1898
  const topGravity = realAnalyses.slice(0, 12).map((a) => a.relativePath);
1284
1899
  const topHeat = wildCandidates.slice(0, 12).map((a) => a.relativePath);
1285
1900
  const map = {
@@ -1295,7 +1910,7 @@ async function scanProject(projectRoot) {
1295
1910
  await writeGraph(projectRoot, graph);
1296
1911
  const analysisStore = { files: persisted };
1297
1912
  await writeAnalysis(projectRoot, analysisStore);
1298
- await writeDeltaTargets(projectRoot, analysisStore);
1913
+ await writeDeltaTargets(projectRoot, analysisStore, entrypoints);
1299
1914
  const uiUrl = `file://${join4(projectRoot, ".vibe-splainer", "ui", "index.html")}`;
1300
1915
  return {
1301
1916
  projectRoot,
@@ -1308,7 +1923,7 @@ async function scanProject(projectRoot) {
1308
1923
  graph
1309
1924
  };
1310
1925
  }
1311
- function buildPillars(real, communities, _stack) {
1926
+ function buildPillars(real, persisted, communities, _stack) {
1312
1927
  const keywordGroups = /* @__PURE__ */ new Map();
1313
1928
  const unlabeled = [];
1314
1929
  for (const a of real) {
@@ -1370,15 +1985,29 @@ function buildPillars(real, communities, _stack) {
1370
1985
  if (p.memberFiles.length > 15) {
1371
1986
  const groups = /* @__PURE__ */ new Map();
1372
1987
  for (const f of p.memberFiles) {
1373
- let bucket = "Core";
1374
- if (f.includes("app/") || f.includes("pages/") || f.includes("routes/"))
1375
- bucket = "Routing";
1376
- else if (f.includes("components/") || f.includes("ui/"))
1988
+ const pf = persisted[f];
1989
+ const role = pf?.frameworkRole || "unknown";
1990
+ const domain = pf?.productDomain || "unknown";
1991
+ let bucket;
1992
+ if (domain !== "unknown" && domain !== "routing_infrastructure" && domain !== "test_infrastructure" && domain !== "generated_noise") {
1993
+ bucket = domainToGroupLabel(domain);
1994
+ } else if (role === "hook") {
1995
+ bucket = "Hooks";
1996
+ } else if ([
1997
+ "app_route_page",
1998
+ "app_route_handler",
1999
+ "app_route_layout",
2000
+ "pages_route",
2001
+ "pages_api_route",
2002
+ "trpc_api_route"
2003
+ ].includes(role)) {
2004
+ bucket = "Routes";
2005
+ } else if (role === "component") {
1377
2006
  bucket = "Components";
1378
- else if (f.includes("hooks/") || f.includes("lib/") || f.includes("utils/"))
2007
+ } else {
1379
2008
  bucket = "Logic";
1380
- const d = basename(dirname(f));
1381
- const key = `${p.name} (${bucket} - ${d})`;
2009
+ }
2010
+ const key = `${p.name} (${bucket})`;
1382
2011
  if (!groups.has(key))
1383
2012
  groups.set(key, []);
1384
2013
  groups.get(key).push(f);
@@ -1407,6 +2036,32 @@ function buildPillars(real, communities, _stack) {
1407
2036
  }
1408
2037
  return finalPillars;
1409
2038
  }
2039
+ function domainToGroupLabel(domain) {
2040
+ const labels = {
2041
+ booking_creation: "Booking",
2042
+ booking_management: "Booking",
2043
+ booking_audit: "Booking Audit",
2044
+ event_type_configuration: "Event Types",
2045
+ availability: "Availability",
2046
+ auth: "Auth",
2047
+ auth_oauth: "Auth OAuth",
2048
+ payments: "Payments",
2049
+ payments_webhooks: "Payment Webhooks",
2050
+ webhooks: "Webhooks",
2051
+ apps_marketplace: "Apps",
2052
+ calendar_integrations: "Calendar",
2053
+ video: "Video",
2054
+ onboarding: "Onboarding",
2055
+ settings: "Settings",
2056
+ admin: "Admin",
2057
+ data_table: "Data Table",
2058
+ shell_navigation: "Shell",
2059
+ forms: "Forms",
2060
+ embed: "Embed",
2061
+ notifications: "Notifications"
2062
+ };
2063
+ return labels[domain] || titleCase(domain.replace(/_/g, " "));
2064
+ }
1410
2065
  function pillarNameFromCluster(files) {
1411
2066
  const hintCounts = /* @__PURE__ */ new Map();
1412
2067
  for (const f of files) {
@@ -1551,7 +2206,7 @@ function validateMermaidNodeCount(diagram) {
1551
2206
 
1552
2207
  // ../brain/dist/watcher.js
1553
2208
  import chokidar from "chokidar";
1554
- import { createHash } from "crypto";
2209
+ import { createHash as createHash2 } from "crypto";
1555
2210
  import { readFile as readFile6 } from "fs/promises";
1556
2211
  import { join as join6 } from "path";
1557
2212
  function startWatcher(projectRoot, watchedPaths) {
@@ -1566,7 +2221,7 @@ function startWatcher(projectRoot, watchedPaths) {
1566
2221
  if (!dossier)
1567
2222
  return;
1568
2223
  const content = await readFile6(filepath, "utf8");
1569
- const newHash = createHash("sha256").update(content).digest("hex");
2224
+ const newHash = createHash2("sha256").update(content).digest("hex");
1570
2225
  let mutated = false;
1571
2226
  for (const pillar of dossier.pillars) {
1572
2227
  for (const card of pillar.decisions) {
@@ -1780,7 +2435,7 @@ async function handleGetFileContext(args) {
1780
2435
 
1781
2436
  // dist/mcp/tools/write_decision_card.js
1782
2437
  import { v4 as uuidv4 } from "uuid";
1783
- import { createHash as createHash2 } from "crypto";
2438
+ import { createHash as createHash3 } from "crypto";
1784
2439
  import { readFile as readFile8 } from "fs/promises";
1785
2440
  import { join as join8 } from "path";
1786
2441
  var CATEGORIES = ["Bottleneck", "Hack", "Smart-Move", "Risk", "Convention", "Dead-Weight"];
@@ -1877,7 +2532,7 @@ async function handleWriteDecisionCard(args) {
1877
2532
  primaryContent = await readFile8(join8(projectRoot, primaryFile), "utf8");
1878
2533
  } catch {
1879
2534
  }
1880
- const hash = createHash2("sha256").update(primaryContent).digest("hex");
2535
+ const hash = createHash3("sha256").update(primaryContent).digest("hex");
1881
2536
  const card = {
1882
2537
  id: uuidv4(),
1883
2538
  pillar,
@@ -2243,7 +2898,7 @@ async function serveCommand() {
2243
2898
 
2244
2899
  // dist/index.js
2245
2900
  var program = new Command();
2246
- program.name("vibe-splain").description("Architectural dossier engine for vibe-coded projects").version("2.3.1");
2901
+ program.name("vibe-splain").description("Architectural dossier engine for vibe-coded projects").version("2.4.1");
2247
2902
  program.command("install").description("Patch coding agent MCP config files to register vibe-splain").action(installCommand);
2248
2903
  program.command("serve").description("Start the MCP server (called by the coding agent, not by you)").action(serveCommand);
2249
2904
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-splain",
3
- "version": "2.3.1",
3
+ "version": "2.4.1",
4
4
  "description": "Architectural dossier engine for vibe-coded TypeScript/JavaScript projects. Runs as an MCP server inside your coding agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  "node": ">=18"
31
31
  },
32
32
  "bin": {
33
- "vibe-splain": "./dist/index.js"
33
+ "vibe-splain": "dist/index.js"
34
34
  },
35
35
  "scripts": {
36
36
  "build": "tsc && node build.mjs"