@afokapu/atdd-bun 0.10.67 → 0.10.69
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/detectors/atdd_traceability_closure/detect.mjs +5 -1
- package/detectors/bun_clean_architecture_detector/detect.mjs +30 -13
- package/detectors/bun_design_system_detector/detect.mjs +30 -13
- package/detectors/bun_fullstack_detector/detect.mjs +30 -13
- package/detectors/bun_green_traceability_detector/detect.mjs +30 -13
- package/detectors/bun_interlocking_coverage/detect.mjs +30 -13
- package/detectors/bun_interlocking_infrastructure/checks/journey_runner_boundary.mjs +36 -5
- package/detectors/bun_interlocking_infrastructure/detect.mjs +30 -13
- package/detectors/bun_responsive_detector/detect.mjs +30 -13
- package/detectors/bun_security_hygiene_detector/detect.mjs +30 -13
- package/detectors/bun_telemetry_code/detect.mjs +34 -24
- package/detectors/bun_telemetry_test/detect.mjs +34 -24
- package/detectors/bun_tester_discipline_detector/detect.mjs +30 -13
- package/detectors/bun_ts_metrics_detector/checks/ts_dead_code.mjs +11 -1
- package/detectors/bun_ts_metrics_detector/detect.mjs +30 -13
- package/detectors/htmx_e2e_detector/detect.mjs +30 -13
- package/detectors/htmx_hypermedia_detector/detect.mjs +30 -13
- package/detectors/htmx_tester_detector/detect.mjs +30 -13
- package/integrity.json +20 -20
- package/lib/journey-chain.mjs +29 -1
- package/package.json +1 -1
|
@@ -62,7 +62,11 @@ for (const root of roots) {
|
|
|
62
62
|
// EVERY binding header is judged, not only the first: a valid Acceptance: must not carry an unresolved Train:.
|
|
63
63
|
const headers = [...head.matchAll(/^[ \t]*\/\/[ \t]*(Acceptance|WMBT|Train):[ \t]*(\S*)/gm)].map((m) => ({ kind: m[1], id: m[2], raw: m[0].trim(), line: lineAt(head, m.index) }));
|
|
64
64
|
const bindings = headers.filter((h) => h.id.startsWith(PREFIX[h.kind]));
|
|
65
|
-
|
|
65
|
+
// A test identity may be reused by equivalent smoke harnesses. Preserve every
|
|
66
|
+
// independently declared binding: replacing the earlier record made a valid
|
|
67
|
+
// acceptance disappear solely because a later file had the same test URN.
|
|
68
|
+
const prior = tests.get(urn[1]);
|
|
69
|
+
tests.set(urn[1], { path, bindings: [...(prior?.bindings ?? []), ...bindings] });
|
|
66
70
|
if (!headers.length) add("traceability.test.binding-resolves", path, lineOf(head, urn[0]), "test has a URN but no Acceptance:, WMBT:, or Train: binding", urn[0]);
|
|
67
71
|
for (const h of headers) if (!bindings.includes(h)) add("traceability.test.binding-resolves", path, h.line, `${h.kind}: ${h.id || "<empty>"} is not a ${PREFIX[h.kind]} identity`, h.raw);
|
|
68
72
|
return;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// their checks re-implements its own walker; this provider factors the walk into
|
|
12
12
|
// lib/scan.mjs instead.)
|
|
13
13
|
import { execFileSync } from "node:child_process";
|
|
14
|
-
import { readFileSync, writeFileSync, mkdtempSync, readdirSync } from "node:fs";
|
|
14
|
+
import { readFileSync, writeFileSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
16
|
import { join, dirname } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
@@ -27,19 +27,36 @@ const checks = readdirSync(join(here, "checks"))
|
|
|
27
27
|
.sort();
|
|
28
28
|
const td = mkdtempSync(join(tmpdir(), "atdd-bun-fam-"));
|
|
29
29
|
const out = [];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
// FAIL CLOSED. A member that cannot run (crash, non-zero exit) or leaves no usable report is a detector
|
|
31
|
+
// failure, never "zero findings": in a ratchet a silent zero on the candidate side reads as resolved debt and
|
|
32
|
+
// masks genuinely new findings. A member that ran and found nothing writes {"violations": []} and passes.
|
|
33
|
+
const failures = [];
|
|
34
|
+
try {
|
|
35
|
+
for (const c of checks) {
|
|
36
|
+
const rep = join(td, c + ".json");
|
|
37
|
+
try {
|
|
38
|
+
execFileSync(process.execPath, [join(here, "checks", c)], {
|
|
39
|
+
env: { ...process.env, ATDD_VIOLATIONS_REPORT: rep },
|
|
40
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
failures.push(`member ${c} failed to run: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const report = JSON.parse(readFileSync(rep, "utf8"));
|
|
48
|
+
if (!report || !Array.isArray(report.violations)) throw new Error("report has no violations array");
|
|
49
|
+
out.push(...report.violations);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
failures.push(`member ${c} left no usable violation report: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
39
53
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
} finally {
|
|
55
|
+
rmSync(td, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
if (failures.length) {
|
|
58
|
+
for (const failure of failures) process.stderr.write("family bun_clean_architecture_detector: " + failure + "\n");
|
|
59
|
+
process.exit(2); // the run failed: the caller must report a failure, not a verdict
|
|
43
60
|
}
|
|
44
61
|
writeFileSync(reportPath, JSON.stringify({ violations: out }, null, 2), "utf8");
|
|
45
62
|
process.stderr.write("family bun_clean_architecture_detector: " + out.length + " violation(s)\n");
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// their checks re-implements its own walker; this provider factors the walk into
|
|
12
12
|
// lib/scan.mjs instead.)
|
|
13
13
|
import { execFileSync } from "node:child_process";
|
|
14
|
-
import { readFileSync, writeFileSync, mkdtempSync, readdirSync } from "node:fs";
|
|
14
|
+
import { readFileSync, writeFileSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
16
|
import { join, dirname } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
@@ -27,19 +27,36 @@ const checks = readdirSync(join(here, "checks"))
|
|
|
27
27
|
.sort();
|
|
28
28
|
const td = mkdtempSync(join(tmpdir(), "atdd-bun-fam-"));
|
|
29
29
|
const out = [];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
// FAIL CLOSED. A member that cannot run (crash, non-zero exit) or leaves no usable report is a detector
|
|
31
|
+
// failure, never "zero findings": in a ratchet a silent zero on the candidate side reads as resolved debt and
|
|
32
|
+
// masks genuinely new findings. A member that ran and found nothing writes {"violations": []} and passes.
|
|
33
|
+
const failures = [];
|
|
34
|
+
try {
|
|
35
|
+
for (const c of checks) {
|
|
36
|
+
const rep = join(td, c + ".json");
|
|
37
|
+
try {
|
|
38
|
+
execFileSync(process.execPath, [join(here, "checks", c)], {
|
|
39
|
+
env: { ...process.env, ATDD_VIOLATIONS_REPORT: rep },
|
|
40
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
failures.push(`member ${c} failed to run: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const report = JSON.parse(readFileSync(rep, "utf8"));
|
|
48
|
+
if (!report || !Array.isArray(report.violations)) throw new Error("report has no violations array");
|
|
49
|
+
out.push(...report.violations);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
failures.push(`member ${c} left no usable violation report: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
39
53
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
} finally {
|
|
55
|
+
rmSync(td, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
if (failures.length) {
|
|
58
|
+
for (const failure of failures) process.stderr.write("family bun_design_system_detector: " + failure + "\n");
|
|
59
|
+
process.exit(2); // the run failed: the caller must report a failure, not a verdict
|
|
43
60
|
}
|
|
44
61
|
writeFileSync(reportPath, JSON.stringify({ violations: out }, null, 2), "utf8");
|
|
45
62
|
process.stderr.write("family bun_design_system_detector: " + out.length + " violation(s)\n");
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// their checks re-implements its own walker; this provider factors the walk into
|
|
12
12
|
// lib/scan.mjs instead.)
|
|
13
13
|
import { execFileSync } from "node:child_process";
|
|
14
|
-
import { readFileSync, writeFileSync, mkdtempSync, readdirSync } from "node:fs";
|
|
14
|
+
import { readFileSync, writeFileSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
16
|
import { join, dirname } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
@@ -27,19 +27,36 @@ const checks = readdirSync(join(here, "checks"))
|
|
|
27
27
|
.sort();
|
|
28
28
|
const td = mkdtempSync(join(tmpdir(), "atdd-bun-fam-"));
|
|
29
29
|
const out = [];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
// FAIL CLOSED. A member that cannot run (crash, non-zero exit) or leaves no usable report is a detector
|
|
31
|
+
// failure, never "zero findings": in a ratchet a silent zero on the candidate side reads as resolved debt and
|
|
32
|
+
// masks genuinely new findings. A member that ran and found nothing writes {"violations": []} and passes.
|
|
33
|
+
const failures = [];
|
|
34
|
+
try {
|
|
35
|
+
for (const c of checks) {
|
|
36
|
+
const rep = join(td, c + ".json");
|
|
37
|
+
try {
|
|
38
|
+
execFileSync(process.execPath, [join(here, "checks", c)], {
|
|
39
|
+
env: { ...process.env, ATDD_VIOLATIONS_REPORT: rep },
|
|
40
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
failures.push(`member ${c} failed to run: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const report = JSON.parse(readFileSync(rep, "utf8"));
|
|
48
|
+
if (!report || !Array.isArray(report.violations)) throw new Error("report has no violations array");
|
|
49
|
+
out.push(...report.violations);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
failures.push(`member ${c} left no usable violation report: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
39
53
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
} finally {
|
|
55
|
+
rmSync(td, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
if (failures.length) {
|
|
58
|
+
for (const failure of failures) process.stderr.write("family bun_fullstack_detector: " + failure + "\n");
|
|
59
|
+
process.exit(2); // the run failed: the caller must report a failure, not a verdict
|
|
43
60
|
}
|
|
44
61
|
writeFileSync(reportPath, JSON.stringify({ violations: out }, null, 2), "utf8");
|
|
45
62
|
process.stderr.write("family bun_fullstack_detector: " + out.length + " violation(s)\n");
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// their checks re-implements its own walker; this provider factors the walk into
|
|
12
12
|
// lib/scan.mjs instead.)
|
|
13
13
|
import { execFileSync } from "node:child_process";
|
|
14
|
-
import { readFileSync, writeFileSync, mkdtempSync, readdirSync } from "node:fs";
|
|
14
|
+
import { readFileSync, writeFileSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
16
|
import { join, dirname } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
@@ -27,19 +27,36 @@ const checks = readdirSync(join(here, "checks"))
|
|
|
27
27
|
.sort();
|
|
28
28
|
const td = mkdtempSync(join(tmpdir(), "atdd-bun-fam-"));
|
|
29
29
|
const out = [];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
// FAIL CLOSED. A member that cannot run (crash, non-zero exit) or leaves no usable report is a detector
|
|
31
|
+
// failure, never "zero findings": in a ratchet a silent zero on the candidate side reads as resolved debt and
|
|
32
|
+
// masks genuinely new findings. A member that ran and found nothing writes {"violations": []} and passes.
|
|
33
|
+
const failures = [];
|
|
34
|
+
try {
|
|
35
|
+
for (const c of checks) {
|
|
36
|
+
const rep = join(td, c + ".json");
|
|
37
|
+
try {
|
|
38
|
+
execFileSync(process.execPath, [join(here, "checks", c)], {
|
|
39
|
+
env: { ...process.env, ATDD_VIOLATIONS_REPORT: rep },
|
|
40
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
failures.push(`member ${c} failed to run: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const report = JSON.parse(readFileSync(rep, "utf8"));
|
|
48
|
+
if (!report || !Array.isArray(report.violations)) throw new Error("report has no violations array");
|
|
49
|
+
out.push(...report.violations);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
failures.push(`member ${c} left no usable violation report: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
39
53
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
} finally {
|
|
55
|
+
rmSync(td, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
if (failures.length) {
|
|
58
|
+
for (const failure of failures) process.stderr.write("family bun_green_traceability_detector: " + failure + "\n");
|
|
59
|
+
process.exit(2); // the run failed: the caller must report a failure, not a verdict
|
|
43
60
|
}
|
|
44
61
|
writeFileSync(reportPath, JSON.stringify({ violations: out }, null, 2), "utf8");
|
|
45
62
|
process.stderr.write("family bun_green_traceability_detector: " + out.length + " violation(s)\n");
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// their checks re-implements its own walker; this provider factors the walk into
|
|
12
12
|
// lib/scan.mjs instead.)
|
|
13
13
|
import { execFileSync } from "node:child_process";
|
|
14
|
-
import { readFileSync, writeFileSync, mkdtempSync, readdirSync } from "node:fs";
|
|
14
|
+
import { readFileSync, writeFileSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
16
|
import { join, dirname } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
@@ -27,19 +27,36 @@ const checks = readdirSync(join(here, "checks"))
|
|
|
27
27
|
.sort();
|
|
28
28
|
const td = mkdtempSync(join(tmpdir(), "atdd-bun-ilc-"));
|
|
29
29
|
const out = [];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
// FAIL CLOSED. A member that cannot run (crash, non-zero exit) or leaves no usable report is a detector
|
|
31
|
+
// failure, never "zero findings": in a ratchet a silent zero on the candidate side reads as resolved debt and
|
|
32
|
+
// masks genuinely new findings. A member that ran and found nothing writes {"violations": []} and passes.
|
|
33
|
+
const failures = [];
|
|
34
|
+
try {
|
|
35
|
+
for (const c of checks) {
|
|
36
|
+
const rep = join(td, c + ".json");
|
|
37
|
+
try {
|
|
38
|
+
execFileSync(process.execPath, [join(here, "checks", c)], {
|
|
39
|
+
env: { ...process.env, ATDD_VIOLATIONS_REPORT: rep },
|
|
40
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
failures.push(`member ${c} failed to run: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const report = JSON.parse(readFileSync(rep, "utf8"));
|
|
48
|
+
if (!report || !Array.isArray(report.violations)) throw new Error("report has no violations array");
|
|
49
|
+
out.push(...report.violations);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
failures.push(`member ${c} left no usable violation report: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
39
53
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
} finally {
|
|
55
|
+
rmSync(td, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
if (failures.length) {
|
|
58
|
+
for (const failure of failures) process.stderr.write("family bun_interlocking_coverage: " + failure + "\n");
|
|
59
|
+
process.exit(2); // the run failed: the caller must report a failure, not a verdict
|
|
43
60
|
}
|
|
44
61
|
writeFileSync(reportPath, JSON.stringify({ violations: out }, null, 2), "utf8");
|
|
45
62
|
process.stderr.write("family bun_interlocking_coverage: " + out.length + " violation(s)\n");
|
|
@@ -47,25 +47,56 @@ function journeyRunnerLine(text) {
|
|
|
47
47
|
return match ? lineOfIndex(text, match.index) : 1;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// Declaration helpers that verifiably read AND parse the file named by their own parameter.
|
|
51
|
+
function declarationHelpers(masked) {
|
|
52
|
+
const names = [];
|
|
53
|
+
const helper = /\b(?:async\s+)?function\s+((?=[A-Za-z_$\w]*(?:declaration|journey))[A-Za-z_$][\w$]*)\s*\(\s*([A-Za-z_$][\w$]*)?[^)]*\)\s*(?::[^\{]+)?\{([\s\S]*?)\n\}/gi;
|
|
54
|
+
for (const match of masked.matchAll(helper)) {
|
|
55
|
+
const [, name, param, body] = match;
|
|
56
|
+
if (!param || !/\b(?:Bun\.YAML\.parse|JSON\.parse)\s*\(/.test(body)) continue;
|
|
57
|
+
// The read argument must reference the helper's own parameter, not a fixed path.
|
|
58
|
+
const paramName = param.replace(/[$]/g, "\\$");
|
|
59
|
+
if (!new RegExp(`\\b(?:Bun\\.file|readFile(?:Sync)?)\\s*\\(\\s*[^)]*\\b${paramName}\\b`).test(body)) continue;
|
|
60
|
+
// JourneyRunner must pass its own declared path to that helper.
|
|
61
|
+
const callee = name.replace(/[$]/g, "\\$");
|
|
62
|
+
if (new RegExp(`\\b${callee}\\s*\\(\\s*this\\s*\\.\\s*journeyYamlPath\\s*\\)`).test(masked)) names.push(name);
|
|
63
|
+
}
|
|
64
|
+
return names;
|
|
65
|
+
}
|
|
66
|
+
|
|
50
67
|
function loadsJourneyDeclaration(text) {
|
|
51
68
|
const masked = maskComments(text);
|
|
52
69
|
const readsJourneyPath =
|
|
53
70
|
/Bun\.file\s*\(\s*[^)]*(?:journey|_journeys)/i.test(masked) ||
|
|
54
71
|
/\breadFile(?:Sync)?\s*\(\s*[^,)]*(?:journey|_journeys)/i.test(masked);
|
|
55
72
|
const parsesDeclaration = /Bun\.YAML\.parse\s*\(|JSON\.parse\s*\(/.test(masked);
|
|
56
|
-
|
|
73
|
+
if (readsJourneyPath && parsesDeclaration) return true;
|
|
74
|
+
// Production runners may centralize parsing and validation in a declaration helper.
|
|
75
|
+
return declarationHelpers(masked).length > 0;
|
|
57
76
|
}
|
|
58
77
|
|
|
59
78
|
function consumesDeclaredTopology(text) {
|
|
60
79
|
const masked = maskComments(text);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
80
|
+
// The topology variable must come from the original parse or from the one verified helper
|
|
81
|
+
// called with this.journeyYamlPath; a topology from any other call is not the declaration.
|
|
82
|
+
const escape = (value) => value.replace(/[$]/g, "\\$");
|
|
83
|
+
const sources = ["Bun\\.YAML\\.parse\\s*\\(", ...declarationHelpers(masked).map(helper =>
|
|
84
|
+
`(?:await\\s+)?${escape(helper)}\\s*\\(\\s*this\\s*\\.\\s*journeyYamlPath\\s*\\)`)];
|
|
85
|
+
const declarations = [...masked.matchAll(new RegExp("\\b(?:const|let)\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*(?:" + sources.join("|") + ")", "g"))];
|
|
86
|
+
return declarations.some(declaration => consumesTopologyFrom(masked, declaration[1]));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function consumesTopologyFrom(masked, variable) {
|
|
90
|
+
const name = variable.replace(/[$]/g, "\\$");
|
|
64
91
|
const reads = (field) => new RegExp("\\b" + name + "\\s*\\.\\s*" + field + "\\s*\\.\\s*find\\s*\\(").test(masked);
|
|
65
92
|
// A runner must retain the interlocking result long enough to decide whether the
|
|
66
93
|
// declared route continues or terminates. Returning the first execute() directly
|
|
67
94
|
// is the characteristic skipped-continuation mutation.
|
|
68
|
-
const
|
|
95
|
+
const execution = "await\\s+(?:new\\s+InterlockingRunner\\s*\\([^)]*\\)|[A-Za-z_$][\\w$]*)\\s*\\.\\s*execute\\s*\\(";
|
|
96
|
+
// The result may also be declared first and assigned once inside a try that has a catch:
|
|
97
|
+
// `let run: T; try { run = await runner.execute(...) } catch { throw error }`.
|
|
98
|
+
const retainsResolution = new RegExp("\\b(?:const|let)\\s+[A-Za-z_$][\\w$]*\\s*=\\s*" + execution).test(masked) ||
|
|
99
|
+
new RegExp("\\blet\\s+([A-Za-z_$][\\w$]*)\\s*(?::[^=;]+)?;\\s*try\\s*\\{\\s*\\1\\s*=\\s*" + execution + "[\\s\\S]{0,400}?\\}\\s*catch\\s*(?:\\([^)]*\\))?\\s*\\{[^}]*\\b(?:throw|return)\\b").test(masked);
|
|
69
100
|
const readsEntrypoint = new RegExp("\\b" + name + "\\s*\\.\\s*entrypoint\\b").test(masked);
|
|
70
101
|
// Reading both lists is only discovery. A continuation must alter control flow to
|
|
71
102
|
// another declared interlocking and a terminal must decide the returned outcome;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// their checks re-implements its own walker; this provider factors the walk into
|
|
12
12
|
// lib/scan.mjs instead.)
|
|
13
13
|
import { execFileSync } from "node:child_process";
|
|
14
|
-
import { readFileSync, writeFileSync, mkdtempSync, readdirSync } from "node:fs";
|
|
14
|
+
import { readFileSync, writeFileSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
16
|
import { join, dirname } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
@@ -27,19 +27,36 @@ const checks = readdirSync(join(here, "checks"))
|
|
|
27
27
|
.sort();
|
|
28
28
|
const td = mkdtempSync(join(tmpdir(), "atdd-bun-il-"));
|
|
29
29
|
const out = [];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
// FAIL CLOSED. A member that cannot run (crash, non-zero exit) or leaves no usable report is a detector
|
|
31
|
+
// failure, never "zero findings": in a ratchet a silent zero on the candidate side reads as resolved debt and
|
|
32
|
+
// masks genuinely new findings. A member that ran and found nothing writes {"violations": []} and passes.
|
|
33
|
+
const failures = [];
|
|
34
|
+
try {
|
|
35
|
+
for (const c of checks) {
|
|
36
|
+
const rep = join(td, c + ".json");
|
|
37
|
+
try {
|
|
38
|
+
execFileSync(process.execPath, [join(here, "checks", c)], {
|
|
39
|
+
env: { ...process.env, ATDD_VIOLATIONS_REPORT: rep },
|
|
40
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
failures.push(`member ${c} failed to run: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const report = JSON.parse(readFileSync(rep, "utf8"));
|
|
48
|
+
if (!report || !Array.isArray(report.violations)) throw new Error("report has no violations array");
|
|
49
|
+
out.push(...report.violations);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
failures.push(`member ${c} left no usable violation report: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
39
53
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
} finally {
|
|
55
|
+
rmSync(td, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
if (failures.length) {
|
|
58
|
+
for (const failure of failures) process.stderr.write("family bun_interlocking_infrastructure: " + failure + "\n");
|
|
59
|
+
process.exit(2); // the run failed: the caller must report a failure, not a verdict
|
|
43
60
|
}
|
|
44
61
|
writeFileSync(reportPath, JSON.stringify({ violations: out }, null, 2), "utf8");
|
|
45
62
|
process.stderr.write("family bun_interlocking_infrastructure: " + out.length + " violation(s)\n");
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// their checks re-implements its own walker; this provider factors the walk into
|
|
12
12
|
// lib/scan.mjs instead.)
|
|
13
13
|
import { execFileSync } from "node:child_process";
|
|
14
|
-
import { readFileSync, writeFileSync, mkdtempSync, readdirSync } from "node:fs";
|
|
14
|
+
import { readFileSync, writeFileSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
16
|
import { join, dirname } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
@@ -27,19 +27,36 @@ const checks = readdirSync(join(here, "checks"))
|
|
|
27
27
|
.sort();
|
|
28
28
|
const td = mkdtempSync(join(tmpdir(), "atdd-bun-fam-"));
|
|
29
29
|
const out = [];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
// FAIL CLOSED. A member that cannot run (crash, non-zero exit) or leaves no usable report is a detector
|
|
31
|
+
// failure, never "zero findings": in a ratchet a silent zero on the candidate side reads as resolved debt and
|
|
32
|
+
// masks genuinely new findings. A member that ran and found nothing writes {"violations": []} and passes.
|
|
33
|
+
const failures = [];
|
|
34
|
+
try {
|
|
35
|
+
for (const c of checks) {
|
|
36
|
+
const rep = join(td, c + ".json");
|
|
37
|
+
try {
|
|
38
|
+
execFileSync(process.execPath, [join(here, "checks", c)], {
|
|
39
|
+
env: { ...process.env, ATDD_VIOLATIONS_REPORT: rep },
|
|
40
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
failures.push(`member ${c} failed to run: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const report = JSON.parse(readFileSync(rep, "utf8"));
|
|
48
|
+
if (!report || !Array.isArray(report.violations)) throw new Error("report has no violations array");
|
|
49
|
+
out.push(...report.violations);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
failures.push(`member ${c} left no usable violation report: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
39
53
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
} finally {
|
|
55
|
+
rmSync(td, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
if (failures.length) {
|
|
58
|
+
for (const failure of failures) process.stderr.write("family bun_responsive_detector: " + failure + "\n");
|
|
59
|
+
process.exit(2); // the run failed: the caller must report a failure, not a verdict
|
|
43
60
|
}
|
|
44
61
|
writeFileSync(reportPath, JSON.stringify({ violations: out }, null, 2), "utf8");
|
|
45
62
|
process.stderr.write("family bun_responsive_detector: " + out.length + " violation(s)\n");
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// their checks re-implements its own walker; this provider factors the walk into
|
|
12
12
|
// lib/scan.mjs instead.)
|
|
13
13
|
import { execFileSync } from "node:child_process";
|
|
14
|
-
import { readFileSync, writeFileSync, mkdtempSync, readdirSync } from "node:fs";
|
|
14
|
+
import { readFileSync, writeFileSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
16
|
import { join, dirname } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
@@ -27,19 +27,36 @@ const checks = readdirSync(join(here, "checks"))
|
|
|
27
27
|
.sort();
|
|
28
28
|
const td = mkdtempSync(join(tmpdir(), "atdd-bun-fam-"));
|
|
29
29
|
const out = [];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
// FAIL CLOSED. A member that cannot run (crash, non-zero exit) or leaves no usable report is a detector
|
|
31
|
+
// failure, never "zero findings": in a ratchet a silent zero on the candidate side reads as resolved debt and
|
|
32
|
+
// masks genuinely new findings. A member that ran and found nothing writes {"violations": []} and passes.
|
|
33
|
+
const failures = [];
|
|
34
|
+
try {
|
|
35
|
+
for (const c of checks) {
|
|
36
|
+
const rep = join(td, c + ".json");
|
|
37
|
+
try {
|
|
38
|
+
execFileSync(process.execPath, [join(here, "checks", c)], {
|
|
39
|
+
env: { ...process.env, ATDD_VIOLATIONS_REPORT: rep },
|
|
40
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
failures.push(`member ${c} failed to run: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const report = JSON.parse(readFileSync(rep, "utf8"));
|
|
48
|
+
if (!report || !Array.isArray(report.violations)) throw new Error("report has no violations array");
|
|
49
|
+
out.push(...report.violations);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
failures.push(`member ${c} left no usable violation report: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
39
53
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
} finally {
|
|
55
|
+
rmSync(td, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
if (failures.length) {
|
|
58
|
+
for (const failure of failures) process.stderr.write("family bun_security_hygiene_detector: " + failure + "\n");
|
|
59
|
+
process.exit(2); // the run failed: the caller must report a failure, not a verdict
|
|
43
60
|
}
|
|
44
61
|
writeFileSync(reportPath, JSON.stringify({ violations: out }, null, 2), "utf8");
|
|
45
62
|
process.stderr.write("family bun_security_hygiene_detector: " + out.length + " violation(s)\n");
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// RAW v1.1 reports into one — one implementation realizing a family of rule_ids.
|
|
5
5
|
// Files whose name begins with `_` are skipped: they are shared helpers.
|
|
6
6
|
import { execFileSync } from "node:child_process";
|
|
7
|
-
import { readFileSync, writeFileSync, mkdtempSync, readdirSync } from "node:fs";
|
|
7
|
+
import { readFileSync, writeFileSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
8
8
|
import { tmpdir } from "node:os";
|
|
9
9
|
import { join, dirname } from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
@@ -20,31 +20,41 @@ const checks = readdirSync(join(here, "checks"))
|
|
|
20
20
|
.sort();
|
|
21
21
|
const td = mkdtempSync(join(tmpdir(), "atdd-bun-fam-"));
|
|
22
22
|
const out = [];
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
23
|
+
// FAIL CLOSED. A member that cannot run (crash, non-zero exit) or leaves no usable report is a detector
|
|
24
|
+
// failure, never "zero findings": in a ratchet a silent zero on the candidate side reads as resolved debt and
|
|
25
|
+
// masks genuinely new findings. A member that ran and found nothing writes {"violations": []} and passes.
|
|
26
|
+
const failures = [];
|
|
27
|
+
try {
|
|
28
|
+
for (const c of checks) {
|
|
29
|
+
const rep = join(td, c + ".json");
|
|
30
|
+
try {
|
|
31
|
+
execFileSync(process.execPath, [join(here, "checks", c)], {
|
|
32
|
+
env: { ...process.env, ATDD_VIOLATIONS_REPORT: rep },
|
|
33
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const why = error instanceof Error ? error.message.split("\n")[0] : String(error);
|
|
37
|
+
// Keep the established wording for the crash-without-a-report case (tests/telemetry-review.test.ts #6).
|
|
38
|
+
let reported = true;
|
|
39
|
+
try { readFileSync(rep, "utf8"); } catch { reported = false; }
|
|
40
|
+
failures.push(reported ? `member ${c} failed to run: ${why}` : `member ${c} crashed without a violation report: ${why}`);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const report = JSON.parse(readFileSync(rep, "utf8"));
|
|
45
|
+
if (!report || !Array.isArray(report.violations)) throw new Error("report has no violations array");
|
|
46
|
+
out.push(...report.violations);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
failures.push(`member ${c} left no usable violation report: ${error instanceof Error ? error.message : String(error)}`);
|
|
49
|
+
}
|
|
45
50
|
}
|
|
51
|
+
} finally {
|
|
52
|
+
rmSync(td, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
if (failures.length) {
|
|
55
|
+
for (const failure of failures) process.stderr.write("family bun_telemetry_code: " + failure + "\n");
|
|
56
|
+
process.exit(2); // the run failed: the caller must report a failure, not a verdict
|
|
46
57
|
}
|
|
47
|
-
if (crashed) process.exit(2); // a crashed member is a detector bug: fail loudly, not silently
|
|
48
58
|
writeFileSync(reportPath, JSON.stringify({ violations: out }, null, 2), "utf8");
|
|
49
59
|
process.stderr.write("family bun_telemetry_code: " + out.length + " violation(s)\n");
|
|
50
60
|
process.exit(0);
|