@blamejs/exceptd-skills 0.13.1 → 0.13.2

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 (38) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/bin/exceptd.js +35 -6
  3. package/data/_indexes/_meta.json +25 -25
  4. package/data/_indexes/activity-feed.json +2 -2
  5. package/data/_indexes/catalog-summaries.json +2 -2
  6. package/data/_indexes/chains.json +1772 -88
  7. package/data/_indexes/frequency.json +8 -0
  8. package/data/_indexes/section-offsets.json +517 -517
  9. package/data/_indexes/token-budget.json +66 -66
  10. package/data/atlas-ttps.json +2 -0
  11. package/data/attack-techniques.json +22 -3
  12. package/data/cve-catalog.json +0 -28
  13. package/data/cwe-catalog.json +19 -3
  14. package/data/framework-control-gaps.json +291 -1
  15. package/data/zeroday-lessons.json +818 -0
  16. package/lib/lint-skills.js +50 -1
  17. package/manifest.json +60 -60
  18. package/orchestrator/index.js +8 -1
  19. package/package.json +1 -1
  20. package/sbom.cdx.json +47 -36
  21. package/scripts/check-test-count.js +146 -0
  22. package/scripts/predeploy.js +16 -0
  23. package/skills/age-gates-child-safety/skill.md +1 -0
  24. package/skills/ai-risk-management/skill.md +1 -0
  25. package/skills/defensive-countermeasure-mapping/skill.md +1 -0
  26. package/skills/email-security-anti-phishing/skill.md +1 -0
  27. package/skills/fuzz-testing-strategy/skill.md +1 -0
  28. package/skills/mlops-security/skill.md +1 -0
  29. package/skills/ot-ics-security/skill.md +1 -0
  30. package/skills/researcher/skill.md +1 -0
  31. package/skills/sector-energy/skill.md +1 -0
  32. package/skills/sector-federal-government/skill.md +1 -0
  33. package/skills/sector-telecom/skill.md +1 -0
  34. package/skills/skill-update-loop/skill.md +1 -0
  35. package/skills/threat-model-currency/skill.md +1 -0
  36. package/skills/threat-modeling-methodology/skill.md +1 -0
  37. package/skills/webapp-security/skill.md +1 -0
  38. package/skills/zeroday-gap-learn/skill.md +1 -0
package/sbom.cdx.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.6",
4
- "serialNumber": "urn:uuid:c9932465-eaaf-45d0-850b-1ac483acd405",
4
+ "serialNumber": "urn:uuid:594c61d8-9616-4df7-9bb0-cef4da00b814",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2133-03-02T22:32:05.000Z",
7
+ "timestamp": "2073-06-23T00:33:28.000Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "blamejs",
11
11
  "name": "scripts/refresh-sbom.js",
12
- "version": "0.13.1"
12
+ "version": "0.13.2"
13
13
  }
14
14
  ],
15
15
  "component": {
16
- "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.13.1",
16
+ "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.13.2",
17
17
  "type": "application",
18
18
  "name": "@blamejs/exceptd-skills",
19
- "version": "0.13.1",
19
+ "version": "0.13.2",
20
20
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 42 skills, 10 catalogs, 34 jurisdictions, pre-computed indexes, Ed25519-signed.",
21
21
  "licenses": [
22
22
  {
@@ -25,17 +25,17 @@
25
25
  }
26
26
  }
27
27
  ],
28
- "purl": "pkg:npm/%40blamejs/exceptd-skills@0.13.1",
28
+ "purl": "pkg:npm/%40blamejs/exceptd-skills@0.13.2",
29
29
  "hashes": [
30
30
  {
31
31
  "alg": "SHA-256",
32
- "content": "4b9469db507f7f20a000a34c40f90adbbc98c328f2fc7ddee22843732a024c09"
32
+ "content": "2ec5e865222c3ce57aa170d553cfb29c3b2e72da144e971138e433e39cd42776"
33
33
  }
34
34
  ],
