@blamejs/exceptd-skills 0.19.33 → 0.19.35
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 +28 -0
- package/bin/exceptd.js +895 -2828
- package/data/_indexes/_meta.json +2 -2
- package/lib/auto-discovery.js +56 -286
- package/lib/canonical-eq.js +7 -40
- package/lib/citation-resolve.js +22 -70
- package/lib/collectors/ai-api.js +170 -76
- package/lib/collectors/cicd-pipeline-compromise.js +113 -136
- package/lib/collectors/citation-hygiene.js +72 -210
- package/lib/collectors/containers.js +41 -130
- package/lib/collectors/cred-stores.js +31 -115
- package/lib/collectors/crypto-codebase.js +55 -138
- package/lib/collectors/crypto.js +24 -54
- package/lib/collectors/hardening.js +20 -78
- package/lib/collectors/kernel.js +16 -46
- package/lib/collectors/library-author.js +198 -211
- package/lib/collectors/mcp.js +24 -70
- package/lib/collectors/runtime.js +24 -86
- package/lib/collectors/sbom.js +130 -118
- package/lib/collectors/scan-excludes.js +33 -139
- package/lib/collectors/secrets.js +62 -178
- package/lib/cross-ref-api.js +39 -123
- package/lib/currency-severity.js +8 -27
- package/lib/cve-batch.js +13 -21
- package/lib/cve-cli.js +13 -20
- package/lib/cve-curation.js +72 -239
- package/lib/cve-regression-watcher.js +29 -155
- package/lib/cvss.js +13 -54
- package/lib/doctor-bucketing.js +3 -19
- package/lib/exit-codes.js +10 -42
- package/lib/flag-suggest.js +7 -25
- package/lib/framework-gap.js +39 -113
- package/lib/gap-detectors.js +37 -159
- package/lib/id-validation.js +9 -30
- package/lib/job-queue.js +13 -36
- package/lib/lint-skills.js +88 -236
- package/lib/playbook-runner.js +759 -2107
- package/lib/prefetch.js +101 -376
- package/lib/refresh-external.js +199 -633
- package/lib/refresh-network.js +78 -311
- package/lib/rfc-cli.js +23 -68
- package/lib/scoring.js +85 -146
- package/lib/sign.js +43 -229
- package/lib/source-advisories.js +43 -194
- package/lib/source-ghsa.js +37 -120
- package/lib/source-osv.js +94 -266
- package/lib/ttp-mapper.js +28 -27
- package/lib/upstream-check-cli.js +36 -29
- package/lib/upstream-check.js +19 -44
- package/lib/validate-catalog-meta.js +17 -61
- package/lib/validate-cve-catalog.js +52 -121
- package/lib/validate-indexes.js +25 -76
- package/lib/validate-package.js +16 -62
- package/lib/validate-playbooks.js +78 -286
- package/lib/validate-vendor.js +16 -49
- package/lib/verify.js +56 -286
- package/lib/version-pins.js +5 -34
- package/lib/worker-pool.js +11 -30
- package/lib/xml-tokenizer.js +47 -152
- package/manifest.json +53 -53
- package/orchestrator/dispatcher.js +17 -68
- package/orchestrator/event-bus.js +11 -74
- package/orchestrator/index.js +138 -413
- package/orchestrator/pipeline.js +28 -85
- package/orchestrator/scanner.js +34 -138
- package/orchestrator/scheduler.js +20 -84
- package/package.json +1 -1
- package/sbom.cdx.json +242 -242
- package/scripts/audit-catalog-gaps.js +9 -62
- package/scripts/audit-cross-skill.js +5 -31
- package/scripts/audit-perf.js +29 -28
- package/scripts/backfill-theater-test.js +7 -64
- package/scripts/bootstrap.js +12 -44
- package/scripts/build-indexes.js +40 -154
- package/scripts/builders/activity-feed.js +4 -14
- package/scripts/builders/catalog-summaries.js +3 -10
- package/scripts/builders/currency.js +7 -20
- package/scripts/builders/cwe-chains.js +7 -30
- package/scripts/builders/did-ladders.js +6 -13
- package/scripts/builders/frequency.js +5 -19
- package/scripts/builders/jurisdiction-clocks.js +6 -25
- package/scripts/builders/recipes.js +6 -14
- package/scripts/builders/section-offsets.js +13 -51
- package/scripts/builders/stale-content.js +7 -28
- package/scripts/builders/summary-cards.js +8 -29
- package/scripts/builders/theater-fingerprints.js +21 -31
- package/scripts/builders/token-budget.js +4 -31
- package/scripts/check-agents-md-collectors.js +26 -57
- package/scripts/check-catalog-gap-budget.js +15 -32
- package/scripts/check-changelog-extract.js +18 -48
- package/scripts/check-codebase-patterns-currency.js +6 -22
- package/scripts/check-codebase-patterns.js +63 -143
- package/scripts/check-epss-consistency.js +9 -64
- package/scripts/check-framework-gap-coverage.js +13 -31
- package/scripts/check-manifest-snapshot.js +62 -81
- package/scripts/check-sbom-currency.js +44 -142
- package/scripts/check-test-count.js +15 -52
- package/scripts/check-test-coverage.js +83 -198
- package/scripts/check-test-subjects.js +21 -62
- package/scripts/check-ttp-references.js +14 -38
- package/scripts/check-ttp-upstream.js +8 -40
- package/scripts/check-version-bump.js +9 -61
- package/scripts/check-version-tags.js +20 -121
- package/scripts/predeploy.js +38 -184
- package/scripts/refresh-manifest-snapshot.js +16 -38
- package/scripts/refresh-mitre-atlas.js +7 -8
- package/scripts/refresh-mitre-attack.js +1 -8
- package/scripts/refresh-mitre-d3fend.js +3 -9
- package/scripts/refresh-mitre-ics-attack.js +7 -8
- package/scripts/refresh-reverse-refs.js +27 -94
- package/scripts/refresh-rfc-index.js +7 -10
- package/scripts/refresh-sbom.js +31 -161
- package/scripts/refresh-upstream-catalogs.js +63 -148
- package/scripts/release.js +69 -234
- package/scripts/run-e2e-scenarios.js +26 -73
- package/scripts/sync-manifest-metadata.js +10 -34
- package/scripts/sync-package-description.js +8 -17
- package/scripts/validate-vendor-online.js +13 -44
- package/scripts/verify-shipped-tarball.js +35 -141
|
@@ -2,24 +2,9 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* ship today" paragraph stays in sync with the actual contents of
|
|
9
|
-
* lib/collectors/. Drift is silent today - AGENTS.md gets bumped by
|
|
10
|
-
* hand each release; a missed bump produces inaccurate count + stale
|
|
11
|
-
* enumeration that downstream AI consumers parse.
|
|
12
|
-
*
|
|
13
|
-
* Checks:
|
|
14
|
-
* 1. The numeric count word in the paragraph (Eleven / Twelve /
|
|
15
|
-
* Thirteen / ...) matches the actual count of
|
|
16
|
-
* lib/collectors/*.js modules.
|
|
17
|
-
* 2. Every collector named in the parenthesized list exists at
|
|
18
|
-
* lib/collectors/<name>.js.
|
|
19
|
-
* 3. Every lib/collectors/<name>.js module appears in the
|
|
20
|
-
* parenthesized list.
|
|
21
|
-
*
|
|
22
|
-
* Exit codes: 0 ok, 1 drift, 2 parse error.
|
|
5
|
+
* Predeploy gate: AGENTS.md's "<N> reference collectors ship today" paragraph
|
|
6
|
+
* must agree with lib/collectors/ — the count word and the enumeration, both
|
|
7
|
+
* directions. Exit codes: 0 ok, 1 drift, 2 parse error.
|
|
23
8
|
*/
|
|
24
9
|
|
|
25
10
|
const fs = require("node:fs");
|
|
@@ -28,31 +13,20 @@ const path = require("node:path");
|
|
|
28
13
|
const ROOT = path.join(__dirname, "..");
|
|
29
14
|
const AGENTS = path.join(ROOT, "AGENTS.md");
|
|
30
15
|
const REAL_COLLECTOR_DIR = path.join(ROOT, "lib", "collectors");
|
|
31
|
-
// EXCEPTD_COLLECTOR_DIR
|
|
32
|
-
//
|
|
33
|
-
// without writing into the real lib/collectors/). It is honored ONLY when the
|
|
34
|
-
// explicit test-only switch EXCEPTD_COLLECTOR_DIR_TESTONLY=1 is also set, so a
|
|
35
|
-
// stray env var in CI or a developer shell can never point the release gate at
|
|
36
|
-
// an alternate directory and let missing/broken real collectors pass.
|
|
16
|
+
// EXCEPTD_COLLECTOR_DIR is honored ONLY alongside EXCEPTD_COLLECTOR_DIR_TESTONLY=1,
|
|
17
|
+
// so a stray env var cannot aim the release gate at another directory.
|
|
37
18
|
const COLLECTOR_DIR =
|
|
38
19
|
process.env.EXCEPTD_COLLECTOR_DIR_TESTONLY === "1" && process.env.EXCEPTD_COLLECTOR_DIR
|
|
39
20
|
? path.resolve(process.env.EXCEPTD_COLLECTOR_DIR)
|
|
40
21
|
: REAL_COLLECTOR_DIR;
|
|
41
22
|
|
|
42
|
-
// A
|
|
43
|
-
//
|
|
44
|
-
// underscore-prefixed file. So a `__`-prefixed file is never a collector:
|
|
45
|
-
// it is test scaffolding or a stray artifact. classifyCollectors SKIPS it so a
|
|
46
|
-
// leaked fixture cannot poison the count/enumeration/load-error scan; the gate
|
|
47
|
-
// separately FORBIDS it (see findReservedFixtures) so a leaked fixture cannot
|
|
48
|
-
// silently ship in the wholesale-published lib/ tree either.
|
|
23
|
+
// A shipped collector is named [a-z0-9-]+.js, so a `__`-prefixed file is test
|
|
24
|
+
// scaffolding: classifyCollectors skips it, findReservedFixtures forbids it in lib/.
|
|
49
25
|
function isReservedFixture(f) {
|
|
50
26
|
return f.startsWith("__");
|
|
51
27
|
}
|
|
52
28
|
|
|
53
|
-
// Reserved-prefix .js files present in `dir
|
|
54
|
-
// must never ship; the gate fails hard if any are found in the directory it
|
|
55
|
-
// validates.
|
|
29
|
+
// Reserved-prefix .js files present in `dir`; an unreadable dir yields [].
|
|
56
30
|
function findReservedFixtures(dir) {
|
|
57
31
|
try {
|
|
58
32
|
return fs.readdirSync(dir).filter((f) => f.endsWith(".js") && isReservedFixture(f));
|
|
@@ -61,10 +35,20 @@ function findReservedFixtures(dir) {
|
|
|
61
35
|
}
|
|
62
36
|
}
|
|
63
37
|
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
38
|
+
// Repo-relative POSIX form of <dir>/<file> for a diagnostic, falling back to the
|
|
39
|
+
// literal path when `dir` sits outside the repo (a test-only override dir).
|
|
40
|
+
function collectorDisplayPath(dir, file) {
|
|
41
|
+
const abs = path.join(dir, file);
|
|
42
|
+
const rel = path.relative(ROOT, abs).split(path.sep).join("/");
|
|
43
|
+
return rel && !rel.startsWith("..") ? rel : abs.split(path.sep).join("/");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Classify every <dir>/*.js: a collector requires cleanly and exports collect();
|
|
47
|
+
// a require that throws becomes a load error rather than a silent omission.
|
|
48
|
+
//
|
|
49
|
+
// collectorFiles entries keep the literal `lib/collectors/` prefix whatever `dir`
|
|
50
|
+
// is: they are compared against the paths AGENTS.md enumerates, which always name
|
|
51
|
+
// the shipped location. Only the load-error diagnostic names the real path.
|
|
68
52
|
function classifyCollectors(dir) {
|
|
69
53
|
const jsFiles = fs.readdirSync(dir)
|
|
70
54
|
.filter((f) => f.endsWith(".js") && !isReservedFixture(f))
|
|
@@ -76,7 +60,7 @@ function classifyCollectors(dir) {
|
|
|
76
60
|
try {
|
|
77
61
|
mod = require(path.join(dir, f));
|
|
78
62
|
} catch (e) {
|
|
79
|
-
loadErrors.push(
|
|
63
|
+
loadErrors.push(`${collectorDisplayPath(dir, f)}: ${e.message.split("\n")[0]}`);
|
|
80
64
|
continue;
|
|
81
65
|
}
|
|
82
66
|
if (typeof mod.collect === "function") {
|
|
@@ -104,10 +88,7 @@ function ok(msg) {
|
|
|
104
88
|
}
|
|
105
89
|
|
|
106
90
|
function main() {
|
|
107
|
-
//
|
|
108
|
-
// `__`-prefixed file in the collectors dir is stray test scaffolding;
|
|
109
|
-
// because lib/ is published wholesale, a leaked one would otherwise ship.
|
|
110
|
-
// Fail hard, naming the file, so the release path deletes it.
|
|
91
|
+
// lib/ is published wholesale, so a stray fixture would ship.
|
|
111
92
|
const stray = findReservedFixtures(COLLECTOR_DIR);
|
|
112
93
|
if (stray.length > 0) {
|
|
113
94
|
console.error(
|
|
@@ -126,18 +107,6 @@ function main() {
|
|
|
126
107
|
return;
|
|
127
108
|
}
|
|
128
109
|
|
|
129
|
-
// Classify every lib/collectors/*.js into exactly one of three buckets:
|
|
130
|
-
// - collector: require() succeeds AND exports a collect() function
|
|
131
|
-
// (counted; must appear in the AGENTS.md enumeration).
|
|
132
|
-
// - helper: require() succeeds but exports no collect() function
|
|
133
|
-
// (e.g. scan-excludes.js, the directory-walk exclusion
|
|
134
|
-
// policy) — legitimately excluded from the count.
|
|
135
|
-
// - load-error: require() THROWS (syntax error, bad top-level require,
|
|
136
|
-
// init-time exception). A broken collector must NOT be
|
|
137
|
-
// silently dropped: doing so excludes it from BOTH the
|
|
138
|
-
// count and the enumeration cross-check, so a file that
|
|
139
|
-
// still ships in the tarball passes the gate undetected.
|
|
140
|
-
// Surface it as a parse error (exit 2) naming the file.
|
|
141
110
|
let collectorFiles, loadErrors;
|
|
142
111
|
try {
|
|
143
112
|
({ collectorFiles, loadErrors } = classifyCollectors(COLLECTOR_DIR));
|
|
@@ -149,7 +118,7 @@ function main() {
|
|
|
149
118
|
|
|
150
119
|
if (loadErrors.length > 0) {
|
|
151
120
|
console.error(
|
|
152
|
-
`[check-agents-md-collectors] cannot load ${loadErrors.length} module(s) in
|
|
121
|
+
`[check-agents-md-collectors] cannot load ${loadErrors.length} module(s) in ${collectorDisplayPath(COLLECTOR_DIR, "")}/ ` +
|
|
153
122
|
`- a require-time failure must not be silently excluded from the count + enumeration check:\n ` +
|
|
154
123
|
loadErrors.join("\n ")
|
|
155
124
|
);
|
|
@@ -206,4 +175,4 @@ if (require.main === module) {
|
|
|
206
175
|
main();
|
|
207
176
|
}
|
|
208
177
|
|
|
209
|
-
module.exports = { classifyCollectors };
|
|
178
|
+
module.exports = { classifyCollectors, collectorDisplayPath };
|
|
@@ -1,23 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Predeploy gate: runs the gap detectors and asserts no class exceeds its
|
|
5
|
+
* budget. Exit 0 within budget, 1 regressed, 2 internal error.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* when the broader test suite is skipped (or is the gate that's
|
|
11
|
-
* failing for an unrelated reason).
|
|
12
|
-
*
|
|
13
|
-
* Exit codes:
|
|
14
|
-
* 0 — every extended class within budget
|
|
15
|
-
* 1 — at least one class regressed
|
|
16
|
-
* 2 — internal error
|
|
17
|
-
*
|
|
18
|
-
* The budget is intentionally duplicated (here + integrity test) for
|
|
19
|
-
* fail-loud-at-two-levels. Operators see the regression in BOTH the
|
|
20
|
-
* test-suite output AND the predeploy gate-summary table.
|
|
7
|
+
* The budget is duplicated in tests/shipped-catalog-integrity.test.js so a
|
|
8
|
+
* regression shows in both the test output and the gate-summary table, and so
|
|
9
|
+
* the gate still reports when the suite is skipped or failing elsewhere. Both
|
|
10
|
+
* copies move together.
|
|
21
11
|
*/
|
|
22
12
|
|
|
23
13
|
const path = require("path");
|
|
@@ -47,16 +37,13 @@ function loadAll() {
|
|
|
47
37
|
};
|
|
48
38
|
}
|
|
49
39
|
|
|
50
|
-
// Per-class regression budgets
|
|
51
|
-
//
|
|
40
|
+
// Per-class regression budgets, mirrored in
|
|
41
|
+
// tests/shipped-catalog-integrity.test.js.
|
|
52
42
|
const BUDGET = {
|
|
53
43
|
"content-quality": 12,
|
|
54
|
-
// temporal-staleness
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
// operator-remediation date, not catalog freshness, and grew without bound as
|
|
58
|
-
// the catalog aged and KEV drafts got curated. Actual is 0 with fresh data;
|
|
59
|
-
// 10 leaves headroom for entries aging past a threshold before a refresh.
|
|
44
|
+
// temporal-staleness counts only the maintainer-controllable freshness fields,
|
|
45
|
+
// which sit at 0 on fresh data; the headroom covers entries aging past a
|
|
46
|
+
// threshold between refreshes.
|
|
60
47
|
"temporal-staleness": 10,
|
|
61
48
|
"logical-consistency": 5,
|
|
62
49
|
"cross-ref-completeness": 5,
|
|
@@ -71,20 +58,16 @@ function main() {
|
|
|
71
58
|
for (const f of all) byClass[f.class] = (byClass[f.class] || 0) + 1;
|
|
72
59
|
const regressions = [];
|
|
73
60
|
|
|
74
|
-
// Fail-closed
|
|
75
|
-
//
|
|
76
|
-
// 8th detector lands without a budget update, the gate fires with
|
|
77
|
-
// an unbudgeted-class error instead of silently passing.
|
|
61
|
+
// Fail-closed: a class the detectors emit without a budget entry fires an
|
|
62
|
+
// unbudgeted-class error rather than passing unmeasured.
|
|
78
63
|
const unbudgeted = [];
|
|
79
64
|
for (const cls of Object.keys(byClass)) {
|
|
80
65
|
if (!(cls in BUDGET)) {
|
|
81
66
|
unbudgeted.push({ class: cls, count: byClass[cls] });
|
|
82
67
|
}
|
|
83
68
|
}
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
// a new class produces zero findings on this run but still needs
|
|
87
|
-
// an explicit budget so a future regression caps fail-closed).
|
|
69
|
+
// The inverse: a class declared in DETECTOR_CLASSES needs a budget even when
|
|
70
|
+
// it emits nothing on this run, or the first regression has no cap to hit.
|
|
88
71
|
const missingBudget = [];
|
|
89
72
|
if (Array.isArray(D.DETECTOR_CLASSES)) {
|
|
90
73
|
for (const cls of D.DETECTOR_CLASSES) {
|
|
@@ -2,34 +2,14 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Release-notes extraction + quality gate.
|
|
5
|
+
* Release-notes extraction + quality gate. .github/workflows/release.yml publishes
|
|
6
|
+
* the GitHub Release body by awk-extracting the `## <version> …` CHANGELOG section,
|
|
7
|
+
* falling back to "Release of v<version>." when the extract is empty. This runs the
|
|
8
|
+
* SAME extraction before tag-push and lints what it finds, so a malformed or
|
|
9
|
+
* internal-narrative section fails here rather than shipping as the public body.
|
|
6
10
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* between the version heading and the next `## ` heading, falling back to a
|
|
10
|
-
* generic "Release of v<version>." line if the extract is empty. This gate
|
|
11
|
-
* runs that SAME extraction locally before tag-push, and additionally lints
|
|
12
|
-
* the extracted notes for operator-facing quality, so a malformed or
|
|
13
|
-
* internal-narrative-laced section fails here rather than shipping as the
|
|
14
|
-
* public release body.
|
|
15
|
-
*
|
|
16
|
-
* Two layers:
|
|
17
|
-
* 1. EXTRACT — the `## <version> — <date>` section exists, is non-empty
|
|
18
|
-
* (won't trigger the workflow's "Release of v…" fallback),
|
|
19
|
-
* the heading version matches package.json, and the heading
|
|
20
|
-
* carries an ISO date.
|
|
21
|
-
* 2. LINT — the extracted body is operator-facing-clean: no internal
|
|
22
|
-
* phase/pass/slice/sweep narrative, no agent-dispatch /
|
|
23
|
-
* conversation residue, no tautological "all tests pass"
|
|
24
|
-
* noise. (Mirrors the operator-facing discipline; the release
|
|
25
|
-
* body is the most public surface there is.)
|
|
26
|
-
*
|
|
27
|
-
* Exit: process.exitCode 0 on pass, 1 on any failure. Functions are exported
|
|
28
|
-
* for fixture-based testing (no subprocess needed).
|
|
29
|
-
*
|
|
30
|
-
* Usage:
|
|
31
|
-
* node scripts/check-changelog-extract.js # uses package.json version
|
|
32
|
-
* node scripts/check-changelog-extract.js <version> # explicit MAJOR.MINOR.PATCH
|
|
11
|
+
* process.exitCode 0 on pass, 1 on any failure. Takes an optional
|
|
12
|
+
* MAJOR.MINOR.PATCH argument, defaulting to the package.json version.
|
|
33
13
|
*/
|
|
34
14
|
|
|
35
15
|
const fs = require('node:fs');
|
|
@@ -39,10 +19,9 @@ const ROOT = path.resolve(__dirname, '..');
|
|
|
39
19
|
const CHANGELOG = path.join(ROOT, 'CHANGELOG.md');
|
|
40
20
|
const PACKAGE_JSON = path.join(ROOT, 'package.json');
|
|
41
21
|
|
|
42
|
-
// Replicates the release.yml awk:
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
// heading can't accidentally match a longer one that shares its prefix.
|
|
22
|
+
// Replicates the release.yml awk: lines after the `## <version> ` heading up to
|
|
23
|
+
// the next `## `. The trailing space mirrors the workflow's `"^## " v " "`, so a
|
|
24
|
+
// shorter version heading cannot match a longer one sharing its prefix.
|
|
46
25
|
function extractSection(text, version) {
|
|
47
26
|
const lines = text.split(/\r?\n/);
|
|
48
27
|
const out = [];
|
|
@@ -56,8 +35,7 @@ function extractSection(text, version) {
|
|
|
56
35
|
}
|
|
57
36
|
if (startRe.test(ln)) capturing = true;
|
|
58
37
|
}
|
|
59
|
-
//
|
|
60
|
-
// either way, but trimming makes the non-empty test honest).
|
|
38
|
+
// awk keeps the blank lines; trimming them makes the non-empty test honest.
|
|
61
39
|
while (out.length && out[0].trim() === '') out.shift();
|
|
62
40
|
while (out.length && out[out.length - 1].trim() === '') out.pop();
|
|
63
41
|
return out;
|
|
@@ -69,10 +47,8 @@ function headingLine(text, version) {
|
|
|
69
47
|
return text.split(/\r?\n/).find((l) => re.test(l)) || null;
|
|
70
48
|
}
|
|
71
49
|
|
|
72
|
-
// Operator-facing forbidden patterns
|
|
73
|
-
//
|
|
74
|
-
// bare "phase" in "multi-phase attack" is fine; "Phase 9" is the tell). Each
|
|
75
|
-
// entry: { id, re, why }.
|
|
50
|
+
// Operator-facing forbidden patterns: high-confidence markers only, since a false
|
|
51
|
+
// positive blocks a release. "multi-phase attack" is prose; "Phase 9" is the tell.
|
|
76
52
|
const FORBIDDEN = [
|
|
77
53
|
{ id: 'phase-number', re: /\bphase\s+\d/i, why: 'internal phase number (operators have no roadmap)' },
|
|
78
54
|
{ id: 'pass-number', re: /\b(?:audit|curation|drift|fix|bug)?[- ]?pass\s+\d/i, why: 'internal pass/batch number' },
|
|
@@ -100,17 +76,11 @@ function readPackageVersion() {
|
|
|
100
76
|
return JSON.parse(fs.readFileSync(PACKAGE_JSON, 'utf8')).version;
|
|
101
77
|
}
|
|
102
78
|
|
|
103
|
-
// Every
|
|
104
|
-
//
|
|
105
|
-
// release
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
// version. Tags are the authoritative record of what was released.
|
|
109
|
-
// Tags whose release never published: the tag-push event was dropped (e.g.
|
|
110
|
-
// a GitHub Actions outage) and — because the v* ruleset forbids re-pushing a
|
|
111
|
-
// tag — the recovery is a version bump re-released with the same notes under
|
|
112
|
-
// the NEW heading. The orphan tag therefore legitimately has no CHANGELOG
|
|
113
|
-
// entry of its own. Tag exists, npm/GitHub Release do not.
|
|
79
|
+
// Every released version keeps its own `## <version> ` heading; tags are the
|
|
80
|
+
// authoritative record of which versions those are. An orphan is a tag whose
|
|
81
|
+
// release never published: the v* ruleset forbids re-pushing a tag, so the
|
|
82
|
+
// recovery was a version bump carrying the same notes under the NEW heading, and
|
|
83
|
+
// such a tag legitimately has no CHANGELOG entry of its own.
|
|
114
84
|
const ORPHAN_RELEASE_TAGS = new Set(['0.13.111', '0.15.25']);
|
|
115
85
|
|
|
116
86
|
function releasedVersionsFromTags() {
|
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Advisory drift detector between exceptd's adopted codebase-pattern classes
|
|
5
|
+
* and the sibling blamejs codebase-patterns test they derive from. A class
|
|
6
|
+
* present upstream but absent from UPSTREAM_TRIAGED is new and wants triage.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* already-owned by another exceptd gate, helper-dependent, or out of scope
|
|
11
|
-
* for a local-file-read security CLI. UPSTREAM_TRIAGED below records every
|
|
12
|
-
* class that triage covered. When upstream grows a NEW class not in that set,
|
|
13
|
-
* this check flags it so the maintainer can decide whether to adopt it — the
|
|
14
|
-
* same forcing function the GitHub-actions / vendored-bundle currency checks
|
|
15
|
-
* provide for those surfaces.
|
|
16
|
-
*
|
|
17
|
-
* Advisory by design: it never fails a release. It exits 0 and prints a
|
|
18
|
-
* NOTICE when upstream has drifted, and exits 0 silently when the sibling
|
|
19
|
-
* repo is not present (a fresh clone / CI runner without the sibling). Point
|
|
20
|
-
* it elsewhere with EXCEPTD_UPSTREAM_PATTERNS env var if the sibling lives at
|
|
21
|
-
* a non-default path.
|
|
8
|
+
* Always exits 0 — it prints a NOTICE on drift and exits silently when the
|
|
9
|
+
* sibling repo is absent. EXCEPTD_UPSTREAM_PATTERNS overrides its path.
|
|
22
10
|
*/
|
|
23
11
|
|
|
24
12
|
const fs = require("node:fs");
|
|
@@ -26,10 +14,7 @@ const path = require("node:path");
|
|
|
26
14
|
|
|
27
15
|
const ROOT = path.resolve(__dirname, "..");
|
|
28
16
|
|
|
29
|
-
//
|
|
30
|
-
// Every key here was classified (adopted / already-owned / helper-dependent /
|
|
31
|
-
// out-of-scope). A class appearing upstream but absent here is NEW and wants
|
|
32
|
-
// triage. Refresh this list (and re-triage the delta) when this check fires.
|
|
17
|
+
// Refresh this list, re-triaging the delta, when this check fires.
|
|
33
18
|
const UPSTREAM_TRIAGED = Object.freeze([ // keep-sorted
|
|
34
19
|
"ai-disclosure-on-request-without-requested-gate",
|
|
35
20
|
"archive-gz-without-safedecompress",
|
|
@@ -83,7 +68,6 @@ function upstreamPatternsPath() {
|
|
|
83
68
|
return path.resolve(ROOT, "..", "blamejs", "test", "layer-0-primitives", "codebase-patterns.test.js");
|
|
84
69
|
}
|
|
85
70
|
|
|
86
|
-
// Extract the allow-class keys from the upstream VALID_ALLOW_CLASSES literal.
|
|
87
71
|
function upstreamClasses(src) {
|
|
88
72
|
const m = src.match(/VALID_ALLOW_CLASSES\s*=\s*(?:Object\.freeze\()?\{([\s\S]*?)\}/);
|
|
89
73
|
if (!m) return null;
|