vibe-splain 2.7.3 → 3.1.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/dist/commands/export.d.ts +1 -0
- package/dist/commands/export.js +19 -0
- package/dist/commands/serve.d.ts +1 -1
- package/dist/commands/serve.js +2 -2
- package/dist/export/ArtifactBundleWriter.d.ts +22 -0
- package/dist/export/ArtifactBundleWriter.js +74 -0
- package/dist/export/ExportOrchestrator.d.ts +12 -0
- package/dist/export/ExportOrchestrator.js +73 -0
- package/dist/export/Watcher.d.ts +1 -0
- package/dist/export/Watcher.js +55 -0
- package/dist/export/renderers/AgentMarkdownRenderer.d.ts +9 -0
- package/dist/export/renderers/AgentMarkdownRenderer.js +106 -0
- package/dist/export/renderers/DeltaRenderer.d.ts +6 -0
- package/dist/export/renderers/DeltaRenderer.js +22 -0
- package/dist/export/renderers/GraphRenderer.d.ts +9 -0
- package/dist/export/renderers/GraphRenderer.js +18 -0
- package/dist/export/renderers/HtmlRenderer.d.ts +6 -0
- package/dist/export/renderers/HtmlRenderer.js +77 -0
- package/dist/export/renderers/JsonRenderer.d.ts +6 -0
- package/dist/export/renderers/JsonRenderer.js +12 -0
- package/dist/export/renderers/RawAnalysisRenderer.d.ts +6 -0
- package/dist/export/renderers/RawAnalysisRenderer.js +12 -0
- package/dist/export/renderers/Renderer.d.ts +5 -0
- package/dist/export/renderers/Renderer.js +2 -0
- package/dist/export/renderers/ValidationRenderer.d.ts +6 -0
- package/dist/export/renderers/ValidationRenderer.js +14 -0
- package/dist/index.js +888 -660
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +3 -3
- package/dist/mcp/tools/mark_stale.d.ts +1 -1
- package/dist/mcp/tools/mark_stale.js +9 -3
- package/dist/mcp/tools/scan_project.d.ts +1 -1
- package/dist/mcp/tools/scan_project.js +25 -5
- package/dist/mcp/tools/set_project_brief.d.ts +1 -1
- package/dist/mcp/tools/set_project_brief.js +9 -3
- package/dist/mcp/tools/write_decision_card.d.ts +1 -1
- package/dist/mcp/tools/write_decision_card.js +15 -4
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
|
@@ -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
|
package/dist/commands/serve.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function serveCommand(): Promise<void>;
|
|
1
|
+
export declare function serveCommand(options?: any): Promise<void>;
|
package/dist/commands/serve.js
CHANGED
|
@@ -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,74 @@
|
|
|
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
|
+
const oldDir = join(this.projectRoot, '.vibe-splainer.old');
|
|
13
|
+
try {
|
|
14
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
15
|
+
await rm(oldDir, { recursive: true, force: true });
|
|
16
|
+
const { existsSync } = await import('fs');
|
|
17
|
+
const { cp } = await import('fs/promises');
|
|
18
|
+
if (existsSync(outputDir)) {
|
|
19
|
+
await cp(outputDir, stagingDir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
await mkdir(stagingDir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
const manifestArtifacts = [];
|
|
25
|
+
for (const artifact of artifacts) {
|
|
26
|
+
const destPath = join(stagingDir, artifact.path);
|
|
27
|
+
await mkdir(join(destPath, '..'), { recursive: true });
|
|
28
|
+
await writeFile(destPath, artifact.content);
|
|
29
|
+
const contentStr = artifact.content;
|
|
30
|
+
const buffer = typeof contentStr === 'string' ? Buffer.from(contentStr, 'utf-8') : contentStr;
|
|
31
|
+
manifestArtifacts.push({
|
|
32
|
+
type: artifact.type,
|
|
33
|
+
path: artifact.path,
|
|
34
|
+
checksum: 'sha256:' + createHash('sha256').update(buffer).digest('hex'),
|
|
35
|
+
sizeBytes: buffer.length,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const manifest = {
|
|
39
|
+
schemaVersion: '1.0.0',
|
|
40
|
+
generatedAt: new Date().toISOString(),
|
|
41
|
+
projectRoot: this.projectRoot,
|
|
42
|
+
artifacts: manifestArtifacts,
|
|
43
|
+
};
|
|
44
|
+
await writeFile(join(stagingDir, 'artifact_manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
|
|
45
|
+
// Atomic swap pattern:
|
|
46
|
+
// 1. Rename current -> old
|
|
47
|
+
// 2. Rename staging -> current
|
|
48
|
+
// 3. Remove old
|
|
49
|
+
let swapped = false;
|
|
50
|
+
if (existsSync(outputDir)) {
|
|
51
|
+
await rename(outputDir, oldDir);
|
|
52
|
+
swapped = true;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
await rename(stagingDir, outputDir);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
// Rollback if possible
|
|
59
|
+
if (swapped) {
|
|
60
|
+
await rename(oldDir, outputDir);
|
|
61
|
+
}
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
if (swapped) {
|
|
65
|
+
await rm(oldDir, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# 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,73 @@
|
|
|
1
|
+
import { readAnalysis, readActionBindings, 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
|
+
const bindings = await readActionBindings(this.projectRoot);
|
|
21
|
+
// Aggressive Boilerplate Culling
|
|
22
|
+
for (const p of dossier.pillars) {
|
|
23
|
+
p.decisions = p.decisions.filter(c => !(c.severity === 1 && c.category === 'Convention'));
|
|
24
|
+
p.cardCount = p.decisions.length;
|
|
25
|
+
}
|
|
26
|
+
const viewModel = this.buildViewModel(dossier, finalStore);
|
|
27
|
+
// Always render JSON, Delta, Validation, and Raw Analysis.
|
|
28
|
+
const artifacts = [];
|
|
29
|
+
artifacts.push(...await new JsonRenderer().render(viewModel, finalStore));
|
|
30
|
+
artifacts.push(...await new DeltaRenderer().render(viewModel, finalStore));
|
|
31
|
+
artifacts.push(...await new ValidationRenderer().render(viewModel, finalStore));
|
|
32
|
+
artifacts.push(...await new RawAnalysisRenderer().render(viewModel, finalStore));
|
|
33
|
+
if (graph) {
|
|
34
|
+
artifacts.push(...await new GraphRenderer(graph).render(viewModel, finalStore));
|
|
35
|
+
}
|
|
36
|
+
// Determine additional formats
|
|
37
|
+
const formats = ['html', 'markdown']; // default
|
|
38
|
+
if (options.format && options.format !== 'json' && options.format !== 'delta') {
|
|
39
|
+
formats.length = 0;
|
|
40
|
+
formats.push(options.format);
|
|
41
|
+
}
|
|
42
|
+
if (formats.includes('html')) {
|
|
43
|
+
artifacts.push(...await new HtmlRenderer().render(viewModel, finalStore));
|
|
44
|
+
}
|
|
45
|
+
if (formats.includes('markdown')) {
|
|
46
|
+
artifacts.push(...await new AgentMarkdownRenderer(options.budget, bindings).render(viewModel, finalStore));
|
|
47
|
+
}
|
|
48
|
+
const writer = new ArtifactBundleWriter(this.projectRoot);
|
|
49
|
+
await writer.writeBundle(artifacts);
|
|
50
|
+
}
|
|
51
|
+
buildViewModel(dossier, store) {
|
|
52
|
+
const recommendations = {};
|
|
53
|
+
for (const file of dossier.map.topGravity) {
|
|
54
|
+
const persisted = store.files[file];
|
|
55
|
+
if (persisted) {
|
|
56
|
+
recommendations[file] = RecommendationEngine.generateRecommendations(persisted);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const file of dossier.map.topHeat) {
|
|
60
|
+
if (!recommendations[file]) {
|
|
61
|
+
const persisted = store.files[file];
|
|
62
|
+
if (persisted) {
|
|
63
|
+
recommendations[file] = RecommendationEngine.generateRecommendations(persisted);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
...dossier,
|
|
69
|
+
recommendations,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=ExportOrchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function startWatcher(projectRoot: string, watchedPaths: string[]): Promise<void>;
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
const activeWatchers = new Map();
|
|
8
|
+
export async function startWatcher(projectRoot, watchedPaths) {
|
|
9
|
+
// Clean up existing watcher for this project to prevent resource leaks
|
|
10
|
+
const existing = activeWatchers.get(projectRoot);
|
|
11
|
+
if (existing) {
|
|
12
|
+
await existing.close();
|
|
13
|
+
activeWatchers.delete(projectRoot);
|
|
14
|
+
}
|
|
15
|
+
const watcher = chokidar.watch(watchedPaths.length > 0 ? watchedPaths : projectRoot, {
|
|
16
|
+
ignoreInitial: true,
|
|
17
|
+
ignored: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.vibe-splainer/**'],
|
|
18
|
+
persistent: true,
|
|
19
|
+
});
|
|
20
|
+
watcher.on('change', async (filepath) => {
|
|
21
|
+
try {
|
|
22
|
+
const dossier = await readDossier(projectRoot);
|
|
23
|
+
if (!dossier)
|
|
24
|
+
return;
|
|
25
|
+
const content = await readFile(filepath, 'utf8');
|
|
26
|
+
const newHash = createHash('sha256').update(content).digest('hex');
|
|
27
|
+
let mutated = false;
|
|
28
|
+
for (const pillar of dossier.pillars) {
|
|
29
|
+
for (const card of pillar.decisions) {
|
|
30
|
+
if (!card.primaryFile)
|
|
31
|
+
continue;
|
|
32
|
+
const absMatch = filepath === join(projectRoot, card.primaryFile) || filepath.endsWith('/' + card.primaryFile);
|
|
33
|
+
if (absMatch && card.lastScannedHash !== newHash) {
|
|
34
|
+
card.status = 'stale';
|
|
35
|
+
const rel = card.primaryFile;
|
|
36
|
+
if (!dossier.stalePaths.includes(rel))
|
|
37
|
+
dossier.stalePaths.push(rel);
|
|
38
|
+
mutated = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (mutated) {
|
|
43
|
+
const orchestrator = new ExportOrchestrator(projectRoot);
|
|
44
|
+
await orchestrator.writeBundle(dossier);
|
|
45
|
+
console.error(`[vibe-splain] File changed: ${filepath}. Dossier artifacts updated.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
console.error('[vibe-splain] Watcher error:', err);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
activeWatchers.set(projectRoot, watcher);
|
|
53
|
+
console.error('[vibe-splain] File watcher started');
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=Watcher.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
private bindings;
|
|
7
|
+
constructor(budget?: number, bindings?: any | null);
|
|
8
|
+
render(viewModel: DossierViewModel, store: AnalysisStore): Artifact[];
|
|
9
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export class AgentMarkdownRenderer {
|
|
2
|
+
budget;
|
|
3
|
+
bindings;
|
|
4
|
+
constructor(budget = 8000, bindings = null) {
|
|
5
|
+
this.budget = budget;
|
|
6
|
+
this.bindings = bindings;
|
|
7
|
+
}
|
|
8
|
+
render(viewModel, store) {
|
|
9
|
+
let md = `# Architectural Dossier: ${viewModel.projectRoot}\n\n`;
|
|
10
|
+
if (viewModel.map.brief) {
|
|
11
|
+
md += `## Project Brief\n${viewModel.map.brief}\n\n`;
|
|
12
|
+
}
|
|
13
|
+
md += `## Stack & Entrypoints\n`;
|
|
14
|
+
md += `- Stack: ${viewModel.map.stack.join(', ')}\n`;
|
|
15
|
+
md += `- Entrypoints: ${viewModel.map.entrypoints.join(', ')}\n\n`;
|
|
16
|
+
// Flatten decisions
|
|
17
|
+
const allDecisions = viewModel.pillars.flatMap(p => p.decisions).concat(viewModel.wildDiscoveries);
|
|
18
|
+
const uniqueDecisions = new Map();
|
|
19
|
+
for (const d of allDecisions) {
|
|
20
|
+
if (d.primaryFile && !uniqueDecisions.has(d.primaryFile)) {
|
|
21
|
+
uniqueDecisions.set(d.primaryFile, d);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const tier1 = [];
|
|
25
|
+
const tier2 = [];
|
|
26
|
+
const tier3 = [];
|
|
27
|
+
// Sort files by gravity
|
|
28
|
+
const sortedFiles = Object.values(store.files)
|
|
29
|
+
.filter(f => f.isRealSource)
|
|
30
|
+
.sort((a, b) => b.gravity - a.gravity);
|
|
31
|
+
for (const f of sortedFiles) {
|
|
32
|
+
const card = uniqueDecisions.get(f.relativePath);
|
|
33
|
+
const isCritical = card && card.severity >= 4;
|
|
34
|
+
if (f.gravity >= 70 || isCritical) {
|
|
35
|
+
tier1.push(f.relativePath);
|
|
36
|
+
}
|
|
37
|
+
else if (f.gravity >= 40 || card) {
|
|
38
|
+
tier2.push(f.relativePath);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
tier3.push(f.relativePath);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
md += `## Tier 1: Critical Files & Risks\n\n`;
|
|
45
|
+
for (const path of tier1) {
|
|
46
|
+
const f = store.files[path];
|
|
47
|
+
const card = uniqueDecisions.get(path);
|
|
48
|
+
const recs = viewModel.recommendations[path] || [];
|
|
49
|
+
md += `### ${path}\n`;
|
|
50
|
+
md += `- Gravity: ${Math.round(f.gravity)} | Heat: ${Math.round(f.heat)}\n`;
|
|
51
|
+
md += `- Domain: ${f.productDomain} | Role: ${f.frameworkRole}\n`;
|
|
52
|
+
if (card) {
|
|
53
|
+
md += `\n**Verdict**: ${card.thesis}\n`;
|
|
54
|
+
md += `**Severity**: ${card.severity} | **Category**: ${card.category}\n`;
|
|
55
|
+
md += `**Narrative**: ${card.narrative}\n`;
|
|
56
|
+
}
|
|
57
|
+
// Add Function-Level Action Bindings for Tier 1
|
|
58
|
+
if (this.bindings && this.bindings.files[path]) {
|
|
59
|
+
const fileBinding = this.bindings.files[path];
|
|
60
|
+
const criticalFunctions = fileBinding.functions.filter((fn) => fn.semanticActions.length > 0 || fn.isEntrypoint);
|
|
61
|
+
if (criticalFunctions.length > 0) {
|
|
62
|
+
md += `\n**Critical Functions**:\n`;
|
|
63
|
+
for (const fn of criticalFunctions) {
|
|
64
|
+
md += `- \`${fn.displayName}\` (lines ${fn.startLine}-${fn.endLine})${fn.isEntrypoint ? ' [Entrypoint]' : ''}\n`;
|
|
65
|
+
for (const action of fn.semanticActions) {
|
|
66
|
+
md += ` - **${action.actionKind}**${action.targetModel ? ` on ${action.targetModel}` : ''}: \`${action.calleeText}\` (line ${action.sourceLine})\n`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (recs.length > 0) {
|
|
72
|
+
md += `\n**Safe Patch Strategies**:\n`;
|
|
73
|
+
for (const r of recs) {
|
|
74
|
+
md += `- **${r.strategy}**: ${r.description}\n`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
md += `\n---\n\n`;
|
|
78
|
+
}
|
|
79
|
+
md += `## Tier 2: Important Files\n\n`;
|
|
80
|
+
for (const path of tier2) {
|
|
81
|
+
const f = store.files[path];
|
|
82
|
+
const card = uniqueDecisions.get(path);
|
|
83
|
+
md += `- **${path}** (Gravity: ${Math.round(f.gravity)})`;
|
|
84
|
+
if (card) {
|
|
85
|
+
md += ` — ${card.thesis}`;
|
|
86
|
+
}
|
|
87
|
+
md += `\n`;
|
|
88
|
+
}
|
|
89
|
+
md += `\n`;
|
|
90
|
+
md += `## Tier 3: Index\n\n`;
|
|
91
|
+
for (const path of tier3) {
|
|
92
|
+
const f = store.files[path];
|
|
93
|
+
md += `- ${path} (Gravity: ${Math.round(f.gravity)})\n`;
|
|
94
|
+
}
|
|
95
|
+
// In a real robust implementation, we would truncate tiers starting from Tier 3 to fit the budget.
|
|
96
|
+
// Given the simplicity, we'll return the full markdown.
|
|
97
|
+
return [
|
|
98
|
+
{
|
|
99
|
+
type: 'markdown',
|
|
100
|
+
path: 'dossier.agent.md',
|
|
101
|
+
content: md,
|
|
102
|
+
}
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# 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
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { join, dirname, relative } from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
function getAllFiles(dirPath, arrayOfFiles = []) {
|
|
6
|
+
const files = readdirSync(dirPath);
|
|
7
|
+
files.forEach(function (file) {
|
|
8
|
+
const fullPath = join(dirPath, file);
|
|
9
|
+
if (statSync(fullPath).isDirectory()) {
|
|
10
|
+
arrayOfFiles = getAllFiles(fullPath, arrayOfFiles);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
arrayOfFiles.push(fullPath);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
return arrayOfFiles;
|
|
17
|
+
}
|
|
18
|
+
export class HtmlRenderer {
|
|
19
|
+
render(viewModel, _store) {
|
|
20
|
+
// Robust template resolution for bundled/unbundled environments
|
|
21
|
+
const candidatePaths = [
|
|
22
|
+
join(__dirname, 'ui'), // bundled: dist/index.js -> dist/ui
|
|
23
|
+
join(__dirname, '..', '..', 'ui'), // unbundled: dist/export/renderers -> dist/ui
|
|
24
|
+
join(__dirname, '..', 'ui'), // alt bundle
|
|
25
|
+
join(__dirname, '..', '..', '..', 'ui', 'dist'), // dev: packages/cli/src/export/renderers -> packages/ui/dist
|
|
26
|
+
join(__dirname, '..', '..', 'packages', 'ui', 'dist'), // repo root -> packages/ui/dist
|
|
27
|
+
];
|
|
28
|
+
let templateDir = '';
|
|
29
|
+
for (const p of candidatePaths) {
|
|
30
|
+
if (existsSync(p) && existsSync(join(p, 'index.html'))) {
|
|
31
|
+
// Double check it's not the source packages/ui (which has vite.config.ts)
|
|
32
|
+
// We want the BUILT UI in the dist folder.
|
|
33
|
+
if (!existsSync(join(p, 'vite.config.ts')) || p.endsWith('dist')) {
|
|
34
|
+
templateDir = p;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Fallback to any 'ui' folder that has index.html as a last resort
|
|
40
|
+
if (!templateDir) {
|
|
41
|
+
for (const p of candidatePaths) {
|
|
42
|
+
if (existsSync(join(p, 'index.html'))) {
|
|
43
|
+
templateDir = p;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!templateDir) {
|
|
49
|
+
console.error('[vibe-splain] UI template not found. Checked:', candidatePaths);
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
const artifacts = [];
|
|
53
|
+
const allFiles = getAllFiles(templateDir);
|
|
54
|
+
for (const file of allFiles) {
|
|
55
|
+
const relPath = relative(templateDir, file);
|
|
56
|
+
if (relPath === 'index.html') {
|
|
57
|
+
const templateHtml = readFileSync(file, 'utf8');
|
|
58
|
+
const injection = `<script>window.__VIBE_DOSSIER__ = ${JSON.stringify(viewModel)};</script>`;
|
|
59
|
+
const bakedHtml = templateHtml.replace('<!-- VIBE_DOSSIER_INJECTION_POINT -->', injection);
|
|
60
|
+
artifacts.push({
|
|
61
|
+
type: 'html',
|
|
62
|
+
path: join('ui', relPath),
|
|
63
|
+
content: bakedHtml,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
artifacts.push({
|
|
68
|
+
type: 'asset',
|
|
69
|
+
path: join('ui', relPath),
|
|
70
|
+
content: readFileSync(file),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return artifacts;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=HtmlRenderer.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 JsonRenderer implements Renderer {
|
|
5
|
+
render(viewModel: DossierViewModel, _store: AnalysisStore): Artifact[];
|
|
6
|
+
}
|
|
@@ -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 RawAnalysisRenderer implements Renderer {
|
|
5
|
+
render(_viewModel: DossierViewModel, store: AnalysisStore): Artifact[];
|
|
6
|
+
}
|
|
@@ -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 ValidationRenderer implements Renderer {
|
|
5
|
+
render(viewModel: DossierViewModel, _store: AnalysisStore): Artifact[];
|
|
6
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class ValidationRenderer {
|
|
2
|
+
render(viewModel, _store) {
|
|
3
|
+
if (!viewModel.map.validation)
|
|
4
|
+
return [];
|
|
5
|
+
return [
|
|
6
|
+
{
|
|
7
|
+
type: 'validation',
|
|
8
|
+
path: 'validation_report.json',
|
|
9
|
+
content: JSON.stringify(viewModel.map.validation, null, 2),
|
|
10
|
+
}
|
|
11
|
+
];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=ValidationRenderer.js.map
|