@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.
Files changed (71) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/GLOSSARY.template.md +33 -0
  3. package/README.md +96 -0
  4. package/dist/index.d.ts +20 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +20 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/presets/js.d.ts +63 -0
  9. package/dist/presets/js.d.ts.map +1 -0
  10. package/dist/presets/js.js +112 -0
  11. package/dist/presets/js.js.map +1 -0
  12. package/dist/presets/kmp.d.ts +27 -0
  13. package/dist/presets/kmp.d.ts.map +1 -0
  14. package/dist/presets/kmp.js +253 -0
  15. package/dist/presets/kmp.js.map +1 -0
  16. package/dist/presets/kotlin.d.ts +40 -0
  17. package/dist/presets/kotlin.d.ts.map +1 -0
  18. package/dist/presets/kotlin.js +172 -0
  19. package/dist/presets/kotlin.js.map +1 -0
  20. package/dist/presets/swift.d.ts +10 -0
  21. package/dist/presets/swift.d.ts.map +1 -0
  22. package/dist/presets/swift.js +285 -0
  23. package/dist/presets/swift.js.map +1 -0
  24. package/dist/rules/acceptance-language.d.ts +95 -0
  25. package/dist/rules/acceptance-language.d.ts.map +1 -0
  26. package/dist/rules/acceptance-language.js +443 -0
  27. package/dist/rules/acceptance-language.js.map +1 -0
  28. package/dist/rules/gates.d.ts +125 -0
  29. package/dist/rules/gates.d.ts.map +1 -0
  30. package/dist/rules/gates.js +285 -0
  31. package/dist/rules/gates.js.map +1 -0
  32. package/dist/rules/kotlin.d.ts +323 -0
  33. package/dist/rules/kotlin.d.ts.map +1 -0
  34. package/dist/rules/kotlin.js +722 -0
  35. package/dist/rules/kotlin.js.map +1 -0
  36. package/dist/rules/ports-and-adapters.d.ts +86 -0
  37. package/dist/rules/ports-and-adapters.d.ts.map +1 -0
  38. package/dist/rules/ports-and-adapters.js +366 -0
  39. package/dist/rules/ports-and-adapters.js.map +1 -0
  40. package/dist/rules/scoping.d.ts +68 -0
  41. package/dist/rules/scoping.d.ts.map +1 -0
  42. package/dist/rules/scoping.js +93 -0
  43. package/dist/rules/scoping.js.map +1 -0
  44. package/dist/rules/spec-test-parity.d.ts +164 -0
  45. package/dist/rules/spec-test-parity.d.ts.map +1 -0
  46. package/dist/rules/spec-test-parity.js +456 -0
  47. package/dist/rules/spec-test-parity.js.map +1 -0
  48. package/dist/rules/swift.d.ts +50 -0
  49. package/dist/rules/swift.d.ts.map +1 -0
  50. package/dist/rules/swift.js +50 -0
  51. package/dist/rules/swift.js.map +1 -0
  52. package/dist/rules/ubiquitous-language.d.ts +36 -0
  53. package/dist/rules/ubiquitous-language.d.ts.map +1 -0
  54. package/dist/rules/ubiquitous-language.js +140 -0
  55. package/dist/rules/ubiquitous-language.js.map +1 -0
  56. package/dist/scripts/scope-report.d.ts +3 -0
  57. package/dist/scripts/scope-report.d.ts.map +1 -0
  58. package/dist/scripts/scope-report.js +184 -0
  59. package/dist/scripts/scope-report.js.map +1 -0
  60. package/kiro/README.md +20 -0
  61. package/kiro/kiro-agent.template.json +45 -0
  62. package/kiro/kiro-transcript-to-claude.py +183 -0
  63. package/kiro/probity-kiro-translate.py +132 -0
  64. package/kiro/probity-kiro.sh +87 -0
  65. package/kiro/skill-activation-forced-eval.sh +45 -0
  66. package/package.json +68 -0
  67. package/probity.config.kmp.ts +40 -0
  68. package/probity.config.kotlin.ts +37 -0
  69. package/probity.config.swift.ts +38 -0
  70. package/probity.config.ts +43 -0
  71. 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"}