@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
@@ -1,44 +1,75 @@
1
1
  /**
2
- * Environment checker for RAG Pipeline Diagnostics
3
- * Checks for missing environment variables referenced in configuration
2
+ * Environment diagnostics: API keys the configured providers need, ${VAR}
3
+ * references in the config, and whether the file-store index exists yet.
4
4
  */
5
5
 
6
- const fs = require("fs/promises");
6
+ const fs = require("fs");
7
+ const path = require("path");
7
8
 
8
- /**
9
- * Check environment issues
10
- * @param {object} options - Doctor options (must include configPath)
11
- * @returns {Promise<Array>} Array of environment issues
12
- */
13
- async function checkEnvironment(options) {
14
- const issues = [];
9
+ const PROVIDER_KEYS = {
10
+ openai: "OPENAI_API_KEY",
11
+ anthropic: "ANTHROPIC_API_KEY",
12
+ cohere: "COHERE_API_KEY",
13
+ };
14
+
15
+ function missingVar(envVar, reason) {
16
+ return {
17
+ category: "environment",
18
+ severity: "error",
19
+ code: "ENV_VAR_MISSING",
20
+ message: `Required environment variable missing: ${envVar}${reason ? ` (${reason})` : ""}`,
21
+ fix: `Set environment variable: export ${envVar}=your_key`,
22
+ autoFixable: false,
23
+ };
24
+ }
15
25
 
26
+ async function checkEnvironment(options) {
27
+ const configPath = path.resolve(options.configPath);
28
+ let config;
16
29
  try {
17
- const configContent = await fs.readFile(options.configPath, "utf8");
18
- const config = JSON.parse(configContent);
30
+ config = JSON.parse(fs.readFileSync(configPath, "utf8"));
31
+ } catch {
32
+ return []; // reported by the configuration checker
33
+ }
19
34
 
20
- // Check for missing environment variables
21
- const configStr = JSON.stringify(config);
22
- const envVarMatches = configStr.match(/\$\{([^}]+)\}/g);
23
- if (envVarMatches) {
24
- envVarMatches.forEach((match) => {
25
- const envVar = match.slice(2, -1);
26
- if (!process.env[envVar]) {
27
- issues.push({
28
- category: "environment",
29
- severity: "error",
30
- code: "ENV_VAR_MISSING",
31
- message: `Required environment variable missing: ${envVar}`,
32
- fix: `Set environment variable: export ${envVar}=your_key`,
33
- autoFixable: false,
34
- });
35
- }
36
- });
35
+ const issues = [];
36
+ const seen = new Set();
37
+ for (const entry of Array.isArray(config.pipeline) ? config.pipeline : []) {
38
+ const envVar = PROVIDER_KEYS[entry?.name];
39
+ if (
40
+ envVar &&
41
+ !entry.config?.apiKey &&
42
+ !process.env[envVar] &&
43
+ !seen.has(envVar)
44
+ ) {
45
+ seen.add(envVar);
46
+ issues.push(missingVar(envVar, `used by ${entry.stage} "${entry.name}"`));
47
+ }
48
+ if (entry?.stage === "retriever" && entry.name === "file-store") {
49
+ const indexPath = path.resolve(
50
+ path.dirname(configPath),
51
+ entry.config?.path || ".rag/index.json",
52
+ );
53
+ if (!fs.existsSync(indexPath)) {
54
+ issues.push({
55
+ category: "environment",
56
+ severity: "info",
57
+ code: "INDEX_MISSING",
58
+ message: `No index yet at ${path.relative(process.cwd(), indexPath)}`,
59
+ fix: "Run: rag-pipeline ingest <file-or-directory>",
60
+ autoFixable: false,
61
+ });
62
+ }
37
63
  }
38
- } catch (error) {
39
- // Config issues handled elsewhere
40
64
  }
41
65
 
66
+ for (const match of JSON.stringify(config).match(/\$\{([^}]+)\}/g) || []) {
67
+ const envVar = match.slice(2, -1);
68
+ if (!process.env[envVar] && !seen.has(envVar)) {
69
+ seen.add(envVar);
70
+ issues.push(missingVar(envVar, "referenced in the config"));
71
+ }
72
+ }
42
73
  return issues;
43
74
  }
44
75
 
