@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,443 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
const DEFAULT_MAX_GLOSSARY_CHARS = 8000;
|
|
4
|
+
const STRICT_VOCABULARY = `### Vocabulary — strict mode (overrides the leniency above)
|
|
5
|
+
|
|
6
|
+
This project enforces "the glossary conversation happens first": when
|
|
7
|
+
a glossary is provided, spec content that introduces a domain concept
|
|
8
|
+
with NO glossary entry IS a violation. Name the missing term(s) in
|
|
9
|
+
the reason so the entry can be added before the spec lands. Judge
|
|
10
|
+
concepts, not words — articles, generic verbs, and quantities need no
|
|
11
|
+
entry; nouns that carry domain meaning do.
|
|
12
|
+
|
|
13
|
+
`;
|
|
14
|
+
// reason comes FIRST in the shape: an autoregressive model commits to
|
|
15
|
+
// each field in order, so kind-first forces the verdict before the
|
|
16
|
+
// analysis — observed live producing a "violation" whose reason
|
|
17
|
+
// reasoned its way to "Passing." Reason-first lets the model conclude,
|
|
18
|
+
// then label.
|
|
19
|
+
const RESPONSE_SPEC = `## Response format
|
|
20
|
+
|
|
21
|
+
Respond with a single JSON object of exactly this shape:
|
|
22
|
+
{"reason":"<your analysis>","kind":"pass"|"violation"}
|
|
23
|
+
Write reason FIRST and set kind to the conclusion your reason reached —
|
|
24
|
+
the two must agree. Keep reason brief on a clear pass ("" is fine).
|
|
25
|
+
Return JSON only. No prose, no code fences.`;
|
|
26
|
+
const PROCESS_INSTRUCTIONS = `## Role
|
|
27
|
+
|
|
28
|
+
You are an acceptance-specification validator. Judge whether the
|
|
29
|
+
pending write keeps executable specifications in the language of the
|
|
30
|
+
problem domain, per the rules below.
|
|
31
|
+
|
|
32
|
+
## Inputs
|
|
33
|
+
|
|
34
|
+
You will see up to four inputs:
|
|
35
|
+
|
|
36
|
+
1. "Ubiquitous language glossary" (optional) — the project's agreed
|
|
37
|
+
domain vocabulary.
|
|
38
|
+
2. "Current file content" — what's on disk right now at the file the
|
|
39
|
+
agent is about to write. May be a parenthesized marker (e.g.
|
|
40
|
+
\`(file does not exist)\`).
|
|
41
|
+
3. "Pending action" — the file path and what the agent is about to
|
|
42
|
+
write. Content may be raw file text or a patch/diff.
|
|
43
|
+
|
|
44
|
+
## What you judge
|
|
45
|
+
|
|
46
|
+
Judge only the specification content this write adds or changes, not
|
|
47
|
+
pre-existing text it leaves untouched. This rule is scoped to
|
|
48
|
+
specification files (layer 1 of the four-layer model). If the file is
|
|
49
|
+
clearly NOT a test-case layer — it is a DSL implementation, a protocol
|
|
50
|
+
driver, a step-definition file that only parses and delegates, or
|
|
51
|
+
plain production code — pass: those layers are supposed to know about
|
|
52
|
+
UI mechanics and protocols.
|
|
53
|
+
|
|
54
|
+
A transient file state (unresolved import, half-finished scenario) is
|
|
55
|
+
never itself a violation.
|
|
56
|
+
|
|
57
|
+
A block recorded earlier in the session is a past verdict, not a rule.
|
|
58
|
+
Re-derive your judgment from the rules below. When the user tells you
|
|
59
|
+
in the session to let this change through, treat it as authoritative
|
|
60
|
+
and pass.`;
|
|
61
|
+
const DEFAULT_LANGUAGE_RULES = `## Specification language rules
|
|
62
|
+
|
|
63
|
+
An acceptance test is an executable specification: a concrete example,
|
|
64
|
+
in the language of the problem domain, that demonstrates a user's need
|
|
65
|
+
is met. It states WHAT the system does for an external user and says
|
|
66
|
+
nothing about HOW the system works.
|
|
67
|
+
|
|
68
|
+
### The Language Test
|
|
69
|
+
|
|
70
|
+
The least technical person who understands the problem domain must be
|
|
71
|
+
able to read the specification and confirm it says what they want.
|
|
72
|
+
Block spec content that mentions implementation mechanics:
|
|
73
|
+
|
|
74
|
+
- UI mechanics: clicking, typing into fields, pages, screens,
|
|
75
|
+
buttons, menus, CSS selectors, element IDs, XPath
|
|
76
|
+
- Protocol mechanics: URLs, endpoints, HTTP verbs, status codes,
|
|
77
|
+
JSON/XML payloads, headers, cookies
|
|
78
|
+
- Persistence mechanics: tables, rows, columns, SQL, cache keys
|
|
79
|
+
- Named internal services, queues, or modules
|
|
80
|
+
|
|
81
|
+
The fix is always the same: state the outcome in domain terms and push
|
|
82
|
+
the mechanics down into the DSL or a protocol driver.
|
|
83
|
+
|
|
84
|
+
### One standard across artifacts
|
|
85
|
+
|
|
86
|
+
A Markdown/Gherkin scenario and the test case that claims it are both
|
|
87
|
+
layer 1 and are held to the SAME standard: wording that would violate
|
|
88
|
+
in a test-case step ("the backend rejects creation") violates in a
|
|
89
|
+
\`## Scenario:\` step too, and vice versa. Never pass mechanism
|
|
90
|
+
language in a spec document on the grounds that it is prose — the
|
|
91
|
+
spec is the source that tests transcribe, so a leak passed there
|
|
92
|
+
resurfaces in every claiming test. Named internal actors — "the
|
|
93
|
+
backend", "the server", "the gateway", "the database", "the API",
|
|
94
|
+
"a queue", "the store" — are mechanism unless the glossary records
|
|
95
|
+
them as domain concepts: state the condition as the user experiences
|
|
96
|
+
it ("creation fails", "the parcel cannot be registered right now")
|
|
97
|
+
and push what failed and why into the DSL, driver, or stub
|
|
98
|
+
programming.
|
|
99
|
+
|
|
100
|
+
### Structure
|
|
101
|
+
|
|
102
|
+
- Each specification asserts a single outcome. Block scenarios with
|
|
103
|
+
long When/Then chains asserting many unrelated outcomes.
|
|
104
|
+
- Express specifications as outcomes ("should ...", "is granted
|
|
105
|
+
access"), not procedures.
|
|
106
|
+
- No sleeps or fixed waits in specs — a delay is a race condition
|
|
107
|
+
with a timer on it; drivers poll for the concluding event.
|
|
108
|
+
- Test-case code talks only to the DSL. Block spec-layer code that
|
|
109
|
+
reaches the system directly (HTTP clients, page objects, database
|
|
110
|
+
handles imported into the spec file).
|
|
111
|
+
|
|
112
|
+
### Vocabulary
|
|
113
|
+
|
|
114
|
+
When a glossary is provided, use its terms verbatim — one term per
|
|
115
|
+
concept. Block spec content that names a domain concept with a term
|
|
116
|
+
that conflicts with the glossary (a synonym or a redefinition). A
|
|
117
|
+
concept the glossary simply doesn't cover yet is NOT a violation on
|
|
118
|
+
its own; mention it in the reason only alongside a real violation.
|
|
119
|
+
|
|
120
|
+
%STRICT_VOCABULARY%### Judgment bar
|
|
121
|
+
|
|
122
|
+
Block on clear mechanics leaking into a specification. Domain terms
|
|
123
|
+
that happen to sound technical (the domain of a deployment tool
|
|
124
|
+
includes "server"; a payments domain includes "card") are not
|
|
125
|
+
violations — judge against the problem domain, not a banned-word list.
|
|
126
|
+
Grammatical tense and phrasing preferences are not mechanism: a
|
|
127
|
+
future- or conditional-tense precondition ("creation will fail",
|
|
128
|
+
"the parcel is going to be rejected") is equivalent to its
|
|
129
|
+
present-tense form and never blocks on its own — and when a step does
|
|
130
|
+
violate, name only the offending phrase, not neighboring wording that
|
|
131
|
+
merely reads awkwardly. When genuinely ambiguous, pass.`;
|
|
132
|
+
const PRECONDITION_PROCESS_INSTRUCTIONS = `## Role
|
|
133
|
+
|
|
134
|
+
You are an acceptance-test precondition validator. Judge whether the
|
|
135
|
+
pending write keeps scenario preconditions CONTROLLED BY THE TEST
|
|
136
|
+
rather than inherited from the ambient environment.
|
|
137
|
+
|
|
138
|
+
## Inputs
|
|
139
|
+
|
|
140
|
+
You will see two inputs:
|
|
141
|
+
|
|
142
|
+
1. "Current file content" — what's on disk right now at the file the
|
|
143
|
+
agent is about to write. May be a parenthesized marker (e.g.
|
|
144
|
+
\`(file does not exist)\`).
|
|
145
|
+
2. "Pending action" — the file path and what the agent is about to
|
|
146
|
+
write. Content may be raw file text or a patch/diff.
|
|
147
|
+
|
|
148
|
+
## What you judge
|
|
149
|
+
|
|
150
|
+
Judge only the change this write makes. The scope is layer 3 and the
|
|
151
|
+
test-control layer of the four-layer acceptance model: protocol
|
|
152
|
+
drivers, port fakes, fixture wiring, and acceptance composition
|
|
153
|
+
roots. A transient file state (unresolved symbol, half-finished
|
|
154
|
+
multi-step change) is never itself a violation — a driver method may
|
|
155
|
+
legitimately be written before the fixture it will set, so block only
|
|
156
|
+
when the write itself declares the environmental dependency (a no-op
|
|
157
|
+
body presented as done, a comment saying the environment covers it,
|
|
158
|
+
or the removal of existing control).
|
|
159
|
+
|
|
160
|
+
A block recorded earlier in the session is a past verdict, not a
|
|
161
|
+
rule. Re-derive your judgment from the rules below. When the user
|
|
162
|
+
tells you in the session to let this change through, treat it as
|
|
163
|
+
authoritative and pass.`;
|
|
164
|
+
const PRECONDITION_RULES = `## Controlled-precondition rules
|
|
165
|
+
|
|
166
|
+
Every Given of an executable specification must be ESTABLISHED by the
|
|
167
|
+
test — through a fixture key, launch environment, programmed stub, or
|
|
168
|
+
substituted port — never satisfied by whatever the environment
|
|
169
|
+
happens to do (no network, no credentials, empty state, an
|
|
170
|
+
unreachable backend).
|
|
171
|
+
|
|
172
|
+
Block:
|
|
173
|
+
|
|
174
|
+
- **The no-op precondition driver.** A driver method whose name or
|
|
175
|
+
doc comment states a precondition ("whose sign-in will fail",
|
|
176
|
+
"with an expired subscription", "while offline") but whose body
|
|
177
|
+
establishes no state: it only navigates, launches, or asserts,
|
|
178
|
+
with nothing that configures a fixture, stub, launch environment,
|
|
179
|
+
or fake. Especially when a comment admits the environment covers
|
|
180
|
+
it ("fails in the simulator anyway").
|
|
181
|
+
- **Deleting control because the test is green without it.** A
|
|
182
|
+
write that removes fixture wiring, a fake registration, or a
|
|
183
|
+
fixture key on the grounds that the scenario already passes. The
|
|
184
|
+
correct move is the inverse scenario: the spec for the other side
|
|
185
|
+
of the same port (the success path when only failure happens for
|
|
186
|
+
free) is genuinely red and legitimately drives the fixture; the
|
|
187
|
+
original scenario then adopts the explicit fixture under green.
|
|
188
|
+
Name this route in the reason when blocking.
|
|
189
|
+
|
|
190
|
+
Do NOT block:
|
|
191
|
+
|
|
192
|
+
- Driver methods whose names state actions or observations rather
|
|
193
|
+
than preconditions.
|
|
194
|
+
- Preconditions genuinely established elsewhere and visibly reached
|
|
195
|
+
from this body (a shared helper that sets the fixture, a base
|
|
196
|
+
launch method the diff calls into).
|
|
197
|
+
- Refactors that move control without removing it.
|
|
198
|
+
- Removal of control the user has explicitly directed in the
|
|
199
|
+
session.
|
|
200
|
+
|
|
201
|
+
When genuinely ambiguous — the method name is vague, or you cannot
|
|
202
|
+
tell whether a called helper establishes the state — pass.`;
|
|
203
|
+
function formatBefore(before) {
|
|
204
|
+
switch (before.kind) {
|
|
205
|
+
case 'present':
|
|
206
|
+
return before.content;
|
|
207
|
+
case 'absent':
|
|
208
|
+
return '(file does not exist)';
|
|
209
|
+
case 'unknown':
|
|
210
|
+
return '(current file content unavailable)';
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function truncate(text, maxChars) {
|
|
214
|
+
if (text.length <= maxChars)
|
|
215
|
+
return text;
|
|
216
|
+
return `${text.slice(0, maxChars)}\n(...glossary truncated...)`;
|
|
217
|
+
}
|
|
218
|
+
function buildPrompt(rules, glossary, before, action) {
|
|
219
|
+
const sections = [PROCESS_INSTRUCTIONS, rules];
|
|
220
|
+
if (glossary) {
|
|
221
|
+
sections.push(`## Ubiquitous language glossary\n\n${glossary}`);
|
|
222
|
+
}
|
|
223
|
+
sections.push(`## Current file content\n\n${formatBefore(before)}`);
|
|
224
|
+
sections.push(`## Pending action\n\nFile: ${action.path}\n\n${action.content}`);
|
|
225
|
+
sections.push(RESPONSE_SPEC);
|
|
226
|
+
return sections.join('\n\n');
|
|
227
|
+
}
|
|
228
|
+
// Deterministic screen used by the fast-path: mechanism words that
|
|
229
|
+
// domain-language test steps should never contain. Deliberately
|
|
230
|
+
// broad — a false hit just falls through to the AI validator.
|
|
231
|
+
const MECHANISM_SCREEN = /\b(?:backend|server|gateway|database|sql|endpoint|https?|url|api|queue|json|xml|payload|click|button|css|xpath|selector|cookie|header|mock)\b/i;
|
|
232
|
+
const KOTLIN_CALL_KEYWORDS = new Set([
|
|
233
|
+
'fun',
|
|
234
|
+
'if',
|
|
235
|
+
'when',
|
|
236
|
+
'while',
|
|
237
|
+
'for',
|
|
238
|
+
'catch',
|
|
239
|
+
'return',
|
|
240
|
+
'super',
|
|
241
|
+
'this',
|
|
242
|
+
'Test',
|
|
243
|
+
]);
|
|
244
|
+
const DECLARATION = /(?:fun|class|object|interface)\s+`?([A-Za-z_]\w*)/g;
|
|
245
|
+
function declaredNames(content, out) {
|
|
246
|
+
for (const match of content.matchAll(DECLARATION))
|
|
247
|
+
out.add(match[1]);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Wraps `enforceAcceptanceLanguage` so the most common spec-layer
|
|
251
|
+
* write — a Kotlin test file gaining exactly one new `@Test` whose
|
|
252
|
+
* added lines only call vocabulary that already exists in the
|
|
253
|
+
* suite's DSL/Robot/driver files — passes deterministically, with no
|
|
254
|
+
* model call. Rationale: when two rule scopes overlap (the TDD rule's
|
|
255
|
+
* Kotlin fast-path plus this rule on `acceptance/**`), a single-test
|
|
256
|
+
* write otherwise still pays one AI call despite being advertised as
|
|
257
|
+
* free.
|
|
258
|
+
*
|
|
259
|
+
* The fast-path is conservative on three axes; failing any one falls
|
|
260
|
+
* through to the wrapped AI rule (it can only skip work, never
|
|
261
|
+
* block):
|
|
262
|
+
* 1. the write must add exactly one `@Test`;
|
|
263
|
+
* 2. no added line may contain mechanism vocabulary
|
|
264
|
+
* (backend/server/database/url/click/... — the deterministic
|
|
265
|
+
* screen errs broad);
|
|
266
|
+
* 3. every identifier the added lines call must already be declared
|
|
267
|
+
* in this file or a sibling `.kt` file in the same directory —
|
|
268
|
+
* a brand-new DSL step name is exactly what the validator should
|
|
269
|
+
* judge.
|
|
270
|
+
*
|
|
271
|
+
* Markdown/Gherkin spec writes never fast-path — prose is where the
|
|
272
|
+
* Language Test earns its keep.
|
|
273
|
+
*/
|
|
274
|
+
export function withAcceptanceLanguageFastPath(rule) {
|
|
275
|
+
const wrapped = async function acceptanceLanguageFastPath(action, ctx) {
|
|
276
|
+
if (action.kind !== 'write' || !/\.kt$/.test(action.path)) {
|
|
277
|
+
return rule(action, ctx);
|
|
278
|
+
}
|
|
279
|
+
const before = await ctx?.readFile?.(action.path);
|
|
280
|
+
if (!before || before.kind === 'unknown')
|
|
281
|
+
return rule(action, ctx);
|
|
282
|
+
const beforeText = before.kind === 'present' ? before.content : '';
|
|
283
|
+
const testDelta = (action.content.match(/@Test\b/g)?.length ?? 0) -
|
|
284
|
+
(beforeText.match(/@Test\b/g)?.length ?? 0);
|
|
285
|
+
if (testDelta !== 1)
|
|
286
|
+
return rule(action, ctx);
|
|
287
|
+
const beforeLines = new Set(beforeText.split('\n').map((line) => line.trim()));
|
|
288
|
+
const added = action.content
|
|
289
|
+
.split('\n')
|
|
290
|
+
.map((line) => line.trim())
|
|
291
|
+
.filter((line) => line.length > 0 && !beforeLines.has(line));
|
|
292
|
+
if (added.some((line) => MECHANISM_SCREEN.test(line))) {
|
|
293
|
+
return rule(action, ctx);
|
|
294
|
+
}
|
|
295
|
+
const known = new Set();
|
|
296
|
+
declaredNames(action.content, known);
|
|
297
|
+
try {
|
|
298
|
+
for (const entry of readdirSync(dirname(action.path))) {
|
|
299
|
+
if (!entry.endsWith('.kt'))
|
|
300
|
+
continue;
|
|
301
|
+
declaredNames(readFileSync(join(dirname(action.path), entry), 'utf8'), known);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return rule(action, ctx);
|
|
306
|
+
}
|
|
307
|
+
for (const line of added) {
|
|
308
|
+
for (const call of line.matchAll(/([A-Za-z_]\w*)\s*\(/g)) {
|
|
309
|
+
const name = call[1];
|
|
310
|
+
if (KOTLIN_CALL_KEYWORDS.has(name))
|
|
311
|
+
continue;
|
|
312
|
+
if (!known.has(name))
|
|
313
|
+
return rule(action, ctx);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return { kind: 'pass', notes: [{ kind: 'fast-path' }] };
|
|
317
|
+
};
|
|
318
|
+
Object.defineProperty(wrapped, 'name', {
|
|
319
|
+
value: `acceptanceLanguageFastPath(${rule.name || 'rule'})`,
|
|
320
|
+
});
|
|
321
|
+
return wrapped;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* AI-validated enforcement of the `acceptance-testing` skill's
|
|
325
|
+
* Language Test: executable specifications stay in domain language —
|
|
326
|
+
* no UI mechanics, protocols, or persistence details — assert a
|
|
327
|
+
* single outcome, and (when a glossary is supplied) use the
|
|
328
|
+
* ubiquitous language verbatim.
|
|
329
|
+
*
|
|
330
|
+
* Applies to: write actions. Scope it with a `{ files, rules }` block
|
|
331
|
+
* to the spec layer only (e.g. `specs/**`, `**\/*.feature`) — the DSL
|
|
332
|
+
* and protocol-driver layers are supposed to contain the mechanics
|
|
333
|
+
* this rule blocks, and every matching write costs an AI call.
|
|
334
|
+
*
|
|
335
|
+
* @param options.glossaryPath — absolute path to the project's
|
|
336
|
+
* ubiquitous-language glossary (see the `ubiquitous-language`
|
|
337
|
+
* skill). Resolve it in the config file, e.g.
|
|
338
|
+
* `fileURLToPath(new URL('./docs/GLOSSARY.md', import.meta.url))`.
|
|
339
|
+
* When set and readable, the glossary is included in the
|
|
340
|
+
* validator's prompt and vocabulary conflicts become violations.
|
|
341
|
+
* @param options.requireGlossaryEntry — strict vocabulary mode: when
|
|
342
|
+
* true (and a glossary is supplied and readable), a domain concept
|
|
343
|
+
* in spec content with no glossary entry becomes a violation —
|
|
344
|
+
* "the glossary conversation happens first" (ubiquitous-language
|
|
345
|
+
* skill). Default false: only conflicts with existing entries
|
|
346
|
+
* violate, so an empty or young glossary doesn't block everything.
|
|
347
|
+
* @param options.instructions — overrides or extends the default
|
|
348
|
+
* language rules text. Pass a string to replace it, or a function
|
|
349
|
+
* `(defaults) => ...` to extend it.
|
|
350
|
+
* @param options.maxGlossaryChars — truncate the glossary beyond this
|
|
351
|
+
* length when building the prompt (default 8000).
|
|
352
|
+
*
|
|
353
|
+
* @example
|
|
354
|
+
* { files: ['specs/**', 'acceptance/**', '**\/*.feature'], rules: [enforceAcceptanceLanguage()] }
|
|
355
|
+
*/
|
|
356
|
+
export function enforceAcceptanceLanguage(options = {}) {
|
|
357
|
+
const baseRules = DEFAULT_LANGUAGE_RULES.replace('%STRICT_VOCABULARY%', options.requireGlossaryEntry ? STRICT_VOCABULARY : '');
|
|
358
|
+
const rules = typeof options.instructions === 'function'
|
|
359
|
+
? options.instructions(baseRules)
|
|
360
|
+
: (options.instructions ?? baseRules);
|
|
361
|
+
const maxGlossaryChars = options.maxGlossaryChars ?? DEFAULT_MAX_GLOSSARY_CHARS;
|
|
362
|
+
return async function enforceAcceptanceLanguage(action, ctx) {
|
|
363
|
+
if (action.kind !== 'write')
|
|
364
|
+
return { kind: 'pass' };
|
|
365
|
+
if (!ctx?.agent) {
|
|
366
|
+
return {
|
|
367
|
+
kind: 'violation',
|
|
368
|
+
reason: 'enforceAcceptanceLanguage: no AI agent available; configure Config.ai or use a vendor that ships one.',
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
let glossary;
|
|
372
|
+
if (options.glossaryPath && ctx.readFile) {
|
|
373
|
+
const file = await ctx.readFile(options.glossaryPath);
|
|
374
|
+
if (file.kind === 'present') {
|
|
375
|
+
glossary = truncate(file.content, maxGlossaryChars);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const before = (await ctx.readFile?.(action.path)) ?? {
|
|
379
|
+
kind: 'unknown',
|
|
380
|
+
};
|
|
381
|
+
const verdict = await ctx.agent.reason(buildPrompt(rules, glossary, before, action));
|
|
382
|
+
if (verdict.kind === 'violation') {
|
|
383
|
+
return { kind: 'violation', reason: verdict.reason };
|
|
384
|
+
}
|
|
385
|
+
return { kind: 'pass', reason: verdict.reason };
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* AI-validated enforcement of the `acceptance-testing` skill's
|
|
390
|
+
* controlled-precondition principle: a scenario's Given is established
|
|
391
|
+
* by the test (fixture, programmed stub, substituted port), never
|
|
392
|
+
* inherited from the ambient environment. Catches the two moves that
|
|
393
|
+
* silently hand a Given to the environment:
|
|
394
|
+
*
|
|
395
|
+
* 1. a driver method whose name states a precondition but whose body
|
|
396
|
+
* sets no fixture/stub/launch state (the no-op precondition
|
|
397
|
+
* driver), and
|
|
398
|
+
* 2. removing control fixtures because the scenario passes without
|
|
399
|
+
* them — the brownfield trap where the environment produces the
|
|
400
|
+
* sad path for free, and the deleted fixture leaves the success
|
|
401
|
+
* path unspecifiable.
|
|
402
|
+
*
|
|
403
|
+
* Applies to: write actions. Scope it with a `{ files, rules }` block
|
|
404
|
+
* to the driver and test-control layers (e.g.
|
|
405
|
+
* `AcceptanceTests/Drivers/**` plus the acceptance composition root) —
|
|
406
|
+
* the opposite scoping from `enforceAcceptanceLanguage`, which
|
|
407
|
+
* excludes those layers. Every matching write costs an AI call.
|
|
408
|
+
*
|
|
409
|
+
* @param options.instructions — overrides or extends the default
|
|
410
|
+
* precondition rules text. Pass a string to replace it, or a
|
|
411
|
+
* function `(defaults) => ...` to extend it.
|
|
412
|
+
*/
|
|
413
|
+
export function enforceControlledPreconditions(options = {}) {
|
|
414
|
+
const rules = typeof options.instructions === 'function'
|
|
415
|
+
? options.instructions(PRECONDITION_RULES)
|
|
416
|
+
: (options.instructions ?? PRECONDITION_RULES);
|
|
417
|
+
return async function enforceControlledPreconditions(action, ctx) {
|
|
418
|
+
if (action.kind !== 'write')
|
|
419
|
+
return { kind: 'pass' };
|
|
420
|
+
if (!ctx?.agent) {
|
|
421
|
+
return {
|
|
422
|
+
kind: 'violation',
|
|
423
|
+
reason: 'enforceControlledPreconditions: no AI agent available; configure Config.ai or use a vendor that ships one.',
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
const before = (await ctx.readFile?.(action.path)) ?? {
|
|
427
|
+
kind: 'unknown',
|
|
428
|
+
};
|
|
429
|
+
const prompt = [
|
|
430
|
+
PRECONDITION_PROCESS_INSTRUCTIONS,
|
|
431
|
+
rules,
|
|
432
|
+
`## Current file content\n\n${formatBefore(before)}`,
|
|
433
|
+
`## Pending action\n\nFile: ${action.path}\n\n${action.content}`,
|
|
434
|
+
RESPONSE_SPEC,
|
|
435
|
+
].join('\n\n');
|
|
436
|
+
const verdict = await ctx.agent.reason(prompt);
|
|
437
|
+
if (verdict.kind === 'violation') {
|
|
438
|
+
return { kind: 'violation', reason: verdict.reason };
|
|
439
|
+
}
|
|
440
|
+
return { kind: 'pass', reason: verdict.reason };
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
//# sourceMappingURL=acceptance-language.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acceptance-language.js","sourceRoot":"","sources":["../../rules/acceptance-language.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAMzC,MAAM,0BAA0B,GAAG,IAAI,CAAA;AAEvC,MAAM,iBAAiB,GAAG;;;;;;;;;CASzB,CAAA;AAED,sEAAsE;AACtE,mEAAmE;AACnE,gEAAgE;AAChE,uEAAuE;AACvE,cAAc;AACd,MAAM,aAAa,GAAG;;;;;;4CAMsB,CAAA;AAE5C,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAkCnB,CAAA;AAEV,MAAM,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wDAsEyB,CAAA;AAExD,MAAM,iCAAiC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA+BlB,CAAA;AAExB,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2DAsCgC,CAAA;AAE3D,SAAS,YAAY,CAAC,MAAmB;IACvC,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,SAAS;YACZ,OAAO,MAAM,CAAC,OAAO,CAAA;QACvB,KAAK,QAAQ;YACX,OAAO,uBAAuB,CAAA;QAChC,KAAK,SAAS;YACZ,OAAO,oCAAoC,CAAA;IAC/C,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,QAAgB;IAC9C,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ;QAAE,OAAO,IAAI,CAAA;IACxC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,8BAA8B,CAAA;AACjE,CAAC;AAED,SAAS,WAAW,CAClB,KAAa,EACb,QAA4B,EAC5B,MAAmB,EACnB,MAAyC;IAEzC,MAAM,QAAQ,GAAG,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAA;IAC9C,IAAI,QAAQ,EAAE,CAAC;QACb,QAAQ,CAAC,IAAI,CAAC,sCAAsC,QAAQ,EAAE,CAAC,CAAA;IACjE,CAAC;IACD,QAAQ,CAAC,IAAI,CAAC,8BAA8B,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IACnE,QAAQ,CAAC,IAAI,CACX,8BAA8B,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,OAAO,EAAE,CACjE,CAAA;IACD,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;IAC5B,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;AAC9B,CAAC;AAED,mEAAmE;AACnE,gEAAgE;AAChE,8DAA8D;AAC9D,MAAM,gBAAgB,GACpB,gJAAgJ,CAAA;AAElJ,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACnC,KAAK;IACL,IAAI;IACJ,MAAM;IACN,OAAO;IACP,KAAK;IACL,OAAO;IACP,QAAQ;IACR,OAAO;IACP,MAAM;IACN,MAAM;CACP,CAAC,CAAA;AAEF,MAAM,WAAW,GAAG,oDAAoD,CAAA;AAExE,SAAS,aAAa,CAAC,OAAe,EAAE,GAAgB;IACtD,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAA;AACvE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,8BAA8B,CAAC,IAAU;IACvD,MAAM,OAAO,GAAG,KAAK,UAAU,0BAA0B,CACvD,MAAc,EACd,GAAiB;QAEjB,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1D,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAC1B,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,GAAG,EAAE,QAAQ,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACjD,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAClE,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;QAClE,MAAM,SAAS,GACb,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC;YAC/C,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC,CAAA;QAC7C,IAAI,SAAS,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAC7C,MAAM,WAAW,GAAG,IAAI,GAAG,CACzB,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAClD,CAAA;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO;aACzB,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;aAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;QAC9D,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YACtD,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAC1B,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAA;QAC/B,aAAa,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QACpC,IAAI,CAAC;YACH,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;gBACtD,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC;oBAAE,SAAQ;gBACpC,aAAa,CACX,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,EACvD,KAAK,CACN,CAAA;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAC1B,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,CAAC;gBACzD,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAE,CAAA;gBACrB,IAAI,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,SAAQ;gBAC5C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;YAChD,CAAC;QACH,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,CAAA;IACzD,CAAC,CAAA;IACD,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE;QACrC,KAAK,EAAE,8BAA8B,IAAI,CAAC,IAAI,IAAI,MAAM,GAAG;KAC5D,CAAC,CAAA;IACF,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,UAAU,yBAAyB,CACvC,UAKI,EAAE;IAEN,MAAM,SAAS,GAAG,sBAAsB,CAAC,OAAO,CAC9C,qBAAqB,EACrB,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CACtD,CAAA;IACD,MAAM,KAAK,GACT,OAAO,OAAO,CAAC,YAAY,KAAK,UAAU;QACxC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC;QACjC,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,IAAI,SAAS,CAAC,CAAA;IACzC,MAAM,gBAAgB,GACpB,OAAO,CAAC,gBAAgB,IAAI,0BAA0B,CAAA;IACxD,OAAO,KAAK,UAAU,yBAAyB,CAC7C,MAAc,EACd,GAAiB;QAEjB,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;QACpD,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC;YAChB,OAAO;gBACL,IAAI,EAAE,WAAW;gBACjB,MAAM,EACJ,uGAAuG;aAC1G,CAAA;QACH,CAAC;QACD,IAAI,QAA4B,CAAA;QAChC,IAAI,OAAO,CAAC,YAAY,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;YACzC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;YACrD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC5B,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAA;YACrD,CAAC;QACH,CAAC;QACD,MAAM,MAAM,GAAgB,CAAC,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI;YACjE,IAAI,EAAE,SAAS;SAChB,CAAA;QACD,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,CACpC,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,CAC7C,CAAA;QACD,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACjC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAA;QACtD,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAA;IACjD,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,8BAA8B,CAC5C,UAEI,EAAE;IAEN,MAAM,KAAK,GACT,OAAO,OAAO,CAAC,YAAY,KAAK,UAAU;QACxC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,kBAAkB,CAAC;QAC1C,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,IAAI,kBAAkB,CAAC,CAAA;IAClD,OAAO,KAAK,UAAU,8BAA8B,CAClD,MAAc,EACd,GAAiB;QAEjB,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;QACpD,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC;YAChB,OAAO;gBACL,IAAI,EAAE,WAAW;gBACjB,MAAM,EACJ,4GAA4G;aAC/G,CAAA;QACH,CAAC;QACD,MAAM,MAAM,GAAgB,CAAC,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI;YACjE,IAAI,EAAE,SAAS;SAChB,CAAA;QACD,MAAM,MAAM,GAAG;YACb,iCAAiC;YACjC,KAAK;YACL,8BAA8B,YAAY,CAAC,MAAM,CAAC,EAAE;YACpD,8BAA8B,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,OAAO,EAAE;YAChE,aAAa;SACd,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACd,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAC9C,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACjC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAA;QACtD,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAA;IACjD,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Rule, RuleContext } from '@nizos/probity';
|
|
2
|
+
/**
|
|
3
|
+
* Language-neutral gate rules. These accumulated in the Kotlin preset
|
|
4
|
+
* (`kotlin.ts`) because that's where the real-project trials happened,
|
|
5
|
+
* but nothing in them is Kotlin-specific — they take every
|
|
6
|
+
* language-shaped decision (what a test command looks like, what an
|
|
7
|
+
* ambient-effect call looks like) as a pattern option. This module is
|
|
8
|
+
* their canonical home; `kotlin.ts` re-exports wrappers that apply the
|
|
9
|
+
* Kotlin/Gradle defaults, so existing preset configs are unchanged.
|
|
10
|
+
*
|
|
11
|
+
* All rules here are deterministic — no AI call — and delta-based
|
|
12
|
+
* where they judge file content: only what a write INTRODUCES blocks,
|
|
13
|
+
* so brownfield files migrate incrementally instead of freezing.
|
|
14
|
+
*/
|
|
15
|
+
/** A labeled content pattern; the label names the hit in deny text. */
|
|
16
|
+
export type NamedPattern = {
|
|
17
|
+
label: string;
|
|
18
|
+
pattern: RegExp;
|
|
19
|
+
};
|
|
20
|
+
/** Patterns whose occurrence count grows from before → after. */
|
|
21
|
+
export declare function introducedPatterns(action: {
|
|
22
|
+
path: string;
|
|
23
|
+
content: string;
|
|
24
|
+
}, ctx: RuleContext | undefined, patterns: NamedPattern[]): Promise<string[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Ambient-effect calls for a JS/TS core: OS clock, randomness, and
|
|
27
|
+
* environment reads. Pass to {@link forbidNewAmbientEffects} from a
|
|
28
|
+
* JS/TS config. `process.env` reads belong in the composition root or
|
|
29
|
+
* a config adapter; if your core legitimately branches on injected
|
|
30
|
+
* config, that config should arrive through a port, not the OS.
|
|
31
|
+
*/
|
|
32
|
+
export declare const JS_AMBIENT_EFFECT_PATTERNS: NamedPattern[];
|
|
33
|
+
/**
|
|
34
|
+
* Blocks production writes that introduce direct ambient-effect calls.
|
|
35
|
+
* Under ports-and-adapters these are unowned OS dependencies: clock,
|
|
36
|
+
* randomness, and environment are ports.
|
|
37
|
+
*
|
|
38
|
+
* Delta-based: pre-existing call sites in a brownfield codebase don't
|
|
39
|
+
* block edits to their files; only net-new occurrences do. Scope to
|
|
40
|
+
* production sources — tests and adapter implementations (e.g. a
|
|
41
|
+
* `SystemClock`) legitimately touch the real OS, so exclude adapter
|
|
42
|
+
* paths via globs or negations.
|
|
43
|
+
*
|
|
44
|
+
* @param options.patterns — what an ambient-effect call looks like in
|
|
45
|
+
* your language ({@link JS_AMBIENT_EFFECT_PATTERNS} for JS/TS; the
|
|
46
|
+
* Kotlin preset supplies JVM patterns). Each RegExp needs `g`.
|
|
47
|
+
* @param options.seamHint — appended to the block message to point
|
|
48
|
+
* the agent at the project's canonical seam(s), e.g.
|
|
49
|
+
* "inject the Clock port from src/ports/clock.ts".
|
|
50
|
+
*/
|
|
51
|
+
export declare function forbidNewAmbientEffects(options: {
|
|
52
|
+
patterns: NamedPattern[];
|
|
53
|
+
seamHint?: string;
|
|
54
|
+
}): Rule;
|
|
55
|
+
/**
|
|
56
|
+
* Commit-on-green, strictly: Probity's `requireCommand` checks only
|
|
57
|
+
* that a matching test invocation was *recorded* after the last write
|
|
58
|
+
* and would happily pass a transcript whose latest run FAILED. This
|
|
59
|
+
* rule additionally judges the recorded run's output: the last
|
|
60
|
+
* matching test command after the last write must look green
|
|
61
|
+
* (`successPattern` present, `failurePattern` absent).
|
|
62
|
+
*
|
|
63
|
+
* Inherent limit (unchanged from requireCommand): the gate sees only
|
|
64
|
+
* the session transcript. A green run in another terminal, CI, or a
|
|
65
|
+
* wrapper script is invisible — rerun the suite in-session, and keep
|
|
66
|
+
* the CI mirror for human commits.
|
|
67
|
+
*
|
|
68
|
+
* Applies to: command actions matching `git commit`. Deterministic —
|
|
69
|
+
* no AI call.
|
|
70
|
+
*
|
|
71
|
+
* @param options.command — regex matching a test invocation.
|
|
72
|
+
* @param options.successPattern — output must match to count as green.
|
|
73
|
+
* @param options.failurePattern — output matching this is red even if
|
|
74
|
+
* the success pattern also appears.
|
|
75
|
+
* @param options.enforceForPaths — only demand the run when the
|
|
76
|
+
* pending commit stages a file whose repo-relative path matches.
|
|
77
|
+
* Scopes an expensive suite to the code it covers: infra/docs/
|
|
78
|
+
* tooling-only commits (CI config, Markdown, the Probity config
|
|
79
|
+
* itself) pay no friction, since they change no behaviour the suite
|
|
80
|
+
* validates and the prior green run still stands. Commit-accurate —
|
|
81
|
+
* read from git's staged set, not the session's write history. Any
|
|
82
|
+
* error listing files falls through to enforcing (fail safe).
|
|
83
|
+
* @param options.listCommitFiles — injectable staged-file lister
|
|
84
|
+
* (defaults to reading git's staged set); present for testing.
|
|
85
|
+
* @param options.reason — appended to the no-run deny text to name
|
|
86
|
+
* the suite and any setup it needs (e.g. "supabase start").
|
|
87
|
+
*/
|
|
88
|
+
export declare function requireGreenTestRun(options: {
|
|
89
|
+
command: RegExp;
|
|
90
|
+
successPattern: RegExp;
|
|
91
|
+
failurePattern: RegExp;
|
|
92
|
+
enforceForPaths?: RegExp;
|
|
93
|
+
listCommitFiles?: (command: string) => string[];
|
|
94
|
+
reason?: string;
|
|
95
|
+
}): Rule;
|
|
96
|
+
/**
|
|
97
|
+
* Surfaces the "renamed a button, broke the E2E suite" failure at
|
|
98
|
+
* write time: when a write REMOVES a string literal that still appears
|
|
99
|
+
* verbatim in files under `searchRoots` (typically your E2E/UI-test
|
|
100
|
+
* specs, which select elements by visible text or accessible name),
|
|
101
|
+
* the write is blocked with the list of dependent files.
|
|
102
|
+
*
|
|
103
|
+
* The inverse of a glossary guard: `surfaceGlossaryTermBreakage`
|
|
104
|
+
* protects the vocabulary file from code that depends on it; this
|
|
105
|
+
* protects test selectors from the UI code they depend on. Scope it to
|
|
106
|
+
* your UI sources, with `searchRoots` pointing at the spec layers the
|
|
107
|
+
* quick local loop does NOT run (E2E, smoke) — specs the inner loop
|
|
108
|
+
* runs will fail red on their own.
|
|
109
|
+
*
|
|
110
|
+
* Deterministic, delta-based — no AI call. The write goes through once
|
|
111
|
+
* the dependent specs are updated in the same session (or the string
|
|
112
|
+
* genuinely stops being referenced).
|
|
113
|
+
*
|
|
114
|
+
* @param options.searchRoots — absolute paths to scan for usages.
|
|
115
|
+
* @param options.searchPattern — which files count as usage sites
|
|
116
|
+
* (default: `*.spec.*` / `*.test.*` under the roots).
|
|
117
|
+
* @param options.minLength — ignore removed literals shorter than
|
|
118
|
+
* this after trimming (default 8; short strings false-positive).
|
|
119
|
+
*/
|
|
120
|
+
export declare function surfaceRemovedStringUsage(options: {
|
|
121
|
+
searchRoots: string[];
|
|
122
|
+
searchPattern?: RegExp;
|
|
123
|
+
minLength?: number;
|
|
124
|
+
}): Rule;
|
|
125
|
+
//# sourceMappingURL=gates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../rules/gates.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAU,IAAI,EAAE,WAAW,EAAc,MAAM,gBAAgB,CAAA;AAE3E;;;;;;;;;;;;GAYG;AAEH,uEAAuE;AACvE,MAAM,MAAM,YAAY,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAQ7D,iEAAiE;AACjE,wBAAsB,kBAAkB,CACtC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EACzC,GAAG,EAAE,WAAW,GAAG,SAAS,EAC5B,QAAQ,EAAE,YAAY,EAAE,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC,CAcnB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,EAAE,YAAY,EAOpD,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,GAAG,IAAI,CAqBP;AAuBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC3C,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,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,CAuDP;AA0CD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE;IACjD,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GAAG,IAAI,CA+CP"}
|