@devilsdev/rag-pipeline-utils 2.3.1 → 2.4.1

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 (160) hide show
  1. package/README.md +94 -480
  2. package/contracts/loader-contract.json +11 -6
  3. package/contracts/reranker-contract.json +18 -26
  4. package/dist/index.cjs +21190 -47
  5. package/dist/index.d.ts +298 -68
  6. package/dist/index.mjs +9 -76
  7. package/package.json +37 -11
  8. package/src/agentic/agentic-pipeline.js +235 -0
  9. package/src/agentic/iterative-retriever.js +213 -0
  10. package/src/agentic/query-planner.js +208 -0
  11. package/src/agentic/self-critique.js +102 -0
  12. package/src/ai/adaptive-retrieval.js +0 -3
  13. package/src/ai/federated-learning.js +0 -3
  14. package/src/ai/federation.js +1 -6
  15. package/src/ai/index.js +9 -20
  16. package/src/ai/model-training.js +0 -3
  17. package/src/ai/multimodal-processing.js +0 -3
  18. package/src/ai/multimodal.js +1 -6
  19. package/src/ai/orchestrator.js +1 -6
  20. package/src/ai/retrieval-engine.js +1 -6
  21. package/src/chunking/chunking-engine.js +142 -0
  22. package/src/chunking/strategies/fixed-size.js +54 -0
  23. package/src/chunking/strategies/recursive.js +127 -0
  24. package/src/chunking/strategies/semantic.js +128 -0
  25. package/src/chunking/strategies/sentence.js +32 -0
  26. package/src/chunking/strategies/structure-aware.js +138 -0
  27. package/src/citation/citation-tracker.js +161 -0
  28. package/src/citation/hallucination-detector.js +74 -0
  29. package/src/citation/source-mapper.js +95 -0
  30. package/src/cli/commands/ai-ml/adaptive-commands.js +178 -0
  31. package/src/cli/commands/ai-ml/federated-commands.js +207 -0
  32. package/src/cli/commands/ai-ml/index.js +17 -0
  33. package/src/cli/commands/ai-ml/multimodal-commands.js +187 -0
  34. package/src/cli/commands/ai-ml/shared.js +51 -0
  35. package/src/cli/commands/ai-ml/training-commands.js +228 -0
  36. package/src/cli/commands/ai-ml.js +176 -757
  37. package/src/cli/commands/plugin-hub/certification-commands.js +221 -0
  38. package/src/cli/commands/plugin-hub/event-handlers.js +64 -0
  39. package/src/cli/commands/plugin-hub/hub-commands.js +290 -0
  40. package/src/cli/commands/plugin-hub/review-commands.js +78 -0
  41. package/src/cli/commands/plugin-hub.js +106 -782
  42. package/src/cli/diagnostics/config-checker.js +81 -0
  43. package/src/cli/diagnostics/dependency-checker.js +69 -0
  44. package/src/cli/diagnostics/environment-checker.js +45 -0
  45. package/src/cli/diagnostics/performance-checker.js +51 -0
  46. package/src/cli/diagnostics/plugin-checker.js +106 -0
  47. package/src/cli/diagnostics/security-checker.js +61 -0
  48. package/src/cli/doctor-command.js +70 -369
  49. package/src/cli/enhanced-cli-commands.js +162 -706
  50. package/src/cli/handlers/config-handler.js +126 -0
  51. package/src/cli/handlers/diagnostics-handler.js +113 -0
  52. package/src/cli/handlers/ingest-handler.js +42 -0
  53. package/src/cli/handlers/init-handler.js +51 -0
  54. package/src/cli/handlers/query-handler.js +45 -0
  55. package/src/cli/handlers/run-handler.js +181 -0
  56. package/src/cli/interactive-wizard.js +99 -723
  57. package/src/cli/shared/cli-utils.js +89 -0
  58. package/src/cli/wizard/configuration.js +148 -0
  59. package/src/cli/wizard/observability.js +79 -0
  60. package/src/cli/wizard/performance.js +76 -0
  61. package/src/cli/wizard/plugin-selection.js +297 -0
  62. package/src/cli/wizard/preview-save.js +172 -0
  63. package/src/cli/wizard/project-setup.js +66 -0
  64. package/src/config/memory-optimization.js +3 -3
  65. package/src/connectors/anthropic-connector.js +179 -0
  66. package/src/connectors/base-connector.js +94 -0
  67. package/src/connectors/cohere-connector.js +143 -0
  68. package/src/connectors/local-embedder.js +171 -0
  69. package/src/connectors/memory-retriever.js +150 -0
  70. package/src/connectors/ollama-connector.js +205 -0
  71. package/src/connectors/openai-connector.js +173 -0
  72. package/src/core/create-pipeline.js +21 -1
  73. package/src/core/observability/tracing.js +2 -1
  74. package/src/core/pipeline-factory.js +0 -1
  75. package/src/core/plugin-registry.js +14 -14
  76. package/src/cost/cost-calculator.js +146 -0
  77. package/src/cost/cost-tracker.js +260 -0
  78. package/src/cost/token-budget.js +245 -0
  79. package/src/dag/core/execution-scheduler.js +2 -1
  80. package/src/dag/core/topology-validation.js +0 -3
  81. package/src/dag/dag-engine.js +3 -5
  82. package/src/dag/dag-node.js +0 -1
  83. package/src/dag/enhanced-dag-engine.js +171 -99
  84. package/src/dashboard/dashboard-generator.js +642 -0
  85. package/src/dashboard/metrics-aggregator.js +409 -0
  86. package/src/debug/execution-tracer.js +210 -0
  87. package/src/debug/trace-inspector.js +281 -0
  88. package/src/dev/dev-server.js +5 -5
  89. package/src/dev/hot-reload.js +3 -2
  90. package/src/dx/integration-templates.js +0 -3
  91. package/src/ecosystem/plugin-analytics-dashboard.js +2 -2
  92. package/src/ecosystem/plugin-hub.js +2 -2
  93. package/src/enterprise/audit-logging.js +2 -2
  94. package/src/enterprise/sso-integration.js +52 -32
  95. package/src/evaluate/context-metrics.js +127 -0
  96. package/src/evaluate/evaluator.js +4 -2
  97. package/src/evaluate/faithfulness.js +117 -0
  98. package/src/evaluate/groundedness.js +60 -0
  99. package/src/evaluate/pipeline-evaluator.js +156 -0
  100. package/src/evaluate/relevance.js +129 -0
  101. package/src/evaluate/scoring.js +27 -0
  102. package/src/graph/entity-extractor.js +183 -0
  103. package/src/graph/graph-index.js +113 -0
  104. package/src/graph/graph-retriever.js +244 -0
  105. package/src/graph/knowledge-graph.js +256 -0
  106. package/src/guardrails/guardrails-pipeline.js +136 -0
  107. package/src/guardrails/post-generation-guard.js +170 -0
  108. package/src/guardrails/pre-retrieval-guard.js +161 -0
  109. package/src/guardrails/retrieval-guard.js +126 -0
  110. package/src/index.js +255 -9
  111. package/src/loader/html-loader.js +4 -15
  112. package/src/loader/markdown-loader.js +4 -15
  113. package/src/mcp/mcp-server.js +199 -0
  114. package/src/mcp/mcp-tool-builder.js +121 -0
  115. package/src/observability/audit-logger.js +0 -2
  116. package/src/observability/event-logger.js +2 -1
  117. package/src/observability/security-monitor.js +0 -2
  118. package/src/observability/telemetry.js +0 -2
  119. package/src/reranker/cascade-reranker.js +79 -0
  120. package/src/reranker/embedding-reranker.js +124 -0
  121. package/src/reranker/llm-reranker.js +6 -3
  122. package/src/reranker/scoring-reranker.js +123 -0
  123. package/src/retrieval/bm25-search.js +157 -0
  124. package/src/retrieval/hybrid-retriever.js +111 -0
  125. package/src/retrieval/rank-fusion.js +62 -0
  126. package/src/security/jwt-config.js +4 -3
  127. package/src/security/jwt-validator.js +2 -1
  128. package/src/security/plugin-sandbox.js +0 -3
  129. package/src/security/plugin-signature-verifier.js +4 -6
  130. package/src/security/validator.js +0 -2
  131. package/src/streaming/embedding-stream.js +197 -0
  132. package/src/streaming/sse-adapter.js +168 -0
  133. package/src/streaming/stream-router.js +210 -0
  134. package/src/streaming/streaming-embedder.js +231 -0
  135. package/src/streaming/websocket-adapter.js +171 -0
  136. package/src/templates/scaffold.js +147 -0
  137. package/src/templates/template-registry.js +272 -0
  138. package/src/utils/contract-validator.js +2 -1
  139. package/src/utils/input-sanitizer.js +80 -99
  140. package/src/utils/performance-monitor.js +0 -3
  141. package/src/utils/plugin-scaffolder.js +9 -16
  142. package/src/utils/rate-limiter.js +0 -1
  143. package/src/utils/retry-policy.js +0 -3
  144. package/src/utils/safe-expression-evaluator.js +0 -3
  145. package/src/utils/secure-logger-config.js +2 -4
  146. package/src/utils/secure-logging.js +0 -3
  147. package/src/utils/structured-logger.js +0 -3
  148. package/src/ai/index-fixed.js +0 -509
  149. package/src/ai/module-template.js +0 -37
  150. package/src/ci/workflow-analyzer.js +0 -71
  151. package/src/cli/plugin-marketplace-commands.js +0 -869
  152. package/src/dev/README.md +0 -126
  153. package/src/dev/examples/hot-reload-usage.js +0 -355
  154. package/src/mocks/openai-embedder.js +0 -119
  155. package/src/mocks/openai-llm.js +0 -77
  156. package/src/mocks/pdf-loader.js +0 -21
  157. package/src/mocks/pinecone-retriever.js +0 -106
  158. package/src/scripts/script-utilities.js +0 -36
  159. package/src/utils/ci/diagnostic-reporter.js +0 -57
  160. package/src/utils/examples/error-formatter-usage.js +0 -351
