@jjchill/probity-rules 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/CHANGELOG.md +18 -0
- package/GLOSSARY.template.md +33 -0
- package/README.md +96 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/presets/js.d.ts +63 -0
- package/dist/presets/js.d.ts.map +1 -0
- package/dist/presets/js.js +112 -0
- package/dist/presets/js.js.map +1 -0
- package/dist/presets/kmp.d.ts +27 -0
- package/dist/presets/kmp.d.ts.map +1 -0
- package/dist/presets/kmp.js +253 -0
- package/dist/presets/kmp.js.map +1 -0
- package/dist/presets/kotlin.d.ts +40 -0
- package/dist/presets/kotlin.d.ts.map +1 -0
- package/dist/presets/kotlin.js +172 -0
- package/dist/presets/kotlin.js.map +1 -0
- package/dist/presets/swift.d.ts +10 -0
- package/dist/presets/swift.d.ts.map +1 -0
- package/dist/presets/swift.js +285 -0
- package/dist/presets/swift.js.map +1 -0
- package/dist/rules/acceptance-language.d.ts +95 -0
- package/dist/rules/acceptance-language.d.ts.map +1 -0
- package/dist/rules/acceptance-language.js +443 -0
- package/dist/rules/acceptance-language.js.map +1 -0
- package/dist/rules/gates.d.ts +125 -0
- package/dist/rules/gates.d.ts.map +1 -0
- package/dist/rules/gates.js +285 -0
- package/dist/rules/gates.js.map +1 -0
- package/dist/rules/kotlin.d.ts +323 -0
- package/dist/rules/kotlin.d.ts.map +1 -0
- package/dist/rules/kotlin.js +722 -0
- package/dist/rules/kotlin.js.map +1 -0
- package/dist/rules/ports-and-adapters.d.ts +86 -0
- package/dist/rules/ports-and-adapters.d.ts.map +1 -0
- package/dist/rules/ports-and-adapters.js +366 -0
- package/dist/rules/ports-and-adapters.js.map +1 -0
- package/dist/rules/scoping.d.ts +68 -0
- package/dist/rules/scoping.d.ts.map +1 -0
- package/dist/rules/scoping.js +93 -0
- package/dist/rules/scoping.js.map +1 -0
- package/dist/rules/spec-test-parity.d.ts +164 -0
- package/dist/rules/spec-test-parity.d.ts.map +1 -0
- package/dist/rules/spec-test-parity.js +456 -0
- package/dist/rules/spec-test-parity.js.map +1 -0
- package/dist/rules/swift.d.ts +50 -0
- package/dist/rules/swift.d.ts.map +1 -0
- package/dist/rules/swift.js +50 -0
- package/dist/rules/swift.js.map +1 -0
- package/dist/rules/ubiquitous-language.d.ts +36 -0
- package/dist/rules/ubiquitous-language.d.ts.map +1 -0
- package/dist/rules/ubiquitous-language.js +140 -0
- package/dist/rules/ubiquitous-language.js.map +1 -0
- package/dist/scripts/scope-report.d.ts +3 -0
- package/dist/scripts/scope-report.d.ts.map +1 -0
- package/dist/scripts/scope-report.js +184 -0
- package/dist/scripts/scope-report.js.map +1 -0
- package/kiro/README.md +20 -0
- package/kiro/kiro-agent.template.json +45 -0
- package/kiro/kiro-transcript-to-claude.py +183 -0
- package/kiro/probity-kiro-translate.py +132 -0
- package/kiro/probity-kiro.sh +87 -0
- package/kiro/skill-activation-forced-eval.sh +45 -0
- package/package.json +68 -0
- package/probity.config.kmp.ts +40 -0
- package/probity.config.kotlin.ts +37 -0
- package/probity.config.swift.ts +38 -0
- package/probity.config.ts +43 -0
- package/scripts/spec-parity.mjs +345 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
function countMatches(content, pattern) {
|
|
5
|
+
let count = 0;
|
|
6
|
+
for (const _ of content.matchAll(pattern))
|
|
7
|
+
count++;
|
|
8
|
+
return count;
|
|
9
|
+
}
|
|
10
|
+
/** Patterns whose occurrence count grows from before → after. */
|
|
11
|
+
export async function introducedPatterns(action, ctx, patterns) {
|
|
12
|
+
const hits = patterns.filter(({ pattern }) => countMatches(action.content, pattern) > 0);
|
|
13
|
+
if (hits.length === 0)
|
|
14
|
+
return [];
|
|
15
|
+
const before = await ctx?.readFile?.(action.path);
|
|
16
|
+
const beforeContent = before?.kind === 'present' ? before.content : '';
|
|
17
|
+
return hits
|
|
18
|
+
.filter(({ pattern }) => countMatches(action.content, pattern) >
|
|
19
|
+
countMatches(beforeContent, pattern))
|
|
20
|
+
.map(({ label }) => label);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Ambient-effect calls for a JS/TS core: OS clock, randomness, and
|
|
24
|
+
* environment reads. Pass to {@link forbidNewAmbientEffects} from a
|
|
25
|
+
* JS/TS config. `process.env` reads belong in the composition root or
|
|
26
|
+
* a config adapter; if your core legitimately branches on injected
|
|
27
|
+
* config, that config should arrive through a port, not the OS.
|
|
28
|
+
*/
|
|
29
|
+
export const JS_AMBIENT_EFFECT_PATTERNS = [
|
|
30
|
+
{ label: 'Date.now()', pattern: /\bDate\.now\s*\(/g },
|
|
31
|
+
{ label: 'new Date() (argless)', pattern: /\bnew\s+Date\s*\(\s*\)/g },
|
|
32
|
+
{ label: 'Math.random()', pattern: /\bMath\.random\s*\(/g },
|
|
33
|
+
{ label: 'crypto.randomUUID()', pattern: /\bcrypto\.randomUUID\s*\(/g },
|
|
34
|
+
{ label: 'randomUUID()', pattern: /\brandomUUID\s*\(/g },
|
|
35
|
+
{ label: 'process.env', pattern: /\bprocess\.env\b/g },
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* Blocks production writes that introduce direct ambient-effect calls.
|
|
39
|
+
* Under ports-and-adapters these are unowned OS dependencies: clock,
|
|
40
|
+
* randomness, and environment are ports.
|
|
41
|
+
*
|
|
42
|
+
* Delta-based: pre-existing call sites in a brownfield codebase don't
|
|
43
|
+
* block edits to their files; only net-new occurrences do. Scope to
|
|
44
|
+
* production sources — tests and adapter implementations (e.g. a
|
|
45
|
+
* `SystemClock`) legitimately touch the real OS, so exclude adapter
|
|
46
|
+
* paths via globs or negations.
|
|
47
|
+
*
|
|
48
|
+
* @param options.patterns — what an ambient-effect call looks like in
|
|
49
|
+
* your language ({@link JS_AMBIENT_EFFECT_PATTERNS} for JS/TS; the
|
|
50
|
+
* Kotlin preset supplies JVM patterns). Each RegExp needs `g`.
|
|
51
|
+
* @param options.seamHint — appended to the block message to point
|
|
52
|
+
* the agent at the project's canonical seam(s), e.g.
|
|
53
|
+
* "inject the Clock port from src/ports/clock.ts".
|
|
54
|
+
*/
|
|
55
|
+
export function forbidNewAmbientEffects(options) {
|
|
56
|
+
return async function forbidNewAmbientEffects(action, ctx) {
|
|
57
|
+
if (action.kind !== 'write')
|
|
58
|
+
return { kind: 'pass' };
|
|
59
|
+
const introduced = await introducedPatterns(action, ctx, options.patterns);
|
|
60
|
+
if (introduced.length === 0)
|
|
61
|
+
return { kind: 'pass' };
|
|
62
|
+
const hint = options.seamHint ? ` ${options.seamHint}.` : '';
|
|
63
|
+
return {
|
|
64
|
+
kind: 'violation',
|
|
65
|
+
reason: `This write introduces direct ambient-effect calls (${introduced.join(', ')}). Clock, randomness, and environment are unowned OS ` +
|
|
66
|
+
'dependencies: reach them through a port injected into this ' +
|
|
67
|
+
`code, implemented by a thin adapter.${hint} Existing call ` +
|
|
68
|
+
'sites in the file are untouched by this rule — only new ones ' +
|
|
69
|
+
'are blocked.',
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Files the pending commit will record, repo-relative. Reads the
|
|
75
|
+
* staged set (`git diff --cached --name-only`); for a `git commit`
|
|
76
|
+
* with `-a`/`-am`/`--all` it also folds in modified-but-unstaged
|
|
77
|
+
* tracked files. Throws if git is unavailable — callers fail safe.
|
|
78
|
+
*/
|
|
79
|
+
function defaultListCommitFiles(command) {
|
|
80
|
+
const run = (args) => execFileSync('git', args, { cwd: process.cwd(), encoding: 'utf8' })
|
|
81
|
+
.split('\n')
|
|
82
|
+
.map((line) => line.trim())
|
|
83
|
+
.filter((line) => line.length > 0);
|
|
84
|
+
const files = run(['diff', '--cached', '--name-only']);
|
|
85
|
+
if (/\s-\w*a\w*\b/.test(command) || /--all\b/.test(command)) {
|
|
86
|
+
for (const file of run(['diff', '--name-only'])) {
|
|
87
|
+
if (!files.includes(file))
|
|
88
|
+
files.push(file);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return files;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Commit-on-green, strictly: Probity's `requireCommand` checks only
|
|
95
|
+
* that a matching test invocation was *recorded* after the last write
|
|
96
|
+
* and would happily pass a transcript whose latest run FAILED. This
|
|
97
|
+
* rule additionally judges the recorded run's output: the last
|
|
98
|
+
* matching test command after the last write must look green
|
|
99
|
+
* (`successPattern` present, `failurePattern` absent).
|
|
100
|
+
*
|
|
101
|
+
* Inherent limit (unchanged from requireCommand): the gate sees only
|
|
102
|
+
* the session transcript. A green run in another terminal, CI, or a
|
|
103
|
+
* wrapper script is invisible — rerun the suite in-session, and keep
|
|
104
|
+
* the CI mirror for human commits.
|
|
105
|
+
*
|
|
106
|
+
* Applies to: command actions matching `git commit`. Deterministic —
|
|
107
|
+
* no AI call.
|
|
108
|
+
*
|
|
109
|
+
* @param options.command — regex matching a test invocation.
|
|
110
|
+
* @param options.successPattern — output must match to count as green.
|
|
111
|
+
* @param options.failurePattern — output matching this is red even if
|
|
112
|
+
* the success pattern also appears.
|
|
113
|
+
* @param options.enforceForPaths — only demand the run when the
|
|
114
|
+
* pending commit stages a file whose repo-relative path matches.
|
|
115
|
+
* Scopes an expensive suite to the code it covers: infra/docs/
|
|
116
|
+
* tooling-only commits (CI config, Markdown, the Probity config
|
|
117
|
+
* itself) pay no friction, since they change no behaviour the suite
|
|
118
|
+
* validates and the prior green run still stands. Commit-accurate —
|
|
119
|
+
* read from git's staged set, not the session's write history. Any
|
|
120
|
+
* error listing files falls through to enforcing (fail safe).
|
|
121
|
+
* @param options.listCommitFiles — injectable staged-file lister
|
|
122
|
+
* (defaults to reading git's staged set); present for testing.
|
|
123
|
+
* @param options.reason — appended to the no-run deny text to name
|
|
124
|
+
* the suite and any setup it needs (e.g. "supabase start").
|
|
125
|
+
*/
|
|
126
|
+
export function requireGreenTestRun(options) {
|
|
127
|
+
const listCommitFiles = options.listCommitFiles ?? defaultListCommitFiles;
|
|
128
|
+
return async function requireGreenTestRun(action, ctx) {
|
|
129
|
+
if (action.kind !== 'command')
|
|
130
|
+
return { kind: 'pass' };
|
|
131
|
+
if (!/git commit/.test(action.command))
|
|
132
|
+
return { kind: 'pass' };
|
|
133
|
+
// Scope the gate to commits that stage code the suite validates.
|
|
134
|
+
// On any error listing files, fall through and enforce (fail safe).
|
|
135
|
+
if (options.enforceForPaths) {
|
|
136
|
+
const pattern = options.enforceForPaths;
|
|
137
|
+
let files;
|
|
138
|
+
try {
|
|
139
|
+
files = listCommitFiles(action.command);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
files = undefined;
|
|
143
|
+
}
|
|
144
|
+
if (files && !files.some((file) => pattern.test(file))) {
|
|
145
|
+
return { kind: 'pass' };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const history = (await ctx?.history?.()) ?? [];
|
|
149
|
+
const lastWrite = history.reduce((last, event, index) => (event.kind === 'write' ? index : last), -1);
|
|
150
|
+
const runs = history.filter((event, index) => index > lastWrite &&
|
|
151
|
+
event.kind === 'command' &&
|
|
152
|
+
options.command.test(event.command));
|
|
153
|
+
if (runs.length === 0) {
|
|
154
|
+
const extra = options.reason ? ` ${options.reason}` : '';
|
|
155
|
+
return {
|
|
156
|
+
kind: 'violation',
|
|
157
|
+
reason: 'Run the test suite after the last change before committing ' +
|
|
158
|
+
`(see test-driven-development: commit only on green).${extra}`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const lastRun = runs[runs.length - 1];
|
|
162
|
+
const output = 'output' in lastRun ? (lastRun.output ?? '') : '';
|
|
163
|
+
if (options.failurePattern.test(output) || !options.successPattern.test(output)) {
|
|
164
|
+
return {
|
|
165
|
+
kind: 'violation',
|
|
166
|
+
reason: 'The last recorded test run after your changes was not ' +
|
|
167
|
+
'green — a recorded invocation is not a passing suite. Fix ' +
|
|
168
|
+
'the failures (or the build) and rerun before committing.',
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return { kind: 'pass' };
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const SKIP_DIRS = new Set([
|
|
175
|
+
'node_modules',
|
|
176
|
+
'.git',
|
|
177
|
+
'dist',
|
|
178
|
+
'build',
|
|
179
|
+
'out',
|
|
180
|
+
'coverage',
|
|
181
|
+
'target',
|
|
182
|
+
'.next',
|
|
183
|
+
]);
|
|
184
|
+
function walk(dir, out = []) {
|
|
185
|
+
let entries;
|
|
186
|
+
try {
|
|
187
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return out;
|
|
191
|
+
}
|
|
192
|
+
for (const entry of entries) {
|
|
193
|
+
if (entry.isDirectory()) {
|
|
194
|
+
if (!SKIP_DIRS.has(entry.name))
|
|
195
|
+
walk(join(dir, entry.name), out);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
out.push(join(dir, entry.name));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
const DEFAULT_STRING_LITERAL = /(['"])((?:(?!\1)[^\\\n]|\\.){4,})\1|`((?:(?!`)(?!\$\{)[^\\]|\\.){4,})`/g;
|
|
204
|
+
function extractStringLiterals(content, minLength) {
|
|
205
|
+
const out = new Set();
|
|
206
|
+
for (const match of content.matchAll(DEFAULT_STRING_LITERAL)) {
|
|
207
|
+
const value = match[2] ?? match[3] ?? '';
|
|
208
|
+
if (value.trim().length >= minLength)
|
|
209
|
+
out.add(value);
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Surfaces the "renamed a button, broke the E2E suite" failure at
|
|
215
|
+
* write time: when a write REMOVES a string literal that still appears
|
|
216
|
+
* verbatim in files under `searchRoots` (typically your E2E/UI-test
|
|
217
|
+
* specs, which select elements by visible text or accessible name),
|
|
218
|
+
* the write is blocked with the list of dependent files.
|
|
219
|
+
*
|
|
220
|
+
* The inverse of a glossary guard: `surfaceGlossaryTermBreakage`
|
|
221
|
+
* protects the vocabulary file from code that depends on it; this
|
|
222
|
+
* protects test selectors from the UI code they depend on. Scope it to
|
|
223
|
+
* your UI sources, with `searchRoots` pointing at the spec layers the
|
|
224
|
+
* quick local loop does NOT run (E2E, smoke) — specs the inner loop
|
|
225
|
+
* runs will fail red on their own.
|
|
226
|
+
*
|
|
227
|
+
* Deterministic, delta-based — no AI call. The write goes through once
|
|
228
|
+
* the dependent specs are updated in the same session (or the string
|
|
229
|
+
* genuinely stops being referenced).
|
|
230
|
+
*
|
|
231
|
+
* @param options.searchRoots — absolute paths to scan for usages.
|
|
232
|
+
* @param options.searchPattern — which files count as usage sites
|
|
233
|
+
* (default: `*.spec.*` / `*.test.*` under the roots).
|
|
234
|
+
* @param options.minLength — ignore removed literals shorter than
|
|
235
|
+
* this after trimming (default 8; short strings false-positive).
|
|
236
|
+
*/
|
|
237
|
+
export function surfaceRemovedStringUsage(options) {
|
|
238
|
+
const pattern = options.searchPattern ?? /\.(spec|test)\.[jt]sx?$/;
|
|
239
|
+
const minLength = options.minLength ?? 8;
|
|
240
|
+
return async function surfaceRemovedStringUsage(action, ctx) {
|
|
241
|
+
if (action.kind !== 'write')
|
|
242
|
+
return { kind: 'pass' };
|
|
243
|
+
const before = await ctx?.readFile?.(action.path);
|
|
244
|
+
if (!before || before.kind !== 'present')
|
|
245
|
+
return { kind: 'pass' };
|
|
246
|
+
const beforeStrings = extractStringLiterals(before.content, minLength);
|
|
247
|
+
if (beforeStrings.size === 0)
|
|
248
|
+
return { kind: 'pass' };
|
|
249
|
+
const afterContent = action.content;
|
|
250
|
+
const removed = [...beforeStrings].filter((s) => !afterContent.includes(s));
|
|
251
|
+
if (removed.length === 0)
|
|
252
|
+
return { kind: 'pass' };
|
|
253
|
+
const files = options.searchRoots
|
|
254
|
+
.flatMap((root) => walk(root))
|
|
255
|
+
.filter((file) => pattern.test(file));
|
|
256
|
+
const broken = [];
|
|
257
|
+
for (const text of removed) {
|
|
258
|
+
// Case-insensitive: specs routinely select via /log out/i-style
|
|
259
|
+
// regexes whose source is lowercased relative to the UI text.
|
|
260
|
+
const needle = text.toLowerCase();
|
|
261
|
+
const users = files.filter((file) => {
|
|
262
|
+
try {
|
|
263
|
+
return readFileSync(file, 'utf8').toLowerCase().includes(needle);
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
if (users.length > 0) {
|
|
270
|
+
broken.push(`"${text}" → ${users.join(', ')}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (broken.length === 0)
|
|
274
|
+
return { kind: 'pass' };
|
|
275
|
+
return {
|
|
276
|
+
kind: 'violation',
|
|
277
|
+
reason: 'This write removes UI text that test specs still select by:\n' +
|
|
278
|
+
broken.map((line) => ` - ${line}`).join('\n') +
|
|
279
|
+
'\nThose suites are not in the quick local loop, so this ' +
|
|
280
|
+
'breaks them silently until CI. Update the listed specs to the ' +
|
|
281
|
+
'new text (and run their suite) alongside this change.',
|
|
282
|
+
};
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=gates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gates.js","sourceRoot":"","sources":["../../rules/gates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import type { Rule } from '@nizos/probity';
|
|
2
|
+
import { type NamedPattern } from './gates.js';
|
|
3
|
+
/**
|
|
4
|
+
* Kotlin/JVM/Android preset for the ports-and-adapters rules. The
|
|
5
|
+
* JS-ecosystem screens in `ports-and-adapters.ts` (ESM imports,
|
|
6
|
+
* jest/vi module mocks) never fire on Kotlin; these are their
|
|
7
|
+
* Kotlin-shaped counterparts.
|
|
8
|
+
*
|
|
9
|
+
* Brownfield note: both rules here judge the DELTA — they block only
|
|
10
|
+
* occurrences the pending write introduces, so files that already
|
|
11
|
+
* carry violations can be edited freely and migrated incrementally.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Known framework/vendor/infrastructure imports that never belong in
|
|
15
|
+
* core code under the Dependency Rule. Kotlin `import` syntax.
|
|
16
|
+
* Extend with your stack's packages; `enforcePortsBoundary` catches
|
|
17
|
+
* what this list misses.
|
|
18
|
+
*/
|
|
19
|
+
export declare const KOTLIN_INFRASTRUCTURE_IMPORTS: RegExp;
|
|
20
|
+
/**
|
|
21
|
+
* Mocking-library imports, for projects whose convention is
|
|
22
|
+
* hand-written fakes at ports with no mocking library at all (pair
|
|
23
|
+
* with `forbidContentPattern`). Distinct from `forbidStaticMocks`,
|
|
24
|
+
* which permits the library but blocks its monkey-patching APIs.
|
|
25
|
+
*/
|
|
26
|
+
export declare const MOCKING_LIBRARY_IMPORTS: RegExp;
|
|
27
|
+
/**
|
|
28
|
+
* Matches Gradle test invocations (`./gradlew test`,
|
|
29
|
+
* `./gradlew :mysudo:testDevDebugUnitTest`, flavored Android unit-test
|
|
30
|
+
* tasks) for the commit-on-green `requireCommand` gate.
|
|
31
|
+
*/
|
|
32
|
+
export declare const GRADLE_TEST_COMMAND: RegExp;
|
|
33
|
+
/**
|
|
34
|
+
* Kotlin counterpart of `forbidInternalModuleMocks`: blocks test
|
|
35
|
+
* writes that introduce static/object/constructor mocking —
|
|
36
|
+
* `Mockito.mockStatic`, MockK's `mockkStatic`/`mockkObject`/
|
|
37
|
+
* `mockkConstructor`, PowerMock — the JVM's monkey-patching
|
|
38
|
+
* equivalents. These always bypass the architecture ("Ports Are the
|
|
39
|
+
* Only Test Seam"): whatever they intercept should be reached through
|
|
40
|
+
* a port and replaced with a fake.
|
|
41
|
+
*
|
|
42
|
+
* Plain `mock<SomeInterface>()` is NOT blocked: whether the mocked
|
|
43
|
+
* type is a port (fine) or an internal class (violation) isn't
|
|
44
|
+
* decidable from the call site — that judgment belongs to
|
|
45
|
+
* `enforcePortsBoundary` or review.
|
|
46
|
+
*
|
|
47
|
+
* Delta-based: pre-existing static mocks in the file don't
|
|
48
|
+
* re-trigger on later edits. Applies to: write actions. No AI call.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* { files: ['**\/src\/test\/**', '**\/src\/androidTest\/**'], rules: [forbidStaticMocks()] }
|
|
52
|
+
*/
|
|
53
|
+
export declare function forbidStaticMocks(): Rule;
|
|
54
|
+
/**
|
|
55
|
+
* Blocks production writes that introduce direct ambient-effect calls
|
|
56
|
+
* — OS clock (`Instant.now()`, `System.currentTimeMillis()`,
|
|
57
|
+
* `Date()`), randomness (`UUID.randomUUID()`, `Random()`), and
|
|
58
|
+
* environment (`System.getenv`). Under ports-and-adapters these are
|
|
59
|
+
* unowned OS dependencies: clock, randomness, and config are ports.
|
|
60
|
+
*
|
|
61
|
+
* Delta-based: the ~hundreds of pre-existing call sites in a
|
|
62
|
+
* brownfield codebase don't block edits to their files; only net-new
|
|
63
|
+
* occurrences do. Scope to production sources — tests and adapter
|
|
64
|
+
* implementations (e.g. a `DefaultTimeProvider`) legitimately touch
|
|
65
|
+
* the real OS, so exclude adapter paths via globs or negations.
|
|
66
|
+
*
|
|
67
|
+
* @param options.seamHint — appended to the block message to point
|
|
68
|
+
* the agent at the project's canonical seam(s), e.g.
|
|
69
|
+
* "inject com.anonyome.sudocommons.core.common.TimeProvider".
|
|
70
|
+
* @param options.patterns — replaces the default pattern list; each
|
|
71
|
+
* RegExp needs the `g` flag.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* { files: ['**\/src\/main\/**', '!**\/adapters\/**'], rules: [forbidNewAmbientEffects()] }
|
|
75
|
+
*/
|
|
76
|
+
export declare function forbidNewAmbientEffects(options?: {
|
|
77
|
+
seamHint?: string;
|
|
78
|
+
patterns?: NamedPattern[];
|
|
79
|
+
}): Rule;
|
|
80
|
+
/**
|
|
81
|
+
* Kotlin equivalent of `enforceTdd({ fastPath: true })`, which Probity
|
|
82
|
+
* only implements for TS/JS/Python/C#/Ruby/PHP: wraps a rule so that a
|
|
83
|
+
* `.kt`/`.kts` write adding exactly one new test function passes
|
|
84
|
+
* deterministically — no AI call for the most common write in a TDD
|
|
85
|
+
* loop, adding the next red test. Everything else (production writes,
|
|
86
|
+
* multi-test writes, non-Kotlin files) delegates to the wrapped rule
|
|
87
|
+
* unchanged.
|
|
88
|
+
*
|
|
89
|
+
* Requires the optional packages `@ast-grep/napi` and
|
|
90
|
+
* `@ast-grep/lang-kotlin` (`npm install -D` both). When they're
|
|
91
|
+
* missing, or the current file content is unavailable, or parsing
|
|
92
|
+
* fails, the wrapper transparently falls through to the wrapped rule
|
|
93
|
+
* — it can only ever skip work, never block.
|
|
94
|
+
*
|
|
95
|
+
* Same trade-off as Probity's own fast-path: a deterministic pass on
|
|
96
|
+
* every single-test addition skips the green→red refactor-readiness
|
|
97
|
+
* check the AI would otherwise perform.
|
|
98
|
+
*
|
|
99
|
+
* @param rule — the rule to wrap, normally `enforceTdd()`.
|
|
100
|
+
* @param options.patterns — replaces the default ast-grep test-node
|
|
101
|
+
* patterns (e.g. to add a Kotest spec pattern).
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* { files: ['**\/src\/main\/**', '**\/src\/test\/**'], rules: [withKotlinFastPath(enforceTdd())] }
|
|
105
|
+
*/
|
|
106
|
+
export declare function withKotlinFastPath(rule: Rule, options?: {
|
|
107
|
+
patterns?: unknown[];
|
|
108
|
+
}): Rule;
|
|
109
|
+
/**
|
|
110
|
+
* Commit-on-GREEN gate — the stricter sibling of Probity's built-in
|
|
111
|
+
* `requireCommand`, which only checks that a matching test command was
|
|
112
|
+
* *recorded* after the last write and would happily pass a transcript
|
|
113
|
+
* whose latest run FAILED. This rule additionally judges the recorded
|
|
114
|
+
* run's output: the last matching test command after the last write
|
|
115
|
+
* must look green (`successPattern` present, `failurePattern` absent).
|
|
116
|
+
*
|
|
117
|
+
* Inherent limit (unchanged from requireCommand): the gate sees only
|
|
118
|
+
* the session transcript. A green run in another terminal, CI, or a
|
|
119
|
+
* wrapper script is invisible — rerun the suite in-session, and keep
|
|
120
|
+
* the CI mirror for human commits.
|
|
121
|
+
*
|
|
122
|
+
* Applies to: command actions matching `git commit`. Deterministic —
|
|
123
|
+
* no AI call.
|
|
124
|
+
*
|
|
125
|
+
* @param options.command — regex matching a test invocation (e.g.
|
|
126
|
+
* {@link GRADLE_TEST_COMMAND}).
|
|
127
|
+
* @param options.successPattern — output must match to count as green
|
|
128
|
+
* (default `/BUILD SUCCESSFUL/`).
|
|
129
|
+
* @param options.failurePattern — output matching this is red even if
|
|
130
|
+
* the success pattern also appears (default `/FAILED|BUILD FAILED/`).
|
|
131
|
+
*/
|
|
132
|
+
export declare function requireGreenTestRun(options: {
|
|
133
|
+
command: RegExp;
|
|
134
|
+
successPattern?: RegExp;
|
|
135
|
+
failurePattern?: RegExp;
|
|
136
|
+
enforceForPaths?: RegExp;
|
|
137
|
+
listCommitFiles?: (command: string) => string[];
|
|
138
|
+
reason?: string;
|
|
139
|
+
}): Rule;
|
|
140
|
+
/**
|
|
141
|
+
* Deterministic fast-path for the write the observability rules
|
|
142
|
+
* encourage: adding telemetry to existing code. A `.kt`/`.kts` write
|
|
143
|
+
* whose entire delta is ADDED lines, every one a complete single-line
|
|
144
|
+
* telemetry call (`logger.event(...)`, `breadcrumbs.action/outcome`),
|
|
145
|
+
* passes without consulting the wrapped rule — no AI call, and no
|
|
146
|
+
* "over-implementation" friction from a TDD gate for instrumentation
|
|
147
|
+
* the adapter-observability rule demands anyway. Anything else — a
|
|
148
|
+
* removed/changed line, a multi-line event call, any non-telemetry
|
|
149
|
+
* addition — falls through unchanged.
|
|
150
|
+
*
|
|
151
|
+
* Wrap it around both sides of the tension: the TDD rule (so
|
|
152
|
+
* telemetry additions aren't judged as unasserted behavior) and
|
|
153
|
+
* `enforceAdapterObservability` (a telemetry-only addition trivially
|
|
154
|
+
* satisfies it).
|
|
155
|
+
*/
|
|
156
|
+
export declare function withTelemetryFastPath(rule: Rule, options?: {
|
|
157
|
+
patterns?: RegExp[];
|
|
158
|
+
filePattern?: RegExp;
|
|
159
|
+
}): Rule;
|
|
160
|
+
/**
|
|
161
|
+
* Marker that declares a write a mutation probe: a deliberate,
|
|
162
|
+
* temporary break of production behavior made to prove a retrofitted
|
|
163
|
+
* test can fail (see acceptance-testing's mutation-check step). Put
|
|
164
|
+
* it in a comment on or near the mutated line:
|
|
165
|
+
*
|
|
166
|
+
* // probity: mutation-probe — proving RetrySpec bites; revert before commit
|
|
167
|
+
*/
|
|
168
|
+
export declare const MUTATION_PROBE_MARKER: RegExp;
|
|
169
|
+
/**
|
|
170
|
+
* Wraps a TDD rule so that a write carrying the
|
|
171
|
+
* {@link MUTATION_PROBE_MARKER} passes deterministically — no AI call,
|
|
172
|
+
* no red-before-green demand. Mutation checks (deliberately breaking
|
|
173
|
+
* production code to prove a retrofitted test fails) are *mandated* by
|
|
174
|
+
* the acceptance-testing skill, and an unwrapped `enforceTdd`
|
|
175
|
+
* correctly denies them: a deliberate regression has no failing test
|
|
176
|
+
* in front of it and never will. Without this wrapper the only way to
|
|
177
|
+
* run a mutation check is to override the gate — which trains agents
|
|
178
|
+
* and humans to ignore deny decisions.
|
|
179
|
+
*
|
|
180
|
+
* The bypass is not free: pair this with {@link enforceProbeReversion}
|
|
181
|
+
* so `git commit` is blocked while any probe marker is still on disk.
|
|
182
|
+
* The pair converts an override into an enforced round-trip: mark →
|
|
183
|
+
* watch the test fail → revert (the marker disappears with the
|
|
184
|
+
* mutation) → commit opens again. Removing just the marker while
|
|
185
|
+
* keeping the mutation is a fresh unmarked production write, judged by
|
|
186
|
+
* the wrapped TDD rule as usual.
|
|
187
|
+
*
|
|
188
|
+
* Only the TDD rule is bypassed. Deterministic screens (vendor
|
|
189
|
+
* imports, ambient effects) and the boundary validator still apply to
|
|
190
|
+
* probe writes — a probe has no business introducing those.
|
|
191
|
+
*
|
|
192
|
+
* @param rule — the rule to wrap, normally
|
|
193
|
+
* `withKotlinFastPath(enforceTdd())`.
|
|
194
|
+
*/
|
|
195
|
+
export declare function withMutationProbe(rule: Rule): Rule;
|
|
196
|
+
/**
|
|
197
|
+
* Wraps a TDD rule so that a violation on a write to the project's
|
|
198
|
+
* test-control layer — acceptance composition roots, fixture/fake
|
|
199
|
+
* registrations — carries the inverse-scenario escape route in its
|
|
200
|
+
* deny text.
|
|
201
|
+
*
|
|
202
|
+
* Why: on brownfield systems the environment often produces the sad
|
|
203
|
+
* path for free (a simulator with no backend fails every sign-in), so
|
|
204
|
+
* a sad-path scenario never goes red and an unwrapped TDD gate appears
|
|
205
|
+
* to "refuse" the control fixture. The observed failure mode is the
|
|
206
|
+
* agent resolving that tension in the wrong direction — deleting the
|
|
207
|
+
* control and letting the environment own the Given. The deny message
|
|
208
|
+
* is what the agent reads at that decision point, so the correct move
|
|
209
|
+
* (write the inverse scenario; its red drives the fixture) must be
|
|
210
|
+
* stated there, not only in the skill prose.
|
|
211
|
+
*
|
|
212
|
+
* Pass-through everywhere else: verdicts are unchanged, only the
|
|
213
|
+
* violation reason on matching paths gains a guidance paragraph — and
|
|
214
|
+
* only the paragraph that applies. The inverse-scenario note is
|
|
215
|
+
* appended when the denial is about a missing red (or the write
|
|
216
|
+
* removes existing content — the deletion temptation); a denial citing
|
|
217
|
+
* an undefined symbol gets the atomic-fixture hint instead (a
|
|
218
|
+
* multi-part fixture judged piecewise). Other denials pass through
|
|
219
|
+
* untouched, so the guidance never reads as boilerplate.
|
|
220
|
+
*
|
|
221
|
+
* @param rule — the TDD rule to wrap (already wrapped in fast-paths /
|
|
222
|
+
* mutation-probe as usual).
|
|
223
|
+
* @param options.filePattern — paths that hold test-control
|
|
224
|
+
* infrastructure (e.g. /App[/\\]Sources[/\\]Acceptance[/\\]/ or a
|
|
225
|
+
* fixtures directory).
|
|
226
|
+
*/
|
|
227
|
+
export declare function withInverseScenarioGuidance(rule: Rule, options: {
|
|
228
|
+
filePattern: RegExp;
|
|
229
|
+
}): Rule;
|
|
230
|
+
/**
|
|
231
|
+
* The commit half of the mutation-probe round-trip (see
|
|
232
|
+
* {@link withMutationProbe}): blocks `git commit` while any source
|
|
233
|
+
* file under `roots` still contains the probe marker, listing the
|
|
234
|
+
* files. Reverting the mutation (e.g. `git checkout -- <file>`)
|
|
235
|
+
* removes the marker with it, so a clean tree needs no bookkeeping.
|
|
236
|
+
* Deterministic filesystem scan — no AI call.
|
|
237
|
+
*
|
|
238
|
+
* Applies to: command actions matching `git commit`.
|
|
239
|
+
*
|
|
240
|
+
* @param options.roots — absolute paths to scan (the repo root is
|
|
241
|
+
* fine; node_modules/build dirs are skipped).
|
|
242
|
+
* @param options.filePattern — which files can carry probes
|
|
243
|
+
* (default: `.kt`/`.kts`/`.java`).
|
|
244
|
+
*/
|
|
245
|
+
export declare function enforceProbeReversion(options: {
|
|
246
|
+
roots: string[];
|
|
247
|
+
filePattern?: RegExp;
|
|
248
|
+
}): Rule;
|
|
249
|
+
/**
|
|
250
|
+
* Marker that declares a test a characterization test: the first test
|
|
251
|
+
* for behavior that already exists in production, so no natural red
|
|
252
|
+
* can precede it (the test is born green). Put it in a comment on or
|
|
253
|
+
* directly above the test function:
|
|
254
|
+
*
|
|
255
|
+
* // probity: characterization
|
|
256
|
+
* func testSignedInUserWithoutAnEntitlementIsOfferedPlans() async { … }
|
|
257
|
+
*/
|
|
258
|
+
export declare const CHARACTERIZATION_MARKER: RegExp;
|
|
259
|
+
/**
|
|
260
|
+
* Wraps a TDD rule to sanction the **characterization round-trip** —
|
|
261
|
+
* the only honest way to add the FIRST test for behavior that predates
|
|
262
|
+
* it (common on brownfield systems). A test for existing behavior is
|
|
263
|
+
* born green, so no red keyed to it can be observed before it exists;
|
|
264
|
+
* an unwrapped TDD gate correctly denies it, and a mutation probe
|
|
265
|
+
* can't help yet because a probe only fails tests that already exist.
|
|
266
|
+
* Without this wrapper the only ways out are an override or leaving
|
|
267
|
+
* the behavior unspecified.
|
|
268
|
+
*
|
|
269
|
+
* The round-trip, each step enforced:
|
|
270
|
+
*
|
|
271
|
+
* 1. Write the test carrying {@link CHARACTERIZATION_MARKER} — this
|
|
272
|
+
* wrapper passes it deterministically (test-layer paths only).
|
|
273
|
+
* 2. Run the suite green, then mutation-probe the production path
|
|
274
|
+
* (`// probity: mutation-probe`) and observe the new test FAIL —
|
|
275
|
+
* the recorded red is the proof the test bites.
|
|
276
|
+
* 3. Revert the probe. Remove the characterization marker — this
|
|
277
|
+
* wrapper allows the removal only when the session transcript
|
|
278
|
+
* records a test run in which the marked test failed.
|
|
279
|
+
* 4. Commit — {@link enforceCharacterizationResolution} blocks while
|
|
280
|
+
* any marker is still on disk, so an unproven characterization
|
|
281
|
+
* test can't land.
|
|
282
|
+
*
|
|
283
|
+
* The bypass is confined: only writes to paths matching
|
|
284
|
+
* `options.filePattern` (the test layer) skip the wrapped rule, so a
|
|
285
|
+
* production write can't borrow the marker. Same inherent limit as
|
|
286
|
+
* every transcript gate: reds observed in another terminal or CI are
|
|
287
|
+
* invisible — run the probe in-session.
|
|
288
|
+
*
|
|
289
|
+
* @param rule — the TDD rule to wrap (fast-paths/probe wrappers
|
|
290
|
+
* included as usual).
|
|
291
|
+
* @param options.filePattern — paths that hold test code (e.g.
|
|
292
|
+
* /AcceptanceTests[/\\]/ or /src[/\\]\w+Test[/\\]/). Required: it is
|
|
293
|
+
* the boundary that keeps the marker useless in production files.
|
|
294
|
+
*/
|
|
295
|
+
export declare function withCharacterizationTest(rule: Rule, options: {
|
|
296
|
+
filePattern: RegExp;
|
|
297
|
+
}): Rule;
|
|
298
|
+
/**
|
|
299
|
+
* The commit half of the characterization round-trip (see
|
|
300
|
+
* {@link withCharacterizationTest}): blocks `git commit` while any
|
|
301
|
+
* test file under `roots` still carries the characterization marker —
|
|
302
|
+
* the marker only comes off through the proof-checked removal path,
|
|
303
|
+
* so a characterization test that has never been observed failing
|
|
304
|
+
* cannot land. Deterministic filesystem scan — no AI call.
|
|
305
|
+
*
|
|
306
|
+
* Applies to: command actions matching `git commit`.
|
|
307
|
+
*
|
|
308
|
+
* @param options.roots — absolute paths to scan.
|
|
309
|
+
* @param options.filePattern — which files can carry the marker
|
|
310
|
+
* (default: `.kt`/`.kts`/`.java`).
|
|
311
|
+
*/
|
|
312
|
+
export declare function enforceCharacterizationResolution(options: {
|
|
313
|
+
roots: string[];
|
|
314
|
+
filePattern?: RegExp;
|
|
315
|
+
}): Rule;
|
|
316
|
+
/**
|
|
317
|
+
* Kotlin/Android addendum for `enforcePortsBoundary` — pass as
|
|
318
|
+
* `enforcePortsBoundary({ instructions: (d) => d + KOTLIN_BOUNDARY_ADDENDUM })`
|
|
319
|
+
* and extend the "Project layout" section with your module/package
|
|
320
|
+
* conventions.
|
|
321
|
+
*/
|
|
322
|
+
export declare const KOTLIN_BOUNDARY_ADDENDUM = "\n\n### Kotlin/Android specifics\n\n - Ambient OS access in core code is a violation: `Instant.now()`\n and friends, `System.currentTimeMillis()`, `Date()`,\n `UUID.randomUUID()`, `Random()`, `System.getenv` \u2014 clock,\n randomness, and environment are ports.\n - Vendor/infrastructure packages (AWS SDK, Amplify, Apollo,\n Firebase, OkHttp, Retrofit, Room, WorkManager, JDBC) belong in\n adapter modules only. In core code, their types in signatures are\n leaked boundaries.\n - Dagger/DI modules, `@Component` definitions, and `\u2026di` packages\n are composition roots: they import both core and adapters by\n design \u2014 always allowed.\n - In tests, `mockStatic`/`mockkStatic`/`mockkObject`/\n `mockkConstructor`/PowerMock are always violations. A\n mockito-kotlin `mock<T>()` where T is a port interface is an\n acceptable seam (though a shared fake is preferred); `mock<T>()`\n of a concrete internal class is a violation \u2014 the substitute\n belongs at a port.\n - Robolectric in a test signals Android-framework coupling; that is\n an adapter concern, fine in adapter/UI tests, a smell in tests of\n core logic.\n - A port need not be an interface. A function-typed constructor\n parameter injected at the composition root (e.g.\n `nowEpochMillis: () -> Long`, `randomIv: () -> ByteArray`) is a\n valid seam \u2014 but a default value that calls the real OS\n (`= { System.currentTimeMillis() }`) inside core/common code\n defeats it; real defaults belong in platform adapters or DI\n wiring.\n - Kotlin Multiplatform: `commonMain` core code is the inside of\n the hexagon; `expect`/`actual` pairs and per-platform source\n sets (`androidMain`, `iosMain`, `desktopMain`) implementing a\n common declaration are adapters and may touch platform APIs.";
|
|
323
|
+
//# sourceMappingURL=kotlin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kotlin.d.ts","sourceRoot":"","sources":["../../rules/kotlin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAU,IAAI,EAA2B,MAAM,gBAAgB,CAAA;AAE3E,OAAO,EAIL,KAAK,YAAY,EAClB,MAAM,YAAY,CAAA;AAEnB;;;;;;;;;GASG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,QACoV,CAAA;AAE9X;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,QACoB,CAAA;AAExD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,QAAqD,CAAA;AAsBrF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAsBxC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAA;CAAO,GAC7D,IAAI,CAON;AAmDD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,IAAI,EACV,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;CAAO,GACrC,IAAI,CAiCN;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC3C,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,EAAE,CAAA;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,IAAI,CAWP;AAUD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,IAAI,EACV,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GAC1D,IAAI,CAmCN;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,QAA8B,CAAA;AA+BhE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAclD;AAmCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,GAC/B,IAAI,CAqCN;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE;IAC7C,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,IAAI,CA6BP;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,QAAkC,CAAA;AA8BtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,GAC/B,IAAI,CA4DN;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,EAAE;IACzD,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,IAAI,CA+BP;AAED;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,0zDAkC4B,CAAA"}
|