@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.
Files changed (225) hide show
  1. package/.ragrc.schema.json +83 -192
  2. package/CHANGELOG.md +345 -0
  3. package/README.md +418 -117
  4. package/SECURITY.md +5 -5
  5. package/dist/experimental.cjs +2 -0
  6. package/dist/experimental.d.ts +1 -0
  7. package/dist/experimental.mjs +5 -0
  8. package/dist/index.cjs +4992 -1242
  9. package/dist/index.d.ts +80 -2
  10. package/dist/index.mjs +1 -1
  11. package/dist/types/agentic/agentic-pipeline.d.ts +103 -0
  12. package/dist/types/agentic/iterative-retriever.d.ts +85 -0
  13. package/dist/types/agentic/query-planner.d.ts +145 -0
  14. package/dist/types/agentic/self-critique.d.ts +45 -0
  15. package/dist/types/ai/adaptive-retrieval.d.ts +44 -0
  16. package/dist/types/ai/federation/federated-learning-coordinator.d.ts +62 -0
  17. package/dist/types/ai/multimodal-processing.d.ts +32 -0
  18. package/dist/types/ai/multimodal.d.ts +39 -0
  19. package/dist/types/ai/retrieval-engine.d.ts +37 -0
  20. package/dist/types/ai/training/model-training-orchestrator.d.ts +31 -0
  21. package/dist/types/chunking/chunking-engine.d.ts +70 -0
  22. package/dist/types/chunking/strategies/fixed-size.d.ts +17 -0
  23. package/dist/types/chunking/strategies/recursive.d.ts +20 -0
  24. package/dist/types/chunking/strategies/semantic.d.ts +19 -0
  25. package/dist/types/chunking/strategies/sentence.d.ts +13 -0
  26. package/dist/types/chunking/strategies/structure-aware.d.ts +19 -0
  27. package/dist/types/citation/citation-tracker.d.ts +64 -0
  28. package/dist/types/citation/hallucination-detector.d.ts +40 -0
  29. package/dist/types/citation/source-mapper.d.ts +37 -0
  30. package/dist/types/connectors/anthropic-connector.d.ts +92 -0
  31. package/dist/types/connectors/base-connector.d.ts +87 -0
  32. package/dist/types/connectors/cohere-connector.d.ts +89 -0
  33. package/dist/types/connectors/connector-utils.d.ts +110 -0
  34. package/dist/types/connectors/local-embedder.d.ts +86 -0
  35. package/dist/types/connectors/memory-retriever.d.ts +91 -0
  36. package/dist/types/connectors/ollama-connector.d.ts +110 -0
  37. package/dist/types/connectors/openai-connector.d.ts +114 -0
  38. package/dist/types/connectors/rag-prompt.d.ts +40 -0
  39. package/dist/types/cost/cost-calculator.d.ts +152 -0
  40. package/dist/types/cost/cost-tracker.d.ts +126 -0
  41. package/dist/types/cost/token-budget.d.ts +110 -0
  42. package/dist/types/dag/core/error-context.d.ts +239 -0
  43. package/dist/types/dag/core/execution-scheduler.d.ts +191 -0
  44. package/dist/types/dag/core/topology-validation.d.ts +226 -0
  45. package/dist/types/dag/dag-engine.d.ts +156 -0
  46. package/dist/types/dag/dag-node.d.ts +37 -0
  47. package/dist/types/dashboard/dashboard-generator.d.ts +127 -0
  48. package/dist/types/dashboard/metrics-aggregator.d.ts +218 -0
  49. package/dist/types/debug/execution-tracer.d.ts +57 -0
  50. package/dist/types/debug/trace-inspector.d.ts +31 -0
  51. package/dist/types/ecosystem/plugin-certification.d.ts +169 -0
  52. package/dist/types/enterprise/data-governance.d.ts +203 -0
  53. package/dist/types/enterprise/multi-tenancy.d.ts +137 -0
  54. package/dist/types/enterprise/sso-integration.d.ts +420 -0
  55. package/dist/types/evaluate/context-metrics.d.ts +55 -0
  56. package/dist/types/evaluate/faithfulness.d.ts +41 -0
  57. package/dist/types/evaluate/groundedness.d.ts +60 -0
  58. package/dist/types/evaluate/pipeline-evaluator.d.ts +88 -0
  59. package/dist/types/evaluate/relevance.d.ts +21 -0
  60. package/dist/types/evaluate/scoring.d.ts +123 -0
  61. package/dist/types/experimental.d.ts +15 -0
  62. package/dist/types/graph/entity-extractor.d.ts +84 -0
  63. package/dist/types/graph/graph-index.d.ts +49 -0
  64. package/dist/types/graph/graph-retriever.d.ts +81 -0
  65. package/dist/types/graph/knowledge-graph.d.ts +129 -0
  66. package/dist/types/guardrails/guardrails-pipeline.d.ts +63 -0
  67. package/dist/types/guardrails/injection-detector.d.ts +47 -0
  68. package/dist/types/guardrails/post-generation-guard.d.ts +61 -0
  69. package/dist/types/guardrails/pre-retrieval-guard.d.ts +64 -0
  70. package/dist/types/guardrails/retrieval-guard.d.ts +57 -0
  71. package/dist/types/mcp/mcp-server.d.ts +85 -0
  72. package/dist/types/mcp/mcp-tool-builder.d.ts +55 -0
  73. package/dist/types/reranker/cascade-reranker.d.ts +40 -0
  74. package/dist/types/reranker/embedding-reranker.d.ts +77 -0
  75. package/dist/types/reranker/scoring-reranker.d.ts +41 -0
  76. package/dist/types/retrieval/bm25-search.d.ts +43 -0
  77. package/dist/types/retrieval/file-vector-store.d.ts +87 -0
  78. package/dist/types/retrieval/hybrid-retriever.d.ts +35 -0
  79. package/dist/types/retrieval/rank-fusion.d.ts +10 -0
  80. package/dist/types/security/jwt-config.d.ts +80 -0
  81. package/dist/types/security/jwt-validator.d.ts +195 -0
  82. package/dist/types/security/plugin-sandbox.d.ts +158 -0
  83. package/dist/types/security/plugin-signature-verifier.d.ts +139 -0
  84. package/dist/types/security/validator.d.ts +5 -0
  85. package/dist/types/streaming/embedding-stream.d.ts +65 -0
  86. package/dist/types/streaming/sse-adapter.d.ts +59 -0
  87. package/dist/types/streaming/stream-router.d.ts +88 -0
  88. package/dist/types/streaming/streaming-embedder.d.ts +99 -0
  89. package/dist/types/streaming/websocket-adapter.d.ts +71 -0
  90. package/dist/types/templates/scaffold.d.ts +55 -0
  91. package/dist/types/templates/template-registry.d.ts +63 -0
  92. package/dist/types/utils/embed-query.d.ts +20 -0
  93. package/dist/types/utils/logger.d.ts +50 -0
  94. package/dist/types/utils/rate-limiter.d.ts +66 -0
  95. package/dist/types/utils/retry.d.ts +27 -0
  96. package/dist/types/utils/run-abort.d.ts +34 -0
  97. package/dist/types/utils/secure-logging.d.ts +51 -0
  98. package/dist/types/utils/structured-logger.d.ts +126 -0
  99. package/package.json +53 -29
  100. package/src/agentic/agentic-pipeline.js +75 -7
  101. package/src/agentic/iterative-retriever.js +47 -10
  102. package/src/agentic/query-planner.js +329 -78
  103. package/src/citation/citation-tracker.js +6 -4
  104. package/src/citation/hallucination-detector.js +2 -2
  105. package/src/citation/source-mapper.js +15 -28
  106. package/src/cli/commands/ai.js +101 -0
  107. package/src/cli/commands/plugin-hub/event-handlers.js +6 -0
  108. package/src/cli/commands/plugin-hub/hub-commands.js +0 -74
  109. package/src/cli/commands/plugin-hub/local-commands.js +143 -0
  110. package/src/cli/commands/plugin-hub.js +96 -36
  111. package/src/cli/diagnostics/config-checker.js +76 -63
  112. package/src/cli/diagnostics/environment-checker.js +62 -31
  113. package/src/cli/diagnostics/plugin-checker.js +36 -89
  114. package/src/cli/doctor-command.js +39 -5
  115. package/src/cli/enhanced-cli-commands.js +72 -217
  116. package/src/cli/handlers/ai-handler.js +364 -0
  117. package/src/cli/handlers/diagnostics-handler.js +78 -31
  118. package/src/cli/handlers/ingest-handler.js +28 -22
  119. package/src/cli/handlers/init-handler.js +148 -25
  120. package/src/cli/handlers/query-handler.js +41 -26
  121. package/src/cli/handlers/run-handler.js +16 -167
  122. package/src/cli/handlers/serve-handler.js +60 -0
  123. package/src/cli/services/bench-stats.js +54 -0
  124. package/src/cli/services/benchmark.js +161 -0
  125. package/src/cli/services/feedback.js +155 -0
  126. package/src/cli/services/rag-service.js +322 -0
  127. package/src/cli/shared/cli-utils.js +24 -7
  128. package/src/cli/shared/config-context.js +21 -0
  129. package/src/cli/wizard/config-builder.js +81 -0
  130. package/src/cli/wizard/init-wizard.js +617 -0
  131. package/src/config/component-factory.js +125 -0
  132. package/src/config/load-config.js +29 -10
  133. package/src/config/load-plugin-config.js +46 -2
  134. package/src/connectors/anthropic-connector.js +93 -55
  135. package/src/connectors/base-connector.js +74 -19
  136. package/src/connectors/cohere-connector.js +88 -32
  137. package/src/connectors/connector-utils.js +302 -0
  138. package/src/connectors/local-embedder.js +43 -5
  139. package/src/connectors/memory-retriever.js +108 -15
  140. package/src/connectors/ollama-connector.js +83 -49
  141. package/src/connectors/openai-connector.js +108 -44
  142. package/src/connectors/rag-prompt.js +88 -0
  143. package/src/core/create-pipeline.js +209 -60
  144. package/src/core/plugin-registry.js +87 -11
  145. package/src/cost/cost-calculator.js +205 -32
  146. package/src/dev/dev-server.js +166 -11
  147. package/src/dev/hot-reload.js +40 -1
  148. package/src/ecosystem/plugin-checks.js +181 -0
  149. package/src/ecosystem/plugin-hub.js +50 -125
  150. package/src/ecosystem/plugin-installer.js +298 -0
  151. package/src/ecosystem/plugin-probe.js +127 -0
  152. package/src/ecosystem/plugin-scanner.js +185 -0
  153. package/src/ecosystem/tar-extract.js +114 -0
  154. package/src/enterprise/audit-logging.js +158 -29
  155. package/src/enterprise/sso-integration.js +715 -228
  156. package/src/evaluate/context-metrics.js +28 -16
  157. package/src/evaluate/evaluator.js +3 -3
  158. package/src/evaluate/faithfulness.js +18 -14
  159. package/src/evaluate/groundedness.js +43 -12
  160. package/src/evaluate/pipeline-evaluator.js +35 -8
  161. package/src/evaluate/scoring.js +251 -3
  162. package/src/experimental.js +71 -0
  163. package/src/guardrails/guardrails-pipeline.js +69 -7
  164. package/src/guardrails/injection-detector.js +283 -0
  165. package/src/guardrails/post-generation-guard.js +19 -0
  166. package/src/guardrails/pre-retrieval-guard.js +36 -36
  167. package/src/guardrails/retrieval-guard.js +58 -8
  168. package/src/index.js +36 -1
  169. package/src/loader/document-reader.js +91 -0
  170. package/src/reranker/cascade-reranker.js +3 -0
  171. package/src/reranker/embedding-reranker.js +136 -21
  172. package/src/reranker/llm-reranker.js +79 -12
  173. package/src/reranker/scoring-reranker.js +3 -0
  174. package/src/retrieval/bm25-search.js +12 -6
  175. package/src/retrieval/file-vector-store.js +212 -0
  176. package/src/retrieval/hybrid-retriever.js +22 -19
  177. package/src/security/jwt-config.js +13 -2
  178. package/src/security/jwt-validator.js +134 -30
  179. package/src/security/plugin-sandbox.js +18 -1
  180. package/src/security/plugin-signature-verifier.js +245 -70
  181. package/src/utils/deprecate.js +69 -0
  182. package/src/utils/embed-query.js +33 -0
  183. package/src/utils/input-sanitizer.js +19 -4
  184. package/src/utils/logger.js +34 -4
  185. package/src/utils/run-abort.js +102 -0
  186. package/src/ai/federated-learning.js +0 -211
  187. package/src/ai/federation.js +0 -71
  188. package/src/ai/index.js +0 -22
  189. package/src/ai/model-trainer.js +0 -217
  190. package/src/ai/model-training.js +0 -202
  191. package/src/ai/multimodal/multi-modal-processor.js +0 -345
  192. package/src/ai/orchestrator.js +0 -65
  193. package/src/ai/retrieval/adaptive-retrieval-engine.js +0 -398
  194. package/src/cli/commands/ai-ml/adaptive-commands.js +0 -178
  195. package/src/cli/commands/ai-ml/federated-commands.js +0 -207
  196. package/src/cli/commands/ai-ml/index.js +0 -17
  197. package/src/cli/commands/ai-ml/multimodal-commands.js +0 -187
  198. package/src/cli/commands/ai-ml/shared.js +0 -51
  199. package/src/cli/commands/ai-ml/training-commands.js +0 -228
  200. package/src/cli/commands/ai-ml.js +0 -386
  201. package/src/cli/commands/docs.js +0 -228
  202. package/src/cli/commands/dx.js +0 -538
  203. package/src/cli/commands/wizard.js +0 -627
  204. package/src/cli/input-sanitization-middleware.js +0 -356
  205. package/src/cli/interactive-wizard.js +0 -373
  206. package/src/cli/templates/development.template.json +0 -64
  207. package/src/cli/templates/minimal.template.json +0 -33
  208. package/src/cli/templates/production.template.json +0 -104
  209. package/src/cli/templates/testing.template.json +0 -45
  210. package/src/cli/wizard/configuration.js +0 -148
  211. package/src/cli/wizard/observability.js +0 -79
  212. package/src/cli/wizard/performance.js +0 -76
  213. package/src/cli/wizard/plugin-selection.js +0 -297
  214. package/src/cli/wizard/preview-save.js +0 -172
  215. package/src/cli/wizard/project-setup.js +0 -66
  216. package/src/core/pipeline-factory.js +0 -59
  217. package/src/dag/enhanced-dag-engine.js +0 -894
  218. package/src/dx/index.js +0 -40
  219. package/src/dx/integration-templates.js +0 -662
  220. package/src/dx/performance-profiler.js +0 -968
  221. package/src/dx/realtime-debugger.js +0 -755
  222. package/src/dx/visual-pipeline-builder.js +0 -578
  223. package/src/ingest.js +0 -59
  224. package/src/pipeline/pipeline.js +0 -18
  225. package/src/query.js +0 -117
@@ -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 || 3000,
35
- host: options.host || 'localhost',
36
- watchPaths: options.watchPaths || ['./src/plugins', './src/mocks'],
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);
@@ -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 };