viberag 0.3.2 → 0.4.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 +2 -2
- package/dist/cli/app.d.ts +3 -0
- package/dist/cli/app.js +100 -102
- package/dist/cli/commands/handlers.d.ts +8 -6
- package/dist/cli/commands/handlers.js +90 -32
- package/dist/cli/commands/useCommands.d.ts +20 -0
- package/dist/cli/commands/useCommands.js +189 -0
- package/dist/cli/commands/useRagCommands.d.ts +2 -5
- package/dist/cli/commands/useRagCommands.js +11 -18
- package/dist/cli/components/InitWizard.js +66 -27
- package/dist/cli/components/McpSetupWizard.js +23 -4
- package/dist/cli/components/SlotRow.d.ts +22 -0
- package/dist/cli/components/SlotRow.js +55 -0
- package/dist/cli/components/StatusBar.d.ts +14 -0
- package/dist/cli/components/StatusBar.js +156 -0
- package/dist/cli/contexts/DaemonStatusContext.d.ts +38 -0
- package/dist/cli/contexts/DaemonStatusContext.js +106 -0
- package/dist/cli/hooks/useStatusPolling.d.ts +34 -0
- package/dist/cli/hooks/useStatusPolling.js +121 -0
- package/dist/cli/store/app/selectors.d.ts +87 -0
- package/dist/cli/store/app/selectors.js +28 -0
- package/dist/cli/store/app/slice.d.ts +1013 -0
- package/dist/cli/store/app/slice.js +112 -0
- package/dist/cli/store/hooks.d.ts +22 -0
- package/dist/cli/store/hooks.js +17 -0
- package/dist/cli/store/store.d.ts +17 -0
- package/dist/cli/store/store.js +18 -0
- package/dist/cli/store/wizard/selectors.d.ts +115 -0
- package/dist/cli/store/wizard/selectors.js +36 -0
- package/dist/cli/store/wizard/slice.d.ts +523 -0
- package/dist/cli/store/wizard/slice.js +119 -0
- package/dist/cli/utils/error-handler.d.ts +55 -0
- package/dist/cli/utils/error-handler.js +92 -0
- package/dist/client/auto-start.d.ts +42 -0
- package/dist/client/auto-start.js +250 -0
- package/dist/client/connection.d.ts +48 -0
- package/dist/client/connection.js +200 -0
- package/dist/client/index.d.ts +93 -0
- package/dist/client/index.js +209 -0
- package/dist/client/types.d.ts +105 -0
- package/dist/client/types.js +7 -0
- package/dist/common/components/SlotRow.d.ts +22 -0
- package/dist/common/components/SlotRow.js +53 -0
- package/dist/common/components/StatusBar.js +82 -31
- package/dist/common/types.d.ts +12 -13
- package/dist/daemon/handlers.d.ts +15 -0
- package/dist/daemon/handlers.js +157 -0
- package/dist/daemon/index.d.ts +21 -0
- package/dist/daemon/index.js +123 -0
- package/dist/daemon/lib/chunker/bounded-channel.d.ts +51 -0
- package/dist/daemon/lib/chunker/bounded-channel.js +138 -0
- package/dist/daemon/lib/chunker/index.d.ts +135 -0
- package/dist/daemon/lib/chunker/index.js +1370 -0
- package/dist/daemon/lib/chunker/types.d.ts +77 -0
- package/dist/daemon/lib/chunker/types.js +50 -0
- package/dist/daemon/lib/config.d.ts +73 -0
- package/dist/daemon/lib/config.js +149 -0
- package/dist/daemon/lib/constants.d.ts +75 -0
- package/dist/daemon/lib/constants.js +114 -0
- package/dist/daemon/lib/gitignore.d.ts +57 -0
- package/dist/daemon/lib/gitignore.js +246 -0
- package/dist/daemon/lib/logger.d.ts +51 -0
- package/dist/daemon/lib/logger.js +167 -0
- package/dist/daemon/lib/manifest.d.ts +58 -0
- package/dist/daemon/lib/manifest.js +116 -0
- package/dist/daemon/lib/merkle/diff.d.ts +32 -0
- package/dist/daemon/lib/merkle/diff.js +107 -0
- package/dist/daemon/lib/merkle/hash.d.ts +40 -0
- package/dist/daemon/lib/merkle/hash.js +180 -0
- package/dist/daemon/lib/merkle/index.d.ts +71 -0
- package/dist/daemon/lib/merkle/index.js +309 -0
- package/dist/daemon/lib/merkle/node.d.ts +55 -0
- package/dist/daemon/lib/merkle/node.js +82 -0
- package/dist/daemon/lifecycle.d.ts +50 -0
- package/dist/daemon/lifecycle.js +142 -0
- package/dist/daemon/owner.d.ts +175 -0
- package/dist/daemon/owner.js +609 -0
- package/dist/daemon/protocol.d.ts +100 -0
- package/dist/daemon/protocol.js +163 -0
- package/dist/daemon/providers/api-utils.d.ts +130 -0
- package/dist/daemon/providers/api-utils.js +248 -0
- package/dist/daemon/providers/gemini.d.ts +39 -0
- package/dist/daemon/providers/gemini.js +205 -0
- package/dist/daemon/providers/index.d.ts +14 -0
- package/dist/daemon/providers/index.js +14 -0
- package/dist/daemon/providers/local-4b.d.ts +28 -0
- package/dist/daemon/providers/local-4b.js +51 -0
- package/dist/daemon/providers/local.d.ts +36 -0
- package/dist/daemon/providers/local.js +166 -0
- package/dist/daemon/providers/mistral.d.ts +35 -0
- package/dist/daemon/providers/mistral.js +160 -0
- package/dist/daemon/providers/mock.d.ts +35 -0
- package/dist/daemon/providers/mock.js +69 -0
- package/dist/daemon/providers/openai.d.ts +41 -0
- package/dist/daemon/providers/openai.js +190 -0
- package/dist/daemon/providers/types.d.ts +68 -0
- package/dist/daemon/providers/types.js +6 -0
- package/dist/daemon/providers/validate.d.ts +30 -0
- package/dist/daemon/providers/validate.js +162 -0
- package/dist/daemon/server.d.ts +79 -0
- package/dist/daemon/server.js +293 -0
- package/dist/daemon/services/index.d.ts +11 -0
- package/dist/daemon/services/index.js +16 -0
- package/dist/daemon/services/indexing.d.ts +117 -0
- package/dist/daemon/services/indexing.js +573 -0
- package/dist/daemon/services/search/filters.d.ts +21 -0
- package/dist/daemon/services/search/filters.js +106 -0
- package/dist/daemon/services/search/fts.d.ts +32 -0
- package/dist/daemon/services/search/fts.js +61 -0
- package/dist/daemon/services/search/hybrid.d.ts +17 -0
- package/dist/daemon/services/search/hybrid.js +58 -0
- package/dist/daemon/services/search/index.d.ts +108 -0
- package/dist/daemon/services/search/index.js +417 -0
- package/dist/daemon/services/search/types.d.ts +126 -0
- package/dist/daemon/services/search/types.js +4 -0
- package/dist/daemon/services/search/vector.d.ts +25 -0
- package/dist/daemon/services/search/vector.js +44 -0
- package/dist/daemon/services/storage/index.d.ts +110 -0
- package/dist/daemon/services/storage/index.js +378 -0
- package/dist/daemon/services/storage/schema.d.ts +24 -0
- package/dist/daemon/services/storage/schema.js +51 -0
- package/dist/daemon/services/storage/types.d.ts +105 -0
- package/dist/daemon/services/storage/types.js +71 -0
- package/dist/daemon/services/types.d.ts +192 -0
- package/dist/daemon/services/types.js +53 -0
- package/dist/daemon/services/watcher.d.ts +98 -0
- package/dist/daemon/services/watcher.js +386 -0
- package/dist/daemon/state.d.ts +119 -0
- package/dist/daemon/state.js +161 -0
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +44 -60
- package/dist/mcp/server.d.ts +10 -14
- package/dist/mcp/server.js +75 -74
- package/dist/mcp/services/lazy-loader.d.ts +23 -0
- package/dist/mcp/services/lazy-loader.js +34 -0
- package/dist/mcp/warmup.d.ts +3 -3
- package/dist/mcp/warmup.js +39 -40
- package/dist/mcp/watcher.d.ts +5 -7
- package/dist/mcp/watcher.js +73 -64
- package/dist/rag/config/index.d.ts +2 -0
- package/dist/rag/constants.d.ts +30 -0
- package/dist/rag/constants.js +38 -0
- package/dist/rag/embeddings/api-utils.d.ts +121 -0
- package/dist/rag/embeddings/api-utils.js +259 -0
- package/dist/rag/embeddings/gemini.d.ts +4 -12
- package/dist/rag/embeddings/gemini.js +22 -72
- package/dist/rag/embeddings/index.d.ts +5 -3
- package/dist/rag/embeddings/index.js +5 -2
- package/dist/rag/embeddings/local-4b.d.ts +2 -2
- package/dist/rag/embeddings/local-4b.js +1 -1
- package/dist/rag/embeddings/local.d.ts +10 -3
- package/dist/rag/embeddings/local.js +58 -12
- package/dist/rag/embeddings/mistral.d.ts +4 -12
- package/dist/rag/embeddings/mistral.js +22 -72
- package/dist/rag/embeddings/mock.d.ts +35 -0
- package/dist/rag/embeddings/mock.js +69 -0
- package/dist/rag/embeddings/openai.d.ts +11 -13
- package/dist/rag/embeddings/openai.js +47 -75
- package/dist/rag/embeddings/types.d.ts +27 -1
- package/dist/rag/embeddings/validate.d.ts +9 -1
- package/dist/rag/embeddings/validate.js +17 -4
- package/dist/rag/index.d.ts +2 -2
- package/dist/rag/index.js +1 -1
- package/dist/rag/indexer/bounded-channel.d.ts +51 -0
- package/dist/rag/indexer/bounded-channel.js +138 -0
- package/dist/rag/indexer/indexer.d.ts +4 -14
- package/dist/rag/indexer/indexer.js +246 -169
- package/dist/rag/indexer/types.d.ts +1 -0
- package/dist/rag/logger/index.d.ts +22 -0
- package/dist/rag/logger/index.js +78 -1
- package/dist/rag/manifest/index.js +1 -2
- package/dist/rag/search/index.js +1 -1
- package/dist/rag/storage/schema.d.ts +2 -4
- package/dist/rag/storage/schema.js +3 -5
- package/dist/store/app/selectors.d.ts +87 -0
- package/dist/store/app/selectors.js +28 -0
- package/dist/store/app/slice.d.ts +1013 -0
- package/dist/store/app/slice.js +112 -0
- package/dist/store/hooks.d.ts +22 -0
- package/dist/store/hooks.js +17 -0
- package/dist/store/index.d.ts +12 -0
- package/dist/store/index.js +18 -0
- package/dist/store/indexing/listeners.d.ts +25 -0
- package/dist/store/indexing/listeners.js +46 -0
- package/dist/store/indexing/selectors.d.ts +195 -0
- package/dist/store/indexing/selectors.js +69 -0
- package/dist/store/indexing/slice.d.ts +309 -0
- package/dist/store/indexing/slice.js +113 -0
- package/dist/store/slot-progress/listeners.d.ts +23 -0
- package/dist/store/slot-progress/listeners.js +33 -0
- package/dist/store/slot-progress/selectors.d.ts +67 -0
- package/dist/store/slot-progress/selectors.js +36 -0
- package/dist/store/slot-progress/slice.d.ts +246 -0
- package/dist/store/slot-progress/slice.js +70 -0
- package/dist/store/store.d.ts +17 -0
- package/dist/store/store.js +18 -0
- package/dist/store/warmup/selectors.d.ts +109 -0
- package/dist/store/warmup/selectors.js +44 -0
- package/dist/store/warmup/slice.d.ts +137 -0
- package/dist/store/warmup/slice.js +72 -0
- package/dist/store/watcher/selectors.d.ts +115 -0
- package/dist/store/watcher/selectors.js +52 -0
- package/dist/store/watcher/slice.d.ts +269 -0
- package/dist/store/watcher/slice.js +100 -0
- package/dist/store/wizard/selectors.d.ts +115 -0
- package/dist/store/wizard/selectors.js +36 -0
- package/dist/store/wizard/slice.d.ts +523 -0
- package/dist/store/wizard/slice.js +119 -0
- package/package.json +10 -2
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon Method Handlers
|
|
3
|
+
*
|
|
4
|
+
* JSON-RPC method implementations for daemon IPC.
|
|
5
|
+
* Each handler receives params and a context with access to owner and server.
|
|
6
|
+
*
|
|
7
|
+
* Simplified for polling-based architecture:
|
|
8
|
+
* - Clients poll status() for state updates
|
|
9
|
+
* - No push notifications or subscriptions
|
|
10
|
+
*/
|
|
11
|
+
import type { HandlerRegistry } from './server.js';
|
|
12
|
+
/**
|
|
13
|
+
* Create the handler registry.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createHandlers(): HandlerRegistry;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon Method Handlers
|
|
3
|
+
*
|
|
4
|
+
* JSON-RPC method implementations for daemon IPC.
|
|
5
|
+
* Each handler receives params and a context with access to owner and server.
|
|
6
|
+
*
|
|
7
|
+
* Simplified for polling-based architecture:
|
|
8
|
+
* - Clients poll status() for state updates
|
|
9
|
+
* - No push notifications or subscriptions
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import { PROTOCOL_VERSION } from './protocol.js';
|
|
13
|
+
import { daemonState } from './state.js';
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Parameter Schemas
|
|
16
|
+
// ============================================================================
|
|
17
|
+
const searchParamsSchema = z.object({
|
|
18
|
+
query: z.string().min(1),
|
|
19
|
+
mode: z
|
|
20
|
+
.enum(['semantic', 'exact', 'hybrid', 'definition', 'similar'])
|
|
21
|
+
.optional(),
|
|
22
|
+
limit: z.number().min(1).max(100).optional(),
|
|
23
|
+
bm25Weight: z.number().min(0).max(1).optional(),
|
|
24
|
+
minScore: z.number().min(0).max(1).optional(),
|
|
25
|
+
filters: z.record(z.string(), z.unknown()).optional(),
|
|
26
|
+
codeSnippet: z.string().optional(),
|
|
27
|
+
symbolName: z.string().optional(),
|
|
28
|
+
autoBoost: z.boolean().optional(),
|
|
29
|
+
autoBoostThreshold: z.number().min(0).max(1).optional(),
|
|
30
|
+
returnDebug: z.boolean().optional(),
|
|
31
|
+
});
|
|
32
|
+
const indexParamsSchema = z.object({
|
|
33
|
+
force: z.boolean().optional(),
|
|
34
|
+
});
|
|
35
|
+
const ACTIVE_INDEX_STATUSES = new Set([
|
|
36
|
+
'initializing',
|
|
37
|
+
'scanning',
|
|
38
|
+
'chunking',
|
|
39
|
+
'embedding',
|
|
40
|
+
]);
|
|
41
|
+
const shutdownParamsSchema = z.object({
|
|
42
|
+
reason: z.string().optional(),
|
|
43
|
+
});
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Handlers
|
|
46
|
+
// ============================================================================
|
|
47
|
+
/**
|
|
48
|
+
* Search handler.
|
|
49
|
+
*/
|
|
50
|
+
const searchHandler = async (params, ctx) => {
|
|
51
|
+
const validated = searchParamsSchema.parse(params ?? {});
|
|
52
|
+
const { query, ...options } = validated;
|
|
53
|
+
return ctx.owner.search(query, options);
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Index handler.
|
|
57
|
+
* Simply runs indexing - clients poll status() for progress.
|
|
58
|
+
*/
|
|
59
|
+
const indexHandler = async (params, ctx) => {
|
|
60
|
+
const validated = indexParamsSchema.parse(params ?? {});
|
|
61
|
+
// Run indexing - Redux state is updated by indexer directly
|
|
62
|
+
// Clients poll status() to see progress
|
|
63
|
+
const stats = await ctx.owner.index({ force: validated.force });
|
|
64
|
+
return stats;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Index async handler.
|
|
68
|
+
* Starts indexing in the background and returns immediately.
|
|
69
|
+
* Clients poll status() to see progress and completion.
|
|
70
|
+
*/
|
|
71
|
+
const indexAsyncHandler = async (params, ctx) => {
|
|
72
|
+
const validated = indexParamsSchema.parse(params ?? {});
|
|
73
|
+
const currentStatus = daemonState.getSnapshot().indexing.status;
|
|
74
|
+
if (ACTIVE_INDEX_STATUSES.has(currentStatus)) {
|
|
75
|
+
return { started: false, reason: 'in_progress' };
|
|
76
|
+
}
|
|
77
|
+
void ctx.owner.index({ force: validated.force }).catch(error => {
|
|
78
|
+
console.error('[daemon] Async index failed:', error);
|
|
79
|
+
const logger = ctx.owner.getLogger();
|
|
80
|
+
if (logger) {
|
|
81
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
82
|
+
logger.error('DaemonServer', 'Async index failed', err);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
return { started: true };
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Status handler.
|
|
89
|
+
*/
|
|
90
|
+
const statusHandler = async (_params, ctx) => {
|
|
91
|
+
return ctx.owner.getStatus();
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Watch status handler.
|
|
95
|
+
*/
|
|
96
|
+
const watchStatusHandler = async (_params, ctx) => {
|
|
97
|
+
return ctx.owner.getWatcherStatus();
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Shutdown handler.
|
|
101
|
+
* Schedules server stop.
|
|
102
|
+
*/
|
|
103
|
+
const shutdownHandler = async (params, ctx) => {
|
|
104
|
+
const validated = shutdownParamsSchema.parse(params ?? {});
|
|
105
|
+
console.error(`[daemon] Shutdown requested: ${validated.reason ?? 'no reason'}`);
|
|
106
|
+
// Schedule shutdown after response is sent
|
|
107
|
+
setImmediate(async () => {
|
|
108
|
+
await ctx.owner.shutdown();
|
|
109
|
+
await ctx.server.stop();
|
|
110
|
+
process.exit(0);
|
|
111
|
+
});
|
|
112
|
+
return { success: true };
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Ping handler.
|
|
116
|
+
* Simple health check.
|
|
117
|
+
*/
|
|
118
|
+
const pingHandler = async (_params, _ctx) => {
|
|
119
|
+
return {
|
|
120
|
+
pong: true,
|
|
121
|
+
timestamp: Date.now(),
|
|
122
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Health handler.
|
|
127
|
+
* Returns detailed health information.
|
|
128
|
+
*/
|
|
129
|
+
const healthHandler = async (_params, ctx) => {
|
|
130
|
+
const snapshot = daemonState.getSnapshot();
|
|
131
|
+
return {
|
|
132
|
+
healthy: true,
|
|
133
|
+
uptime: process.uptime(),
|
|
134
|
+
memoryUsage: process.memoryUsage(),
|
|
135
|
+
clients: ctx.server.getClientCount(),
|
|
136
|
+
indexStatus: snapshot.indexing.status,
|
|
137
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// Handler Registry
|
|
142
|
+
// ============================================================================
|
|
143
|
+
/**
|
|
144
|
+
* Create the handler registry.
|
|
145
|
+
*/
|
|
146
|
+
export function createHandlers() {
|
|
147
|
+
return {
|
|
148
|
+
search: searchHandler,
|
|
149
|
+
index: indexHandler,
|
|
150
|
+
indexAsync: indexAsyncHandler,
|
|
151
|
+
status: statusHandler,
|
|
152
|
+
watchStatus: watchStatusHandler,
|
|
153
|
+
shutdown: shutdownHandler,
|
|
154
|
+
ping: pingHandler,
|
|
155
|
+
health: healthHandler,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* VibeRAG Daemon Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Single daemon per project, owns all mutable state.
|
|
6
|
+
* CLI and MCP connect as clients via Unix socket.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx viberag-daemon
|
|
10
|
+
*
|
|
11
|
+
* The daemon uses the current working directory as the project root.
|
|
12
|
+
* It creates a socket at .viberag/daemon.sock and a PID file at .viberag/daemon.pid.
|
|
13
|
+
*
|
|
14
|
+
* The daemon will:
|
|
15
|
+
* - Acquire exclusive lock to prevent multiple instances
|
|
16
|
+
* - Start the file watcher for auto-indexing
|
|
17
|
+
* - Warm up the embedding provider
|
|
18
|
+
* - Listen for IPC requests (search, index, status, etc.)
|
|
19
|
+
* - Auto-shutdown after 5 minutes of idle (no connected clients)
|
|
20
|
+
*/
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* VibeRAG Daemon Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Single daemon per project, owns all mutable state.
|
|
6
|
+
* CLI and MCP connect as clients via Unix socket.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx viberag-daemon
|
|
10
|
+
*
|
|
11
|
+
* The daemon uses the current working directory as the project root.
|
|
12
|
+
* It creates a socket at .viberag/daemon.sock and a PID file at .viberag/daemon.pid.
|
|
13
|
+
*
|
|
14
|
+
* The daemon will:
|
|
15
|
+
* - Acquire exclusive lock to prevent multiple instances
|
|
16
|
+
* - Start the file watcher for auto-indexing
|
|
17
|
+
* - Warm up the embedding provider
|
|
18
|
+
* - Listen for IPC requests (search, index, status, etc.)
|
|
19
|
+
* - Auto-shutdown after 5 minutes of idle (no connected clients)
|
|
20
|
+
*/
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import lockfile from 'proper-lockfile';
|
|
23
|
+
import { DaemonOwner } from './owner.js';
|
|
24
|
+
import { DaemonServer } from './server.js';
|
|
25
|
+
import { LifecycleManager } from './lifecycle.js';
|
|
26
|
+
import { createHandlers } from './handlers.js';
|
|
27
|
+
import { configExists, loadConfig } from './lib/config.js';
|
|
28
|
+
// Use current working directory as project root
|
|
29
|
+
const projectRoot = process.cwd();
|
|
30
|
+
// Lock file path - inside .viberag directory
|
|
31
|
+
const LOCK_FILE_PATH = path.join(projectRoot, '.viberag', 'daemon.lock');
|
|
32
|
+
// Lock release function - set when lock is acquired
|
|
33
|
+
let releaseLock = null;
|
|
34
|
+
/**
|
|
35
|
+
* Acquire exclusive daemon lock to prevent multiple instances.
|
|
36
|
+
* Uses proper-lockfile which is cross-platform and handles crash recovery.
|
|
37
|
+
*/
|
|
38
|
+
async function acquireDaemonLock() {
|
|
39
|
+
try {
|
|
40
|
+
const release = await lockfile.lock(projectRoot, {
|
|
41
|
+
lockfilePath: LOCK_FILE_PATH,
|
|
42
|
+
stale: 30000, // Lock is stale after 30s without mtime update
|
|
43
|
+
update: 10000, // Update mtime every 10s to prove liveness
|
|
44
|
+
retries: 0, // Fail immediately if locked
|
|
45
|
+
onCompromised: err => {
|
|
46
|
+
// Another process stole our lock (very rare edge case)
|
|
47
|
+
console.error('[daemon] Lock compromised:', err.message);
|
|
48
|
+
console.error('[daemon] Another daemon may have started. Exiting.');
|
|
49
|
+
process.exit(1);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
console.error('[daemon] Acquired exclusive lock');
|
|
53
|
+
return release;
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (err instanceof Error && 'code' in err && err.code === 'ELOCKED') {
|
|
57
|
+
console.error('[daemon] Another daemon is already running');
|
|
58
|
+
console.error('[daemon] Only one daemon per project is allowed.');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Release the daemon lock on exit.
|
|
66
|
+
*/
|
|
67
|
+
function setupLockRelease(release) {
|
|
68
|
+
releaseLock = release;
|
|
69
|
+
// Release lock on clean exit
|
|
70
|
+
const cleanup = () => {
|
|
71
|
+
if (releaseLock) {
|
|
72
|
+
// Synchronous-ish cleanup - lockfile handles this gracefully
|
|
73
|
+
releaseLock().catch(() => { });
|
|
74
|
+
releaseLock = null;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
process.on('exit', cleanup);
|
|
78
|
+
process.on('SIGINT', cleanup);
|
|
79
|
+
process.on('SIGTERM', cleanup);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Main daemon entry point.
|
|
83
|
+
*/
|
|
84
|
+
async function main() {
|
|
85
|
+
console.error(`[daemon] Starting for ${projectRoot}`);
|
|
86
|
+
// Verify project is initialized (needed before lock since lock is in .viberag/)
|
|
87
|
+
if (!(await configExists(projectRoot))) {
|
|
88
|
+
console.error('[daemon] Project not initialized');
|
|
89
|
+
console.error('[daemon] Run "npx viberag" and use /init command to initialize.');
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
// Acquire exclusive lock BEFORE any other operations
|
|
93
|
+
// This prevents multiple daemons from running concurrently
|
|
94
|
+
const release = await acquireDaemonLock();
|
|
95
|
+
setupLockRelease(release);
|
|
96
|
+
// Load config
|
|
97
|
+
await loadConfig(projectRoot);
|
|
98
|
+
// Default idle timeout: 5 minutes (can be made configurable later)
|
|
99
|
+
const idleTimeoutMs = 5 * 60 * 1000;
|
|
100
|
+
// Create components
|
|
101
|
+
const owner = new DaemonOwner(projectRoot);
|
|
102
|
+
const server = new DaemonServer(owner);
|
|
103
|
+
const lifecycle = new LifecycleManager(server, owner, idleTimeoutMs);
|
|
104
|
+
// Register handlers
|
|
105
|
+
server.setHandlers(createHandlers());
|
|
106
|
+
// Initialize owner (starts watcher, begins warmup)
|
|
107
|
+
await owner.initialize();
|
|
108
|
+
// Start server
|
|
109
|
+
await server.start();
|
|
110
|
+
// Register signal handlers
|
|
111
|
+
lifecycle.registerSignalHandlers();
|
|
112
|
+
// Start idle timer (cancelled on first connect)
|
|
113
|
+
lifecycle.startInitialIdleTimer();
|
|
114
|
+
console.error(`[daemon] Ready`);
|
|
115
|
+
console.error(`[daemon] Socket: ${owner.getSocketPath()}`);
|
|
116
|
+
console.error(`[daemon] PID: ${process.pid}`);
|
|
117
|
+
}
|
|
118
|
+
// Run main with error handling
|
|
119
|
+
main().catch(error => {
|
|
120
|
+
// Pass Error object directly to preserve stack trace (ADR-011)
|
|
121
|
+
console.error('[daemon] Fatal error:', error);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded async channel with backpressure.
|
|
3
|
+
*
|
|
4
|
+
* Implements producer-consumer pattern where:
|
|
5
|
+
* - Producer blocks when buffer is full (backpressure)
|
|
6
|
+
* - Consumer blocks when buffer is empty
|
|
7
|
+
* - Provides memory-bounded streaming between async operations
|
|
8
|
+
*
|
|
9
|
+
* Used in indexing pipeline to decouple batch building from embedding.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Bounded async channel for producer-consumer communication.
|
|
13
|
+
*
|
|
14
|
+
* @template T - Type of items in the channel
|
|
15
|
+
*/
|
|
16
|
+
export declare class BoundedChannel<T> {
|
|
17
|
+
private readonly capacity;
|
|
18
|
+
private buffer;
|
|
19
|
+
private closed;
|
|
20
|
+
private waitingPush;
|
|
21
|
+
private waitingPull;
|
|
22
|
+
/**
|
|
23
|
+
* Create a bounded channel.
|
|
24
|
+
* @param capacity - Maximum number of items in buffer (must be >= 1)
|
|
25
|
+
*/
|
|
26
|
+
constructor(capacity: number);
|
|
27
|
+
/**
|
|
28
|
+
* Push an item to the channel.
|
|
29
|
+
* Blocks if buffer is full (backpressure).
|
|
30
|
+
*
|
|
31
|
+
* @throws Error if channel is closed
|
|
32
|
+
*/
|
|
33
|
+
push(item: T): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Pull an item from the channel.
|
|
36
|
+
* Blocks if buffer is empty.
|
|
37
|
+
* Returns null when channel is closed and empty.
|
|
38
|
+
*/
|
|
39
|
+
pull(): Promise<T | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Close the channel.
|
|
42
|
+
* Remaining items can still be pulled, but no new items can be pushed.
|
|
43
|
+
*/
|
|
44
|
+
close(): void;
|
|
45
|
+
/** Current number of items in buffer */
|
|
46
|
+
get size(): number;
|
|
47
|
+
/** Whether channel is closed */
|
|
48
|
+
get isClosed(): boolean;
|
|
49
|
+
/** Maximum buffer capacity */
|
|
50
|
+
get maxCapacity(): number;
|
|
51
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded async channel with backpressure.
|
|
3
|
+
*
|
|
4
|
+
* Implements producer-consumer pattern where:
|
|
5
|
+
* - Producer blocks when buffer is full (backpressure)
|
|
6
|
+
* - Consumer blocks when buffer is empty
|
|
7
|
+
* - Provides memory-bounded streaming between async operations
|
|
8
|
+
*
|
|
9
|
+
* Used in indexing pipeline to decouple batch building from embedding.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Bounded async channel for producer-consumer communication.
|
|
13
|
+
*
|
|
14
|
+
* @template T - Type of items in the channel
|
|
15
|
+
*/
|
|
16
|
+
export class BoundedChannel {
|
|
17
|
+
/**
|
|
18
|
+
* Create a bounded channel.
|
|
19
|
+
* @param capacity - Maximum number of items in buffer (must be >= 1)
|
|
20
|
+
*/
|
|
21
|
+
constructor(capacity) {
|
|
22
|
+
Object.defineProperty(this, "capacity", {
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
writable: true,
|
|
26
|
+
value: capacity
|
|
27
|
+
});
|
|
28
|
+
Object.defineProperty(this, "buffer", {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: true,
|
|
31
|
+
writable: true,
|
|
32
|
+
value: []
|
|
33
|
+
});
|
|
34
|
+
Object.defineProperty(this, "closed", {
|
|
35
|
+
enumerable: true,
|
|
36
|
+
configurable: true,
|
|
37
|
+
writable: true,
|
|
38
|
+
value: false
|
|
39
|
+
});
|
|
40
|
+
Object.defineProperty(this, "waitingPush", {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
writable: true,
|
|
44
|
+
value: []
|
|
45
|
+
});
|
|
46
|
+
Object.defineProperty(this, "waitingPull", {
|
|
47
|
+
enumerable: true,
|
|
48
|
+
configurable: true,
|
|
49
|
+
writable: true,
|
|
50
|
+
value: []
|
|
51
|
+
});
|
|
52
|
+
if (capacity < 1) {
|
|
53
|
+
throw new Error('BoundedChannel capacity must be >= 1');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Push an item to the channel.
|
|
58
|
+
* Blocks if buffer is full (backpressure).
|
|
59
|
+
*
|
|
60
|
+
* @throws Error if channel is closed
|
|
61
|
+
*/
|
|
62
|
+
async push(item) {
|
|
63
|
+
if (this.closed) {
|
|
64
|
+
throw new Error('Cannot push to closed channel');
|
|
65
|
+
}
|
|
66
|
+
// If there's a waiting consumer, deliver directly
|
|
67
|
+
const consumer = this.waitingPull.shift();
|
|
68
|
+
if (consumer) {
|
|
69
|
+
consumer.resolve(item);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// If buffer has space, add to it
|
|
73
|
+
if (this.buffer.length < this.capacity) {
|
|
74
|
+
this.buffer.push(item);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// Buffer full - wait for space (backpressure)
|
|
78
|
+
await new Promise((resolve, reject) => {
|
|
79
|
+
this.waitingPush.push({ resolve, reject });
|
|
80
|
+
});
|
|
81
|
+
// After waking, add to buffer
|
|
82
|
+
this.buffer.push(item);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Pull an item from the channel.
|
|
86
|
+
* Blocks if buffer is empty.
|
|
87
|
+
* Returns null when channel is closed and empty.
|
|
88
|
+
*/
|
|
89
|
+
async pull() {
|
|
90
|
+
// If buffer has items, return one
|
|
91
|
+
if (this.buffer.length > 0) {
|
|
92
|
+
const item = this.buffer.shift();
|
|
93
|
+
// Wake a waiting producer
|
|
94
|
+
const producer = this.waitingPush.shift();
|
|
95
|
+
if (producer) {
|
|
96
|
+
producer.resolve();
|
|
97
|
+
}
|
|
98
|
+
return item;
|
|
99
|
+
}
|
|
100
|
+
// If closed and empty, return null (signal completion)
|
|
101
|
+
if (this.closed) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
// Wait for item
|
|
105
|
+
return new Promise(resolve => {
|
|
106
|
+
this.waitingPull.push({ resolve });
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Close the channel.
|
|
111
|
+
* Remaining items can still be pulled, but no new items can be pushed.
|
|
112
|
+
*/
|
|
113
|
+
close() {
|
|
114
|
+
this.closed = true;
|
|
115
|
+
// Reject all waiting producers
|
|
116
|
+
for (const producer of this.waitingPush) {
|
|
117
|
+
producer.reject(new Error('Channel closed'));
|
|
118
|
+
}
|
|
119
|
+
this.waitingPush = [];
|
|
120
|
+
// Resolve all waiting consumers with null
|
|
121
|
+
for (const consumer of this.waitingPull) {
|
|
122
|
+
consumer.resolve(null);
|
|
123
|
+
}
|
|
124
|
+
this.waitingPull = [];
|
|
125
|
+
}
|
|
126
|
+
/** Current number of items in buffer */
|
|
127
|
+
get size() {
|
|
128
|
+
return this.buffer.length;
|
|
129
|
+
}
|
|
130
|
+
/** Whether channel is closed */
|
|
131
|
+
get isClosed() {
|
|
132
|
+
return this.closed;
|
|
133
|
+
}
|
|
134
|
+
/** Maximum buffer capacity */
|
|
135
|
+
get maxCapacity() {
|
|
136
|
+
return this.capacity;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chunker - Tree-sitter based code chunking.
|
|
3
|
+
*
|
|
4
|
+
* Uses web-tree-sitter (WASM) to parse code and extract semantic chunks
|
|
5
|
+
* (functions, classes, methods) for embedding.
|
|
6
|
+
*/
|
|
7
|
+
import { type Chunk, type SupportedLanguage } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Chunker that uses web-tree-sitter (WASM) to extract semantic code chunks.
|
|
10
|
+
* Provides 100% platform compatibility - no native compilation required.
|
|
11
|
+
*/
|
|
12
|
+
export declare class Chunker {
|
|
13
|
+
private parser;
|
|
14
|
+
private languages;
|
|
15
|
+
private initialized;
|
|
16
|
+
private wasmBasePath;
|
|
17
|
+
constructor();
|
|
18
|
+
/**
|
|
19
|
+
* Initialize web-tree-sitter and load language grammars.
|
|
20
|
+
* Must be called before using chunkFile().
|
|
21
|
+
*/
|
|
22
|
+
initialize(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Get the language for a file extension.
|
|
25
|
+
*/
|
|
26
|
+
getLanguageForExtension(ext: string): SupportedLanguage | null;
|
|
27
|
+
/**
|
|
28
|
+
* Check if a language is supported.
|
|
29
|
+
*/
|
|
30
|
+
isLanguageSupported(lang: SupportedLanguage): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Check if a file is a markdown file.
|
|
33
|
+
*/
|
|
34
|
+
private isMarkdownFile;
|
|
35
|
+
/**
|
|
36
|
+
* Extract chunks from a file.
|
|
37
|
+
*
|
|
38
|
+
* @param filepath - Path to the file (used for extension detection and context headers)
|
|
39
|
+
* @param content - File content to parse
|
|
40
|
+
* @param maxChunkSize - Maximum chunk size in characters (default: 2000)
|
|
41
|
+
* @returns Array of extracted chunks
|
|
42
|
+
*/
|
|
43
|
+
chunkFile(filepath: string, content: string, maxChunkSize?: number): Chunk[];
|
|
44
|
+
/**
|
|
45
|
+
* Extract chunks from a syntax tree.
|
|
46
|
+
*/
|
|
47
|
+
private extractChunks;
|
|
48
|
+
/**
|
|
49
|
+
* Recursively traverse nodes to extract chunks.
|
|
50
|
+
* Tracks parent context (class name) for context headers.
|
|
51
|
+
*/
|
|
52
|
+
private traverseNode;
|
|
53
|
+
/**
|
|
54
|
+
* Convert a syntax node to a chunk.
|
|
55
|
+
*/
|
|
56
|
+
private nodeToChunk;
|
|
57
|
+
/**
|
|
58
|
+
* Extract the signature line (first line of function/class declaration).
|
|
59
|
+
*/
|
|
60
|
+
private extractSignature;
|
|
61
|
+
/**
|
|
62
|
+
* Extract docstring from a function/class node.
|
|
63
|
+
*/
|
|
64
|
+
private extractDocstring;
|
|
65
|
+
/**
|
|
66
|
+
* Check if a node is exported/public.
|
|
67
|
+
*/
|
|
68
|
+
private extractIsExported;
|
|
69
|
+
/**
|
|
70
|
+
* Helper to check for visibility modifiers in a node.
|
|
71
|
+
*/
|
|
72
|
+
private hasVisibilityModifier;
|
|
73
|
+
/**
|
|
74
|
+
* Extract decorator names from a node.
|
|
75
|
+
*/
|
|
76
|
+
private extractDecoratorNames;
|
|
77
|
+
/**
|
|
78
|
+
* Helper to find a child node of specific types.
|
|
79
|
+
*/
|
|
80
|
+
private findChildOfType;
|
|
81
|
+
/**
|
|
82
|
+
* Build a context header for a chunk.
|
|
83
|
+
*/
|
|
84
|
+
private buildContextHeader;
|
|
85
|
+
/**
|
|
86
|
+
* Extract the name of a function/class/method from its node.
|
|
87
|
+
*/
|
|
88
|
+
private extractName;
|
|
89
|
+
/**
|
|
90
|
+
* Create a module-level chunk for the entire file.
|
|
91
|
+
*/
|
|
92
|
+
private createModuleChunk;
|
|
93
|
+
/**
|
|
94
|
+
* Chunk markdown files with heading-aware splitting and overlap.
|
|
95
|
+
*
|
|
96
|
+
* Strategy:
|
|
97
|
+
* 1. Try to split at heading boundaries (# lines)
|
|
98
|
+
* 2. Use sliding window with overlap between chunks
|
|
99
|
+
* 3. Merge small final chunks to avoid orphans
|
|
100
|
+
*/
|
|
101
|
+
private chunkMarkdown;
|
|
102
|
+
/**
|
|
103
|
+
* Create a chunk from markdown content.
|
|
104
|
+
*/
|
|
105
|
+
private createMarkdownChunk;
|
|
106
|
+
/**
|
|
107
|
+
* Enforce size limits: split oversized chunks and merge tiny ones.
|
|
108
|
+
*
|
|
109
|
+
* @param overlapLines - Number of lines to overlap between chunks (for context continuity)
|
|
110
|
+
*/
|
|
111
|
+
private enforceSizeLimits;
|
|
112
|
+
/**
|
|
113
|
+
* Split an oversized chunk by lines.
|
|
114
|
+
* Tries to split at natural boundaries (empty lines, statement ends).
|
|
115
|
+
*
|
|
116
|
+
* @param overlapLines - Number of lines from previous chunk to include for context
|
|
117
|
+
*/
|
|
118
|
+
private splitChunkByLines;
|
|
119
|
+
/**
|
|
120
|
+
* Create a chunk from a split portion.
|
|
121
|
+
*/
|
|
122
|
+
private createSplitChunk;
|
|
123
|
+
/**
|
|
124
|
+
* Extract class name from a context header string.
|
|
125
|
+
*/
|
|
126
|
+
private extractClassFromContext;
|
|
127
|
+
/**
|
|
128
|
+
* Merge small adjacent chunks of the same type to avoid fragment explosion.
|
|
129
|
+
*/
|
|
130
|
+
private mergeSmallChunks;
|
|
131
|
+
/**
|
|
132
|
+
* Close the parser and free resources.
|
|
133
|
+
*/
|
|
134
|
+
close(): void;
|
|
135
|
+
}
|