@blamejs/exceptd-skills 0.13.0 → 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 (40) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/bin/exceptd.js +35 -6
  3. package/data/_indexes/_meta.json +26 -26
  4. package/data/_indexes/activity-feed.json +3 -3
  5. package/data/_indexes/catalog-summaries.json +3 -3
  6. package/data/_indexes/chains.json +2868 -700
  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 +3 -0
  11. package/data/attack-techniques.json +35 -7
  12. package/data/cve-catalog.json +177 -31
  13. package/data/cwe-catalog.json +26 -6
  14. package/data/framework-control-gaps.json +310 -8
  15. package/data/zeroday-lessons.json +996 -0
  16. package/lib/lint-skills.js +50 -1
  17. package/lib/refresh-external.js +7 -0
  18. package/lib/source-advisories.js +281 -0
  19. package/manifest.json +60 -60
  20. package/orchestrator/index.js +183 -1
  21. package/package.json +1 -1
  22. package/sbom.cdx.json +59 -37
  23. package/scripts/check-test-count.js +146 -0
  24. package/scripts/predeploy.js +16 -0
  25. package/skills/age-gates-child-safety/skill.md +1 -0
  26. package/skills/ai-risk-management/skill.md +1 -0
  27. package/skills/defensive-countermeasure-mapping/skill.md +1 -0
  28. package/skills/email-security-anti-phishing/skill.md +1 -0
  29. package/skills/fuzz-testing-strategy/skill.md +1 -0
  30. package/skills/mlops-security/skill.md +1 -0
  31. package/skills/ot-ics-security/skill.md +1 -0
  32. package/skills/researcher/skill.md +1 -0
  33. package/skills/sector-energy/skill.md +1 -0
  34. package/skills/sector-federal-government/skill.md +1 -0
  35. package/skills/sector-telecom/skill.md +1 -0
  36. package/skills/skill-update-loop/skill.md +1 -0
  37. package/skills/threat-model-currency/skill.md +1 -0
  38. package/skills/threat-modeling-methodology/skill.md +1 -0
  39. package/skills/webapp-security/skill.md +1 -0
  40. 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:829585d1-91e2-45de-874d-d62fe48ec566",
4
+ "serialNumber": "urn:uuid:594c61d8-9616-4df7-9bb0-cef4da00b814",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2095-06-04T21:53:21.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.0"
12
+ "version": "0.13.2"
13
13
  }
14
14
  ],
15
15
  "component": {
16
- "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.13.0",
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.0",
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.0",
28
+ "purl": "pkg:npm/%40blamejs/exceptd-skills@0.13.2",
29
29
  "hashes": [
30
30
  {
31
31
  "alg": "SHA-256",
32
- "content": "3ea4b95d46e8c9369f369fdadba342d8fc1638d8e43239e4ff3d11678cdd3587"
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.0"
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": "c058721430ec2177c5e8ca4490ba848cef441e9476273cb17825292541c22909"
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": "1301e6d053be91ac41ec38e64c72ac726e478b7fd6482884c3e2219c8fa16173"
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": "4afc226cc7359771a54aab1a1ff53c5530d1ef54eabc1d2fbd3b1caeda2f64a6"
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": "d4d2ecc24a0b010d5e83fea9850130278bea120ef130c99dcbb03eff64008f2d"
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": "7a8e01c6f5538125b81ddb29f1b658f0fd407682bc2998aafc6b759e3b1afe48"
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": "2c37446ad38270ebe51c0eaf340b26f19367c4eae47886d6310f16a4185354bf"
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": "27d46a0e09a3edbe97dfbb070c3991348567cf93c86a3e94c767c5ad2dfb653e"
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
  },
@@ -724,7 +724,7 @@
724
724
  "hashes": [
725
725
  {
726
726
  "alg": "SHA-256",
727
- "content": "46bd5c0efe40e5c2bfc95df9df69dc53cf7f10e6537dd575821a5e39ac59dcab"
727
+ "content": "3624e86150e85a33cb79d62193091fc1409085c3600f222dd65b2aa09ef728a8"
728
728
  }
729
729
  ]
730
730
  },
@@ -805,6 +805,17 @@
805
805
  }
806
806
  ]
807
807
  },
808
+ {
809
+ "bom-ref": "file:lib/source-advisories.js",
810
+ "type": "file",
811
+ "name": "lib/source-advisories.js",
812
+ "hashes": [
813
+ {
814
+ "alg": "SHA-256",
815
+ "content": "678cce9841ee92128e777cc0f355e020bd69c37cbd637be91479858e6a4958fd"
816
+ }
817
+ ]
818
+ },
808
819
  {
809
820
  "bom-ref": "file:lib/source-ghsa.js",
810
821
  "type": "file",
@@ -977,7 +988,7 @@
977
988
  "hashes": [
978
989
  {
979
990
  "alg": "SHA-256",
980
- "content": "e4db72d2b307adb316fad1af15322392587c50c4de5b7592c015b93631f6f26b"
991
+ "content": "034fb84e4508b8cb103699bdfe1ca220c8aa5d18cc8951f76cf74c5a1b30e418"
981
992
  }
982
993
  ]
983
994
  },