@@ -0,0 +1,147 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @module templates/scaffold
5
+ * @description Creates complete project directories from templates.
6
+ */
7
+
8
+ const { EventEmitter } = require('events');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { TemplateRegistry } = require('./template-registry');
12
+
13
+ /** @type {Object} Default configuration for the project scaffolder */
14
+ const DEFAULT_CONFIG = {
15
+ installDependencies: false,
16
+ overwrite: false,
17
+ };
18
+
19
+ /**
20
+ * Creates complete project directories from registered templates.
21
+ *
22
+ * @extends EventEmitter
23
+ * @fires ProjectScaffolder#created
24
+ *
25
+ * @example
26
+ * const scaffolder = new ProjectScaffolder();
27
+ * const result = scaffolder.create('document-qa', '/path/to/my-project');
28
+ * console.log(result.files); // list of created files
29
+ */
30
+ class ProjectScaffolder extends EventEmitter {
31
+ /**
32
+ * @param {Object} [options={}] - Scaffolder configuration
33
+ * @param {boolean} [options.installDependencies=false] - Whether to auto-run npm install
34
+ * @param {boolean} [options.overwrite=false] - Allow overwriting existing directories
35
+ */
36
+ constructor(options = {}) {
37
+ super();
38
+ this.config = { ...DEFAULT_CONFIG, ...options };
39
+ this.registry = new TemplateRegistry();
40
+ }
41
+
42
+ /**
43
+ * Create a new project from a template.
44
+ *
45
+ * @param {string} templateKey - Key of the template to use
46
+ * @param {string} projectDir - Target directory for the new project
47
+ * @param {Object} [options={}] - Per-call overrides
48
+ * @param {boolean} [options.overwrite] - Override the instance-level overwrite setting
49
+ * @returns {{projectDir: string, files: string[], template: string}} Creation result
50
+ * @throws {Error} If template is not found or directory already exists
51
+ */
52
+ create(templateKey, projectDir, options = {}) {
53
+ const template = this.registry.get(templateKey);
54
+ if (!template) {
55
+ throw new Error(`Unknown template: "${templateKey}"`);
56
+ }
57
+
58
+ const resolvedDir = path.resolve(projectDir);
59
+ const allowOverwrite =
60
+ options.overwrite !== undefined
61
+ ? options.overwrite
62
+ : this.config.overwrite;
63
+
64
+ if (fs.existsSync(resolvedDir) && !allowOverwrite) {
65
+ throw new Error(
66
+ `Directory already exists: ${resolvedDir}. Use overwrite option to replace.`,
67
+ );
68
+ }
69
+
70
+ fs.mkdirSync(resolvedDir, { recursive: true });
71
+
72
+ const createdFiles = [];
73
+
74
+ // Write package.json
75
+ const projectName = path.basename(resolvedDir);
76
+ const packageJson = {
77
+ name: projectName,
78
+ version: '1.0.0',
79
+ description: template.description,
80
+ main: 'index.js',
81
+ scripts: {
82
+ start: 'node index.js',
83
+ },
84
+ dependencies: template.dependencies || {},
85
+ devDependencies: template.devDependencies || {},
86
+ };
87
+
88
+ const packageJsonPath = path.join(resolvedDir, 'package.json');
89
+ fs.writeFileSync(
90
+ packageJsonPath,
91
+ JSON.stringify(packageJson, null, 2) + '\n',
92
+ );
93
+ createdFiles.push('package.json');
94
+
95
+ // Write template files
96
+ const files = template.files || {};
97
+ for (const [fileName, content] of Object.entries(files)) {
98
+ const filePath = path.join(resolvedDir, fileName);
99
+ const fileDir = path.dirname(filePath);
100
+
101
+ if (fileDir !== resolvedDir) {
102
+ fs.mkdirSync(fileDir, { recursive: true });
103
+ }
104
+
105
+ fs.writeFileSync(filePath, content + '\n');
106
+ createdFiles.push(fileName);
107
+ }
108
+
109
+ // Write .ragrc.json from template config
110
+ if (template.config) {
111
+ const ragrcPath = path.join(resolvedDir, '.ragrc.json');
112
+ fs.writeFileSync(
113
+ ragrcPath,
114
+ JSON.stringify(template.config, null, 2) + '\n',
115
+ );
116
+ createdFiles.push('.ragrc.json');
117
+ }
118
+
119
+ const result = {
120
+ projectDir: resolvedDir,
121
+ files: createdFiles,
122
+ template: templateKey,
123
+ };
124
+
125
+ /**
126
+ * @event ProjectScaffolder#created
127
+ * @type {Object}
128
+ * @property {string} projectDir - Absolute path to created project
129
+ * @property {string[]} files - List of created file names
130
+ * @property {string} template - Template key that was used
131
+ */
132
+ this.emit('created', result);
133
+
134
+ return result;
135
+ }
136
+
137
+ /**
138
+ * List all available templates.
139
+ *
140
+ * @returns {Array<{key: string, name: string, description: string}>} Available templates
141
+ */
142
+ listTemplates() {
143
+ return this.registry.list();
144
+ }
145
+ }
146
+
147
+ module.exports = { ProjectScaffolder, DEFAULT_CONFIG };
@@ -0,0 +1,272 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @module templates/template-registry
5
+ * @description Registry of built-in project templates for RAG pipeline scaffolding.
6
+ */
7
+
8
+ /** @type {Object<string, Object>} Built-in template definitions */
9
+ const TEMPLATES = {
10
+ 'document-qa': {
11
+ name: 'Document Q&A',
12
+ description: 'Question answering over documents (PDF, Markdown, HTML)',
13
+ dependencies: { '@devilsdev/rag-pipeline-utils': 'latest' },
14
+ devDependencies: {},
15
+ config: {
16
+ pipeline: {
17
+ loader: 'markdown',
18
+ embedder: 'openai',
19
+ retriever: 'memory',
20
+ llm: 'openai',
21
+ },
22
+ options: { topK: 5, citations: true, evaluate: true },
23
+ },
24
+ files: {
25
+ 'index.js': [
26
+ '// Document Q&A Pipeline',
27
+ 'const { createRagPipeline, ChunkingEngine } = require(\'@devilsdev/rag-pipeline-utils\');',
28
+ '',
29
+ '// Configure your pipeline',
30
+ 'const pipeline = createRagPipeline({',
31
+ ' // Add your plugins here',
32
+ ' // loader: new YourLoader(),',
33
+ ' // embedder: new YourEmbedder({ apiKey: process.env.OPENAI_API_KEY }),',
34
+ ' // retriever: new YourRetriever(),',
35
+ ' // llm: new YourLLM({ apiKey: process.env.OPENAI_API_KEY }),',
36
+ '});',
37
+ '',
38
+ 'async function main() {',
39
+ ' const result = await pipeline.run({',
40
+ ' query: \'What does this document say?\',',
41
+ ' options: { citations: true, evaluate: true },',
42
+ ' });',
43
+ ' console.log(\'Answer:\', result.answer);',
44
+ ' if (result.citations) {',
45
+ ' console.log(\'Groundedness:\', result.citations.groundednessScore);',
46
+ ' }',
47
+ '}',
48
+ '',
49
+ 'main().catch(console.error);',
50
+ ].join('\n'),
51
+ 'README.md': [
52
+ '# Document Q&A Pipeline',
53
+ '',
54
+ 'A RAG pipeline for answering questions about your documents.',
55
+ '',
56
+ '## Setup',
57
+ '',
58
+ '1. Install dependencies: `npm install`',
59
+ '2. Set your API key: `export OPENAI_API_KEY=your-key`',
60
+ '3. Run: `node index.js`',
61
+ ].join('\n'),
62
+ },
63
+ },
64
+ chatbot: {
65
+ name: 'Chatbot',
66
+ description: 'Conversational chatbot with document context',
67
+ dependencies: { '@devilsdev/rag-pipeline-utils': 'latest' },
68
+ devDependencies: {},
69
+ config: {
70
+ pipeline: { embedder: 'openai', retriever: 'memory', llm: 'openai' },
71
+ options: { topK: 3, stream: true },
72
+ },
73
+ files: {
74
+ 'index.js': [
75
+ '// Chatbot Pipeline',
76
+ 'const { createRagPipeline } = require(\'@devilsdev/rag-pipeline-utils\');',
77
+ '',
78
+ 'const pipeline = createRagPipeline({',
79
+ ' // Configure your chatbot plugins',
80
+ '});',
81
+ '',
82
+ 'async function chat(question) {',
83
+ ' const result = await pipeline.run({',
84
+ ' query: question,',
85
+ ' options: { stream: false },',
86
+ ' });',
87
+ ' return result.results;',
88
+ '}',
89
+ '',
90
+ '// Example usage',
91
+ 'chat(\'Hello, how can you help me?\').then(console.log).catch(console.error);',
92
+ ].join('\n'),
93
+ 'README.md': [
94
+ '# Chatbot Pipeline',
95
+ '',
96
+ 'A conversational RAG chatbot.',
97
+ '',
98
+ '## Setup',
99
+ '',
100
+ '1. `npm install`',
101
+ '2. `export OPENAI_API_KEY=your-key`',
102
+ '3. `node index.js`',
103
+ ].join('\n'),
104
+ },
105
+ },
106
+ 'code-search': {
107
+ name: 'Code Search',
108
+ description: 'Search and explain code repositories',
109
+ dependencies: { '@devilsdev/rag-pipeline-utils': 'latest' },
110
+ devDependencies: {},
111
+ config: {
112
+ pipeline: {
113
+ loader: 'directory',
114
+ embedder: 'openai',
115
+ retriever: 'memory',
116
+ llm: 'openai',
117
+ },
118
+ options: { topK: 10 },
119
+ },
120
+ files: {
121
+ 'index.js': [
122
+ '// Code Search Pipeline',
123
+ 'const { createRagPipeline, ChunkingEngine } = require(\'@devilsdev/rag-pipeline-utils\');',
124
+ '',
125
+ 'const chunker = new ChunkingEngine({ strategy: \'structure-aware\', chunkSize: 1024 });',
126
+ '',
127
+ 'const pipeline = createRagPipeline({',
128
+ ' // Configure for code search',
129
+ '});',
130
+ '',
131
+ 'async function searchCode(query) {',
132
+ ' const result = await pipeline.run({ query, options: { topK: 10 } });',
133
+ ' return result.results;',
134
+ '}',
135
+ '',
136
+ 'searchCode(\'How does authentication work?\').then(console.log).catch(console.error);',
137
+ ].join('\n'),
138
+ 'README.md': [
139
+ '# Code Search Pipeline',
140
+ '',
141
+ 'Search and explain code in your repository.',
142
+ '',
143
+ '## Setup',
144
+ '',
145
+ '1. `npm install`',
146
+ '2. `export OPENAI_API_KEY=your-key`',
147
+ '3. `node index.js`',
148
+ ].join('\n'),
149
+ },
150
+ },
151
+ 'customer-support': {
152
+ name: 'Customer Support',
153
+ description: 'AI-powered customer support with knowledge base',
154
+ dependencies: { '@devilsdev/rag-pipeline-utils': 'latest' },
155
+ devDependencies: {},
156
+ config: {
157
+ pipeline: {
158
+ loader: 'markdown',
159
+ embedder: 'openai',
160
+ retriever: 'memory',
161
+ llm: 'openai',
162
+ },
163
+ options: { topK: 5, citations: true, guardrails: true },
164
+ },
165
+ files: {
166
+ 'index.js': [
167
+ '// Customer Support Pipeline',
168
+ 'const { createRagPipeline, GuardrailsPipeline } = require(\'@devilsdev/rag-pipeline-utils\');',
169
+ '',
170
+ 'const basePipeline = createRagPipeline({',
171
+ ' // Configure your support pipeline',
172
+ '});',
173
+ '',
174
+ '// Wrap with guardrails for production safety',
175
+ 'const pipeline = new GuardrailsPipeline(basePipeline, {',
176
+ ' preRetrieval: { enableInjectionDetection: true },',
177
+ ' postGeneration: { enablePIIDetection: true, enableGroundednessCheck: true },',
178
+ '});',
179
+ '',
180
+ 'async function handleTicket(question) {',
181
+ ' const result = await pipeline.run({ query: question });',
182
+ ' return { answer: result.answer, sources: result.citations, safe: result.guardrails };',
183
+ '}',
184
+ '',
185
+ 'handleTicket(\'How do I reset my password?\').then(console.log).catch(console.error);',
186
+ ].join('\n'),
187
+ 'README.md': [
188
+ '# Customer Support Pipeline',
189
+ '',
190
+ 'AI-powered support with guardrails and citations.',
191
+ '',
192
+ '## Setup',
193
+ '',
194
+ '1. `npm install`',
195
+ '2. `export OPENAI_API_KEY=your-key`',
196
+ '3. `node index.js`',
197
+ ].join('\n'),
198
+ },
199
+ },
200
+ };
201
+
202
+ /**
203
+ * Registry of built-in and custom project templates.
204
+ *
205
+ * @example
206
+ * const registry = new TemplateRegistry();
207
+ * const templates = registry.list();
208
+ * const template = registry.get('document-qa');
209
+ */
210
+ class TemplateRegistry {
211
+ constructor() {
212
+ /** @type {Map<string, Object>} */
213
+ this.templates = new Map(Object.entries(TEMPLATES));
214
+ }
215
+
216
+ /**
217
+ * List all available templates.
218
+ *
219
+ * @returns {Array<{key: string, name: string, description: string}>} Summary of each template
220
+ */
221
+ list() {
222
+ return Array.from(this.templates.entries()).map(([key, t]) => ({
223
+ key,
224
+ name: t.name,
225
+ description: t.description,
226
+ }));
227
+ }
228
+
229
+ /**
230
+ * Get a full template definition by key.
231
+ *
232
+ * @param {string} templateKey - Template identifier
233
+ * @returns {Object|null} Full template object, or null if not found
234
+ */
235
+ get(templateKey) {
236
+ return this.templates.get(templateKey) || null;
237
+ }
238
+
239
+ /**
240
+ * Register a custom template.
241
+ *
242
+ * @param {string} key - Template identifier
243
+ * @param {Object} template - Template definition
244
+ * @param {string} template.name - Display name
245
+ * @param {string} template.description - Short description
246
+ * @param {Object} template.dependencies - npm dependencies
247
+ * @param {Object} template.config - Pipeline configuration
248
+ * @param {Object} template.files - Map of filename to file content
249
+ * @returns {void}
250
+ */
251
+ register(key, template) {
252
+ if (!key || typeof key !== 'string') {
253
+ throw new TypeError('Template key must be a non-empty string');
254
+ }
255
+ if (!template || !template.name || !template.files) {
256
+ throw new TypeError('Template must have a name and files');
257
+ }
258
+ this.templates.set(key, template);
259
+ }
260
+
261
+ /**
262
+ * Check if a template exists.
263
+ *
264
+ * @param {string} key - Template identifier
265
+ * @returns {boolean} Whether the template is registered
266
+ */
267
+ has(key) {
268
+ return this.templates.has(key);
269
+ }
270
+ }
271
+
272
+ module.exports = { TemplateRegistry, TEMPLATES };
@@ -15,6 +15,7 @@
15
15
 