35
35
  "externalReferences": [
36
36
  {
37
37
  "type": "distribution",
38
- "url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.13.1"
38
+ "url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.13.2"
39
39
  },
40
40
  {
41
41
  "type": "vcs",
@@ -108,7 +108,7 @@
108
108
  "hashes": [
109
109
  {
110
110
  "alg": "SHA-256",
111
- "content": "27fbbfa0da04040c626789c887d0ea570d865bcb9e99ea6ef3fd670c73a39b5b"
111
+ "content": "119d02d2e0db3628538359c7a0714a109faf1abc51780ce0c3be25d6ade94817"
112
112
  }
113
113
  ]
114
114
  },
@@ -229,7 +229,7 @@
229
229
  "hashes": [
230
230
  {
231
231
  "alg": "SHA-256",
232
- "content": "e4eda996f3de0a8ffc727717065165b66c690452a576b0605036db2d5caef429"
232
+ "content": "2451bc7b4e6775fdb71d2255f0d0d20230951a049343fffbd7999efb7bd665d3"
233
233
  }
234
234
  ]
235
235
  },
@@ -240,7 +240,7 @@
240
240
  "hashes": [
241
241
  {
242
242
  "alg": "SHA-256",
243
- "content": "0ec427652a9e613f04675beb26dc4c08934ba291e47427972b2a008c151cca78"
243
+ "content": "2b021f47355365d1ba59078dfa582397c7a64c2b4ebea4657ea260a66b76daf6"
244
244
  }
245
245
  ]
246
246
  },
@@ -251,7 +251,7 @@
251
251
  "hashes": [
252
252
  {
253
253
  "alg": "SHA-256",
254
- "content": "0ca33f8b0cf55a43de1290e310096020c4e0d16305bd01bcbe6cb46e0278caa8"
254
+ "content": "5c992a3c2974e117ee38b62f7ead36043819880baf23863979b490f19fe5826b"
255
255
  }
256
256
  ]
257
257
  },
@@ -262,7 +262,7 @@
262
262
  "hashes": [
263
263
  {
264
264
  "alg": "SHA-256",
265
- "content": "7fae34cf0abbd09abbbbd6a61ea06e487ddbd57060d3af6a58528c684156cf60"
265
+ "content": "8ddc5d3f9441334d544df5bc4e34846259f981d15a87dd7bed825e7f2d8b961d"
266
266
  }
267
267
  ]
268
268
  },
@@ -273,7 +273,7 @@
273
273
  "hashes": [
274
274
  {
275
275
  "alg": "SHA-256",
276
- "content": "832d096bd52081fe43c082fd6958f9054d6b6e136df5b3d4cef7efd0ea49a843"
276
+ "content": "4baff0970c17224aef4606598b90d72e09da5e927ee4f46bdbf3e12b2e6247e3"
277
277
  }
278
278
  ]
279
279
  },
@@ -317,7 +317,7 @@
317
317
  "hashes": [
318
318
  {
319
319
  "alg": "SHA-256",
320
- "content": "5e2baf1e435c5b61b183e3f603636eae4fab34ee800488919c679665882c4f62"
320
+ "content": "c4b735cac63559b4dad4cccfc97dda57434de4d9bb61a712264131ec3aae8ae6"
321
321
  }
322
322
  ]
323
323
  },
@@ -570,7 +570,7 @@
570
570
  "hashes": [
571
571
  {
572
572
  "alg": "SHA-256",
573
- "content": "40d666d0932da24b425b01ced0f9c9e5f2e6cfd2082f53861d982919dde56a4a"
573
+ "content": "6e503b75e52c8baea7e3ffaa872a2f7faedc36ca1cf53c8aec07e610c4c4ce07"
574
574
  }
575
575
  ]
576
576
  },
@@ -691,7 +691,7 @@
691
691
  "hashes": [
692
692
  {
693
693
  "alg": "SHA-256",
694
- "content": "17c6fda9bffce0c0bcf782fca3961e6a77b1caba9054ce12121adb541028bc53"
694
+ "content": "4e6667e490ed81afb0b23b0119e182b2c916c20d6f6a2c82163d8db1981c3ebe"
695
695
  }
696
696
  ]
697
697
  },
@@ -988,7 +988,7 @@
988
988
  "hashes": [
989
989
  {
990
990
  "alg": "SHA-256",
991
- "content": "4535de4e48530ccb3c5d97402ae9dcf45f0336b838bddb8ab081c6df9632014a"
991
+ "content": "034fb84e4508b8cb103699bdfe1ca220c8aa5d18cc8951f76cf74c5a1b30e418"
992
992
  }
993
993
  ]
