cuke-dedup 0.1.1 → 0.2.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.
@@ -0,0 +1,97 @@
1
+ # Safety and limitations
2
+
3
+ CukeDedup analyzes repositories that may contain malformed or adversarial input. It does not
4
+ execute project configuration, imported modules, package scripts, matchers, or test code. Explicit
5
+ resource limits keep failures visible and bound the work performed.
6
+
7
+ ## Compatibility
8
+
9
+ - The minimum supported Rust version is 1.90.
10
+ - The npm launcher is tested on Node.js 20 and 24.
11
+ - Source adapters cover JavaScript and TypeScript, including JSX and common module variants.
12
+ - Unsupported source languages are rejected rather than guessed.
13
+ - Classic Gherkin and Gherkin Markdown are supported, including declared Gherkin dialects.
14
+ - Before version 1.0, configuration and machine-report schemas may evolve between minor releases.
15
+
16
+ ## Static-analysis boundaries
17
+
18
+ - Dynamic framework configuration may require explicit `features` or `definitions` patterns.
19
+ - Named handlers declared in the same file resolve to their bodies. Imported or unresolved handler
20
+ references remain usable for matcher and usage rules but are not compared by name.
21
+ - Malformed JavaScript and TypeScript fail closed. Unsupported regular-expression constructs warn;
22
+ malformed matcher escapes are operational errors.
23
+ - One analysis root is one corpus. Analyze independent monorepo packages separately.
24
+ - Regular expressions are matched against feature steps but are never reversed into invented
25
+ overlap witnesses.
26
+ - Bounded fuzzy indexes preserve a deterministic sample in highly repetitive vocabularies but do
27
+ not promise every possible pair after a work limit is reached.
28
+
29
+ When analysis is incomplete, existing findings remain valid but the absence of a finding proves
30
+ nothing. Machine reports expose the incomplete or truncated status. Pass `--fail-on-incomplete`
31
+ when CI must reject degraded analysis.
32
+
33
+ ## Input limits
34
+
35
+ | Input | Limit |
36
+ | --- | ---: |
37
+ | Definition source, feature file, imported registration module, or baseline | 8 MiB each |
38
+ | Package, CukeDedup, framework, or static project configuration | 1 MiB each |
39
+ | Project-resolution metadata files | 1,024 files / 16 MiB total |
40
+ | Project-resolution mappings | 16,384 |
41
+ | Workspace discovery entries | 100,000 |
42
+ | Imported registration modules | 1,024 modules / 64 MiB total |
43
+ | Memoized module path/depth states | 16,384 |
44
+
45
+ Resolution confines imports and package targets to canonical analysis or package roots and shares
46
+ cached work between importing files.
47
+
48
+ ## Matcher and Gherkin limits
49
+
50
+ - A matcher pattern or compiled program is limited to 1 MiB.
51
+ - Each matcher receives a 2 MiB lazy-DFA cache.
52
+ - Oversized matcher sets split recursively into bounded sets rather than falling back to a full
53
+ definition-by-step loop.
54
+ - Gherkin Markdown conversion collects at most 10,000 ambiguous candidates, performs at most 128
55
+ parser probes, and parses at most 64 MiB of synthesized input.
56
+ - Default-English bullets use exact Gherkin keyword filtering and one whole-document probe.
57
+ - Dialect-dependent candidates use a whole-document retry only within the cumulative byte budget;
58
+ larger inputs use bounded 32-item restoration batches.
59
+
60
+ ## Analysis limits
61
+
62
+ | Work | Limit |
63
+ | --- | ---: |
64
+ | Unique definition comparisons and static overlap pairs | 2,000,000 |
65
+ | Structural proposals per handler class | 250,000 |
66
+ | Raw overlap witness proposals | Four times the remaining unique-pair budget |
67
+ | Retained overlap findings | 10,000 |
68
+ | Trigram keys per definition / per run | 4,096 / 250,000 |
69
+ | Matcher-blocking posting proposals | 2,000,000 |
70
+ | Combined handler events | 10,000,000 |
71
+ | Pairwise or overlap suppression work | 100,000,000 units each |
72
+ | Unmatched-suppression advisory work | 10,000,000 units |
73
+ | One matcher-edit or handler-LCS matrix | 100,000,000 estimated units |
74
+ | Aggregate verification and evidence work | 1,000,000,000 units |
75
+
76
+ Project configuration and CLI flags may lower candidate and structural limits but cannot raise the
77
+ hard ceilings. Matcher similarity is evaluated before reserving expensive handler-LCS work.
78
+
79
+ Fuzzy matcher discovery uses collision-free, fixed-width, case-folded character trigrams with
80
+ digit runs normalized. Unsaturated posting lists are fully considered. Lists above 256 definitions
81
+ use deterministic lexical neighbors instead of quadratic expansion. Candidates must be capable of
82
+ reaching the 50% handler-behavior gate and—unless structurally equivalent—share a canonical call
83
+ action.
84
+
85
+ Reaching a candidate-generation or verification limit preserves evaluated findings, warns, and
86
+ sets `analysis.truncated: true` with a nonzero skipped count. Exit status follows finding severity
87
+ unless `--fail-on-incomplete` is enabled. Baselines are never updated from incomplete analysis.
88
+
89
+ ## Report safety
90
+
91
+ Reports bound source snippets and cluster memberships, escape HTML, encode script-closing JSON
92
+ characters, and remove bidirectional, invisible-format, and Unicode Tag characters. JSONL content
93
+ still originates in the analyzed repository and must be treated as untrusted data rather than
94
+ agent instructions.
95
+
96
+ See [Reports](reports.md) for presentation limits and [Discovery and frameworks](discovery-and-frameworks.md)
97
+ for corpus-completeness rules.
@@ -1,16 +1,13 @@
1
- import { accessSync, constants, existsSync, readFileSync } from "node:fs";
1
+ import { accessSync, constants, existsSync } from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { constants as osConstants } from "node:os";
6
+ import { TARGETS, targetFor as validatedTargetFor } from "./targets.mjs";
6
7
 
