@eddacraft/anvil-core 0.1.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/LICENSE +14 -0
- package/dist/antipattern/index.d.ts +11 -0
- package/dist/antipattern/index.d.ts.map +1 -0
- package/dist/antipattern/index.js +31 -0
- package/dist/antipattern/patterns-css.d.ts +17 -0
- package/dist/antipattern/patterns-css.d.ts.map +1 -0
- package/dist/antipattern/patterns-css.js +72 -0
- package/dist/antipattern/patterns-html.d.ts +21 -0
- package/dist/antipattern/patterns-html.d.ts.map +1 -0
- package/dist/antipattern/patterns-html.js +139 -0
- package/dist/antipattern/patterns.d.ts +72 -0
- package/dist/antipattern/patterns.d.ts.map +1 -0
- package/dist/antipattern/patterns.js +301 -0
- package/dist/antipattern/scanner.d.ts +32 -0
- package/dist/antipattern/scanner.d.ts.map +1 -0
- package/dist/antipattern/scanner.js +89 -0
- package/dist/antipattern/types.d.ts +318 -0
- package/dist/antipattern/types.d.ts.map +1 -0
- package/dist/antipattern/types.js +278 -0
- package/dist/architecture/analyzer.d.ts +123 -0
- package/dist/architecture/analyzer.d.ts.map +1 -0
- package/dist/architecture/analyzer.js +321 -0
- package/dist/architecture/baseline.d.ts +112 -0
- package/dist/architecture/baseline.d.ts.map +1 -0
- package/dist/architecture/baseline.js +245 -0
- package/dist/architecture/compiler.d.ts +24 -0
- package/dist/architecture/compiler.d.ts.map +1 -0
- package/dist/architecture/compiler.js +57 -0
- package/dist/architecture/context.d.ts +129 -0
- package/dist/architecture/context.d.ts.map +1 -0
- package/dist/architecture/context.js +116 -0
- package/dist/architecture/dc-generator.d.ts +9 -0
- package/dist/architecture/dc-generator.d.ts.map +1 -0
- package/dist/architecture/dc-generator.js +220 -0
- package/dist/architecture/definition-schema.d.ts +128 -0
- package/dist/architecture/definition-schema.d.ts.map +1 -0
- package/dist/architecture/definition-schema.js +94 -0
- package/dist/architecture/edge-detector-html.d.ts +6 -0
- package/dist/architecture/edge-detector-html.d.ts.map +1 -0
- package/dist/architecture/edge-detector-html.js +5 -0
- package/dist/architecture/edge-detector-web.d.ts +32 -0
- package/dist/architecture/edge-detector-web.d.ts.map +1 -0
- package/dist/architecture/edge-detector-web.js +133 -0
- package/dist/architecture/edge-detector.d.ts +116 -0
- package/dist/architecture/edge-detector.d.ts.map +1 -0
- package/dist/architecture/edge-detector.js +229 -0
- package/dist/architecture/entry-detector.d.ts +44 -0
- package/dist/architecture/entry-detector.d.ts.map +1 -0
- package/dist/architecture/entry-detector.js +263 -0
- package/dist/architecture/index.d.ts +21 -0
- package/dist/architecture/index.d.ts.map +1 -0
- package/dist/architecture/index.js +48 -0
- package/dist/architecture/layer-detector.d.ts +60 -0
- package/dist/architecture/layer-detector.d.ts.map +1 -0
- package/dist/architecture/layer-detector.js +331 -0
- package/dist/architecture/rego-generator.d.ts +25 -0
- package/dist/architecture/rego-generator.d.ts.map +1 -0
- package/dist/architecture/rego-generator.js +229 -0
- package/dist/architecture/templates/index.d.ts +39 -0
- package/dist/architecture/templates/index.d.ts.map +1 -0
- package/dist/architecture/templates/index.js +124 -0
- package/dist/architecture/types.d.ts +280 -0
- package/dist/architecture/types.d.ts.map +1 -0
- package/dist/architecture/types.js +269 -0
- package/dist/architecture/yaml-parser.d.ts +13 -0
- package/dist/architecture/yaml-parser.d.ts.map +1 -0
- package/dist/architecture/yaml-parser.js +234 -0
- package/dist/config/constants.d.ts +9 -0
- package/dist/config/constants.d.ts.map +1 -0
- package/dist/config/constants.js +20 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +8 -0
- package/dist/config/loader.d.ts +41 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +76 -0
- package/dist/config/nudge-config.d.ts +35 -0
- package/dist/config/nudge-config.d.ts.map +1 -0
- package/dist/config/nudge-config.js +34 -0
- package/dist/config/types.d.ts +30 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +4 -0
- package/dist/contracts/index.d.ts +14 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +13 -0
- package/dist/contracts/schemas/aps.schema.d.ts +269 -0
- package/dist/contracts/schemas/aps.schema.d.ts.map +1 -0
- package/dist/contracts/schemas/aps.schema.js +183 -0
- package/dist/contracts/schemas/index.d.ts +12 -0
- package/dist/contracts/schemas/index.d.ts.map +1 -0
- package/dist/contracts/schemas/index.js +14 -0
- package/dist/contracts/schemas/json-schema.d.ts +14 -0
- package/dist/contracts/schemas/json-schema.d.ts.map +1 -0
- package/dist/contracts/schemas/json-schema.js +31 -0
- package/dist/contracts/schemas/warning.schema.d.ts +171 -0
- package/dist/contracts/schemas/warning.schema.d.ts.map +1 -0
- package/dist/contracts/schemas/warning.schema.js +123 -0
- package/dist/contracts/types/gate.types.d.ts +194 -0
- package/dist/contracts/types/gate.types.d.ts.map +1 -0
- package/dist/contracts/types/gate.types.js +19 -0
- package/dist/contracts/types/index.d.ts +9 -0
- package/dist/contracts/types/index.d.ts.map +1 -0
- package/dist/contracts/types/index.js +8 -0
- package/dist/crypto/hash.d.ts +47 -0
- package/dist/crypto/hash.d.ts.map +1 -0
- package/dist/crypto/hash.js +110 -0
- package/dist/crypto/index.d.ts +7 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/crypto/index.js +6 -0
- package/dist/drift/index.d.ts +6 -0
- package/dist/drift/index.d.ts.map +1 -0
- package/dist/drift/index.js +5 -0
- package/dist/drift/report-generator.d.ts +21 -0
- package/dist/drift/report-generator.d.ts.map +1 -0
- package/dist/drift/report-generator.js +240 -0
- package/dist/drift/snapshot-capture.d.ts +26 -0
- package/dist/drift/snapshot-capture.d.ts.map +1 -0
- package/dist/drift/snapshot-capture.js +195 -0
- package/dist/drift/snapshot-compare.d.ts +50 -0
- package/dist/drift/snapshot-compare.d.ts.map +1 -0
- package/dist/drift/snapshot-compare.js +142 -0
- package/dist/drift/snapshot-schema.d.ts +197 -0
- package/dist/drift/snapshot-schema.d.ts.map +1 -0
- package/dist/drift/snapshot-schema.js +193 -0
- package/dist/drift/snapshot-storage.d.ts +25 -0
- package/dist/drift/snapshot-storage.d.ts.map +1 -0
- package/dist/drift/snapshot-storage.js +179 -0
- package/dist/explain/antipattern-explainer.d.ts +4 -0
- package/dist/explain/antipattern-explainer.d.ts.map +1 -0
- package/dist/explain/antipattern-explainer.js +196 -0
- package/dist/explain/boundary-explainer.d.ts +5 -0
- package/dist/explain/boundary-explainer.d.ts.map +1 -0
- package/dist/explain/boundary-explainer.js +261 -0
- package/dist/explain/explain-service.d.ts +19 -0
- package/dist/explain/explain-service.d.ts.map +1 -0
- package/dist/explain/explain-service.js +106 -0
- package/dist/explain/index.d.ts +7 -0
- package/dist/explain/index.d.ts.map +1 -0
- package/dist/explain/index.js +5 -0
- package/dist/explain/template-loader.d.ts +9 -0
- package/dist/explain/template-loader.d.ts.map +1 -0
- package/dist/explain/template-loader.js +51 -0
- package/dist/explain/types.d.ts +46 -0
- package/dist/explain/types.d.ts.map +1 -0
- package/dist/explain/types.js +31 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/provenance/collector.d.ts +86 -0
- package/dist/provenance/collector.d.ts.map +1 -0
- package/dist/provenance/collector.js +425 -0
- package/dist/provenance/git-ai-standard/git-notes.d.ts +85 -0
- package/dist/provenance/git-ai-standard/git-notes.d.ts.map +1 -0
- package/dist/provenance/git-ai-standard/git-notes.js +292 -0
- package/dist/provenance/git-ai-standard/index.d.ts +44 -0
- package/dist/provenance/git-ai-standard/index.d.ts.map +1 -0
- package/dist/provenance/git-ai-standard/index.js +47 -0
- package/dist/provenance/git-ai-standard/serializer.d.ts +54 -0
- package/dist/provenance/git-ai-standard/serializer.d.ts.map +1 -0
- package/dist/provenance/git-ai-standard/serializer.js +224 -0
- package/dist/provenance/git-ai-standard/session.d.ts +51 -0
- package/dist/provenance/git-ai-standard/session.d.ts.map +1 -0
- package/dist/provenance/git-ai-standard/session.js +118 -0
- package/dist/provenance/git-ai-standard/types.d.ts +173 -0
- package/dist/provenance/git-ai-standard/types.d.ts.map +1 -0
- package/dist/provenance/git-ai-standard/types.js +109 -0
- package/dist/provenance/index.d.ts +5 -0
- package/dist/provenance/index.d.ts.map +1 -0
- package/dist/provenance/index.js +6 -0
- package/dist/provenance/store.d.ts +83 -0
- package/dist/provenance/store.d.ts.map +1 -0
- package/dist/provenance/store.js +248 -0
- package/dist/provenance/types.d.ts +160 -0
- package/dist/provenance/types.d.ts.map +1 -0
- package/dist/provenance/types.js +112 -0
- package/dist/suppression/index.d.ts +4 -0
- package/dist/suppression/index.d.ts.map +1 -0
- package/dist/suppression/index.js +3 -0
- package/dist/suppression/parser.d.ts +31 -0
- package/dist/suppression/parser.d.ts.map +1 -0
- package/dist/suppression/parser.js +219 -0
- package/dist/suppression/service.d.ts +29 -0
- package/dist/suppression/service.d.ts.map +1 -0
- package/dist/suppression/service.js +132 -0
- package/dist/suppression/store.d.ts +61 -0
- package/dist/suppression/store.d.ts.map +1 -0
- package/dist/suppression/store.js +169 -0
- package/dist/utils/debug.d.ts +48 -0
- package/dist/utils/debug.d.ts.map +1 -0
- package/dist/utils/debug.js +100 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/path-safety.d.ts +21 -0
- package/dist/utils/path-safety.d.ts.map +1 -0
- package/dist/utils/path-safety.js +49 -0
- package/dist/utils/severity.d.ts +37 -0
- package/dist/utils/severity.d.ts.map +1 -0
- package/dist/utils/severity.js +22 -0
- package/dist/validation/aps-validator.d.ts +66 -0
- package/dist/validation/aps-validator.d.ts.map +1 -0
- package/dist/validation/aps-validator.js +173 -0
- package/dist/validation/errors.d.ts +52 -0
- package/dist/validation/errors.d.ts.map +1 -0
- package/dist/validation/errors.js +115 -0
- package/dist/validation/index.d.ts +8 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +13 -0
- package/dist/warnings/index.d.ts +2 -0
- package/dist/warnings/index.d.ts.map +1 -0
- package/dist/warnings/index.js +1 -0
- package/dist/warnings/warning-id.d.ts +180 -0
- package/dist/warnings/warning-id.d.ts.map +1 -0
- package/dist/warnings/warning-id.js +257 -0
- package/package.json +79 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hash Generation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides deterministic hashing functions for APS plans
|
|
5
|
+
* ensuring data integrity and immutability.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Generates a SHA-256 hash of the provided data
|
|
9
|
+
* @param data - The data to hash (will be canonicalized if object)
|
|
10
|
+
* @returns Hexadecimal string representation of the hash
|
|
11
|
+
*/
|
|
12
|
+
export declare function generateHash(data: unknown): string;
|
|
13
|
+
/**
|
|
14
|
+
* Converts an object to a canonical JSON string with sorted keys
|
|
15
|
+
* This ensures consistent hashing regardless of property order
|
|
16
|
+
*
|
|
17
|
+
* @param obj - The object to serialize
|
|
18
|
+
* @returns Canonical JSON string representation
|
|
19
|
+
* @throws {TypeError} If obj is `undefined` (not a valid JSON value)
|
|
20
|
+
*/
|
|
21
|
+
export declare function canonicalizeJSON(obj: unknown): string;
|
|
22
|
+
/**
|
|
23
|
+
* Verifies that the provided hash matches the hash of the data
|
|
24
|
+
* Uses constant-time comparison to prevent timing attacks
|
|
25
|
+
* @param data - The data to verify
|
|
26
|
+
* @param expectedHash - The expected hash value
|
|
27
|
+
* @returns True if hashes match, false otherwise
|
|
28
|
+
*/
|
|
29
|
+
export declare function verifyHash(data: unknown, expectedHash: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Generates a unique plan ID in the format 'aps-[16 hex chars]'
|
|
32
|
+
* @returns A unique plan identifier
|
|
33
|
+
*/
|
|
34
|
+
export declare function generatePlanId(): string;
|
|
35
|
+
/**
|
|
36
|
+
* Validates that a string is a valid plan ID format
|
|
37
|
+
* @param id - The ID to validate
|
|
38
|
+
* @returns True if valid, false otherwise
|
|
39
|
+
*/
|
|
40
|
+
export declare function isValidPlanId(id: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Validates that a string is a valid SHA-256 hash
|
|
43
|
+
* @param hash - The hash to validate
|
|
44
|
+
* @returns True if valid, false otherwise
|
|
45
|
+
*/
|
|
46
|
+
export declare function isValidHash(hash: string): boolean;
|
|
47
|
+
//# sourceMappingURL=hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/crypto/hash.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAQlD;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAgDrD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAUvE;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAKvC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAGjD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hash Generation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides deterministic hashing functions for APS plans
|
|
5
|
+
* ensuring data integrity and immutability.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
8
|
+
/**
|
|
9
|
+
* Generates a SHA-256 hash of the provided data
|
|
10
|
+
* @param data - The data to hash (will be canonicalized if object)
|
|
11
|
+
* @returns Hexadecimal string representation of the hash
|
|
12
|
+
*/
|
|
13
|
+
export function generateHash(data) {
|
|
14
|
+
// Convert data to canonical JSON string if it's an object
|
|
15
|
+
const content = typeof data === 'object' && data !== null ? canonicalizeJSON(data) : String(data);
|
|
16
|
+
// Create SHA-256 hash
|
|
17
|
+
const hash = createHash('sha256');
|
|
18
|
+
hash.update(content, 'utf8');
|
|
19
|
+
return hash.digest('hex');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Converts an object to a canonical JSON string with sorted keys
|
|
23
|
+
* This ensures consistent hashing regardless of property order
|
|
24
|
+
*
|
|
25
|
+
* @param obj - The object to serialize
|
|
26
|
+
* @returns Canonical JSON string representation
|
|
27
|
+
* @throws {TypeError} If obj is `undefined` (not a valid JSON value)
|
|
28
|
+
*/
|
|
29
|
+
export function canonicalizeJSON(obj) {
|
|
30
|
+
if (obj === null) {
|
|
31
|
+
return 'null';
|
|
32
|
+
}
|
|
33
|
+
if (obj === undefined) {
|
|
34
|
+
throw new TypeError('canonicalizeJSON does not accept undefined — it is not a valid JSON value. Use null instead.');
|
|
35
|
+
}
|
|
36
|
+
if (typeof obj === 'string') {
|
|
37
|
+
return JSON.stringify(obj);
|
|
38
|
+
}
|
|
39
|
+
if (typeof obj === 'number' || typeof obj === 'boolean') {
|
|
40
|
+
return String(obj);
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(obj)) {
|
|
43
|
+
// Recursively canonicalize array elements
|
|
44
|
+
const elements = obj.map((element) => canonicalizeJSON(element));
|
|
45
|
+
return `[${elements.join(',')}]`;
|
|
46
|
+
}
|
|
47
|
+
if (obj instanceof Date) {
|
|
48
|
+
return JSON.stringify(obj.toISOString());
|
|
49
|
+
}
|
|
50
|
+
if (typeof obj === 'object') {
|
|
51
|
+
// Sort object keys and recursively canonicalize values
|
|
52
|
+
const sortedKeys = Object.keys(obj).sort();
|
|
53
|
+
const pairs = sortedKeys
|
|
54
|
+
.map((key) => {
|
|
55
|
+
const value = obj[key];
|
|
56
|
+
// Skip undefined values (like JSON.stringify does)
|
|
57
|
+
if (value === undefined) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return `${JSON.stringify(key)}:${canonicalizeJSON(value)}`;
|
|
61
|
+
})
|
|
62
|
+
.filter((pair) => pair !== null);
|
|
63
|
+
return `{${pairs.join(',')}}`;
|
|
64
|
+
}
|
|
65
|
+
// Fallback for other types
|
|
66
|
+
return JSON.stringify(obj);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Verifies that the provided hash matches the hash of the data
|
|
70
|
+
* Uses constant-time comparison to prevent timing attacks
|
|
71
|
+
* @param data - The data to verify
|
|
72
|
+
* @param expectedHash - The expected hash value
|
|
73
|
+
* @returns True if hashes match, false otherwise
|
|
74
|
+
*/
|
|
75
|
+
export function verifyHash(data, expectedHash) {
|
|
76
|
+
const actualHash = generateHash(data);
|
|
77
|
+
// Use constant-time comparison to prevent timing attacks
|
|
78
|
+
// Both hashes must be the same length for timingSafeEqual
|
|
79
|
+
if (actualHash.length !== expectedHash.length) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return timingSafeEqual(Buffer.from(actualHash, 'utf8'), Buffer.from(expectedHash, 'utf8'));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Generates a unique plan ID in the format 'aps-[16 hex chars]'
|
|
86
|
+
* @returns A unique plan identifier
|
|
87
|
+
*/
|
|
88
|
+
export function generatePlanId() {
|
|
89
|
+
// Generate 8 random bytes (16 hex characters) for sufficient entropy
|
|
90
|
+
const buffer = randomBytes(8);
|
|
91
|
+
const hexString = buffer.toString('hex');
|
|
92
|
+
return `aps-${hexString}`;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Validates that a string is a valid plan ID format
|
|
96
|
+
* @param id - The ID to validate
|
|
97
|
+
* @returns True if valid, false otherwise
|
|
98
|
+
*/
|
|
99
|
+
export function isValidPlanId(id) {
|
|
100
|
+
// Accept both legacy 8-char and new 16-char IDs
|
|
101
|
+
return /^aps-[a-f0-9]{8}$/.test(id) || /^aps-[a-f0-9]{16}$/.test(id);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Validates that a string is a valid SHA-256 hash
|
|
105
|
+
* @param hash - The hash to validate
|
|
106
|
+
* @returns True if valid, false otherwise
|
|
107
|
+
*/
|
|
108
|
+
export function isValidHash(hash) {
|
|
109
|
+
return /^[a-f0-9]{64}$/.test(hash);
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/crypto/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,aAAa,EACb,WAAW,GACZ,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { SNAPSHOT_SCHEMA_VERSION, SnapshotViolationSchema, type SnapshotViolation, SnapshotAntiPatternSchema, type SnapshotAntiPattern, SnapshotSuppressionSchema, type SnapshotSuppression, SnapshotMetricsSchema, type SnapshotMetrics, AntiPatternBreakdownSchema, type AntiPatternBreakdown, HotspotSchema, type Hotspot, DriftSnapshotSchema, type DriftSnapshot, SnapshotMetadataSchema, type SnapshotMetadata, generateSnapshotFilename, generateNamedSnapshotFilename, parseSnapshotFilename, createEmptySnapshot, validateSnapshot, } from './snapshot-schema.js';
|
|
2
|
+
export { SNAPSHOTS_DIR, ensureSnapshotsDir, saveSnapshot, loadSnapshot, listSnapshots, deleteSnapshot, snapshotExists, getLatestSnapshot, resolveSnapshotName, SnapshotStore, } from './snapshot-storage.js';
|
|
3
|
+
export { type CaptureOptions, type CaptureContext, captureSnapshot, SnapshotCaptureService, createSnapshotCaptureService, } from './snapshot-capture.js';
|
|
4
|
+
export { type ItemChange, type MetricChange, type MetricsComparison, type AntiPatternChange, type SnapshotComparison, compareSnapshots, formatComparisonSummary, } from './snapshot-compare.js';
|
|
5
|
+
export { type ReportOptions, type DriftReport, type ReportSection, generateReport, formatReportAsText, formatReportAsJson, } from './report-generator.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/drift/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,qBAAqB,EACrB,KAAK,eAAe,EACpB,0BAA0B,EAC1B,KAAK,oBAAoB,EACzB,aAAa,EACb,KAAK,OAAO,EACZ,mBAAmB,EACnB,KAAK,aAAa,EAClB,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,wBAAwB,EACxB,6BAA6B,EAC7B,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,aAAa,GACd,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,eAAe,EACf,sBAAsB,EACtB,4BAA4B,GAC7B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,cAAc,EACd,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { SNAPSHOT_SCHEMA_VERSION, SnapshotViolationSchema, SnapshotAntiPatternSchema, SnapshotSuppressionSchema, SnapshotMetricsSchema, AntiPatternBreakdownSchema, HotspotSchema, DriftSnapshotSchema, SnapshotMetadataSchema, generateSnapshotFilename, generateNamedSnapshotFilename, parseSnapshotFilename, createEmptySnapshot, validateSnapshot, } from './snapshot-schema.js';
|
|
2
|
+
export { SNAPSHOTS_DIR, ensureSnapshotsDir, saveSnapshot, loadSnapshot, listSnapshots, deleteSnapshot, snapshotExists, getLatestSnapshot, resolveSnapshotName, SnapshotStore, } from './snapshot-storage.js';
|
|
3
|
+
export { captureSnapshot, SnapshotCaptureService, createSnapshotCaptureService, } from './snapshot-capture.js';
|
|
4
|
+
export { compareSnapshots, formatComparisonSummary, } from './snapshot-compare.js';
|
|
5
|
+
export { generateReport, formatReportAsText, formatReportAsJson, } from './report-generator.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SnapshotComparison } from './snapshot-compare.js';
|
|
2
|
+
export interface ReportOptions {
|
|
3
|
+
format?: 'text' | 'json';
|
|
4
|
+
includeDetails?: boolean;
|
|
5
|
+
maxEdges?: number;
|
|
6
|
+
maxHotspots?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface DriftReport {
|
|
9
|
+
summary: string;
|
|
10
|
+
sections: ReportSection[];
|
|
11
|
+
recommendation: string;
|
|
12
|
+
json?: unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface ReportSection {
|
|
15
|
+
title: string;
|
|
16
|
+
content: string[];
|
|
17
|
+
}
|
|
18
|
+
export declare function generateReport(comparison: SnapshotComparison, options?: ReportOptions): DriftReport;
|
|
19
|
+
export declare function formatReportAsText(report: DriftReport): string;
|
|
20
|
+
export declare function formatReportAsJson(report: DriftReport): string;
|
|
21
|
+
//# sourceMappingURL=report-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-generator.d.ts","sourceRoot":"","sources":["../../src/drift/report-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAKhE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAqND,wBAAgB,cAAc,CAC5B,UAAU,EAAE,kBAAkB,EAC9B,OAAO,GAAE,aAAkB,GAC1B,WAAW,CAiCb;AAiDD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAE9D;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAE9D"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { createDebugger } from '../utils/debug.js';
|
|
2
|
+
const debug = createDebugger('drift');
|
|
3
|
+
const SEPARATOR = '\u2500'.repeat(56);
|
|
4
|
+
function formatDelta(delta) {
|
|
5
|
+
if (delta > 0)
|
|
6
|
+
return `+${delta}`;
|
|
7
|
+
if (delta < 0)
|
|
8
|
+
return `${delta}`;
|
|
9
|
+
return '0';
|
|
10
|
+
}
|
|
11
|
+
function formatTrend(trend) {
|
|
12
|
+
switch (trend) {
|
|
13
|
+
case 'increasing':
|
|
14
|
+
return '\u26A0\uFE0F';
|
|
15
|
+
case 'decreasing':
|
|
16
|
+
return '\u2705';
|
|
17
|
+
default:
|
|
18
|
+
return '\u2796';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function formatOverallTrend(trend) {
|
|
22
|
+
switch (trend) {
|
|
23
|
+
case 'improving':
|
|
24
|
+
return 'IMPROVING \u2705';
|
|
25
|
+
case 'degrading':
|
|
26
|
+
return 'INCREASING \u26A0\uFE0F';
|
|
27
|
+
default:
|
|
28
|
+
return 'STABLE \u2796';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function formatDate(isoDate) {
|
|
32
|
+
return isoDate.split('T')[0];
|
|
33
|
+
}
|
|
34
|
+
function generateHeader(comparison) {
|
|
35
|
+
const beforeName = comparison.before.name ?? formatDate(comparison.before.created_at);
|
|
36
|
+
const afterName = comparison.after.name ?? formatDate(comparison.after.created_at);
|
|
37
|
+
return [
|
|
38
|
+
` Drift Report: ${beforeName} \u2192 ${afterName} (${comparison.duration_days} days)`,
|
|
39
|
+
'',
|
|
40
|
+
SEPARATOR,
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
function generateBoundarySection(comparison, options) {
|
|
44
|
+
const content = [];
|
|
45
|
+
const metrics = comparison.metrics.boundary_violations;
|
|
46
|
+
content.push(` New violations: ${formatDelta(comparison.violations.added.length)} (was ${metrics.before}, now ${metrics.after})`);
|
|
47
|
+
content.push(` Resolved: ${formatDelta(-comparison.violations.removed.length)}`);
|
|
48
|
+
content.push(` Net change: ${formatDelta(comparison.net_change.violations)} ${formatTrend(metrics.trend)}`);
|
|
49
|
+
if (options.includeDetails && comparison.violations.added.length > 0) {
|
|
50
|
+
content.push('');
|
|
51
|
+
content.push(' New edges detected:');
|
|
52
|
+
const maxEdges = options.maxEdges ?? 5;
|
|
53
|
+
const edgesToShow = comparison.violations.added.slice(0, maxEdges);
|
|
54
|
+
for (const v of edgesToShow) {
|
|
55
|
+
const rule = v.rule ?? `${v.from_layer}\u2192${v.to_layer}`;
|
|
56
|
+
content.push(` \u2022 ${v.from_file} \u2192 ${v.to_file} (${rule})`);
|
|
57
|
+
}
|
|
58
|
+
if (comparison.violations.added.length > maxEdges) {
|
|
59
|
+
content.push(` ... and ${comparison.violations.added.length - maxEdges} more`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { title: 'ARCHITECTURE BOUNDARIES', content };
|
|
63
|
+
}
|
|
64
|
+
function generateAntiPatternSection(comparison, options) {
|
|
65
|
+
const content = [];
|
|
66
|
+
const metrics = comparison.metrics.antipattern_count;
|
|
67
|
+
content.push(` New introductions: ${formatDelta(comparison.antipatterns.added.length)}`);
|
|
68
|
+
content.push(` Resolved: ${formatDelta(-comparison.antipatterns.removed.length)}`);
|
|
69
|
+
content.push(` Net change: ${formatDelta(comparison.net_change.antipatterns)} ${formatTrend(metrics.trend)}`);
|
|
70
|
+
if (comparison.antipattern_changes.length > 0) {
|
|
71
|
+
content.push('');
|
|
72
|
+
content.push(' By type:');
|
|
73
|
+
for (const change of comparison.antipattern_changes.slice(0, 5)) {
|
|
74
|
+
content.push(` \u2022 ${change.id}: ${formatDelta(change.delta)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (options.includeDetails) {
|
|
78
|
+
const hotspots = findAntiPatternHotspots(comparison);
|
|
79
|
+
if (hotspots.length > 0) {
|
|
80
|
+
content.push('');
|
|
81
|
+
content.push(' Hotspots:');
|
|
82
|
+
const maxHotspots = options.maxHotspots ?? 3;
|
|
83
|
+
for (const hs of hotspots.slice(0, maxHotspots)) {
|
|
84
|
+
content.push(` \u2022 ${hs.path} \u2014 ${hs.count} new violations`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { title: 'ANTI-PATTERNS', content };
|
|
89
|
+
}
|
|
90
|
+
function findAntiPatternHotspots(comparison) {
|
|
91
|
+
const dirCounts = new Map();
|
|
92
|
+
for (const ap of comparison.antipatterns.added) {
|
|
93
|
+
const parts = ap.file.split('/');
|
|
94
|
+
const dir = parts.slice(0, -1).join('/') || '.';
|
|
95
|
+
dirCounts.set(dir, (dirCounts.get(dir) ?? 0) + 1);
|
|
96
|
+
}
|
|
97
|
+
return Array.from(dirCounts.entries())
|
|
98
|
+
.filter(([_, count]) => count > 1)
|
|
99
|
+
.sort((a, b) => b[1] - a[1])
|
|
100
|
+
.map(([path, count]) => ({ path, count }));
|
|
101
|
+
}
|
|
102
|
+
function generateSuppressionSection(comparison) {
|
|
103
|
+
const content = [];
|
|
104
|
+
const expiredMetrics = comparison.metrics.expired_suppressions;
|
|
105
|
+
content.push(` New suppressions: ${formatDelta(comparison.suppressions.added.length)}`);
|
|
106
|
+
content.push(` Expired: ${formatDelta(expiredMetrics.delta)}`);
|
|
107
|
+
content.push(` Net change: ${formatDelta(comparison.net_change.suppressions)}`);
|
|
108
|
+
const oldestUnexpired = findOldestUnexpiredSuppression(comparison);
|
|
109
|
+
if (oldestUnexpired) {
|
|
110
|
+
content.push('');
|
|
111
|
+
content.push(` Oldest unexpired: ${oldestUnexpired.age} days (${oldestUnexpired.file})`);
|
|
112
|
+
}
|
|
113
|
+
return { title: 'SUPPRESSIONS', content };
|
|
114
|
+
}
|
|
115
|
+
function findOldestUnexpiredSuppression(comparison) {
|
|
116
|
+
const activeSuppressions = comparison.suppressions.unchanged.filter((s) => !s.is_expired);
|
|
117
|
+
if (activeSuppressions.length === 0)
|
|
118
|
+
return null;
|
|
119
|
+
return {
|
|
120
|
+
age: comparison.duration_days,
|
|
121
|
+
file: activeSuppressions[0].file,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function generateSummarySection(comparison) {
|
|
125
|
+
const content = [];
|
|
126
|
+
content.push(` Overall drift: ${formatOverallTrend(comparison.overall_trend)}`);
|
|
127
|
+
return { title: 'SUMMARY', content };
|
|
128
|
+
}
|
|
129
|
+
function generateRecommendation(comparison) {
|
|
130
|
+
if (comparison.overall_trend === 'degrading') {
|
|
131
|
+
if (comparison.violations.added.length > 0) {
|
|
132
|
+
const topViolationDir = getMostAffectedDirectory(comparison.violations.added);
|
|
133
|
+
return `Review new violations in ${topViolationDir}`;
|
|
134
|
+
}
|
|
135
|
+
if (comparison.antipatterns.added.length > 0) {
|
|
136
|
+
const topPattern = comparison.antipattern_changes[0]?.id ?? 'anti-patterns';
|
|
137
|
+
return `Address increasing ${topPattern} occurrences`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (comparison.overall_trend === 'improving') {
|
|
141
|
+
return 'Good progress! Continue addressing remaining issues.';
|
|
142
|
+
}
|
|
143
|
+
return 'Architecture is stable. Monitor for new changes.';
|
|
144
|
+
}
|
|
145
|
+
function getMostAffectedDirectory(violations) {
|
|
146
|
+
const dirCounts = new Map();
|
|
147
|
+
for (const v of violations) {
|
|
148
|
+
const parts = v.from_file.split('/');
|
|
149
|
+
const dir = parts.slice(0, 2).join('/') || '.';
|
|
150
|
+
dirCounts.set(dir, (dirCounts.get(dir) ?? 0) + 1);
|
|
151
|
+
}
|
|
152
|
+
let maxDir = '.';
|
|
153
|
+
let maxCount = 0;
|
|
154
|
+
for (const [dir, count] of dirCounts) {
|
|
155
|
+
if (count > maxCount) {
|
|
156
|
+
maxCount = count;
|
|
157
|
+
maxDir = dir;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return maxDir + '/';
|
|
161
|
+
}
|
|
162
|
+
export function generateReport(comparison, options = {}) {
|
|
163
|
+
debug('generating drift report', {
|
|
164
|
+
format: options.format,
|
|
165
|
+
includeDetails: options.includeDetails,
|
|
166
|
+
trend: comparison.overall_trend,
|
|
167
|
+
});
|
|
168
|
+
const { includeDetails = true } = options;
|
|
169
|
+
const effectiveOptions = { ...options, includeDetails };
|
|
170
|
+
const sections = [
|
|
171
|
+
generateBoundarySection(comparison, effectiveOptions),
|
|
172
|
+
generateAntiPatternSection(comparison, effectiveOptions),
|
|
173
|
+
generateSuppressionSection(comparison),
|
|
174
|
+
generateSummarySection(comparison),
|
|
175
|
+
];
|
|
176
|
+
const recommendation = generateRecommendation(comparison);
|
|
177
|
+
const header = generateHeader(comparison);
|
|
178
|
+
const sectionTexts = sections.map((s) => {
|
|
179
|
+
return [` ${s.title}`, '', ...s.content, '', SEPARATOR].join('\n');
|
|
180
|
+
});
|
|
181
|
+
const summary = [...header, '', ...sectionTexts, ` Recommendation: ${recommendation}`, ''].join('\n');
|
|
182
|
+
return {
|
|
183
|
+
summary,
|
|
184
|
+
sections,
|
|
185
|
+
recommendation,
|
|
186
|
+
json: options.format === 'json' ? generateJsonReport(comparison) : undefined,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function generateJsonReport(comparison) {
|
|
190
|
+
return {
|
|
191
|
+
before: comparison.before,
|
|
192
|
+
after: comparison.after,
|
|
193
|
+
duration_days: comparison.duration_days,
|
|
194
|
+
metrics: {
|
|
195
|
+
boundary_violations: {
|
|
196
|
+
before: comparison.metrics.boundary_violations.before,
|
|
197
|
+
after: comparison.metrics.boundary_violations.after,
|
|
198
|
+
delta: comparison.metrics.boundary_violations.delta,
|
|
199
|
+
},
|
|
200
|
+
antipattern_count: {
|
|
201
|
+
before: comparison.metrics.antipattern_count.before,
|
|
202
|
+
after: comparison.metrics.antipattern_count.after,
|
|
203
|
+
delta: comparison.metrics.antipattern_count.delta,
|
|
204
|
+
},
|
|
205
|
+
suppression_count: {
|
|
206
|
+
before: comparison.metrics.suppression_count.before,
|
|
207
|
+
after: comparison.metrics.suppression_count.after,
|
|
208
|
+
delta: comparison.metrics.suppression_count.delta,
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
net_change: comparison.net_change,
|
|
212
|
+
changes: {
|
|
213
|
+
violations: {
|
|
214
|
+
added: comparison.violations.added.length,
|
|
215
|
+
removed: comparison.violations.removed.length,
|
|
216
|
+
unchanged: comparison.violations.unchanged.length,
|
|
217
|
+
},
|
|
218
|
+
antipatterns: {
|
|
219
|
+
added: comparison.antipatterns.added.length,
|
|
220
|
+
removed: comparison.antipatterns.removed.length,
|
|
221
|
+
unchanged: comparison.antipatterns.unchanged.length,
|
|
222
|
+
},
|
|
223
|
+
suppressions: {
|
|
224
|
+
added: comparison.suppressions.added.length,
|
|
225
|
+
removed: comparison.suppressions.removed.length,
|
|
226
|
+
unchanged: comparison.suppressions.unchanged.length,
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
antipattern_changes: comparison.antipattern_changes,
|
|
230
|
+
overall_trend: comparison.overall_trend,
|
|
231
|
+
added_violations: comparison.violations.added,
|
|
232
|
+
added_antipatterns: comparison.antipatterns.added,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
export function formatReportAsText(report) {
|
|
236
|
+
return report.summary;
|
|
237
|
+
}
|
|
238
|
+
export function formatReportAsJson(report) {
|
|
239
|
+
return JSON.stringify(report.json ?? {}, null, 2);
|
|
240
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type DriftSnapshot } from './snapshot-schema.js';
|
|
2
|
+
import { type ArchitectureBaseline } from '../architecture/index.js';
|
|
3
|
+
import { type ScanResult } from '../antipattern/index.js';
|
|
4
|
+
import { type FileSuppressions } from '../suppression/index.js';
|
|
5
|
+
export interface CaptureOptions {
|
|
6
|
+
name?: string;
|
|
7
|
+
includeOptInPatterns?: boolean;
|
|
8
|
+
sourcePatterns?: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface CaptureContext {
|
|
11
|
+
workspaceRoot: string;
|
|
12
|
+
files: string[];
|
|
13
|
+
baseline: ArchitectureBaseline | null;
|
|
14
|
+
scanResults: ScanResult[];
|
|
15
|
+
suppressions: FileSuppressions[];
|
|
16
|
+
gitRef?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function captureSnapshot(context: CaptureContext, options?: CaptureOptions): Promise<DriftSnapshot>;
|
|
19
|
+
export declare class SnapshotCaptureService {
|
|
20
|
+
private workspaceRoot;
|
|
21
|
+
constructor(workspaceRoot: string);
|
|
22
|
+
capture(files: string[], options?: CaptureOptions): Promise<DriftSnapshot>;
|
|
23
|
+
captureWithContext(context: Partial<CaptureContext>, options?: CaptureOptions): Promise<DriftSnapshot>;
|
|
24
|
+
}
|
|
25
|
+
export declare function createSnapshotCaptureService(workspaceRoot: string): SnapshotCaptureService;
|
|
26
|
+
//# sourceMappingURL=snapshot-capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-capture.d.ts","sourceRoot":"","sources":["../../src/drift/snapshot-capture.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,aAAa,EAQnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,oBAAoB,EAE1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAsB,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAMpF,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACtC,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA0ID,wBAAsB,eAAe,CACnC,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC,CA0BxB;AAED,qBAAa,sBAAsB;IACjC,OAAO,CAAC,aAAa,CAAS;gBAElB,aAAa,EAAE,MAAM;IAI3B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC;IAwC9E,kBAAkB,CACtB,OAAO,EAAE,OAAO,CAAC,cAAc,CAAC,EAChC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC;CAY1B;AAED,wBAAgB,4BAA4B,CAAC,aAAa,EAAE,MAAM,GAAG,sBAAsB,CAE1F"}
|