vibe-splain 3.0.0 → 3.2.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/bundle.d.ts +4 -0
- package/dist/commands/bundle.js +68 -0
- package/dist/commands/gc.d.ts +3 -0
- package/dist/commands/gc.js +59 -0
- package/dist/commands/importBundle.d.ts +4 -0
- package/dist/commands/importBundle.js +80 -0
- package/dist/export/ArtifactBundleWriter.js +24 -6
- package/dist/export/ExportOrchestrator.d.ts +19 -1
- package/dist/export/ExportOrchestrator.js +90 -3
- package/dist/export/Watcher.d.ts +1 -1
- package/dist/export/Watcher.js +9 -1
- package/dist/export/renderers/AgentMarkdownRenderer.d.ts +2 -1
- package/dist/export/renderers/AgentMarkdownRenderer.js +17 -1
- package/dist/export/renderers/HtmlRenderer.js +29 -6
- package/dist/index.js +1671 -129
- package/dist/mcp/BudgetGuard.d.ts +13 -0
- package/dist/mcp/BudgetGuard.js +55 -0
- package/dist/mcp/SessionScope.d.ts +26 -0
- package/dist/mcp/SessionScope.js +56 -0
- package/dist/mcp/server.js +38 -0
- package/dist/mcp/tools/apply_patch.d.ts +37 -0
- package/dist/mcp/tools/apply_patch.js +103 -0
- package/dist/mcp/tools/get_file_skeleton.d.ts +23 -0
- package/dist/mcp/tools/get_file_skeleton.js +124 -0
- package/dist/mcp/tools/hydration/get_evidence_slice.d.ts +31 -0
- package/dist/mcp/tools/hydration/get_evidence_slice.js +59 -0
- package/dist/mcp/tools/hydration/get_project_summary.d.ts +23 -0
- package/dist/mcp/tools/hydration/get_project_summary.js +58 -0
- package/dist/mcp/tools/hydration/get_start_here.d.ts +23 -0
- package/dist/mcp/tools/hydration/get_start_here.js +52 -0
- package/dist/mcp/tools/read_file.d.ts +31 -0
- package/dist/mcp/tools/read_file.js +90 -0
- package/dist/mcp/tools/scan_project.js +6 -3
- package/dist/mcp/tools/set_session_scope.d.ts +19 -0
- package/dist/mcp/tools/set_session_scope.js +40 -0
- package/dist/mcp/tools/submit_receipt.d.ts +68 -0
- package/dist/mcp/tools/submit_receipt.js +94 -0
- package/dist/mcp/tools/work_orders.d.ts +79 -0
- package/dist/mcp/tools/work_orders.js +126 -0
- package/dist/mcp/tools/yield_for_scope_expansion.d.ts +29 -0
- package/dist/mcp/tools/yield_for_scope_expansion.js +59 -0
- package/dist/store/BlobStore.d.ts +22 -0
- package/dist/store/BlobStore.js +96 -0
- package/dist/store/PointerStore.d.ts +52 -0
- package/dist/store/PointerStore.js +138 -0
- package/package.json +8 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { writeFile, mkdir, copyFile, rm } from 'fs/promises';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import * as tar from 'tar';
|
|
5
|
+
import { PointerStore } from '../store/PointerStore.js';
|
|
6
|
+
import { BlobStore } from '../store/BlobStore.js';
|
|
7
|
+
export async function bundleCommand(scanId, opts = {}) {
|
|
8
|
+
const root = opts.projectRoot ?? process.cwd();
|
|
9
|
+
const outputPath = opts.output ?? join(root, `vibe-bundle-${scanId}.tar.gz`);
|
|
10
|
+
console.error(`[vibe-splain bundle] Bundling scan ${scanId} from ${root}`);
|
|
11
|
+
const pointerStore = PointerStore.open(root);
|
|
12
|
+
const blobStore = new BlobStore(root);
|
|
13
|
+
const pointers = pointerStore.listPointersByScan(scanId);
|
|
14
|
+
if (pointers.length === 0) {
|
|
15
|
+
throw new Error(`No pointers found for scanId "${scanId}"`);
|
|
16
|
+
}
|
|
17
|
+
// Stage bundle into a temp directory with predictable layout
|
|
18
|
+
const stagingDir = join(root, '.vibe-splainer', 'tmp', `bundle-stage-${scanId}`);
|
|
19
|
+
const blobsStageDir = join(stagingDir, 'blobs');
|
|
20
|
+
await mkdir(blobsStageDir, { recursive: true });
|
|
21
|
+
try {
|
|
22
|
+
// Build manifest for the bundle
|
|
23
|
+
const bundleManifest = {
|
|
24
|
+
schemaVersion: '1.0.0',
|
|
25
|
+
scanId,
|
|
26
|
+
exportedAt: new Date().toISOString(),
|
|
27
|
+
projectRoot: root,
|
|
28
|
+
pointers: pointers.map(p => ({
|
|
29
|
+
pointerId: p.pointerId,
|
|
30
|
+
scanId: p.scanId,
|
|
31
|
+
artifactName: p.artifactName,
|
|
32
|
+
contentHash: p.contentHash,
|
|
33
|
+
blobFile: `blobs/${p.contentHash.replace('sha256:', 'sha256_')}`,
|
|
34
|
+
schemaVersion: p.schemaVersion,
|
|
35
|
+
createdAt: p.createdAt,
|
|
36
|
+
expiresAt: p.expiresAt,
|
|
37
|
+
})),
|
|
38
|
+
};
|
|
39
|
+
await writeFile(join(stagingDir, 'bundle-manifest.json'), JSON.stringify(bundleManifest, null, 2), 'utf8');
|
|
40
|
+
// Copy blobs (deduplicated by contentHash)
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
for (const p of pointers) {
|
|
43
|
+
const hex = p.contentHash.replace('sha256:', '');
|
|
44
|
+
if (seen.has(hex))
|
|
45
|
+
continue;
|
|
46
|
+
seen.add(hex);
|
|
47
|
+
const srcPath = p.blobPath;
|
|
48
|
+
if (!existsSync(srcPath)) {
|
|
49
|
+
console.error(`[vibe-splain bundle] Warning: blob missing for ${p.pointerId}: ${srcPath}`);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
await copyFile(srcPath, join(blobsStageDir, `sha256_${hex}`));
|
|
53
|
+
}
|
|
54
|
+
// Create tarball from staging directory
|
|
55
|
+
await tar.create({
|
|
56
|
+
gzip: true,
|
|
57
|
+
file: outputPath,
|
|
58
|
+
cwd: stagingDir,
|
|
59
|
+
portable: true,
|
|
60
|
+
}, ['.']);
|
|
61
|
+
console.error(`[vibe-splain bundle] Bundle written: ${outputPath}`);
|
|
62
|
+
console.error(`[vibe-splain bundle] ${pointers.length} pointers, ${seen.size} blobs`);
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=bundle.js.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { rm, readdir } from 'fs/promises';
|
|
3
|
+
import { PointerStore } from '../store/PointerStore.js';
|
|
4
|
+
import { BlobStore } from '../store/BlobStore.js';
|
|
5
|
+
const DEFAULT_KEEP_SCANS = 3;
|
|
6
|
+
export async function gcCommand(projectRoot, opts = {}) {
|
|
7
|
+
const root = projectRoot ?? process.cwd();
|
|
8
|
+
const keepScans = opts.keepScans ?? DEFAULT_KEEP_SCANS;
|
|
9
|
+
console.error(`[vibe-splain gc] Running GC on ${root} (keeping last ${keepScans} scans)`);
|
|
10
|
+
const pointerStore = PointerStore.open(root);
|
|
11
|
+
const blobStore = new BlobStore(root);
|
|
12
|
+
// 1. Get all scan IDs ordered by createdAt desc
|
|
13
|
+
const allScanIds = pointerStore.listAllScanIds();
|
|
14
|
+
console.error(`[vibe-splain gc] Found ${allScanIds.length} scans`);
|
|
15
|
+
// Keep the N most recent by taking last N from sorted list
|
|
16
|
+
// Scan IDs contain timestamps, sort lexicographically descending
|
|
17
|
+
const sorted = [...allScanIds].sort().reverse();
|
|
18
|
+
const keepIds = sorted.slice(0, keepScans);
|
|
19
|
+
const deleteIds = sorted.slice(keepScans);
|
|
20
|
+
if (deleteIds.length === 0) {
|
|
21
|
+
console.error('[vibe-splain gc] Nothing to collect');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// 2. Collect all blob paths still referenced by kept pointers before deletion
|
|
25
|
+
const keptPointers = keepIds.flatMap(id => pointerStore.listPointersByScan(id));
|
|
26
|
+
const referencedBlobs = new Set(keptPointers.map(p => p.blobPath));
|
|
27
|
+
// 3. Delete old scan pointers
|
|
28
|
+
const deleted = await pointerStore.gcScanPointers(keepIds);
|
|
29
|
+
console.error(`[vibe-splain gc] Deleted ${deleted} pointer rows`);
|
|
30
|
+
// 4. Delete unreferenced blobs (reference count = 0)
|
|
31
|
+
const allBlobs = await blobStore.listBlobPaths();
|
|
32
|
+
let blobsDeleted = 0;
|
|
33
|
+
for (const blobPath of allBlobs) {
|
|
34
|
+
if (!referencedBlobs.has(blobPath)) {
|
|
35
|
+
try {
|
|
36
|
+
await rm(blobPath);
|
|
37
|
+
blobsDeleted++;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// ignore — may have already been deleted
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
console.error(`[vibe-splain gc] Deleted ${blobsDeleted} unreferenced blobs`);
|
|
45
|
+
// 5. Clean up tmp dir
|
|
46
|
+
const tmpDir = join(root, '.vibe-splainer', 'tmp');
|
|
47
|
+
try {
|
|
48
|
+
const tmpFiles = await readdir(tmpDir);
|
|
49
|
+
for (const f of tmpFiles) {
|
|
50
|
+
await rm(join(tmpDir, f), { force: true });
|
|
51
|
+
}
|
|
52
|
+
console.error(`[vibe-splain gc] Cleaned ${tmpFiles.length} tmp files`);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// tmp dir may not exist
|
|
56
|
+
}
|
|
57
|
+
console.error('[vibe-splain gc] Done');
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=gc.js.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { readFile, mkdir, rm } from 'fs/promises';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import * as tar from 'tar';
|
|
5
|
+
import { createHash } from 'crypto';
|
|
6
|
+
import { BlobStore } from '../store/BlobStore.js';
|
|
7
|
+
import { PointerStore } from '../store/PointerStore.js';
|
|
8
|
+
export async function importBundleCommand(tarballPath, opts = {}) {
|
|
9
|
+
const root = opts.projectRoot ?? process.cwd();
|
|
10
|
+
const namespace = opts.namespace ?? `imported_${Date.now()}`;
|
|
11
|
+
console.error(`[vibe-splain import] Importing ${tarballPath} into ${root} (namespace: ${namespace})`);
|
|
12
|
+
if (!existsSync(tarballPath)) {
|
|
13
|
+
throw new Error(`Tarball not found: ${tarballPath}`);
|
|
14
|
+
}
|
|
15
|
+
// Extract to a temp directory
|
|
16
|
+
const extractDir = join(root, '.vibe-splainer', 'tmp', `import-${namespace}`);
|
|
17
|
+
await mkdir(extractDir, { recursive: true });
|
|
18
|
+
try {
|
|
19
|
+
await tar.extract({
|
|
20
|
+
file: tarballPath,
|
|
21
|
+
cwd: extractDir,
|
|
22
|
+
});
|
|
23
|
+
// Read bundle manifest
|
|
24
|
+
const manifestPath = join(extractDir, 'bundle-manifest.json');
|
|
25
|
+
if (!existsSync(manifestPath)) {
|
|
26
|
+
throw new Error('Invalid bundle: missing bundle-manifest.json');
|
|
27
|
+
}
|
|
28
|
+
const manifestRaw = await readFile(manifestPath, 'utf8');
|
|
29
|
+
const manifest = JSON.parse(manifestRaw);
|
|
30
|
+
if (manifest.schemaVersion !== '1.0.0') {
|
|
31
|
+
throw new Error(`Unsupported bundle schema version: ${manifest.schemaVersion}`);
|
|
32
|
+
}
|
|
33
|
+
const blobStore = new BlobStore(root);
|
|
34
|
+
const pointerStore = PointerStore.open(root);
|
|
35
|
+
await blobStore.ensureDirs();
|
|
36
|
+
let imported = 0;
|
|
37
|
+
let hashErrors = 0;
|
|
38
|
+
for (const entry of manifest.pointers) {
|
|
39
|
+
const blobSrcPath = join(extractDir, entry.blobFile);
|
|
40
|
+
if (!existsSync(blobSrcPath)) {
|
|
41
|
+
console.error(`[vibe-splain import] Missing blob for pointer ${entry.pointerId}: ${entry.blobFile}`);
|
|
42
|
+
hashErrors++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
// Verify hash before importing
|
|
46
|
+
const content = await readFile(blobSrcPath);
|
|
47
|
+
const actualHash = `sha256:${createHash('sha256').update(content).digest('hex')}`;
|
|
48
|
+
if (actualHash !== entry.contentHash) {
|
|
49
|
+
console.error(`[vibe-splain import] Hash mismatch for ${entry.pointerId}: expected ${entry.contentHash}, got ${actualHash}`);
|
|
50
|
+
hashErrors++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// Write blob to local store (atomic)
|
|
54
|
+
const { blobPath } = await blobStore.writeAtomic(content);
|
|
55
|
+
// Insert pointer under bundle namespace alias
|
|
56
|
+
const namespacedPointerId = `${namespace}::${entry.pointerId}`;
|
|
57
|
+
const namespacedScanId = `${namespace}::${entry.scanId}`;
|
|
58
|
+
await pointerStore.insertPointer({
|
|
59
|
+
pointerId: namespacedPointerId,
|
|
60
|
+
scanId: namespacedScanId,
|
|
61
|
+
artifactName: entry.artifactName,
|
|
62
|
+
contentHash: entry.contentHash,
|
|
63
|
+
blobPath,
|
|
64
|
+
schemaVersion: entry.schemaVersion,
|
|
65
|
+
createdAt: entry.createdAt,
|
|
66
|
+
expiresAt: entry.expiresAt,
|
|
67
|
+
});
|
|
68
|
+
imported++;
|
|
69
|
+
}
|
|
70
|
+
if (hashErrors > 0) {
|
|
71
|
+
console.error(`[vibe-splain import] Warning: ${hashErrors} blobs failed hash verification and were skipped`);
|
|
72
|
+
}
|
|
73
|
+
console.error(`[vibe-splain import] Imported ${imported}/${manifest.pointers.length} pointers under namespace "${namespace}"`);
|
|
74
|
+
console.error(`[vibe-splain import] Original scanId: ${manifest.scanId} → namespaced as: ${namespace}::${manifest.scanId}`);
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
await rm(extractDir, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=importBundle.js.map
|
|
@@ -9,8 +9,10 @@ export class ArtifactBundleWriter {
|
|
|
9
9
|
async writeBundle(artifacts) {
|
|
10
10
|
const outputDir = join(this.projectRoot, '.vibe-splainer');
|
|
11
11
|
const stagingDir = join(this.projectRoot, '.vibe-splainer.tmp');
|
|
12
|
+
const oldDir = join(this.projectRoot, '.vibe-splainer.old');
|
|
12
13
|
try {
|
|
13
14
|
await rm(stagingDir, { recursive: true, force: true });
|
|
15
|
+
await rm(oldDir, { recursive: true, force: true });
|
|
14
16
|
const { existsSync } = await import('fs');
|
|
15
17
|
const { cp } = await import('fs/promises');
|
|
16
18
|
if (existsSync(outputDir)) {
|
|
@@ -40,12 +42,28 @@ export class ArtifactBundleWriter {
|
|
|
40
42
|
artifacts: manifestArtifacts,
|
|
41
43
|
};
|
|
42
44
|
await writeFile(join(stagingDir, 'artifact_manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
|
|
43
|
-
// Atomic
|
|
44
|
-
// Rename
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
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
|
+
}
|
|
49
67
|
}
|
|
50
68
|
catch (err) {
|
|
51
69
|
await rm(stagingDir, { recursive: true, force: true });
|
|
@@ -4,9 +4,27 @@ export interface ExportOptions {
|
|
|
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
|
+
}
|
|
7
22
|
export declare class ExportOrchestrator {
|
|
8
23
|
private projectRoot;
|
|
9
24
|
constructor(projectRoot: string);
|
|
10
|
-
writeBundle(dossier: Dossier, options?: ExportOptions, store?: AnalysisStore, graph?: ImportGraph): Promise<
|
|
25
|
+
writeBundle(dossier: Dossier, options?: ExportOptions, store?: AnalysisStore, graph?: ImportGraph, scanId?: string): Promise<{
|
|
26
|
+
scanId: string;
|
|
27
|
+
manifestPointer: string;
|
|
28
|
+
}>;
|
|
11
29
|
private buildViewModel;
|
|
12
30
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readAnalysis, RecommendationEngine } from '@vibe-splain/brain';
|
|
1
|
+
import { readAnalysis, readActionBindings, RecommendationEngine } from '@vibe-splain/brain';
|
|
2
2
|
import { ArtifactBundleWriter } from './ArtifactBundleWriter.js';
|
|
3
3
|
import { JsonRenderer } from './renderers/JsonRenderer.js';
|
|
4
4
|
import { HtmlRenderer } from './renderers/HtmlRenderer.js';
|
|
@@ -7,16 +7,20 @@ import { AgentMarkdownRenderer } from './renderers/AgentMarkdownRenderer.js';
|
|
|
7
7
|
import { ValidationRenderer } from './renderers/ValidationRenderer.js';
|
|
8
8
|
import { RawAnalysisRenderer } from './renderers/RawAnalysisRenderer.js';
|
|
9
9
|
import { GraphRenderer } from './renderers/GraphRenderer.js';
|
|
10
|
+
import { BlobStore } from '../store/BlobStore.js';
|
|
11
|
+
import { PointerStore } from '../store/PointerStore.js';
|
|
12
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
10
13
|
export class ExportOrchestrator {
|
|
11
14
|
projectRoot;
|
|
12
15
|
constructor(projectRoot) {
|
|
13
16
|
this.projectRoot = projectRoot;
|
|
14
17
|
}
|
|
15
|
-
async writeBundle(dossier, options = {}, store, graph) {
|
|
18
|
+
async writeBundle(dossier, options = {}, store, graph, scanId) {
|
|
16
19
|
const finalStore = store || await readAnalysis(this.projectRoot);
|
|
17
20
|
if (!finalStore) {
|
|
18
21
|
throw new Error('Analysis store not found. Scan the project first.');
|
|
19
22
|
}
|
|
23
|
+
const bindings = await readActionBindings(this.projectRoot);
|
|
20
24
|
// Aggressive Boilerplate Culling
|
|
21
25
|
for (const p of dossier.pillars) {
|
|
22
26
|
p.decisions = p.decisions.filter(c => !(c.severity === 1 && c.category === 'Convention'));
|
|
@@ -42,10 +46,93 @@ export class ExportOrchestrator {
|
|
|
42
46
|
artifacts.push(...await new HtmlRenderer().render(viewModel, finalStore));
|
|
43
47
|
}
|
|
44
48
|
if (formats.includes('markdown')) {
|
|
45
|
-
artifacts.push(...await new AgentMarkdownRenderer(options.budget).render(viewModel, finalStore));
|
|
49
|
+
artifacts.push(...await new AgentMarkdownRenderer(options.budget, bindings).render(viewModel, finalStore));
|
|
46
50
|
}
|
|
47
51
|
const writer = new ArtifactBundleWriter(this.projectRoot);
|
|
48
52
|
await writer.writeBundle(artifacts);
|
|
53
|
+
// Register artifacts in BlobStore + PointerStore and build manifest
|
|
54
|
+
const effectiveScanId = scanId ?? `scan_${Date.now()}`;
|
|
55
|
+
const blobStore = new BlobStore(this.projectRoot);
|
|
56
|
+
const pointerStore = PointerStore.open(this.projectRoot);
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
const manifestEntries = [];
|
|
59
|
+
for (const artifact of artifacts) {
|
|
60
|
+
const content = typeof artifact.content === 'string'
|
|
61
|
+
? Buffer.from(artifact.content, 'utf8')
|
|
62
|
+
: artifact.content;
|
|
63
|
+
const { contentHash, blobPath } = await blobStore.writeAtomic(content);
|
|
64
|
+
const pointerId = `ptr_${uuidv4().replace(/-/g, '').slice(0, 16)}`;
|
|
65
|
+
await pointerStore.insertPointer({
|
|
66
|
+
pointerId,
|
|
67
|
+
scanId: effectiveScanId,
|
|
68
|
+
artifactName: artifact.type,
|
|
69
|
+
contentHash,
|
|
70
|
+
blobPath,
|
|
71
|
+
schemaVersion: '1.0.0',
|
|
72
|
+
createdAt: now,
|
|
73
|
+
expiresAt: null,
|
|
74
|
+
});
|
|
75
|
+
const entry = {
|
|
76
|
+
name: artifact.type,
|
|
77
|
+
pointer: pointerId,
|
|
78
|
+
contentHash,
|
|
79
|
+
sizeBytes: content.length,
|
|
80
|
+
};
|
|
81
|
+
// Attach hydrator hints for the large analysis artifact
|
|
82
|
+
if (artifact.type === 'analysis') {
|
|
83
|
+
entry.hydrators = ['get_project_summary', 'get_start_here'];
|
|
84
|
+
// Generate analysis.index.json (Start-Here + Top-Heat)
|
|
85
|
+
const analysisIndex = {
|
|
86
|
+
schemaVersion: '1.0.0',
|
|
87
|
+
scanId: effectiveScanId,
|
|
88
|
+
startHere: dossier.map.topGravity.slice(0, 12),
|
|
89
|
+
topHeat: dossier.map.topHeat.slice(0, 12),
|
|
90
|
+
pillarSummary: dossier.map.pillars.map(p => ({
|
|
91
|
+
name: p.name,
|
|
92
|
+
fileCount: p.memberFiles?.length ?? 0,
|
|
93
|
+
})),
|
|
94
|
+
totalFiles: Object.keys(finalStore.files).length,
|
|
95
|
+
realSourceFiles: Object.values(finalStore.files).filter(f => f.isRealSource).length,
|
|
96
|
+
};
|
|
97
|
+
const indexContent = Buffer.from(JSON.stringify(analysisIndex, null, 2), 'utf8');
|
|
98
|
+
const indexWrite = await blobStore.writeAtomic(indexContent);
|
|
99
|
+
const indexPointerId = `ptr_${uuidv4().replace(/-/g, '').slice(0, 16)}`;
|
|
100
|
+
await pointerStore.insertPointer({
|
|
101
|
+
pointerId: indexPointerId,
|
|
102
|
+
scanId: effectiveScanId,
|
|
103
|
+
artifactName: 'analysis.index',
|
|
104
|
+
contentHash: indexWrite.contentHash,
|
|
105
|
+
blobPath: indexWrite.blobPath,
|
|
106
|
+
schemaVersion: '1.0.0',
|
|
107
|
+
createdAt: now,
|
|
108
|
+
expiresAt: null,
|
|
109
|
+
});
|
|
110
|
+
entry.indexes = { startHere: indexPointerId };
|
|
111
|
+
}
|
|
112
|
+
manifestEntries.push(entry);
|
|
113
|
+
}
|
|
114
|
+
// Write and register the scan manifest itself
|
|
115
|
+
const manifest = {
|
|
116
|
+
schemaVersion: '2.0.0',
|
|
117
|
+
scanId: effectiveScanId,
|
|
118
|
+
generatedAt: new Date(now).toISOString(),
|
|
119
|
+
projectRoot: this.projectRoot,
|
|
120
|
+
artifacts: manifestEntries,
|
|
121
|
+
};
|
|
122
|
+
const manifestContent = Buffer.from(JSON.stringify(manifest, null, 2), 'utf8');
|
|
123
|
+
const manifestWrite = await blobStore.writeAtomic(manifestContent);
|
|
124
|
+
const manifestPointerId = `ptr_manifest_${effectiveScanId}`;
|
|
125
|
+
await pointerStore.insertPointer({
|
|
126
|
+
pointerId: manifestPointerId,
|
|
127
|
+
scanId: effectiveScanId,
|
|
128
|
+
artifactName: 'artifact_manifest',
|
|
129
|
+
contentHash: manifestWrite.contentHash,
|
|
130
|
+
blobPath: manifestWrite.blobPath,
|
|
131
|
+
schemaVersion: '2.0.0',
|
|
132
|
+
createdAt: now,
|
|
133
|
+
expiresAt: null,
|
|
134
|
+
});
|
|
135
|
+
return { scanId: effectiveScanId, manifestPointer: manifestPointerId };
|
|
49
136
|
}
|
|
50
137
|
buildViewModel(dossier, store) {
|
|
51
138
|
const recommendations = {};
|
package/dist/export/Watcher.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function startWatcher(projectRoot: string, watchedPaths: string[]): void
|
|
1
|
+
export declare function startWatcher(projectRoot: string, watchedPaths: string[]): Promise<void>;
|
package/dist/export/Watcher.js
CHANGED
|
@@ -4,7 +4,14 @@ import { readFile } from 'fs/promises';
|
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { readDossier } from '@vibe-splain/brain';
|
|
6
6
|
import { ExportOrchestrator } from './ExportOrchestrator.js';
|
|
7
|
-
|
|
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
|
+
}
|
|
8
15
|
const watcher = chokidar.watch(watchedPaths.length > 0 ? watchedPaths : projectRoot, {
|
|
9
16
|
ignoreInitial: true,
|
|
10
17
|
ignored: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.vibe-splainer/**'],
|
|
@@ -42,6 +49,7 @@ export function startWatcher(projectRoot, watchedPaths) {
|
|
|
42
49
|
console.error('[vibe-splain] Watcher error:', err);
|
|
43
50
|
}
|
|
44
51
|
});
|
|
52
|
+
activeWatchers.set(projectRoot, watcher);
|
|
45
53
|
console.error('[vibe-splain] File watcher started');
|
|
46
54
|
}
|
|
47
55
|
//# sourceMappingURL=Watcher.js.map
|
|
@@ -3,6 +3,7 @@ import type { Renderer } from './Renderer.js';
|
|
|
3
3
|
import type { Artifact } from '../ArtifactBundleWriter.js';
|
|
4
4
|
export declare class AgentMarkdownRenderer implements Renderer {
|
|
5
5
|
private budget;
|
|
6
|
-
|
|
6
|
+
private bindings;
|
|
7
|
+
constructor(budget?: number, bindings?: any | null);
|
|
7
8
|
render(viewModel: DossierViewModel, store: AnalysisStore): Artifact[];
|
|
8
9
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export class AgentMarkdownRenderer {
|
|
2
2
|
budget;
|
|
3
|
-
|
|
3
|
+
bindings;
|
|
4
|
+
constructor(budget = 8000, bindings = null) {
|
|
4
5
|
this.budget = budget;
|
|
6
|
+
this.bindings = bindings;
|
|
5
7
|
}
|
|
6
8
|
render(viewModel, store) {
|
|
7
9
|
let md = `# Architectural Dossier: ${viewModel.projectRoot}\n\n`;
|
|
@@ -52,6 +54,20 @@ export class AgentMarkdownRenderer {
|
|
|
52
54
|
md += `**Severity**: ${card.severity} | **Category**: ${card.category}\n`;
|
|
53
55
|
md += `**Narrative**: ${card.narrative}\n`;
|
|
54
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
|
+
}
|
|
55
71
|
if (recs.length > 0) {
|
|
56
72
|
md += `\n**Safe Patch Strategies**:\n`;
|
|
57
73
|
for (const r of recs) {
|
|
@@ -17,13 +17,36 @@ function getAllFiles(dirPath, arrayOfFiles = []) {
|
|
|
17
17
|
}
|
|
18
18
|
export class HtmlRenderer {
|
|
19
19
|
render(viewModel, _store) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
+
}
|
|
24
47
|
}
|
|
25
|
-
if (!
|
|
26
|
-
console.error('[vibe-splain] UI template not found
|
|
48
|
+
if (!templateDir) {
|
|
49
|
+
console.error('[vibe-splain] UI template not found. Checked:', candidatePaths);
|
|
27
50
|
return [];
|
|
28
51
|
}
|
|
29
52
|
const artifacts = [];
|