vibe-splain 2.7.2 → 3.0.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.
Files changed (40) hide show
  1. package/README.md +237 -0
  2. package/dist/commands/export.d.ts +1 -0
  3. package/dist/commands/export.js +19 -0
  4. package/dist/commands/serve.d.ts +1 -1
  5. package/dist/commands/serve.js +2 -2
  6. package/dist/export/ArtifactBundleWriter.d.ts +22 -0
  7. package/dist/export/ArtifactBundleWriter.js +56 -0
  8. package/dist/export/ExportOrchestrator.d.ts +12 -0
  9. package/dist/export/ExportOrchestrator.js +72 -0
  10. package/dist/export/Watcher.d.ts +1 -0
  11. package/dist/export/Watcher.js +47 -0
  12. package/dist/export/renderers/AgentMarkdownRenderer.d.ts +8 -0
  13. package/dist/export/renderers/AgentMarkdownRenderer.js +90 -0
  14. package/dist/export/renderers/DeltaRenderer.d.ts +6 -0
  15. package/dist/export/renderers/DeltaRenderer.js +22 -0
  16. package/dist/export/renderers/GraphRenderer.d.ts +9 -0
  17. package/dist/export/renderers/GraphRenderer.js +18 -0
  18. package/dist/export/renderers/HtmlRenderer.d.ts +6 -0
  19. package/dist/export/renderers/HtmlRenderer.js +54 -0
  20. package/dist/export/renderers/JsonRenderer.d.ts +6 -0
  21. package/dist/export/renderers/JsonRenderer.js +12 -0
  22. package/dist/export/renderers/RawAnalysisRenderer.d.ts +6 -0
  23. package/dist/export/renderers/RawAnalysisRenderer.js +12 -0
  24. package/dist/export/renderers/Renderer.d.ts +5 -0
  25. package/dist/export/renderers/Renderer.js +2 -0
  26. package/dist/export/renderers/ValidationRenderer.d.ts +6 -0
  27. package/dist/export/renderers/ValidationRenderer.js +14 -0
  28. package/dist/index.js +722 -574
  29. package/dist/mcp/server.d.ts +1 -1
  30. package/dist/mcp/server.js +3 -3
  31. package/dist/mcp/tools/mark_stale.d.ts +1 -1
  32. package/dist/mcp/tools/mark_stale.js +9 -3
  33. package/dist/mcp/tools/scan_project.d.ts +1 -1
  34. package/dist/mcp/tools/scan_project.js +24 -4
  35. package/dist/mcp/tools/set_project_brief.d.ts +1 -1
  36. package/dist/mcp/tools/set_project_brief.js +9 -3
  37. package/dist/mcp/tools/write_decision_card.d.ts +1 -1
  38. package/dist/mcp/tools/write_decision_card.js +15 -4
  39. package/dist/ui/index.html +2 -2
  40. package/package.json +1 -1
@@ -1 +1 @@
1
- export declare function startMCPServer(): Promise<void>;
1
+ export declare function startMCPServer(options?: any): Promise<void>;
@@ -39,11 +39,11 @@ const TOOL_HANDLERS = {
39
39
  get_wild_discoveries: handleGetWildDiscoveries,
40
40
  mark_stale: handleMarkStale,
41
41
  };