16
16
  const fs = require('fs');
17
17
  const path = require('path');
18
+ const { logger } = require('./logger');
18
19
 
19
20
  /**
20
21
  * ContractValidator class for validating plugin implementations
@@ -107,7 +108,7 @@ class ContractValidator {
107
108
  contracts[type] = this.loadContract(type);
108
109
  } catch (error) {
109
110
  // Skip invalid contracts
110
- console.warn(`Skipping invalid contract: ${file}`);
111
+ logger.warn(`Skipping invalid contract: ${file}`);
111
112
  }
112
113
  }
113
114
  }
@@ -313,118 +313,99 @@ class InputSanitizer {
313
313
  }
314
314
 
315
315
  // Check cache
316
- if (this.options.enableCache) {
317
- const cacheKey = `${rule}:${str}`;
318
- if (this.cache.has(cacheKey)) {
319
- this.cacheAccessTimes.set(cacheKey, Date.now());
320
- this.stats.cached++;
321
- return this.cache.get(cacheKey);
322
- }
316
+ const cached = this._getCachedResult(rule, str);
317
+ if (cached !== undefined) {
318
+ return cached;
323
319
  }
324
320
 
325
- let result;
326
-
327
- switch (rule) {
328
- case SanitizationRules.NONE:
329
- result = str;
330
- break;
331
-
332
- case SanitizationRules.HTML:
333
- result = this._escapeHtml(str);
334
- break;
335
-
336
- case SanitizationRules.HTML_STRICT:
337
- result = this._stripHtml(str);
338
- break;
339
-
340
- case SanitizationRules.XSS:
341
- result = this._sanitizeXss(str);
342
- break;
343
-
344
- case SanitizationRules.SQL:
345
- result = this._sanitizeSql(str);
346
- break;
347
-
348
- case SanitizationRules.COMMAND:
349
- result = this._sanitizeCommand(str);
350
- break;
351
-
352
- case SanitizationRules.PATH:
353
- result = this._sanitizePath(str);
354
- break;
355
-
356
- case SanitizationRules.URL:
357
- result = this._validateAndSanitizeUrl(str);
358
- break;
359
-
360
- case SanitizationRules.EMAIL:
361
- result = this._validateEmail(str);
362
- break;
321
+ const result = this._applyRule(str, rule);
363
322
 
364
- case SanitizationRules.ALPHANUMERIC:
365
- result = this._validateAlphanumeric(str);
366
- break;
367
-
368
- case SanitizationRules.UUID:
369
- result = this._validateUuid(str);
370
- break;
371
-
372
- case SanitizationRules.INTEGER:
373
- result = this._validateInteger(str);
374
- break;
375
-
376
- case SanitizationRules.FLOAT:
377
- result = this._validateFloat(str);
378
- break;
379
-
380
- case SanitizationRules.BOOLEAN:
381
- result = this._validateBoolean(str);
382
- break;
383
-
384
- case SanitizationRules.JSON:
385
- result = this._validateJson(str);
386
- break;
387
-
388
- case SanitizationRules.BASE64:
389
- result = this._validateBase64(str);
390
- break;
323
+ // Cache result
324
+ if (this.options.enableCache) {
325
+ const cacheKey = `${rule}:${str}`;
326
+ this._addToCache(cacheKey, result);
327
+ }
391
328
 
392
- case SanitizationRules.HEX:
393
- result = this._validateHex(str);
394
- break;
329
+ return result;
330
+ }
395
331
 
396
- case SanitizationRules.SLUG:
397
- result = this._validateSlug(str);
398
- break;
332
+ /**
333
+ * Retrieve a previously cached sanitization result.
334
+ * @param {string} rule - The sanitization rule that was applied
335
+ * @param {string} str - The original input string
336
+ * @returns {*} The cached result, or undefined if not cached
337
+ * @private
338
+ */
339
+ _getCachedResult(rule, str) {
340
+ if (!this.options.enableCache) {
341
+ return undefined;
342
+ }
399
343
 
400
- case SanitizationRules.DOMAIN:
401
- result = this._validateDomain(str);
402
- break;
344
+ const cacheKey = `${rule}:${str}`;
345
+ if (this.cache.has(cacheKey)) {
346
+ this.cacheAccessTimes.set(cacheKey, Date.now());
347
+ this.stats.cached++;
348
+ return this.cache.get(cacheKey);
349
+ }
403
350
 
404
- case SanitizationRules.IPV4:
405
- result = this._validateIpv4(str);
406
- break;
351
+ return undefined;
352
+ }
407
353
 
