@devilsdev/rag-pipeline-utils 1.0.1 → 2.0.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 (43) hide show
  1. package/.codecov.yml +21 -0
  2. package/.github/workflows/ci.yml +18 -0
  3. package/.husky/pre-commit +5 -0
  4. package/CHANGELOG.md +33 -0
  5. package/__tests__/fixtures/.ragrc.json +8 -0
  6. package/__tests__/fixtures/invalid-ragrc.json +4 -0
  7. package/__tests__/fixtures/src/mocks/openai-embedder.js +16 -0
  8. package/__tests__/fixtures/src/mocks/openai-llm.js +9 -0
  9. package/__tests__/fixtures/src/mocks/pdf-loader.js +17 -0
  10. package/__tests__/fixtures/src/mocks/pinecone-retriever.js +26 -0
  11. package/__tests__/fixtures/valid-ragrc.json +16 -0
  12. package/__tests__/integration/cli/config-flow.test.js +84 -15
  13. package/__tests__/integration/config/load-config.test.js +45 -30
  14. package/__tests__/unit/config/validate-schema.test.js +121 -0
  15. package/__tests__/unit/reranker/llm-reranker.test.js +9 -33
  16. package/__tests__/unit/reranker/reranker.enriched.test.js +19 -0
  17. package/__tests__/unit/reranker/reranker.fallback.test.js +15 -0
  18. package/__tests__/unit/reranker/reranker.snapshot.test.js +9 -11
  19. package/__tests__/unit/reranker/reranker.structured-output.test.js +17 -0
  20. package/bin/cli.js +49 -155
  21. package/package.json +17 -2
  22. package/plugin.config.json +14 -0
  23. package/scripts/banner-injector.js +89 -0
  24. package/scripts/ci-runner.js +51 -0
  25. package/scripts/repair-fixtures.js +30 -0
  26. package/scripts/setup.js +26 -2
  27. package/scripts/verify-fixture-mocks.js +45 -0
  28. package/scripts/verify-fixtures.js +36 -0
  29. package/src/config/load-config.js +19 -21
  30. package/src/config/load-plugin-config.js +56 -0
  31. package/src/config/validate-plugin-schema.js +28 -0
  32. package/src/config/validate-schema.js +88 -30
  33. package/src/core/create-pipeline.js +40 -26
  34. package/src/core/plugin-contracts.js +21 -0
  35. package/src/core/plugin-registry.js +4 -17
  36. package/src/mocks/openai-embedder.js +30 -0
  37. package/src/mocks/openai-llm.js +18 -0
  38. package/src/mocks/pdf-loader.js +17 -0
  39. package/src/mocks/pinecone-retriever.js +25 -0
  40. package/src/reranker/llm-reranker.js +28 -43
  41. package/src/utils/ci/diagnostic-reporter.js +36 -0
  42. package/src/utils/validate-plugin-contract.js +36 -0
  43. package/.github/workflows/release.yml +0 -20
@@ -0,0 +1,17 @@
1
+ /**
2
+ * File: __tests__/unit/reranker/reranker.structured-output.test.js
3
+ * Version: 1.0.0
4
+ * Description: Validates structured LLM output parsing
5
+ * Author: @devilsdev
6
+ */
7
+ import { LLMReranker } from '../../../src/reranker/llm-reranker.js';
8
+
9
+ test('parses structured LLM JSON result', async () => {
10
+ const mockLLM = { async generate() {
11
+ return JSON.stringify({ ranking: [0, 2, 1] });
12
+ }};
13
+ const docs = [{ text: 'Alpha' }, { text: 'Beta' }, { text: 'Gamma' }];
14
+ const reranker = new LLMReranker({ llm: mockLLM });
15
+ const result = await reranker.rerank('Which is alpha?', docs);
16
+ expect(result[0].text).toBe('Alpha');
17
+ });
package/bin/cli.js CHANGED
@@ -1,168 +1,62 @@
1
- #!/usr/bin/env node
2
-
3
1
  /**
4
- * Version: 0.1.8
5
- * Path: /bin/cli.js
6
- * Description: CLI interface for rag-pipeline utilities with loaders, reranker, and evaluation
2
+ * Version: 1.1.0
3
+ * Description: Final CLI entrypoint with dynamic plugin loading and full config validation
7
4
  * Author: Ali Kahwaji
8
5
  */