42
- export async function startMCPServer() {
42
+ export async function startMCPServer(options = {}) {
43
43
  // Initialize Tree-Sitter WASM once at startup
44
44
  await initParser();
45
45
  console.error('[vibe-splain] Tree-Sitter parser initialized');
46
- const server = new Server({ name: 'vibe-splain', version: '2.0.0' }, { capabilities: { tools: {}, prompts: {} } });
46
+ const server = new Server({ name: 'vibe-splain', version: '3.0.0' }, { capabilities: { tools: {}, prompts: {} } });
47
47
  // Register prompts
48
48
  server.setRequestHandler(ListPromptsRequestSchema, async () => ({
49
49
  prompts: [
@@ -143,7 +143,7 @@ When done, share the exact file:// UI link returned by scan_project. Never inven
143
143
  };
144
144
  }
145
145
  try {
146
- const result = await handler((args || {}));
146
+ const result = await handler(args, options);
147
147
  return {
148
148
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
149
149
  };
@@ -19,4 +19,4 @@ export declare const markStaleTool: {
19
19
  required: string[];
20
20
  };
21
21
  };
22
- export declare function handleMarkStale(args: Record<string, unknown>): Promise<unknown>;
22
+ export declare function handleMarkStale(args: Record<string, unknown>, options?: any): Promise<unknown>;
@@ -1,4 +1,5 @@
1
- import { readDossier, writeDossier } from '@vibe-splain/brain';
1
+ import { readDossier } from '@vibe-splain/brain';
2
+ import { ExportOrchestrator } from '../../export/ExportOrchestrator.js';
2
3
  export const markStaleTool = {
3
4
  name: 'mark_stale',
4
5
  description: 'Manually marks Decision Cards as stale when you detect a file has changed. The file watcher does this automatically, but call this if you modify a file yourself during a session.',
@@ -18,7 +19,7 @@ export const markStaleTool = {
18
19
  required: ['projectRoot', 'filePaths'],
19
20
  },
20
21
  };
21
- export async function handleMarkStale(args) {
22
+ export async function handleMarkStale(args, options = {}) {
22
23
  const projectRoot = args.projectRoot;
23
24
  const filePaths = args.filePaths;
24
25
  if (!projectRoot || !filePaths)
@@ -48,7 +49,12 @@ export async function handleMarkStale(args) {
48
49
  dossier.stalePaths.push(filePath);
49
50
  }
50
51
  }
51
- await writeDossier(projectRoot, dossier);
52
+ const orchestrator = new ExportOrchestrator(projectRoot);
53
+ await orchestrator.writeBundle(dossier, {
54
+ format: options.format,
55
+ budget: options.budget ? parseInt(options.budget, 10) : undefined,
56
+ scope: options.scope,
57
+ });
52
58
  return {
53
59
  success: true,
54
60
  staleCardsMarked: staleCount,
@@ -12,4 +12,4 @@ export declare const scanProjectTool: {
12
12
  required: string[];
13
13
  };
14
14
  };
15
- export declare function handleScanProject(args: Record<string, unknown>): Promise<unknown>;
15
+ export declare function handleScanProject(args: Record<string, unknown>, options?: any): Promise<unknown>;
@@ -1,4 +1,6 @@
1
- import { scanProject, writeDossier, readDossier, startWatcher } from '@vibe-splain/brain';
1
+ import { scanProject, readDossier } from '@vibe-splain/brain';
2
+ import { ExportOrchestrator } from '../../export/ExportOrchestrator.js';
3
+ import { startWatcher } from '../../export/Watcher.js';
2
4
  export const scanProjectTool = {
3
5
  name: 'scan_project',
4
6
  description: 'Scans a codebase (TS/JS/Python/Go/Rust/Java) and returns a structural analysis. CALL THIS FIRST, then call get_project_map. Files are scored on two axes: GRAVITY (importance — fan-in + PageRank centrality) and HEAT (smell/tech-debt). Mockups, vendored code, and orphan files are demoted (isRealSource:false) so cards target the real application. After scanning, call get_project_map to get the fixed pillar set, Start-Here (top gravity) and Wild-Discovery (top heat) lists. The uiUrl is a file:// link — share it with the user.',
@@ -13,7 +15,7 @@ export const scanProjectTool = {
13
15
  required: ['projectRoot'],
14
16
  },
15
17
  };
16
- export async function handleScanProject(args) {
18
+ export async function handleScanProject(args, options = {}) {
17
19
  const projectRoot = args.projectRoot;
18
20
  if (!projectRoot)
19
21
  throw new Error('projectRoot is required');
@@ -26,7 +28,15 @@ export async function handleScanProject(args) {
26
28
  version: '2.0.0',
27
29
  scannedAt: new Date().toISOString(),
28
30
  projectRoot,
29
- map: { ...result.map, brief },
31
+ map: {
32
+ ...result.map,
33
+ brief,
34
+ validation: result.validation ? {
35
+ passed: result.validation.passed,
36
+ errors: result.validation.errors,
37
+ warnings: result.validation.warnings,
38
+ } : undefined
39
+ },
30
40
  pillars: existing?.pillars ?? [],
31
41
  wildDiscoveries: existing?.wildDiscoveries ?? [],
32
42
  stalePaths: existing?.stalePaths ?? [],
@@ -37,13 +47,23 @@ export async function handleScanProject(args) {
37
47
  dossier.pillars.push({ name: def.name, cardCount: 0, decisions: [] });
38
48
  }
39
49
  }
40
- await writeDossier(projectRoot, dossier);
50
+ const orchestrator = new ExportOrchestrator(projectRoot);
51
+ await orchestrator.writeBundle(dossier, {
52
+ format: options.format,
53
+ budget: options.budget ? parseInt(options.budget, 10) : undefined,
54
+ scope: options.scope,
55
+ }, result.store, result.graph);
41
56
  // Watch the real-source files for staleness.
42
57
  startWatcher(projectRoot, result.files.map(f => f.path));
43
58
  console.error(`[vibe-splain] Scan complete. ${result.totalFilesScanned} files, ${result.realSourceCount} real-source, ${result.wildCandidates.length} wild candidates.`);
44
59
  const validation = result.validation ?? { passed: true, errors: 0, warnings: 0, reportPath: '.vibe-splainer/validation_report.json' };
60
+ let statusMsg = 'Scan complete.';
61
+ if (!validation.passed) {
62
+ statusMsg = `SCAN QUALITY WARNING: ${validation.errors} errors and ${validation.warnings} warnings found in validation report. Delta Engine automation may be blocked.`;
63
+ }
45
64
  return {
46
65
  ok: true,
66
+ message: statusMsg,
47
67
  validation: {
48
68
  passed: validation.passed,
49
69
  errors: validation.errors,
@@ -16,4 +16,4 @@ export declare const setProjectBriefTool: {
16
16
  required: string[];
17
17
  };
18
18
  };
19
- export declare function handleSetProjectBrief(args: Record<string, unknown>): Promise<unknown>;
19
+ export declare function handleSetProjectBrief(args: Record<string, unknown>, options?: any): Promise<unknown>;
@@ -1,4 +1,5 @@
1
- import { readDossier, writeDossier } from '@vibe-splain/brain';
1
+ import { readDossier } from '@vibe-splain/brain';
2
+ import { ExportOrchestrator } from '../../export/ExportOrchestrator.js';
2
3
  export const setProjectBriefTool = {
3
4
  name: 'set_project_brief',
4
5
  description: 'Persists your 3-5 sentence project brief into the dossier (and regenerates the UI). Call this AFTER get_project_map and BEFORE writing any decision card. The brief must say: what this project IS, the real stack, and — critically — which files are the actual application vs. mockups/generated/vendored noise.',
@@ -11,7 +12,7 @@ export const setProjectBriefTool = {
11
12
  required: ['projectRoot', 'brief'],
12
13
  },
13
14
  };
14
- export async function handleSetProjectBrief(args) {
15
+ export async function handleSetProjectBrief(args, options = {}) {
15
16
  const projectRoot = args.projectRoot;
16
17
  const brief = args.brief;
17
18
  if (!projectRoot || !brief)
@@ -21,7 +22,12 @@ export async function handleSetProjectBrief(args) {
21
22
  return { error: 'No project map found. Run scan_project first.' };
22
23
  }
23
24
  dossier.map.brief = brief;
24
- await writeDossier(projectRoot, dossier);
25
+ const orchestrator = new ExportOrchestrator(projectRoot);
26
+ await orchestrator.writeBundle(dossier, {
27
+ format: options.format,
28
+ budget: options.budget ? parseInt(options.budget, 10) : undefined,
29
+ scope: options.scope,
30
+ });
25
31
  // Drive the loop: hand back the exact remaining worklist so the agent does
26
32
  // not stop and ask the user. Weak models treat "brief saved" as done otherwise.
27
33
  const documented = new Set([...dossier.pillars.flatMap(p => p.decisions), ...dossier.wildDiscoveries]
@@ -79,4 +79,4 @@ export declare const writeDecisionCardTool: {
79
79
  required: string[];
80
80
  };
81
81
  };
82
- export declare function handleWriteDecisionCard(args: Record<string, unknown>): Promise<unknown>;
82
+ export declare function handleWriteDecisionCard(args: Record<string, unknown>, options?: any): Promise<unknown>;
@@ -1,4 +1,5 @@
1
- import { readDossier, writeDossier, validateMermaidNodeCount, readAnalysis } from '@vibe-splain/brain';
1
+ import { readDossier, validateMermaidNodeCount, readAnalysis } from '@vibe-splain/brain';
2
+ import { ExportOrchestrator } from '../../export/ExportOrchestrator.js';
2
3
  import { v4 as uuidv4 } from 'uuid';
3
4
  import { createHash } from 'crypto';
4
5
  import { readFile } from 'fs/promises';
@@ -49,7 +50,7 @@ export const writeDecisionCardTool = {
49
50
  required: ['projectRoot', 'pillar', 'primaryFile', 'title', 'thesis', 'category', 'severity', 'narrative', 'confidence', 'evidence'],
50
51
  },
51
52
  };
52
- export async function handleWriteDecisionCard(args) {
53
+ export async function handleWriteDecisionCard(args, options = {}) {
53
54
  const projectRoot = args.projectRoot;
54
55
  const pillar = args.pillar;
55
56
  const primaryFile = args.primaryFile;
@@ -94,9 +95,14 @@ export async function handleWriteDecisionCard(args) {
94
95
  p.decisions = p.decisions.filter(c => c.id !== existing.id);
95
96
  dossier.wildDiscoveries = dossier.wildDiscoveries.filter(c => c.id !== existing.id);
96
97
  }
97
- // Auto-carry gravity/heat from the scan.
98
+ // ADR-019: machine-derived confidence cap
98
99
  const store = await readAnalysis(projectRoot);
99
100
  const persisted = store?.files[primaryFile];
101
+ const machineConfidence = persisted?.confidence || 'low';
102
+ const confidenceOrder = { low: 0, medium: 1, high: 2 };
103
+ if (confidenceOrder[confidence] > confidenceOrder[machineConfidence]) {
104
+ throw new Error(`Requested confidence "${confidence}" exceeds machine-derived confidence "${machineConfidence}" for this file. Ground your narrative in the MRI evidence.`);
105
+ }
100
106
  const gravity = persisted ? Math.round(persisted.gravity) : undefined;
101
107
  const heat = persisted ? Math.round(persisted.heat) : undefined;
102
108
  // Hash the primaryFile so the watcher can detect staleness per-file.
@@ -126,7 +132,12 @@ export async function handleWriteDecisionCard(args) {
126
132
  }
127
133
  bucket.decisions.push(card);
128
134
  bucket.cardCount = bucket.decisions.length;
129
- await writeDossier(projectRoot, dossier);
135
+ const orchestrator = new ExportOrchestrator(projectRoot);
136
+ await orchestrator.writeBundle(dossier, {
137
+ format: options.format,
138
+ budget: options.budget ? parseInt(options.budget, 10) : undefined,
139
+ scope: options.scope,
140
+ });
130
141
  console.error(`[vibe-splain] Card written: "${title}" [${category} sev${severity}] in "${pillar}"${isWild ? ' (Wild Discovery)' : ''}`);
131
142
  // Keep the loop going: compute what is still undocumented.
132
143
  const documented = new Set([...dossier.pillars.flatMap(p => p.decisions), ...dossier.wildDiscoveries]