7
8
  const require = createRequire(import.meta.url);
8
9
 
9
- const targetManifest = JSON.parse(
10
- readFileSync(new URL("../prebuilt-targets.json", import.meta.url), "utf8"),
11
- );
12
-
13
- export const TARGETS = Object.freeze(targetManifest.targets);
10
+ export { TARGETS };
14
11
 
15
12
  export function detectLibc(report = process.report) {
16
13
  try {
@@ -25,14 +22,7 @@ export function targetFor(
25
22
  arch = process.arch,
26
23
  libc = platform === "linux" ? detectLibc() : undefined,
27
24
  ) {
28
- const key = platform === "linux" ? `${platform}-${arch}-${libc}` : `${platform}-${arch}`;
29
- const target = TARGETS[key];
30
- if (!target) {
31
- throw new Error(
32
- `unsupported platform ${key}; supported platforms: ${Object.keys(TARGETS).join(", ")}`,
33
- );
34
- }
35
- return target;
25
+ return validatedTargetFor(platform, arch, libc);
36
26
  }
37
27
 
38
28
  export function resolveBinary({
@@ -0,0 +1,134 @@
1
+ import { readFileSync } from "node:fs";
2
+
3
+ const manifest = JSON.parse(
4
+ readFileSync(new URL("../prebuilt-targets.json", import.meta.url), "utf8"),
5
+ );
6
+
7
+ const EXPECTED_TARGETS = Object.freeze({
8
+ "darwin-arm64": Object.freeze({
9
+ rustTarget: "aarch64-apple-darwin",
10
+ packageName: "cuke-dedup-darwin-arm64",
11
+ packageDirectory: "darwin-arm64",
12
+ platform: "darwin",
13
+ arch: "arm64",
14
+ runner: "macos-15",
15
+ binaryName: "cuke-dedup",
16
+ }),
17
+ "darwin-x64": Object.freeze({
18
+ rustTarget: "x86_64-apple-darwin",
19
+ packageName: "cuke-dedup-darwin-x64",
20
+ packageDirectory: "darwin-x64",
21
+ platform: "darwin",
22
+ arch: "x64",
23
+ runner: "macos-15-intel",
24
+ binaryName: "cuke-dedup",
25
+ }),
26
+ "linux-arm64-gnu": Object.freeze({
27
+ rustTarget: "aarch64-unknown-linux-gnu",
28
+ packageName: "cuke-dedup-linux-arm64-gnu",
29
+ packageDirectory: "linux-arm64-gnu",
30
+ platform: "linux",
31
+ arch: "arm64",
32
+ libc: "gnu",
33
+ runner: "ubuntu-22.04-arm",
34
+ binaryName: "cuke-dedup",
35
+ }),
36
+ "linux-arm64-musl": Object.freeze({
37
+ rustTarget: "aarch64-unknown-linux-musl",
38
+ packageName: "cuke-dedup-linux-arm64-musl",
39
+ packageDirectory: "linux-arm64-musl",
40
+ platform: "linux",
41
+ arch: "arm64",
42
+ libc: "musl",
43
+ runner: "ubuntu-22.04-arm",
44
+ binaryName: "cuke-dedup",
45
+ }),
46
+ "linux-x64-gnu": Object.freeze({
47
+ rustTarget: "x86_64-unknown-linux-gnu",
48
+ packageName: "cuke-dedup-linux-x64-gnu",
49
+ packageDirectory: "linux-x64-gnu",
50
+ platform: "linux",
51
+ arch: "x64",
52
+ libc: "gnu",
53
+ runner: "ubuntu-22.04",
54
+ binaryName: "cuke-dedup",
55
+ }),
56
+ "linux-x64-musl": Object.freeze({
57
+ rustTarget: "x86_64-unknown-linux-musl",
58
+ packageName: "cuke-dedup-linux-x64-musl",
59
+ packageDirectory: "linux-x64-musl",
60
+ platform: "linux",
61
+ arch: "x64",
62
+ libc: "musl",
63
+ runner: "ubuntu-22.04",
64
+ binaryName: "cuke-dedup",
65
+ }),
66
+ "win32-arm64": Object.freeze({
67
+ rustTarget: "aarch64-pc-windows-msvc",
68
+ packageName: "cuke-dedup-windows-arm64-msvc",
69
+ packageDirectory: "win32-arm64",
70
+ platform: "win32",
71
+ arch: "arm64",
72
+ runner: "windows-11-arm",
73
+ binaryName: "cuke-dedup.exe",
74
+ }),
75
+ "win32-x64": Object.freeze({
76
+ rustTarget: "x86_64-pc-windows-msvc",
77
+ packageName: "cuke-dedup-windows-x64-msvc",
78
+ packageDirectory: "win32-x64",
79
+ platform: "win32",
80
+ arch: "x64",
81
+ runner: "windows-latest",
82
+ binaryName: "cuke-dedup.exe",
83
+ }),
84
+ });
85
+
86
+ export function validateTargets(targets) {
87
+ const actualKeys = Object.keys(targets ?? {}).sort();
88
+ const expectedKeys = Object.keys(EXPECTED_TARGETS).sort();
89
+ if (JSON.stringify(actualKeys) !== JSON.stringify(expectedKeys)) {
90
+ throw new Error("prebuilt target keys do not match the supported release targets");
91
+ }
92
+ for (const key of expectedKeys) {
93
+ validateTarget(key, targets[key]);
94
+ }
95
+ return targets;
96
+ }
97
+
98
+ export function validateTarget(key, target) {
99
+ const expected = EXPECTED_TARGETS[key];
100
+ if (!expected) {
101
+ throw new Error(`unsupported release target ${key}`);
102
+ }
103
+ const actualKeys = Object.keys(target ?? {}).sort();
104
+ const expectedKeys = Object.keys(expected).sort();
105
+ if (
106
+ JSON.stringify(actualKeys) !== JSON.stringify(expectedKeys)
107
+ || expectedKeys.some((field) => target[field] !== expected[field])
108
+ ) {
109
+ throw new Error(`invalid release target metadata for ${key}`);
110
+ }
111
+ return target;
112
+ }
113
+
114
+ export const TARGETS = Object.freeze(Object.fromEntries(
115
+ Object.entries(validateTargets(manifest.targets)).map(
116
+ ([key, target]) => [key, Object.freeze({ ...target })],
117
+ ),
118
+ ));
119
+
120
+ export function targetFor(
121
+ platform,
122
+ arch,
123
+ libc,
124
+ targets = TARGETS,
125
+ ) {
126
+ const key = platform === "linux" ? `${platform}-${arch}-${libc}` : `${platform}-${arch}`;
127
+ const target = targets[key];
128
+ if (!target) {
129
+ throw new Error(
130
+ `unsupported platform ${key}; supported platforms: ${Object.keys(EXPECTED_TARGETS).join(", ")}`,
131
+ );
132
+ }
133
+ return validateTarget(key, target);
134
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cuke-dedup",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Static analysis for duplicate and reusable Cucumber step definitions",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,6 +11,7 @@
11
11
  "npm/lib/",
12
12
  "npm/prebuilt-targets.json",
13
13
  "README.md",
14
+ "docs/",
14
15
  "LICENSE",
15
16
  "CHANGELOG.md",
16
17
  "SECURITY.md",
@@ -45,13 +46,13 @@
45
46
  "npm/platforms/*"
46
47
  ],
47
48
  "optionalDependencies": {
48
- "cuke-dedup-darwin-arm64": "0.1.1",
49
- "cuke-dedup-darwin-x64": "0.1.1",
50
- "cuke-dedup-linux-arm64-gnu": "0.1.1",
51
- "cuke-dedup-linux-arm64-musl": "0.1.1",
52
- "cuke-dedup-linux-x64-gnu": "0.1.1",
53
- "cuke-dedup-linux-x64-musl": "0.1.1",
54
- "cuke-dedup-windows-arm64-msvc": "0.1.1",
55
- "cuke-dedup-windows-x64-msvc": "0.1.1"
49
+ "cuke-dedup-darwin-arm64": "0.2.0",
50
+ "cuke-dedup-darwin-x64": "0.2.0",
51
+ "cuke-dedup-linux-arm64-gnu": "0.2.0",
52
+ "cuke-dedup-linux-arm64-musl": "0.2.0",
53
+ "cuke-dedup-linux-x64-gnu": "0.2.0",
54
+ "cuke-dedup-linux-x64-musl": "0.2.0",
55
+ "cuke-dedup-windows-arm64-msvc": "0.2.0",
56
+ "cuke-dedup-windows-x64-msvc": "0.2.0"
56
57
  }
57
58
  }