9
6
 
10
- import { Command } from 'commander';
11
- import { createRagPipeline, registry } from '../src/core/create-pipeline.js';
7
+ import { resolve } from 'path';
8
+ import { fileURLToPath } from 'url';
12
9
  import { loadRagConfig } from '../src/config/load-config.js';
13
- import { logger } from '../src/utils/logger.js';
14
- import { evaluateRagDataset } from '../src/evaluate/evaluator.js';
15
- import { LLMReranker } from '../src/reranker/llm-reranker.js';
10
+ import { loadPluginsFromJson } from '../src/config/load-plugin-config.js';
11
+ import registry from '../src/core/plugin-registry.js';
16
12
 
17
- import { MarkdownLoader } from '../src/loader/markdown-loader.js';
18
- import { HTMLLoader } from '../src/loader/html-loader.js';
19
- import { CSVLoader } from '../src/loader/csv-loader.js';
20
- import { DirectoryLoader } from '../src/loader/directory-loader.js';
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = resolve(__filename, '..');
15
+ const CONFIG_PATH = resolve('.ragrc.json');
21
16
 
22
- // Minimal test plugin implementations
23
- class PDFLoader {
24
- async load(filePath) {
25
- return [{ chunk: () => [`Test loaded: ${filePath}`] }];
26
- }
27
- }
28
- class OpenAIEmbedder {
29
- async embed(chunks) {
30
- return chunks.map((text, i) => ({ id: `v${i}`, values: [0.1, 0.2, 0.3], metadata: { text } }));
31
- }
32
- async embedQuery(prompt) {
33
- return [0.1, 0.2, 0.3];
34
- }
35
- }
36
- class PineconeRetriever {
37
- async store() {}
38
- async retrieve() {
39
- return [
40
- { id: 'a', text: 'Chunk about pine trees', metadata: {} },
41
- { id: 'b', text: 'Chunk about vectors', metadata: {} },
42
- { id: 'c', text: 'Chunk about databases', metadata: {} }
43
- ];
44
- }
45
- }
46
- class OpenAILLM {
47
- async generate(prompt, context) {
48
- return `Generated answer using: ${context.map(d => d.text).join(', ')}`;
49
- }
50
- }
17
+ const args = process.argv.slice(2);
18
+ const command = args[0];
51
19
 
