@descent-vtt/spec-guard 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 +21 -0
- package/README.md +393 -0
- package/bin/spec-guard.js +24 -0
- package/dist/cli.d.ts +43 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +243 -0
- package/dist/cli.js.map +1 -0
- package/dist/engine.d.ts +80 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +539 -0
- package/dist/engine.js.map +1 -0
- package/dist/glob.d.ts +60 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +238 -0
- package/dist/glob.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +33 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +180 -0
- package/dist/parser.js.map +1 -0
- package/dist/reporter.d.ts +45 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +198 -0
- package/dist/reporter.js.map +1 -0
- package/dist/runner.d.ts +54 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +387 -0
- package/dist/runner.js.map +1 -0
- package/dist/types.d.ts +136 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +72 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command line entrypoint.
|
|
3
|
+
*
|
|
4
|
+
* Exit codes are the contract CI depends on:
|
|
5
|
+
* 0 - every assertion held
|
|
6
|
+
* 1 - an assertion failed, or a directive was invalid
|
|
7
|
+
* 2 - spec-guard could not run (bad usage, no spec files, missing engine)
|
|
8
|
+
*/
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { formatJson, formatReport, shouldUseAscii, shouldUseColor } from './reporter.js';
|
|
12
|
+
import { DEFAULT_CONCURRENCY, DEFAULT_MAX_SNIPPETS, runSpecGuard } from './runner.js';
|
|
13
|
+
export const EXIT_OK = 0;
|
|
14
|
+
export const EXIT_FAILED = 1;
|
|
15
|
+
export const EXIT_ERROR = 2;
|
|
16
|
+
export class UsageError extends Error {
|
|
17
|
+
}
|
|
18
|
+
function version() {
|
|
19
|
+
try {
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
const pkg = require('../package.json');
|
|
22
|
+
return pkg.version ?? '0.0.0';
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
/* c8 ignore next 2 -- only reachable from a broken install */
|
|
26
|
+
return '0.0.0';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export const HELP = `spec-guard - Executable architecture assertions for Markdown specs & ADRs
|
|
30
|
+
|
|
31
|
+
Usage
|
|
32
|
+
spec-guard [patterns...] [options]
|
|
33
|
+
|
|
34
|
+
Patterns
|
|
35
|
+
Globs or paths to the Markdown specs to execute. A directory expands to the
|
|
36
|
+
Markdown files inside it. Defaults to "docs/**/*.md" when omitted.
|
|
37
|
+
|
|
38
|
+
Options
|
|
39
|
+
-r, --root <path> Codebase root that assertions are resolved against (default: cwd)
|
|
40
|
+
-v, --verbose Print passing assertions too
|
|
41
|
+
--fail-fast Stop at the first failing assertion
|
|
42
|
+
--json Emit a machine-readable JSON report
|
|
43
|
+
--engine <name> auto | rg | js (default: auto - ripgrep when available)
|
|
44
|
+
--strict Treat a target path that does not exist as a failure
|
|
45
|
+
--include-specs Also count matches inside the spec files themselves
|
|
46
|
+
--max-snippets <n> Failure snippets per assertion (default: ${DEFAULT_MAX_SNIPPETS})
|
|
47
|
+
--concurrency <n> Assertions executed in parallel (default: ${DEFAULT_CONCURRENCY})
|
|
48
|
+
--allow-empty Exit 0 when no spec files matched
|
|
49
|
+
--color/--no-color Force colour on or off (NO_COLOR is honoured)
|
|
50
|
+
-h, --help Show this help
|
|
51
|
+
--version Print the version
|
|
52
|
+
|
|
53
|
+
Directives
|
|
54
|
+
<!-- @assert-absence target="src/" symbol="LegacyGateway" -->
|
|
55
|
+
<!-- @assert-count target="src/" symbol="SessionManager" expected="1" -->
|
|
56
|
+
<!-- @assert-present file="SECURITY.md" -->
|
|
57
|
+
|
|
58
|
+
Exit codes
|
|
59
|
+
0 all assertions passed 1 an assertion failed 2 spec-guard could not run`;
|
|
60
|
+
const ENGINE_ALIASES = {
|
|
61
|
+
auto: 'auto',
|
|
62
|
+
rg: 'ripgrep',
|
|
63
|
+
ripgrep: 'ripgrep',
|
|
64
|
+
js: 'javascript',
|
|
65
|
+
javascript: 'javascript',
|
|
66
|
+
node: 'javascript',
|
|
67
|
+
};
|
|
68
|
+
function requireValue(name, value) {
|
|
69
|
+
if (value === undefined || value.startsWith('-')) {
|
|
70
|
+
throw new UsageError(`Option ${name} requires a value.`);
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
function positiveInteger(name, value) {
|
|
75
|
+
if (!/^\d+$/.test(value))
|
|
76
|
+
throw new UsageError(`Option ${name} expects a non-negative integer, got "${value}".`);
|
|
77
|
+
return Number.parseInt(value, 10);
|
|
78
|
+
}
|
|
79
|
+
/** Minimal, dependency-free argv parser. Supports `--flag value` and `--flag=value`. */
|
|
80
|
+
export function parseArgs(argv, cwd) {
|
|
81
|
+
const options = {
|
|
82
|
+
patterns: [],
|
|
83
|
+
root: cwd,
|
|
84
|
+
verbose: false,
|
|
85
|
+
failFast: false,
|
|
86
|
+
json: false,
|
|
87
|
+
engine: 'auto',
|
|
88
|
+
strictTargets: false,
|
|
89
|
+
includeSpecs: false,
|
|
90
|
+
allowEmpty: false,
|
|
91
|
+
maxSnippets: DEFAULT_MAX_SNIPPETS,
|
|
92
|
+
concurrency: DEFAULT_CONCURRENCY,
|
|
93
|
+
help: false,
|
|
94
|
+
version: false,
|
|
95
|
+
};
|
|
96
|
+
let onlyPositional = false;
|
|
97
|
+
for (let index = 0; index < argv.length; index++) {
|
|
98
|
+
const argument = argv[index];
|
|
99
|
+
if (onlyPositional || !argument.startsWith('-') || argument === '-') {
|
|
100
|
+
options.patterns.push(argument);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (argument === '--') {
|
|
104
|
+
onlyPositional = true;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const equals = argument.indexOf('=');
|
|
108
|
+
const name = equals === -1 ? argument : argument.slice(0, equals);
|
|
109
|
+
const inlineValue = equals === -1 ? undefined : argument.slice(equals + 1);
|
|
110
|
+
const nextValue = () => {
|
|
111
|
+
if (inlineValue !== undefined)
|
|
112
|
+
return requireValue(name, inlineValue);
|
|
113
|
+
index += 1;
|
|
114
|
+
return requireValue(name, argv[index]);
|
|
115
|
+
};
|
|
116
|
+
switch (name) {
|
|
117
|
+
case '-h':
|
|
118
|
+
case '--help':
|
|
119
|
+
options.help = true;
|
|
120
|
+
break;
|
|
121
|
+
case '--version':
|
|
122
|
+
options.version = true;
|
|
123
|
+
break;
|
|
124
|
+
case '-v':
|
|
125
|
+
case '--verbose':
|
|
126
|
+
options.verbose = true;
|
|
127
|
+
break;
|
|
128
|
+
case '--fail-fast':
|
|
129
|
+
options.failFast = true;
|
|
130
|
+
break;
|
|
131
|
+
case '--json':
|
|
132
|
+
options.json = true;
|
|
133
|
+
break;
|
|
134
|
+
case '--strict':
|
|
135
|
+
options.strictTargets = true;
|
|
136
|
+
break;
|
|
137
|
+
case '--include-specs':
|
|
138
|
+
options.includeSpecs = true;
|
|
139
|
+
break;
|
|
140
|
+
case '--allow-empty':
|
|
141
|
+
options.allowEmpty = true;
|
|
142
|
+
break;
|
|
143
|
+
case '--color':
|
|
144
|
+
options.color = true;
|
|
145
|
+
break;
|
|
146
|
+
case '--no-color':
|
|
147
|
+
options.color = false;
|
|
148
|
+
break;
|
|
149
|
+
case '-r':
|
|
150
|
+
case '--root':
|
|
151
|
+
options.root = path.resolve(cwd, nextValue());
|
|
152
|
+
break;
|
|
153
|
+
case '--engine': {
|
|
154
|
+
const value = nextValue().toLowerCase();
|
|
155
|
+
const engine = ENGINE_ALIASES[value];
|
|
156
|
+
if (!engine) {
|
|
157
|
+
throw new UsageError(`Unknown engine "${value}". Expected auto, rg or js.`);
|
|
158
|
+
}
|
|
159
|
+
options.engine = engine;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case '--max-snippets':
|
|
163
|
+
options.maxSnippets = positiveInteger(name, nextValue());
|
|
164
|
+
break;
|
|
165
|
+
case '--concurrency':
|
|
166
|
+
options.concurrency = Math.max(1, positiveInteger(name, nextValue()));
|
|
167
|
+
break;
|
|
168
|
+
default:
|
|
169
|
+
throw new UsageError(`Unknown option "${name}". Run spec-guard --help.`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (options.patterns.length === 0)
|
|
173
|
+
options.patterns = ['docs/**/*.md'];
|
|
174
|
+
return options;
|
|
175
|
+
}
|
|
176
|
+
function defaultIO() {
|
|
177
|
+
return {
|
|
178
|
+
stdout: (text) => process.stdout.write(`${text}\n`),
|
|
179
|
+
stderr: (text) => process.stderr.write(`${text}\n`),
|
|
180
|
+
env: process.env,
|
|
181
|
+
cwd: process.cwd(),
|
|
182
|
+
isTTY: Boolean(process.stdout.isTTY),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** Runs the CLI and resolves to the process exit code. */
|
|
186
|
+
export async function main(argv = process.argv.slice(2), io = defaultIO()) {
|
|
187
|
+
let options;
|
|
188
|
+
try {
|
|
189
|
+
options = parseArgs(argv, io.cwd);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
io.stderr(error instanceof UsageError ? error.message : String(error));
|
|
193
|
+
io.stderr('');
|
|
194
|
+
io.stderr(HELP);
|
|
195
|
+
return EXIT_ERROR;
|
|
196
|
+
}
|
|
197
|
+
if (options.help) {
|
|
198
|
+
io.stdout(HELP);
|
|
199
|
+
return EXIT_OK;
|
|
200
|
+
}
|
|
201
|
+
if (options.version) {
|
|
202
|
+
io.stdout(version());
|
|
203
|
+
return EXIT_OK;
|
|
204
|
+
}
|
|
205
|
+
let report;
|
|
206
|
+
try {
|
|
207
|
+
report = await runSpecGuard({
|
|
208
|
+
patterns: options.patterns,
|
|
209
|
+
root: options.root,
|
|
210
|
+
engine: options.engine,
|
|
211
|
+
failFast: options.failFast,
|
|
212
|
+
strictTargets: options.strictTargets,
|
|
213
|
+
includeSpecs: options.includeSpecs,
|
|
214
|
+
concurrency: options.concurrency,
|
|
215
|
+
maxSnippets: options.maxSnippets,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
io.stderr(`spec-guard: ${error instanceof Error ? error.message : String(error)}`);
|
|
220
|
+
return EXIT_ERROR;
|
|
221
|
+
}
|
|
222
|
+
if (report.summary.specs === 0) {
|
|
223
|
+
if (options.json) {
|
|
224
|
+
io.stdout(formatJson(report));
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
io.stderr(`spec-guard: no spec files matched ${options.patterns.map((p) => `"${p}"`).join(', ')}`);
|
|
228
|
+
}
|
|
229
|
+
return options.allowEmpty ? EXIT_OK : EXIT_ERROR;
|
|
230
|
+
}
|
|
231
|
+
if (options.json) {
|
|
232
|
+
io.stdout(formatJson(report));
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
io.stdout(formatReport(report, {
|
|
236
|
+
color: shouldUseColor({ isTTY: io.isTTY }, options.color, io.env),
|
|
237
|
+
verbose: options.verbose,
|
|
238
|
+
ascii: shouldUseAscii(io.env),
|
|
239
|
+
}, options.maxSnippets));
|
|
240
|
+
}
|
|
241
|
+
return report.ok ? EXIT_OK : EXIT_FAILED;
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search engines.
|
|
3
|
+
*
|
|
4
|
+
* Primary: native `ripgrep`, consumed as newline-delimited JSON. ripgrep is
|
|
5
|
+
* multi-threaded, gitignore-aware and binary-skipping, which is why spec-guard
|
|
6
|
+
* reaches for it before doing anything clever itself.
|
|
7
|
+
*
|
|
8
|
+
* Fallback: a pure-JS walker with identical assertion semantics, used when `rg`
|
|
9
|
+
* is not on PATH (or when `--engine js` is passed). Nothing about spec-guard's
|
|
10
|
+
* behaviour depends on ripgrep being installed - only its speed does.
|
|
11
|
+
*
|
|
12
|
+
* Both engines expose a batch API. Scanning a tree costs the same whether you
|
|
13
|
+
* look for one symbol or twenty, so assertions that share a target set and
|
|
14
|
+
* flags are answered by a single pass. Measured on a 2,000 file / 5 MB tree,
|
|
15
|
+
* eight assertions cost ~280ms as eight ripgrep passes and ~90ms as one.
|
|
16
|
+
*/
|
|
17
|
+
import type { EngineName, MatchLocation, SearchOptions, SearchResult } from './types.js';
|
|
18
|
+
/** Files larger than this are skipped by both engines, keeping them in sync. */
|
|
19
|
+
export declare const MAX_FILE_SIZE: number;
|
|
20
|
+
export interface SearchRequest {
|
|
21
|
+
/** Absolute root directory. All targets are resolved against it. */
|
|
22
|
+
root: string;
|
|
23
|
+
/** Literal string or regular expression source. */
|
|
24
|
+
symbol: string;
|
|
25
|
+
/** Existing target paths, relative to root, POSIX separators. */
|
|
26
|
+
targets: string[];
|
|
27
|
+
options: SearchOptions;
|
|
28
|
+
}
|
|
29
|
+
export interface Engine {
|
|
30
|
+
readonly name: EngineName;
|
|
31
|
+
search(request: SearchRequest): Promise<SearchResult>;
|
|
32
|
+
/**
|
|
33
|
+
* Answers several requests that share a root, target list and options.
|
|
34
|
+
* Optional: `runSearches` falls back to parallel `search` calls.
|
|
35
|
+
*/
|
|
36
|
+
searchBatch?(requests: SearchRequest[]): Promise<SearchResult[]>;
|
|
37
|
+
}
|
|
38
|
+
export type EnginePreference = 'auto' | 'ripgrep' | 'javascript';
|
|
39
|
+
export declare function escapeRegExp(value: string): string;
|
|
40
|
+
/** Per-pattern accumulator used by both engines. */
|
|
41
|
+
interface Tally {
|
|
42
|
+
count: number;
|
|
43
|
+
locations: MatchLocation[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* True when no two of these literals can ever match overlapping text, which is
|
|
47
|
+
* exactly when merging them into one ripgrep alternation is safe.
|
|
48
|
+
*
|
|
49
|
+
* Two ways an alternation loses a match that separate passes would find:
|
|
50
|
+
* containment - ["Primary", "PrimaryButton"] on "PrimaryButton"; and
|
|
51
|
+
* dovetailing - ["abc", "cd"] on "abcd", where the scan resumes past "cd".
|
|
52
|
+
* Rejecting both leaves batching indistinguishable from separate passes.
|
|
53
|
+
*/
|
|
54
|
+
export declare function canBatchLiterals(patterns: readonly string[]): boolean;
|
|
55
|
+
/** Runs requests through the batch API when the engine has one. */
|
|
56
|
+
export declare function runSearches(engine: Engine, requests: SearchRequest[]): Promise<SearchResult[]>;
|
|
57
|
+
export declare function resetRipgrepProbe(): void;
|
|
58
|
+
/** Locates a usable `rg` binary. `SPEC_GUARD_RG` overrides PATH lookup. */
|
|
59
|
+
export declare function findRipgrep(): Promise<string | null>;
|
|
60
|
+
/** Builds the argv for one ripgrep pass over `patterns`. */
|
|
61
|
+
export declare function buildRipgrepArgs(request: SearchRequest, patterns?: readonly string[]): string[];
|
|
62
|
+
/** Builds the matching RegExp shared by the JS engine. */
|
|
63
|
+
export declare function buildJsRegExp(symbol: string, options: SearchOptions): RegExp;
|
|
64
|
+
/** Counts matches in one file and records per-line snippets. */
|
|
65
|
+
export declare function scanContent(content: string, relativePath: string, regexp: RegExp): Tally;
|
|
66
|
+
/** An engine that is guaranteed to implement the batch API. */
|
|
67
|
+
export type BatchEngine = Engine & Required<Pick<Engine, 'searchBatch'>>;
|
|
68
|
+
export declare const javascriptEngine: BatchEngine;
|
|
69
|
+
/** Resolves the engine to use, honouring an explicit preference. */
|
|
70
|
+
export declare function resolveEngine(preference?: EnginePreference): Promise<Engine>;
|
|
71
|
+
export type CachedEngine = Engine & {
|
|
72
|
+
fallbacks: string[];
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Wraps an engine with a de-duplicating cache plus an automatic fallback to the
|
|
76
|
+
* JS engine, so a ripgrep hiccup degrades to "slower" instead of "broken".
|
|
77
|
+
*/
|
|
78
|
+
export declare function createCachedEngine(engine: Engine): CachedEngine;
|
|
79
|
+
export {};
|
|
80
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEzF,gFAAgF;AAChF,eAAO,MAAM,aAAa,QAAmB,CAAC;AAQ9C,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACtD;;;OAGG;IACH,WAAW,CAAC,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;CAClE;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,YAAY,CAAC;AAEjE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAWD,oDAAoD;AACpD,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,aAAa,EAAE,CAAC;CAC5B;AAMD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAWrE;AAqBD,mEAAmE;AACnE,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAMpG;AAOD,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAepD;AAeD,4DAA4D;AAC5D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,QAAQ,GAAE,SAAS,MAAM,EAAqB,GAAG,MAAM,EAAE,CAWjH;AA4ID,0DAA0D;AAC1D,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,MAAM,CAS5E;AA6GD,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CA8CxF;AAID,+DAA+D;AAC/D,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;AAEzE,eAAO,MAAM,gBAAgB,EAAE,WAAoC,CAAC;AA8CpE,oEAAoE;AACpE,wBAAsB,aAAa,CAAC,UAAU,GAAE,gBAAyB,GAAG,OAAO,CAAC,MAAM,CAAC,CAU1F;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE5D;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAgE/D"}
|