@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,456 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Spec↔test traceability for the `acceptance-testing` workflow:
|
|
5
|
+
* every `## Scenario:` in a `*.feature.md` spec is claimed by an
|
|
6
|
+
* acceptance test, and every claim resolves to a real scenario.
|
|
7
|
+
*
|
|
8
|
+
* The link convention: an acceptance test declares the scenario it
|
|
9
|
+
* covers with a tag anywhere in the file (comment or annotation
|
|
10
|
+
* argument), one per scenario:
|
|
11
|
+
*
|
|
12
|
+
* // Covers: messaging.feature.md :: Scenario: Message is delivered
|
|
13
|
+
*
|
|
14
|
+
* Scenario keys are `<spec basename> :: <title>`, so titles are link
|
|
15
|
+
* keys: renaming a scenario must update its tests' tags — which
|
|
16
|
+
* `surfaceScenarioLinkBreakage` turns into a guided step instead of
|
|
17
|
+
* silent rot.
|
|
18
|
+
*
|
|
19
|
+
* In-progress scenarios being driven outside-in are exempted with
|
|
20
|
+
* `## Scenario (wip):` — the parity gate ignores them until the
|
|
21
|
+
* marker is dropped.
|
|
22
|
+
*
|
|
23
|
+
* Scenarios may additionally declare **driver scopes** — coverage
|
|
24
|
+
* floors naming which driver suites must cover them, on top of the
|
|
25
|
+
* base "at least one test" requirement:
|
|
26
|
+
*
|
|
27
|
+
* ## Scenario [system]: Payment is retried after a network failure
|
|
28
|
+
*
|
|
29
|
+
* Scope names are policy labels ("also proven against the deployed
|
|
30
|
+
* system"), not mechanisms; the mapping from scope name to test files
|
|
31
|
+
* is the `driverScopes` option. Tags never change the scenario key,
|
|
32
|
+
* so Covers tags and baselines are unaffected by adding one.
|
|
33
|
+
*/
|
|
34
|
+
// Two spec formats during the .feature.md -> .feature migration:
|
|
35
|
+
// Markdown: ## Scenario (wip) [system]: Title
|
|
36
|
+
// Gherkin: @wip @system\n Scenario: Title
|
|
37
|
+
// parseScenarios below handles both, line by line.
|
|
38
|
+
const MD_SCENARIO = /^##\s*Scenario(\s*\((?:wip|planned)\))?(\s*\[([^\]]+)\])?\s*:\s*(.+?)\s*$/;
|
|
39
|
+
const GHERKIN_SCENARIO = /^Scenario(?:\s+Outline)?:\s*(.+?)\s*$/;
|
|
40
|
+
const COVERS_TAG = /Covers:\s*([\w.-]+\.feature(?:\.md)?)\s*::\s*Scenario:\s*([^\n]+)/g;
|
|
41
|
+
const SKIP_DIRS = new Set([
|
|
42
|
+
'node_modules',
|
|
43
|
+
'.git',
|
|
44
|
+
'build',
|
|
45
|
+
'.gradle',
|
|
46
|
+
'out',
|
|
47
|
+
'dist',
|
|
48
|
+
'.idea',
|
|
49
|
+
]);
|
|
50
|
+
function normalizeTitle(title) {
|
|
51
|
+
return title
|
|
52
|
+
.replace(/\s*(?:\*\/|["')\]]+)?\s*$/, '')
|
|
53
|
+
.replace(/\s+/g, ' ')
|
|
54
|
+
.trim()
|
|
55
|
+
.toLowerCase();
|
|
56
|
+
}
|
|
57
|
+
// A spec is identified by its stem (filename without .feature/.feature.md)
|
|
58
|
+
// so a scenario keeps one key across the extension change and Covers tags
|
|
59
|
+
// resolve regardless of which extension either side currently uses.
|
|
60
|
+
function specStem(name) {
|
|
61
|
+
return basename(name).replace(/\.feature(?:\.md)?$/i, '');
|
|
62
|
+
}
|
|
63
|
+
function scenarioKey(specFile, title) {
|
|
64
|
+
return `${specStem(specFile)} :: ${normalizeTitle(title)}`;
|
|
65
|
+
}
|
|
66
|
+
function parseScopes(tag) {
|
|
67
|
+
if (!tag)
|
|
68
|
+
return [];
|
|
69
|
+
return tag
|
|
70
|
+
.split(',')
|
|
71
|
+
.map((scope) => scope.trim().toLowerCase())
|
|
72
|
+
.filter(Boolean);
|
|
73
|
+
}
|
|
74
|
+
export function extractScenarios(specFile, content) {
|
|
75
|
+
const scenarios = [];
|
|
76
|
+
let tags = [];
|
|
77
|
+
for (const line of content.split(/\r?\n/)) {
|
|
78
|
+
const md = line.match(MD_SCENARIO);
|
|
79
|
+
if (md) {
|
|
80
|
+
const title = md[4] ?? '';
|
|
81
|
+
scenarios.push({
|
|
82
|
+
key: scenarioKey(specFile, title),
|
|
83
|
+
specFile,
|
|
84
|
+
title: title.trim(),
|
|
85
|
+
wip: md[1] !== undefined,
|
|
86
|
+
scopes: parseScopes(md[3]),
|
|
87
|
+
});
|
|
88
|
+
tags = [];
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const trimmed = line.trim();
|
|
92
|
+
if (/^@\S/.test(trimmed)) {
|
|
93
|
+
tags.push(...trimmed
|
|
94
|
+
.split(/\s+/)
|
|
95
|
+
.filter((token) => token.startsWith('@'))
|
|
96
|
+
.map((token) => token.slice(1).toLowerCase()));
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const gherkin = trimmed.match(GHERKIN_SCENARIO);
|
|
100
|
+
if (gherkin) {
|
|
101
|
+
const title = gherkin[1] ?? '';
|
|
102
|
+
scenarios.push({
|
|
103
|
+
key: scenarioKey(specFile, title),
|
|
104
|
+
specFile,
|
|
105
|
+
title: title.trim(),
|
|
106
|
+
wip: tags.includes('wip') || tags.includes('planned'),
|
|
107
|
+
scopes: tags.filter((tag) => tag !== 'wip' && tag !== 'planned'),
|
|
108
|
+
});
|
|
109
|
+
tags = [];
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
// Tags only attach to the next Scenario; any non-tag, non-comment
|
|
113
|
+
// content (a Feature line, a step) ends a dangling tag block.
|
|
114
|
+
if (trimmed !== '' && !trimmed.startsWith('#'))
|
|
115
|
+
tags = [];
|
|
116
|
+
}
|
|
117
|
+
return scenarios;
|
|
118
|
+
}
|
|
119
|
+
export function extractCoversRefs(testFile, content) {
|
|
120
|
+
const refs = [];
|
|
121
|
+
for (const match of content.matchAll(COVERS_TAG)) {
|
|
122
|
+
const specFile = match[1] ?? '';
|
|
123
|
+
const title = match[2] ?? '';
|
|
124
|
+
refs.push({
|
|
125
|
+
key: scenarioKey(specFile, title),
|
|
126
|
+
testFile,
|
|
127
|
+
specFile,
|
|
128
|
+
title: title.replace(/\s*(?:\*\/|["')\]]+)?\s*$/, '').trim(),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return refs;
|
|
132
|
+
}
|
|
133
|
+
function walk(dir, out = []) {
|
|
134
|
+
let entries;
|
|
135
|
+
try {
|
|
136
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
if (entry.isDirectory()) {
|
|
143
|
+
if (!SKIP_DIRS.has(entry.name))
|
|
144
|
+
walk(join(dir, entry.name), out);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
out.push(join(dir, entry.name));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
const DEFAULT_TEST_FILE_PATTERN = /[/\\]acceptance[/\\]/;
|
|
153
|
+
/** Parses a baseline file into normalized scenario keys. Missing file → empty set. */
|
|
154
|
+
export function readBaseline(path) {
|
|
155
|
+
if (!path || !existsSync(path))
|
|
156
|
+
return new Set();
|
|
157
|
+
return new Set(readFileSync(path, 'utf8')
|
|
158
|
+
.split('\n')
|
|
159
|
+
.map((line) => line.trim())
|
|
160
|
+
.filter((line) => line && !line.startsWith('#'))
|
|
161
|
+
.flatMap((line) => {
|
|
162
|
+
const idx = line.indexOf(' :: ');
|
|
163
|
+
if (idx === -1)
|
|
164
|
+
return [];
|
|
165
|
+
const spec = line.slice(0, idx).trim();
|
|
166
|
+
const title = line.slice(idx + 4);
|
|
167
|
+
return [scenarioKey(spec, title)];
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
/** All spec files (either extension) under a directory. */
|
|
171
|
+
export function findSpecFiles(specsDir) {
|
|
172
|
+
return walk(specsDir).filter((file) => file.endsWith('.feature.md') || file.endsWith('.feature'));
|
|
173
|
+
}
|
|
174
|
+
export function scanSpecs(specsDir) {
|
|
175
|
+
return findSpecFiles(specsDir).flatMap((file) => extractScenarios(file, readFileSync(file, 'utf8')));
|
|
176
|
+
}
|
|
177
|
+
export function scanCoversRefs(testRoots, testFilePattern) {
|
|
178
|
+
return testRoots
|
|
179
|
+
.flatMap((root) => walk(root))
|
|
180
|
+
.filter((file) => testFilePattern.test(file) &&
|
|
181
|
+
!file.endsWith('.feature.md') &&
|
|
182
|
+
!file.endsWith('.feature'))
|
|
183
|
+
.flatMap((file) => extractCoversRefs(file, readFileSync(file, 'utf8')));
|
|
184
|
+
}
|
|
185
|
+
function formatList(lines, max = 10) {
|
|
186
|
+
const shown = lines.slice(0, max).map((line) => ` ${line}`);
|
|
187
|
+
if (lines.length > max)
|
|
188
|
+
shown.push(` …and ${lines.length - max} more`);
|
|
189
|
+
return shown.join('\n');
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Bidirectional parity gate on `git commit`: blocks when any
|
|
193
|
+
* non-wip `## Scenario:` heading in `specsDir` has no acceptance
|
|
194
|
+
* test claiming it via a `Covers:` tag, or when any `Covers:` tag
|
|
195
|
+
* points at a scenario that no longer exists (spec renamed or
|
|
196
|
+
* removed). Deterministic filesystem scan — no AI call; milliseconds
|
|
197
|
+
* at typical spec-suite scale.
|
|
198
|
+
*
|
|
199
|
+
* The test claiming a scenario may still be red or quarantined —
|
|
200
|
+
* parity requires the *link*, not a passing run; keeping in-progress
|
|
201
|
+
* specs out of the pipeline is the pipeline's job. Alternatively
|
|
202
|
+
* mark the scenario `## Scenario (wip):` to exempt it entirely.
|
|
203
|
+
*
|
|
204
|
+
* Applies to: command actions matching `git commit`.
|
|
205
|
+
*
|
|
206
|
+
* @param options.specsDir — absolute path to the specs directory
|
|
207
|
+
* (e.g. `<root>/docs/specs`). A missing directory passes: no
|
|
208
|
+
* specs, nothing to enforce.
|
|
209
|
+
* @param options.testRoots — absolute paths to scan for acceptance
|
|
210
|
+
* tests (the repo root is fine; node_modules/build dirs are
|
|
211
|
+
* skipped).
|
|
212
|
+
* @param options.testFilePattern — which files count as acceptance
|
|
213
|
+
* tests (default: any file under an `acceptance/` directory).
|
|
214
|
+
* @param options.baselinePath — optional incremental-adoption
|
|
215
|
+
* baseline; scenarios listed there are exempt from the orphan
|
|
216
|
+
* check (see {@link readBaseline}). Missing file → full enforcement.
|
|
217
|
+
* @param options.driverScopes — optional per-scenario driver mapping;
|
|
218
|
+
* see {@link DriverScope}. Scenarios tagged `[scope]` (and every
|
|
219
|
+
* scenario, for `defaultScopes` entries) must be covered by a test
|
|
220
|
+
* matching that scope's `filePattern`, on top of the base check.
|
|
221
|
+
* @param options.defaultScopes — scope names required of untagged
|
|
222
|
+
* scenarios too (the project's standard driver set).
|
|
223
|
+
*/
|
|
224
|
+
export function enforceSpecTestParity(options) {
|
|
225
|
+
const pattern = options.testFilePattern ?? DEFAULT_TEST_FILE_PATTERN;
|
|
226
|
+
const scopes = new Map((options.driverScopes ?? []).map((scope) => [scope.name.toLowerCase(), scope]));
|
|
227
|
+
const defaultScopes = (options.defaultScopes ?? []).map((name) => name.toLowerCase());
|
|
228
|
+
return function enforceSpecTestParity(action) {
|
|
229
|
+
if (action.kind !== 'command')
|
|
230
|
+
return { kind: 'pass' };
|
|
231
|
+
if (!/git commit/.test(action.command))
|
|
232
|
+
return { kind: 'pass' };
|
|
233
|
+
if (!existsSync(options.specsDir))
|
|
234
|
+
return { kind: 'pass' };
|
|
235
|
+
// During the .feature.md -> .feature migration a spec is one extension
|
|
236
|
+
// or the other; the same stem in both forms double-counts scenarios and
|
|
237
|
+
// masks a half-finished rename.
|
|
238
|
+
const specFiles = findSpecFiles(options.specsDir);
|
|
239
|
+
const byStem = new Map();
|
|
240
|
+
for (const file of specFiles) {
|
|
241
|
+
const stem = specStem(file);
|
|
242
|
+
const existing = byStem.get(stem);
|
|
243
|
+
if (existing) {
|
|
244
|
+
return {
|
|
245
|
+
kind: 'violation',
|
|
246
|
+
reason: `Spec "${stem}" exists in both .feature and .feature.md forms:\n` +
|
|
247
|
+
` ${existing}\n ${file}\n` +
|
|
248
|
+
'Finish the migration for this spec — keep a single extension.',
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
byStem.set(stem, file);
|
|
252
|
+
}
|
|
253
|
+
const scenarios = specFiles.flatMap((file) => extractScenarios(file, readFileSync(file, 'utf8')));
|
|
254
|
+
const refs = scanCoversRefs(options.testRoots, pattern);
|
|
255
|
+
const baseline = readBaseline(options.baselinePath);
|
|
256
|
+
const claimed = new Set(refs.map((ref) => ref.key));
|
|
257
|
+
const known = new Set(scenarios.map((scenario) => scenario.key));
|
|
258
|
+
const orphaned = scenarios.filter((s) => !s.wip && !claimed.has(s.key) && !baseline.has(s.key));
|
|
259
|
+
const dangling = refs.filter((ref) => !known.has(ref.key));
|
|
260
|
+
const unknownScopes = [];
|
|
261
|
+
const missingScopes = [];
|
|
262
|
+
for (const scenario of scenarios) {
|
|
263
|
+
if (scenario.wip || baseline.has(scenario.key))
|
|
264
|
+
continue;
|
|
265
|
+
const required = [...new Set([...defaultScopes, ...scenario.scopes])];
|
|
266
|
+
for (const name of required) {
|
|
267
|
+
const scope = scopes.get(name);
|
|
268
|
+
if (!scope) {
|
|
269
|
+
unknownScopes.push(`${basename(scenario.specFile)} :: ${scenario.title} — [${name}]`);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const covered = refs.some((ref) => ref.key === scenario.key && scope.filePattern.test(ref.testFile));
|
|
273
|
+
if (!covered) {
|
|
274
|
+
missingScopes.push(`${basename(scenario.specFile)} :: ${scenario.title} — needs [${name}] coverage`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (orphaned.length === 0 &&
|
|
279
|
+
dangling.length === 0 &&
|
|
280
|
+
unknownScopes.length === 0 &&
|
|
281
|
+
missingScopes.length === 0)
|
|
282
|
+
return { kind: 'pass' };
|
|
283
|
+
const sections = ['Spec↔test parity check failed.'];
|
|
284
|
+
if (orphaned.length > 0) {
|
|
285
|
+
sections.push('Scenarios with no covering acceptance test (add a test with ' +
|
|
286
|
+
'`// Covers: <spec>.feature.md :: Scenario: <title>`, or mark ' +
|
|
287
|
+
'the heading `## Scenario (wip):` while driving it):\n' +
|
|
288
|
+
formatList(orphaned.map((s) => `${basename(s.specFile)} :: ${s.title}`)));
|
|
289
|
+
}
|
|
290
|
+
if (dangling.length > 0) {
|
|
291
|
+
sections.push('Covers tags pointing at no existing scenario (spec renamed or ' +
|
|
292
|
+
'removed — update or delete these tests):\n' +
|
|
293
|
+
formatList(dangling.map((r) => `${r.testFile} → ${r.specFile} :: ${r.title}`)));
|
|
294
|
+
}
|
|
295
|
+
if (missingScopes.length > 0) {
|
|
296
|
+
sections.push('Scenarios covered, but not by every driver scope they require ' +
|
|
297
|
+
'(add a covering test in the named suite — with shared ' +
|
|
298
|
+
'scenario bodies this is a thin spec class calling the ' +
|
|
299
|
+
'existing body):\n' +
|
|
300
|
+
formatList(missingScopes));
|
|
301
|
+
}
|
|
302
|
+
if (unknownScopes.length > 0) {
|
|
303
|
+
sections.push('Scenario tags naming driver scopes this gate does not know ' +
|
|
304
|
+
`(known scopes: ${scopes.size > 0 ? [...scopes.keys()].join(', ') : 'none configured'} — ` +
|
|
305
|
+
'fix the tag or add the scope to `driverScopes`):\n' +
|
|
306
|
+
formatList(unknownScopes));
|
|
307
|
+
}
|
|
308
|
+
return { kind: 'violation', reason: sections.join('\n') };
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Matches a test-case declaration in the acceptance layer: a Kotlin/
|
|
313
|
+
* Java `@Test` annotation or a Swift XCTest `func test…` method. The
|
|
314
|
+
* pattern is intentionally coarse — it only needs to detect that a
|
|
315
|
+
* write ADDS test cases, not parse them.
|
|
316
|
+
*/
|
|
317
|
+
const DEFAULT_TEST_DECLARATION = /@Test\b|\bfunc\s+test\w*\s*\(/g;
|
|
318
|
+
function countTests(content, pattern) {
|
|
319
|
+
return [...content.matchAll(pattern)].length;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Spec-first, made mechanical: a write that adds a new acceptance
|
|
323
|
+
* test case is blocked unless it also adds a `Covers:` tag pointing
|
|
324
|
+
* at a `## Scenario:` heading that ALREADY EXISTS in `specsDir`. The
|
|
325
|
+
* executable specification (`*.feature.md`) must be written before
|
|
326
|
+
* the test that claims it — an agent that codes the test first gets
|
|
327
|
+
* told exactly what to create and where.
|
|
328
|
+
*
|
|
329
|
+
* This is the write-time front half of the traceability story;
|
|
330
|
+
* {@link enforceSpecTestParity} is the commit-time back half (every
|
|
331
|
+
* scenario covered, every tag resolving). Together they close the
|
|
332
|
+
* gap where an agent authors a whole acceptance suite with no
|
|
333
|
+
* feature file and nothing objects until commit — or ever, if the
|
|
334
|
+
* tests carry no tags at all.
|
|
335
|
+
*
|
|
336
|
+
* Delta-based and deterministic (no AI call): only NEW test cases
|
|
337
|
+
* demand a NEW resolving tag, so edits inside an existing brownfield
|
|
338
|
+
* test file pass untouched, and a pre-existing dangling tag can't
|
|
339
|
+
* block unrelated work (the commit gate owns that). `(wip)` and
|
|
340
|
+
* `(planned)` scenarios count as existing — they are exactly the
|
|
341
|
+
* headings outside-in work drives against.
|
|
342
|
+
*
|
|
343
|
+
* Applies to: write actions — scope it via a `{ files, rules }`
|
|
344
|
+
* block to the acceptance test-case layer only (e.g.
|
|
345
|
+
* `**\/acceptance/**\/*Spec.kt`), never to drivers/DSL/infrastructure.
|
|
346
|
+
*
|
|
347
|
+
* @param options.specsDir — absolute path to the specs directory.
|
|
348
|
+
* Missing directory ⇒ every new tag is unresolved ⇒ the deny
|
|
349
|
+
* message says to create the first feature file (greenfield
|
|
350
|
+
* enforcement, not a free pass).
|
|
351
|
+
* @param options.testDeclarationPattern — what counts as a test-case
|
|
352
|
+
* declaration (default: Kotlin/Java `@Test` or Swift `func test…`).
|
|
353
|
+
*/
|
|
354
|
+
export function requireSpecBackedAcceptanceTest(options) {
|
|
355
|
+
const declaration = options.testDeclarationPattern ?? DEFAULT_TEST_DECLARATION;
|
|
356
|
+
return async function requireSpecBackedAcceptanceTest(action, ctx) {
|
|
357
|
+
if (action.kind !== 'write')
|
|
358
|
+
return { kind: 'pass' };
|
|
359
|
+
const before = await ctx?.readFile?.(action.path);
|
|
360
|
+
const beforeContent = before?.kind === 'present' ? before.content : '';
|
|
361
|
+
const testsAdded = countTests(action.content, declaration) > countTests(beforeContent, declaration);
|
|
362
|
+
if (!testsAdded)
|
|
363
|
+
return { kind: 'pass' };
|
|
364
|
+
const beforeKeys = new Set(extractCoversRefs(action.path, beforeContent).map((ref) => ref.key));
|
|
365
|
+
const afterRefs = extractCoversRefs(action.path, action.content);
|
|
366
|
+
const newRefs = afterRefs.filter((ref) => !beforeKeys.has(ref.key));
|
|
367
|
+
const known = new Set(scanSpecs(options.specsDir).map((s) => s.key));
|
|
368
|
+
if (newRefs.length === 0) {
|
|
369
|
+
// No NEW tag — still fine when the file already claims a real
|
|
370
|
+
// scenario: "one scenario, many drivers" adds a second/third
|
|
371
|
+
// test transcribing an already-covered scenario to a file whose
|
|
372
|
+
// tag resolves. Only a file with no resolving tag at all is a
|
|
373
|
+
// spec-first violation. (File-level granularity by design: the
|
|
374
|
+
// commit-time parity gate owns the per-scenario bookkeeping.)
|
|
375
|
+
if (afterRefs.some((ref) => known.has(ref.key)))
|
|
376
|
+
return { kind: 'pass' };
|
|
377
|
+
return {
|
|
378
|
+
kind: 'violation',
|
|
379
|
+
reason: 'This write adds a new acceptance test case with no ' +
|
|
380
|
+
'`Covers:` tag resolving to an existing scenario. ' +
|
|
381
|
+
'Acceptance tests are written spec-first: ' +
|
|
382
|
+
`add a \`## Scenario: <title>\` to a *.feature.md under ` +
|
|
383
|
+
`${options.specsDir} describing the behavior in domain ` +
|
|
384
|
+
'language, then re-apply this test with\n' +
|
|
385
|
+
' // Covers: <spec>.feature.md :: Scenario: <title>\n' +
|
|
386
|
+
'If the scenario is still being shaped, write it as ' +
|
|
387
|
+
'`## Scenario (wip):` — it still counts.',
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
const unresolved = newRefs.filter((ref) => !known.has(ref.key));
|
|
391
|
+
if (unresolved.length === 0)
|
|
392
|
+
return { kind: 'pass' };
|
|
393
|
+
return {
|
|
394
|
+
kind: 'violation',
|
|
395
|
+
reason: 'This write adds acceptance test(s) whose `Covers:` tag points ' +
|
|
396
|
+
'at a scenario that does not exist yet. The feature file comes ' +
|
|
397
|
+
'first: create the scenario heading (exact title match, ' +
|
|
398
|
+
`case-insensitive) under ${options.specsDir}, then re-apply ` +
|
|
399
|
+
'this test.\n' +
|
|
400
|
+
formatList(unresolved.map((ref) => `${ref.specFile} :: Scenario: ${ref.title}`)),
|
|
401
|
+
};
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Surfaces link breakage at the moment it is created: when a write
|
|
406
|
+
* to a `*.feature.md` file removes or renames a `## Scenario:`
|
|
407
|
+
* heading that acceptance tests still claim, the write is blocked
|
|
408
|
+
* with the list of affected tests — update their `Covers:` tags (or
|
|
409
|
+
* delete the tests) in the same change, then re-apply the spec edit.
|
|
410
|
+
* Removing a scenario nothing covers passes silently.
|
|
411
|
+
*
|
|
412
|
+
* Applies to: write actions to `*.feature.md` files (scope via a
|
|
413
|
+
* `{ files, rules }` block). Deterministic — no AI call.
|
|
414
|
+
*
|
|
415
|
+
* @param options.testRoots — absolute paths to scan for acceptance
|
|
416
|
+
* tests.
|
|
417
|
+
* @param options.testFilePattern — which files count as acceptance
|
|
418
|
+
* tests (default: any file under an `acceptance/` directory).
|
|
419
|
+
*/
|
|
420
|
+
export function surfaceScenarioLinkBreakage(options) {
|
|
421
|
+
const pattern = options.testFilePattern ?? DEFAULT_TEST_FILE_PATTERN;
|
|
422
|
+
return async function surfaceScenarioLinkBreakage(action, ctx) {
|
|
423
|
+
if (action.kind !== 'write')
|
|
424
|
+
return { kind: 'pass' };
|
|
425
|
+
if (!action.path.endsWith('.feature.md') && !action.path.endsWith('.feature'))
|
|
426
|
+
return { kind: 'pass' };
|
|
427
|
+
const before = await ctx?.readFile?.(action.path);
|
|
428
|
+
if (!before || before.kind !== 'present')
|
|
429
|
+
return { kind: 'pass' };
|
|
430
|
+
const beforeKeys = new Map(extractScenarios(action.path, before.content).map((s) => [s.key, s]));
|
|
431
|
+
const afterKeys = new Set(extractScenarios(action.path, action.content).map((s) => s.key));
|
|
432
|
+
const removed = [...beforeKeys.values()].filter((s) => !afterKeys.has(s.key));
|
|
433
|
+
if (removed.length === 0)
|
|
434
|
+
return { kind: 'pass' };
|
|
435
|
+
const refs = scanCoversRefs(options.testRoots, pattern);
|
|
436
|
+
const broken = removed.flatMap((scenario) => {
|
|
437
|
+
const claimants = [
|
|
438
|
+
...new Set(refs.filter((ref) => ref.key === scenario.key).map((ref) => ref.testFile)),
|
|
439
|
+
];
|
|
440
|
+
return claimants.length > 0
|
|
441
|
+
? [`"${scenario.title}" is still covered by:\n` + formatList(claimants)]
|
|
442
|
+
: [];
|
|
443
|
+
});
|
|
444
|
+
if (broken.length === 0)
|
|
445
|
+
return { kind: 'pass' };
|
|
446
|
+
return {
|
|
447
|
+
kind: 'violation',
|
|
448
|
+
reason: 'This edit removes or renames scenario(s) that acceptance ' +
|
|
449
|
+
'tests still claim. Update those tests’ Covers: tags (or ' +
|
|
450
|
+
'delete the tests) in the same change, then re-apply this ' +
|
|
451
|
+
'spec edit.\n' +
|
|
452
|
+
broken.join('\n'),
|
|
453
|
+
};
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
//# sourceMappingURL=spec-test-parity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec-test-parity.js","sourceRoot":"","sources":["../../rules/spec-test-parity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swift/iOS-shaped deterministic rules and constants for the Probity
|
|
3
|
+
* presets — the Apple-toolchain counterpart of ./kotlin.ts.
|
|
4
|
+
*
|
|
5
|
+
* Most of the enforcement stack is language-neutral and reused as-is
|
|
6
|
+
* (spec-test-parity, acceptance-language, glossary rules, the
|
|
7
|
+
* mutation-probe pair, the green-run gate). This module supplies only
|
|
8
|
+
* what is genuinely Swift/Xcode-specific: content screens for the
|
|
9
|
+
* acceptance suite and the xcodebuild patterns the generic gates need.
|
|
10
|
+
* Calibrated against a production iOS app (CocoaPods + SwiftPM
|
|
11
|
+
* workspace, XCUITest + app-hosted component-test targets, MVVM view
|
|
12
|
+
* models over use-case ports) — adjust to your layout.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Fixed waits in test code — the top cause of flaky XCUITest suites.
|
|
16
|
+
* Matches `sleep(2)`, `usleep(...)`, `Thread.sleep(forTimeInterval:)`,
|
|
17
|
+
* and `try await Task.sleep(...)`. Synchronize with XCTest
|
|
18
|
+
* expectations, predicate expectations, or element-existence timeouts
|
|
19
|
+
* instead (`waitForExistence(timeout:)`).
|
|
20
|
+
*/
|
|
21
|
+
export declare const SWIFT_FIXED_SLEEPS: RegExp;
|
|
22
|
+
/**
|
|
23
|
+
* XCUITest mechanics — `XCUIApplication`, `XCUIElement`, coordinate
|
|
24
|
+
* taps. In the four-layer model these belong ONLY in protocol drivers
|
|
25
|
+
* (and only in the driver that owns the deployed-app scope): scope
|
|
26
|
+
* this screen to the spec/scenario/DSL layers, where any match means
|
|
27
|
+
* UI mechanics have leaked upward.
|
|
28
|
+
*/
|
|
29
|
+
export declare const XCUITEST_MECHANICS: RegExp;
|
|
30
|
+
/**
|
|
31
|
+
* An `xcodebuild … test` invocation, for the green-run commit gate.
|
|
32
|
+
* Matches plain `test`, `test-without-building`, and scheme-qualified
|
|
33
|
+
* forms. Pair with {@link XCODEBUILD_TEST_SUCCEEDED} /
|
|
34
|
+
* {@link XCODEBUILD_TEST_FAILED}: xcodebuild prints
|
|
35
|
+
* `** TEST SUCCEEDED **` / `** TEST FAILED **` verdict banners, and
|
|
36
|
+
* `xcrun xcresulttool get test-results summary` reports
|
|
37
|
+
* `"result" : "Passed"`. Either counts as evidence; a recorded
|
|
38
|
+
* invocation with neither is not a passing suite.
|
|
39
|
+
*
|
|
40
|
+
* `-quiet` suppresses the verdict banner (confirmed against a real
|
|
41
|
+
* run: the last line is just `Testing started`), so with a -quiet
|
|
42
|
+
* runbook the xcresulttool readback is the ONLY green evidence the
|
|
43
|
+
* gate will see — make it a mandatory step of "run the tests".
|
|
44
|
+
*/
|
|
45
|
+
export declare const XCODEBUILD_TEST_COMMAND: RegExp;
|
|
46
|
+
export declare const XCODEBUILD_TEST_SUCCEEDED: RegExp;
|
|
47
|
+
export declare const XCODEBUILD_TEST_FAILED: RegExp;
|
|
48
|
+
/** Probe carriers for enforceProbeReversion on an Apple codebase. */
|
|
49
|
+
export declare const SWIFT_PROBE_FILE_PATTERN: RegExp;
|
|
50
|
+
//# sourceMappingURL=swift.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swift.d.ts","sourceRoot":"","sources":["../../rules/swift.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,QACsB,CAAA;AAErD;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,QAA+C,CAAA;AAE9E;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uBAAuB,QACqD,CAAA;AAEzF,eAAO,MAAM,yBAAyB,QACyC,CAAA;AAE/E,eAAO,MAAM,sBAAsB,QACqD,CAAA;AAExF,qEAAqE;AACrE,eAAO,MAAM,wBAAwB,QAAsB,CAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swift/iOS-shaped deterministic rules and constants for the Probity
|
|
3
|
+
* presets — the Apple-toolchain counterpart of ./kotlin.ts.
|
|
4
|
+
*
|
|
5
|
+
* Most of the enforcement stack is language-neutral and reused as-is
|
|
6
|
+
* (spec-test-parity, acceptance-language, glossary rules, the
|
|
7
|
+
* mutation-probe pair, the green-run gate). This module supplies only
|
|
8
|
+
* what is genuinely Swift/Xcode-specific: content screens for the
|
|
9
|
+
* acceptance suite and the xcodebuild patterns the generic gates need.
|
|
10
|
+
* Calibrated against a production iOS app (CocoaPods + SwiftPM
|
|
11
|
+
* workspace, XCUITest + app-hosted component-test targets, MVVM view
|
|
12
|
+
* models over use-case ports) — adjust to your layout.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Fixed waits in test code — the top cause of flaky XCUITest suites.
|
|
16
|
+
* Matches `sleep(2)`, `usleep(...)`, `Thread.sleep(forTimeInterval:)`,
|
|
17
|
+
* and `try await Task.sleep(...)`. Synchronize with XCTest
|
|
18
|
+
* expectations, predicate expectations, or element-existence timeouts
|
|
19
|
+
* instead (`waitForExistence(timeout:)`).
|
|
20
|
+
*/
|
|
21
|
+
export const SWIFT_FIXED_SLEEPS = /\b(?:sleep|usleep)\s*\(|Thread\.sleep|Task\.sleep/;
|
|
22
|
+
/**
|
|
23
|
+
* XCUITest mechanics — `XCUIApplication`, `XCUIElement`, coordinate
|
|
24
|
+
* taps. In the four-layer model these belong ONLY in protocol drivers
|
|
25
|
+
* (and only in the driver that owns the deployed-app scope): scope
|
|
26
|
+
* this screen to the spec/scenario/DSL layers, where any match means
|
|
27
|
+
* UI mechanics have leaked upward.
|
|
28
|
+
*/
|
|
29
|
+
export const XCUITEST_MECHANICS = /XCUIApplication|XCUIElement|XCUICoordinate/;
|
|
30
|
+
/**
|
|
31
|
+
* An `xcodebuild … test` invocation, for the green-run commit gate.
|
|
32
|
+
* Matches plain `test`, `test-without-building`, and scheme-qualified
|
|
33
|
+
* forms. Pair with {@link XCODEBUILD_TEST_SUCCEEDED} /
|
|
34
|
+
* {@link XCODEBUILD_TEST_FAILED}: xcodebuild prints
|
|
35
|
+
* `** TEST SUCCEEDED **` / `** TEST FAILED **` verdict banners, and
|
|
36
|
+
* `xcrun xcresulttool get test-results summary` reports
|
|
37
|
+
* `"result" : "Passed"`. Either counts as evidence; a recorded
|
|
38
|
+
* invocation with neither is not a passing suite.
|
|
39
|
+
*
|
|
40
|
+
* `-quiet` suppresses the verdict banner (confirmed against a real
|
|
41
|
+
* run: the last line is just `Testing started`), so with a -quiet
|
|
42
|
+
* runbook the xcresulttool readback is the ONLY green evidence the
|
|
43
|
+
* gate will see — make it a mandatory step of "run the tests".
|
|
44
|
+
*/
|
|
45
|
+
export const XCODEBUILD_TEST_COMMAND = /\bxcodebuild\b[\s\S]*\btest(?:-without-building)?\b|xcresulttool\s+get\s+test-results/;
|
|
46
|
+
export const XCODEBUILD_TEST_SUCCEEDED = /\*\*\s*TEST SUCCEEDED\s*\*\*|"result"\s*:\s*"Passed"|Test Suite '.*' passed/;
|
|
47
|
+
export const XCODEBUILD_TEST_FAILED = /\*\*\s*(?:TEST|BUILD)\s+FAILED\s*\*\*|"result"\s*:\s*"Failed"|Test Suite '.*' failed/;
|
|
48
|
+
/** Probe carriers for enforceProbeReversion on an Apple codebase. */
|
|
49
|
+
export const SWIFT_PROBE_FILE_PATTERN = /\.(?:swift|m|mm)$/;
|
|
50
|
+
//# sourceMappingURL=swift.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swift.js","sourceRoot":"","sources":["../../rules/swift.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAC7B,mDAAmD,CAAA;AAErD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,4CAA4C,CAAA;AAE9E;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAClC,uFAAuF,CAAA;AAEzF,MAAM,CAAC,MAAM,yBAAyB,GACpC,6EAA6E,CAAA;AAE/E,MAAM,CAAC,MAAM,sBAAsB,GACjC,sFAAsF,CAAA;AAExF,qEAAqE;AACrE,MAAM,CAAC,MAAM,wBAAwB,GAAG,mBAAmB,CAAA"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Rule } from '@nizos/probity';
|
|
2
|
+
export declare function extractGlossaryTerms(content: string): string[];
|
|
3
|
+
/**
|
|
4
|
+
* A term is "used" when it appears as a phrase in prose (case-
|
|
5
|
+
* insensitive, whitespace-tolerant) or as an identifier in code —
|
|
6
|
+
* PascalCase, camelCase, or snake_case concatenations of its words
|
|
7
|
+
* ("Delivery Window" → DeliveryWindow, deliveryWindow,
|
|
8
|
+
* delivery_window).
|
|
9
|
+
*/
|
|
10
|
+
export declare function termUsagePattern(term: string): RegExp;
|
|
11
|
+
/**
|
|
12
|
+
* Surfaces glossary drift at the moment it is created: when a write
|
|
13
|
+
* to the glossary removes or renames a term that specs, tests, or
|
|
14
|
+
* source code still use, the write is blocked with the list of files
|
|
15
|
+
* using the term — rename the usages in the same change (or record
|
|
16
|
+
* the old term's fate in the glossary entry) and re-apply. Removing
|
|
17
|
+
* a term nothing uses passes silently, as do additions and
|
|
18
|
+
* definition edits.
|
|
19
|
+
*
|
|
20
|
+
* The companion invariant — new spec/port vocabulary conforming to
|
|
21
|
+
* the glossary — is judged by `enforceAcceptanceLanguage` and
|
|
22
|
+
* `enforcePortsBoundary` when given `glossaryPath`.
|
|
23
|
+
*
|
|
24
|
+
* Applies to: write actions to the glossary file (scope via a
|
|
25
|
+
* `{ files, rules }` block on its path). Deterministic — no AI call.
|
|
26
|
+
*
|
|
27
|
+
* @param options.searchRoots — absolute paths to scan for usages
|
|
28
|
+
* (repo root is fine; node_modules/build dirs are skipped).
|
|
29
|
+
* @param options.searchPattern — which files count as usage sites
|
|
30
|
+
* (default: common source, spec, and doc extensions).
|
|
31
|
+
*/
|
|
32
|
+
export declare function surfaceGlossaryTermBreakage(options: {
|
|
33
|
+
searchRoots: string[];
|
|
34
|
+
searchPattern?: RegExp;
|
|
35
|
+
}): Rule;
|
|
36
|
+
//# sourceMappingURL=ubiquitous-language.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ubiquitous-language.d.ts","sourceRoot":"","sources":["../../rules/ubiquitous-language.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAU,IAAI,EAA2B,MAAM,gBAAgB,CAAA;AAiB3E,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAE9D;AAMD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAcrD;AA+BD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE;IACnD,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,GAAG,IAAI,CA8CP"}
|