@@ -1021,7 +1032,7 @@
1021
1032
  "hashes": [
1022
1033
  {
1023
1034
  "alg": "SHA-256",
1024
- "content": "0803533bc88268e1aa43dc612f13c8a1f280fb08e4b8dc95d1dd8a57bcf7a3d5"
1035
+ "content": "58e6e1acf753cfa8f578eec3e41420f518dbd26d4ee0a7db7f6f0019e46350bb"
1025
1036
  }
1026
1037
  ]
1027
1038
  },
@@ -1278,6 +1289,17 @@
1278
1289
  }
1279
1290
  ]
1280
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
+ },
1281
1303
  {
1282
1304
  "bom-ref": "file:scripts/check-test-coverage.README.md",
1283
1305
  "type": "file",
@@ -1318,7 +1340,7 @@
1318
1340
  "hashes": [
1319
1341
  {
1320
1342
  "alg": "SHA-256",
1321
- "content": "83c795a05c0a1abcbb358eb47de22f04dfc3ecec602b35fbb686093dbe27c182"
1343
+ "content": "6a7766b986988fd14105d92f3488052333afff8b72eda17460e193ca58b2d60a"
1322
1344
  }
1323
1345
  ]
1324
1346
  },
@@ -1395,7 +1417,7 @@
1395
1417
  "hashes": [
1396
1418
  {
1397
1419
  "alg": "SHA-256",
1398
- "content": "66e7773d29c179ab62f409007c05e05993e04a19273225a1e520f2481fd9a90d"
1420
+ "content": "51295c849bcced965b6448eb6b4bbd5caef5ba0b0cea7ce48abbacf47d331621"
1399
1421
  }
1400
1422
  ]
1401
1423
  },
@@ -1428,7 +1450,7 @@
1428
1450
  "hashes": [
1429
1451
  {
1430
1452
  "alg": "SHA-256",
1431
- "content": "67e62791f60231f2ff53408922fa7137a9060de72097769c630f838a1c227c45"
1453
+ "content": "2b611eb8fa4841fdfc3f1dd1ffd504a46c6ecdc654213a955efbabefb6b1db87"
1432
1454
  }
1433
1455
  ]
1434
1456
  },
@@ -1516,7 +1538,7 @@
1516
1538
  "hashes": [
1517
1539
  {
1518
1540
  "alg": "SHA-256",
1519
- "content": "e62c71ba3be2b4d0f7dfa529fec007cba6bee3013f76b93756e3e6310f2d22ab"
1541
+ "content": "3d0c7ca85f32ee1fe74598889361ef2be16d099fe6e9e8d8c8184b7004306b30"
1520
1542
  }
1521
1543
  ]
1522
1544
  },
@@ -1538,7 +1560,7 @@
1538
1560
  "hashes": [
1539
1561
  {
1540
1562
  "alg": "SHA-256",
1541
- "content": "e4e9e5a820c0ed3fde9483282e7a0ecaf79284cd2e9923ce66f2b0fb1fc44626"
1563
+ "content": "82af58b98bd808c0c6ec92554d89948378702465504db1113fc462a96366a601"
1542
1564
  }
1543
1565
  ]
1544
1566
  },
@@ -1571,7 +1593,7 @@
1571
1593
  "hashes": [
1572
1594
  {
1573
1595
  "alg": "SHA-256",
1574
- "content": "51acb746cd63366ca62567588c700a9eb3f37c43250bd9ae4e1477ccb71c5b6d"
1596
+ "content": "fb8c261def9e3344b44fd219c209027029e1eddf0e6bee1ecffb2d2176e1585e"
1575
1597
  }
1576
1598
  ]
1577
1599
  },
@@ -1648,7 +1670,7 @@
1648
1670
  "hashes": [
1649
1671
  {
1650
1672
  "alg": "SHA-256",
1651
- "content": "ca3fd922b43fc57aeb5e65c2d5a2823e6bc438167d6afa3a767cee83e4af1f96"
1673
+ "content": "72429f05010accbcb191cb1544f1b88493c2f5249362846e5713ec3226b83dc2"
1652
1674
  }
1653
1675
  ]
1654
1676
  },
