@blamejs/exceptd-skills 0.15.47 → 0.15.49

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.15.49 — 2026-05-30
4
+
5
+ Internal: a new predeploy gate, `scripts/check-codebase-patterns.js`, enforces code-shape bug classes that recurred across releases. It blocks a library-callable function that writes to stdout and then calls `process.exit()` (which truncates buffered output when the stream is piped — the class the v0.15.47 validate-cves fix addressed) and a stale or reason-less `// allow:` suppression marker, and warns on dynamic `RegExp` construction. The flagged `process.exit` sites across the catalog, playbook, package, and vendor validators were converted to the flush-safe `safeExit` form, and the dynamic-`RegExp` sites carry inline justification markers. A companion advisory, wired into the release `prepare` step, flags when the upstream pattern catalog grows a class exceptd hasn't triaged. No change to the shipped CLI surface, catalogs, or skills.
6
+
7
+ ## 0.15.48 — 2026-05-30
8
+
9
+ Internal: the release flow is now driven by a phased orchestrator, `scripts/release.js`. Each subcommand (prepare, gates, commit, push, watch, merge, tag, release) runs one idempotent, resumable phase and exits with a script-safe code; the tag phase enforces a GUARD against tag-on-stale-HEAD and version skew between `package.json`, `manifest.json`, and the CHANGELOG heading. No change to the shipped CLI, catalogs, or skills.
10
+
3
11
  ## 0.15.47 — 2026-05-30
4
12
 
5
13
  A consistency pass on error envelopes and flag handling.
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "schema_version": "1.1.0",
3
- "generated_at": "2026-05-30T18:46:17.416Z",
3
+ "generated_at": "2026-05-30T20:22:06.536Z",
4
4
  "generator": "scripts/build-indexes.js",
5
5
  "source_count": 54,