994
994
  },
@@ -1032,7 +1032,7 @@
1032
1032
  "hashes": [
1033
1033
  {
1034
1034
  "alg": "SHA-256",
1035
- "content": "7c7378f69024b1abb11d2f17978f9023b262ac6a67a82c2eb31d4996c2caee28"
1035
+ "content": "58e6e1acf753cfa8f578eec3e41420f518dbd26d4ee0a7db7f6f0019e46350bb"
1036
1036
  }
1037
1037
  ]
1038
1038
  },
@@ -1289,6 +1289,17 @@
1289
1289
  }
1290
1290
  ]
1291
1291
  },
1292
+ {
1293
+ "bom-ref": "file:scripts/check-test-count.js",
1294
+ "type": "file",
1295
+ "name": "scripts/check-test-count.js",
1296
+ "hashes": [
1297
+ {
1298
+ "alg": "SHA-256",
1299
+ "content": "e8c7473d7a1f87d27aeab39cefa54c10c773831c3c3b0a786c81f9ac9a50d6e3"
1300
+ }
1301
+ ]
1302
+ },
1292
1303
  {
1293
1304
  "bom-ref": "file:scripts/check-test-coverage.README.md",
1294
1305
  "type": "file",
@@ -1329,7 +1340,7 @@
1329
1340
  "hashes": [
1330
1341
  {
1331
1342
  "alg": "SHA-256",
1332
- "content": "83c795a05c0a1abcbb358eb47de22f04dfc3ecec602b35fbb686093dbe27c182"
1343
+ "content": "6a7766b986988fd14105d92f3488052333afff8b72eda17460e193ca58b2d60a"
1333
1344
  }
1334
1345
  ]
1335
1346
  },
@@ -1406,7 +1417,7 @@
1406
1417
  "hashes": [
1407
1418
  {
1408
1419
  "alg": "SHA-256",
1409
- "content": "66e7773d29c179ab62f409007c05e05993e04a19273225a1e520f2481fd9a90d"
1420
+ "content": "51295c849bcced965b6448eb6b4bbd5caef5ba0b0cea7ce48abbacf47d331621"
1410
1421
  }
1411
1422
  ]
1412
1423
  },
@@ -1439,7 +1450,7 @@
1439
1450
  "hashes": [
1440
1451
  {
1441
1452
  "alg": "SHA-256",
1442
- "content": "67e62791f60231f2ff53408922fa7137a9060de72097769c630f838a1c227c45"
1453
+ "content": "2b611eb8fa4841fdfc3f1dd1ffd504a46c6ecdc654213a955efbabefb6b1db87"
1443
1454
  }
1444
1455
  ]
1445
1456
  },
@@ -1527,7 +1538,7 @@
1527
1538
  "hashes": [
1528
1539
  {
1529
1540
  "alg": "SHA-256",
1530
- "content": "e62c71ba3be2b4d0f7dfa529fec007cba6bee3013f76b93756e3e6310f2d22ab"
1541
+ "content": "3d0c7ca85f32ee1fe74598889361ef2be16d099fe6e9e8d8c8184b7004306b30"
1531
1542
  }
1532
1543
  ]
1533
1544
  },
@@ -1549,7 +1560,7 @@
1549
1560
  "hashes": [
1550
1561
  {
1551
1562
  "alg": "SHA-256",
1552
- "content": "e4e9e5a820c0ed3fde9483282e7a0ecaf79284cd2e9923ce66f2b0fb1fc44626"
1563
+ "content": "82af58b98bd808c0c6ec92554d89948378702465504db1113fc462a96366a601"
1553
1564
  }
1554
1565
  ]
1555
1566
  },
@@ -1582,7 +1593,7 @@
1582
1593
  "hashes": [
1583
1594
  {
1584
1595
  "alg": "SHA-256",
1585
- "content": "51acb746cd63366ca62567588c700a9eb3f37c43250bd9ae4e1477ccb71c5b6d"
1596
+ "content": "fb8c261def9e3344b44fd219c209027029e1eddf0e6bee1ecffb2d2176e1585e"
1586
1597
  }
1587
1598
  ]
1588
1599
  },