@@ -1659,7 +1681,7 @@
1659
1681
  "hashes": [
1660
1682
  {
1661
1683
  "alg": "SHA-256",
1662
- "content": "9ece7b1fb7f24e37dbdd8618b94b2a4434e182e3426e15f17e26464c0a1fdfd1"
1684
+ "content": "7423cca19aab1026c07de63279137441018345731d3ee895c474316d432adaa2"
1663
1685
  }
1664
1686
  ]
1665
1687
  },
@@ -1714,7 +1736,7 @@
1714
1736
  "hashes": [
1715
1737
  {
1716
1738
  "alg": "SHA-256",
1717
- "content": "b47daaa26fdac07aa23e7becaa18487c5302e65c654f99fecab3689f23ec1bd2"
1739
+ "content": "fd441131484dc5af4cd785ded0bac039123e6205483543752cb16fa508460c00"
1718
1740
  }
1719
1741
  ]
1720
1742
  },
@@ -1725,7 +1747,7 @@
1725
1747
  "hashes": [
1726
1748
  {
1727
1749
  "alg": "SHA-256",
1728
- "content": "643fd951359c2602d9b029a244fe66c1e23f726e711141a06c09cc760a479534"
1750
+ "content": "91f00e7a9be2608393ec8cb6d5f0c9828f81b954a12a7c9fd04bd642b9091e09"
1729
1751
  }
1730
1752
  ]
1731
1753
  },
@@ -1736,7 +1758,7 @@
1736
1758
  "hashes": [
1737
1759
  {
1738
1760
  "alg": "SHA-256",
1739
- "content": "c63cf1c7c98e920f968cfe60f14e718ea71b120c1b01616af22f64a796963bbe"
1761
+ "content": "a73c3f36f23c12750d369931b7e3f884edae4a8aef35fc8690d15ef4500c4dd0"
1740
1762
  }
1741
1763
  ]
1742
1764
  },
@@ -1769,7 +1791,7 @@
1769
1791
  "hashes": [
1770
1792
  {
1771
1793
  "alg": "SHA-256",
1772
- "content": "862f9482af88e5409e011a6981a5d719863deeb646e41cd4df63e5d6597c50b1"
1794
+ "content": "59193e39c2fd73fdd7fede38a956bc730bbe4b712d7d6020788bb4d85f001ad8"
1773
1795
  }
1774
1796
  ]
1775
1797
  },
@@ -1791,7 +1813,7 @@
1791
1813
  "hashes": [
1792
1814
  {
1793
1815
  "alg": "SHA-256",
1794
- "content": "cf2b996cb18a5146614c06e3a50f4734a07d02b5be36bbdf492583f9cdcfed4d"
1816
+ "content": "b6f3bee321833dc18f5624a9be4d28673d22e22018254b0bd1f3690b945073af"
1795
1817
  }
1796
1818
  ]
1797
1819
  },
@@ -1813,7 +1835,7 @@
1813
1835
  "hashes": [
1814
1836
  {
1815
1837
  "alg": "SHA-256",
1816
- "content": "ecc6441cb47ef2bc24547e47be018098228c956a41d61ddb50de7e7b37114a37"
1838
+ "content": "cf1cc27ae5ae68d336c56d9f3afd950641e1d8d5b9f90b64c2daf00abe92bab0"
1817
1839
  }
1818
1840
  ]
1819
1841
  },
@@ -1824,7 +1846,7 @@
1824
1846
  "hashes": [
1825
1847
  {
1826
1848
  "alg": "SHA-256",
1827
- "content": "ac623f61585de66c9ef5ed63e9c6059faef77e525abc672ac6d435c616a7268f"
1849
+ "content": "cebeba3940320ebc5b44ad2bb7b4cdcda412257c1a6319a1b7379c875ebe8d6a"
1828
1850
  }
1829
1851
  ]
1830
1852
  },
@@ -1835,7 +1857,7 @@
1835
1857
  "hashes": [
1836
1858
  {
1837
1859
  "alg": "SHA-256",
1838
- "content": "fdb07324b69a3a724e3eaba17bf687d72d4bd9d5c4f440be816bc9b08b8aef04"
1860
+ "content": "f2063eaea3f5ddf0f3d37b41985bf522b682a41f104796b3f0dff611cefd043c"
1839
1861
  }
1840
1862
  ]
1841
1863
  },
@@ -1846,7 +1868,7 @@
1846
1868
  "hashes": [
1847
1869
  {
1848
1870
  "alg": "SHA-256",
1849
- "content": "59a0d7cd85b923b3f5633bdc15c1a88eef7dea6332480d93b0bb0ae93a4cd0fe"
1871
+ "content": "e26f194880cd6acf46abe31e9348d445e9222c7691e9b9b953662c4a472462f5"
1850
1872
  }
1851
1873
  ]
1852
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