vibe-splain 3.4.2 → 4.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.
- package/README.md +110 -158
- package/dist/commands/install.d.ts +0 -5
- package/dist/commands/install.js +5 -55
- package/dist/commands/serve.js +1 -1
- package/dist/export/ArtifactBundleWriter.js +3 -3
- package/dist/export/ExportOrchestrator.d.ts +2 -20
- package/dist/export/ExportOrchestrator.js +5 -116
- package/dist/export/Watcher.js +5 -5
- package/dist/export/renderers/AgentMarkdownRenderer.d.ts +1 -1
- package/dist/export/renderers/GraphRenderer.d.ts +2 -2
- package/dist/export/renderers/HtmlRenderer.d.ts +1 -1
- package/dist/export/renderers/HtmlRenderer.js +1 -1
- package/dist/export/renderers/JsonRenderer.d.ts +1 -1
- package/dist/export/renderers/RawAnalysisRenderer.d.ts +1 -1
- package/dist/export/renderers/Renderer.d.ts +1 -1
- package/dist/export/renderers/ValidationRenderer.d.ts +1 -1
- package/dist/index.js +1425 -6511
- package/dist/mcp/server.js +6 -46
- package/dist/mcp/tools/get_file_context.js +1 -1
- package/dist/mcp/tools/get_project_map.js +1 -1
- package/dist/mcp/tools/get_strategic_overview.js +1 -1
- package/dist/mcp/tools/get_wild_discoveries.js +1 -1
- package/dist/mcp/tools/inspect_pillar.js +1 -1
- package/dist/mcp/tools/mark_stale.js +1 -1
- package/dist/mcp/tools/scan_project.d.ts +2 -3
- package/dist/mcp/tools/scan_project.js +12 -13
- package/dist/mcp/tools/set_project_brief.js +1 -1
- package/dist/mcp/tools/write_decision_card.d.ts +1 -1
- package/dist/mcp/tools/write_decision_card.js +2 -2
- package/dist/ui/index.html +1 -1
- package/package.json +9 -14
- package/dist/commands/bundle.d.ts +0 -4
- package/dist/commands/bundle.js +0 -68
- package/dist/commands/export.d.ts +0 -1
- package/dist/commands/export.js +0 -19
- package/dist/commands/gc.d.ts +0 -3
- package/dist/commands/gc.js +0 -59
- package/dist/commands/hook.d.ts +0 -9
- package/dist/commands/hook.js +0 -84
- package/dist/commands/importBundle.d.ts +0 -4
- package/dist/commands/importBundle.js +0 -80
- package/dist/commands/network.d.ts +0 -1
- package/dist/commands/network.js +0 -91
- package/dist/commands/scan.d.ts +0 -13
- package/dist/commands/scan.js +0 -97
- package/dist/export/renderers/DeltaRenderer.d.ts +0 -6
- package/dist/export/renderers/DeltaRenderer.js +0 -22
- package/dist/hook/preToolUse.d.ts +0 -30
- package/dist/hook/preToolUse.js +0 -81
- package/dist/hook-entry.d.ts +0 -2
- package/dist/hook-entry.js +0 -8
- package/dist/hook.d.ts +0 -1
- package/dist/hook.js +0 -337
- package/dist/mcp/BudgetGuard.d.ts +0 -13
- package/dist/mcp/BudgetGuard.js +0 -55
- package/dist/mcp/SessionScope.d.ts +0 -26
- package/dist/mcp/SessionScope.js +0 -56
- package/dist/mcp/tools/apply_patch.d.ts +0 -37
- package/dist/mcp/tools/apply_patch.js +0 -103
- package/dist/mcp/tools/explainSpecialistScope.d.ts +0 -15
- package/dist/mcp/tools/explainSpecialistScope.js +0 -30
- package/dist/mcp/tools/get_call_chain.d.ts +0 -42
- package/dist/mcp/tools/get_call_chain.js +0 -50
- package/dist/mcp/tools/get_file_skeleton.d.ts +0 -23
- package/dist/mcp/tools/get_file_skeleton.js +0 -124
- package/dist/mcp/tools/hydration/get_evidence_slice.d.ts +0 -31
- package/dist/mcp/tools/hydration/get_evidence_slice.js +0 -57
- package/dist/mcp/tools/hydration/get_project_summary.d.ts +0 -23
- package/dist/mcp/tools/hydration/get_project_summary.js +0 -58
- package/dist/mcp/tools/hydration/get_start_here.d.ts +0 -23
- package/dist/mcp/tools/hydration/get_start_here.js +0 -52
- package/dist/mcp/tools/prepareTaskContext.d.ts +0 -38
- package/dist/mcp/tools/prepareTaskContext.js +0 -93
- package/dist/mcp/tools/read_file.d.ts +0 -31
- package/dist/mcp/tools/read_file.js +0 -90
- package/dist/mcp/tools/set_session_scope.d.ts +0 -19
- package/dist/mcp/tools/set_session_scope.js +0 -40
- package/dist/mcp/tools/submit_receipt.d.ts +0 -68
- package/dist/mcp/tools/submit_receipt.js +0 -94
- package/dist/mcp/tools/work_orders.d.ts +0 -79
- package/dist/mcp/tools/work_orders.js +0 -126
- package/dist/mcp/tools/yield_for_scope_expansion.d.ts +0 -29
- package/dist/mcp/tools/yield_for_scope_expansion.js +0 -59
- package/dist/store/BlobStore.d.ts +0 -22
- package/dist/store/BlobStore.js +0 -96
- package/dist/store/PointerStore.d.ts +0 -52
- package/dist/store/PointerStore.js +0 -154
package/README.md
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<strong>◈
|
|
3
|
-
<
|
|
4
|
-
<em>Map architectural DNA and behavioral call-chains in complex codebases.</em>
|
|
2
|
+
<strong>◈ vibesplain</strong><br/>
|
|
3
|
+
<em>Give your AI agent an architectural map of any codebase.</em>
|
|
5
4
|
</p>
|
|
6
5
|
|
|
7
6
|
<p align="center">
|
|
8
7
|
<a href="#install">Install</a> ·
|
|
9
|
-
<a href="#
|
|
8
|
+
<a href="#usage">Usage</a> ·
|
|
10
9
|
<a href="#mcp-tools">MCP Tools</a> ·
|
|
11
|
-
<a href="#
|
|
10
|
+
<a href="#how-it-works">How It Works</a> ·
|
|
12
11
|
<a href="#development">Development</a>
|
|
13
12
|
</p>
|
|
14
13
|
|
|
15
14
|
---
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
**vibesplain** is an MCP server that scans a codebase with Tree-Sitter, scores every file by importance and complexity, and produces an **Architectural Dossier** — a structured set of decision cards your agent writes after analyzing the code.
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
The output is a self-contained HTML file you can open in any browser.
|
|
20
19
|
|
|
21
|
-
**Zero LLM calls. Zero API keys. Pure static analysis.**
|
|
20
|
+
**Zero LLM calls. Zero API keys. Zero network. Pure static analysis.**
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
---
|
|
24
23
|
|
|
25
24
|
## Install
|
|
26
25
|
|
|
@@ -28,215 +27,168 @@ Your coding agent does all the thinking — vibe-splain just gives it the right
|
|
|
28
27
|
npx vibe-splain install
|
|
29
28
|
```
|
|
30
29
|
|
|
31
|
-
|
|
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.
|
|
30
|
+
This patches your coding agent's config to register vibesplain as an MCP server. Restart your agent afterward.
|
|
39
31
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
### Supported Agents
|
|
46
|
-
|
|
47
|
-
| Agent | Config File |
|
|
48
|
-
|-------|------------|
|
|
49
|
-
| Claude Code | `~/.claude/claude_desktop_config.json` |
|
|
32
|
+
| Agent | Config patched |
|
|
33
|
+
|-------|---------------|
|
|
34
|
+
| Claude Code | `~/.claude/settings.json` |
|
|
35
|
+
| Claude Desktop | `~/.claude/claude_desktop_config.json` |
|
|
50
36
|
| Gemini CLI | `~/.gemini/settings.json` |
|
|
51
37
|
| Cursor | `~/.cursor/mcp.json` |
|
|
52
38
|
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
|
|
53
39
|
|
|
54
|
-
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
55
43
|
|
|
44
|
+
Once installed, open a project in your agent and run the built-in prompt:
|
|
45
|
+
|
|
46
|
+
**Claude Code / Gemini CLI:**
|
|
56
47
|
```
|
|
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
|
-
└─────────────────────────────────────────────────────────┘
|
|
48
|
+
/prompt build_dossier
|
|
71
49
|
```
|
|
72
50
|
|
|
73
|
-
|
|
74
|
-
1. **Level 0 — Semantic Classification**: Maps files to architectural "pillars" (Auth, Payments, Database, etc.) using import-path heuristics and library signatures.
|
|
75
|
-
2. **Level 1 — Structural and Behavioral Gravity**: Core computes `staticGravity` using Tree-Sitter AST analysis based on link density, nesting depth, and mutation counts. **Bundled Adapters** (like Cal.com and n8n) interpret product semantics to add `behavioralLift` for critical dynamic execution paths. Final `gravity` is the combination of static evidence and adapter interpretation.
|
|
76
|
-
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).
|
|
51
|
+
**Cursor / Windsurf:** Select the `build_dossier` prompt from the vibesplain MCP panel.
|
|
77
52
|
|
|
78
|
-
|
|
53
|
+
Your agent will:
|
|
54
|
+
1. Scan the project and rank files by architectural importance
|
|
55
|
+
2. Read each high-importance file in depth
|
|
56
|
+
3. Write a **Decision Card** for each — thesis, blast radius, evidence, optional diagram
|
|
57
|
+
4. Return a `file://` link to the Dossier UI
|
|
79
58
|
|
|
80
|
-
|
|
59
|
+
Open the link. Done.
|
|
81
60
|
|
|
82
|
-
|
|
83
|
-
- **Hybrid Blast-Radius Logic**: Combines static gravity and substance-based direct dependents to protect important files (e.g. dynamically-loaded execution nodes or central utility modules) even if they lack standard entrypoints.
|
|
84
|
-
- **Developer Friction Suppression**: Automatically demotes generated, minified, or vendored files to a `low` blast-radius, ensuring the agent is never blocked when editing build targets or lockfiles.
|
|
85
|
-
- **Warn-Once session behavior**: If `.vibe-splainer/gate.json` is missing, the hook notifies you exactly once per session to run a project scan, then gracefully gets out of the way.
|
|
61
|
+
---
|
|
86
62
|
|
|
87
63
|
## MCP Tools
|
|
88
64
|
|
|
89
|
-
|
|
65
|
+
The `build_dossier` prompt orchestrates these tools automatically. You can also call them manually.
|
|
66
|
+
|
|
67
|
+
| Tool | What it does |
|
|
68
|
+
|------|-------------|
|
|
69
|
+
| `scan_project` | Scans the repo. Returns ranked files grouped by architectural pillar. Call this first. |
|
|
70
|
+
| `get_project_map` | Returns the pillar map, top-gravity files, and wild-discovery candidates. |
|
|
71
|
+
| `set_project_brief` | Stores a 3–5 sentence project summary. Write this before any decision cards. |
|
|
72
|
+
| `get_file_context` | Returns full source + import graph + hot spans for a specific file. |
|
|
73
|
+
| `write_decision_card` | Persists a decision card (thesis, category, evidence, optional Mermaid diagram). |
|
|
74
|
+
| `get_strategic_overview` | Returns dossier state without evidence snippets — useful for quick status checks. |
|
|
75
|
+
| `inspect_pillar` | Returns all decision cards for a given architectural pillar. |
|
|
76
|
+
| `get_wild_discoveries` | Returns the highest-complexity files that don't fit standard patterns. |
|
|
77
|
+
| `mark_stale` | Marks a file's decision card as stale after you edit it. |
|
|
90
78
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
| `get_file_context` | Returns full source + import graph neighbors for a specific file. |
|
|
95
|
-
| `write_decision_card` | Persists a Decision Card (narrative + evidence + optional Mermaid diagram). |
|
|
96
|
-
| `get_strategic_overview` | Returns dossier state without evidence snippets (saves tokens). |
|
|
97
|
-
| `inspect_pillar` | Returns all Decision Cards for a pillar with full evidence. |
|
|
98
|
-
| `get_wild_discoveries` | Returns the most complex files that don't fit standard patterns. |
|
|
99
|
-
| `mark_stale` | Marks cards as stale when you modify files during a session. |
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## How It Works
|
|
100
82
|
|
|
101
|
-
|
|
83
|
+
vibesplain runs a deterministic static analysis pipeline — no model calls, no network:
|
|
102
84
|
|
|
103
85
|
```
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
86
|
+
Your project
|
|
87
|
+
│
|
|
88
|
+
▼
|
|
89
|
+
Tree-Sitter AST parsing
|
|
90
|
+
│
|
|
91
|
+
├── Gravity score (0–100)
|
|
92
|
+
│ PageRank centrality × fan-in × cyclomatic complexity
|
|
93
|
+
│ × public surface × nesting depth
|
|
94
|
+
│
|
|
95
|
+
├── Heat score (0–100)
|
|
96
|
+
│ Smell density: swallowed catches, deep nesting,
|
|
97
|
+
│ long functions, god files, magic numbers
|
|
98
|
+
│
|
|
99
|
+
└── Pillar detection
|
|
100
|
+
Auth / Database / Payments / Queue /
|
|
101
|
+
Storage / Config / Email / Realtime / Logic
|
|
102
|
+
│
|
|
103
|
+
▼
|
|
104
|
+
.vibesplain/
|
|
105
|
+
├── dossier.json ← agent writes decision cards here
|
|
106
|
+
├── analysis.json ← raw scored file store
|
|
107
|
+
├── graph.json ← import graph
|
|
108
|
+
└── ui/index.html ← self-contained dossier viewer
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
**Top 12 by gravity** = Start Here (the load-bearing hubs).
|
|
112
|
+
**Top 12 by heat** = Wild Discoveries (the most complex / smelly files).
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
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.
|
|
114
|
+
The UI is a single self-contained HTML file. Open it with `file://` — no server needed.
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
vibe-splain uses specialized rulesets to understand framework-specific semantics. Current optimizations include:
|
|
118
|
-
- **Next.js**: Server Actions, `cookies()`, `headers()`, and App Router conventions.
|
|
119
|
-
- **Database**: Prisma model mutations and raw query patterns.
|
|
120
|
-
- **API**: tRPC procedure calls (`mutate`/`query`) and standard `fetch`/`axios` patterns.
|
|
121
|
-
- **Auth**: Clerk, NextAuth, and custom rate-limiting/validation logic.
|
|
116
|
+
---
|
|
122
117
|
|
|
123
118
|
## Dossier UI
|
|
124
119
|
|
|
125
|
-
After your agent writes
|
|
120
|
+
After your agent writes decision cards, open the generated file:
|
|
126
121
|
|
|
127
122
|
```
|
|
128
|
-
file:///path/to/your/project/.
|
|
123
|
+
file:///path/to/your/project/.vibesplain/ui/index.html
|
|
129
124
|
```
|
|
130
125
|
|
|
131
|
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
|
|
138
|
-
|
|
126
|
+
Features:
|
|
127
|
+
- Pillar tabs to navigate architectural areas
|
|
128
|
+
- Decision cards with fresh/stale status
|
|
129
|
+
- Mermaid diagrams rendered inline
|
|
130
|
+
- Syntax-highlighted evidence snippets
|
|
131
|
+
- Works fully offline from `file://`
|
|
132
|
+
|
|
133
|
+
---
|
|
139
134
|
|
|
140
135
|
## Architecture
|
|
141
136
|
|
|
142
137
|
```
|
|
143
138
|
packages/
|
|
144
|
-
├── brain/
|
|
139
|
+
├── brain/ # @vibesplain/brain — pure static analysis, no I/O side effects
|
|
145
140
|
│ └── src/
|
|
146
|
-
│ ├── pipeline/
|
|
147
|
-
│ │ └── adapters/
|
|
148
|
-
│ ├── scanner.ts
|
|
149
|
-
│ ├── dossier.ts
|
|
150
|
-
│ ├──
|
|
151
|
-
│ └──
|
|
152
|
-
|
|
141
|
+
│ ├── pipeline/ # 13-stage analysis pipeline
|
|
142
|
+
│ │ └── adapters/ # Optional domain-adapter extension point (none bundled)
|
|
143
|
+
│ ├── scanner.ts # Tree-Sitter AST + gravity/heat scoring
|
|
144
|
+
│ ├── dossier.ts # Atomic dossier persistence + UI injection
|
|
145
|
+
│ ├── analysis.ts # Scored file store + validation report
|
|
146
|
+
│ └── graph.ts # Import graph read/write
|
|
147
|
+
│
|
|
148
|
+
├── cli/ # vibesplain — MCP server + CLI (the published npm package)
|
|
153
149
|
│ └── src/
|
|
154
|
-
│ ├── index.ts #
|
|
150
|
+
│ ├── index.ts # CLI entrypoint
|
|
155
151
|
│ ├── commands/
|
|
156
|
-
│ │ ├── install.ts #
|
|
157
|
-
│ │ └── serve.ts # MCP server
|
|
152
|
+
│ │ ├── install.ts # Patches agent MCP configs
|
|
153
|
+
│ │ └── serve.ts # Starts the MCP server
|
|
154
|
+
│ ├── export/ # Artifact writing (HTML, Markdown, JSON)
|
|
158
155
|
│ └── mcp/
|
|
159
|
-
│ ├── server.ts #
|
|
160
|
-
│ └── tools/ #
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
├── App.tsx # Main app (reads window.__VIBE_DOSSIER__)
|
|
164
|
-
├── components/ # Header, PillarTabs, DecisionCard, etc.
|
|
165
|
-
└── index.css # Design system
|
|
156
|
+
│ ├── server.ts # MCP tool registration
|
|
157
|
+
│ └── tools/ # One file per tool
|
|
158
|
+
│
|
|
159
|
+
└── ui/ # @vibesplain/ui — React dossier viewer (embedded into cli at build time)
|
|
166
160
|
```
|
|
167
161
|
|
|
168
|
-
|
|
162
|
+
**Build order:** `brain` → `cli` → `ui` → `bundle-ui` (copies `ui/dist/` into `cli/dist/ui/`).
|
|
163
|
+
Only the `cli` package is published to npm.
|
|
169
164
|
|
|
170
|
-
|
|
171
|
-
- **`async-mutex`**: All dossier writes are guarded by a mutex with atomic tmp+rename.
|
|
172
|
-
- **`startOnLoad: false`**: Mermaid is initialized manually — never auto-scans the DOM.
|
|
173
|
-
- **`base: './'`**: Vite builds with relative paths so the UI works from `file://` URLs.
|
|
174
|
-
- **`console.log` banned**: Brain and CLI use only `console.error` to avoid corrupting MCP stdio.
|
|
175
|
-
- **Tree-Sitter WASM**: Loaded from the `tree-sitter-wasms` npm package — no network calls.
|
|
165
|
+
---
|
|
176
166
|
|
|
177
167
|
## Development
|
|
178
168
|
|
|
179
|
-
### Prerequisites
|
|
180
|
-
|
|
181
|
-
- Node.js ≥ 18
|
|
182
|
-
- npm ≥ 9 (for workspaces)
|
|
183
|
-
|
|
184
|
-
### Setup
|
|
185
|
-
|
|
186
169
|
```bash
|
|
187
|
-
git clone https://github.com/abp2204/
|
|
188
|
-
cd
|
|
170
|
+
git clone https://github.com/abp2204/vibesplain.git
|
|
171
|
+
cd vibesplain
|
|
189
172
|
npm install
|
|
173
|
+
npm run build # full build
|
|
174
|
+
npm run dev:ui # Vite dev server for the UI
|
|
175
|
+
npm run test:regression
|
|
190
176
|
```
|
|
191
177
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
```bash
|
|
195
|
-
npm run build
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
This runs in sequence: brain → cli → ui → bundle-ui (copies UI dist into CLI dist).
|
|
199
|
-
|
|
200
|
-
### Dev UI
|
|
201
|
-
|
|
202
|
-
```bash
|
|
203
|
-
npm run dev:ui
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
Starts the Vite dev server for the UI package at `http://localhost:5173`.
|
|
207
|
-
|
|
208
|
-
### Test Install Locally
|
|
178
|
+
Test locally after build:
|
|
209
179
|
|
|
210
180
|
```bash
|
|
211
181
|
node packages/cli/dist/index.js install
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### Test MCP Server
|
|
215
|
-
|
|
216
|
-
```bash
|
|
217
182
|
node packages/cli/dist/index.js serve
|
|
218
183
|
```
|
|
219
184
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
### Publish
|
|
185
|
+
Publish:
|
|
223
186
|
|
|
224
187
|
```bash
|
|
225
188
|
npm run release
|
|
226
189
|
```
|
|
227
190
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
## How the Dossier Stays Fresh
|
|
231
|
-
|
|
232
|
-
When `scan_project` runs, it starts a [Chokidar](https://github.com/paulmillr/chokidar) file watcher. When source files change:
|
|
233
|
-
|
|
234
|
-
1. The watcher detects the change
|
|
235
|
-
2. Matching Decision Cards are marked **stale** (amber badge in UI)
|
|
236
|
-
3. The `stalePaths` array in the dossier tracks which files need re-analysis
|
|
237
|
-
4. Your agent can call `get_strategic_overview` to see what's stale, then re-scan
|
|
238
|
-
|
|
239
|
-
You can also manually mark files stale with `mark_stale` if you modify code during a session.
|
|
191
|
+
---
|
|
240
192
|
|
|
241
193
|
## License
|
|
242
194
|
|
|
@@ -1,6 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Idempotently register the vibe-splain PreToolUse hook in a Claude Code settings object.
|
|
3
|
-
* Pure: takes a config, returns the same object mutated. Safe to run repeatedly.
|
|
4
|
-
*/
|
|
5
|
-
export declare function addPreToolUseHook(config: Record<string, any>, hookPath?: string): Record<string, any>;
|
|
6
1
|
export declare function installCommand(): Promise<void>;
|
package/dist/commands/install.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'fs/promises';
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
|
-
import { join
|
|
3
|
+
import { join } from 'path';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
|
-
import { fileURLToPath } from 'url';
|
|
6
5
|
function expandPath(p) {
|
|
7
6
|
if (p.startsWith('~')) {
|
|
8
7
|
return join(homedir(), p.slice(1));
|
|
@@ -25,48 +24,8 @@ const MCP_ENTRY = {
|
|
|
25
24
|
command: 'npx',
|
|
26
25
|
args: ['-y', 'vibe-splain', 'serve'],
|
|
27
26
|
};
|
|
28
|
-
// Claude Code PreToolUse hook: deterministically gate edits to high-blast-radius
|
|
29
|
-
// files. Hooks are a Claude Code CLI feature (settings.json) — not Desktop/Cursor.
|
|
30
|
-
const HOOK_MATCHER = 'Edit|Write|MultiEdit';
|
|
31
|
-
const DEFAULT_HOOK_COMMAND = 'npx -y vibe-splain hook pretooluse';
|
|
32
|
-
/**
|
|
33
|
-
* Idempotently register the vibe-splain PreToolUse hook in a Claude Code settings object.
|
|
34
|
-
* Pure: takes a config, returns the same object mutated. Safe to run repeatedly.
|
|
35
|
-
*/
|
|
36
|
-
export function addPreToolUseHook(config, hookPath) {
|
|
37
|
-
if (!config.hooks)
|
|
38
|
-
config.hooks = {};
|
|
39
|
-
if (!Array.isArray(config.hooks.PreToolUse))
|
|
40
|
-
config.hooks.PreToolUse = [];
|
|
41
|
-
const hookCommand = hookPath ? `node "${hookPath}"` : DEFAULT_HOOK_COMMAND;
|
|
42
|
-
const already = config.hooks.PreToolUse.some((entry) => Array.isArray(entry?.hooks) &&
|
|
43
|
-
entry.hooks.some((h) => typeof h?.command === 'string' && (h.command.includes('vibe-splain hook pretooluse') || h.command.includes('hook.js'))));
|
|
44
|
-
if (already) {
|
|
45
|
-
// Update existing hook command to the resolved hook.js path
|
|
46
|
-
config.hooks.PreToolUse = config.hooks.PreToolUse.map((entry) => {
|
|
47
|
-
if (Array.isArray(entry?.hooks)) {
|
|
48
|
-
return {
|
|
49
|
-
...entry,
|
|
50
|
-
hooks: entry.hooks.map((h) => {
|
|
51
|
-
if (typeof h?.command === 'string' && (h.command.includes('vibe-splain hook pretooluse') || h.command.includes('hook.js'))) {
|
|
52
|
-
return { ...h, command: hookCommand };
|
|
53
|
-
}
|
|
54
|
-
return h;
|
|
55
|
-
})
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
return entry;
|
|
59
|
-
});
|
|
60
|
-
return config;
|
|
61
|
-
}
|
|
62
|
-
config.hooks.PreToolUse.push({
|
|
63
|
-
matcher: HOOK_MATCHER,
|
|
64
|
-
hooks: [{ type: 'command', command: hookCommand }],
|
|
65
|
-
});
|
|
66
|
-
return config;
|
|
67
|
-
}
|
|
68
27
|
export async function installCommand() {
|
|
69
|
-
console.error('\n🔧
|
|
28
|
+
console.error('\n🔧 vibesplain Installer\n');
|
|
70
29
|
let patchedCount = 0;
|
|
71
30
|
for (const agent of AGENT_CONFIGS) {
|
|
72
31
|
const resolvedPath = expandPath(agent.path);
|
|
@@ -84,20 +43,11 @@ export async function installCommand() {
|
|
|
84
43
|
console.error(` ⚠ Could not parse JSON, skipping`);
|
|
85
44
|
continue;
|
|
86
45
|
}
|
|
87
|
-
// Patch config (idempotent: only write when something actually changes,
|
|
88
|
-
// so existing users still pick up newly-added pieces like the hook).
|
|
89
46
|
const before = JSON.stringify(config);
|
|
90
47
|
if (!config.mcpServers)
|
|
91
48
|
config.mcpServers = {};
|
|
92
|
-
if (!config.mcpServers['
|
|
93
|
-
config.mcpServers['
|
|
94
|
-
// The PreToolUse gate is a Claude Code CLI feature (settings.json hooks).
|
|
95
|
-
// Only register it there; Desktop/Gemini/Cursor don't run these hooks.
|
|
96
|
-
if (agent.name === 'Claude Code CLI') {
|
|
97
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
98
|
-
const hookPath = join(__dirname, '..', 'hook.js');
|
|
99
|
-
addPreToolUseHook(config, hookPath);
|
|
100
|
-
}
|
|
49
|
+
if (!config.mcpServers['vibesplain'])
|
|
50
|
+
config.mcpServers['vibesplain'] = MCP_ENTRY;
|
|
101
51
|
if (JSON.stringify(config) === before) {
|
|
102
52
|
console.error(` ✓ Already configured, skipping`);
|
|
103
53
|
patchedCount++;
|
|
@@ -116,7 +66,7 @@ export async function installCommand() {
|
|
|
116
66
|
console.error(`Add this manually to your agent's MCP config:\n`);
|
|
117
67
|
console.error(JSON.stringify({
|
|
118
68
|
mcpServers: {
|
|
119
|
-
'
|
|
69
|
+
'vibesplain': MCP_ENTRY
|
|
120
70
|
}
|
|
121
71
|
}, null, 2));
|
|
122
72
|
process.exit(1);
|
package/dist/commands/serve.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { startMCPServer } from '../mcp/server.js';
|
|
2
2
|
export async function serveCommand(options) {
|
|
3
|
-
console.error('[
|
|
3
|
+
console.error('[vibesplain] Starting MCP server...');
|
|
4
4
|
await startMCPServer(options);
|
|
5
5
|
// Process stays alive — do NOT call process.exit() here
|
|
6
6
|
}
|
|
@@ -7,9 +7,9 @@ export class ArtifactBundleWriter {
|
|
|
7
7
|
this.projectRoot = projectRoot;
|
|
8
8
|
}
|
|
9
9
|
async writeBundle(artifacts) {
|
|
10
|
-
const outputDir = join(this.projectRoot, '.
|
|
11
|
-
const stagingDir = join(this.projectRoot, '.
|
|
12
|
-
const oldDir = join(this.projectRoot, '.
|
|
10
|
+
const outputDir = join(this.projectRoot, '.vibesplain');
|
|
11
|
+
const stagingDir = join(this.projectRoot, '.vibesplain.tmp');
|
|
12
|
+
const oldDir = join(this.projectRoot, '.vibesplain.old');
|
|
13
13
|
try {
|
|
14
14
|
await rm(stagingDir, { recursive: true, force: true });
|
|
15
15
|
await rm(oldDir, { recursive: true, force: true });
|
|
@@ -1,30 +1,12 @@
|
|
|
1
|
-
import type { Dossier, AnalysisStore, ImportGraph } from '@
|
|
1
|
+
import type { Dossier, AnalysisStore, ImportGraph } from '@vibesplain/brain';
|
|
2
2
|
export interface ExportOptions {
|
|
3
3
|
format?: 'json' | 'html' | 'markdown' | 'delta';
|
|
4
4
|
budget?: number;
|
|
5
5
|
scope?: string;
|
|
6
6
|
}
|
|
7
|
-
export interface ManifestArtifactEntry {
|
|
8
|
-
name: string;
|
|
9
|
-
pointer: string;
|
|
10
|
-
contentHash: string;
|
|
11
|
-
sizeBytes: number;
|
|
12
|
-
indexes?: Record<string, string>;
|
|
13
|
-
hydrators?: string[];
|
|
14
|
-
}
|
|
15
|
-
export interface ScanManifest {
|
|
16
|
-
schemaVersion: '2.0.0';
|
|
17
|
-
scanId: string;
|
|
18
|
-
generatedAt: string;
|
|
19
|
-
projectRoot: string;
|
|
20
|
-
artifacts: ManifestArtifactEntry[];
|
|
21
|
-
}
|
|
22
7
|
export declare class ExportOrchestrator {
|
|
23
8
|
private projectRoot;
|
|
24
9
|
constructor(projectRoot: string);
|
|
25
|
-
writeBundle(dossier: Dossier, options?: ExportOptions, store?: AnalysisStore, graph?: ImportGraph
|
|
26
|
-
scanId: string;
|
|
27
|
-
manifestPointer: string;
|
|
28
|
-
}>;
|
|
10
|
+
writeBundle(dossier: Dossier, options?: ExportOptions, store?: AnalysisStore, graph?: ImportGraph): Promise<void>;
|
|
29
11
|
private buildViewModel;
|
|
30
12
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readAnalysis
|
|
1
|
+
import { readAnalysis } from '@vibesplain/brain';
|
|
2
2
|
import { ArtifactBundleWriter } from './ArtifactBundleWriter.js';
|
|
3
3
|
import { JsonRenderer } from './renderers/JsonRenderer.js';
|
|
4
4
|
import { HtmlRenderer } from './renderers/HtmlRenderer.js';
|
|
@@ -6,15 +6,12 @@ import { AgentMarkdownRenderer } from './renderers/AgentMarkdownRenderer.js';
|
|
|
6
6
|
import { ValidationRenderer } from './renderers/ValidationRenderer.js';
|
|
7
7
|
import { RawAnalysisRenderer } from './renderers/RawAnalysisRenderer.js';
|
|
8
8
|
import { GraphRenderer } from './renderers/GraphRenderer.js';
|
|
9
|
-
import { BlobStore } from '../store/BlobStore.js';
|
|
10
|
-
import { PointerStore } from '../store/PointerStore.js';
|
|
11
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
12
9
|
export class ExportOrchestrator {
|
|
13
10
|
projectRoot;
|
|
14
11
|
constructor(projectRoot) {
|
|
15
12
|
this.projectRoot = projectRoot;
|
|
16
13
|
}
|
|
17
|
-
async writeBundle(dossier, options = {}, store, graph
|
|
14
|
+
async writeBundle(dossier, options = {}, store, graph) {
|
|
18
15
|
const finalStore = store || await readAnalysis(this.projectRoot);
|
|
19
16
|
if (!finalStore) {
|
|
20
17
|
throw new Error('Analysis store not found. Scan the project first.');
|
|
@@ -32,14 +29,7 @@ export class ExportOrchestrator {
|
|
|
32
29
|
if (graph) {
|
|
33
30
|
artifacts.push(...await new GraphRenderer(graph).render(viewModel, finalStore));
|
|
34
31
|
}
|
|
35
|
-
const
|
|
36
|
-
artifacts.push({
|
|
37
|
-
type: 'gate',
|
|
38
|
-
path: 'gate.json',
|
|
39
|
-
content: JSON.stringify(gateIndex, null, 2),
|
|
40
|
-
});
|
|
41
|
-
// Determine additional formats
|
|
42
|
-
const formats = ['html', 'markdown']; // default
|
|
32
|
+
const formats = ['html', 'markdown'];
|
|
43
33
|
if (options.format && options.format !== 'json' && options.format !== 'delta') {
|
|
44
34
|
formats.length = 0;
|
|
45
35
|
formats.push(options.format);
|
|
@@ -52,110 +42,9 @@ export class ExportOrchestrator {
|
|
|
52
42
|
}
|
|
53
43
|
const writer = new ArtifactBundleWriter(this.projectRoot);
|
|
54
44
|
await writer.writeBundle(artifacts);
|
|
55
|
-
// Register artifacts in BlobStore + PointerStore and build manifest
|
|
56
|
-
const effectiveScanId = scanId ?? `scan_${Date.now()}`;
|
|
57
|
-
const blobStore = new BlobStore(this.projectRoot);
|
|
58
|
-
const pointerStore = PointerStore.open(this.projectRoot);
|
|
59
|
-
const now = Date.now();
|
|
60
|
-
const manifestEntries = [];
|
|
61
|
-
for (const artifact of artifacts) {
|
|
62
|
-
const content = typeof artifact.content === 'string'
|
|
63
|
-
? Buffer.from(artifact.content, 'utf8')
|
|
64
|
-
: artifact.content;
|
|
65
|
-
const { contentHash, blobPath } = await blobStore.writeAtomic(content);
|
|
66
|
-
const pointerId = `ptr_${uuidv4().replace(/-/g, '').slice(0, 16)}`;
|
|
67
|
-
await pointerStore.insertPointer({
|
|
68
|
-
pointerId,
|
|
69
|
-
scanId: effectiveScanId,
|
|
70
|
-
artifactName: artifact.type,
|
|
71
|
-
contentHash,
|
|
72
|
-
blobPath,
|
|
73
|
-
schemaVersion: '1.0.0',
|
|
74
|
-
createdAt: now,
|
|
75
|
-
expiresAt: null,
|
|
76
|
-
});
|
|
77
|
-
const entry = {
|
|
78
|
-
name: artifact.type,
|
|
79
|
-
pointer: pointerId,
|
|
80
|
-
contentHash,
|
|
81
|
-
sizeBytes: content.length,
|
|
82
|
-
};
|
|
83
|
-
// Attach hydrator hints for the large analysis artifact
|
|
84
|
-
if (artifact.type === 'analysis') {
|
|
85
|
-
entry.hydrators = ['get_project_summary', 'get_start_here'];
|
|
86
|
-
// Generate analysis.index.json (Start-Here + Top-Heat)
|
|
87
|
-
const analysisIndex = {
|
|
88
|
-
schemaVersion: '1.0.0',
|
|
89
|
-
scanId: effectiveScanId,
|
|
90
|
-
startHere: dossier.map.topGravity.slice(0, 12),
|
|
91
|
-
topHeat: dossier.map.topHeat.slice(0, 12),
|
|
92
|
-
pillarSummary: dossier.map.pillars.map(p => ({
|
|
93
|
-
name: p.name,
|
|
94
|
-
fileCount: p.memberFiles?.length ?? 0,
|
|
95
|
-
})),
|
|
96
|
-
totalFiles: Object.keys(finalStore.files).length,
|
|
97
|
-
realSourceFiles: Object.values(finalStore.files).filter(f => f.isRealSource).length,
|
|
98
|
-
};
|
|
99
|
-
const indexContent = Buffer.from(JSON.stringify(analysisIndex, null, 2), 'utf8');
|
|
100
|
-
const indexWrite = await blobStore.writeAtomic(indexContent);
|
|
101
|
-
const indexPointerId = `ptr_${uuidv4().replace(/-/g, '').slice(0, 16)}`;
|
|
102
|
-
await pointerStore.insertPointer({
|
|
103
|
-
pointerId: indexPointerId,
|
|
104
|
-
scanId: effectiveScanId,
|
|
105
|
-
artifactName: 'analysis.index',
|
|
106
|
-
contentHash: indexWrite.contentHash,
|
|
107
|
-
blobPath: indexWrite.blobPath,
|
|
108
|
-
schemaVersion: '1.0.0',
|
|
109
|
-
createdAt: now,
|
|
110
|
-
expiresAt: null,
|
|
111
|
-
});
|
|
112
|
-
entry.indexes = { startHere: indexPointerId };
|
|
113
|
-
}
|
|
114
|
-
manifestEntries.push(entry);
|
|
115
|
-
}
|
|
116
|
-
// Write and register the scan manifest itself
|
|
117
|
-
const manifest = {
|
|
118
|
-
schemaVersion: '2.0.0',
|
|
119
|
-
scanId: effectiveScanId,
|
|
120
|
-
generatedAt: new Date(now).toISOString(),
|
|
121
|
-
projectRoot: this.projectRoot,
|
|
122
|
-
artifacts: manifestEntries,
|
|
123
|
-
};
|
|
124
|
-
const manifestContent = Buffer.from(JSON.stringify(manifest, null, 2), 'utf8');
|
|
125
|
-
const manifestWrite = await blobStore.writeAtomic(manifestContent);
|
|
126
|
-
const manifestPointerId = `ptr_manifest_${effectiveScanId}`;
|
|
127
|
-
await pointerStore.insertPointer({
|
|
128
|
-
pointerId: manifestPointerId,
|
|
129
|
-
scanId: effectiveScanId,
|
|
130
|
-
artifactName: 'artifact_manifest',
|
|
131
|
-
contentHash: manifestWrite.contentHash,
|
|
132
|
-
blobPath: manifestWrite.blobPath,
|
|
133
|
-
schemaVersion: '2.0.0',
|
|
134
|
-
createdAt: now,
|
|
135
|
-
expiresAt: null,
|
|
136
|
-
});
|
|
137
|
-
return { scanId: effectiveScanId, manifestPointer: manifestPointerId };
|
|
138
45
|
}
|
|
139
|
-
buildViewModel(dossier,
|
|
140
|
-
|
|
141
|
-
for (const file of dossier.map.topGravity) {
|
|
142
|
-
const persisted = store.files[file];
|
|
143
|
-
if (persisted) {
|
|
144
|
-
recommendations[file] = RecommendationEngine.generateRecommendations(persisted);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
for (const file of dossier.map.topHeat) {
|
|
148
|
-
if (!recommendations[file]) {
|
|
149
|
-
const persisted = store.files[file];
|
|
150
|
-
if (persisted) {
|
|
151
|
-
recommendations[file] = RecommendationEngine.generateRecommendations(persisted);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return {
|
|
156
|
-
...dossier,
|
|
157
|
-
recommendations,
|
|
158
|
-
};
|
|
46
|
+
buildViewModel(dossier, _store) {
|
|
47
|
+
return { ...dossier, recommendations: {} };
|
|
159
48
|
}
|
|
160
49
|
}
|
|
161
50
|
//# sourceMappingURL=ExportOrchestrator.js.map
|