@@ -1,105 +1,52 @@
1
1
  /**
2
- * Plugin checker for RAG Pipeline Diagnostics
3
- * Checks plugin availability, version conflicts, and outdated plugins
2
+ * Plugin diagnostics: every custom plugin path under `plugins` exists and
3
+ * loads. Checked locally; no registry is contacted.
4
4
  */
5
5
 
6
- const fs = require("fs/promises");
6
+ const fs = require("fs");
7
7
  const path = require("path");
8
8
 
9
- /**
10
- * Check plugin issues
11
- * @param {object} options - Doctor options (must include configPath)
12
- * @returns {Promise<Array>} Array of plugin issues
13
- */
14
9
  async function checkPlugins(options) {
15
- const issues = [];
16
-
10
+ const configPath = path.resolve(options.configPath);
11
+ let config;
17
12
  try {
18
- // First check if config file exists
19
- await fs.access(options.configPath);
20
-
21
- const configContent = await fs.readFile(options.configPath, "utf8");
22
- const config = JSON.parse(configContent);
23
-
24
- if (config.plugins) {
25
- // Process plugins in the order expected by tests: loader first, then embedder
26
- const categories = ["loader", "embedder"];
27
- for (const category of categories) {
28
- if (config.plugins[category]) {
29
- const plugins = config.plugins[category];
30
- const sortedPlugins = Object.keys(plugins).sort();
31
- for (const pluginName of sortedPlugins) {
32
- try {
33
- // Check if plugin file exists (simplified check)
34
- await fs.access(path.join("node_modules", pluginName));
35
- } catch (error) {
36
- issues.push({
37
- category: "plugins",
38
- severity: "error",
39
- code: "PLUGIN_MISSING",
40
- message: `Plugin not found: ${pluginName}`,
41
- fix: `Install plugin: npm install ${pluginName}`,
42
- autoFixable: true,
43
- });
44
- }
45
- }
46
- }
47
- }
13
+ config = JSON.parse(fs.readFileSync(configPath, "utf8"));
14
+ } catch {
15
+ return []; // reported by the configuration checker
16
+ }
48
17
 
49
- // Check for version conflicts
50
- try {
51
- const {
52
- resolvePluginVersions,
53
- } = require("../../core/plugin-marketplace/version-resolver.js");
54
- const resolution = await resolvePluginVersions(config.plugins);
55
- if (resolution.conflicts) {
56
- resolution.conflicts.forEach((conflict) => {
57
- issues.push({
58
- category: "plugins",
59
- severity: "warning",
60
- code: "PLUGIN_VERSION_CONFLICT",
61
- message: `Version conflict: ${conflict.plugin} - ${conflict.conflict}`,
62
- fix: "Update plugin versions to resolve conflicts",
63
- autoFixable: false,
64
- });
65
- });
66
- }
67
- } catch (resolverError) {
68
- // Version resolver not available, skip
18
+ const issues = [];
19
+ const configDir = path.dirname(configPath);
20
+ for (const [stage, plugins] of Object.entries(config.plugins || {})) {
21
+ if (!plugins || typeof plugins !== "object") continue;
22
+ for (const [name, modulePath] of Object.entries(plugins)) {
23
+ if (typeof modulePath !== "string") continue;
24
+ const resolved = path.resolve(configDir, modulePath);
25
+ if (!fs.existsSync(resolved)) {
26
+ issues.push({
27
+ category: "plugins",
28
+ severity: "error",
29
+ code: "PLUGIN_MISSING",
30
+ message: `Plugin ${stage}/${name} not found at ${modulePath}`,
31
+ fix: `Create ${modulePath} or fix the path in plugins.${stage}.${name}`,
32
+ autoFixable: false,
33
+ });
34
+ continue;
69
35
  }
70
-
71
- // Check for outdated plugins
72
36
  try {
73
- const registryUrl = "https://registry.rag-pipeline.dev";
74
- const response = await fetch(`${registryUrl}/plugins`);
75
- if (response.ok) {
76
- const registry = await response.json();
77
- for (const [category, plugins] of Object.entries(config.plugins)) {
78
- for (const [pluginName, version] of Object.entries(plugins)) {
79
- if (registry.plugins && registry.plugins[pluginName]) {
80
- const latest = registry.plugins[pluginName].versions.latest;
81
- if (version !== "latest" && version !== latest) {
82
- issues.push({
83
- category: "plugins",
84
- severity: "info",
85
- code: "PLUGIN_OUTDATED",
86
- message: `Plugin outdated: ${pluginName}@${version} (latest: ${latest})`,
87
- fix: `Update to latest version: rag-pipeline plugin install ${pluginName}@latest`,
88
- autoFixable: true,
89
- });
90
- }
91
- }
92
- }
93
- }
94
- }
95
- } catch (fetchError) {
96
- // Registry not available, skip
37
+ require(resolved);
38
+ } catch (error) {
39
+ issues.push({
40
+ category: "plugins",
41
+ severity: "error",
42
+ code: "PLUGIN_LOAD_FAILED",
43
+ message: `Plugin ${stage}/${name} failed to load: ${error.message}`,
44
+ fix: `Fix the error in ${modulePath}`,
45
+ autoFixable: false,
46
+ });
97
47
  }
98
48
  }
99
- } catch (configError) {
100
- // Config issues handled in checkConfiguration
101
49
  }
102
-
103
50
  return issues;
104
51
  }
105
52
 
@@ -38,6 +38,25 @@ const SEVERITY_LEVELS = {
38
38
  SUCCESS: "success",
39
39
  };
40
40
 
41
+ /**
42
+ * Locate npm's CLI script next to the running node binary (Windows layout:
43
+ * <node dir>/node_modules/npm/bin/npm-cli.js).
44
+ * @returns {string}
45
+ */
46
+ function resolveNpmCli() {
47
+ const candidate = path.join(
48
+ path.dirname(process.execPath),
49
+ "node_modules",
50
+ "npm",
51
+ "bin",
52
+ "npm-cli.js",
53
+ );
54
+ if (!require("fs").existsSync(candidate)) {
55
+ throw new Error("npm not found next to node; run npm install manually");
56
+ }
57
+ return candidate;
58
+ }
59
+
41
60
  /**
42
61
  * Doctor command for pipeline diagnostics
43
62
  */
@@ -118,22 +137,32 @@ class PipelineDoctor {
118
137
  throw new Error(`Command not allowed: ${command}`);
119
138
  }
120
139
 
121
- // Sanitize arguments
140
+ // Sanitize arguments. Backslashes are kept: there is no shell to
141
+ // interpret them, and they are path separators on Windows.
122
142
  const sanitizedArgs = args.map((arg) => {
123
143
  if (typeof arg !== "string") {
124
144
  throw new Error("All arguments must be strings");
125
145
  }
126
- // Remove shell metacharacters
127
- return arg.replace(/[;&|`$(){}[\]<>"'\\]/g, "");
146
+ return arg.replace(/[;&|`$(){}[\]<>"']/g, "");
128
147
  });
129
148
 
130
149
  const execOptions = {
131
- shell: false, // Never use shell to prevent injection
132
150
  timeout: options.timeout || 30000,
133
151
  env: { ...process.env, PATH: process.env.PATH }, // Controlled environment
134
152
  ...options,
153
+ shell: false, // Never use a shell; set last so callers cannot override it
135
154
  };
136
155
 
156
+ // npm is a .cmd shim on Windows, which execFile cannot run without a
157
+ // shell; run npm's JS entry point with node instead.
158
+ if (command === "npm" && process.platform === "win32") {
159
+ return await execFileAsync(
160
+ process.execPath,
161
+ [resolveNpmCli(), ...sanitizedArgs],
162
+ execOptions,
163
+ );
164
+ }
165
+
137
166
  return await execFileAsync(command, sanitizedArgs, execOptions);
138
167
  }
139
168
 
@@ -168,7 +197,12 @@ class PipelineDoctor {
168
197
  // Validate config path is within project directory
169
198
  const configPath = path.resolve(this.options.configPath);
170
199
  const projectDir = path.resolve(".");
171
- if (!configPath.startsWith(projectDir)) {
200
+ // Containment check: a bare startsWith(projectDir) also accepted
201
+ // sibling directories such as "/proj-evil".
202
+ const inside = ["/", "\\"].some((sep) =>
203
+ configPath.startsWith(projectDir + sep),
204
+ );
205
+ if (!inside) {
172
206
  throw new Error("Config path outside project directory");
173
207
  }
174
208