@devilsdev/rag-pipeline-utils 2.4.5 → 2.5.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/.ragrc.schema.json +83 -192
- package/CHANGELOG.md +345 -0
- package/README.md +418 -117
- package/SECURITY.md +5 -5
- package/dist/experimental.cjs +2 -0
- package/dist/experimental.d.ts +1 -0
- package/dist/experimental.mjs +5 -0
- package/dist/index.cjs +4992 -1242
- package/dist/index.d.ts +80 -2
- package/dist/index.mjs +1 -1
- package/dist/types/agentic/agentic-pipeline.d.ts +103 -0
- package/dist/types/agentic/iterative-retriever.d.ts +85 -0
- package/dist/types/agentic/query-planner.d.ts +145 -0
- package/dist/types/agentic/self-critique.d.ts +45 -0
- package/dist/types/ai/adaptive-retrieval.d.ts +44 -0
- package/dist/types/ai/federation/federated-learning-coordinator.d.ts +62 -0
- package/dist/types/ai/multimodal-processing.d.ts +32 -0
- package/dist/types/ai/multimodal.d.ts +39 -0
- package/dist/types/ai/retrieval-engine.d.ts +37 -0
- package/dist/types/ai/training/model-training-orchestrator.d.ts +31 -0
- package/dist/types/chunking/chunking-engine.d.ts +70 -0
- package/dist/types/chunking/strategies/fixed-size.d.ts +17 -0
- package/dist/types/chunking/strategies/recursive.d.ts +20 -0
- package/dist/types/chunking/strategies/semantic.d.ts +19 -0
- package/dist/types/chunking/strategies/sentence.d.ts +13 -0
- package/dist/types/chunking/strategies/structure-aware.d.ts +19 -0
- package/dist/types/citation/citation-tracker.d.ts +64 -0
- package/dist/types/citation/hallucination-detector.d.ts +40 -0
- package/dist/types/citation/source-mapper.d.ts +37 -0
- package/dist/types/connectors/anthropic-connector.d.ts +92 -0
- package/dist/types/connectors/base-connector.d.ts +87 -0
- package/dist/types/connectors/cohere-connector.d.ts +89 -0
- package/dist/types/connectors/connector-utils.d.ts +110 -0
- package/dist/types/connectors/local-embedder.d.ts +86 -0
- package/dist/types/connectors/memory-retriever.d.ts +91 -0
- package/dist/types/connectors/ollama-connector.d.ts +110 -0
- package/dist/types/connectors/openai-connector.d.ts +114 -0
- package/dist/types/connectors/rag-prompt.d.ts +40 -0
- package/dist/types/cost/cost-calculator.d.ts +152 -0
- package/dist/types/cost/cost-tracker.d.ts +126 -0
- package/dist/types/cost/token-budget.d.ts +110 -0
- package/dist/types/dag/core/error-context.d.ts +239 -0
- package/dist/types/dag/core/execution-scheduler.d.ts +191 -0
- package/dist/types/dag/core/topology-validation.d.ts +226 -0
- package/dist/types/dag/dag-engine.d.ts +156 -0
- package/dist/types/dag/dag-node.d.ts +37 -0
- package/dist/types/dashboard/dashboard-generator.d.ts +127 -0
- package/dist/types/dashboard/metrics-aggregator.d.ts +218 -0
- package/dist/types/debug/execution-tracer.d.ts +57 -0
- package/dist/types/debug/trace-inspector.d.ts +31 -0
- package/dist/types/ecosystem/plugin-certification.d.ts +169 -0
- package/dist/types/enterprise/data-governance.d.ts +203 -0
- package/dist/types/enterprise/multi-tenancy.d.ts +137 -0
- package/dist/types/enterprise/sso-integration.d.ts +420 -0
- package/dist/types/evaluate/context-metrics.d.ts +55 -0
- package/dist/types/evaluate/faithfulness.d.ts +41 -0
- package/dist/types/evaluate/groundedness.d.ts +60 -0
- package/dist/types/evaluate/pipeline-evaluator.d.ts +88 -0
- package/dist/types/evaluate/relevance.d.ts +21 -0
- package/dist/types/evaluate/scoring.d.ts +123 -0
- package/dist/types/experimental.d.ts +15 -0
- package/dist/types/graph/entity-extractor.d.ts +84 -0
- package/dist/types/graph/graph-index.d.ts +49 -0
- package/dist/types/graph/graph-retriever.d.ts +81 -0
- package/dist/types/graph/knowledge-graph.d.ts +129 -0
- package/dist/types/guardrails/guardrails-pipeline.d.ts +63 -0
- package/dist/types/guardrails/injection-detector.d.ts +47 -0
- package/dist/types/guardrails/post-generation-guard.d.ts +61 -0
- package/dist/types/guardrails/pre-retrieval-guard.d.ts +64 -0
- package/dist/types/guardrails/retrieval-guard.d.ts +57 -0
- package/dist/types/mcp/mcp-server.d.ts +85 -0
- package/dist/types/mcp/mcp-tool-builder.d.ts +55 -0
- package/dist/types/reranker/cascade-reranker.d.ts +40 -0
- package/dist/types/reranker/embedding-reranker.d.ts +77 -0
- package/dist/types/reranker/scoring-reranker.d.ts +41 -0
- package/dist/types/retrieval/bm25-search.d.ts +43 -0
- package/dist/types/retrieval/file-vector-store.d.ts +87 -0
- package/dist/types/retrieval/hybrid-retriever.d.ts +35 -0
- package/dist/types/retrieval/rank-fusion.d.ts +10 -0
- package/dist/types/security/jwt-config.d.ts +80 -0
- package/dist/types/security/jwt-validator.d.ts +195 -0
- package/dist/types/security/plugin-sandbox.d.ts +158 -0
- package/dist/types/security/plugin-signature-verifier.d.ts +139 -0
- package/dist/types/security/validator.d.ts +5 -0
- package/dist/types/streaming/embedding-stream.d.ts +65 -0
- package/dist/types/streaming/sse-adapter.d.ts +59 -0
- package/dist/types/streaming/stream-router.d.ts +88 -0
- package/dist/types/streaming/streaming-embedder.d.ts +99 -0
- package/dist/types/streaming/websocket-adapter.d.ts +71 -0
- package/dist/types/templates/scaffold.d.ts +55 -0
- package/dist/types/templates/template-registry.d.ts +63 -0
- package/dist/types/utils/embed-query.d.ts +20 -0
- package/dist/types/utils/logger.d.ts +50 -0
- package/dist/types/utils/rate-limiter.d.ts +66 -0
- package/dist/types/utils/retry.d.ts +27 -0
- package/dist/types/utils/run-abort.d.ts +34 -0
- package/dist/types/utils/secure-logging.d.ts +51 -0
- package/dist/types/utils/structured-logger.d.ts +126 -0
- package/package.json +53 -29
- package/src/agentic/agentic-pipeline.js +75 -7
- package/src/agentic/iterative-retriever.js +47 -10
- package/src/agentic/query-planner.js +329 -78
- package/src/citation/citation-tracker.js +6 -4
- package/src/citation/hallucination-detector.js +2 -2
- package/src/citation/source-mapper.js +15 -28
- package/src/cli/commands/ai.js +101 -0
- package/src/cli/commands/plugin-hub/event-handlers.js +6 -0
- package/src/cli/commands/plugin-hub/hub-commands.js +0 -74
- package/src/cli/commands/plugin-hub/local-commands.js +143 -0
- package/src/cli/commands/plugin-hub.js +96 -36
- package/src/cli/diagnostics/config-checker.js +76 -63
- package/src/cli/diagnostics/environment-checker.js +62 -31
- package/src/cli/diagnostics/plugin-checker.js +36 -89
- package/src/cli/doctor-command.js +39 -5
- package/src/cli/enhanced-cli-commands.js +72 -217
- package/src/cli/handlers/ai-handler.js +364 -0
- package/src/cli/handlers/diagnostics-handler.js +78 -31
- package/src/cli/handlers/ingest-handler.js +28 -22
- package/src/cli/handlers/init-handler.js +148 -25
- package/src/cli/handlers/query-handler.js +41 -26
- package/src/cli/handlers/run-handler.js +16 -167
- package/src/cli/handlers/serve-handler.js +60 -0
- package/src/cli/services/bench-stats.js +54 -0
- package/src/cli/services/benchmark.js +161 -0
- package/src/cli/services/feedback.js +155 -0
- package/src/cli/services/rag-service.js +322 -0
- package/src/cli/shared/cli-utils.js +24 -7
- package/src/cli/shared/config-context.js +21 -0
- package/src/cli/wizard/config-builder.js +81 -0
- package/src/cli/wizard/init-wizard.js +617 -0
- package/src/config/component-factory.js +125 -0
- package/src/config/load-config.js +29 -10
- package/src/config/load-plugin-config.js +46 -2
- package/src/connectors/anthropic-connector.js +93 -55
- package/src/connectors/base-connector.js +74 -19
- package/src/connectors/cohere-connector.js +88 -32
- package/src/connectors/connector-utils.js +302 -0
- package/src/connectors/local-embedder.js +43 -5
- package/src/connectors/memory-retriever.js +108 -15
- package/src/connectors/ollama-connector.js +83 -49
- package/src/connectors/openai-connector.js +108 -44
- package/src/connectors/rag-prompt.js +88 -0
- package/src/core/create-pipeline.js +209 -60
- package/src/core/plugin-registry.js +87 -11
- package/src/cost/cost-calculator.js +205 -32
- package/src/dev/dev-server.js +166 -11
- package/src/dev/hot-reload.js +40 -1
- package/src/ecosystem/plugin-checks.js +181 -0
- package/src/ecosystem/plugin-hub.js +50 -125
- package/src/ecosystem/plugin-installer.js +298 -0
- package/src/ecosystem/plugin-probe.js +127 -0
- package/src/ecosystem/plugin-scanner.js +185 -0
- package/src/ecosystem/tar-extract.js +114 -0
- package/src/enterprise/audit-logging.js +158 -29
- package/src/enterprise/sso-integration.js +715 -228
- package/src/evaluate/context-metrics.js +28 -16
- package/src/evaluate/evaluator.js +3 -3
- package/src/evaluate/faithfulness.js +18 -14
- package/src/evaluate/groundedness.js +43 -12
- package/src/evaluate/pipeline-evaluator.js +35 -8
- package/src/evaluate/scoring.js +251 -3
- package/src/experimental.js +71 -0
- package/src/guardrails/guardrails-pipeline.js +69 -7
- package/src/guardrails/injection-detector.js +283 -0
- package/src/guardrails/post-generation-guard.js +19 -0
- package/src/guardrails/pre-retrieval-guard.js +36 -36
- package/src/guardrails/retrieval-guard.js +58 -8
- package/src/index.js +36 -1
- package/src/loader/document-reader.js +91 -0
- package/src/reranker/cascade-reranker.js +3 -0
- package/src/reranker/embedding-reranker.js +136 -21
- package/src/reranker/llm-reranker.js +79 -12
- package/src/reranker/scoring-reranker.js +3 -0
- package/src/retrieval/bm25-search.js +12 -6
- package/src/retrieval/file-vector-store.js +212 -0
- package/src/retrieval/hybrid-retriever.js +22 -19
- package/src/security/jwt-config.js +13 -2
- package/src/security/jwt-validator.js +134 -30
- package/src/security/plugin-sandbox.js +18 -1
- package/src/security/plugin-signature-verifier.js +245 -70
- package/src/utils/deprecate.js +69 -0
- package/src/utils/embed-query.js +33 -0
- package/src/utils/input-sanitizer.js +19 -4
- package/src/utils/logger.js +34 -4
- package/src/utils/run-abort.js +102 -0
- package/src/ai/federated-learning.js +0 -211
- package/src/ai/federation.js +0 -71
- package/src/ai/index.js +0 -22
- package/src/ai/model-trainer.js +0 -217
- package/src/ai/model-training.js +0 -202
- package/src/ai/multimodal/multi-modal-processor.js +0 -345
- package/src/ai/orchestrator.js +0 -65
- package/src/ai/retrieval/adaptive-retrieval-engine.js +0 -398
- package/src/cli/commands/ai-ml/adaptive-commands.js +0 -178
- package/src/cli/commands/ai-ml/federated-commands.js +0 -207
- package/src/cli/commands/ai-ml/index.js +0 -17
- package/src/cli/commands/ai-ml/multimodal-commands.js +0 -187
- package/src/cli/commands/ai-ml/shared.js +0 -51
- package/src/cli/commands/ai-ml/training-commands.js +0 -228
- package/src/cli/commands/ai-ml.js +0 -386
- package/src/cli/commands/docs.js +0 -228
- package/src/cli/commands/dx.js +0 -538
- package/src/cli/commands/wizard.js +0 -627
- package/src/cli/input-sanitization-middleware.js +0 -356
- package/src/cli/interactive-wizard.js +0 -373
- package/src/cli/templates/development.template.json +0 -64
- package/src/cli/templates/minimal.template.json +0 -33
- package/src/cli/templates/production.template.json +0 -104
- package/src/cli/templates/testing.template.json +0 -45
- package/src/cli/wizard/configuration.js +0 -148
- package/src/cli/wizard/observability.js +0 -79
- package/src/cli/wizard/performance.js +0 -76
- package/src/cli/wizard/plugin-selection.js +0 -297
- package/src/cli/wizard/preview-save.js +0 -172
- package/src/cli/wizard/project-setup.js +0 -66
- package/src/core/pipeline-factory.js +0 -59
- package/src/dag/enhanced-dag-engine.js +0 -894
- package/src/dx/index.js +0 -40
- package/src/dx/integration-templates.js +0 -662
- package/src/dx/performance-profiler.js +0 -968
- package/src/dx/realtime-debugger.js +0 -755
- package/src/dx/visual-pipeline-builder.js +0 -578
- package/src/ingest.js +0 -59
- package/src/pipeline/pipeline.js +0 -18
- package/src/query.js +0 -117
package/src/dev/dev-server.js
CHANGED
|
@@ -7,20 +7,25 @@
|
|
|
7
7
|
* for rapid plugin and configuration development.
|
|
8
8
|
*
|
|
9
9
|
* Features:
|
|
10
|
+
* - Local HTTP API over the configured pipeline:
|
|
11
|
+
* GET /health -> { status, uptime }
|
|
12
|
+
* POST /query -> { question, topK? } => { answer, results, citations }
|
|
13
|
+
* Binds to 127.0.0.1 by default, JSON only, no CORS headers, request
|
|
14
|
+
* bodies limited to 64 KB, no static file serving.
|
|
10
15
|
* - Automatic hot reloading of plugins and configuration
|
|
11
|
-
* - Live feedback on reload status
|
|
12
|
-
* - Error notifications
|
|
13
16
|
* - Statistics dashboard
|
|
14
|
-
* - WebSocket support for real-time updates (optional)
|
|
15
17
|
*
|
|
16
18
|
* @module dev/dev-server
|
|
17
19
|
* @since 2.4.0
|
|
18
20
|
*/
|
|
19
21
|
|
|
22
|
+
const http = require('http');
|
|
20
23
|
const { EventEmitter } = require('events');
|
|
21
24
|
const { createHotReloadManager } = require('./hot-reload');
|
|
22
25
|
const { logger } = require('../utils/logger');
|
|
23
26
|
|
|
27
|
+
const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '[::1]']);
|
|
28
|
+
|
|
24
29
|
/**
|
|
25
30
|
* Development Server
|
|
26
31
|
*
|
|
@@ -31,9 +36,12 @@ class DevServer extends EventEmitter {
|
|
|
31
36
|
super();
|
|
32
37
|
|
|
33
38
|
this.options = {
|
|
34
|
-
port: options.port
|
|
35
|
-
host: options.host || '
|
|
36
|
-
watchPaths: options.watchPaths || [
|
|
39
|
+
port: options.port ?? 3000, // 0 picks a free port
|
|
40
|
+
host: options.host || '127.0.0.1',
|
|
41
|
+
watchPaths: options.watchPaths || [],
|
|
42
|
+
maxBodyBytes: options.maxBodyBytes || 64 * 1024,
|
|
43
|
+
// Optional custom handler: async ({ question, topK }) => result
|
|
44
|
+
query: options.query || null,
|
|
37
45
|
configPath: options.configPath || '.ragrc.json',
|
|
38
46
|
verbose: options.verbose !== false,
|
|
39
47
|
autoReload: options.autoReload !== false,
|
|
@@ -147,12 +155,23 @@ class DevServer extends EventEmitter {
|
|
|
147
155
|
// Start hot reload
|
|
148
156
|
await this.hotReload.start();
|
|
149
157
|
|
|
158
|
+
this.server = http.createServer((req, res) => {
|
|
159
|
+
this._handleRequest(req, res).catch((error) => {
|
|
160
|
+
this._send(res, 500, { error: error.message });
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
await new Promise((resolve, reject) => {
|
|
164
|
+
this.server.once('error', reject);
|
|
165
|
+
this.server.listen(this.options.port, this.options.host, () => {
|
|
166
|
+
this.server.off('error', reject);
|
|
167
|
+
resolve();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
this.port = this.server.address().port;
|
|
171
|
+
|
|
150
172
|
this.isRunning = true;
|
|
151
173
|
|
|
152
|
-
this._log(
|
|
153
|
-
`Development server started on ${this.options.host}:${this.options.port}`,
|
|
154
|
-
'success',
|
|
155
|
-
);
|
|
174
|
+
this._log(`Development server listening on ${this.url}`, 'success');
|
|
156
175
|
this._log(`Watching: ${this.options.watchPaths.join(', ')}`, 'info');
|
|
157
176
|
this._log(
|
|
158
177
|
'Hot reload enabled - changes will be applied automatically',
|
|
@@ -178,6 +197,11 @@ class DevServer extends EventEmitter {
|
|
|
178
197
|
// Stop hot reload
|
|
179
198
|
await this.hotReload.stop();
|
|
180
199
|
|
|
200
|
+
if (this.server) {
|
|
201
|
+
await new Promise((resolve) => this.server.close(() => resolve()));
|
|
202
|
+
this.server = null;
|
|
203
|
+
}
|
|
204
|
+
|
|
181
205
|
this.isRunning = false;
|
|
182
206
|
|
|
183
207
|
const uptime = this.startTime ? Date.now() - this.startTime : 0;
|
|
@@ -202,7 +226,8 @@ class DevServer extends EventEmitter {
|
|
|
202
226
|
isRunning: this.isRunning,
|
|
203
227
|
uptime,
|
|
204
228
|
host: this.options.host,
|
|
205
|
-
port: this.options.port,
|
|
229
|
+
port: this.port ?? this.options.port,
|
|
230
|
+
url: this.isRunning ? this.url : null,
|
|
206
231
|
watchPaths: this.options.watchPaths,
|
|
207
232
|
configPath: this.options.configPath,
|
|
208
233
|
hotReload: {
|
|
@@ -249,6 +274,136 @@ ${this.options.watchPaths.map((p) => `║ - ${p.padEnd(56)}║`).join('\n')}
|
|
|
249
274
|
* @param {string} type - Type ('plugin' or 'config')
|
|
250
275
|
* @returns {Promise<void>}
|
|
251
276
|
*/
|
|
277
|
+
/** @returns {string} Base URL of the running server */
|
|
278
|
+
get url() {
|
|
279
|
+
return `http://${this.options.host}:${this.port ?? this.options.port}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Route a request. Only GET /health and POST /query exist.
|
|
284
|
+
* @private
|
|
285
|
+
*/
|
|
286
|
+
async _handleRequest(req, res) {
|
|
287
|
+
// DNS rebinding: a web page can point its own domain at 127.0.0.1 and
|
|
288
|
+
// then call this server same-origin. When bound to loopback, only accept
|
|
289
|
+
// requests addressed to a loopback name.
|
|
290
|
+
if (
|
|
291
|
+
LOOPBACK_HOSTS.has(this.options.host) &&
|
|
292
|
+
!this._isLoopbackHostHeader(req)
|
|
293
|
+
) {
|
|
294
|
+
return this._send(res, 403, { error: 'Forbidden host' });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const { pathname } = new URL(req.url, 'http://localhost');
|
|
298
|
+
|
|
299
|
+
if (pathname === '/health') {
|
|
300
|
+
if (req.method !== 'GET')
|
|
301
|
+
return this._send(res, 405, { error: 'Use GET' });
|
|
302
|
+
const uptime = this.startTime ? Date.now() - this.startTime : 0;
|
|
303
|
+
return this._send(res, 200, { status: 'ok', uptime });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (pathname === '/query') {
|
|
307
|
+
if (req.method !== 'POST')
|
|
308
|
+
return this._send(res, 405, { error: 'Use POST' });
|
|
309
|
+
let body;
|
|
310
|
+
try {
|
|
311
|
+
body = await this._readJson(req);
|
|
312
|
+
} catch (error) {
|
|
313
|
+
return this._send(res, error.statusCode || 400, {
|
|
314
|
+
error: error.message,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
const { question, topK } = body || {};
|
|
318
|
+
if (typeof question !== 'string' || !question.trim()) {
|
|
319
|
+
return this._send(res, 400, {
|
|
320
|
+
error: '"question" must be a non-empty string',
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (
|
|
324
|
+
topK !== undefined &&
|
|
325
|
+
!(Number.isInteger(topK) && topK > 0 && topK <= 50)
|
|
326
|
+
) {
|
|
327
|
+
return this._send(res, 400, {
|
|
328
|
+
error: '"topK" must be an integer from 1 to 50',
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
const result = await this._runQuery({ question, topK: topK || 3 });
|
|
332
|
+
return this._send(res, 200, result);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return this._send(res, 404, { error: 'Not found' });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** @private */
|
|
339
|
+
_isLoopbackHostHeader(req) {
|
|
340
|
+
const header = req.headers.host || '';
|
|
341
|
+
const hostname = header.startsWith('[')
|
|
342
|
+
? header.slice(0, header.indexOf(']') + 1)
|
|
343
|
+
: header.split(':')[0];
|
|
344
|
+
return LOOPBACK_HOSTS.has(hostname.toLowerCase());
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Answer with the custom `query` option, or the pipeline described by the
|
|
349
|
+
* config file (reloaded per request, so config edits apply immediately).
|
|
350
|
+
* @private
|
|
351
|
+
*/
|
|
352
|
+
async _runQuery({ question, topK }) {
|
|
353
|
+
if (this.options.query) return this.options.query({ question, topK });
|
|
354
|
+
const { loadCliConfig } = require('../cli/shared/config-context');
|
|
355
|
+
const { query } = require('../cli/services/rag-service');
|
|
356
|
+
const { config, configDir } = loadCliConfig({
|
|
357
|
+
config: this.options.configPath,
|
|
358
|
+
});
|
|
359
|
+
return query({ config, configDir, question, topK });
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** @private */
|
|
363
|
+
_readJson(req) {
|
|
364
|
+
return new Promise((resolve, reject) => {
|
|
365
|
+
const type = req.headers['content-type'] || '';
|
|
366
|
+
if (!type.includes('application/json')) {
|
|
367
|
+
const error = new Error('Content-Type must be application/json');
|
|
368
|
+
error.statusCode = 415;
|
|
369
|
+
return reject(error);
|
|
370
|
+
}
|
|
371
|
+
let size = 0;
|
|
372
|
+
const chunks = [];
|
|
373
|
+
req.on('data', (chunk) => {
|
|
374
|
+
size += chunk.length;
|
|
375
|
+
if (size > this.options.maxBodyBytes) {
|
|
376
|
+
const error = new Error('Request body too large');
|
|
377
|
+
error.statusCode = 413;
|
|
378
|
+
reject(error);
|
|
379
|
+
// Stop buffering but keep draining, so the 413 response is delivered
|
|
380
|
+
req.removeAllListeners('data');
|
|
381
|
+
req.resume();
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
chunks.push(chunk);
|
|
385
|
+
});
|
|
386
|
+
req.on('end', () => {
|
|
387
|
+
try {
|
|
388
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}'));
|
|
389
|
+
} catch {
|
|
390
|
+
reject(new Error('Invalid JSON body'));
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
req.on('error', reject);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** @private */
|
|
398
|
+
_send(res, status, payload) {
|
|
399
|
+
if (res.headersSent) return;
|
|
400
|
+
res.writeHead(status, {
|
|
401
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
402
|
+
'X-Content-Type-Options': 'nosniff',
|
|
403
|
+
});
|
|
404
|
+
res.end(JSON.stringify(payload));
|
|
405
|
+
}
|
|
406
|
+
|
|
252
407
|
async triggerReload(filePath, type = 'plugin') {
|
|
253
408
|
this._log(`Manually triggering reload: ${filePath}`, 'info');
|
|
254
409
|
await this.hotReload.triggerReload(filePath, type);
|
package/src/dev/hot-reload.js
CHANGED
|
@@ -28,6 +28,42 @@ const {
|
|
|
28
28
|
} = require('../utils/error-formatter');
|
|
29
29
|
const { logger } = require('../utils/logger');
|
|
30
30
|
|
|
31
|
+
const RELOADABLE_EXTENSIONS = new Set(['.js', '.cjs', '.mjs']);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* chokidar 4 removed glob support, so glob strings in `ignored` were treated
|
|
35
|
+
* as literal paths and matched nothing. Convert them to one matcher function.
|
|
36
|
+
*
|
|
37
|
+
* @param {Array<string|RegExp|Function>} patterns
|
|
38
|
+
* @returns {(filePath: string) => boolean}
|
|
39
|
+
*/
|
|
40
|
+
function toIgnoreMatcher(patterns) {
|
|
41
|
+
const matchers = patterns.map((pattern) => {
|
|
42
|
+
if (typeof pattern === 'function') return pattern;
|
|
43
|
+
if (pattern instanceof RegExp) return (p) => pattern.test(p);
|
|
44
|
+
const regex = new RegExp(
|
|
45
|
+
'^' +
|
|
46
|
+
pattern
|
|
47
|
+
.split('**/')
|
|
48
|
+
.map((part) =>
|
|
49
|
+
part
|
|
50
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
51
|
+
.replace(/\*\*/g, '\u0000') // a remaining ** (e.g. trailing) spans directories
|
|
52
|
+
.replace(/\*/g, '[^/]*')
|
|
53
|
+
.replace(/\?/g, '[^/]')
|
|
54
|
+
.replace(/\u0000/g, '.*'),
|
|
55
|
+
)
|
|
56
|
+
.join('(?:.*/)?') +
|
|
57
|
+
'$',
|
|
58
|
+
);
|
|
59
|
+
return (p) => regex.test(p);
|
|
60
|
+
});
|
|
61
|
+
return (filePath) => {
|
|
62
|
+
const normalized = filePath.split(path.sep).join('/');
|
|
63
|
+
return matchers.some((match) => match(normalized));
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
31
67
|
/**
|
|
32
68
|
* Hot Reload Manager
|
|
33
69
|
*
|
|
@@ -135,7 +171,7 @@ class HotReloadManager extends EventEmitter {
|
|
|
135
171
|
*/
|
|
136
172
|
async _watchPlugins() {
|
|
137
173
|
const watcher = chokidar.watch(this.options.watchPaths, {
|
|
138
|
-
ignored: this.options.ignorePatterns,
|
|
174
|
+
ignored: toIgnoreMatcher(this.options.ignorePatterns),
|
|
139
175
|
persistent: true,
|
|
140
176
|
ignoreInitial: true,
|
|
141
177
|
awaitWriteFinish: {
|
|
@@ -206,6 +242,8 @@ class HotReloadManager extends EventEmitter {
|
|
|
206
242
|
* @param {string} filePath - Changed file path
|
|
207
243
|
*/
|
|
208
244
|
_handlePluginChange(filePath) {
|
|
245
|
+
// Only JavaScript modules can be re-required; ignore data and docs.
|
|
246
|
+
if (!RELOADABLE_EXTENSIONS.has(path.extname(filePath))) return;
|
|
209
247
|
this._log(`Plugin changed: ${filePath}`);
|
|
210
248
|
|
|
211
249
|
// Debounce rapid changes
|
|
@@ -803,6 +841,7 @@ function createHotReloadManager(options = {}) {
|
|
|
803
841
|
}
|
|
804
842
|
|
|
805
843
|
module.exports = {
|
|
844
|
+
toIgnoreMatcher,
|
|
806
845
|
HotReloadManager,
|
|
807
846
|
createHotReloadManager,
|
|
808
847
|
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Automated certification checks for a local plugin. Every score is computed
|
|
5
|
+
* from the plugin's files: the stage contract (checked in a separate
|
|
6
|
+
* process), the static security scan, tests and documentation. Nothing is
|
|
7
|
+
* assumed; checks that cannot be computed score 0 with a reason.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { scanPlugin } = require('./plugin-scanner');
|
|
13
|
+
const { probeContract } = require('./plugin-probe');
|
|
14
|
+
const { relativeWithin } = require('./tar-extract');
|
|
15
|
+
|
|
16
|
+
/** Automated score needed for each level (manual review/audit are separate). */
|
|
17
|
+
const LEVELS = [
|
|
18
|
+
{
|
|
19
|
+
level: 'ENTERPRISE',
|
|
20
|
+
score: 95,
|
|
21
|
+
requires: 'manual review and security audit',
|
|
22
|
+
},
|
|
23
|
+
{ level: 'VERIFIED', score: 80, requires: 'manual review' },
|
|
24
|
+
{ level: 'BASIC', score: 60, requires: null },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const TEST_FILE = /(^|[\\/])(__tests__|tests?)[\\/]|\.(test|spec)\.[cm]?js$/;
|
|
28
|
+
|
|
29
|
+
function listFiles(dir) {
|
|
30
|
+
const out = [];
|
|
31
|
+
const walk = (current) => {
|
|
32
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
33
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
34
|
+
const full = path.join(current, entry.name);
|
|
35
|
+
if (entry.isDirectory()) walk(full);
|
|
36
|
+
else if (entry.isFile()) out.push(relativeWithin(dir, full));
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
walk(dir);
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function securityCheck(root) {
|
|
44
|
+
const scan = scanPlugin(root);
|
|
45
|
+
const { high, medium, low } = scan.counts;
|
|
46
|
+
const score = Math.max(0, 100 - 40 * high - 10 * medium - 2 * low);
|
|
47
|
+
return {
|
|
48
|
+
id: 'security',
|
|
49
|
+
label: 'Security scan',
|
|
50
|
+
weight: 30,
|
|
51
|
+
score,
|
|
52
|
+
details: scan.findings.map(
|
|
53
|
+
(f) =>
|
|
54
|
+
`${f.severity}: ${f.message} (${f.file}${f.line ? `:${f.line}` : ''})`,
|
|
55
|
+
),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function testsCheck(files, manifest) {
|
|
60
|
+
const testFiles = files.filter((f) => TEST_FILE.test(f));
|
|
61
|
+
const script = manifest.scripts?.test;
|
|
62
|
+
const realScript = Boolean(script) && !/no test specified/.test(script);
|
|
63
|
+
let score = 0;
|
|
64
|
+
if (testFiles.length > 0) score = realScript ? 100 : 60;
|
|
65
|
+
return {
|
|
66
|
+
id: 'tests',
|
|
67
|
+
label: 'Tests',
|
|
68
|
+
weight: 15,
|
|
69
|
+
score,
|
|
70
|
+
details: [
|
|
71
|
+
`${testFiles.length} test file(s)`,
|
|
72
|
+
realScript
|
|
73
|
+
? `test script: ${script}`
|
|
74
|
+
: 'no "test" script in package.json',
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function docsCheck(root, files, manifest) {
|
|
80
|
+
const readme = files.find((f) => /^readme(\.md|\.txt)?$/i.test(f));
|
|
81
|
+
const readmeLength = readme
|
|
82
|
+
? fs.readFileSync(path.join(root, readme), 'utf8').trim().length
|
|
83
|
+
: 0;
|
|
84
|
+
const parts = [
|
|
85
|
+
[Boolean(readme), 50, readme ? `${readme} present` : 'no README'],
|
|
86
|
+
[
|
|
87
|
+
readmeLength >= 300,
|
|
88
|
+
20,
|
|
89
|
+
`README length ${readmeLength} chars (300+ expected)`,
|
|
90
|
+
],
|
|
91
|
+
[
|
|
92
|
+
Boolean(manifest.description),
|
|
93
|
+
15,
|
|
94
|
+
manifest.description
|
|
95
|
+
? 'package description present'
|
|
96
|
+
: 'no package description',
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
Boolean(manifest.license),
|
|
100
|
+
15,
|
|
101
|
+
manifest.license ? `license: ${manifest.license}` : 'no license field',
|
|
102
|
+
],
|
|
103
|
+
];
|
|
104
|
+
return {
|
|
105
|
+
id: 'documentation',
|
|
106
|
+
label: 'Documentation',
|
|
107
|
+
weight: 20,
|
|
108
|
+
score: parts.reduce((sum, [ok, points]) => sum + (ok ? points : 0), 0),
|
|
109
|
+
details: parts.map(([ok, , text]) => `${ok ? 'ok' : 'missing'}: ${text}`),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {string} pluginPath - Plugin directory or single .js file
|
|
115
|
+
* @param {object} options
|
|
116
|
+
* @param {string} [options.stage] - Overrides package.json "ragPipeline.stage"
|
|
117
|
+
* @returns {Promise<{plugin: string, stage: string, checks: object[], score: number, level: string|null, nextLevel: object|null}>}
|
|
118
|
+
*/
|
|
119
|
+
async function runPluginChecks(pluginPath, options = {}) {
|
|
120
|
+
const absolute = path.resolve(pluginPath);
|
|
121
|
+
if (!fs.existsSync(absolute))
|
|
122
|
+
throw new Error(`Plugin not found: ${pluginPath}`);
|
|
123
|
+
const isDir = fs.statSync(absolute).isDirectory();
|
|
124
|
+
const root = isDir ? absolute : path.dirname(absolute);
|
|
125
|
+
const manifestPath = path.join(root, 'package.json');
|
|
126
|
+
const manifest =
|
|
127
|
+
isDir && fs.existsSync(manifestPath)
|
|
128
|
+
? JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
|
|
129
|
+
: {};
|
|
130
|
+
const stage = options.stage || manifest.ragPipeline?.stage;
|
|
131
|
+
if (!stage) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
'Unknown stage. Pass --stage or set "ragPipeline.stage" in package.json.',
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const entry = isDir
|
|
137
|
+
? path.resolve(root, manifest.main || 'index.js')
|
|
138
|
+
: absolute;
|
|
139
|
+
const files = isDir ? listFiles(root) : [path.basename(absolute)];
|
|
140
|
+
|
|
141
|
+
const probe = await probeContract(entry, stage);
|
|
142
|
+
const contract = {
|
|
143
|
+
id: 'contract',
|
|
144
|
+
label: `${stage} contract`,
|
|
145
|
+
weight: 35,
|
|
146
|
+
score: probe.ok ? 100 : 0,
|
|
147
|
+
details: probe.ok
|
|
148
|
+
? [`methods: ${probe.methods.join(', ')}`]
|
|
149
|
+
: [probe.error || `missing: ${probe.missing.join(', ')}`],
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const checks = [
|
|
153
|
+
contract,
|
|
154
|
+
isDir ? securityCheck(root) : securityCheck(absolute),
|
|
155
|
+
testsCheck(files, manifest),
|
|
156
|
+
docsCheck(root, isDir ? files : [], manifest),
|
|
157
|
+
];
|
|
158
|
+
const totalWeight = checks.reduce((sum, c) => sum + c.weight, 0);
|
|
159
|
+
const score = Math.round(
|
|
160
|
+
checks.reduce((sum, c) => sum + c.score * c.weight, 0) / totalWeight,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// A failed contract means the plugin cannot run, whatever else scores.
|
|
164
|
+
const eligible =
|
|
165
|
+
contract.score === 100 ? LEVELS.filter((l) => score >= l.score) : [];
|
|
166
|
+
const best = eligible[0] || null;
|
|
167
|
+
const nextLevel = [...LEVELS].reverse().find((l) => score < l.score) || null;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
plugin: manifest.name || path.basename(absolute),
|
|
171
|
+
version: manifest.version || null,
|
|
172
|
+
stage,
|
|
173
|
+
checks,
|
|
174
|
+
score,
|
|
175
|
+
level: best ? best.level : null,
|
|
176
|
+
pendingReview: best ? best.requires : null,
|
|
177
|
+
nextLevel: nextLevel && { level: nextLevel.level, score: nextLevel.score },
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = { runPluginChecks, LEVELS };
|