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
package/README.md ADDED
@@ -0,0 +1,237 @@
1
+ <p align="center">
2
+ <strong>◈ VIBE-SPLAIN</strong>
3
+ <br />
4
+ <em>Map architectural DNA and behavioral call-chains in complex codebases.</em>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="#install">Install</a> ·
9
+ <a href="#how-it-works">How It Works</a> ·
10
+ <a href="#mcp-tools">MCP Tools</a> ·
11
+ <a href="#dossier-ui">Dossier UI</a> ·
12
+ <a href="#development">Development</a>
13
+ </p>
14
+
15
+ ---
16
+
17
+ VIBE-SPLAIN is a high-fidelity **static analysis engine** and MCP server. It uses [Tree-Sitter](https://tree-sitter.github.io/tree-sitter/) to extract the structural and behavioral patterns of a codebase—identifying high-gravity components, mapping semantic actions, and tracing call-chains between entrypoints and side effects.
18
+
19
+ While VIBE-SPLAIN is built on a language-agnostic foundation, the current toolset is **highly optimized for TypeScript and JavaScript** (especially Next.js, Prisma, and tRPC environments).
20
+
21
+ **Zero LLM calls. Zero API keys. Pure static analysis.**
22
+
23
+ Your coding agent does all the thinking — VIBE-SPLAIN just gives it the right data.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npx vibe-splain install
29
+ ```
30
+
31
+ That's it. This patches your coding agent's MCP config so it can call VIBE-SPLAIN's tools. Restart your agent.
32
+
33
+ ### Running the Analysis
34
+
35
+ You don't need to write a complex prompt. VIBE-SPLAIN provides a built-in MCP Prompt called `build_dossier` that automatically tells your agent exactly what to do.
36
+
37
+ **In Claude Code / Gemini CLI:**
38
+ Type `/prompt build_dossier` and press enter.
39
+
40
+ **In Cursor / Windsurf:**
41
+ Open the MCP panel or agent chat, select the `build_dossier` prompt from the VIBE-SPLAIN server, and run it.
42
+
43
+ Your agent will loop through the high-gravity files, analyze each one, and build an **Architectural Dossier** — a structured set of **Decision Cards** explaining the technical rationale of the code.
44
+
45
+ ### Supported Agents
46
+
47
+ | Agent | Config File |
48
+ |-------|------------|
49
+ | Claude Code | `~/.claude/claude_desktop_config.json` |
50
+ | Gemini CLI | `~/.gemini/settings.json` |
51
+ | Cursor | `~/.cursor/mcp.json` |
52
+ | Windsurf | `~/.codeium/windsurf/mcp_config.json` |
53
+
54
+ ## How It Works
55
+
56
+ ```
57
+ ┌─────────────────────────────────────────────────────────┐
58
+ │ Your Coding Agent (Claude / Gemini / Cursor) │
59
+ │ │
60
+ │ "Scan this project" ──► scan_project ──► get_file_ctx │
61
+ │ │ │ │
62
+ │ Agent synthesizes ◄──────┘ ◄──────────────┘ │
63
+ │ narratives + diagrams │
64
+ │ │ │
65
+ │ ▼ │
66
+ │ write_decision_card ──► .vibe-splainer/dossier.json │
67
+ │ │ │
68
+ │ ▼ │
69
+ │ file:// Dossier UI │
70
+ └─────────────────────────────────────────────────────────┘
71
+ ```
72
+
73
+ ### Three Levels of Analysis
74
+
75
+ 1. **Level 0 — Semantic Classification**: Maps files to architectural "pillars" (Auth, Payments, Database, etc.) using import-path heuristics and library signatures.
76
+
77
+ 2. **Level 1 — Cognitive Complexity**: Tree-Sitter AST analysis computes a complexity score per file based on link density, nesting depth, and mutation counts. Files scoring ≥ 15 are identified as **High-Gravity**.
78
+
79
+ 3. **Level 2 — Behavioral Traceability**: Tree-Sitter powered call-graph analysis. It maps function-level dependencies and identifies **Critical Functions** (entrypoints, semantic actions, or high-outbound callers) so your agent can trace the exact ripple effect of a code change.
80
+
81
+ ## MCP Tools
82
+
83
+ VIBE-SPLAIN exposes **8 tools** over MCP stdio:
84
+
85
+ | Tool | Purpose |
86
+ |------|---------|
87
+ | `scan_project` | **Call first.** Scans the codebase, returns high-gravity files grouped by pillar. Starts file watcher. |
88
+ | `get_file_context` | Returns full source + import graph neighbors for a specific file. |
89
+ | `get_call_chain` | **New.** Traces function-level call chains (upstream/downstream) to map behavior paths. |
90
+ | `write_decision_card` | Persists a Decision Card (narrative + evidence + optional Mermaid diagram). |
91
+ | `get_strategic_overview` | Returns dossier state without evidence snippets (saves tokens). |
92
+ | `inspect_pillar` | Returns all Decision Cards for a pillar with full evidence. |
93
+ | `get_wild_discoveries` | Returns the most complex files that don't fit standard patterns. |
94
+ | `mark_stale` | Marks cards as stale when you modify files during a session. |
95
+
96
+ ### Recommended Agent Workflow
97
+
98
+ ```
99
+ 1. scan_project → get high-gravity files
100
+ 2. For each file: get_file_context → read source + neighbors
101
+ 3. Trace: use get_call_chain to see what calls what
102
+ 4. Synthesize: "WHY does this code exist?"
103
+ 5. write_decision_card → persist the narrative
104
+ 6. Share the file:// UI link with the user
105
+ ```
106
+
107
+ ## How It Works
108
+
109
+ ### Deep Analysis Pipeline
110
+ Unlike simple regex scanners, VIBE-SPLAIN runs a deterministic **13-stage pipeline**—from AST inventory and alias resolution to semantic classification and function-level scoring—to ensure every Decision Card is grounded in actual code paths.
111
+
112
+ ### Semantic Rulesets
113
+ VIBE-SPLAIN uses specialized rulesets to understand framework-specific semantics. Current optimizations include:
114
+ - **Next.js**: Server Actions, `cookies()`, `headers()`, and App Router conventions.
115
+ - **Database**: Prisma model mutations and raw query patterns.
116
+ - **API**: tRPC procedure calls (`mutate`/`query`) and standard `fetch`/`axios` patterns.
117
+ - **Auth**: Clerk, NextAuth, and custom rate-limiting/validation logic.
118
+
119
+ ## Dossier UI
120
+
121
+ After your agent writes Decision Cards, open the generated file in your browser:
122
+
123
+ ```
124
+ file:///path/to/your/project/.vibe-splainer/ui/index.html
125
+ ```
126
+
127
+ The UI features:
128
+ - **Dark theme** with glassmorphism and subtle grid texture
129
+ - **Pillar tabs** for navigating architectural areas
130
+ - **Decision Cards** with fresh/stale status badges
131
+ - **Mermaid diagrams** rendered inline as SVG
132
+ - **Evidence sidebar** with Shiki syntax highlighting (tokyo-night)
133
+ - **Wild Discoveries** tab for the most complex outlier files
134
+ - Works entirely offline via `file://` — no server needed
135
+
136
+ ## Architecture
137
+
138
+ ```
139
+ packages/
140
+ ├── brain/ # @vibe-splain/brain — analysis engine
141
+ │ └── src/
142
+ │ ├── scanner.ts # Tree-Sitter AST analysis (L0 + L1 + L2)
143
+ │ ├── dossier.ts # Atomic persistence + UI regeneration
144
+ │ ├── graph.ts # Import graph read/write
145
+ │ └── watcher.ts # Chokidar file watcher
146
+ ├── cli/ # vibe-splain — MCP server + CLI
147
+ │ └── src/
148
+ │ ├── index.ts # #!/usr/bin/env node entry
149
+ │ ├── commands/
150
+ │ │ ├── install.ts # Agent config patcher
151
+ │ │ └── serve.ts # MCP server launcher
152
+ │ └── mcp/
153
+ │ ├── server.ts # @modelcontextprotocol/sdk setup
154
+ │ └── tools/ # 7 tool handlers
155
+ └── ui/ # @vibe-splain/ui — React dossier viewer
156
+ └── src/
157
+ ├── App.tsx # Main app (reads window.__VIBE_DOSSIER__)
158
+ ├── components/ # Header, PillarTabs, DecisionCard, etc.
159
+ └── index.css # Design system
160
+ ```
161
+
162
+ ### Key Design Decisions
163
+
164
+ - **No LLM calls**: VIBE-SPLAIN is a pure static analysis tool. The coding agent provides all synthesis.
165
+ - **`async-mutex`**: All dossier writes are guarded by a mutex with atomic tmp+rename.
166
+ - **`startOnLoad: false`**: Mermaid is initialized manually — never auto-scans the DOM.
167
+ - **`base: './'`**: Vite builds with relative paths so the UI works from `file://` URLs.
168
+ - **`console.log` banned**: Brain and CLI use only `console.error` to avoid corrupting MCP stdio.
169
+ - **Tree-Sitter WASM**: Loaded from the `tree-sitter-wasms` npm package — no network calls.
170
+
171
+ ## Development
172
+
173
+ ### Prerequisites
174
+
175
+ - Node.js ≥ 18
176
+ - npm ≥ 9 (for workspaces)
177
+
178
+ ### Setup
179
+
180
+ ```bash
181
+ git clone https://github.com/abp2204/vibe-splain.git
182
+ cd vibe-splain
183
+ npm install
184
+ ```
185
+
186
+ ### Build
187
+
188
+ ```bash
189
+ npm run build
190
+ ```
191
+
192
+ This runs in sequence: brain → cli → ui → bundle-ui (copies UI dist into CLI dist).
193
+
194
+ ### Dev UI
195
+
196
+ ```bash
197
+ npm run dev:ui
198
+ ```
199
+
200
+ Starts the Vite dev server for the UI package at `http://localhost:5173`.
201
+
202
+ ### Test Install Locally
203
+
204
+ ```bash
205
+ node packages/cli/dist/index.js install
206
+ ```
207
+
208
+ ### Test MCP Server
209
+
210
+ ```bash
211
+ node packages/cli/dist/index.js serve
212
+ ```
213
+
214
+ Then send JSON-RPC messages over stdin (see [MCP specification](https://modelcontextprotocol.io)).
215
+
216
+ ### Publish
217
+
218
+ ```bash
219
+ npm run release
220
+ ```
221
+
222
+ Builds everything and publishes the `vibe-splain` CLI package to npm.
223
+
224
+ ## How the Dossier Stays Fresh
225
+
226
+ When `scan_project` runs, it starts a [Chokidar](https://github.com/paulmillr/chokidar) file watcher. When source files change:
227
+
228
+ 1. The watcher detects the change
229
+ 2. Matching Decision Cards are marked **stale** (amber badge in UI)
230
+ 3. The `stalePaths` array in the dossier tracks which files need re-analysis
231
+ 4. Your agent can call `get_strategic_overview` to see what's stale, then re-scan
232
+
233
+ You can also manually mark files stale with `mark_stale` if you modify code during a session.
234
+
235
+ ## License
236
+
237
+ [MIT](LICENSE)
@@ -0,0 +1 @@
1
+ export declare function exportCommand(projectRoot: string, options: any): Promise<void>;
@@ -0,0 +1,19 @@
1
+ import { readDossier } from '@vibe-splain/brain';
2
+ import { ExportOrchestrator } from '../export/ExportOrchestrator.js';
3
+ export async function exportCommand(projectRoot, options) {
4
+ const root = projectRoot || process.cwd();
5
+ console.error(`[vibe-splain] Exporting dossier for ${root}`);
6
+ const dossier = await readDossier(root);
7
+ if (!dossier) {
8
+ console.error('[vibe-splain] Dossier not found. Run scan first.');
9
+ process.exit(1);
10
+ }
11
+ const orchestrator = new ExportOrchestrator(root);
12
+ await orchestrator.writeBundle(dossier, {
13
+ format: options.format,
14
+ budget: options.budget ? parseInt(options.budget, 10) : undefined,
15
+ scope: options.scope,
16
+ });
17
+ console.error('[vibe-splain] Export complete.');
18
+ }
19
+ //# sourceMappingURL=export.js.map
@@ -1 +1 @@
1
- export declare function serveCommand(): Promise<void>;
1
+ export declare function serveCommand(options?: any): Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import { startMCPServer } from '../mcp/server.js';
2
- export async function serveCommand() {
2
+ export async function serveCommand(options) {
3
3
  console.error('[vibe-splain] Starting MCP server...');
4
- await startMCPServer();
4
+ await startMCPServer(options);
5
5
  // Process stays alive — do NOT call process.exit() here
6
6
  }
7
7
  //# sourceMappingURL=serve.js.map
@@ -0,0 +1,22 @@
1
+ export interface Artifact {
2
+ type: string;
3
+ path: string;
4
+ content: string | Buffer;
5
+ }
6
+ export interface ManifestArtifact {
7
+ type: string;
8
+ path: string;
9
+ checksum: string;
10
+ sizeBytes: number;
11
+ }
12
+ export interface ArtifactManifest {
13
+ schemaVersion: string;
14
+ generatedAt: string;
15
+ projectRoot: string;
16
+ artifacts: ManifestArtifact[];
17
+ }
18
+ export declare class ArtifactBundleWriter {
19
+ private projectRoot;
20
+ constructor(projectRoot: string);
21
+ writeBundle(artifacts: Artifact[]): Promise<void>;
22
+ }
@@ -0,0 +1,56 @@
1
+ import { join } from 'path';
2
+ import { writeFile, mkdir, rm, rename } from 'fs/promises';
3
+ import { createHash } from 'crypto';
4
+ export class ArtifactBundleWriter {
5
+ projectRoot;
6
+ constructor(projectRoot) {
7
+ this.projectRoot = projectRoot;
8
+ }
9
+ async writeBundle(artifacts) {
10
+ const outputDir = join(this.projectRoot, '.vibe-splainer');
11
+ const stagingDir = join(this.projectRoot, '.vibe-splainer.tmp');
12
+ try {
13
+ await rm(stagingDir, { recursive: true, force: true });
14
+ const { existsSync } = await import('fs');
15
+ const { cp } = await import('fs/promises');
16
+ if (existsSync(outputDir)) {
17
+ await cp(outputDir, stagingDir, { recursive: true });
18
+ }
19
+ else {
20
+ await mkdir(stagingDir, { recursive: true });
21
+ }
22
+ const manifestArtifacts = [];
23
+ for (const artifact of artifacts) {
24
+ const destPath = join(stagingDir, artifact.path);
25
+ await mkdir(join(destPath, '..'), { recursive: true });
26
+ await writeFile(destPath, artifact.content);
27
+ const contentStr = artifact.content;
28
+ const buffer = typeof contentStr === 'string' ? Buffer.from(contentStr, 'utf-8') : contentStr;
29
+ manifestArtifacts.push({
30
+ type: artifact.type,
31
+ path: artifact.path,
32
+ checksum: 'sha256:' + createHash('sha256').update(buffer).digest('hex'),
33
+ sizeBytes: buffer.length,
34
+ });
35
+ }
36
+ const manifest = {
37
+ schemaVersion: '1.0.0',
38
+ generatedAt: new Date().toISOString(),
39
+ projectRoot: this.projectRoot,
40
+ artifacts: manifestArtifacts,
41
+ };
42
+ await writeFile(join(stagingDir, 'artifact_manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
43
+ // Atomic rename.
44
+ // Rename fails if destination is a non-empty directory.
45
+ // So we first delete the existing outputDir.
46
+ // Since it's inside the project root, it's safe to do rm -rf .vibe-splainer
47
+ await rm(outputDir, { recursive: true, force: true });
48
+ await rename(stagingDir, outputDir);
49
+ }
50
+ catch (err) {
51
+ await rm(stagingDir, { recursive: true, force: true });
52
+ throw err;
53
+ }
54
+ }
55
+ }
56
+ //# sourceMappingURL=ArtifactBundleWriter.js.map
@@ -0,0 +1,12 @@
1
+ import type { Dossier, AnalysisStore, ImportGraph } from '@vibe-splain/brain';
2
+ export interface ExportOptions {
3
+ format?: 'json' | 'html' | 'markdown' | 'delta';
4
+ budget?: number;
5
+ scope?: string;
6
+ }
7
+ export declare class ExportOrchestrator {
8
+ private projectRoot;
9
+ constructor(projectRoot: string);
10
+ writeBundle(dossier: Dossier, options?: ExportOptions, store?: AnalysisStore, graph?: ImportGraph): Promise<void>;
11
+ private buildViewModel;
12
+ }
@@ -0,0 +1,72 @@
1
+ import { readAnalysis, RecommendationEngine } from '@vibe-splain/brain';
2
+ import { ArtifactBundleWriter } from './ArtifactBundleWriter.js';
3
+ import { JsonRenderer } from './renderers/JsonRenderer.js';
4
+ import { HtmlRenderer } from './renderers/HtmlRenderer.js';
5
+ import { DeltaRenderer } from './renderers/DeltaRenderer.js';
6
+ import { AgentMarkdownRenderer } from './renderers/AgentMarkdownRenderer.js';
7
+ import { ValidationRenderer } from './renderers/ValidationRenderer.js';
8
+ import { RawAnalysisRenderer } from './renderers/RawAnalysisRenderer.js';
9
+ import { GraphRenderer } from './renderers/GraphRenderer.js';
10
+ export class ExportOrchestrator {
11
+ projectRoot;
12
+ constructor(projectRoot) {
13
+ this.projectRoot = projectRoot;
14
+ }
15
+ async writeBundle(dossier, options = {}, store, graph) {
16
+ const finalStore = store || await readAnalysis(this.projectRoot);
17
+ if (!finalStore) {
18
+ throw new Error('Analysis store not found. Scan the project first.');
19
+ }
20
+ // Aggressive Boilerplate Culling
21
+ for (const p of dossier.pillars) {
22
+ p.decisions = p.decisions.filter(c => !(c.severity === 1 && c.category === 'Convention'));
23
+ p.cardCount = p.decisions.length;
24
+ }
25
+ const viewModel = this.buildViewModel(dossier, finalStore);
26
+ // Always render JSON, Delta, Validation, and Raw Analysis.
27
+ const artifacts = [];
28
+ artifacts.push(...await new JsonRenderer().render(viewModel, finalStore));
29
+ artifacts.push(...await new DeltaRenderer().render(viewModel, finalStore));
30
+ artifacts.push(...await new ValidationRenderer().render(viewModel, finalStore));
31
+ artifacts.push(...await new RawAnalysisRenderer().render(viewModel, finalStore));
32
+ if (graph) {
33
+ artifacts.push(...await new GraphRenderer(graph).render(viewModel, finalStore));
34
+ }
35
+ // Determine additional formats
36
+ const formats = ['html', 'markdown']; // default
37
+ if (options.format && options.format !== 'json' && options.format !== 'delta') {
38
+ formats.length = 0;
39
+ formats.push(options.format);
40
+ }
41
+ if (formats.includes('html')) {
42
+ artifacts.push(...await new HtmlRenderer().render(viewModel, finalStore));
43
+ }
44
+ if (formats.includes('markdown')) {
45
+ artifacts.push(...await new AgentMarkdownRenderer(options.budget).render(viewModel, finalStore));
46
+ }
47
+ const writer = new ArtifactBundleWriter(this.projectRoot);
48
+ await writer.writeBundle(artifacts);
49
+ }
50
+ buildViewModel(dossier, store) {
51
+ const recommendations = {};
52
+ for (const file of dossier.map.topGravity) {
53
+ const persisted = store.files[file];
54
+ if (persisted) {
55
+ recommendations[file] = RecommendationEngine.generateRecommendations(persisted);
56
+ }
57
+ }
58
+ for (const file of dossier.map.topHeat) {
59
+ if (!recommendations[file]) {
60
+ const persisted = store.files[file];
61
+ if (persisted) {
62
+ recommendations[file] = RecommendationEngine.generateRecommendations(persisted);
63
+ }
64
+ }
65
+ }
66
+ return {
67
+ ...dossier,
68
+ recommendations,
69
+ };
70
+ }
71
+ }
72
+ //# sourceMappingURL=ExportOrchestrator.js.map
@@ -0,0 +1 @@
1
+ export declare function startWatcher(projectRoot: string, watchedPaths: string[]): void;
@@ -0,0 +1,47 @@
1
+ import chokidar from 'chokidar';
2
+ import { createHash } from 'crypto';
3
+ import { readFile } from 'fs/promises';
4
+ import { join } from 'path';
5
+ import { readDossier } from '@vibe-splain/brain';
6
+ import { ExportOrchestrator } from './ExportOrchestrator.js';
7
+ export function startWatcher(projectRoot, watchedPaths) {
8
+ const watcher = chokidar.watch(watchedPaths.length > 0 ? watchedPaths : projectRoot, {
9
+ ignoreInitial: true,
10
+ ignored: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.vibe-splainer/**'],
11
+ persistent: true,
12
+ });
13
+ watcher.on('change', async (filepath) => {
14
+ try {
15
+ const dossier = await readDossier(projectRoot);
16
+ if (!dossier)
17
+ return;
18
+ const content = await readFile(filepath, 'utf8');
19
+ const newHash = createHash('sha256').update(content).digest('hex');
20
+ let mutated = false;
21
+ for (const pillar of dossier.pillars) {
22
+ for (const card of pillar.decisions) {
23
+ if (!card.primaryFile)
24
+ continue;
25
+ const absMatch = filepath === join(projectRoot, card.primaryFile) || filepath.endsWith('/' + card.primaryFile);
26
+ if (absMatch && card.lastScannedHash !== newHash) {
27
+ card.status = 'stale';
28
+ const rel = card.primaryFile;
29
+ if (!dossier.stalePaths.includes(rel))
30
+ dossier.stalePaths.push(rel);
31
+ mutated = true;
32
+ }
33
+ }
34
+ }
35
+ if (mutated) {
36
+ const orchestrator = new ExportOrchestrator(projectRoot);
37
+ await orchestrator.writeBundle(dossier);
38
+ console.error(`[vibe-splain] File changed: ${filepath}. Dossier artifacts updated.`);
39
+ }
40
+ }
41
+ catch (err) {
42
+ console.error('[vibe-splain] Watcher error:', err);
43
+ }
44
+ });
45
+ console.error('[vibe-splain] File watcher started');
46
+ }
47
+ //# sourceMappingURL=Watcher.js.map
@@ -0,0 +1,8 @@
1
+ import type { DossierViewModel, AnalysisStore } from '@vibe-splain/brain';
2
+ import type { Renderer } from './Renderer.js';
3
+ import type { Artifact } from '../ArtifactBundleWriter.js';
4
+ export declare class AgentMarkdownRenderer implements Renderer {
5
+ private budget;
6
+ constructor(budget?: number);
7
+ render(viewModel: DossierViewModel, store: AnalysisStore): Artifact[];
8
+ }
@@ -0,0 +1,90 @@
1
+ export class AgentMarkdownRenderer {
2
+ budget;
3
+ constructor(budget = 8000) {
4
+ this.budget = budget;
5
+ }
6
+ render(viewModel, store) {
7
+ let md = `# Architectural Dossier: ${viewModel.projectRoot}\n\n`;
8
+ if (viewModel.map.brief) {
9
+ md += `## Project Brief\n${viewModel.map.brief}\n\n`;
10
+ }
11
+ md += `## Stack & Entrypoints\n`;
12
+ md += `- Stack: ${viewModel.map.stack.join(', ')}\n`;
13
+ md += `- Entrypoints: ${viewModel.map.entrypoints.join(', ')}\n\n`;
14
+ // Flatten decisions
15
+ const allDecisions = viewModel.pillars.flatMap(p => p.decisions).concat(viewModel.wildDiscoveries);
16
+ const uniqueDecisions = new Map();
17
+ for (const d of allDecisions) {
18
+ if (d.primaryFile && !uniqueDecisions.has(d.primaryFile)) {
19
+ uniqueDecisions.set(d.primaryFile, d);
20
+ }
21
+ }
22
+ const tier1 = [];
23
+ const tier2 = [];
24
+ const tier3 = [];
25
+ // Sort files by gravity
26
+ const sortedFiles = Object.values(store.files)
27
+ .filter(f => f.isRealSource)
28
+ .sort((a, b) => b.gravity - a.gravity);
29
+ for (const f of sortedFiles) {
30
+ const card = uniqueDecisions.get(f.relativePath);
31
+ const isCritical = card && card.severity >= 4;
32
+ if (f.gravity >= 70 || isCritical) {
33
+ tier1.push(f.relativePath);
34
+ }
35
+ else if (f.gravity >= 40 || card) {
36
+ tier2.push(f.relativePath);
37
+ }
38
+ else {
39
+ tier3.push(f.relativePath);
40
+ }
41
+ }
42
+ md += `## Tier 1: Critical Files & Risks\n\n`;
43
+ for (const path of tier1) {
44
+ const f = store.files[path];
45
+ const card = uniqueDecisions.get(path);
46
+ const recs = viewModel.recommendations[path] || [];
47
+ md += `### ${path}\n`;
48
+ md += `- Gravity: ${Math.round(f.gravity)} | Heat: ${Math.round(f.heat)}\n`;
49
+ md += `- Domain: ${f.productDomain} | Role: ${f.frameworkRole}\n`;
50
+ if (card) {
51
+ md += `\n**Verdict**: ${card.thesis}\n`;
52
+ md += `**Severity**: ${card.severity} | **Category**: ${card.category}\n`;
53
+ md += `**Narrative**: ${card.narrative}\n`;
54
+ }
55
+ if (recs.length > 0) {
56
+ md += `\n**Safe Patch Strategies**:\n`;
57
+ for (const r of recs) {
58
+ md += `- **${r.strategy}**: ${r.description}\n`;
59
+ }
60
+ }
61
+ md += `\n---\n\n`;
62
+ }
63
+ md += `## Tier 2: Important Files\n\n`;
64
+ for (const path of tier2) {
65
+ const f = store.files[path];
66
+ const card = uniqueDecisions.get(path);
67
+ md += `- **${path}** (Gravity: ${Math.round(f.gravity)})`;
68
+ if (card) {
69
+ md += ` — ${card.thesis}`;
70
+ }
71
+ md += `\n`;
72
+ }
73
+ md += `\n`;
74
+ md += `## Tier 3: Index\n\n`;
75
+ for (const path of tier3) {
76
+ const f = store.files[path];
77
+ md += `- ${path} (Gravity: ${Math.round(f.gravity)})\n`;
78
+ }
79
+ // In a real robust implementation, we would truncate tiers starting from Tier 3 to fit the budget.
80
+ // Given the simplicity, we'll return the full markdown.
81
+ return [
82
+ {
83
+ type: 'markdown',
84
+ path: 'dossier.agent.md',
85
+ content: md,
86
+ }
87
+ ];
88
+ }
89
+ }
90
+ //# sourceMappingURL=AgentMarkdownRenderer.js.map
@@ -0,0 +1,6 @@
1
+ import type { DossierViewModel, AnalysisStore } from '@vibe-splain/brain';
2
+ import type { Renderer } from './Renderer.js';
3
+ import type { Artifact } from '../ArtifactBundleWriter.js';
4
+ export declare class DeltaRenderer implements Renderer {
5
+ render(_viewModel: DossierViewModel, store: AnalysisStore): Artifact[];
6
+ }
@@ -0,0 +1,22 @@
1
+ export class DeltaRenderer {
2
+ render(_viewModel, store) {
3
+ const deltaTargets = Object.values(store.files)
4
+ .filter(pf => pf.isRealSource)
5
+ .sort((a, b) => b.gravity - a.gravity)
6
+ .map(pf => ({
7
+ path: pf.relativePath,
8
+ gravity: Math.round(pf.gravity),
9
+ isLoadBearing: pf.canonicalLoadBearing,
10
+ blastRadius: pf.importedBy,
11
+ pillarHint: pf.pillarHint,
12
+ }));
13
+ return [
14
+ {
15
+ type: 'delta',
16
+ path: 'delta_targets.json',
17
+ content: JSON.stringify(deltaTargets, null, 2),
18
+ }
19
+ ];
20
+ }
21
+ }
22
+ //# sourceMappingURL=DeltaRenderer.js.map
@@ -0,0 +1,9 @@
1
+ import { type ImportGraph } from '@vibe-splain/brain';
2
+ import type { DossierViewModel, AnalysisStore } from '@vibe-splain/brain';
3
+ import type { Renderer } from './Renderer.js';
4
+ import type { Artifact } from '../ArtifactBundleWriter.js';
5
+ export declare class GraphRenderer implements Renderer {
6
+ private graph?;
7
+ constructor(graph?: ImportGraph | undefined);
8
+ render(_viewModel: DossierViewModel, _store: AnalysisStore): Artifact[];
9
+ }
@@ -0,0 +1,18 @@
1
+ export class GraphRenderer {
2
+ graph;
3
+ constructor(graph) {
4
+ this.graph = graph;
5
+ }
6
+ render(_viewModel, _store) {
7
+ if (!this.graph)
8
+ return [];
9
+ return [
10
+ {
11
+ type: 'graph',
12
+ path: 'graph.json',
13
+ content: JSON.stringify(this.graph, null, 2),
14
+ }
15
+ ];
16
+ }
17
+ }
18
+ //# sourceMappingURL=GraphRenderer.js.map
@@ -0,0 +1,6 @@
1
+ import type { DossierViewModel, AnalysisStore } from '@vibe-splain/brain';
2
+ import type { Renderer } from './Renderer.js';
3
+ import type { Artifact } from '../ArtifactBundleWriter.js';
4
+ export declare class HtmlRenderer implements Renderer {
5
+ render(viewModel: DossierViewModel, _store: AnalysisStore): Artifact[];
6
+ }