@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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 spec-guard contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,393 @@
1
+ # spec-guard
2
+
3
+ **Executable architecture assertions for Markdown specs & ADRs.**
4
+
5
+ Turn the claims your design documents make about your codebase into assertions
6
+ that run in CI, in milliseconds, with no runtime dependencies.
7
+
8
+ ```md
9
+ The retired payment gateway is gone from the service layer.
10
+
11
+ <!-- @assert-absence target="src/services" symbol="LegacyPaymentGateway" -->
12
+ ```
13
+
14
+ If someone reintroduces `LegacyPaymentGateway`, CI fails and points at the line
15
+ of the ADR that promised it was gone.
16
+
17
+ ---
18
+
19
+ ## Why this exists
20
+
21
+ Architecture Decision Records, RFCs and technical briefs are full of hard claims
22
+ about a repository:
23
+
24
+ - *"`LegacyPaymentGateway` no longer exists anywhere in the service layer."*
25
+ - *"There is exactly one `UserSessionManager`."*
26
+ - *"No secret is ever read from `process.env` outside `src/config`."*
27
+
28
+ Every one of those claims is true on the day it is written and unverified from
29
+ the day after. Prose does not fail a build. Six months later the document is
30
+ still confidently asserting something the code stopped doing in March.
31
+
32
+ That has always been an annoyance. It became a defect multiplier the moment
33
+ coding agents started reading these documents as ground truth. An agent that
34
+ reads *"module X does not exist yet"* will happily build a second X. A reviewer
35
+ who trusts *"this symbol occurs zero times"* skips the grep. Stale documentation
36
+ is now executable in the worst sense: it executes inside someone's head, or
37
+ inside a model's context window, and produces work that has to be thrown away.
38
+
39
+ spec-guard makes the claims executable in the good sense. It reads directives
40
+ written as ordinary HTML comments - invisible in every Markdown renderer - and
41
+ checks them against the real tree with ripgrep. A stale claim becomes a failing
42
+ build with a line number, not a landmine.
43
+
44
+ ## Quickstart
45
+
46
+ ```bash
47
+ npm install --save-dev @descent-vtt/spec-guard
48
+ npx spec-guard "docs/**/*.md"
49
+ ```
50
+
51
+ **The package is scoped; the command is not.** Once installed, the binary is
52
+ plain `spec-guard`, so `npx spec-guard`, `npm scripts` and a global install all
53
+ use that name:
54
+
55
+ ```bash
56
+ npm install -g @descent-vtt/spec-guard # then: spec-guard "docs/**/*.md"
57
+ ```
58
+
59
+ Without a local install, `npx` needs the full package name - `npx spec-guard`
60
+ on its own would resolve to a different package on the registry:
61
+
62
+ ```bash
63
+ npx @descent-vtt/spec-guard "docs/**/*.md"
64
+ ```
65
+
66
+ Add a directive to any Markdown file, directly under the sentence it makes
67
+ executable:
68
+
69
+ ```md
70
+ ## Decision
71
+
72
+ Session state has exactly one owner.
73
+
74
+ <!-- @assert-count target="src/" symbol="UserSessionManager" expected="1" -->
75
+ ```
76
+
77
+ Run it:
78
+
79
+ ```bash
80
+ npx spec-guard "docs/**/*.md" --verbose
81
+ ```
82
+
83
+ ```text
84
+ spec-guard 1 spec · 1 assertion · ripgrep
85
+
86
+ ✔ docs/adr/0007-sessions.md:5 @assert-count "UserSessionManager" (1 match) in src
87
+
88
+ 1 passed · 7ms
89
+ ✔ every spec assertion holds
90
+ ```
91
+
92
+ And when it drifts:
93
+
94
+ ```text
95
+ ✖ docs/adr/0007-sessions.md:5 @assert-count
96
+ "UserSessionManager" must appear exactly 1 time in src
97
+ expected exactly 1 match, found 3
98
+ src/auth/UserSessionManager.ts:12:14 export class UserSessionManager {
99
+ src/legacy/sessions.ts:4:22 import { UserSessionManager } from '../auth';
100
+ src/workers/refresh.ts:9:10 const manager = new UserSessionManager();
101
+
102
+ 0 passed · 1 failed · 9ms
103
+ ```
104
+
105
+ Exit code 1. The ADR is now a test.
106
+
107
+ ## Directives
108
+
109
+ Every directive is an HTML comment. It may span multiple lines. Attribute values
110
+ may use double or single quotes, and a bare attribute means `="true"`.
111
+
112
+ ### `@assert-absence` - this symbol is gone
113
+
114
+ ```md
115
+ <!-- @assert-absence target="src/controllers,src/services" symbol="LegacyPaymentGateway" -->
116
+ <!-- @assert-absence target="src/" symbol="STRIPE_SECRET_KEY" expected="0" -->
117
+ <!-- @assert-absence target="src/" symbol="TODO" expected="5" reason="burn down the backlog" -->
118
+ ```
119
+
120
+ Fails when the symbol occurs more than `expected` times (default `0`).
121
+ `max="..."` is accepted as a synonym for `expected`.
122
+
123
+ ### `@assert-count` - this symbol occurs exactly / at least / at most N times
124
+
125
+ ```md
126
+ <!-- @assert-count target="src/" symbol="UserSessionManager" expected="1" -->
127
+ <!-- @assert-count target="src/ui/" symbol="PrimaryButton" min="1" -->
128
+ <!-- @assert-count target="src/core/" symbol="DeprecatedHelper" max="3" -->
129
+ <!-- @assert-count target="src/" symbol="Repository" min="2" max="10" -->
130
+ ```
131
+
132
+ Requires `expected`, or `min` and/or `max`. `expected` cannot be combined with
133
+ `min`/`max`.
134
+
135
+ ### `@assert-present` - this file exists
136
+
137
+ ```md
138
+ <!-- @assert-present file="SECURITY.md" -->
139
+ <!-- @assert-present file="config/production.json,config/staging.json" -->
140
+ ```
141
+
142
+ Passes when every listed path exists relative to `--root`. Directories count.
143
+
144
+ ### Attributes
145
+
146
+ | Attribute | Applies to | Meaning |
147
+ | --- | --- | --- |
148
+ | `target` | absence, count | Comma-separated paths to search, relative to `--root`. Default `.` |
149
+ | `symbol` | absence, count | The literal string to search for (or a regex with `regex="true"`) |
150
+ | `file` | present | Comma-separated paths that must exist |
151
+ | `expected` | absence, count | Upper bound for absence; exact count for count |
152
+ | `min` / `max` | count (`max` also on absence) | Inclusive bounds |
153
+ | `glob` | absence, count | Comma-separated file filters, e.g. `*.ts,*.tsx` (ripgrep `-g` semantics) |
154
+ | `regex` | absence, count | Treat `symbol` as a regular expression |
155
+ | `word` | absence, count | Require word boundaries, so `Primary` does not match `PrimaryButton` |
156
+ | `ignore-case` | absence, count | Case-insensitive matching |
157
+ | `reason` | all | Human-readable justification, printed on failure |
158
+
159
+ Unknown attributes are an error, not a shrug: `expct="1"` fails the run instead
160
+ of silently asserting nothing.
161
+
162
+ ## CLI
163
+
164
+ ```bash
165
+ spec-guard [patterns...] [options]
166
+ ```
167
+
168
+ | Option | Description |
169
+ | --- | --- |
170
+ | `-r, --root <path>` | Codebase root that assertions resolve against (default: cwd) |
171
+ | `-v, --verbose` | Print passing assertions too |
172
+ | `--fail-fast` | Stop at the first failing assertion |
173
+ | `--json` | Machine-readable report on stdout |
174
+ | `--engine <auto\|rg\|js>` | Search engine (default `auto`: ripgrep when available) |
175
+ | `--strict` | Treat a `target` that does not exist as a failure, not a warning |
176
+ | `--include-specs` | Also count matches inside the spec files themselves |
177
+ | `--max-snippets <n>` | Failure snippets per assertion (default 5) |
178
+ | `--concurrency <n>` | Search passes in flight at once (default 8) |
179
+ | `--allow-empty` | Exit 0 when no spec file matched the patterns |
180
+ | `--color` / `--no-color` | Force colour on or off (`NO_COLOR` honoured) |
181
+
182
+ Patterns are expanded by spec-guard itself, so quoted globs behave identically
183
+ on Windows, macOS and Linux. A directory expands to the Markdown files in it.
184
+
185
+ ### Exit codes
186
+
187
+ | Code | Meaning |
188
+ | --- | --- |
189
+ | `0` | Every assertion held |
190
+ | `1` | An assertion failed, or a directive was malformed |
191
+ | `2` | spec-guard could not run: bad usage, no spec files matched, `--engine rg` with no ripgrep |
192
+
193
+ ## CI integration
194
+
195
+ ```yaml
196
+ # .github/workflows/specs.yml
197
+ name: Specs
198
+ on: [push, pull_request]
199
+
200
+ jobs:
201
+ spec-guard:
202
+ runs-on: ubuntu-latest
203
+ steps:
204
+ - uses: actions/checkout@v5
205
+ - uses: actions/setup-node@v5
206
+ with:
207
+ node-version: '22'
208
+ - run: npx @descent-vtt/spec-guard "docs/**/*.md" "README.md" --verbose
209
+ ```
210
+
211
+ That job needs nothing else installed. GitHub-hosted runners do **not** ship
212
+ ripgrep on `PATH` - spec-guard's own CI reports `engine: javascript` there - so
213
+ the fallback is what actually runs, and it produces identical results. If your
214
+ repository is large enough that you want ripgrep's speed, install it first:
215
+
216
+ ```yaml
217
+ - run: sudo apt-get update && sudo apt-get install -y ripgrep
218
+ ```
219
+
220
+ Or point spec-guard at a binary you already have with `SPEC_GUARD_RG=/path/to/rg`.
221
+
222
+ As a pre-commit hook (assuming a local install, so the bare command resolves):
223
+
224
+ ```bash
225
+ npx spec-guard "docs/**/*.md" --fail-fast
226
+ ```
227
+
228
+ ## How it works
229
+
230
+ ```text
231
+ parser ──▶ Directive[] ──▶ runner ──▶ Assertion[] ──▶ engine ──▶ AssertionResult[] ──▶ reporter
232
+ (pure) (I/O) (rg | js) (pure)
233
+ ```
234
+
235
+ 1. **Parse.** Markdown is scanned for `<!-- @assert-* -->` comments. Fenced code
236
+ blocks and inline code spans are masked first, so documentation that shows
237
+ the syntax (like this README) never executes it.
238
+ 2. **Resolve.** Each directive becomes a typed assertion. Bad numbers, unknown
239
+ booleans, unknown attributes and paths escaping `--root` are rejected here,
240
+ before any I/O.
241
+ 3. **Search.** Assertions that share a target list and flags are answered by a
242
+ *single* pass over the tree.
243
+ 4. **Report.** ANSI output with the spec location, the expectation, the observed
244
+ count, and up to five real snippets; or JSON for tooling.
245
+
246
+ ### Performance
247
+
248
+ Scanning a tree costs about the same whether you look for one symbol or twenty,
249
+ so spec-guard groups assertions by target set and flags and answers each group
250
+ in one pass. Measured on Windows 11 / Node 24 / ripgrep 15 against a synthetic
251
+ 2,000-file, 5.1 MB tree with 8 assertions:
252
+
253
+ | | one pass per assertion | batched (current) |
254
+ | --- | --- | --- |
255
+ | ripgrep engine | ~280 ms | **~72 ms** |
256
+ | JavaScript fallback | ~780 ms | **~283 ms** |
257
+
258
+ Two honest caveats:
259
+
260
+ - **Process spawning is expensive on Windows** (~27 ms each). On a small tree
261
+ the JavaScript engine can beat ripgrep outright - spec-guard checks its own
262
+ repository in ~15 ms with `--engine js` versus ~170 ms with ripgrep. On Linux
263
+ and macOS, where spawning costs a few milliseconds, ripgrep wins at every
264
+ size. If your repository is small and you care about the last millisecond,
265
+ `--engine js` is a legitimate choice.
266
+ - **ripgrep is the reference implementation.** The fallback matches it on
267
+ everything the test suite covers - counts, snippets, word boundaries, globs,
268
+ binary skipping, file-size limits - but ripgrep also honours `.gitignore`,
269
+ while the fallback uses a fixed ignore list (`node_modules`, `dist`, `build`,
270
+ `coverage`, `.git`, dotfiles, and friends).
271
+
272
+ ## Programmatic API
273
+
274
+ ```ts
275
+ import { runSpecGuard, formatReport } from '@descent-vtt/spec-guard';
276
+
277
+ const report = await runSpecGuard({
278
+ patterns: ['docs/**/*.md'],
279
+ root: process.cwd(),
280
+ });
281
+
282
+ if (!report.ok) {
283
+ console.error(formatReport(report, { color: true, verbose: false }));
284
+ process.exitCode = 1;
285
+ }
286
+ ```
287
+
288
+ `runSpecGuard` returns the full report: every assertion, its bounds, the
289
+ observed count, match locations, warnings and timings. The parser
290
+ (`parseDirectives`) and reporter (`formatReport`, `formatJson`) are pure
291
+ functions you can use on their own.
292
+
293
+ ## Design decisions
294
+
295
+ This tool was specified loosely and built opinionatedly. Where the
296
+ implementation departs from the obvious reading of the brief, here is why.
297
+
298
+ **Spec files are excluded from their own searches.** An ADR that says
299
+ "`LegacyGateway` must not appear" contains the string `LegacyGateway`. Without
300
+ this rule, absence assertions would fail on the document asserting them - the
301
+ single most confusing possible first-run experience. `--include-specs` restores
302
+ the naive behaviour.
303
+
304
+ **Fenced code and inline code are masked before parsing.** A README documenting
305
+ the syntax must not execute it. Masking preserves byte offsets, so reported line
306
+ numbers stay exact. (This is subtle: pairing backtick runs the naive way
307
+ desynchronises after a stray unmatched run and un-masks real prose. spec-guard
308
+ uses CommonMark's equal-length pairing rule, and there is a regression test.)
309
+
310
+ **Malformed directives fail the run.** A typo like `expct="1"` could be ignored
311
+ as "not a directive". It is instead an error, because a spec tool whose typos
312
+ silently assert nothing is worse than no spec tool.
313
+
314
+ **Four attributes were added beyond the original brief** - `word`, `regex`,
315
+ `glob` and `ignore-case` - because `symbol="Primary"` matching `PrimaryButton`
316
+ is the first thing every user hits, and `reason` because a failure message
317
+ should say *why* the rule exists.
318
+
319
+ **`min`/`max` are accepted on `@assert-absence` too.** "At most 5 TODOs" is an
320
+ absence claim with a budget, and burning a budget down is a real workflow.
321
+
322
+ **The engine is detected lazily.** Probing with `rg --version` up front costs a
323
+ process spawn (~27 ms on Windows) on the critical path of every run, including
324
+ runs where ripgrep is missing. Discovering its absence from the first real
325
+ search is free.
326
+
327
+ **Assertions are batched, but only when provably safe.** Merging literals into
328
+ one ripgrep alternation can lose matches two ways: containment (`Primary` /
329
+ `PrimaryButton`) and dovetailing (`abc` / `cd` in `abcd`). spec-guard checks for
330
+ both and falls back to separate passes when either is possible, and never
331
+ batches regexes or case-insensitive searches. Speed is never traded for a wrong
332
+ count.
333
+
334
+ **Exit code 2 exists.** "Your specs failed" and "spec-guard could not run" are
335
+ different facts, and CI should be able to tell them apart.
336
+
337
+ ## spec-guard checks itself
338
+
339
+ The invariants in [`docs/adr/0001-invariants.md`](docs/adr/0001-invariants.md)
340
+ and [`docs/adr/0002-directive-format.md`](docs/adr/0002-directive-format.md) are
341
+ executed against this repository on every CI run. The CLI never calls
342
+ `console.log`, nothing outside the engine spawns a process, and the parser and
343
+ reporter never touch the filesystem - because those documents say so, and the
344
+ build fails if they stop being true.
345
+
346
+ This README is executable too:
347
+
348
+ <!-- @assert-present file="src/parser.ts,src/engine.ts,src/reporter.ts,src/runner.ts,src/cli.ts" -->
349
+
350
+ ## Development
351
+
352
+ ```bash
353
+ npm install
354
+ npm run build # tsc -> dist/
355
+ npm test # vitest
356
+ npm run test:coverage
357
+ npm run test:mutation # stryker (~20 minutes)
358
+ npm run lint # tsc --noEmit
359
+ npm run selfcheck # run spec-guard on its own docs
360
+ ```
361
+
362
+ The test suite runs every assertion case against **both** engines and asserts
363
+ they agree, so the fallback cannot quietly drift from ripgrep. `@vscode/ripgrep`
364
+ is a devDependency purely so that the ripgrep path is exercised on every
365
+ platform in CI, including machines that have no `rg` on PATH; it ships a
366
+ prebuilt binary and is never a runtime dependency.
367
+
368
+ ### Mutation testing
369
+
370
+ Coverage says a line ran. It does not say an assertion would notice if the line
371
+ behaved differently. This repository measures the difference: **83.16%** of
372
+ 2,060 mutants are killed, against 98.98% line coverage.
373
+
374
+ That gap is the point. The first run scored 77.23%, and the weakest file was
375
+ the reporter at 65.48% - not because it lacked tests, but because its tests were
376
+ almost all `toContain` against colourless output. A mutant could prepend a junk
377
+ line to the output, drop a colour, or turn `remaining > 0` into `remaining >= 0`
378
+ and every test still passed. Seventy-five tests later - exact whole-output
379
+ comparison instead of substring matching - the reporter is at 87.30%.
380
+
381
+ `npm run test:mutation` runs it. CI runs it weekly, on demand, and on pull
382
+ requests that touch `src/` or `tests/`, with the score gated at 80%. The full
383
+ story, including a run whose score turned out to be fiction, is in
384
+ [ADR-0003](docs/adr/0003-mutation-testing.md).
385
+
386
+ ## Requirements
387
+
388
+ - Node.js 22 or newer (native ESM)
389
+ - ripgrep optional - used when present, replaced by a built-in scanner when not
390
+
391
+ ## License
392
+
393
+ MIT
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Thin launcher. All logic lives in dist/cli.js so the published binary stays
4
+ * a two-line shim that is trivially auditable.
5
+ */
6
+ import { pathToFileURL } from 'node:url';
7
+
8
+ const entry = new URL('../dist/cli.js', import.meta.url);
9
+
10
+ let cli;
11
+ try {
12
+ cli = await import(entry.href);
13
+ } catch (error) {
14
+ if (error?.code === 'ERR_MODULE_NOT_FOUND') {
15
+ process.stderr.write(
16
+ `spec-guard: build output missing at ${pathToFileURL(entry.pathname).pathname}\n` +
17
+ 'Run "npm run build" first (or install the published package).\n',
18
+ );
19
+ process.exit(2);
20
+ }
21
+ throw error;
22
+ }
23
+
24
+ process.exitCode = await cli.main();
package/dist/cli.d.ts ADDED
@@ -0,0 +1,43 @@
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 type { EnginePreference } from './engine.js';
10
+ export declare const EXIT_OK = 0;
11
+ export declare const EXIT_FAILED = 1;
12
+ export declare const EXIT_ERROR = 2;
13
+ export interface CliIO {
14
+ stdout: (text: string) => void;
15
+ stderr: (text: string) => void;
16
+ env: NodeJS.ProcessEnv;
17
+ cwd: string;
18
+ isTTY: boolean;
19
+ }
20
+ export interface CliOptions {
21
+ patterns: string[];
22
+ root: string;
23
+ verbose: boolean;
24
+ failFast: boolean;
25
+ json: boolean;
26
+ engine: EnginePreference;
27
+ strictTargets: boolean;
28
+ includeSpecs: boolean;
29
+ allowEmpty: boolean;
30
+ maxSnippets: number;
31
+ concurrency: number;
32
+ color?: boolean;
33
+ help: boolean;
34
+ version: boolean;
35
+ }
36
+ export declare class UsageError extends Error {
37
+ }
38
+ export declare const HELP = "spec-guard - Executable architecture assertions for Markdown specs & ADRs\n\nUsage\n spec-guard [patterns...] [options]\n\nPatterns\n Globs or paths to the Markdown specs to execute. A directory expands to the\n Markdown files inside it. Defaults to \"docs/**/*.md\" when omitted.\n\nOptions\n -r, --root <path> Codebase root that assertions are resolved against (default: cwd)\n -v, --verbose Print passing assertions too\n --fail-fast Stop at the first failing assertion\n --json Emit a machine-readable JSON report\n --engine <name> auto | rg | js (default: auto - ripgrep when available)\n --strict Treat a target path that does not exist as a failure\n --include-specs Also count matches inside the spec files themselves\n --max-snippets <n> Failure snippets per assertion (default: 5)\n --concurrency <n> Assertions executed in parallel (default: 8)\n --allow-empty Exit 0 when no spec files matched\n --color/--no-color Force colour on or off (NO_COLOR is honoured)\n -h, --help Show this help\n --version Print the version\n\nDirectives\n <!-- @assert-absence target=\"src/\" symbol=\"LegacyGateway\" -->\n <!-- @assert-count target=\"src/\" symbol=\"SessionManager\" expected=\"1\" -->\n <!-- @assert-present file=\"SECURITY.md\" -->\n\nExit codes\n 0 all assertions passed 1 an assertion failed 2 spec-guard could not run";
39
+ /** Minimal, dependency-free argv parser. Supports `--flag value` and `--flag=value`. */
40
+ export declare function parseArgs(argv: readonly string[], cwd: string): CliOptions;
41
+ /** Runs the CLI and resolves to the process exit code. */
42
+ export declare function main(argv?: readonly string[], io?: CliIO): Promise<number>;
43
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,eAAO,MAAM,OAAO,IAAI,CAAC;AACzB,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,UAAU,IAAI,CAAC;AAE5B,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,gBAAgB,CAAC;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAaxC,eAAO,MAAM,IAAI,o9CA8B8D,CAAC;AAuBhF,wFAAwF;AACxF,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAmG1E;AAYD,0DAA0D;AAC1D,wBAAsB,IAAI,CAAC,IAAI,GAAE,SAAS,MAAM,EAA0B,EAAE,EAAE,GAAE,KAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CA+DpH"}