@grafema/util 0.3.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/dist/api/GraphAPI.d.ts +87 -0
- package/dist/api/GraphAPI.d.ts.map +1 -0
- package/dist/api/GraphAPI.js +212 -0
- package/dist/api/GraphAPI.js.map +1 -0
- package/dist/api/GuaranteeAPI.d.ts +147 -0
- package/dist/api/GuaranteeAPI.d.ts.map +1 -0
- package/dist/api/GuaranteeAPI.js +290 -0
- package/dist/api/GuaranteeAPI.js.map +1 -0
- package/dist/config/ConfigLoader.d.ts +214 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +441 -0
- package/dist/config/ConfigLoader.js.map +1 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +5 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/CoverageAnalyzer.d.ts +65 -0
- package/dist/core/CoverageAnalyzer.d.ts.map +1 -0
- package/dist/core/CoverageAnalyzer.js +199 -0
- package/dist/core/CoverageAnalyzer.js.map +1 -0
- package/dist/core/FileExplainer.d.ts +101 -0
- package/dist/core/FileExplainer.d.ts.map +1 -0
- package/dist/core/FileExplainer.js +140 -0
- package/dist/core/FileExplainer.js.map +1 -0
- package/dist/core/FileOverview.d.ts +124 -0
- package/dist/core/FileOverview.d.ts.map +1 -0
- package/dist/core/FileOverview.js +279 -0
- package/dist/core/FileOverview.js.map +1 -0
- package/dist/core/GrafemaUri.d.ts +66 -0
- package/dist/core/GrafemaUri.d.ts.map +1 -0
- package/dist/core/GrafemaUri.js +191 -0
- package/dist/core/GrafemaUri.js.map +1 -0
- package/dist/core/GraphBackend.d.ts +158 -0
- package/dist/core/GraphBackend.d.ts.map +1 -0
- package/dist/core/GraphBackend.js +85 -0
- package/dist/core/GraphBackend.js.map +1 -0
- package/dist/core/GraphFreshnessChecker.d.ts +33 -0
- package/dist/core/GraphFreshnessChecker.d.ts.map +1 -0
- package/dist/core/GraphFreshnessChecker.js +104 -0
- package/dist/core/GraphFreshnessChecker.js.map +1 -0
- package/dist/core/GuaranteeManager.d.ts +254 -0
- package/dist/core/GuaranteeManager.d.ts.map +1 -0
- package/dist/core/GuaranteeManager.js +447 -0
- package/dist/core/GuaranteeManager.js.map +1 -0
- package/dist/core/HashUtils.d.ts +24 -0
- package/dist/core/HashUtils.d.ts.map +1 -0
- package/dist/core/HashUtils.js +46 -0
- package/dist/core/HashUtils.js.map +1 -0
- package/dist/core/IncrementalReanalyzer.d.ts +33 -0
- package/dist/core/IncrementalReanalyzer.d.ts.map +1 -0
- package/dist/core/IncrementalReanalyzer.js +67 -0
- package/dist/core/IncrementalReanalyzer.js.map +1 -0
- package/dist/core/ResourceRegistry.d.ts +17 -0
- package/dist/core/ResourceRegistry.d.ts.map +1 -0
- package/dist/core/ResourceRegistry.js +32 -0
- package/dist/core/ResourceRegistry.js.map +1 -0
- package/dist/core/SemanticId.d.ts +159 -0
- package/dist/core/SemanticId.d.ts.map +1 -0
- package/dist/core/SemanticId.js +291 -0
- package/dist/core/SemanticId.js.map +1 -0
- package/dist/core/VersionManager.d.ts +166 -0
- package/dist/core/VersionManager.d.ts.map +1 -0
- package/dist/core/VersionManager.js +239 -0
- package/dist/core/VersionManager.js.map +1 -0
- package/dist/core/brandNodeInternal.d.ts +14 -0
- package/dist/core/brandNodeInternal.d.ts.map +1 -0
- package/dist/core/brandNodeInternal.js +4 -0
- package/dist/core/brandNodeInternal.js.map +1 -0
- package/dist/core/nodes/GuaranteeNode.d.ts +76 -0
- package/dist/core/nodes/GuaranteeNode.d.ts.map +1 -0
- package/dist/core/nodes/GuaranteeNode.js +118 -0
- package/dist/core/nodes/GuaranteeNode.js.map +1 -0
- package/dist/core/nodes/IssueNode.d.ts +73 -0
- package/dist/core/nodes/IssueNode.d.ts.map +1 -0
- package/dist/core/nodes/IssueNode.js +130 -0
- package/dist/core/nodes/IssueNode.js.map +1 -0
- package/dist/core/nodes/NodeKind.d.ts +104 -0
- package/dist/core/nodes/NodeKind.d.ts.map +1 -0
- package/dist/core/nodes/NodeKind.js +166 -0
- package/dist/core/nodes/NodeKind.js.map +1 -0
- package/dist/diagnostics/DiagnosticCollector.d.ts +103 -0
- package/dist/diagnostics/DiagnosticCollector.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticCollector.js +133 -0
- package/dist/diagnostics/DiagnosticCollector.js.map +1 -0
- package/dist/diagnostics/DiagnosticReporter.d.ts +122 -0
- package/dist/diagnostics/DiagnosticReporter.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticReporter.js +300 -0
- package/dist/diagnostics/DiagnosticReporter.js.map +1 -0
- package/dist/diagnostics/DiagnosticWriter.d.ts +31 -0
- package/dist/diagnostics/DiagnosticWriter.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticWriter.js +44 -0
- package/dist/diagnostics/DiagnosticWriter.js.map +1 -0
- package/dist/diagnostics/categories.d.ts +57 -0
- package/dist/diagnostics/categories.d.ts.map +1 -0
- package/dist/diagnostics/categories.js +71 -0
- package/dist/diagnostics/categories.js.map +1 -0
- package/dist/diagnostics/index.d.ts +17 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +15 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/errors/GrafemaError.d.ts +200 -0
- package/dist/errors/GrafemaError.d.ts.map +1 -0
- package/dist/errors/GrafemaError.js +209 -0
- package/dist/errors/GrafemaError.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/instructions/index.d.ts +8 -0
- package/dist/instructions/index.d.ts.map +1 -0
- package/dist/instructions/index.js +20 -0
- package/dist/instructions/index.js.map +1 -0
- package/dist/instructions/onboarding.md +133 -0
- package/dist/knowledge/KnowledgeBase.d.ts +113 -0
- package/dist/knowledge/KnowledgeBase.d.ts.map +1 -0
- package/dist/knowledge/KnowledgeBase.js +420 -0
- package/dist/knowledge/KnowledgeBase.js.map +1 -0
- package/dist/knowledge/SemanticAddressResolver.d.ts +59 -0
- package/dist/knowledge/SemanticAddressResolver.d.ts.map +1 -0
- package/dist/knowledge/SemanticAddressResolver.js +160 -0
- package/dist/knowledge/SemanticAddressResolver.js.map +1 -0
- package/dist/knowledge/git-ingest.d.ts +58 -0
- package/dist/knowledge/git-ingest.d.ts.map +1 -0
- package/dist/knowledge/git-ingest.js +301 -0
- package/dist/knowledge/git-ingest.js.map +1 -0
- package/dist/knowledge/git-queries.d.ts +86 -0
- package/dist/knowledge/git-queries.d.ts.map +1 -0
- package/dist/knowledge/git-queries.js +177 -0
- package/dist/knowledge/git-queries.js.map +1 -0
- package/dist/knowledge/index.d.ts +14 -0
- package/dist/knowledge/index.d.ts.map +1 -0
- package/dist/knowledge/index.js +10 -0
- package/dist/knowledge/index.js.map +1 -0
- package/dist/knowledge/parser.d.ts +39 -0
- package/dist/knowledge/parser.d.ts.map +1 -0
- package/dist/knowledge/parser.js +292 -0
- package/dist/knowledge/parser.js.map +1 -0
- package/dist/knowledge/types.d.ts +133 -0
- package/dist/knowledge/types.d.ts.map +1 -0
- package/dist/knowledge/types.js +8 -0
- package/dist/knowledge/types.js.map +1 -0
- package/dist/logging/Logger.d.ts +98 -0
- package/dist/logging/Logger.d.ts.map +1 -0
- package/dist/logging/Logger.js +274 -0
- package/dist/logging/Logger.js.map +1 -0
- package/dist/notation/archetypes.d.ts +36 -0
- package/dist/notation/archetypes.d.ts.map +1 -0
- package/dist/notation/archetypes.js +173 -0
- package/dist/notation/archetypes.js.map +1 -0
- package/dist/notation/fold.d.ts +25 -0
- package/dist/notation/fold.d.ts.map +1 -0
- package/dist/notation/fold.js +598 -0
- package/dist/notation/fold.js.map +1 -0
- package/dist/notation/index.d.ts +18 -0
- package/dist/notation/index.d.ts.map +1 -0
- package/dist/notation/index.js +16 -0
- package/dist/notation/index.js.map +1 -0
- package/dist/notation/lodExtractor.d.ts +32 -0
- package/dist/notation/lodExtractor.d.ts.map +1 -0
- package/dist/notation/lodExtractor.js +149 -0
- package/dist/notation/lodExtractor.js.map +1 -0
- package/dist/notation/nameShortener.d.ts +22 -0
- package/dist/notation/nameShortener.d.ts.map +1 -0
- package/dist/notation/nameShortener.js +24 -0
- package/dist/notation/nameShortener.js.map +1 -0
- package/dist/notation/perspectives.d.ts +11 -0
- package/dist/notation/perspectives.d.ts.map +1 -0
- package/dist/notation/perspectives.js +16 -0
- package/dist/notation/perspectives.js.map +1 -0
- package/dist/notation/renderer.d.ts +31 -0
- package/dist/notation/renderer.d.ts.map +1 -0
- package/dist/notation/renderer.js +315 -0
- package/dist/notation/renderer.js.map +1 -0
- package/dist/notation/traceRenderer.d.ts +39 -0
- package/dist/notation/traceRenderer.d.ts.map +1 -0
- package/dist/notation/traceRenderer.js +358 -0
- package/dist/notation/traceRenderer.js.map +1 -0
- package/dist/notation/types.d.ts +66 -0
- package/dist/notation/types.d.ts.map +1 -0
- package/dist/notation/types.js +10 -0
- package/dist/notation/types.js.map +1 -0
- package/dist/queries/NodeContext.d.ts +81 -0
- package/dist/queries/NodeContext.d.ts.map +1 -0
- package/dist/queries/NodeContext.js +196 -0
- package/dist/queries/NodeContext.js.map +1 -0
- package/dist/queries/findCallsInFunction.d.ts +62 -0
- package/dist/queries/findCallsInFunction.d.ts.map +1 -0
- package/dist/queries/findCallsInFunction.js +169 -0
- package/dist/queries/findCallsInFunction.js.map +1 -0
- package/dist/queries/findContainingFunction.d.ts +57 -0
- package/dist/queries/findContainingFunction.d.ts.map +1 -0
- package/dist/queries/findContainingFunction.js +91 -0
- package/dist/queries/findContainingFunction.js.map +1 -0
- package/dist/queries/index.d.ts +18 -0
- package/dist/queries/index.d.ts.map +1 -0
- package/dist/queries/index.js +14 -0
- package/dist/queries/index.js.map +1 -0
- package/dist/queries/traceDataflow.d.ts +65 -0
- package/dist/queries/traceDataflow.d.ts.map +1 -0
- package/dist/queries/traceDataflow.js +754 -0
- package/dist/queries/traceDataflow.js.map +1 -0
- package/dist/queries/traceValues.d.ts +70 -0
- package/dist/queries/traceValues.d.ts.map +1 -0
- package/dist/queries/traceValues.js +373 -0
- package/dist/queries/traceValues.js.map +1 -0
- package/dist/queries/types.d.ts +166 -0
- package/dist/queries/types.d.ts.map +1 -0
- package/dist/queries/types.js +10 -0
- package/dist/queries/types.js.map +1 -0
- package/dist/schema/GraphSchemaExtractor.d.ts +53 -0
- package/dist/schema/GraphSchemaExtractor.d.ts.map +1 -0
- package/dist/schema/GraphSchemaExtractor.js +125 -0
- package/dist/schema/GraphSchemaExtractor.js.map +1 -0
- package/dist/schema/InterfaceSchemaExtractor.d.ts +73 -0
- package/dist/schema/InterfaceSchemaExtractor.d.ts.map +1 -0
- package/dist/schema/InterfaceSchemaExtractor.js +113 -0
- package/dist/schema/InterfaceSchemaExtractor.js.map +1 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/storage/backends/RFDBServerBackend.d.ts +356 -0
- package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -0
- package/dist/storage/backends/RFDBServerBackend.js +748 -0
- package/dist/storage/backends/RFDBServerBackend.js.map +1 -0
- package/dist/storage/backends/typeValidation.d.ts +47 -0
- package/dist/storage/backends/typeValidation.d.ts.map +1 -0
- package/dist/storage/backends/typeValidation.js +141 -0
- package/dist/storage/backends/typeValidation.js.map +1 -0
- package/dist/utils/findRfdbBinary.d.ts +67 -0
- package/dist/utils/findRfdbBinary.d.ts.map +1 -0
- package/dist/utils/findRfdbBinary.js +261 -0
- package/dist/utils/findRfdbBinary.js.map +1 -0
- package/dist/utils/lazyDownload.d.ts +43 -0
- package/dist/utils/lazyDownload.d.ts.map +1 -0
- package/dist/utils/lazyDownload.js +175 -0
- package/dist/utils/lazyDownload.js.map +1 -0
- package/dist/utils/moduleResolution.d.ts +134 -0
- package/dist/utils/moduleResolution.d.ts.map +1 -0
- package/dist/utils/moduleResolution.js +189 -0
- package/dist/utils/moduleResolution.js.map +1 -0
- package/dist/utils/resolveNodeFile.d.ts +13 -0
- package/dist/utils/resolveNodeFile.d.ts.map +1 -0
- package/dist/utils/resolveNodeFile.js +18 -0
- package/dist/utils/resolveNodeFile.js.map +1 -0
- package/dist/utils/startRfdbServer.d.ts +63 -0
- package/dist/utils/startRfdbServer.d.ts.map +1 -0
- package/dist/utils/startRfdbServer.js +142 -0
- package/dist/utils/startRfdbServer.js.map +1 -0
- package/dist/validation/PathValidator.d.ts +80 -0
- package/dist/validation/PathValidator.d.ts.map +1 -0
- package/dist/validation/PathValidator.js +252 -0
- package/dist/validation/PathValidator.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +26 -0
- package/dist/version.js.map +1 -0
- package/package.json +50 -0
- package/src/api/GraphAPI.ts +307 -0
- package/src/api/GuaranteeAPI.ts +402 -0
- package/src/config/ConfigLoader.ts +653 -0
- package/src/config/index.ts +13 -0
- package/src/core/CoverageAnalyzer.ts +243 -0
- package/src/core/FileExplainer.ts +179 -0
- package/src/core/FileOverview.ts +397 -0
- package/src/core/GrafemaUri.ts +216 -0
- package/src/core/GraphBackend.ts +266 -0
- package/src/core/GraphFreshnessChecker.ts +145 -0
- package/src/core/GuaranteeManager.ts +684 -0
- package/src/core/HashUtils.ts +48 -0
- package/src/core/IncrementalReanalyzer.ts +106 -0
- package/src/core/ResourceRegistry.ts +39 -0
- package/src/core/SemanticId.ts +423 -0
- package/src/core/VersionManager.ts +405 -0
- package/src/core/brandNodeInternal.ts +16 -0
- package/src/core/nodes/GuaranteeNode.ts +162 -0
- package/src/core/nodes/IssueNode.ts +177 -0
- package/src/core/nodes/NodeKind.ts +192 -0
- package/src/diagnostics/DiagnosticCollector.ts +170 -0
- package/src/diagnostics/DiagnosticReporter.ts +395 -0
- package/src/diagnostics/DiagnosticWriter.ts +50 -0
- package/src/diagnostics/categories.ts +104 -0
- package/src/diagnostics/index.ts +30 -0
- package/src/errors/GrafemaError.ts +297 -0
- package/src/index.ts +261 -0
- package/src/instructions/index.ts +21 -0
- package/src/instructions/onboarding.md +133 -0
- package/src/knowledge/KnowledgeBase.ts +486 -0
- package/src/knowledge/SemanticAddressResolver.ts +191 -0
- package/src/knowledge/git-ingest.ts +402 -0
- package/src/knowledge/git-queries.ts +269 -0
- package/src/knowledge/index.ts +29 -0
- package/src/knowledge/parser.ts +294 -0
- package/src/knowledge/types.ts +146 -0
- package/src/logging/Logger.ts +303 -0
- package/src/notation/archetypes.ts +189 -0
- package/src/notation/fold.ts +696 -0
- package/src/notation/index.ts +27 -0
- package/src/notation/lodExtractor.ts +177 -0
- package/src/notation/nameShortener.ts +24 -0
- package/src/notation/perspectives.ts +18 -0
- package/src/notation/renderer.ts +394 -0
- package/src/notation/traceRenderer.ts +458 -0
- package/src/notation/types.ts +79 -0
- package/src/queries/NodeContext.ts +280 -0
- package/src/queries/findCallsInFunction.ts +249 -0
- package/src/queries/findContainingFunction.ts +124 -0
- package/src/queries/index.ts +44 -0
- package/src/queries/traceDataflow.ts +838 -0
- package/src/queries/traceValues.ts +531 -0
- package/src/queries/types.ts +191 -0
- package/src/schema/GraphSchemaExtractor.ts +177 -0
- package/src/schema/InterfaceSchemaExtractor.ts +173 -0
- package/src/schema/index.ts +5 -0
- package/src/storage/backends/RFDBServerBackend.ts +895 -0
- package/src/storage/backends/typeValidation.ts +154 -0
- package/src/utils/findRfdbBinary.ts +288 -0
- package/src/utils/lazyDownload.ts +206 -0
- package/src/utils/moduleResolution.ts +271 -0
- package/src/utils/resolveNodeFile.ts +18 -0
- package/src/utils/startRfdbServer.ts +197 -0
- package/src/validation/PathValidator.ts +334 -0
- package/src/version.ts +28 -0
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
import { readFileSync, existsSync, statSync } from 'fs';
|
|
2
|
+
import { join, basename } from 'path';
|
|
3
|
+
import { parse as parseYAML } from 'yaml';
|
|
4
|
+
import type { ServiceDefinition, RoutingRule } from '@grafema/types';
|
|
5
|
+
import { GRAFEMA_VERSION, getSchemaVersion } from '../version.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Grafema configuration schema.
|
|
9
|
+
*
|
|
10
|
+
* YAML Location: .grafema/config.yaml (preferred) or .grafema/config.json (deprecated)
|
|
11
|
+
*
|
|
12
|
+
* Example config.yaml:
|
|
13
|
+
*
|
|
14
|
+
* ```yaml
|
|
15
|
+
* # Plugins for each analysis phase
|
|
16
|
+
* plugins:
|
|
17
|
+
* indexing:
|
|
18
|
+
* - JSModuleIndexer
|
|
19
|
+
* analysis:
|
|
20
|
+
* - CoreV2Analyzer
|
|
21
|
+
* - ExpressRouteAnalyzer
|
|
22
|
+
* enrichment:
|
|
23
|
+
* - ExportEntityLinker
|
|
24
|
+
* validation:
|
|
25
|
+
* - EvalBanValidator
|
|
26
|
+
*
|
|
27
|
+
* # Optional: Explicit service definitions (bypass auto-discovery)
|
|
28
|
+
* services:
|
|
29
|
+
* - name: "backend"
|
|
30
|
+
* path: "apps/backend" # Relative to project root
|
|
31
|
+
* entryPoint: "src/index.ts" # Optional, auto-detected if omitted
|
|
32
|
+
* - name: "frontend"
|
|
33
|
+
* path: "apps/frontend"
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* If 'services' is not specified or empty, auto-discovery is used (SimpleProjectDiscovery).
|
|
37
|
+
* If 'services' is specified and non-empty, auto-discovery plugins are skipped entirely.
|
|
38
|
+
*/
|
|
39
|
+
export interface GrafemaConfig {
|
|
40
|
+
/**
|
|
41
|
+
* Config schema version (major.minor.patch, no pre-release tag).
|
|
42
|
+
* Must be compatible with the running Grafema version.
|
|
43
|
+
* If omitted, no version check is performed (backward compatibility).
|
|
44
|
+
*
|
|
45
|
+
* @example "0.2.5"
|
|
46
|
+
*/
|
|
47
|
+
version?: string;
|
|
48
|
+
|
|
49
|
+
plugins: {
|
|
50
|
+
discovery?: string[];
|
|
51
|
+
indexing: string[];
|
|
52
|
+
analysis: string[];
|
|
53
|
+
enrichment: string[];
|
|
54
|
+
validation: string[];
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Optional explicit services for manual configuration.
|
|
58
|
+
* If provided and non-empty, auto-discovery is skipped.
|
|
59
|
+
*/
|
|
60
|
+
services: ServiceDefinition[];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Glob patterns for files to include during indexing (optional).
|
|
64
|
+
* See OrchestratorConfig.include for documentation.
|
|
65
|
+
*/
|
|
66
|
+
include?: string[];
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Glob patterns for files to exclude during indexing (optional).
|
|
70
|
+
* See OrchestratorConfig.exclude for documentation.
|
|
71
|
+
*/
|
|
72
|
+
exclude?: string[];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Routing rules for cross-service URL mapping (REG-256).
|
|
76
|
+
* Describes how infrastructure (nginx, gateway) transforms URLs between services.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```yaml
|
|
80
|
+
* routing:
|
|
81
|
+
* - from: frontend
|
|
82
|
+
* to: backend
|
|
83
|
+
* stripPrefix: /api
|
|
84
|
+
* - from: frontend
|
|
85
|
+
* to: auth-service
|
|
86
|
+
* stripPrefix: /auth
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
routing?: RoutingRule[];
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Enable strict mode for fail-fast debugging.
|
|
93
|
+
* When true, analysis fails if enrichers cannot resolve references.
|
|
94
|
+
* When false (default), graceful degradation with warnings.
|
|
95
|
+
*
|
|
96
|
+
* Can be overridden via CLI: --strict
|
|
97
|
+
*/
|
|
98
|
+
strict?: boolean;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Multi-root workspace configuration (REG-76).
|
|
102
|
+
* Allows indexing multiple directories as a single unified graph.
|
|
103
|
+
* Each root is prefixed in semantic IDs to prevent collisions.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```yaml
|
|
107
|
+
* workspace:
|
|
108
|
+
* roots:
|
|
109
|
+
* - ./backend
|
|
110
|
+
* - ./frontend
|
|
111
|
+
* - ./shared
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
workspace?: WorkspaceConfig;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* URI configuration for grafema:// identifiers.
|
|
118
|
+
* Used by MCP and CLI for compact<->URI conversion.
|
|
119
|
+
*/
|
|
120
|
+
uri?: {
|
|
121
|
+
/** URI authority (e.g., "github.com/owner/repo"). Auto-detected if omitted. */
|
|
122
|
+
authority?: string;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Multi-root workspace configuration.
|
|
128
|
+
* Each root directory is indexed separately but produces a unified graph.
|
|
129
|
+
* Root names (basename of path) are prefixed to file paths in semantic IDs.
|
|
130
|
+
*/
|
|
131
|
+
export interface WorkspaceConfig {
|
|
132
|
+
/**
|
|
133
|
+
* List of root directories to include in the workspace.
|
|
134
|
+
* Paths are relative to the project root (where .grafema/ is located).
|
|
135
|
+
* Each root's basename is used as prefix in semantic IDs.
|
|
136
|
+
*/
|
|
137
|
+
roots: string[];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Default plugin configuration.
|
|
142
|
+
* Matches current DEFAULT_PLUGINS in analyze.ts and config.ts (MCP).
|
|
143
|
+
*/
|
|
144
|
+
export const DEFAULT_CONFIG: GrafemaConfig = {
|
|
145
|
+
version: getSchemaVersion(GRAFEMA_VERSION),
|
|
146
|
+
plugins: {
|
|
147
|
+
discovery: [],
|
|
148
|
+
indexing: ['JSModuleIndexer'],
|
|
149
|
+
analysis: [
|
|
150
|
+
'CoreV2Analyzer',
|
|
151
|
+
'ExpressRouteAnalyzer',
|
|
152
|
+
'ExpressResponseAnalyzer',
|
|
153
|
+
'NestJSRouteAnalyzer',
|
|
154
|
+
'SocketIOAnalyzer',
|
|
155
|
+
'DatabaseAnalyzer',
|
|
156
|
+
'FetchAnalyzer',
|
|
157
|
+
'ServiceLayerAnalyzer',
|
|
158
|
+
],
|
|
159
|
+
enrichment: [
|
|
160
|
+
'ExportEntityLinker',
|
|
161
|
+
'CallbackCallResolver',
|
|
162
|
+
'RejectionPropagationEnricher',
|
|
163
|
+
'ValueDomainAnalyzer',
|
|
164
|
+
'MountPointResolver',
|
|
165
|
+
'ExpressHandlerLinker',
|
|
166
|
+
'PrefixEvaluator',
|
|
167
|
+
'ConfigRoutingMapBuilder',
|
|
168
|
+
'ServiceConnectionEnricher',
|
|
169
|
+
'RedisEnricher',
|
|
170
|
+
],
|
|
171
|
+
validation: [
|
|
172
|
+
'GraphConnectivityValidator',
|
|
173
|
+
'DataFlowValidator',
|
|
174
|
+
'EvalBanValidator',
|
|
175
|
+
'CallResolverValidator',
|
|
176
|
+
'SQLInjectionValidator',
|
|
177
|
+
'AwaitInLoopValidator',
|
|
178
|
+
'ShadowingDetector',
|
|
179
|
+
'BrokenImportValidator',
|
|
180
|
+
'UnconnectedRouteValidator',
|
|
181
|
+
'PackageCoverageValidator',
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
services: [], // Empty by default (uses auto-discovery)
|
|
185
|
+
strict: false, // Graceful degradation by default
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Load Grafema config from project directory.
|
|
190
|
+
*
|
|
191
|
+
* Priority:
|
|
192
|
+
* 1. config.yaml (preferred)
|
|
193
|
+
* 2. config.json (deprecated, fallback)
|
|
194
|
+
* 3. DEFAULT_CONFIG (if neither exists)
|
|
195
|
+
*
|
|
196
|
+
* Warnings:
|
|
197
|
+
* - Logs deprecation warning if config.json is used
|
|
198
|
+
* - Logs parse errors but doesn't throw (returns defaults)
|
|
199
|
+
*
|
|
200
|
+
* @param projectPath - Absolute path to project root
|
|
201
|
+
* @param logger - Optional logger for warnings (defaults to console.warn)
|
|
202
|
+
* @returns Parsed config or defaults
|
|
203
|
+
*/
|
|
204
|
+
export function loadConfig(
|
|
205
|
+
projectPath: string,
|
|
206
|
+
logger: { warn: (msg: string) => void } = console
|
|
207
|
+
): GrafemaConfig {
|
|
208
|
+
const grafemaDir = join(projectPath, '.grafema');
|
|
209
|
+
const yamlPath = join(grafemaDir, 'config.yaml');
|
|
210
|
+
const jsonPath = join(grafemaDir, 'config.json');
|
|
211
|
+
|
|
212
|
+
// 1. Try YAML first (preferred)
|
|
213
|
+
if (existsSync(yamlPath)) {
|
|
214
|
+
let parsed: Partial<GrafemaConfig>;
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const content = readFileSync(yamlPath, 'utf-8');
|
|
218
|
+
parsed = parseYAML(content) as Partial<GrafemaConfig>;
|
|
219
|
+
|
|
220
|
+
// Validate structure - ensure plugins sections are arrays if they exist
|
|
221
|
+
if (parsed.plugins) {
|
|
222
|
+
for (const phase of ['discovery', 'indexing', 'analysis', 'enrichment', 'validation'] as const) {
|
|
223
|
+
const value = parsed.plugins[phase];
|
|
224
|
+
if (value !== undefined && value !== null && !Array.isArray(value)) {
|
|
225
|
+
throw new Error(`plugins.${phase} must be an array, got ${typeof value}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
} catch (err) {
|
|
230
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
231
|
+
logger.warn(`Failed to parse config.yaml: ${error.message}`);
|
|
232
|
+
logger.warn('Using default configuration');
|
|
233
|
+
return DEFAULT_CONFIG;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Validate version compatibility (THROWS on error) - REG-403
|
|
237
|
+
validateVersion(parsed.version);
|
|
238
|
+
|
|
239
|
+
// Validate services array if present (THROWS on error per Linus review)
|
|
240
|
+
// This is OUTSIDE try-catch - config errors MUST throw
|
|
241
|
+
validateServices(parsed.services, projectPath);
|
|
242
|
+
|
|
243
|
+
// Validate include/exclude patterns (THROWS on error)
|
|
244
|
+
validatePatterns(parsed.include, parsed.exclude, logger);
|
|
245
|
+
|
|
246
|
+
// Validate workspace.roots if present (THROWS on error) - REG-76
|
|
247
|
+
validateWorkspace(parsed.workspace, projectPath);
|
|
248
|
+
|
|
249
|
+
// Validate routing rules if present (THROWS on error) - REG-256
|
|
250
|
+
validateRouting(parsed.routing, (parsed.services || []) as ServiceDefinition[]);
|
|
251
|
+
|
|
252
|
+
// Merge with defaults (user config may be partial)
|
|
253
|
+
return mergeConfig(DEFAULT_CONFIG, parsed);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 2. Fallback to JSON (migration path)
|
|
257
|
+
if (existsSync(jsonPath)) {
|
|
258
|
+
logger.warn('⚠ config.json is deprecated. Run "grafema init --force" to migrate to config.yaml');
|
|
259
|
+
|
|
260
|
+
let parsed: Partial<GrafemaConfig>;
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
const content = readFileSync(jsonPath, 'utf-8');
|
|
264
|
+
parsed = JSON.parse(content) as Partial<GrafemaConfig>;
|
|
265
|
+
} catch (err) {
|
|
266
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
267
|
+
logger.warn(`Failed to parse config.json: ${error.message}`);
|
|
268
|
+
logger.warn('Using default configuration');
|
|
269
|
+
return DEFAULT_CONFIG;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Validate version compatibility (THROWS on error) - REG-403
|
|
273
|
+
validateVersion(parsed.version);
|
|
274
|
+
|
|
275
|
+
// Validate services array if present (THROWS on error)
|
|
276
|
+
// This is OUTSIDE try-catch - config errors MUST throw
|
|
277
|
+
validateServices(parsed.services, projectPath);
|
|
278
|
+
|
|
279
|
+
// Validate include/exclude patterns (THROWS on error)
|
|
280
|
+
validatePatterns(parsed.include, parsed.exclude, logger);
|
|
281
|
+
|
|
282
|
+
// Validate workspace.roots if present (THROWS on error) - REG-76
|
|
283
|
+
validateWorkspace(parsed.workspace, projectPath);
|
|
284
|
+
|
|
285
|
+
// Validate routing rules if present (THROWS on error) - REG-256
|
|
286
|
+
validateRouting(parsed.routing, (parsed.services || []) as ServiceDefinition[]);
|
|
287
|
+
|
|
288
|
+
return mergeConfig(DEFAULT_CONFIG, parsed);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 3. No config file - return defaults
|
|
292
|
+
return DEFAULT_CONFIG;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Validate config version compatibility with running Grafema version.
|
|
297
|
+
* THROWS on error (fail loudly per project convention).
|
|
298
|
+
*
|
|
299
|
+
* Compares major.minor.patch (pre-release tags are stripped).
|
|
300
|
+
* If config has no version field, validation passes silently (backward compat).
|
|
301
|
+
*
|
|
302
|
+
* @param configVersion - Version string from config file (may be undefined)
|
|
303
|
+
* @param currentVersion - Override for testing (defaults to GRAFEMA_VERSION)
|
|
304
|
+
*/
|
|
305
|
+
export function validateVersion(
|
|
306
|
+
configVersion: unknown,
|
|
307
|
+
currentVersion?: string
|
|
308
|
+
): void {
|
|
309
|
+
// No version field = backward compat, accept silently
|
|
310
|
+
if (configVersion === undefined || configVersion === null) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (typeof configVersion !== 'string') {
|
|
315
|
+
throw new Error(`Config error: version must be a string, got ${typeof configVersion}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (!configVersion.trim()) {
|
|
319
|
+
throw new Error('Config error: version cannot be empty');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const current = currentVersion ?? GRAFEMA_VERSION;
|
|
323
|
+
const configSchema = getSchemaVersion(configVersion);
|
|
324
|
+
const currentSchema = getSchemaVersion(current);
|
|
325
|
+
|
|
326
|
+
if (configSchema !== currentSchema) {
|
|
327
|
+
throw new Error(
|
|
328
|
+
`Config error: config version "${configVersion}" is not compatible with ` +
|
|
329
|
+
`Grafema ${current}. Expected "${currentSchema}".\n` +
|
|
330
|
+
` Run: grafema init --force (to regenerate config for current version)`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Validate services array structure.
|
|
337
|
+
* THROWS on error (fail loudly per Linus review).
|
|
338
|
+
*
|
|
339
|
+
* @param services - Parsed services array (may be undefined)
|
|
340
|
+
* @param projectPath - Project root for path validation
|
|
341
|
+
*/
|
|
342
|
+
export function validateServices(services: unknown, projectPath: string): void {
|
|
343
|
+
// undefined/null is valid (means use defaults)
|
|
344
|
+
if (services === undefined || services === null) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Must be an array
|
|
349
|
+
if (!Array.isArray(services)) {
|
|
350
|
+
throw new Error(`Config error: services must be an array, got ${typeof services}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Validate each service
|
|
354
|
+
for (let i = 0; i < services.length; i++) {
|
|
355
|
+
const svc = services[i];
|
|
356
|
+
|
|
357
|
+
// Must be an object
|
|
358
|
+
if (typeof svc !== 'object' || svc === null) {
|
|
359
|
+
throw new Error(`Config error: services[${i}] must be an object`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Name validation - required, non-empty string
|
|
363
|
+
if (typeof svc.name !== 'string') {
|
|
364
|
+
throw new Error(`Config error: services[${i}].name must be a string, got ${typeof svc.name}`);
|
|
365
|
+
}
|
|
366
|
+
if (!svc.name.trim()) {
|
|
367
|
+
throw new Error(`Config error: services[${i}].name cannot be empty or whitespace-only`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Path validation - required, non-empty string
|
|
371
|
+
if (typeof svc.path !== 'string') {
|
|
372
|
+
throw new Error(`Config error: services[${i}].path must be a string, got ${typeof svc.path}`);
|
|
373
|
+
}
|
|
374
|
+
if (!svc.path.trim()) {
|
|
375
|
+
throw new Error(`Config error: services[${i}].path cannot be empty or whitespace-only`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Path validation - must be relative (reject absolute paths per Linus review)
|
|
379
|
+
if (svc.path.startsWith('/') || svc.path.startsWith('~')) {
|
|
380
|
+
throw new Error(`Config error: services[${i}].path must be relative to project root, got "${svc.path}"`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Path validation - must exist
|
|
384
|
+
const absolutePath = join(projectPath, svc.path);
|
|
385
|
+
if (!existsSync(absolutePath)) {
|
|
386
|
+
throw new Error(`Config error: services[${i}].path "${svc.path}" does not exist`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Path validation - must be directory
|
|
390
|
+
if (!statSync(absolutePath).isDirectory()) {
|
|
391
|
+
throw new Error(`Config error: services[${i}].path "${svc.path}" must be a directory`);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// entryPoint validation (optional field) - must be non-empty string if provided
|
|
395
|
+
if (svc.entryPoint !== undefined) {
|
|
396
|
+
if (typeof svc.entryPoint !== 'string') {
|
|
397
|
+
throw new Error(`Config error: services[${i}].entryPoint must be a string, got ${typeof svc.entryPoint}`);
|
|
398
|
+
}
|
|
399
|
+
if (!svc.entryPoint.trim()) {
|
|
400
|
+
throw new Error(`Config error: services[${i}].entryPoint cannot be empty or whitespace-only`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// customerFacing validation (optional field) - must be boolean if provided (REG-256)
|
|
405
|
+
if (svc.customerFacing !== undefined) {
|
|
406
|
+
if (typeof svc.customerFacing !== 'boolean') {
|
|
407
|
+
throw new Error(`Config error: services[${i}].customerFacing must be a boolean, got ${typeof svc.customerFacing}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Validate workspace configuration (REG-76).
|
|
415
|
+
* THROWS on error (fail loudly per project convention).
|
|
416
|
+
*
|
|
417
|
+
* Validation rules:
|
|
418
|
+
* 1. workspace.roots must be an array if provided
|
|
419
|
+
* 2. Each root must be a non-empty string
|
|
420
|
+
* 3. Each root path must exist and be a directory
|
|
421
|
+
* 4. Root basenames must be unique (to prevent semantic ID collisions)
|
|
422
|
+
*
|
|
423
|
+
* @param workspace - Parsed workspace config (may be undefined)
|
|
424
|
+
* @param projectPath - Project root for path validation
|
|
425
|
+
*/
|
|
426
|
+
export function validateWorkspace(workspace: unknown, projectPath: string): void {
|
|
427
|
+
// undefined/null is valid (means single-root mode)
|
|
428
|
+
if (workspace === undefined || workspace === null) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Must be an object
|
|
433
|
+
if (typeof workspace !== 'object') {
|
|
434
|
+
throw new Error(`Config error: workspace must be an object, got ${typeof workspace}`);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const ws = workspace as { roots?: unknown };
|
|
438
|
+
|
|
439
|
+
// roots is optional, but if provided must be valid
|
|
440
|
+
if (ws.roots === undefined || ws.roots === null) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// roots must be an array
|
|
445
|
+
if (!Array.isArray(ws.roots)) {
|
|
446
|
+
throw new Error(`Config error: workspace.roots must be an array, got ${typeof ws.roots}`);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Track root names for duplicate detection
|
|
450
|
+
const seenNames = new Set<string>();
|
|
451
|
+
|
|
452
|
+
// Validate each root
|
|
453
|
+
for (let i = 0; i < ws.roots.length; i++) {
|
|
454
|
+
const root = ws.roots[i];
|
|
455
|
+
|
|
456
|
+
// Must be a string
|
|
457
|
+
if (typeof root !== 'string') {
|
|
458
|
+
throw new Error(`Config error: workspace.roots[${i}] must be a string, got ${typeof root}`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Must not be empty
|
|
462
|
+
if (!root.trim()) {
|
|
463
|
+
throw new Error(`Config error: workspace.roots[${i}] cannot be empty or whitespace-only`);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Must be relative path
|
|
467
|
+
if (root.startsWith('/') || root.startsWith('~')) {
|
|
468
|
+
throw new Error(`Config error: workspace.roots[${i}] must be relative to project root, got "${root}"`);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Path must exist
|
|
472
|
+
const absolutePath = join(projectPath, root);
|
|
473
|
+
if (!existsSync(absolutePath)) {
|
|
474
|
+
throw new Error(`Config error: workspace.roots[${i}] "${root}" does not exist`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Path must be a directory
|
|
478
|
+
if (!statSync(absolutePath).isDirectory()) {
|
|
479
|
+
throw new Error(`Config error: workspace.roots[${i}] "${root}" must be a directory`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Check for duplicate root names (basename)
|
|
483
|
+
const rootName = basename(root);
|
|
484
|
+
if (seenNames.has(rootName)) {
|
|
485
|
+
throw new Error(`Config error: Duplicate workspace root name "${rootName}" - root names (basenames) must be unique`);
|
|
486
|
+
}
|
|
487
|
+
seenNames.add(rootName);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Validate routing rules structure (REG-256).
|
|
493
|
+
* THROWS on error (fail loudly per project convention).
|
|
494
|
+
*
|
|
495
|
+
* Validation rules:
|
|
496
|
+
* 1. Must be an array if provided
|
|
497
|
+
* 2. Each rule must have 'from' and 'to' as non-empty strings
|
|
498
|
+
* 3. 'stripPrefix' must start with '/' if provided
|
|
499
|
+
* 4. 'addPrefix' must start with '/' if provided
|
|
500
|
+
* 5. 'from' and 'to' must reference services defined in the services array
|
|
501
|
+
*
|
|
502
|
+
* @param routing - Parsed routing rules (may be undefined)
|
|
503
|
+
* @param services - Parsed services array (for cross-validation)
|
|
504
|
+
*/
|
|
505
|
+
export function validateRouting(routing: unknown, services: ServiceDefinition[]): void {
|
|
506
|
+
if (routing === undefined || routing === null) return;
|
|
507
|
+
|
|
508
|
+
if (!Array.isArray(routing)) {
|
|
509
|
+
throw new Error(`Config error: routing must be an array, got ${typeof routing}`);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const serviceNames = new Set(services.map(s => s.name));
|
|
513
|
+
|
|
514
|
+
for (let i = 0; i < routing.length; i++) {
|
|
515
|
+
const rule = routing[i];
|
|
516
|
+
|
|
517
|
+
if (typeof rule !== 'object' || rule === null) {
|
|
518
|
+
throw new Error(`Config error: routing[${i}] must be an object`);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// from — required
|
|
522
|
+
if (typeof rule.from !== 'string' || !rule.from.trim()) {
|
|
523
|
+
throw new Error(`Config error: routing[${i}].from must be a non-empty string`);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// to — required
|
|
527
|
+
if (typeof rule.to !== 'string' || !rule.to.trim()) {
|
|
528
|
+
throw new Error(`Config error: routing[${i}].to must be a non-empty string`);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Cross-validate against services (only if services are defined)
|
|
532
|
+
if (serviceNames.size > 0) {
|
|
533
|
+
if (!serviceNames.has(rule.from)) {
|
|
534
|
+
throw new Error(
|
|
535
|
+
`Config error: routing[${i}].from "${rule.from}" does not match any service name. ` +
|
|
536
|
+
`Available: ${[...serviceNames].join(', ')}`
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
if (!serviceNames.has(rule.to)) {
|
|
540
|
+
throw new Error(
|
|
541
|
+
`Config error: routing[${i}].to "${rule.to}" does not match any service name. ` +
|
|
542
|
+
`Available: ${[...serviceNames].join(', ')}`
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// stripPrefix — optional, must start with /
|
|
548
|
+
if (rule.stripPrefix !== undefined) {
|
|
549
|
+
if (typeof rule.stripPrefix !== 'string') {
|
|
550
|
+
throw new Error(`Config error: routing[${i}].stripPrefix must be a string`);
|
|
551
|
+
}
|
|
552
|
+
if (!rule.stripPrefix.startsWith('/')) {
|
|
553
|
+
throw new Error(`Config error: routing[${i}].stripPrefix must start with '/'`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// addPrefix — optional, must start with /
|
|
558
|
+
if (rule.addPrefix !== undefined) {
|
|
559
|
+
if (typeof rule.addPrefix !== 'string') {
|
|
560
|
+
throw new Error(`Config error: routing[${i}].addPrefix must be a string`);
|
|
561
|
+
}
|
|
562
|
+
if (!rule.addPrefix.startsWith('/')) {
|
|
563
|
+
throw new Error(`Config error: routing[${i}].addPrefix must start with '/'`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Validate include/exclude patterns.
|
|
571
|
+
* THROWS on error (fail loudly per project convention).
|
|
572
|
+
*
|
|
573
|
+
* Validation rules:
|
|
574
|
+
* 1. Must be arrays if provided
|
|
575
|
+
* 2. Array elements must be non-empty strings
|
|
576
|
+
* 3. Warn (don't error) if include array is empty (would exclude everything)
|
|
577
|
+
*
|
|
578
|
+
* @param include - Parsed include patterns (may be undefined)
|
|
579
|
+
* @param exclude - Parsed exclude patterns (may be undefined)
|
|
580
|
+
* @param logger - Logger for warnings
|
|
581
|
+
*/
|
|
582
|
+
export function validatePatterns(
|
|
583
|
+
include: unknown,
|
|
584
|
+
exclude: unknown,
|
|
585
|
+
logger: { warn: (msg: string) => void }
|
|
586
|
+
): void {
|
|
587
|
+
// Validate include
|
|
588
|
+
if (include !== undefined && include !== null) {
|
|
589
|
+
if (!Array.isArray(include)) {
|
|
590
|
+
throw new Error(`Config error: include must be an array, got ${typeof include}`);
|
|
591
|
+
}
|
|
592
|
+
for (let i = 0; i < include.length; i++) {
|
|
593
|
+
if (typeof include[i] !== 'string') {
|
|
594
|
+
throw new Error(`Config error: include[${i}] must be a string, got ${typeof include[i]}`);
|
|
595
|
+
}
|
|
596
|
+
if (!include[i].trim()) {
|
|
597
|
+
throw new Error(`Config error: include[${i}] cannot be empty or whitespace-only`);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
// Warn if empty array (would exclude everything)
|
|
601
|
+
if (include.length === 0) {
|
|
602
|
+
logger.warn('Warning: include is an empty array - no files will be processed');
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Validate exclude
|
|
607
|
+
if (exclude !== undefined && exclude !== null) {
|
|
608
|
+
if (!Array.isArray(exclude)) {
|
|
609
|
+
throw new Error(`Config error: exclude must be an array, got ${typeof exclude}`);
|
|
610
|
+
}
|
|
611
|
+
for (let i = 0; i < exclude.length; i++) {
|
|
612
|
+
if (typeof exclude[i] !== 'string') {
|
|
613
|
+
throw new Error(`Config error: exclude[${i}] must be a string, got ${typeof exclude[i]}`);
|
|
614
|
+
}
|
|
615
|
+
if (!exclude[i].trim()) {
|
|
616
|
+
throw new Error(`Config error: exclude[${i}] cannot be empty or whitespace-only`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Merge user config with defaults.
|
|
624
|
+
* User config takes precedence, but missing sections use defaults.
|
|
625
|
+
*/
|
|
626
|
+
function mergeConfig(
|
|
627
|
+
defaults: GrafemaConfig,
|
|
628
|
+
user: Partial<GrafemaConfig>
|
|
629
|
+
): GrafemaConfig {
|
|
630
|
+
return {
|
|
631
|
+
version: user.version ?? defaults.version,
|
|
632
|
+
plugins: {
|
|
633
|
+
discovery: user.plugins?.discovery ?? defaults.plugins.discovery,
|
|
634
|
+
indexing: user.plugins?.indexing ?? defaults.plugins.indexing,
|
|
635
|
+
analysis: user.plugins?.analysis ?? defaults.plugins.analysis,
|
|
636
|
+
enrichment: user.plugins?.enrichment ?? defaults.plugins.enrichment,
|
|
637
|
+
validation: user.plugins?.validation ?? defaults.plugins.validation,
|
|
638
|
+
},
|
|
639
|
+
services: user.services ?? defaults.services,
|
|
640
|
+
// Include/exclude patterns: pass through if specified, otherwise undefined
|
|
641
|
+
// (don't merge with defaults - undefined means "no filtering")
|
|
642
|
+
// Note: YAML null becomes undefined here (null ?? undefined = undefined)
|
|
643
|
+
include: user.include ?? undefined,
|
|
644
|
+
exclude: user.exclude ?? undefined,
|
|
645
|
+
strict: user.strict ?? defaults.strict,
|
|
646
|
+
// Routing rules: pass through if specified (REG-256)
|
|
647
|
+
routing: user.routing ?? undefined,
|
|
648
|
+
// Workspace config: pass through if specified (REG-76)
|
|
649
|
+
workspace: user.workspace ?? undefined,
|
|
650
|
+
// URI config: pass through if specified (REG-666)
|
|
651
|
+
uri: user.uri ?? undefined,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loading utilities
|
|
3
|
+
*/
|
|
4
|
+
export {
|
|
5
|
+
loadConfig,
|
|
6
|
+
DEFAULT_CONFIG,
|
|
7
|
+
validateVersion,
|
|
8
|
+
validateServices,
|
|
9
|
+
validatePatterns,
|
|
10
|
+
validateWorkspace,
|
|
11
|
+
validateRouting,
|
|
12
|
+
} from './ConfigLoader.js';
|
|
13
|
+
export type { GrafemaConfig } from './ConfigLoader.js';
|