@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.
Files changed (119) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/bin/exceptd.js +895 -2828
  3. package/data/_indexes/_meta.json +2 -2
  4. package/lib/auto-discovery.js +56 -286
  5. package/lib/canonical-eq.js +7 -40
  6. package/lib/citation-resolve.js +22 -70
  7. package/lib/collectors/ai-api.js +170 -76
  8. package/lib/collectors/cicd-pipeline-compromise.js +113 -136
  9. package/lib/collectors/citation-hygiene.js +72 -210
  10. package/lib/collectors/containers.js +41 -130
  11. package/lib/collectors/cred-stores.js +31 -115
  12. package/lib/collectors/crypto-codebase.js +55 -138
  13. package/lib/collectors/crypto.js +24 -54
  14. package/lib/collectors/hardening.js +20 -78
  15. package/lib/collectors/kernel.js +16 -46
  16. package/lib/collectors/library-author.js +198 -211
  17. package/lib/collectors/mcp.js +24 -70
  18. package/lib/collectors/runtime.js +24 -86
  19. package/lib/collectors/sbom.js +130 -118
  20. package/lib/collectors/scan-excludes.js +33 -139
  21. package/lib/collectors/secrets.js +62 -178
  22. package/lib/cross-ref-api.js +39 -123
  23. package/lib/currency-severity.js +8 -27
  24. package/lib/cve-batch.js +13 -21
  25. package/lib/cve-cli.js +13 -20
  26. package/lib/cve-curation.js +72 -239
  27. package/lib/cve-regression-watcher.js +29 -155
  28. package/lib/cvss.js +13 -54
  29. package/lib/doctor-bucketing.js +3 -19
  30. package/lib/exit-codes.js +10 -42
  31. package/lib/flag-suggest.js +7 -25
  32. package/lib/framework-gap.js +39 -113
  33. package/lib/gap-detectors.js +37 -159
  34. package/lib/id-validation.js +9 -30
  35. package/lib/job-queue.js +13 -36
  36. package/lib/lint-skills.js +88 -236
  37. package/lib/playbook-runner.js +759 -2107
  38. package/lib/prefetch.js +101 -376
  39. package/lib/refresh-external.js +199 -633
  40. package/lib/refresh-network.js +78 -311
  41. package/lib/rfc-cli.js +23 -68
  42. package/lib/scoring.js +85 -146
  43. package/lib/sign.js +43 -229
  44. package/lib/source-advisories.js +43 -194
  45. package/lib/source-ghsa.js +37 -120
  46. package/lib/source-osv.js +94 -266
  47. package/lib/ttp-mapper.js +28 -27
  48. package/lib/upstream-check-cli.js +36 -29
  49. package/lib/upstream-check.js +19 -44
  50. package/lib/validate-catalog-meta.js +17 -61
  51. package/lib/validate-cve-catalog.js +52 -121
  52. package/lib/validate-indexes.js +25 -76
  53. package/lib/validate-package.js +16 -62
  54. package/lib/validate-playbooks.js +78 -286
  55. package/lib/validate-vendor.js +16 -49
  56. package/lib/verify.js +56 -286
  57. package/lib/version-pins.js +5 -34
  58. package/lib/worker-pool.js +11 -30
  59. package/lib/xml-tokenizer.js +47 -152
  60. package/manifest.json +53 -53
  61. package/orchestrator/dispatcher.js +17 -68
  62. package/orchestrator/event-bus.js +11 -74
  63. package/orchestrator/index.js +138 -413
  64. package/orchestrator/pipeline.js +28 -85
  65. package/orchestrator/scanner.js +34 -138
  66. package/orchestrator/scheduler.js +20 -84
  67. package/package.json +1 -1
  68. package/sbom.cdx.json +242 -242
  69. package/scripts/audit-catalog-gaps.js +9 -62
  70. package/scripts/audit-cross-skill.js +5 -31
  71. package/scripts/audit-perf.js +29 -28
  72. package/scripts/backfill-theater-test.js +7 -64
  73. package/scripts/bootstrap.js +12 -44
  74. package/scripts/build-indexes.js +40 -154
  75. package/scripts/builders/activity-feed.js +4 -14
  76. package/scripts/builders/catalog-summaries.js +3 -10
  77. package/scripts/builders/currency.js +7 -20
  78. package/scripts/builders/cwe-chains.js +7 -30
  79. package/scripts/builders/did-ladders.js +6 -13
  80. package/scripts/builders/frequency.js +5 -19
  81. package/scripts/builders/jurisdiction-clocks.js +6 -25
  82. package/scripts/builders/recipes.js +6 -14
  83. package/scripts/builders/section-offsets.js +13 -51
  84. package/scripts/builders/stale-content.js +7 -28
  85. package/scripts/builders/summary-cards.js +8 -29
  86. package/scripts/builders/theater-fingerprints.js +21 -31
  87. package/scripts/builders/token-budget.js +4 -31
  88. package/scripts/check-agents-md-collectors.js +26 -57
  89. package/scripts/check-catalog-gap-budget.js +15 -32
  90. package/scripts/check-changelog-extract.js +18 -48
  91. package/scripts/check-codebase-patterns-currency.js +6 -22
  92. package/scripts/check-codebase-patterns.js +63 -143
  93. package/scripts/check-epss-consistency.js +9 -64
  94. package/scripts/check-framework-gap-coverage.js +13 -31
  95. package/scripts/check-manifest-snapshot.js +62 -81
  96. package/scripts/check-sbom-currency.js +44 -142
  97. package/scripts/check-test-count.js +15 -52
  98. package/scripts/check-test-coverage.js +83 -198
  99. package/scripts/check-test-subjects.js +21 -62
  100. package/scripts/check-ttp-references.js +14 -38
  101. package/scripts/check-ttp-upstream.js +8 -40
  102. package/scripts/check-version-bump.js +9 -61
  103. package/scripts/check-version-tags.js +20 -121
  104. package/scripts/predeploy.js +38 -184
  105. package/scripts/refresh-manifest-snapshot.js +16 -38
  106. package/scripts/refresh-mitre-atlas.js +7 -8
  107. package/scripts/refresh-mitre-attack.js +1 -8
  108. package/scripts/refresh-mitre-d3fend.js +3 -9
  109. package/scripts/refresh-mitre-ics-attack.js +7 -8
  110. package/scripts/refresh-reverse-refs.js +27 -94
  111. package/scripts/refresh-rfc-index.js +7 -10
  112. package/scripts/refresh-sbom.js +31 -161
  113. package/scripts/refresh-upstream-catalogs.js +63 -148
  114. package/scripts/release.js +69 -234
  115. package/scripts/run-e2e-scenarios.js +26 -73
  116. package/scripts/sync-manifest-metadata.js +10 -34
  117. package/scripts/sync-package-description.js +8 -17
  118. package/scripts/validate-vendor-online.js +13 -44
  119. package/scripts/verify-shipped-tarball.js +35 -141
