driftdetect-core 0.4.6 → 0.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.
- package/dist/call-graph/extractors/csharp-data-access-extractor.d.ts +8 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/csharp-data-access-extractor.js +8 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.js.map +1 -1
- package/dist/call-graph/extractors/csharp-extractor.d.ts +35 -0
- package/dist/call-graph/extractors/csharp-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/csharp-extractor.js +362 -4
- package/dist/call-graph/extractors/csharp-extractor.js.map +1 -1
- package/dist/call-graph/extractors/csharp-hybrid-extractor.d.ts +37 -0
- package/dist/call-graph/extractors/csharp-hybrid-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/csharp-hybrid-extractor.js +408 -0
- package/dist/call-graph/extractors/csharp-hybrid-extractor.js.map +1 -0
- package/dist/call-graph/extractors/hybrid-extractor-base.d.ts +102 -0
- package/dist/call-graph/extractors/hybrid-extractor-base.d.ts.map +1 -0
- package/dist/call-graph/extractors/hybrid-extractor-base.js +289 -0
- package/dist/call-graph/extractors/hybrid-extractor-base.js.map +1 -0
- package/dist/call-graph/extractors/index.d.ts +17 -13
- package/dist/call-graph/extractors/index.d.ts.map +1 -1
- package/dist/call-graph/extractors/index.js +24 -23
- package/dist/call-graph/extractors/index.js.map +1 -1
- package/dist/call-graph/extractors/java-data-access-extractor.d.ts +8 -0
- package/dist/call-graph/extractors/java-data-access-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/java-data-access-extractor.js +8 -0
- package/dist/call-graph/extractors/java-data-access-extractor.js.map +1 -1
- package/dist/call-graph/extractors/java-extractor.d.ts +15 -0
- package/dist/call-graph/extractors/java-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/java-extractor.js +120 -4
- package/dist/call-graph/extractors/java-extractor.js.map +1 -1
- package/dist/call-graph/extractors/java-hybrid-extractor.d.ts +36 -0
- package/dist/call-graph/extractors/java-hybrid-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/java-hybrid-extractor.js +426 -0
- package/dist/call-graph/extractors/java-hybrid-extractor.js.map +1 -0
- package/dist/call-graph/extractors/php-data-access-extractor.d.ts +8 -0
- package/dist/call-graph/extractors/php-data-access-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/php-data-access-extractor.js +8 -0
- package/dist/call-graph/extractors/php-data-access-extractor.js.map +1 -1
- package/dist/call-graph/extractors/php-extractor.d.ts +48 -1
- package/dist/call-graph/extractors/php-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/php-extractor.js +460 -6
- package/dist/call-graph/extractors/php-extractor.js.map +1 -1
- package/dist/call-graph/extractors/php-hybrid-extractor.d.ts +35 -0
- package/dist/call-graph/extractors/php-hybrid-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/php-hybrid-extractor.js +393 -0
- package/dist/call-graph/extractors/php-hybrid-extractor.js.map +1 -0
- package/dist/call-graph/extractors/python-data-access-extractor.d.ts +8 -0
- package/dist/call-graph/extractors/python-data-access-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/python-data-access-extractor.js +8 -0
- package/dist/call-graph/extractors/python-data-access-extractor.js.map +1 -1
- package/dist/call-graph/extractors/python-hybrid-extractor.d.ts +85 -0
- package/dist/call-graph/extractors/python-hybrid-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/python-hybrid-extractor.js +462 -0
- package/dist/call-graph/extractors/python-hybrid-extractor.js.map +1 -0
- package/dist/call-graph/extractors/regex/base-regex-extractor.d.ts +154 -0
- package/dist/call-graph/extractors/regex/base-regex-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/regex/base-regex-extractor.js +346 -0
- package/dist/call-graph/extractors/regex/base-regex-extractor.js.map +1 -0
- package/dist/call-graph/extractors/regex/csharp-regex.d.ts +34 -0
- package/dist/call-graph/extractors/regex/csharp-regex.d.ts.map +1 -0
- package/dist/call-graph/extractors/regex/csharp-regex.js +393 -0
- package/dist/call-graph/extractors/regex/csharp-regex.js.map +1 -0
- package/dist/call-graph/extractors/regex/index.d.ts +25 -0
- package/dist/call-graph/extractors/regex/index.d.ts.map +1 -0
- package/dist/call-graph/extractors/regex/index.js +66 -0
- package/dist/call-graph/extractors/regex/index.js.map +1 -0
- package/dist/call-graph/extractors/regex/java-regex.d.ts +34 -0
- package/dist/call-graph/extractors/regex/java-regex.d.ts.map +1 -0
- package/dist/call-graph/extractors/regex/java-regex.js +327 -0
- package/dist/call-graph/extractors/regex/java-regex.js.map +1 -0
- package/dist/call-graph/extractors/regex/php-regex.d.ts +30 -0
- package/dist/call-graph/extractors/regex/php-regex.d.ts.map +1 -0
- package/dist/call-graph/extractors/regex/php-regex.js +333 -0
- package/dist/call-graph/extractors/regex/php-regex.js.map +1 -0
- package/dist/call-graph/extractors/regex/python-regex.d.ts +46 -0
- package/dist/call-graph/extractors/regex/python-regex.d.ts.map +1 -0
- package/dist/call-graph/extractors/regex/python-regex.js +380 -0
- package/dist/call-graph/extractors/regex/python-regex.js.map +1 -0
- package/dist/call-graph/extractors/regex/typescript-regex.d.ts +27 -0
- package/dist/call-graph/extractors/regex/typescript-regex.d.ts.map +1 -0
- package/dist/call-graph/extractors/regex/typescript-regex.js +349 -0
- package/dist/call-graph/extractors/regex/typescript-regex.js.map +1 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts +7 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts.map +1 -1
- package/dist/call-graph/extractors/semantic-data-access-scanner.js +7 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.js.map +1 -1
- package/dist/call-graph/extractors/types.d.ts +111 -0
- package/dist/call-graph/extractors/types.d.ts.map +1 -0
- package/dist/call-graph/extractors/types.js +68 -0
- package/dist/call-graph/extractors/types.js.map +1 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts +8 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/typescript-data-access-extractor.js +8 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.js.map +1 -1
- package/dist/call-graph/extractors/typescript-hybrid-extractor.d.ts +116 -0
- package/dist/call-graph/extractors/typescript-hybrid-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/typescript-hybrid-extractor.js +635 -0
- package/dist/call-graph/extractors/typescript-hybrid-extractor.js.map +1 -0
- package/dist/error-handling/error-handling-analyzer.d.ts +73 -0
- package/dist/error-handling/error-handling-analyzer.d.ts.map +1 -0
- package/dist/error-handling/error-handling-analyzer.js +706 -0
- package/dist/error-handling/error-handling-analyzer.js.map +1 -0
- package/dist/error-handling/index.d.ts +8 -0
- package/dist/error-handling/index.d.ts.map +1 -0
- package/dist/error-handling/index.js +8 -0
- package/dist/error-handling/index.js.map +1 -0
- package/dist/error-handling/types.d.ts +307 -0
- package/dist/error-handling/types.d.ts.map +1 -0
- package/dist/error-handling/types.js +7 -0
- package/dist/error-handling/types.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -1
- package/dist/lake/pattern-shard-store.d.ts +6 -0
- package/dist/lake/pattern-shard-store.d.ts.map +1 -1
- package/dist/lake/pattern-shard-store.js +6 -0
- package/dist/lake/pattern-shard-store.js.map +1 -1
- package/dist/module-coupling/coupling-analyzer.d.ts +73 -0
- package/dist/module-coupling/coupling-analyzer.d.ts.map +1 -0
- package/dist/module-coupling/coupling-analyzer.js +668 -0
- package/dist/module-coupling/coupling-analyzer.js.map +1 -0
- package/dist/module-coupling/index.d.ts +9 -0
- package/dist/module-coupling/index.d.ts.map +1 -0
- package/dist/module-coupling/index.js +9 -0
- package/dist/module-coupling/index.js.map +1 -0
- package/dist/module-coupling/types.d.ts +273 -0
- package/dist/module-coupling/types.d.ts.map +1 -0
- package/dist/module-coupling/types.js +8 -0
- package/dist/module-coupling/types.js.map +1 -0
- package/dist/patterns/adapters/index.d.ts +11 -0
- package/dist/patterns/adapters/index.d.ts.map +1 -0
- package/dist/patterns/adapters/index.js +11 -0
- package/dist/patterns/adapters/index.js.map +1 -0
- package/dist/patterns/adapters/pattern-store-adapter.d.ts +59 -0
- package/dist/patterns/adapters/pattern-store-adapter.d.ts.map +1 -0
- package/dist/patterns/adapters/pattern-store-adapter.js +468 -0
- package/dist/patterns/adapters/pattern-store-adapter.js.map +1 -0
- package/dist/patterns/adapters/service-factory.d.ts +40 -0
- package/dist/patterns/adapters/service-factory.d.ts.map +1 -0
- package/dist/patterns/adapters/service-factory.js +144 -0
- package/dist/patterns/adapters/service-factory.js.map +1 -0
- package/dist/patterns/errors.d.ts +32 -0
- package/dist/patterns/errors.d.ts.map +1 -0
- package/dist/patterns/errors.js +45 -0
- package/dist/patterns/errors.js.map +1 -0
- package/dist/patterns/impl/cached-repository.d.ts +79 -0
- package/dist/patterns/impl/cached-repository.d.ts.map +1 -0
- package/dist/patterns/impl/cached-repository.js +296 -0
- package/dist/patterns/impl/cached-repository.js.map +1 -0
- package/dist/patterns/impl/file-repository.d.ts +75 -0
- package/dist/patterns/impl/file-repository.d.ts.map +1 -0
- package/dist/patterns/impl/file-repository.js +507 -0
- package/dist/patterns/impl/file-repository.js.map +1 -0
- package/dist/patterns/impl/index.d.ts +16 -0
- package/dist/patterns/impl/index.d.ts.map +1 -0
- package/dist/patterns/impl/index.js +21 -0
- package/dist/patterns/impl/index.js.map +1 -0
- package/dist/patterns/impl/memory-repository.d.ts +56 -0
- package/dist/patterns/impl/memory-repository.d.ts.map +1 -0
- package/dist/patterns/impl/memory-repository.js +323 -0
- package/dist/patterns/impl/memory-repository.js.map +1 -0
- package/dist/patterns/impl/pattern-service.d.ts +52 -0
- package/dist/patterns/impl/pattern-service.d.ts.map +1 -0
- package/dist/patterns/impl/pattern-service.js +382 -0
- package/dist/patterns/impl/pattern-service.js.map +1 -0
- package/dist/patterns/impl/repository-factory.d.ts +44 -0
- package/dist/patterns/impl/repository-factory.d.ts.map +1 -0
- package/dist/patterns/impl/repository-factory.js +140 -0
- package/dist/patterns/impl/repository-factory.js.map +1 -0
- package/dist/patterns/impl/unified-file-repository.d.ts +111 -0
- package/dist/patterns/impl/unified-file-repository.d.ts.map +1 -0
- package/dist/patterns/impl/unified-file-repository.js +677 -0
- package/dist/patterns/impl/unified-file-repository.js.map +1 -0
- package/dist/patterns/index.d.ts +23 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +41 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/patterns/repository.d.ts +241 -0
- package/dist/patterns/repository.d.ts.map +1 -0
- package/dist/patterns/repository.js +23 -0
- package/dist/patterns/repository.js.map +1 -0
- package/dist/patterns/service.d.ts +245 -0
- package/dist/patterns/service.d.ts.map +1 -0
- package/dist/patterns/service.js +25 -0
- package/dist/patterns/service.js.map +1 -0
- package/dist/patterns/types.d.ts +227 -0
- package/dist/patterns/types.d.ts.map +1 -0
- package/dist/patterns/types.js +117 -0
- package/dist/patterns/types.js.map +1 -0
- package/dist/store/pattern-store.d.ts +6 -0
- package/dist/store/pattern-store.d.ts.map +1 -1
- package/dist/store/pattern-store.js +6 -0
- package/dist/store/pattern-store.js.map +1 -1
- package/dist/test-topology/extractors/base-test-extractor.d.ts +89 -0
- package/dist/test-topology/extractors/base-test-extractor.d.ts.map +1 -0
- package/dist/test-topology/extractors/base-test-extractor.js +187 -0
- package/dist/test-topology/extractors/base-test-extractor.js.map +1 -0
- package/dist/test-topology/extractors/csharp-test-extractor.d.ts +23 -0
- package/dist/test-topology/extractors/csharp-test-extractor.d.ts.map +1 -0
- package/dist/test-topology/extractors/csharp-test-extractor.js +367 -0
- package/dist/test-topology/extractors/csharp-test-extractor.js.map +1 -0
- package/dist/test-topology/extractors/index.d.ts +12 -0
- package/dist/test-topology/extractors/index.d.ts.map +1 -0
- package/dist/test-topology/extractors/index.js +12 -0
- package/dist/test-topology/extractors/index.js.map +1 -0
- package/dist/test-topology/extractors/java-test-extractor.d.ts +20 -0
- package/dist/test-topology/extractors/java-test-extractor.d.ts.map +1 -0
- package/dist/test-topology/extractors/java-test-extractor.js +275 -0
- package/dist/test-topology/extractors/java-test-extractor.js.map +1 -0
- package/dist/test-topology/extractors/php-test-extractor.d.ts +24 -0
- package/dist/test-topology/extractors/php-test-extractor.d.ts.map +1 -0
- package/dist/test-topology/extractors/php-test-extractor.js +409 -0
- package/dist/test-topology/extractors/php-test-extractor.js.map +1 -0
- package/dist/test-topology/extractors/python-test-extractor.d.ts +23 -0
- package/dist/test-topology/extractors/python-test-extractor.d.ts.map +1 -0
- package/dist/test-topology/extractors/python-test-extractor.js +342 -0
- package/dist/test-topology/extractors/python-test-extractor.js.map +1 -0
- package/dist/test-topology/extractors/regex/csharp-test-regex.d.ts +51 -0
- package/dist/test-topology/extractors/regex/csharp-test-regex.d.ts.map +1 -0
- package/dist/test-topology/extractors/regex/csharp-test-regex.js +383 -0
- package/dist/test-topology/extractors/regex/csharp-test-regex.js.map +1 -0
- package/dist/test-topology/extractors/regex/index.d.ts +18 -0
- package/dist/test-topology/extractors/regex/index.d.ts.map +1 -0
- package/dist/test-topology/extractors/regex/index.js +43 -0
- package/dist/test-topology/extractors/regex/index.js.map +1 -0
- package/dist/test-topology/extractors/regex/java-test-regex.d.ts +50 -0
- package/dist/test-topology/extractors/regex/java-test-regex.d.ts.map +1 -0
- package/dist/test-topology/extractors/regex/java-test-regex.js +370 -0
- package/dist/test-topology/extractors/regex/java-test-regex.js.map +1 -0
- package/dist/test-topology/extractors/regex/php-test-regex.d.ts +56 -0
- package/dist/test-topology/extractors/regex/php-test-regex.d.ts.map +1 -0
- package/dist/test-topology/extractors/regex/php-test-regex.js +503 -0
- package/dist/test-topology/extractors/regex/php-test-regex.js.map +1 -0
- package/dist/test-topology/extractors/regex/python-test-regex.d.ts +57 -0
- package/dist/test-topology/extractors/regex/python-test-regex.d.ts.map +1 -0
- package/dist/test-topology/extractors/regex/python-test-regex.js +381 -0
- package/dist/test-topology/extractors/regex/python-test-regex.js.map +1 -0
- package/dist/test-topology/extractors/regex/typescript-test-regex.d.ts +60 -0
- package/dist/test-topology/extractors/regex/typescript-test-regex.d.ts.map +1 -0
- package/dist/test-topology/extractors/regex/typescript-test-regex.js +368 -0
- package/dist/test-topology/extractors/regex/typescript-test-regex.js.map +1 -0
- package/dist/test-topology/extractors/typescript-test-extractor.d.ts +24 -0
- package/dist/test-topology/extractors/typescript-test-extractor.d.ts.map +1 -0
- package/dist/test-topology/extractors/typescript-test-extractor.js +266 -0
- package/dist/test-topology/extractors/typescript-test-extractor.js.map +1 -0
- package/dist/test-topology/hybrid-test-topology-analyzer.d.ts +98 -0
- package/dist/test-topology/hybrid-test-topology-analyzer.d.ts.map +1 -0
- package/dist/test-topology/hybrid-test-topology-analyzer.js +555 -0
- package/dist/test-topology/hybrid-test-topology-analyzer.js.map +1 -0
- package/dist/test-topology/index.d.ts +16 -0
- package/dist/test-topology/index.d.ts.map +1 -0
- package/dist/test-topology/index.js +19 -0
- package/dist/test-topology/index.js.map +1 -0
- package/dist/test-topology/test-topology-analyzer.d.ts +85 -0
- package/dist/test-topology/test-topology-analyzer.d.ts.map +1 -0
- package/dist/test-topology/test-topology-analyzer.js +538 -0
- package/dist/test-topology/test-topology-analyzer.js.map +1 -0
- package/dist/test-topology/types.d.ts +300 -0
- package/dist/test-topology/types.d.ts.map +1 -0
- package/dist/test-topology/types.js +7 -0
- package/dist/test-topology/types.js.map +1 -0
- package/dist/wrappers/clustering/clusterer.d.ts +43 -0
- package/dist/wrappers/clustering/clusterer.d.ts.map +1 -0
- package/dist/wrappers/clustering/clusterer.js +374 -0
- package/dist/wrappers/clustering/clusterer.js.map +1 -0
- package/dist/wrappers/clustering/exclusions.d.ts +47 -0
- package/dist/wrappers/clustering/exclusions.d.ts.map +1 -0
- package/dist/wrappers/clustering/exclusions.js +318 -0
- package/dist/wrappers/clustering/exclusions.js.map +1 -0
- package/dist/wrappers/clustering/index.d.ts +6 -0
- package/dist/wrappers/clustering/index.d.ts.map +1 -0
- package/dist/wrappers/clustering/index.js +6 -0
- package/dist/wrappers/clustering/index.js.map +1 -0
- package/dist/wrappers/detection/detector.d.ts +69 -0
- package/dist/wrappers/detection/detector.d.ts.map +1 -0
- package/dist/wrappers/detection/detector.js +279 -0
- package/dist/wrappers/detection/detector.js.map +1 -0
- package/dist/wrappers/detection/index.d.ts +5 -0
- package/dist/wrappers/detection/index.d.ts.map +1 -0
- package/dist/wrappers/detection/index.js +5 -0
- package/dist/wrappers/detection/index.js.map +1 -0
- package/dist/wrappers/export/index.d.ts +5 -0
- package/dist/wrappers/export/index.d.ts.map +1 -0
- package/dist/wrappers/export/index.js +5 -0
- package/dist/wrappers/export/index.js.map +1 -0
- package/dist/wrappers/export/json.d.ts +127 -0
- package/dist/wrappers/export/json.d.ts.map +1 -0
- package/dist/wrappers/export/json.js +160 -0
- package/dist/wrappers/export/json.js.map +1 -0
- package/dist/wrappers/index.d.ts +56 -0
- package/dist/wrappers/index.d.ts.map +1 -0
- package/dist/wrappers/index.js +159 -0
- package/dist/wrappers/index.js.map +1 -0
- package/dist/wrappers/integration/adapter.d.ts +52 -0
- package/dist/wrappers/integration/adapter.d.ts.map +1 -0
- package/dist/wrappers/integration/adapter.js +209 -0
- package/dist/wrappers/integration/adapter.js.map +1 -0
- package/dist/wrappers/integration/index.d.ts +9 -0
- package/dist/wrappers/integration/index.d.ts.map +1 -0
- package/dist/wrappers/integration/index.js +12 -0
- package/dist/wrappers/integration/index.js.map +1 -0
- package/dist/wrappers/integration/pattern-adapter.d.ts +52 -0
- package/dist/wrappers/integration/pattern-adapter.d.ts.map +1 -0
- package/dist/wrappers/integration/pattern-adapter.js +192 -0
- package/dist/wrappers/integration/pattern-adapter.js.map +1 -0
- package/dist/wrappers/integration/scanner.d.ts +85 -0
- package/dist/wrappers/integration/scanner.d.ts.map +1 -0
- package/dist/wrappers/integration/scanner.js +367 -0
- package/dist/wrappers/integration/scanner.js.map +1 -0
- package/dist/wrappers/primitives/discovery.d.ts +57 -0
- package/dist/wrappers/primitives/discovery.d.ts.map +1 -0
- package/dist/wrappers/primitives/discovery.js +389 -0
- package/dist/wrappers/primitives/discovery.js.map +1 -0
- package/dist/wrappers/primitives/index.d.ts +6 -0
- package/dist/wrappers/primitives/index.d.ts.map +1 -0
- package/dist/wrappers/primitives/index.js +6 -0
- package/dist/wrappers/primitives/index.js.map +1 -0
- package/dist/wrappers/primitives/registry.d.ts +63 -0
- package/dist/wrappers/primitives/registry.d.ts.map +1 -0
- package/dist/wrappers/primitives/registry.js +447 -0
- package/dist/wrappers/primitives/registry.js.map +1 -0
- package/dist/wrappers/types.d.ts +137 -0
- package/dist/wrappers/types.d.ts.map +1 -0
- package/dist/wrappers/types.js +7 -0
- package/dist/wrappers/types.js.map +1 -0
- package/package.json +5 -1
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified File Pattern Repository
|
|
3
|
+
*
|
|
4
|
+
* Phase 3 implementation that consolidates the two storage systems:
|
|
5
|
+
* - PatternStore (status-based directories)
|
|
6
|
+
* - PatternShardStore (category-based shards)
|
|
7
|
+
*
|
|
8
|
+
* New unified storage structure:
|
|
9
|
+
* .drift/patterns/
|
|
10
|
+
* ├── api.json # All API patterns (with status field)
|
|
11
|
+
* ├── auth.json # All auth patterns
|
|
12
|
+
* ├── security.json # All security patterns
|
|
13
|
+
* └── ...
|
|
14
|
+
*
|
|
15
|
+
* Benefits:
|
|
16
|
+
* - Single source of truth (no sync issues)
|
|
17
|
+
* - Category-based sharding (load only what you need)
|
|
18
|
+
* - Status tracked per-pattern (not per-directory)
|
|
19
|
+
* - Backward compatible migration from old format
|
|
20
|
+
*
|
|
21
|
+
* @module patterns/impl/unified-file-repository
|
|
22
|
+
*/
|
|
23
|
+
import { EventEmitter } from 'node:events';
|
|
24
|
+
import * as fs from 'node:fs/promises';
|
|
25
|
+
import * as path from 'node:path';
|
|
26
|
+
import * as crypto from 'node:crypto';
|
|
27
|
+
import { PATTERN_CATEGORIES, VALID_STATUS_TRANSITIONS, computeConfidenceLevel, toPatternSummary, } from '../types.js';
|
|
28
|
+
import { PatternNotFoundError, InvalidStatusTransitionError, PatternAlreadyExistsError, } from '../errors.js';
|
|
29
|
+
import { DEFAULT_REPOSITORY_CONFIG } from '../repository.js';
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Constants
|
|
32
|
+
// ============================================================================
|
|
33
|
+
const DRIFT_DIR = '.drift';
|
|
34
|
+
const PATTERNS_DIR = 'patterns';
|
|
35
|
+
const UNIFIED_FILE_VERSION = '2.0.0';
|
|
36
|
+
// Legacy directories for migration
|
|
37
|
+
const LEGACY_STATUS_DIRS = {
|
|
38
|
+
discovered: 'discovered',
|
|
39
|
+
approved: 'approved',
|
|
40
|
+
ignored: 'ignored',
|
|
41
|
+
};
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// Helper Functions
|
|
44
|
+
// ============================================================================
|
|
45
|
+
async function fileExists(filePath) {
|
|
46
|
+
try {
|
|
47
|
+
await fs.access(filePath);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function ensureDir(dirPath) {
|
|
55
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
function generateChecksum(patterns) {
|
|
58
|
+
const content = JSON.stringify(patterns.map(p => p.id).sort());
|
|
59
|
+
return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
|
|
60
|
+
}
|
|
61
|
+
function patternToEntry(pattern) {
|
|
62
|
+
const entry = {
|
|
63
|
+
id: pattern.id,
|
|
64
|
+
subcategory: pattern.subcategory,
|
|
65
|
+
name: pattern.name,
|
|
66
|
+
description: pattern.description,
|
|
67
|
+
status: pattern.status,
|
|
68
|
+
detectorId: pattern.detectorId,
|
|
69
|
+
detectorName: pattern.detectorName,
|
|
70
|
+
detectionMethod: pattern.detectionMethod,
|
|
71
|
+
detector: pattern.detector,
|
|
72
|
+
confidence: pattern.confidence,
|
|
73
|
+
confidenceLevel: pattern.confidenceLevel,
|
|
74
|
+
locations: pattern.locations,
|
|
75
|
+
outliers: pattern.outliers,
|
|
76
|
+
severity: pattern.severity,
|
|
77
|
+
firstSeen: pattern.firstSeen,
|
|
78
|
+
lastSeen: pattern.lastSeen,
|
|
79
|
+
tags: pattern.tags,
|
|
80
|
+
autoFixable: pattern.autoFixable,
|
|
81
|
+
metadata: pattern.metadata,
|
|
82
|
+
};
|
|
83
|
+
// Only include optional fields if they have values
|
|
84
|
+
if (pattern.approvedAt !== undefined) {
|
|
85
|
+
entry.approvedAt = pattern.approvedAt;
|
|
86
|
+
}
|
|
87
|
+
if (pattern.approvedBy !== undefined) {
|
|
88
|
+
entry.approvedBy = pattern.approvedBy;
|
|
89
|
+
}
|
|
90
|
+
return entry;
|
|
91
|
+
}
|
|
92
|
+
function entryToPattern(entry, category) {
|
|
93
|
+
return {
|
|
94
|
+
...entry,
|
|
95
|
+
category,
|
|
96
|
+
detectionMethod: entry.detectionMethod,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function computeStatusCounts(patterns) {
|
|
100
|
+
const counts = {
|
|
101
|
+
discovered: 0,
|
|
102
|
+
approved: 0,
|
|
103
|
+
ignored: 0,
|
|
104
|
+
};
|
|
105
|
+
for (const p of patterns) {
|
|
106
|
+
counts[p.status]++;
|
|
107
|
+
}
|
|
108
|
+
return counts;
|
|
109
|
+
}
|
|
110
|
+
const DEFAULT_UNIFIED_CONFIG = {
|
|
111
|
+
...DEFAULT_REPOSITORY_CONFIG,
|
|
112
|
+
autoMigrate: true,
|
|
113
|
+
keepLegacyFiles: true,
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Unified file-based pattern repository.
|
|
117
|
+
*
|
|
118
|
+
* Stores patterns in .drift/patterns/{category}.json files.
|
|
119
|
+
* Each file contains all patterns for that category with their status.
|
|
120
|
+
*/
|
|
121
|
+
export class UnifiedFilePatternRepository extends EventEmitter {
|
|
122
|
+
config;
|
|
123
|
+
patternsDir;
|
|
124
|
+
patterns = new Map();
|
|
125
|
+
initialized = false;
|
|
126
|
+
dirty = false;
|
|
127
|
+
dirtyCategories = new Set();
|
|
128
|
+
saveTimeout = null;
|
|
129
|
+
constructor(config = {}) {
|
|
130
|
+
super();
|
|
131
|
+
this.config = { ...DEFAULT_UNIFIED_CONFIG, ...config };
|
|
132
|
+
this.patternsDir = path.join(this.config.rootDir, DRIFT_DIR, PATTERNS_DIR);
|
|
133
|
+
}
|
|
134
|
+
// ==========================================================================
|
|
135
|
+
// Lifecycle
|
|
136
|
+
// ==========================================================================
|
|
137
|
+
async initialize() {
|
|
138
|
+
if (this.initialized)
|
|
139
|
+
return;
|
|
140
|
+
await ensureDir(this.patternsDir);
|
|
141
|
+
// Check if we need to migrate from legacy format
|
|
142
|
+
if (this.config.autoMigrate && (await this.hasLegacyFormat())) {
|
|
143
|
+
await this.migrateFromLegacy();
|
|
144
|
+
}
|
|
145
|
+
// Load all patterns
|
|
146
|
+
await this.loadAll();
|
|
147
|
+
this.initialized = true;
|
|
148
|
+
this.emit('patterns:loaded', undefined, { count: this.patterns.size });
|
|
149
|
+
}
|
|
150
|
+
async close() {
|
|
151
|
+
if (this.saveTimeout) {
|
|
152
|
+
clearTimeout(this.saveTimeout);
|
|
153
|
+
this.saveTimeout = null;
|
|
154
|
+
}
|
|
155
|
+
if (this.dirty) {
|
|
156
|
+
await this.saveAll();
|
|
157
|
+
}
|
|
158
|
+
this.patterns.clear();
|
|
159
|
+
this.initialized = false;
|
|
160
|
+
}
|
|
161
|
+
// ==========================================================================
|
|
162
|
+
// Migration from Legacy Format
|
|
163
|
+
// ==========================================================================
|
|
164
|
+
/**
|
|
165
|
+
* Check if legacy format exists (status-based directories)
|
|
166
|
+
*/
|
|
167
|
+
async hasLegacyFormat() {
|
|
168
|
+
for (const status of Object.values(LEGACY_STATUS_DIRS)) {
|
|
169
|
+
const statusDir = path.join(this.patternsDir, status);
|
|
170
|
+
if (await fileExists(statusDir)) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Check if unified format exists (public for migration tools)
|
|
178
|
+
*/
|
|
179
|
+
async hasUnifiedFormat() {
|
|
180
|
+
for (const category of PATTERN_CATEGORIES) {
|
|
181
|
+
const filePath = path.join(this.patternsDir, `${category}.json`);
|
|
182
|
+
if (await fileExists(filePath)) {
|
|
183
|
+
try {
|
|
184
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
185
|
+
const data = JSON.parse(content);
|
|
186
|
+
if (data.version?.startsWith('2.')) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Not valid unified format
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Migrate from legacy status-based format to unified category-based format
|
|
199
|
+
*/
|
|
200
|
+
async migrateFromLegacy() {
|
|
201
|
+
console.log('[UnifiedFilePatternRepository] Migrating from legacy format...');
|
|
202
|
+
const migratedPatterns = [];
|
|
203
|
+
const migratedCategories = new Set();
|
|
204
|
+
// Load patterns from all legacy status directories
|
|
205
|
+
for (const status of Object.keys(LEGACY_STATUS_DIRS)) {
|
|
206
|
+
const statusDir = path.join(this.patternsDir, LEGACY_STATUS_DIRS[status]);
|
|
207
|
+
if (!(await fileExists(statusDir)))
|
|
208
|
+
continue;
|
|
209
|
+
for (const category of PATTERN_CATEGORIES) {
|
|
210
|
+
const filePath = path.join(statusDir, `${category}.json`);
|
|
211
|
+
if (!(await fileExists(filePath)))
|
|
212
|
+
continue;
|
|
213
|
+
try {
|
|
214
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
215
|
+
const data = JSON.parse(content);
|
|
216
|
+
// Handle legacy format (patterns without status field)
|
|
217
|
+
for (const stored of data.patterns || []) {
|
|
218
|
+
const pattern = {
|
|
219
|
+
...stored,
|
|
220
|
+
category,
|
|
221
|
+
status,
|
|
222
|
+
detectionMethod: stored.detectionMethod || 'ast',
|
|
223
|
+
confidenceLevel: stored.confidenceLevel || computeConfidenceLevel(stored.confidence || 0.5),
|
|
224
|
+
};
|
|
225
|
+
migratedPatterns.push(pattern);
|
|
226
|
+
migratedCategories.add(category);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
console.warn(`[UnifiedFilePatternRepository] Failed to read legacy file ${filePath}:`, error);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Store migrated patterns in memory
|
|
235
|
+
for (const pattern of migratedPatterns) {
|
|
236
|
+
this.patterns.set(pattern.id, pattern);
|
|
237
|
+
}
|
|
238
|
+
// Save in new unified format
|
|
239
|
+
if (migratedPatterns.length > 0) {
|
|
240
|
+
this.dirty = true;
|
|
241
|
+
for (const category of migratedCategories) {
|
|
242
|
+
this.dirtyCategories.add(category);
|
|
243
|
+
}
|
|
244
|
+
await this.saveAll();
|
|
245
|
+
}
|
|
246
|
+
// Optionally remove legacy directories
|
|
247
|
+
if (!this.config.keepLegacyFiles) {
|
|
248
|
+
await this.removeLegacyFiles();
|
|
249
|
+
}
|
|
250
|
+
console.log(`[UnifiedFilePatternRepository] Migrated ${migratedPatterns.length} patterns from ${migratedCategories.size} categories`);
|
|
251
|
+
return {
|
|
252
|
+
migrated: migratedPatterns.length,
|
|
253
|
+
categories: Array.from(migratedCategories),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Remove legacy status-based directories
|
|
258
|
+
*/
|
|
259
|
+
async removeLegacyFiles() {
|
|
260
|
+
for (const status of Object.values(LEGACY_STATUS_DIRS)) {
|
|
261
|
+
const statusDir = path.join(this.patternsDir, status);
|
|
262
|
+
if (await fileExists(statusDir)) {
|
|
263
|
+
await fs.rm(statusDir, { recursive: true, force: true });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// ==========================================================================
|
|
268
|
+
// Loading
|
|
269
|
+
// ==========================================================================
|
|
270
|
+
async loadAll() {
|
|
271
|
+
this.patterns.clear();
|
|
272
|
+
for (const category of PATTERN_CATEGORIES) {
|
|
273
|
+
await this.loadCategory(category);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async loadCategory(category) {
|
|
277
|
+
const filePath = path.join(this.patternsDir, `${category}.json`);
|
|
278
|
+
if (!(await fileExists(filePath))) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
283
|
+
const data = JSON.parse(content);
|
|
284
|
+
for (const entry of data.patterns) {
|
|
285
|
+
const pattern = entryToPattern(entry, category);
|
|
286
|
+
this.patterns.set(pattern.id, pattern);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
if (error.code !== 'ENOENT') {
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// ==========================================================================
|
|
296
|
+
// Saving
|
|
297
|
+
// ==========================================================================
|
|
298
|
+
async saveAll() {
|
|
299
|
+
const grouped = this.groupPatternsByCategory();
|
|
300
|
+
for (const [category, patterns] of grouped.entries()) {
|
|
301
|
+
await this.saveCategoryFile(category, patterns);
|
|
302
|
+
}
|
|
303
|
+
this.dirty = false;
|
|
304
|
+
this.dirtyCategories.clear();
|
|
305
|
+
this.emit('patterns:saved', undefined, { count: this.patterns.size });
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Save only dirty categories (incremental save)
|
|
309
|
+
*/
|
|
310
|
+
async saveIncremental() {
|
|
311
|
+
if (this.dirtyCategories.size === 0)
|
|
312
|
+
return;
|
|
313
|
+
const grouped = this.groupPatternsByCategory();
|
|
314
|
+
for (const category of this.dirtyCategories) {
|
|
315
|
+
const patterns = grouped.get(category) || [];
|
|
316
|
+
await this.saveCategoryFile(category, patterns);
|
|
317
|
+
}
|
|
318
|
+
this.dirty = false;
|
|
319
|
+
this.dirtyCategories.clear();
|
|
320
|
+
}
|
|
321
|
+
groupPatternsByCategory() {
|
|
322
|
+
const grouped = new Map();
|
|
323
|
+
for (const category of PATTERN_CATEGORIES) {
|
|
324
|
+
grouped.set(category, []);
|
|
325
|
+
}
|
|
326
|
+
for (const pattern of this.patterns.values()) {
|
|
327
|
+
grouped.get(pattern.category).push(pattern);
|
|
328
|
+
}
|
|
329
|
+
return grouped;
|
|
330
|
+
}
|
|
331
|
+
async saveCategoryFile(category, patterns) {
|
|
332
|
+
const filePath = path.join(this.patternsDir, `${category}.json`);
|
|
333
|
+
if (patterns.length === 0) {
|
|
334
|
+
// Remove empty files
|
|
335
|
+
if (await fileExists(filePath)) {
|
|
336
|
+
await fs.unlink(filePath);
|
|
337
|
+
}
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const entries = patterns.map(patternToEntry);
|
|
341
|
+
const patternFile = {
|
|
342
|
+
version: UNIFIED_FILE_VERSION,
|
|
343
|
+
category,
|
|
344
|
+
patterns: entries,
|
|
345
|
+
lastUpdated: new Date().toISOString(),
|
|
346
|
+
checksum: generateChecksum(entries),
|
|
347
|
+
patternCount: entries.length,
|
|
348
|
+
statusCounts: computeStatusCounts(entries),
|
|
349
|
+
};
|
|
350
|
+
await fs.writeFile(filePath, JSON.stringify(patternFile, null, 2), 'utf-8');
|
|
351
|
+
}
|
|
352
|
+
scheduleSave() {
|
|
353
|
+
if (!this.config.autoSave)
|
|
354
|
+
return;
|
|
355
|
+
if (this.saveTimeout) {
|
|
356
|
+
clearTimeout(this.saveTimeout);
|
|
357
|
+
}
|
|
358
|
+
this.saveTimeout = setTimeout(() => {
|
|
359
|
+
this.saveIncremental().catch(console.error);
|
|
360
|
+
}, this.config.autoSaveDelayMs);
|
|
361
|
+
}
|
|
362
|
+
// ==========================================================================
|
|
363
|
+
// CRUD Operations
|
|
364
|
+
// ==========================================================================
|
|
365
|
+
async add(pattern) {
|
|
366
|
+
this.ensureInitialized();
|
|
367
|
+
if (this.patterns.has(pattern.id)) {
|
|
368
|
+
throw new PatternAlreadyExistsError(pattern.id);
|
|
369
|
+
}
|
|
370
|
+
const patternWithLevel = {
|
|
371
|
+
...pattern,
|
|
372
|
+
confidenceLevel: computeConfidenceLevel(pattern.confidence),
|
|
373
|
+
};
|
|
374
|
+
this.patterns.set(pattern.id, patternWithLevel);
|
|
375
|
+
this.markDirty(pattern.category);
|
|
376
|
+
this.emit('pattern:added', patternWithLevel);
|
|
377
|
+
}
|
|
378
|
+
async addMany(patterns) {
|
|
379
|
+
for (const pattern of patterns) {
|
|
380
|
+
await this.add(pattern);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async get(id) {
|
|
384
|
+
this.ensureInitialized();
|
|
385
|
+
return this.patterns.get(id) ?? null;
|
|
386
|
+
}
|
|
387
|
+
async update(id, updates) {
|
|
388
|
+
this.ensureInitialized();
|
|
389
|
+
const existing = this.patterns.get(id);
|
|
390
|
+
if (!existing) {
|
|
391
|
+
throw new PatternNotFoundError(id);
|
|
392
|
+
}
|
|
393
|
+
const updated = {
|
|
394
|
+
...existing,
|
|
395
|
+
...updates,
|
|
396
|
+
id: existing.id,
|
|
397
|
+
lastSeen: new Date().toISOString(),
|
|
398
|
+
};
|
|
399
|
+
if (updates.confidence !== undefined) {
|
|
400
|
+
updated.confidenceLevel = computeConfidenceLevel(updates.confidence);
|
|
401
|
+
}
|
|
402
|
+
this.patterns.set(id, updated);
|
|
403
|
+
this.markDirty(updated.category);
|
|
404
|
+
// If category changed, mark old category dirty too
|
|
405
|
+
if (updates.category && updates.category !== existing.category) {
|
|
406
|
+
this.markDirty(existing.category);
|
|
407
|
+
}
|
|
408
|
+
this.emit('pattern:updated', updated);
|
|
409
|
+
return updated;
|
|
410
|
+
}
|
|
411
|
+
async delete(id) {
|
|
412
|
+
this.ensureInitialized();
|
|
413
|
+
const pattern = this.patterns.get(id);
|
|
414
|
+
if (!pattern) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
this.patterns.delete(id);
|
|
418
|
+
this.markDirty(pattern.category);
|
|
419
|
+
this.emit('pattern:deleted', pattern);
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
markDirty(category) {
|
|
423
|
+
this.dirty = true;
|
|
424
|
+
this.dirtyCategories.add(category);
|
|
425
|
+
this.scheduleSave();
|
|
426
|
+
}
|
|
427
|
+
// ==========================================================================
|
|
428
|
+
// Querying
|
|
429
|
+
// ==========================================================================
|
|
430
|
+
async query(options) {
|
|
431
|
+
this.ensureInitialized();
|
|
432
|
+
let patterns = Array.from(this.patterns.values());
|
|
433
|
+
if (options.filter) {
|
|
434
|
+
patterns = this.applyFilter(patterns, options.filter);
|
|
435
|
+
}
|
|
436
|
+
const total = patterns.length;
|
|
437
|
+
if (options.sort) {
|
|
438
|
+
patterns = this.applySort(patterns, options.sort);
|
|
439
|
+
}
|
|
440
|
+
if (options.pagination) {
|
|
441
|
+
const { offset, limit } = options.pagination;
|
|
442
|
+
patterns = patterns.slice(offset, offset + limit);
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
patterns,
|
|
446
|
+
total,
|
|
447
|
+
hasMore: options.pagination
|
|
448
|
+
? options.pagination.offset + patterns.length < total
|
|
449
|
+
: false,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
applyFilter(patterns, filter) {
|
|
453
|
+
return patterns.filter((p) => {
|
|
454
|
+
if (filter.ids && !filter.ids.includes(p.id))
|
|
455
|
+
return false;
|
|
456
|
+
if (filter.categories && !filter.categories.includes(p.category))
|
|
457
|
+
return false;
|
|
458
|
+
if (filter.statuses && !filter.statuses.includes(p.status))
|
|
459
|
+
return false;
|
|
460
|
+
if (filter.minConfidence !== undefined && p.confidence < filter.minConfidence)
|
|
461
|
+
return false;
|
|
462
|
+
if (filter.maxConfidence !== undefined && p.confidence > filter.maxConfidence)
|
|
463
|
+
return false;
|
|
464
|
+
if (filter.confidenceLevels && !filter.confidenceLevels.includes(p.confidenceLevel))
|
|
465
|
+
return false;
|
|
466
|
+
if (filter.severities && !filter.severities.includes(p.severity))
|
|
467
|
+
return false;
|
|
468
|
+
if (filter.files) {
|
|
469
|
+
const hasFile = p.locations.some((loc) => filter.files.includes(loc.file));
|
|
470
|
+
if (!hasFile)
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
if (filter.hasOutliers !== undefined) {
|
|
474
|
+
const hasOutliers = p.outliers.length > 0;
|
|
475
|
+
if (filter.hasOutliers !== hasOutliers)
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
if (filter.tags) {
|
|
479
|
+
const hasTags = filter.tags.some((tag) => p.tags.includes(tag));
|
|
480
|
+
if (!hasTags)
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
if (filter.search) {
|
|
484
|
+
const searchLower = filter.search.toLowerCase();
|
|
485
|
+
const matches = p.name.toLowerCase().includes(searchLower) ||
|
|
486
|
+
p.description.toLowerCase().includes(searchLower);
|
|
487
|
+
if (!matches)
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
if (filter.createdAfter) {
|
|
491
|
+
const firstSeen = new Date(p.firstSeen);
|
|
492
|
+
if (firstSeen < filter.createdAfter)
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
if (filter.createdBefore) {
|
|
496
|
+
const firstSeen = new Date(p.firstSeen);
|
|
497
|
+
if (firstSeen > filter.createdBefore)
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
return true;
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
applySort(patterns, sort) {
|
|
504
|
+
const sorted = [...patterns];
|
|
505
|
+
const direction = sort.direction === 'asc' ? 1 : -1;
|
|
506
|
+
sorted.sort((a, b) => {
|
|
507
|
+
let comparison = 0;
|
|
508
|
+
switch (sort.field) {
|
|
509
|
+
case 'name':
|
|
510
|
+
comparison = a.name.localeCompare(b.name);
|
|
511
|
+
break;
|
|
512
|
+
case 'confidence':
|
|
513
|
+
comparison = a.confidence - b.confidence;
|
|
514
|
+
break;
|
|
515
|
+
case 'severity':
|
|
516
|
+
const severityOrder = { error: 4, warning: 3, info: 2, hint: 1 };
|
|
517
|
+
comparison = severityOrder[a.severity] - severityOrder[b.severity];
|
|
518
|
+
break;
|
|
519
|
+
case 'firstSeen':
|
|
520
|
+
comparison = new Date(a.firstSeen).getTime() - new Date(b.firstSeen).getTime();
|
|
521
|
+
break;
|
|
522
|
+
case 'lastSeen':
|
|
523
|
+
comparison = new Date(a.lastSeen).getTime() - new Date(b.lastSeen).getTime();
|
|
524
|
+
break;
|
|
525
|
+
case 'locationCount':
|
|
526
|
+
comparison = a.locations.length - b.locations.length;
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
return comparison * direction;
|
|
530
|
+
});
|
|
531
|
+
return sorted;
|
|
532
|
+
}
|
|
533
|
+
async getByCategory(category) {
|
|
534
|
+
const result = await this.query({ filter: { categories: [category] } });
|
|
535
|
+
return result.patterns;
|
|
536
|
+
}
|
|
537
|
+
async getByStatus(status) {
|
|
538
|
+
const result = await this.query({ filter: { statuses: [status] } });
|
|
539
|
+
return result.patterns;
|
|
540
|
+
}
|
|
541
|
+
async getByFile(file) {
|
|
542
|
+
const result = await this.query({ filter: { files: [file] } });
|
|
543
|
+
return result.patterns;
|
|
544
|
+
}
|
|
545
|
+
async getAll() {
|
|
546
|
+
this.ensureInitialized();
|
|
547
|
+
return Array.from(this.patterns.values());
|
|
548
|
+
}
|
|
549
|
+
async count(filter) {
|
|
550
|
+
if (!filter) {
|
|
551
|
+
return this.patterns.size;
|
|
552
|
+
}
|
|
553
|
+
const result = await this.query({ filter });
|
|
554
|
+
return result.total;
|
|
555
|
+
}
|
|
556
|
+
// ==========================================================================
|
|
557
|
+
// Status Transitions
|
|
558
|
+
// ==========================================================================
|
|
559
|
+
async approve(id, approvedBy) {
|
|
560
|
+
this.ensureInitialized();
|
|
561
|
+
const pattern = this.patterns.get(id);
|
|
562
|
+
if (!pattern) {
|
|
563
|
+
throw new PatternNotFoundError(id);
|
|
564
|
+
}
|
|
565
|
+
if (!VALID_STATUS_TRANSITIONS[pattern.status].includes('approved')) {
|
|
566
|
+
throw new InvalidStatusTransitionError(id, pattern.status, 'approved');
|
|
567
|
+
}
|
|
568
|
+
const now = new Date().toISOString();
|
|
569
|
+
const updated = await this.update(id, {
|
|
570
|
+
status: 'approved',
|
|
571
|
+
approvedAt: now,
|
|
572
|
+
approvedBy,
|
|
573
|
+
metadata: {
|
|
574
|
+
...pattern.metadata,
|
|
575
|
+
approvedAt: now,
|
|
576
|
+
approvedBy,
|
|
577
|
+
},
|
|
578
|
+
});
|
|
579
|
+
this.emit('pattern:approved', updated);
|
|
580
|
+
return updated;
|
|
581
|
+
}
|
|
582
|
+
async ignore(id) {
|
|
583
|
+
this.ensureInitialized();
|
|
584
|
+
const pattern = this.patterns.get(id);
|
|
585
|
+
if (!pattern) {
|
|
586
|
+
throw new PatternNotFoundError(id);
|
|
587
|
+
}
|
|
588
|
+
if (!VALID_STATUS_TRANSITIONS[pattern.status].includes('ignored')) {
|
|
589
|
+
throw new InvalidStatusTransitionError(id, pattern.status, 'ignored');
|
|
590
|
+
}
|
|
591
|
+
const updated = await this.update(id, { status: 'ignored' });
|
|
592
|
+
this.emit('pattern:ignored', updated);
|
|
593
|
+
return updated;
|
|
594
|
+
}
|
|
595
|
+
// ==========================================================================
|
|
596
|
+
// Batch Operations
|
|
597
|
+
// ==========================================================================
|
|
598
|
+
async clear() {
|
|
599
|
+
this.ensureInitialized();
|
|
600
|
+
this.patterns.clear();
|
|
601
|
+
this.dirty = true;
|
|
602
|
+
for (const category of PATTERN_CATEGORIES) {
|
|
603
|
+
this.dirtyCategories.add(category);
|
|
604
|
+
}
|
|
605
|
+
await this.saveAll();
|
|
606
|
+
}
|
|
607
|
+
// ==========================================================================
|
|
608
|
+
// Events
|
|
609
|
+
// ==========================================================================
|
|
610
|
+
on(event, handler) {
|
|
611
|
+
return super.on(event, handler);
|
|
612
|
+
}
|
|
613
|
+
off(event, handler) {
|
|
614
|
+
return super.off(event, handler);
|
|
615
|
+
}
|
|
616
|
+
// ==========================================================================
|
|
617
|
+
// Utilities
|
|
618
|
+
// ==========================================================================
|
|
619
|
+
async exists(id) {
|
|
620
|
+
this.ensureInitialized();
|
|
621
|
+
return this.patterns.has(id);
|
|
622
|
+
}
|
|
623
|
+
async getSummaries(options) {
|
|
624
|
+
const result = await this.query(options ?? {});
|
|
625
|
+
return result.patterns.map(toPatternSummary);
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Get storage statistics
|
|
629
|
+
*/
|
|
630
|
+
async getStorageStats() {
|
|
631
|
+
this.ensureInitialized();
|
|
632
|
+
const byCategory = {};
|
|
633
|
+
const byStatus = {
|
|
634
|
+
discovered: 0,
|
|
635
|
+
approved: 0,
|
|
636
|
+
ignored: 0,
|
|
637
|
+
};
|
|
638
|
+
for (const category of PATTERN_CATEGORIES) {
|
|
639
|
+
byCategory[category] = 0;
|
|
640
|
+
}
|
|
641
|
+
for (const pattern of this.patterns.values()) {
|
|
642
|
+
const catCount = byCategory[pattern.category];
|
|
643
|
+
if (catCount !== undefined) {
|
|
644
|
+
byCategory[pattern.category] = catCount + 1;
|
|
645
|
+
}
|
|
646
|
+
const statusCount = byStatus[pattern.status];
|
|
647
|
+
if (statusCount !== undefined) {
|
|
648
|
+
byStatus[pattern.status] = statusCount + 1;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
// Count non-empty category files
|
|
652
|
+
let fileCount = 0;
|
|
653
|
+
for (const category of PATTERN_CATEGORIES) {
|
|
654
|
+
const count = byCategory[category];
|
|
655
|
+
if (count !== undefined && count > 0)
|
|
656
|
+
fileCount++;
|
|
657
|
+
}
|
|
658
|
+
return {
|
|
659
|
+
totalPatterns: this.patterns.size,
|
|
660
|
+
byCategory: byCategory,
|
|
661
|
+
byStatus,
|
|
662
|
+
fileCount,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
ensureInitialized() {
|
|
666
|
+
if (!this.initialized) {
|
|
667
|
+
throw new Error('Repository not initialized. Call initialize() first.');
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
// ============================================================================
|
|
672
|
+
// Factory Function
|
|
673
|
+
// ============================================================================
|
|
674
|
+
export function createUnifiedFilePatternRepository(config = {}) {
|
|
675
|
+
return new UnifiedFilePatternRepository(config);
|
|
676
|
+
}
|
|
677
|
+
//# sourceMappingURL=unified-file-repository.js.map
|