52
- // Register mock/default plugins
53
- registry.register('loader', 'pdf', new PDFLoader());
54
- registry.register('loader', 'markdown', new MarkdownLoader());
55
- registry.register('loader', 'html', new HTMLLoader());
56
- registry.register('loader', 'csv', new CSVLoader());
57
- registry.register('loader', 'directory', new DirectoryLoader());
58
- registry.register('embedder', 'openai', new OpenAIEmbedder());
59
- registry.register('retriever', 'pinecone', new PineconeRetriever());
60
- registry.register('llm', 'openai-gpt-4', new OpenAILLM());
61
-
62
- const program = new Command();
63
- program
64
- .name('rag-pipeline')
65
- .description('CLI for RAG pipeline utilities')
66
- .version('0.1.8');
67
-
68
- function resolveConfig(cliOpts) {
20
+ async function runCLI() {
69
21
  try {
70
- const fileConfig = loadRagConfig();
71
- return { ...fileConfig, ...cliOpts };
22
+ const config = loadRagConfig(CONFIG_PATH);
23
+ await loadPluginsFromJson(CONFIG_PATH);
24
+
25
+ switch (command) {
26
+ case 'ingest': {
27
+ const input = args[1];
28
+ const loader = registry.get('loader', Object.keys(config.loader)[0]);
29
+ const embedder = registry.get('embedder', Object.keys(config.embedder)[0]);
30
+ const retriever = registry.get('retriever', Object.keys(config.retriever)[0]);
31
+
32
+ const docs = await loader.load(input);
33
+ const vectors = await embedder.embed(docs);
34
+ await retriever.store(vectors, config.namespace);
35
+
36
+ console.log('Ingestion complete');
37
+ break;
38
+ }
39
+ case 'query': {
40
+ const question = args.slice(1).join(' ');
41
+ const embedder = registry.get('embedder', Object.keys(config.embedder)[0]);
42
+ const retriever = registry.get('retriever', Object.keys(config.retriever)[0]);
43
+ const llm = registry.get('llm', Object.keys(config.llm)[0]);
44
+
45
+ const queryVec = await embedder.embedQuery(question);
46
+ const docs = await retriever.search(queryVec, config.namespace);
47
+ const answer = await llm.ask(question, docs);
48
+
49
+ console.log('Answer:', answer);
50
+ break;
51
+ }
52
+ default:
53
+ console.error('Unknown command:', command);
54
+ process.exit(1);
55
+ }
72
56
  } catch (err) {
73
- logger.warn({ error: err.message }, 'Config file fallback failed');
74
- return cliOpts;
57
+ console.error('Error during execution:', err.message);
58
+ process.exit(1);
75
59
  }
76
60
  }
77
61
 
78
- program
79
- .command('ingest')
80
- .argument('<path>', 'Path to document(s)')
81
- .option('--loader <type>', 'Document loader type')
82
- .option('--embedder <type>', 'Embedder type')
83
- .option('--retriever <type>', 'Vector store retriever type')
84
- .action(async (docPath, opts) => {
85
- try {
86
- const config = resolveConfig(opts);
87
- const pipeline = createRagPipeline(config);
88
- await pipeline.ingest(docPath);
89
- console.log('Ingestion complete'); // required by test assertion
90
- } catch (err) {
91
- logger.error({ error: err.message }, 'Ingestion failed');
92
- console.error('Error during ingestion:', err.message);
93
- process.exit(1);
94
- }
95
- });
96
-
97
- program
98
- .command('query')
99
- .argument('<prompt>', 'Prompt to submit')
100
- .option('--embedder <type>', 'Embedder type')
101
- .option('--retriever <type>', 'Retriever type')
102
- .option('--llm <type>', 'LLM type')
103
- .action(async (prompt, opts) => {
104
- try {
105
- const config = resolveConfig(opts);
106
- const pipeline = createRagPipeline(config);
107
- const answer = await pipeline.query(prompt);
108
- console.log('\n Answer:\n', answer);
109
- } catch (err) {
110
- logger.error({ error: err.message }, 'Query failed');
111
- process.exit(1);
112
- }
113
- });
114
-
115
- program
116
- .command('evaluate')
117
- .argument('<dataset>', 'Path to JSON file of evaluation cases')
118
- .option('--embedder <type>', 'Embedder type')
119
- .option('--retriever <type>', 'Retriever type')
120
- .option('--llm <type>', 'LLM type')
121
- .action(async (dataset, opts) => {
122
- try {
123
- const config = resolveConfig(opts);
124
- const results = await evaluateRagDataset(dataset, config);
125
- const passRate = results.filter(r => r.success).length / results.length;
126
- const avgBLEU = results.reduce((sum, r) => sum + r.scores.bleu, 0) / results.length;
127
- const avgROUGE = results.reduce((sum, r) => sum + r.scores.rouge, 0) / results.length;
128
-
129
- results.forEach((r, i) => {
130
- console.log(`\nCase ${i + 1}:\nPrompt: ${r.prompt}\nPass: ${r.success}\nBLEU: ${r.scores.bleu.toFixed(2)}\nROUGE: ${r.scores.rouge.toFixed(2)}`);
131
- });
132
-
133
- console.log(`\n Summary:\nPassed: ${Math.round(passRate * 100)}%\nAvg BLEU: ${avgBLEU.toFixed(2)}\nAvg ROUGE: ${avgROUGE.toFixed(2)}`);
134
- } catch (err) {
135
- logger.error({ error: err.message }, 'Evaluation failed');
136
- process.exit(1);
137
- }
138
- });
139
-
140
- program
141
- .command('rerank')
142
- .argument('<prompt>', 'Prompt to rerank context for')
143
- .option('--retriever <type>', 'Retriever type')
144
- .option('--llm <type>', 'LLM type')
145
- .action(async (prompt, opts) => {
146
- try {
147
- const config = resolveConfig(opts);
148
- const retriever = registry.get('retriever', config.retriever);
149
- const llm = registry.get('llm', config.llm);
150
- const embedder = registry.get('embedder', config.embedder);
151
-
152
- const queryVector = await embedder.embedQuery(prompt);
153
- const docs = await retriever.retrieve(queryVector);
154
-
155
- const reranker = new LLMReranker({ llm });
156
- const reranked = await reranker.rerank(prompt, docs);
157
-
158
- console.log('\n Reranked Results:');
159
- reranked.forEach((doc, i) => {
160
- console.log(`\n#${i + 1}: ${doc.text}`);
161
- });
162
- } catch (err) {
163
- logger.error({ error: err.message }, 'Rerank failed');
164
- process.exit(1);
165
- }
166
- });
167
-
168
- program.parse();
62
+ await runCLI();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devilsdev/rag-pipeline-utils",
3
- "version": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "description": "A modular toolkit for building RAG (Retrieval-Augmented Generation) pipelines in Node.js",
5
5
  "main": "src/core/create-pipeline.js",
6
6
  "bin": {
@@ -8,10 +8,22 @@
8
8
  },
9
9
  "type": "module",
10
10
  "scripts": {
11
+ "validate-fixtures": "node ./scripts/verify-fixtures.js",
12
+ "setup": "node ./scripts/setup.js",
11
13
  "test": "node ./scripts/setup.js && cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage",
12
14
  "test:watch": "jest --watch",
13
15
  "lint": "eslint .",
14
- "prepare": "husky install"
16
+ "prepare": "husky install",
17
+ "lint-staged": "lint-staged",
18
+ "postinstall": "husky install",
19
+ "verify:mocks": "node scripts/verify-fixture-mocks.js",
20
+ "ci": "node scripts/ci-runner.js"
21
+ },
22
+ "lint-staged": {
23
+ "**/*.{js,ts}": [
24
+ "npm run lint",
25
+ "npm run validate-fixtures"
26
+ ]
15
27
  },
16
28
  "repository": {
17
29
  "type": "git",
@@ -43,17 +55,20 @@
43
55
  "@semantic-release/npm": "^11.0.3",
44
56
  "@semantic-release/release-notes-generator": "^14.0.3",
45
57
  "@types/jest": "^29.5.14",
58
+ "codecov": "^3.8.2",
46
59
  "cross-env": "^7.0.3",
47
60
  "esbuild": "^0.25.2",
48
61
  "eslint": "^8.57.1",
49
62
  "husky": "^8.0.3",
50
63
  "jest": "^29.7.0",
64
+ "lint-staged": "^15.5.1",
51
65
  "prettier": "^3.5.3",
52
66
  "rimraf": "^6.0.1",
53
67
  "semantic-release": "^21.1.2",
54
68
  "ts-jest": "^29.3.2"
55
69
  },
56
70
  "dependencies": {
71
+ "ajv": "^8.17.1",
57
72
  "axios": "^1.8.4",
58
73
  "chalk": "^5.4.1",
59
74
  "commander": "^13.1.0",
@@ -0,0 +1,14 @@
1
+ {
2
+ "loader": {
3
+ "pdf": "./src/mocks/pdf-loader.js"
4
+ },
5
+ "embedder": {
6
+ "openai": "./src/mocks/openai-embedder.js"
7
+ },
8
+ "retriever": {
9
+ "pinecone": "./src/mocks/pinecone-retriever.js"
10
+ },
11
+ "llm": {
12
+ "openai-gpt-4": "./src/mocks/openai-llm.js"
13
+ }
14
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * File: scripts/banner-injector.js
3
+ * Description: Script to programmatically inject file-level banners into specified project files
4
+ * Author: Ali Kahwaji
5
+ * Version: 1.0.0
6
+ */
7
+
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import glob from 'glob';
11
+
12
+ const banners = {
13
+ '__tests__/unit/reranker/llm-reranker.test.js': `/**
14
+ * File: __tests__/unit/reranker/llm-reranker.test.js
15
+ * Description: Unit and integration tests with mocks for RAG pipeline
16
+ * Author: Ali Kahwaji
17
+ * Version: 1.0.4
18
+ */\n\n`,
19
+
20
+ '__tests__/unit/reranker/reranker.snapshot.test.js': `/**
21
+ * File: __tests__/unit/reranker/reranker.snapshot.test.js
22
+ * Description: Snapshot tests for LLM reranker
23
+ * Author: Ali Kahwaji
24
+ * Version: 1.0.4
25
+ */\n\n`,
26
+
27
+ '__tests__/unit/core/plugin-registry.test.js': `/**
28
+ * File: __tests__/unit/core/plugin-registry.test.js
29
+ * Description: Unit and integration tests with mocks for RAG pipeline
30
+ * Author: Ali Kahwaji
31
+ * Version: 1.0.4
32
+ */\n\n`,
33
+
34
+ '__tests__/integration/config/load-config.test.js': `/**
35
+ * File: __tests__/integration/config/load-config.test.js
36
+ * Description: Unit and integration tests with mocks for RAG pipeline
37
+ * Author: Ali Kahwaji
38
+ * Version: 1.0.4
39
+ */\n\n`,
40
+
41
+ '__tests__/integration/cli/config-flow.test.js': `/**
42
+ * File: __tests__/integration/cli/config-flow.test.js
43
+ * Description: Unit and integration tests with mocks for RAG pipeline
44
+ * Author: Ali Kahwaji
45
+ * Version: 1.0.4
46
+ */\n\n`,
47
+
48
+ 'src/mocks/pdf-loader.js': `/**
49
+ * File: src/mocks/pdf-loader.js
50
+ * Description: Unit and integration tests with mocks for RAG pipeline
51
+ * Author: Ali Kahwaji
52
+ * Version: 1.0.4
53
+ */\n\n`,
54
+
55
+ 'src/mocks/openai-embedder.js': `/**
56
+ * File: src/mocks/openai-embedder.js
57
+ * Description: Unit and integration tests with mocks for RAG pipeline
58
+ * Author: Ali Kahwaji
59
+ * Version: 1.0.4
60
+ */\n\n`,
61
+
62
+ 'src/mocks/pinecone-retriever.js': `/**
63
+ * File: src/mocks/pinecone-retriever.js
64
+ * Description: Unit and integration tests with mocks for RAG pipeline
65
+ * Author: Ali Kahwaji
66
+ * Version: 1.0.4
67
+ */\n\n`
68
+ };
69
+
70
+ Object.entries(banners).forEach(([filePath, banner]) => {
71
+ const resolvedPath = path.resolve(filePath);
72
+
73
+ if (!fs.existsSync(resolvedPath)) {
74
+ console.warn(`[SKIPPED] ${filePath} does not exist.`);
75
+ return;
76
+ }
77
+
78
+ const originalContent = fs.readFileSync(resolvedPath, 'utf-8');
79
+
80
+ // Skip if banner already present
81
+ if (originalContent.startsWith('/**')) {
82
+ console.log(`[SKIPPED] ${filePath} already contains a banner.`);
83
+ return;
84
+ }
85
+
86
+ const newContent = banner + originalContent;
87
+ fs.writeFileSync(resolvedPath, newContent, 'utf-8');
88
+ console.log(`[INJECTED] Banner added to ${filePath}`);
89
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Version: 1.1.0
3
+ * Description: CI runner for linting, mock validation, testing and coverage
4
+ * Author: Ali Kahwaji
5
+ * File: scripts/ci-runner.js
6
+ */
7
+
8
+ import { execSync } from 'child_process';
9
+ import path from 'path';
10
+ import { pathToFileURL } from 'url';
11
+ import fs from 'fs';
12
+
13
+ // Entry helpers
14
+ const ROOT = path.resolve();
15
+ const resolveScript = (relPath) => pathToFileURL(path.resolve(ROOT, relPath)).href;
16
+
17
+ const run = (label, command) => {
18
+ console.log(`\n🔧 Running: ${label}`);
19
+ try {
20
+ execSync(command, { stdio: 'inherit', cwd: ROOT });
21
+ console.log(`✅ ${label} passed`);
22
+ } catch (err) {
23
+ console.error(`❌ ${label} failed`);
24
+ process.exit(1);
25
+ }
26
+ };
27
+
28
+ const runDynamicModule = async (label, modulePath) => {
29
+ console.log(`\n🔍 Validating: ${label}`);
30
+ try {
31
+ const mod = await import(resolveScript(modulePath));
32
+ if (typeof mod.default === 'function') await mod.default();
33
+ console.log(`✅ ${label} passed`);
34
+ } catch (err) {
35
+ console.error(`❌ ${label} failed:\n${err.message}`);
36
+ process.exit(1);
37
+ }
38
+ };
39
+
40
+ (async () => {
41
+ // Step 1: Lint
42
+ run('Lint', 'npx eslint . --ext .js,.ts');
43
+
44
+ // Step 2: Verify mocks for required interface methods
45
+ await runDynamicModule('Fixture Mock Interface Validation', 'scripts/verify-fixture-mocks.js');
46
+
47
+ // Step 3: Test and coverage
48
+ run('Unit + Integration Tests w/ Coverage', 'cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage');
49
+
50
+ console.log('\n🎉 CI runner completed successfully.\n');
51
+ })();
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Version: 1.0.0
3
+ * Description: Auto-repairs mock plugin fixtures for integration tests
4
+ * Author: Ali Kahwaji
5
+ */
6
+
7
+ import { mkdirSync, copyFileSync, existsSync } from 'fs';
8
+ import { resolve, join } from 'path';
9
+
10
+ const MOCKS = [
11
+ 'pdf-loader.js',
12
+ 'openai-embedder.js',
13
+ 'pinecone-retriever.js',
14
+ 'openai-llm.js'
15
+ ];
16
+
17
+ const SRC_FIXTURES = resolve('__tests__/fixtures/src/mocks');
18
+ const DEST_FIXTURES = resolve('__tests__/__temp__/cli-config-fallback/src/mocks');
19
+
20
+ mkdirSync(DEST_FIXTURES, { recursive: true });
21
+
22
+ MOCKS.forEach((file) => {
23
+ const src = join(SRC_FIXTURES, file);
24
+ const dest = join(DEST_FIXTURES, file);
25
+ if (!existsSync(src)) throw new Error(`[FIXTURE MISSING] ${src}`);
26
+ copyFileSync(src, dest);
27
+ console.log(`[COPIED] ${file}`);
28
+ });
29
+
30
+ console.log('✅ Fixture mocks successfully repaired.');
package/scripts/setup.js CHANGED
@@ -1,7 +1,31 @@
1
+ /**
2
+ * Version: 1.1.0
3
+ * Description: Test setup script to ensure fixtures exist and validate mocks before running tests.
4
+ * Author: Ali Kahwaji
5
+ */
6
+
1
7
  import fs from 'fs';
2
- const fixturePath = './__tests__/fixtures/sample.pdf';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { execSync } from 'child_process';
11
+
12
+ // Resolve __dirname in ES module scope
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ const fixturePath = path.resolve(__dirname, '../__tests__/fixtures/sample.pdf');
3
17
 
18
+ // Ensure PDF test fixture
4
19
  if (!fs.existsSync(fixturePath)) {
5
- fs.mkdirSync('./__tests__/fixtures', { recursive: true });
20
+ fs.mkdirSync(path.dirname(fixturePath), { recursive: true });
6
21
  fs.writeFileSync(fixturePath, 'Dummy PDF content for test');
22
+ console.log('[setup] Created sample.pdf fixture');
23
+ }
24
+
25
+ // Validate plugin fixture presence
26
+ try {
27
+ execSync('node ./scripts/verify-fixtures.js', { stdio: 'inherit' });
28
+ } catch (err) {
29
+ console.error('[setup] Fixture verification failed. Please check missing mocks.');
30
+ process.exit(1);
7
31
  }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Version: 1.0.0
3
+ * Description: Verifies that plugin mock implementations expose required methods
4
+ * Author: Ali Kahwaji
5
+ */
6
+
7
+ import { readdirSync, statSync } from 'fs';
8
+ import { resolve, join, extname } from 'path';
9
+ import { pathToFileURL } from 'url';
10
+
11
+ const MOCKS_DIR = resolve('__tests__/fixtures/src/mocks');
12
+
13
+ const pluginContracts = {
14
+ 'openai-embedder.js': ['embed', 'embedQuery'],
15
+ 'openai-llm.js': ['ask'],
16
+ 'pdf-loader.js': ['load'],
17
+ 'pinecone-retriever.js': ['store', 'search'],
18
+ };
19
+
20
+ const failures = [];
21
+
22
+ for (const file of readdirSync(MOCKS_DIR)) {
23
+ const fullPath = join(MOCKS_DIR, file);
24
+ const isFile = statSync(fullPath).isFile();
25
+ if (!isFile || extname(file) !== '.js') continue;
26
+
27
+ const expectedMethods = pluginContracts[file];
28
+ if (!expectedMethods) continue;
29
+
30
+ const mod = await import(pathToFileURL(fullPath).href);
31
+ const plugin = new mod.default();
32
+
33
+ for (const method of expectedMethods) {
34
+ if (typeof plugin[method] !== 'function') {
35
+ failures.push(`❌ ${file} failed: [${file}] missing method: ${method}`);
36
+ }
37
+ }
38
+ }
39
+
40
+ if (failures.length > 0) {
41
+ failures.forEach(msg => console.error(msg));
42
+ process.exit(1);
43
+ } else {
44
+ console.log('✅ All plugin mocks verified.');
45
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Version: 1.0.0
3
+ * Description: Script to verify presence of all expected fixture plugins before running integration tests.
4
+ * Author: Ali Kahwaji
5
+ * File: scripts/verify-fixtures.js
6
+ */
7
+
8
+ import { existsSync } from 'fs';
9
+ import { resolve } from 'path';
10
+
11
+ const REQUIRED_FIXTURES = [
12
+ '__tests__/fixtures/sample.pdf',
13
+ '__tests__/fixtures/.ragrc.json',
14
+ '__tests__/fixtures/src/mocks/pdf-loader.js',
15
+ '__tests__/fixtures/src/mocks/openai-embedder.js',
16
+ '__tests__/fixtures/src/mocks/pinecone-retriever.js',
17
+ '__tests__/fixtures/src/mocks/openai-llm.js'
18
+ ];
19
+
20
+ let missing = [];
21
+ for (const file of REQUIRED_FIXTURES) {
22
+ const fullPath = resolve(file);
23
+ if (!existsSync(fullPath)) {
24
+ missing.push(fullPath);
25
+ }
26
+ }
27
+
28
+ if (missing.length) {
29
+ console.error('[ERROR] Missing test fixture files:');
30
+ for (const f of missing) {
31
+ console.error(' -', f);
32
+ }
33
+ process.exit(1);
34
+ } else {
35
+ console.log('[OK] All fixture files are present.');
36
+ }
@@ -1,35 +1,33 @@
1
1
  /**
2
- * Version: 0.1.0
3
- * Path: /src/config/load-config.js
4
- * Description: Loader and validator for `.ragrc.json` configuration files
2
+ * Version: 1.1.0
3
+ * Description: Loads validated .ragrc.json config from disk
5
4
  * Author: Ali Kahwaji
6
5
  */
7
6
 
8
7
  import fs from 'fs';
9
8
  import path from 'path';
10
- import { validate } from './validate-schema.js';
11
-
12
- const DEFAULT_CONFIG_FILE = '.ragrc.json';
9
+ import { validateRagrcSchema } from './validate-schema.js';
10
+ import { logger } from '../utils/logger.js';
13
11
 
14
12
  /**
15
- * Load and validate the RAG configuration from a file.
16
- *
17
- * @param {string} [configPath] - Optional custom config path
18
- * @returns {object} - Validated config object
19
- * @throws {Error} - If file not found or validation fails
13
+ * Loads and validates a RAG config file.
14
+ * @param {string} configPath - Absolute path to .ragrc.json
15
+ * @returns {object} Parsed config object
16
+ * @throws {Error} If file is missing or invalid
20
17
  */
21
- export function loadRagConfig(configPath = DEFAULT_CONFIG_FILE) {
22
- const fullPath = path.resolve(process.cwd(), configPath);
23
-
24
- if (!fs.existsSync(fullPath)) {
25
- throw new Error(`Config file not found: ${fullPath}`);
18
+ export function loadRagConfig(configPath) {
19
+ if (!fs.existsSync(configPath)) {
20
+ throw new Error(`Config file not found: ${configPath}`);
26
21
  }
27
22
 
28
- const raw = fs.readFileSync(fullPath, 'utf-8');
29
- const config = JSON.parse(raw);
23
+ const raw = fs.readFileSync(configPath, 'utf-8');
24
+ const parsed = JSON.parse(raw);
30
25
 
31
- validate(config); // Throws if invalid
26
+ const result = validateRagrcSchema(parsed);
27
+ if (!result.valid) {
28
+ logger.error({ errors: result.errors }, 'Invalid .ragrc.json schema');
29
+ throw new Error('Config validation failed');
30
+ }
32
31
 
33
- return config;
32
+ return parsed;
34
33
  }
35
-