@@ -1659,7 +1670,7 @@
1659
1670
  "hashes": [
1660
1671
  {
1661
1672
  "alg": "SHA-256",
1662
- "content": "ca3fd922b43fc57aeb5e65c2d5a2823e6bc438167d6afa3a767cee83e4af1f96"
1673
+ "content": "72429f05010accbcb191cb1544f1b88493c2f5249362846e5713ec3226b83dc2"
1663
1674
  }
1664
1675
  ]
1665
1676
  },
@@ -1670,7 +1681,7 @@
1670
1681
  "hashes": [
1671
1682
  {
1672
1683
  "alg": "SHA-256",
1673
- "content": "9ece7b1fb7f24e37dbdd8618b94b2a4434e182e3426e15f17e26464c0a1fdfd1"
1684
+ "content": "7423cca19aab1026c07de63279137441018345731d3ee895c474316d432adaa2"
1674
1685
  }
1675
1686
  ]
1676
1687
  },
@@ -1725,7 +1736,7 @@
1725
1736
  "hashes": [
1726
1737
  {
1727
1738
  "alg": "SHA-256",
1728
- "content": "b47daaa26fdac07aa23e7becaa18487c5302e65c654f99fecab3689f23ec1bd2"
1739
+ "content": "fd441131484dc5af4cd785ded0bac039123e6205483543752cb16fa508460c00"
1729
1740
  }
1730
1741
  ]
1731
1742
  },
@@ -1736,7 +1747,7 @@
1736
1747
  "hashes": [
1737
1748
  {
1738
1749
  "alg": "SHA-256",
1739
- "content": "643fd951359c2602d9b029a244fe66c1e23f726e711141a06c09cc760a479534"
1750
+ "content": "91f00e7a9be2608393ec8cb6d5f0c9828f81b954a12a7c9fd04bd642b9091e09"
1740
1751
  }
1741
1752
  ]
1742
1753
  },
@@ -1747,7 +1758,7 @@
1747
1758
  "hashes": [
1748
1759
  {
1749
1760
  "alg": "SHA-256",
1750
- "content": "c63cf1c7c98e920f968cfe60f14e718ea71b120c1b01616af22f64a796963bbe"
1761
+ "content": "a73c3f36f23c12750d369931b7e3f884edae4a8aef35fc8690d15ef4500c4dd0"
1751
1762
  }
1752
1763
  ]
1753
1764
  },
@@ -1780,7 +1791,7 @@
1780
1791
  "hashes": [
1781
1792
  {
1782
1793
  "alg": "SHA-256",
1783
- "content": "862f9482af88e5409e011a6981a5d719863deeb646e41cd4df63e5d6597c50b1"
1794
+ "content": "59193e39c2fd73fdd7fede38a956bc730bbe4b712d7d6020788bb4d85f001ad8"
1784
1795
  }
1785
1796
  ]
1786
1797
  },
@@ -1802,7 +1813,7 @@
1802
1813
  "hashes": [
1803
1814
  {
1804
1815
  "alg": "SHA-256",
1805
- "content": "cf2b996cb18a5146614c06e3a50f4734a07d02b5be36bbdf492583f9cdcfed4d"
1816
+ "content": "b6f3bee321833dc18f5624a9be4d28673d22e22018254b0bd1f3690b945073af"
1806
1817
  }
1807
1818
  ]
1808
1819
  },
@@ -1824,7 +1835,7 @@
1824
1835
  "hashes": [
1825
1836
  {
1826
1837
  "alg": "SHA-256",
1827
- "content": "ecc6441cb47ef2bc24547e47be018098228c956a41d61ddb50de7e7b37114a37"
1838
+ "content": "cf1cc27ae5ae68d336c56d9f3afd950641e1d8d5b9f90b64c2daf00abe92bab0"
1828
1839
  }
1829
1840
  ]
1830
1841
  },
@@ -1835,7 +1846,7 @@
1835
1846
  "hashes": [
1836
1847
  {
1837
1848
  "alg": "SHA-256",
1838
- "content": "ac623f61585de66c9ef5ed63e9c6059faef77e525abc672ac6d435c616a7268f"
1849
+ "content": "cebeba3940320ebc5b44ad2bb7b4cdcda412257c1a6319a1b7379c875ebe8d6a"
1839
1850
  }
1840
1851
  ]