@@ -2,24 +2,9 @@
2
2
  "use strict";
3
3
 
4
4
  /**
5
- * scripts/check-agents-md-collectors.js
6
- *
7
- * Predeploy gate. Verifies that AGENTS.md's "<N> reference collectors
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 lets the test suite drive the gate against a throwaway
32
- // tempdir (so the require-throw / leaked-fixture behaviours are exercised
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 real shipped collector is named [a-z0-9-]+.js — the AGENTS.md enumeration
43
- // regex (`lib/collectors/([a-z0-9-]+\.js)`) cannot even reference an
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`. These are test scaffolding and
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
- // Classify every <dir>/*.js into collectors (require succeeds + exports
65
- // collect()), helpers (require succeeds, no collect() — silently excluded),
66
- // and load-errors (require throws — surfaced, never silently dropped).
67
- // Exported so the test suite can drive it against a tempdir.
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(`lib/collectors/${f}: ${e.message.split("\n")[0]}`);
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
- // Leak-guard (P2: forbid leaked reserved fixtures before packing). A
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 lib/collectors/ ` +
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
- * scripts/check-catalog-gap-budget.js
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
- * Predeploy / CI gate that runs the v0.13.21 extended gap detectors
7
- * and asserts no class exceeds its budget. Mirrors the budget in
8
- * tests/shipped-catalog-integrity.test.js but runs as a standalone
9
- * predeploy gate so the check is visible in the gate summary even
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. Kept in sync with the canonical version
51
- // in tests/shipped-catalog-integrity.test.js.
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 now measures only maintainer-controllable data-freshness
55
- // fields (source_verified > 180d, last_updated > 365d, epss_date > 90d). The
56
- // calendar-driven KEV-due-passed sub-check was removed — it was an external
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 contract (codex P2 PR #61): every class actually
75
- // emitted by the detector must have a budget entry. If a future
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
- // Inverse check: every class declared by the detector module's
85
- // canonical class list must appear in BUDGET (covers the case where
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
- * The release workflow (.github/workflows/release.yml) publishes the GitHub
8
- * Release body by awk-extracting the `## <version> ...` CHANGELOG section
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: capture lines AFTER the `## <version> `
43
- // heading up to (not including) the next `## ` heading. The trailing space in
44
- // the heading match mirrors the workflow's `"^## " v " "` so a shorter version
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
- // Trim leading/trailing blank lines (awk keeps them; the body is the same
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. Tight, high-confidence internal-narrative
73
- // markers only — must not false-positive on legitimate operator prose (e.g. a
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 previously released version must keep its own `## <version> ` heading.
104
- // The release flow edits the TOP of the file; an edit that replaces the prior
105
- // release's heading instead of inserting above it silently merges that
106
- // release's notes into the new section — the extract then spans multiple
107
- // releases and the public release body republishes old notes under the new
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
- * check-codebase-patterns-currency.js — advisory drift detector between
5
- * exceptd's adopted codebase-pattern classes and the upstream catalog they
6
- * were derived from (the sibling blamejs codebase-patterns test).
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
- * exceptd's grep gate (scripts/check-codebase-patterns.js) ships a scoped
9
- * subset of the upstream pattern classes; the rest were triaged as either
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
- // The upstream allow-class registry as triaged during the codebase-patterns adoption.
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;