408
- case SanitizationRules.IPV6:
409
- result = this._validateIpv6(str);
410
- break;
354
+ /**
355
+ * Apply a sanitization or validation rule to an input string.
356
+ * Dispatches to the appropriate handler based on the rule name.
357
+ * @param {string} str - The input string to sanitize
358
+ * @param {string} rule - The sanitization rule to apply
359
+ * @returns {*} The sanitized or validated result
360
+ * @private
361
+ */
362
+ _applyRule(str, rule) {
363
+ const ruleHandlers = this._getRuleHandlers();
364
+ const handler = ruleHandlers[rule];
411
365
 
412
- default:
413
- // Check custom rules
414
- if (this.options.customRules[rule]) {
415
- result = this.options.customRules[rule](str);
416
- } else {
417
- result = this._escapeHtml(str); // Default to HTML escape
418
- }
366
+ if (handler) {
367
+ return handler(str);
419
368
  }
420
369
 
421
- // Cache result
422
- if (this.options.enableCache) {
423
- const cacheKey = `${rule}:${str}`;
424
- this._addToCache(cacheKey, result);
370
+ // Check custom rules
371
+ if (this.options.customRules[rule]) {
372
+ return this.options.customRules[rule](str);
425
373
  }
426
374
 
427
- return result;
375
+ // Default to HTML escape
376
+ return this._escapeHtml(str);
377
+ }
378
+
379
+ /**
380
+ * Build the mapping from sanitization rule names to their handler functions.
381
+ * @returns {Object.<string, Function>} Map of rule name to handler function
382
+ * @private
383
+ */
384
+ _getRuleHandlers() {
385
+ return {
386
+ [SanitizationRules.NONE]: (str) => str,
387
+ [SanitizationRules.HTML]: (str) => this._escapeHtml(str),
388
+ [SanitizationRules.HTML_STRICT]: (str) => this._stripHtml(str),
389
+ [SanitizationRules.XSS]: (str) => this._sanitizeXss(str),
390
+ [SanitizationRules.SQL]: (str) => this._sanitizeSql(str),
391
+ [SanitizationRules.COMMAND]: (str) => this._sanitizeCommand(str),
392
+ [SanitizationRules.PATH]: (str) => this._sanitizePath(str),
393
+ [SanitizationRules.URL]: (str) => this._validateAndSanitizeUrl(str),
394
+ [SanitizationRules.EMAIL]: (str) => this._validateEmail(str),
395
+ [SanitizationRules.ALPHANUMERIC]: (str) =>
396
+ this._validateAlphanumeric(str),
397
+ [SanitizationRules.UUID]: (str) => this._validateUuid(str),
398
+ [SanitizationRules.INTEGER]: (str) => this._validateInteger(str),
399
+ [SanitizationRules.FLOAT]: (str) => this._validateFloat(str),
400
+ [SanitizationRules.BOOLEAN]: (str) => this._validateBoolean(str),
401
+ [SanitizationRules.JSON]: (str) => this._validateJson(str),
402
+ [SanitizationRules.BASE64]: (str) => this._validateBase64(str),
403
+ [SanitizationRules.HEX]: (str) => this._validateHex(str),
404
+ [SanitizationRules.SLUG]: (str) => this._validateSlug(str),
405
+ [SanitizationRules.DOMAIN]: (str) => this._validateDomain(str),
406
+ [SanitizationRules.IPV4]: (str) => this._validateIpv4(str),
407
+ [SanitizationRules.IPV6]: (str) => this._validateIpv6(str),
408
+ };
428
409
  }
429
410
 
430
411
  /**
@@ -556,6 +556,3 @@ module.exports = {
556
556
  createPerformanceMonitor,
557
557
  monitor: defaultMonitor,
558
558
  };
559
-
560
- // CommonJS/ESM interop
561
- module.exports.default = module.exports;
@@ -11,6 +11,7 @@ const path = require('path');
11
11
  // eslint-disable-line global-require
12
12
  const { pluginContracts } = require('../core/plugin-contracts.js');
13
13
  // eslint-disable-line global-require
14
+ const { logger } = require('./logger');
14
15
 
15
16
  class PluginScaffolder {
16
17
  constructor(_options = {}) {
@@ -47,24 +48,16 @@ class PluginScaffolder {
47
48
  await this.generateReadme(pluginDir, pluginType, pluginName, _options);
48
49
  await this.generatePackageJson(pluginDir, pluginType, pluginName, _options);
49
50
 
50
- console.log(`✅ Plugin scaffolded successfully at: ${pluginDir}`);
51
- // eslint-disable-line no-console
52
- console.log('📁 Generated files:');
53
- // eslint-disable-line no-console
54
- console.log(` - src/${pluginName}-${pluginType}.js (main plugin)`);
55
- // eslint-disable-line no-console
56
- console.log(` - __tests__/${pluginName}-${pluginType}.test.js (tests)`);
57
- // eslint-disable-line no-console
58
- console.log(
51
+ logger.info(`Plugin scaffolded successfully at: ${pluginDir}`);
52
+ logger.info('Generated files:');
53
+ logger.info(` - src/${pluginName}-${pluginType}.js (main plugin)`);
54
+ logger.info(` - __tests__/${pluginName}-${pluginType}.test.js (tests)`);
55
+ logger.info(
59
56
  ` - mocks/${pluginName}-${pluginType}-mock.js (mock implementation)`,
60
57
  );
61
- // eslint-disable-line no-console
62
- console.log(' - .ragrc.example.json (configuration example)');
63
- // eslint-disable-line no-console
64
- console.log(' - README.md (documentation)');
65
- // eslint-disable-line no-console
66
- console.log(' - package.json (npm package)');
67
- // eslint-disable-line no-console
58
+ logger.info(' - .ragrc.example.json (configuration example)');
59
+ logger.info(' - README.md (documentation)');
60
+ logger.info(' - package.json (npm package)');
68
61
  }
69
62
 
70
63
  /**
@@ -241,4 +241,3 @@ class RateLimiter {
241
241
  }
242
242
 
243
243
  module.exports = RateLimiter;
244
- module.exports.default = module.exports;