1841
1852
  },
@@ -1846,7 +1857,7 @@
1846
1857
  "hashes": [
1847
1858
  {
1848
1859
  "alg": "SHA-256",
1849
- "content": "fdb07324b69a3a724e3eaba17bf687d72d4bd9d5c4f440be816bc9b08b8aef04"
1860
+ "content": "f2063eaea3f5ddf0f3d37b41985bf522b682a41f104796b3f0dff611cefd043c"
1850
1861
  }
1851
1862
  ]
1852
1863
  },
@@ -1857,7 +1868,7 @@
1857
1868
  "hashes": [
1858
1869
  {
1859
1870
  "alg": "SHA-256",
1860
- "content": "59a0d7cd85b923b3f5633bdc15c1a88eef7dea6332480d93b0bb0ae93a4cd0fe"
1871
+ "content": "e26f194880cd6acf46abe31e9348d445e9222c7691e9b9b953662c4a472462f5"
1861
1872
  }
1862
1873
  ]
1863
1874
  },
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * scripts/check-test-count.js — v0.13.2 canonical-test-count predeploy gate.
6
+ *
7
+ * Why this exists. The v0.12 audit flagged that nothing in the suite asserts
8
+ * "we expect N tests today." A test file accidentally deleted, a skip-all
9
+ * mistakenly committed, or a misnamed file glob-excluded would all silently
10
+ * drop tests without anyone noticing. The lint + diff-coverage gates catch
11
+ * source changes; this gate catches test-set shrinkage.
12
+ *
13
+ * Mechanism: count `test(`, `test.only(`, and `test.skip(` declarations
14
+ * across `tests/*.test.js` via static analysis (faster than running). Compare
15
+ * to a baseline pinned in `tests/.test-count-baseline.json`. Fail if the
16
+ * observed count drops MORE than the configured tolerance (default 1) below
17
+ * the baseline. Growth above baseline is fine; if the count grows by more
18
+ * than `update_baseline_when_growth_exceeds`, surface a notice that the
19
+ * baseline file should be refreshed (operator commits the refresh as part
20
+ * of the release that added the tests).
21
+ *
22
+ * Output:
23
+ * stdout: structured JSON when --json, else a one-line summary
24
+ * exit 0: observed count is at or above baseline minus tolerance
25
+ * exit 1: observed count dropped beyond tolerance — fail predeploy
26
+ * exit 2: baseline file missing or malformed
27
+ */
28
+
29
+ const fs = require('fs');
30
+ const path = require('path');
31
+
32
+ const ROOT = path.resolve(__dirname, '..');
33
+ const TESTS_DIR = path.join(ROOT, 'tests');
34
+ const BASELINE_PATH = path.join(TESTS_DIR, '.test-count-baseline.json');
35
+
36
+ function listTestFiles(dir) {
37
+ const out = [];
38
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
39
+ for (const e of entries) {
40
+ const p = path.join(dir, e.name);
41
+ if (e.isDirectory()) {
42
+ if (e.name === '_helpers' || e.name === 'fixtures' || e.name === 'e2e-scenarios') continue;
43
+ out.push(...listTestFiles(p));
44
+ } else if (e.isFile() && e.name.endsWith('.test.js')) {
45
+ out.push(p);
46
+ }
47
+ }
48
+ return out;
49
+ }
50
+
51
+ function countTests(filePath) {
52
+ const text = fs.readFileSync(filePath, 'utf8');
53
+ const lines = text.split('\n');
54
+ let count = 0;
55
+ for (const line of lines) {
56
+ const stripped = line.replace(/^\s*\/\/.*$/, '').trim();
57
+ if (!stripped) continue;
58
+ if (/(?<![A-Za-z0-9_$.])test(?:\.only|\.skip)?\s*\(/.test(stripped)) count++;
59
+ }
60
+ return count;
61
+ }
62
+
63
+ function main() {
64
+ const wantJson = process.argv.includes('--json');
65
+ const wantUpdate = process.argv.includes('--update-baseline');
66
+
67
+ if (!fs.existsSync(BASELINE_PATH)) {
68
+ if (wantUpdate) {
69
+ const files = listTestFiles(TESTS_DIR);
70
+ const observed = files.reduce((n, f) => n + countTests(f), 0);
71
+ fs.writeFileSync(BASELINE_PATH, JSON.stringify({
72
+ baseline: observed,
73
+ tolerance: 1,
74
+ update_baseline_when_growth_exceeds: 20,
75
+ notes: 'Operator-pinned canonical test count. Bump when new test files land in a release. See scripts/check-test-count.js for the contract.',
76
+ recorded_at: new Date().toISOString().slice(0, 10),
77
+ }, null, 2) + '\n', 'utf8');
78
+ console.error(`[check-test-count] wrote initial baseline: ${observed}`);
79
+ process.exit(0);
80
+ }
81
+ console.error(`[check-test-count] baseline missing at ${path.relative(ROOT, BASELINE_PATH)}. Run with --update-baseline to create it.`);
82
+ process.exit(2);
83
+ }
84
+
85
+ let baselineFile;
86
+ try { baselineFile = JSON.parse(fs.readFileSync(BASELINE_PATH, 'utf8')); }
87
+ catch (e) {
88
+ console.error(`[check-test-count] cannot parse baseline: ${e.message}`);
89
+ process.exit(2);
90
+ }
91
+ const baseline = baselineFile.baseline;
92
+ const tolerance = baselineFile.tolerance || 1;
93
+ const updateThreshold = baselineFile.update_baseline_when_growth_exceeds || 20;
94
+ if (typeof baseline !== 'number' || baseline <= 0) {
95
+ console.error(`[check-test-count] baseline value invalid: ${baseline}`);
96
+ process.exit(2);
97
+ }
98
+
99
+ const files = listTestFiles(TESTS_DIR);
100
+ const observed = files.reduce((n, f) => n + countTests(f), 0);
101
+
102
+ if (wantUpdate) {
103
+ fs.writeFileSync(BASELINE_PATH, JSON.stringify({
104
+ ...baselineFile,
105
+ baseline: observed,
106
+ recorded_at: new Date().toISOString().slice(0, 10),
107
+ }, null, 2) + '\n', 'utf8');
108
+ console.error(`[check-test-count] baseline updated: ${baseline} -> ${observed}`);
109
+ process.exit(0);
110
+ }
111
+
112
+ const delta = observed - baseline;
113
+ const status = delta < -tolerance
114
+ ? 'shrunk_beyond_tolerance'
115
+ : delta > updateThreshold
116
+ ? 'grew_beyond_threshold_consider_bump'
117
+ : 'ok';
118
+
119
+ if (wantJson) {
120
+ process.stdout.write(JSON.stringify({
121
+ ok: status === 'ok' || status === 'grew_beyond_threshold_consider_bump',
122
+ verb: 'check-test-count',
123
+ observed,
124
+ baseline,
125
+ tolerance,
126
+ delta,
127
+ status,
128
+ files_scanned: files.length,
129
+ }) + '\n');
130
+ } else {
131
+ console.log(`[check-test-count] observed=${observed} baseline=${baseline} delta=${delta >= 0 ? '+' : ''}${delta} tolerance=${tolerance} files=${files.length} status=${status}`);
132
+ }
133
+
134
+ if (status === 'shrunk_beyond_tolerance') {
135
+ console.error(`[check-test-count] FAIL - test count dropped from ${baseline} to ${observed} (delta ${delta}, tolerance -${tolerance}).`);
136
+ console.error('[check-test-count] Either a test file was accidentally removed, a test() invocation was deleted, OR the baseline is stale.');
137
+ console.error('[check-test-count] If the drop is intentional, run: node scripts/check-test-count.js --update-baseline');
138
+ process.exit(1);
139
+ }
140
+ if (status === 'grew_beyond_threshold_consider_bump') {
141
+ console.error(`[check-test-count] NOTICE - test count grew by ${delta} (above the ${updateThreshold} notice threshold). Consider refreshing the baseline: node scripts/check-test-count.js --update-baseline`);
142
+ }
143
+ process.exit(0);
144
+ }
145
+
146
+ main();
@@ -177,6 +177,22 @@ const GATES = [
177
177
  args: [path.join(ROOT, "lib", "validate-playbooks.js")],
178
178
  ciJobName: "Validate playbooks",
179
179
  },
180
+ {
181
+ // v0.13.2: refuse silent test-set shrinkage. Static-counts `test(`
182
+ // declarations across tests/*.test.js and compares to the pinned
183
+ // baseline in tests/.test-count-baseline.json. Catches the class
184
+ // of regression where a test file gets accidentally deleted, a
185
+ // skip-all lands without review, or a misnamed file slips through
186
+ // the glob. The baseline is operator-refreshed on releases that
187
+ // intentionally add many new tests; --update-baseline rewrites it.
188
+ name: "Test-count baseline (no silent shrinkage)",
189
+ command: process.execPath,
190
+ args: [path.join(ROOT, "scripts", "check-test-count.js")],
191
+ // Folds under the existing Data integrity CI job rather than a
192
+ // dedicated job — the check is fast (~70ms) static analysis and
193
+ // shares the integrity-tier framing with manifest-snapshot etc.
194
+ ciJobName: "Data integrity (catalog + manifest snapshot)",
195
+ },
180
196
  ];