6
6
  "source_hashes": {
7
- "manifest.json": "913d165fe1132e187dfdb9938d26d905af595901ad839f7adb079ac3c78445d8",
7
+ "manifest.json": "b2d79207d42e38d82a76c13b827623baa3e951b16cfbcc05250572070eb76aed",
8
8
  "data/atlas-ttps.json": "878b4a08bb73c8d20396d85cf433a88f2bc5e7a8cbf7f6ab773ce7ede0a11251",
9
9
  "data/attack-techniques.json": "84fad74c8497cab922ed64b814752f54aa4620c2a938cb06642ff1510e1c5cb3",
10
10
  "data/cve-catalog.json": "7a5f4e31401505e53330cdc4b54b39f8a8b04459d6b9411676d291c583ae535f",
@@ -73,16 +73,16 @@ function walkWorkflows(root) {
73
73
  // - mapping: `on:\n push:\n pull_request_target:`
74
74
  // Heuristic accepts all four.
75
75
  function workflowHasTrigger(content, name) {
76
- if (new RegExp(`^\\s*on:\\s*['"]?${name}['"]?\\s*(?:#.*)?$`, "m").test(content)) return true;
76
+ if (new RegExp(`^\\s*on:\\s*['"]?${name}['"]?\\s*(?:#.*)?$`, "m").test(content)) return true; // allow:dynamic-regex — `name` is a hardcoded trigger literal (pull_request_target / issue_comment / pull_request), never operator/file input
77
77
  const listMatch = content.match(/^\s*on:\s*\[([^\]]*)\]/m);
78
- if (listMatch && new RegExp(`(?:^|,)\\s*['"]?${name}['"]?\\s*(?:,|$)`).test(listMatch[1])) return true;
78
+ if (listMatch && new RegExp(`(?:^|,)\\s*['"]?${name}['"]?\\s*(?:,|$)`).test(listMatch[1])) return true; // allow:dynamic-regex — `name` is a hardcoded trigger literal, never operator/file input
79
79
  // block list AND mapping forms both follow `on:\n` with indented
80
80
  // continuation lines. Capture the block and inspect for either
81
81
  // `- <name>` (list) or `<name>:` (mapping) within it.
82
82
  const blockMatch = content.match(/^\s*on:\s*\n((?:[ \t]+[^\n]+\n?)+)/m);
83
83
  if (blockMatch) {
84
- if (new RegExp(`^[ \\t]+-\\s+['"]?${name}['"]?\\s*(?:#.*)?\\s*$`, "m").test(blockMatch[1])) return true;
85
- if (new RegExp(`^[ \\t]+${name}:`, "m").test(blockMatch[1])) return true;
84
+ if (new RegExp(`^[ \\t]+-\\s+['"]?${name}['"]?\\s*(?:#.*)?\\s*$`, "m").test(blockMatch[1])) return true; // allow:dynamic-regex — `name` is a hardcoded trigger literal, never operator/file input
85
+ if (new RegExp(`^[ \\t]+${name}:`, "m").test(blockMatch[1])) return true; // allow:dynamic-regex — `name` is a hardcoded trigger literal, never operator/file input
86
86
  }
87
87
  return false;
88
88
  }
@@ -40,6 +40,7 @@
40
40
  const fs = require('node:fs');
41
41
  const path = require('node:path');
42
42
  const process = require('node:process');
43
+ const { safeExit } = require('./exit-codes');
43
44
 
44
45
  const REPO_ROOT = path.resolve(__dirname, '..');
45
46
  const MANIFEST_PATH = path.join(REPO_ROOT, 'manifest.json');
@@ -866,7 +867,8 @@ function main() {
866
867
  if (strictFail) {
867
868
  console.log(`[lint-skills] --strict: ${warned + (airGapWarnings ? airGapWarnings.length : 0)} warning(s) treated as failures.`);
868
869
  }
869
- process.exit(failed === 0 && orphans.length === 0 && !strictFail ? 0 : 1);
870
+ safeExit(failed === 0 && orphans.length === 0 && !strictFail ? 0 : 1);
871
+ return;
870
872
  }
871
873
 
872
874
  // Export the minimal frontmatter parser for downstream consumers
@@ -3632,7 +3632,7 @@ function evalCondition(expr, ctx, playbook) {
3632
3632
  // analyze() can surface analyze.runtime_errors[] without losing the
3633
3633
  // diagnostic.
3634
3634
  try {
3635
- return new RegExp(m[2], 'i').test(val);
3635
+ return new RegExp(m[2], 'i').test(val); // allow:dynamic-regex — m[2] is the pattern from a signed-catalog playbook condition (/…/), and construction + .test() are already wrapped in this try/catch to neutralize a malformed/pathological pattern
3636
3636
  } catch (e) {
3637
3637
  const errorRec = { _regex_eval_error: { source: m[1], expr: m[2], message: e && e.message ? String(e.message) : String(e) } };
3638
3638
  // Two sites where ctx may carry an accumulator: runOpts._runErrors
package/lib/sign.js CHANGED
@@ -77,6 +77,7 @@ const fs = require('fs');
77
77
  const path = require('path');
78
78
  const crypto = require('crypto');
79
79
  const { execFileSync } = require('child_process');
80
+ const { safeExit } = require('./exit-codes');
80
81
 
81
82
  const ROOT = path.join(__dirname, '..');
82
83
  const MANIFEST_PATH = path.join(ROOT, 'manifest.json');
@@ -219,7 +220,7 @@ function signAll() {
219
220
  }
220
221
  printFingerprintBanner();
221
222
 
222
- if (errors > 0) process.exit(1);
223
+ if (errors > 0) { safeExit(1); return; }
223
224
  }
224
225
 
225
226
  /**
@@ -23,6 +23,7 @@
23
23
 
24
24
  const path = require("path");
25
25
  const fs = require("fs");
26
+ const { safeExit } = require("./exit-codes");
26
27
 
27
28
  const ROOT = path.resolve(__dirname, "..");
28
29
  const { fetchLatestPublished, buildFreshnessReport } = require("./upstream-check.js");
@@ -65,7 +66,8 @@ function readManifest() {
65
66
  reason: "registry probe disabled in air-gap mode",
66
67
  source: "upstream-check",
67
68
  }) + "\n");
68
- process.exit(0);
69
+ safeExit(0);
70
+ return;
69
71
  }
70
72
  const registry = await fetchLatestPublished({ timeoutMs: opts.timeoutMs });
71
73
  if (opts.raw) {
@@ -31,6 +31,7 @@
31
31
  const fs = require('node:fs');
32
32
  const path = require('node:path');
33
33
  const process = require('node:process');
34
+ const { safeExit } = require('./exit-codes');
34
35
 
35
36
  const REPO_ROOT = path.resolve(__dirname, '..');
36
37
  const DATA_DIR = path.join(REPO_ROOT, 'data');
@@ -64,10 +65,12 @@ function parseArgs(argv) {
64
65
  ' --quiet Suppress per-catalog PASS output; show failures only.\n' +
65
66
  ' --strict Promote freshness warnings to errors (used by the predeploy gate).\n',
66
67
  );
67
- process.exit(0);
68
+ safeExit(0);
69
+ return null;
68
70
  } else {
69
71
  console.error(`Unknown argument: ${a}`);
70
- process.exit(2);
72
+ safeExit(2);
73
+ return null;
71
74
  }
72
75
  }
73
76
  return opts;
@@ -208,6 +211,7 @@ function validateMeta(catalogPath, opts) {
208
211
 
209
212
  function main() {
210
213
  const opts = parseArgs(process.argv);
214
+ if (opts === null) return; // parseArgs handled --help / bad-arg and set the exit code
211
215
  const files = fs
212
216
  .readdirSync(DATA_DIR)
213
217
  .filter((f) => f.endsWith('.json'))
@@ -26,6 +26,7 @@
26
26
  const fs = require('node:fs');
27
27
  const path = require('node:path');
28
28
  const process = require('node:process');
29
+ const { safeExit } = require('./exit-codes');
29
30
 
30
31
  const REPO_ROOT = path.resolve(__dirname, '..');
31
32
  const SCHEMA_PATH = path.join(REPO_ROOT, 'lib', 'schemas', 'cve-catalog.schema.json');
@@ -82,10 +83,12 @@ function parseArgs(argv) {
82
83
  ' --quiet Suppress per-CVE PASS output; show failures only.\n' +
83
84
  ' --strict Promote advisory warnings to errors (used by the predeploy gate). Off by default.\n',
84
85
  );
85
- process.exit(0);
86
+ safeExit(0);
87
+ return null;
86
88
  } else {
87
89
  console.error(`Unknown argument: ${a}`);
88
- process.exit(2);
90
+ safeExit(2);
91
+ return null;
89
92
  }
90
93
  }
91
94
  return opts;
@@ -138,7 +141,7 @@ function validate(value, schema, schemaName, pathStr) {
138
141
  errors.push(`${here}: string shorter than minLength ${schema.minLength}`);
139
142
  }
140
143
  if (schema.pattern !== undefined) {
141
- const re = new RegExp(schema.pattern);
144
+ const re = new RegExp(schema.pattern); // allow:dynamic-regex — bundled schema.pattern, not operator input
142
145
  if (!re.test(value)) {
143
146
  errors.push(`${here}: string ${JSON.stringify(value)} does not match pattern /${schema.pattern}/`);
144
147
  }
@@ -312,6 +315,7 @@ function additionalChecks(key, entry, ctx) {
312
315
 
313
316
  function main() {
314
317
  const opts = parseArgs(process.argv);
318
+ if (opts === null) return; // parseArgs handled --help / bad-arg and set the exit code
315
319
  const schema = readJson(SCHEMA_PATH);
316
320
  const catalog = readJson(CATALOG_PATH);
317
321
  const lessons = readJson(LESSONS_PATH);
@@ -19,6 +19,7 @@
19
19
  const fs = require("fs");
20
20
  const path = require("path");
21
21
  const { spawnSync } = require("child_process");
22
+ const { safeExit } = require("./exit-codes");
22
23
 
23
24
  const ROOT = path.join(__dirname, "..");
24
25
  const ABS = (p) => path.join(ROOT, p);
@@ -154,12 +155,14 @@ function main() {
154
155
  `${packInfo.files.length} files, ` +
155
156
  `${sizeMB} MB packed / ${unpackedMB} MB unpacked.\n`
156
157
  );
157
- process.exit(0);
158
+ safeExit(0);
159
+ return;
158
160
  }
159
161
 
160
162
  process.stderr.write(`[validate-package] FAILED — ${issues.length} issue(s):\n`);
161
163
  for (const i of issues) process.stderr.write(` • ${i}\n`);
162
- process.exit(1);
164
+ safeExit(1);
165
+ return;
163
166
  }
164
167
 
165
168
  if (require.main === module) main();
@@ -70,6 +70,7 @@
70
70
  const fs = require('node:fs');
71
71
  const path = require('node:path');
72
72
  const process = require('node:process');
73
+ const { safeExit } = require('./exit-codes');
73
74
 
74
75
  const REPO_ROOT = path.resolve(__dirname, '..');
75
76
  const SCHEMA_PATH = path.join(REPO_ROOT, 'lib', 'schemas', 'playbook.schema.json');
@@ -94,10 +95,12 @@ function parseArgs(argv) {
94
95
  ' --quiet Suppress per-playbook PASS output; show failures only.\n' +
95
96
  ' --strict Treat warnings as errors (used by the predeploy gate).\n',
96
97
  );
97
- process.exit(0);
98
+ safeExit(0);
99
+ return null;
98
100
  } else {
99
101
  console.error(`Unknown argument: ${a}`);
100
- process.exit(2);
102
+ safeExit(2);
103
+ return null;
101
104
  }
102
105
  }
103
106
  return opts;
@@ -161,7 +164,7 @@ function validate(value, schema, schemaName, pathStr) {
161
164
  err(`${here}: string shorter than minLength ${schema.minLength}`);
162
165
  }
163
166
  if (schema.pattern !== undefined) {
164
- const re = new RegExp(schema.pattern);
167
+ const re = new RegExp(schema.pattern); // allow:dynamic-regex — bundled schema.pattern, not operator input
165
168
  if (!re.test(value)) {
166
169
  err(`${here}: string ${JSON.stringify(value)} does not match pattern /${schema.pattern}/`);
167
170
  }
@@ -559,6 +562,7 @@ function checkMutexReciprocity(playbooks) {
559
562
 
560
563
  function main() {
561
564
  const opts = parseArgs(process.argv);
565
+ if (opts === null) return; // parseArgs handled --help / bad-arg and set the exit code
562
566
  const schema = readJson(SCHEMA_PATH);
563
567
  const ctx = loadContext();
564
568
  const playbooks = loadPlaybooks();
@@ -617,7 +621,8 @@ function main() {
617
621
  (warned ? `, ${warned} with warnings` : '') +
618
622
  (errored ? `, ${errored} failed` : '') + '.',
619
623
  );
620
- process.exit(errored === 0 ? 0 : 1);
624
+ safeExit(errored === 0 ? 0 : 1);
625
+ return;
621
626
  }
622
627
 
623
628
  module.exports = {
@@ -20,6 +20,7 @@
20
20
  const fs = require("fs");
21
21
  const path = require("path");
22
22
  const crypto = require("crypto");
23
+ const { safeExit } = require("./exit-codes");
23
24
 
24
25
  const ROOT = path.join(__dirname, "..");
25
26
  const PROV = path.join(ROOT, "vendor", "blamejs", "_PROVENANCE.json");
@@ -71,13 +72,15 @@ function main() {
71
72
  if (issues.length === 0) {
72
73
  const fileCount = Object.keys(prov.files || {}).length;
73
74
  console.log(`[validate-vendor] vendor tree current — ${fileCount} file(s) validated against pin ${prov.pinned_commit?.slice(0, 12) || "?"}.`);
74
- process.exit(0);
75
+ safeExit(0);
76
+ return;
75
77
  }
76
78
 
77
79
  console.error("[validate-vendor] vendor tree DRIFT:");
78
80
  for (const i of issues) console.error(" • " + i);
79
81
  console.error("[validate-vendor] re-vendor instructions: vendor/blamejs/README.md");
80
- process.exit(1);
82
+ safeExit(1);
83
+ return;
81
84
  }
82
85
 
83
86
  if (require.main === module) main();
package/lib/verify.js CHANGED
@@ -520,7 +520,7 @@ function validateAgainstSchema(value, schema, here, root) {
520
520
  errors.push(`${here}: string shorter than minLength ${effectiveSchema.minLength}`);
521
521
  }
522
522
  if (effectiveSchema.pattern !== undefined) {
523
- const re = new RegExp(effectiveSchema.pattern);
523
+ const re = new RegExp(effectiveSchema.pattern); // allow:dynamic-regex — bundled schema.pattern, not operator input
524
524
  if (!re.test(value)) {
525
525
  errors.push(`${here}: string ${JSON.stringify(value)} does not match pattern /${effectiveSchema.pattern}/`);
526
526
  }