181
197
 
182
198
  function runGate(gate) {
@@ -59,6 +59,7 @@ forward_watch:
59
59
  - France SREN (Securing and Regulating the Digital Space) Act 2024 — ARCOM age-verification referential for adult content services; double-anonymity model under deployment
60
60
  - US state adult-site age-verification laws — 19+ states by mid-2026 (TX HB 18 upheld by SCOTUS June 2025 in Free Speech Coalition v. Paxton); track ongoing challenges in remaining states
61
61
  last_threat_review: "2026-05-11"
62
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief age-gates-child-safety` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
62
63
  ---
63
64
 
64
65
  # Age Gates and Child Online Safety (mid-2026)
@@ -45,6 +45,7 @@ cwe_refs:
45
45
  d3fend_refs:
46
46
  - D3-IOPR
47
47
  last_threat_review: "2026-05-15"
48
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief ai-risk-management` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
48
49
  ---
49
50
 
50
51
  # AI Risk Management (Governance Layer)
@@ -48,6 +48,7 @@ d3fend_refs:
48
48
  - D3-RPA
49
49
  - D3-SCP
50
50
  last_threat_review: "2026-05-11"
51
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief defensive-countermeasure-mapping` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
51
52
  ---
52
53
 
53
54
  # Defensive Countermeasure Mapping — D3FEND as the Blue-Team Counterpart to ATT&CK / ATLAS
@@ -48,6 +48,7 @@ d3fend_refs:
48
48
  - D3-IOPR
49
49
  - D3-MFA
50
50
  last_threat_review: "2026-05-11"
51
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief email-security-anti-phishing` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
51
52
  ---
52
53
 
53
54
  # Email Security and Anti-Phishing Assessment
@@ -45,6 +45,7 @@ d3fend_refs:
45
45
  - D3-IOPR
46
46
  - D3-PSEP
47
47
  last_threat_review: "2026-05-11"
48
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief fuzz-testing-strategy` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
48
49
  ---
49
50
 
50
51
  # Fuzz Testing Strategy
@@ -62,6 +62,7 @@ forward_watch:
62
62
  - EU AI Act high-risk technical-file implementing acts (2026-2027) — operational requirements for Article 10 / 13 / 15 documentation may pin ML-BOM or model-signing
63
63
  - MITRE ATLAS v5.4.0 (released February 2026) shipped the AML.T0010 sub-technique expansion this forecast tracked plus new techniques ("Publish Poisoned AI Agent Tool", "Escape to Host"); inventory now 16 tactics, 84 techniques, 56 sub-techniques. Forward watch: subsequent ATLAS minor and major releases — track next-cadence updates to agentic-AI TTPs and MLOps-pipeline-specific techniques
64
64
  last_threat_review: "2026-05-15"
65
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief mlops-security` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
65
66
  ---
66
67
 
67
68
  # MLOps Pipeline Security Assessment
@@ -44,6 +44,7 @@ cwe_refs:
44
44
  - CWE-1037
45
45
  d3fend_refs: []
46
46
  last_threat_review: "2026-05-11"
47
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief ot-ics-security` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
47
48
  ---
48
49
 
49
50
  # OT / ICS Security (mid-2026)
@@ -25,6 +25,7 @@ atlas_refs: []
25
25
  attack_refs: []
26
26
  framework_gaps: []
27
27
  last_threat_review: "2026-05-11"
28
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief researcher` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
28
29
  ---
29
30
 
30
31
  # Researcher — Threat Intel Triage and Dispatch
@@ -57,6 +57,7 @@ forward_watch:
57
57
  - MadIoT-class research on consumer-IoT-driven grid frequency manipulation moving from proof-of-concept to attributed campaigns
58
58
  - ICS-CERT advisory feed (https://www.cisa.gov/news-events/cybersecurity-advisories/ics-advisories) for vendor CVEs in Siemens, Rockwell, Schneider Electric, ABB, GE Vernova, Hitachi Energy, AVEVA / OSIsoft PI
59
59
  last_threat_review: "2026-05-11"
60
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief sector-energy` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
60
61
  ---
61
62
 
62
63
  # Sector — Energy (Electric Power, Oil & Gas, Water/Wastewater, Renewables) — mid-2026
@@ -61,6 +61,7 @@ forward_watch:
61
61
  - EU Cybersecurity Certification Scheme on Common Criteria (EUCC) operational — first certificates issued 2024; high-assurance level for government use cases ramping
62
62
  - Australia PSPF 2024 revision and ISM quarterly updates — track for Essential Eight Maturity Level requirements for federal entities
63
63
  last_threat_review: "2026-05-11"
64
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief sector-federal-government` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
64
65
  ---
65
66
 
66
67
  # Federal Government and Defense Contractor Cybersecurity
@@ -67,6 +67,7 @@ forward_watch:
67
67
  - "3GPP TS 33.501 updates (5G security architecture rebaseline)"
68
68
  - "O-RAN SFG / WG11 security specifications"
69
69
  last_threat_review: 2026-05-15
70
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief sector-telecom` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
70
71
  ---
71
72
 
72
73
  ## Threat Context (mid-2026)
@@ -34,6 +34,7 @@ forward_watch:
34
34
  - Framework publication updates (NIST SP updates, ISO amendments, NIS2 implementing acts)
35
35
  - IETF RFC publications and draft status changes (datatracker.ietf.org, rfc-editor.org); run `npm run validate-rfcs` quarterly
36
36
  last_threat_review: "2026-05-15"
37
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief skill-update-loop` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
37
38
  ---
38
39
 
39
40
  # Skill Update Loop
@@ -23,6 +23,7 @@ forward_watch:
23
23
  - New MCP or agent protocol security disclosures
24
24
  - Emerging malware families using AI for evasion
25
25
  last_threat_review: "2026-05-15"
26
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief threat-model-currency` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
26
27
  ---
27
28
 
28
29
  # Threat Model Currency Assessment
@@ -44,6 +44,7 @@ forward_watch:
44
44
  - LINDDUN-GO and LINDDUN-PRO updates incorporating LLM privacy threats
45
45
  - PASTA v2 updates incorporating AI/ML application threats
46
46
  last_threat_review: "2026-05-11"
47
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief threat-modeling-methodology` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
47
48
  ---
48
49
 
49
50
  # Threat Modeling Methodology
@@ -66,6 +66,7 @@ d3fend_refs:
66
66
  forward_watch:
67
67
  - NGINX Rift CVE-2026-42945 (disclosed 2026-05-13, source depthfirst) — KEV-watch predicted CISA KEV listing by 2026-05-29; AI-assisted discovery angle; track for active-exploitation confirmation and patch advisory affecting front-door web app deployments
68
68
  last_threat_review: "2026-05-11"
69
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief webapp-security` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
69
70
  ---
70
71
 
71
72
  # Web Application Security Assessment
@@ -24,6 +24,7 @@ forward_watch:
24
24
  - Framework updates that close previously open gaps
25
25
  - Vendor advisories for MCP/AI tool supply chain CVEs
26
26
  last_threat_review: "2026-05-15"
27
+ discovery_mode: "standalone" # v0.13.2: operator-reached via `exceptd brief zeroday-gap-learn` or `exceptd ask`; not chained into any playbook's direct.skill_chain by design
27
28
  ---
28
29
 
29
30
  # Zero-Day Learning Loop