@blamejs/exceptd-skills 0.15.50 → 0.15.52

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/sbom.cdx.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.6",
4
- "serialNumber": "urn:uuid:ede2338f-0db3-4a5c-859d-567abe4a25ef",
4
+ "serialNumber": "urn:uuid:7ed2b616-e18e-40a0-bf4e-b08af006675e",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2152-06-21T09:55:27.000Z",
7
+ "timestamp": "2093-06-04T14:26:30.000Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "blamejs",
11
11
  "name": "scripts/refresh-sbom.js",
12
- "version": "0.15.50"
12
+ "version": "0.15.52"
13
13
  }
14
14
  ],
15
15
  "component": {
16
- "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.15.50",
16
+ "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.15.52",
17
17
  "type": "application",
18
18
  "name": "@blamejs/exceptd-skills",
19
- "version": "0.15.50",
19
+ "version": "0.15.52",
20
20
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 42 skills, 11 catalogs (427 CVEs / 173 CWEs / 805 ATT&CK + ICS / 170 ATLAS / 468 D3FEND / 8888 RFCs), 35 jurisdictions, 10-class catalog gap detector + budget gate, real XML parser + canonical-form diff + content-pattern regression detection, 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.15.50",
28
+ "purl": "pkg:npm/%40blamejs/exceptd-skills@0.15.52",
29
29
  "hashes": [
30
30
  {
31
31
  "alg": "SHA-256",
32
- "content": "332e8187d86819842a9abb646bcd8645e87c14dcaf6596cc28ca6dbb18af82c5"
32
+ "content": "926aefd5a4e9417e02388bf3fba187507cb15c4d562d1b73383978b440923626"
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.15.50"
38
+ "url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.15.52"
39
39
  },
40
40
  {
41
41
  "type": "vcs",
@@ -116,11 +116,11 @@
116
116
  "hashes": [
117
117
  {
118
118
  "alg": "SHA-256",
119
- "content": "127bec3e5780f53bf98eaa33157c004423edda4c0c9931018bc8d67bd690d2d7"
119
+ "content": "efc1b8a8b88115b6f3c39a68fd41633df8bc7992f72ec4683e3c1760323df012"
120
120
  },
121
121
  {
122
122
  "alg": "SHA3-512",
123
- "content": "aebfca6cb624dfddaee726ba8760cd3318347278a107629bd10c3266d2bb2ef132bf916b3a8d38f83f5a712b647bd770adc98d0635d9348a6f0cf940baeea75b"
123
+ "content": "8191b5774e3f765a359738f31c9f1dae30e1fae21e9d65c6dd2abc4531823c08761b037bb808ab6cd57815145b15057aeefef9a103e9222e19502315e13bcf7b"
124
124
  }
125
125
  ]
126
126
  },
@@ -161,11 +161,11 @@
161
161
  "hashes": [
162
162
  {
163
163
  "alg": "SHA-256",
164
- "content": "68a43ab8f9495c0af7a10b4f1c7f8fc729aeee2ab5fe523f932bdd62eb971d83"
164
+ "content": "34dd0f6eab14ad5b2c974b81e7016c841c7849f862b58fcc94fd499a5ed2b8a9"
165
165
  },
166
166
  {
167
167
  "alg": "SHA3-512",
168
- "content": "dbf4c6f0414142a42f37a4f977f07d0d5167abe2e899f6775b29039aff8270b094986747a9c875f362945b0fe8a4173260232a51a57af61ec4553ec598039e18"
168
+ "content": "7f16c6f48c03f0fa2858edeab6388a9c2b6a952e7d40186bac5748e2ca90d4b3b085422416640eff8c02ead3dd8e19071c696c8cf9f8f2dcc13e8b9d725fc36c"
169
169
  }
170
170
  ]
171
171
  },
@@ -281,11 +281,11 @@
281
281
  "hashes": [
282
282
  {
283
283
  "alg": "SHA-256",
284
- "content": "cf9b74140298bdee14d4826d11ed9f7d6b1266db8a782d8dce48425e76ec9af5"
284
+ "content": "d0ca92931278d72229edb9f2526157f2832df1d97cd7e8440a0a841f8b1c664d"
285
285
  },
286
286
  {
287
287
  "alg": "SHA3-512",
288
- "content": "0528bbd4277fe3b8f4cb99eeaf35d5e9f310cfd48a0164fa4e4edb03154f1c7302af7d44617d7b6f1d6ab52e301a7772042e4c609693c49b8ce6eb64991419b9"
288
+ "content": "93377a5256af65b898ffda3586e081e64f6bb3d820305b6995dd37f022857a66e929bec0c167fed5aed5c5b18a8dec7e7e4214b3d5cfacea32e93dc8065f01c0"
289
289
  }
290
290
  ]
291
291
  },
@@ -311,11 +311,11 @@
311
311
  "hashes": [
312
312
  {
313
313
  "alg": "SHA-256",
314
- "content": "84fad74c8497cab922ed64b814752f54aa4620c2a938cb06642ff1510e1c5cb3"
314
+ "content": "318bf8e9c5aee1d0a4a1dc37c4b211f2fbc937bf332a401a22483cc7d0547252"
315
315
  },
316
316
  {
317
317
  "alg": "SHA3-512",
318
- "content": "bcb5bbab48f5374c4714cc324c10a65b5005e8a8ac6882f356427e2e3ad43d7e9a5320b156539d2dc14b1b4ee17c4e44a97d9109db9162718533202fd268af34"
318
+ "content": "d929874af7a8091416f2adc116fcfe98383325c83ece819a3e07320e29b4d8645b27256a662a6fff3267156ff70cfe2f53d7b0a82b5f249902c495f5d668b962"
319
319
  }
320
320
  ]
321
321
  },
@@ -326,11 +326,11 @@
326
326
  "hashes": [
327
327
  {
328
328
  "alg": "SHA-256",
329
- "content": "7a5f4e31401505e53330cdc4b54b39f8a8b04459d6b9411676d291c583ae535f"
329
+ "content": "cb5e305b5488a2a02e177f10e913d22f602d6016109f152903093e9614e0b470"
330
330
  },
331
331
  {
332
332
  "alg": "SHA3-512",
333
- "content": "b4f57d7819e255a3754d0fc758198df5f1ac4f69c6d9bdee8696987bb6ab15ffe03a461c509e832f706e46e50b4755d5d1f1b06e0442d50ff55d8439bacadfa7"
333
+ "content": "a7abc804efd7dfd46056a857ee13e19b8d99cdd46db7390845f4036b1e4b97bce0e00ec3f677322716f6c74b071340561fdf8cfda9b2f5ff3b502353eaf4d135"
334
334
  }
335
335
  ]
336
336
  },
@@ -341,11 +341,11 @@
341
341
  "hashes": [
342
342
  {
343
343
  "alg": "SHA-256",
344
- "content": "0fd275c2a61754958d68cea03a92794a67cf1c1d4d609f81a5728334df013ee3"
344
+ "content": "b0e4d8f90b655b2b35b1e91c682ee66f2aa51ae5d38efb14f0e1b77f75ec5f7b"
345
345
  },
346
346
  {
347
347
  "alg": "SHA3-512",
348
- "content": "f6ca0b0e10d8b5d9d45ed43fd3b80d95ecd6ecb99a0241daa7e08695de2c02c7532d720cd4e61b6c3e9a138dfb9306ccefab43715d2a1c2b16cc34784e6d34ce"
348
+ "content": "ac9ca3b420093c62dd5a53b0349f1d5e9f10f90c958d0ab921654bd0bcf674d3aef478fc5bc3d359a224b03cd26b32578fe162f55ba2423c37c0061aaa80c862"
349
349
  }
350
350
  ]
351
351
  },
@@ -401,11 +401,11 @@
401
401
  "hashes": [
402
402
  {
403
403
  "alg": "SHA-256",
404
- "content": "29e7b6aa841ddf2530ca5971bdb60d7a715684b2f6264141ad49f0de9a039d78"
404
+ "content": "49cfbcaf0f27662db7e12340839c29f05d4ae31bc255dc9fa49ad1b4a45d0fa3"
405
405
  },
406
406
  {
407
407
  "alg": "SHA3-512",
408
- "content": "db18692914f44c8cbc9a853fe43865a5547058b5b0dc656e45e83da322072a323fa08b40479ecb8dbb2d1aac1997dccff05be3c26351098806b41a750248c43e"
408
+ "content": "677e753faee573812cfcb3b33e351b36430b38d36c409149a471b0561139ba1dccc23f1a15c9b836f0c5c7384abaa5dc7a56b3f0d8c747fc7d5f216f03911028"
409
409
  }
410
410
  ]
411
411
  },
@@ -731,11 +731,11 @@
731
731
  "hashes": [
732
732
  {
733
733
  "alg": "SHA-256",
734
- "content": "d9dd7f88dca3f91bc143cb3abdb5e90ae6109b7c4b6fc8253802b9776bc9102e"
734
+ "content": "1e2f307a7207a260f8c9ab23f95e8eebb4950349d79eceb2af5b8c4df2b37dc4"
735
735
  },
736
736
  {
737
737
  "alg": "SHA3-512",
738
- "content": "117d523bb04163dceba8f2ca1ba6b487244c15d5e1b206721687411dab4c3c5265b91c224c513dafc9971087d49aed96b171dac74e15bd49030e2e8375a24f20"
738
+ "content": "9298e037ccac473c5ac994e5ef4804d93a7abfbb3fa14c836a97c4503de2e9d10b1ad8a2f371115bf17c1f63851fd252214cb183c8655f1f4150b0e3a2dc9248"
739
739
  }
740
740
  ]
741
741
  },
@@ -806,11 +806,11 @@
806
806
  "hashes": [
807
807
  {
808
808
  "alg": "SHA-256",
809
- "content": "acf9b2b001844dd2cacf1d29c7175d60db49b103847c9fddd242d2a98087541d"
809
+ "content": "c7419ef8265a8385ab29e37e3f3237f120dd2fa448692f9dc1aa2fd79339fc76"
810
810
  },
811
811
  {
812
812
  "alg": "SHA3-512",
813
- "content": "ea633dba87756ee5346ca8cbdc75d2f5d773c421cbe483792804266eea94fa9cd7e952955b43b43a93da76663c65236e36ab1a76c8bce09e9c3bb7da86df8c24"
813
+ "content": "7aea30cb368c94ff5e9fa3d1d6e60a71f81a1c36d5b358310b372b99d1e448ff5c19c34f2fb33316841906e3f988bc305a8c9c158d9fb33c62b91538a56fcde5"
814
814
  }
815
815
  ]
816
816
  },
@@ -1331,11 +1331,11 @@
1331
1331
  "hashes": [
1332
1332
  {
1333
1333
  "alg": "SHA-256",
1334
- "content": "fc9d13e98934b99981965e61affae0b03bffd8d8a0ee8f6fbb6cb8f33f2cadb5"
1334
+ "content": "bd757082645bbe88e0d03e5d533ee7c5c8e2ea922e31e13e28a336f69cd3a756"
1335
1335
  },
1336
1336
  {
1337
1337
  "alg": "SHA3-512",
1338
- "content": "4053e54232a55090a53ff765f43bd7c389bdd8ff7d9458c123e1e297ee613f8ed007805588c3e1b6e3857db088d344077ba4d8889efb2129797708ef5f5b58f2"
1338
+ "content": "85c3844a6562a1df8b6b71e54c6fc4788ad62ad918856ceb441c443625032f547c30e39618631f397c49c02708af4e1420bf2ee78885c23248f9c7f8885cef2f"
1339
1339
  }
1340
1340
  ]
1341
1341
  },
@@ -1751,11 +1751,11 @@
1751
1751
  "hashes": [
1752
1752
  {
1753
1753
  "alg": "SHA-256",
1754
- "content": "33da1072778152239ab47e8b4ef930f702678299bfa641e297a233dc9022dbfa"
1754
+ "content": "e5f2d2a803f6972ef1759593ddbec1e3badc297b8f83e667fedeaf4b68fd9819"
1755
1755
  },
1756
1756
  {
1757
1757
  "alg": "SHA3-512",
1758
- "content": "3125320c4384560007a8ee89bd9bc426445f80b5db11526ec5f7c666c92ee0f6ceac1e578a09c916e6b3fc0e3e3cea364ec968223e5827e66088fdda217edbc4"
1758
+ "content": "3388f49da3b6e08d553623d467dc0114c41c3ff39cc780751867908fcc0ba503f8236f1e740700befc5b862e471196a20ebe9590c2200c81e62db12bebe63dfd"
1759
1759
  }
1760
1760
  ]
1761
1761
  },
@@ -2179,6 +2179,21 @@
2179
2179
  }
2180
2180
  ]
2181
2181
  },
2182
+ {
2183
+ "bom-ref": "file:scripts/check-changelog-extract.js",
2184
+ "type": "file",
2185
+ "name": "scripts/check-changelog-extract.js",
2186
+ "hashes": [
2187
+ {
2188
+ "alg": "SHA-256",
2189
+ "content": "eeee0adf320e7aefee1ebcfa2283c021ca43c80938aafe1873abcf21b927686d"
2190
+ },
2191
+ {
2192
+ "alg": "SHA3-512",
2193
+ "content": "710b6d5b6d1a60c1934a2bd42c7a64aab24150a14d74368d2f5eea27bd568708ae0daa2ca3c7fdee286faec577a926e5cfbaa1b6740ad3886fc5bf3f7ed026c9"
2194
+ }
2195
+ ]
2196
+ },
2182
2197
  {
2183
2198
  "bom-ref": "file:scripts/check-codebase-patterns-currency.js",
2184
2199
  "type": "file",
@@ -2261,11 +2276,11 @@
2261
2276
  "hashes": [
2262
2277
  {
2263
2278
  "alg": "SHA-256",
2264
- "content": "467d2ae2c73a803edc61ee31f04a7a584da6aaa729940de799a482a37b2be6c3"
2279
+ "content": "d43e59b96a252c1228ca709e3753582fc02d4c32d2766b247a69ee78c0a18e28"
2265
2280
  },
2266
2281
  {
2267
2282
  "alg": "SHA3-512",
2268
- "content": "4c3638049e850410bebd27ed06f2497fab89be9c5b1917561b9e5ce3a961351c7f3a7076a58b642fb6e72e5aa2597838d77351a13d9eafe725a9e32fba3adfe1"
2283
+ "content": "994eea61e1d84f9f8c9ab77085f855d167b772e31f07ad49f5b4094d7c939f7854595f37902c4a0f360c2ce3ec7754e9db58eee0d7c865dfa2ea20d0e33885e3"
2269
2284
  }
2270
2285
  ]
2271
2286
  },
@@ -2276,11 +2291,11 @@
2276
2291
  "hashes": [
2277
2292
  {
2278
2293
  "alg": "SHA-256",
2279
- "content": "1d3b7bd15af17a88afb82dada50494433299455e76206b23a895f1ff9ca8a696"
2294
+ "content": "906735e02f452962cc221e129a9d7d08cb645f0b79b48168fea62038c35f6b71"
2280
2295
  },
2281
2296
  {
2282
2297
  "alg": "SHA3-512",
2283
- "content": "d4c3a7dc1af799ed3d8f28ba47b87a3ef4781cb28f688e76322d15404772e5199cc292bcee979eaefb19d0bb519635353a328412c59fd5baa412548282637ec8"
2298
+ "content": "09590d90712ecec967caf198f33f0ea62b24152dcfda51b1bd0422e64add9cf2f97a5873310d11d654846b83dd92c9c82ca014973ca51888d14607e2b3092e0f"
2284
2299
  }
2285
2300
  ]
2286
2301
  },
@@ -2291,11 +2306,11 @@
2291
2306
  "hashes": [
2292
2307
  {
2293
2308
  "alg": "SHA-256",
2294
- "content": "ab58a0d6a5f43e94f63161af45b4e436fe50f898e9a44588bdc15103dbc31aa8"
2309
+ "content": "2306d6560552040baf57e9a552c14f2344967338babdc1a4fff8e3b4fc0d4d9b"
2295
2310
  },
2296
2311
  {
2297
2312
  "alg": "SHA3-512",
2298
- "content": "36a39edf675786de2e7417e6097cd18f03f499ef994b8d728f93f1ca7b657e5c577f2b6ad7360a3470e7fba6287cbc65ae1eb98578c0d4ce689bd9e0f533c398"
2313
+ "content": "12d2a7a167fa40712453f60e5e1f8f57d35a2396e77b40afc38986ddf866984157ed5c312fe6eddb9803df4025e52628d7d636ee123118b64f9eef5b5644cf23"
2299
2314
  }
2300
2315
  ]
2301
2316
  },
@@ -2321,11 +2336,11 @@
2321
2336
  "hashes": [
2322
2337
  {
2323
2338
  "alg": "SHA-256",
2324
- "content": "4622fd121015535bb0a1b47a7bf57abe3a195b02e42a0be99dfadc9d67a72160"
2339
+ "content": "3e37c0d93dfc1ff3c6fd5e90bb65417f3ebcabd8435925c4292b4ca86bdd11c2"
2325
2340
  },
2326
2341
  {
2327
2342
  "alg": "SHA3-512",
2328
- "content": "85f755a9228afe24b704d39d58ee11436c8dd663df47e8c7c84da9454c5bad1cf9f166c833a9648517f9c2689730103617b41bec0739c637d91fafb041f7db63"
2343
+ "content": "8c1be019521e12c483cb5e71ee94e6601497c514b09080c2560ca942bb2979f0b08928866213b13fc882105f6aa6e33901fb7edb8cc7dad9a1f03d8bd8a45ce6"
2329
2344
  }
2330
2345
  ]
2331
2346
  },
@@ -2471,11 +2486,11 @@
2471
2486
  "hashes": [
2472
2487
  {
2473
2488
  "alg": "SHA-256",
2474
- "content": "a5a43d931bcc09f0e5866e5860efd373943e5ce797ff368dc009ea4161d03cd6"
2489
+ "content": "f199cc754f202fa994c1769af5e02a4ef995ee02e87221422b00f945b03e99db"
2475
2490
  },
2476
2491
  {
2477
2492
  "alg": "SHA3-512",
2478
- "content": "dff963f00485e37546f605542d94d5e8730be9b888d98dd9bc9d256f38b4820d3c3346b1bfdbacedefc4b47d7b07afffdd1b3d7a77ba4cf71593efdd5530b9af"
2493
+ "content": "d504c65cda545acda811997487ca8b6fef3ee5b5447b537f502295d5f6f4266cb2ecb743389b04aa2b6118b021af6f2ce9c6f00e44cf60bd7c06b72456a500f8"
2479
2494
  }
2480
2495
  ]
2481
2496
  },
@@ -2531,11 +2546,11 @@
2531
2546
  "hashes": [
2532
2547
  {
2533
2548
  "alg": "SHA-256",
2534
- "content": "59a341868f7f362d4138d61cbbf480fa5e293e06399b58b57ea6ff27e4e66d03"
2549
+ "content": "fd04f3e4122b4f3ca7e9a266c763dd4e954710d496da8072f5c2e500a4dbe32c"
2535
2550
  },
2536
2551
  {
2537
2552
  "alg": "SHA3-512",
2538
- "content": "415dec1034ba285597cf8dcde916b8cb3e9f526a1b7df3cf871c55843de659dcc0dc0cb970e25dbdb646b5da40a7383b8f2cea51d76c8ec1a924fe1913123ff2"
2553
+ "content": "9b606b4d5afcc79a7a39650c3f425af0a8abb21f484e70e5469431a0be26b27969108bd51f167cab23b41d6e0ec7d14b8e6d82d597ca6166942605e15fc016c0"
2539
2554
  }
2540
2555
  ]
2541
2556
  },
@@ -3101,11 +3116,11 @@
3101
3116
  "hashes": [
3102
3117
  {
3103
3118
  "alg": "SHA-256",
3104
- "content": "7c568ee9805f4c822c16c266348e35fa6f2d7a3c76135fa34b0cfa77f003a878"
3119
+ "content": "23d15c234afedec011d9c3e588334132373a22d09ef33cd2d943e479c88fcb43"
3105
3120
  },
3106
3121
  {
3107
3122
  "alg": "SHA3-512",
3108
- "content": "135e7723a95b7e3cc929e6074bf572d10e31100e3362730784a94273d7db0a85aa06bb6946207e56a3ee45adf3431a8d8cffadad9571fec3a4a98f6e2ca879e7"
3123
+ "content": "d4da986f67c1b537431731f28ac048bdca8bba2269896fe674aecc91d5da99d4963624e62309c60ec4c8a2422e0ed3899290a1f951db0eaf0fe46b43a90ebc74"
3109
3124
  }
3110
3125
  ]
3111
3126
  },
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Release-notes extraction + quality gate.
6
+ *
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
33
+ */
34
+
35
+ const fs = require('node:fs');
36
+ const path = require('node:path');
37
+
38
+ const ROOT = path.resolve(__dirname, '..');
39
+ const CHANGELOG = path.join(ROOT, 'CHANGELOG.md');
40
+ const PACKAGE_JSON = path.join(ROOT, 'package.json');
41
+
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.
46
+ function extractSection(text, version) {
47
+ const lines = text.split(/\r?\n/);
48
+ const out = [];
49
+ let capturing = false;
50
+ const startRe = new RegExp('^## ' + version.replace(/\./g, '\\.') + ' ');
51
+ for (const ln of lines) {
52
+ if (capturing) {
53
+ if (/^## /.test(ln)) break;
54
+ out.push(ln);
55
+ continue;
56
+ }
57
+ if (startRe.test(ln)) capturing = true;
58
+ }
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).
61
+ while (out.length && out[0].trim() === '') out.shift();
62
+ while (out.length && out[out.length - 1].trim() === '') out.pop();
63
+ return out;
64
+ }
65
+
66
+ // Returns the `## <version> — <date>` heading line for the version, or null.
67
+ function headingLine(text, version) {
68
+ const re = new RegExp('^## ' + version.replace(/\./g, '\\.') + ' ');
69
+ return text.split(/\r?\n/).find((l) => re.test(l)) || null;
70
+ }
71
+
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 }.
76
+ const FORBIDDEN = [
77
+ { id: 'phase-number', re: /\bphase\s+\d/i, why: 'internal phase number (operators have no roadmap)' },
78
+ { id: 'pass-number', re: /\b(?:audit|curation|drift|fix|bug)?[- ]?pass\s+\d/i, why: 'internal pass/batch number' },
79
+ { id: 'slice-number', re: /\bslice\s+\d/i, why: 'internal slice number' },
80
+ { id: 'sweep-number', re: /\bsweep\s+\d/i, why: 'internal sweep number' },
81
+ { id: 'tier-letter', re: /\bTier-[ABC]\b/, why: 'internal tier label' },
82
+ { id: 'agent-dispatch', re: /\b(?:sub-?agent|parallel agent|agent dispatch|fan(?:ned)?[ -]out|multi-agent)\b/i, why: 'implementation detail (agent/parallelization)' },
83
+ { id: 'conversation-residue', re: /\b(?:as discussed|per your|operator-confirmed|as you (?:noted|requested)|per the conversation|PR feedback:)\b/i, why: 'conversation residue (invisible to the reader)' },
84
+ { id: 'process-narrative', re: /\b(?:audit-derived|post-phase-\d|as part of the \d|the \d+-gap closure)\b/i, why: 'internal-process narrative' },
85
+ { id: 'tautological-green', re: /\b(?:all tests (?:pass|passing|green)|CI green|smoke \+ e2e (?:clean|pass)|tests? (?:are )?passing)\b/i, why: 'tautological pass/green claim (noise — the release exists)' },
86
+ ];
87
+
88
+ function lintOperatorClean(sectionLines) {
89
+ const findings = [];
90
+ sectionLines.forEach((ln, i) => {
91
+ for (const rule of FORBIDDEN) {
92
+ const m = ln.match(rule.re);
93
+ if (m) findings.push({ rule: rule.id, why: rule.why, line: i + 1, match: m[0], text: ln.trim().slice(0, 100) });
94
+ }
95
+ });
96
+ return findings;
97
+ }
98
+
99
+ function readPackageVersion() {
100
+ return JSON.parse(fs.readFileSync(PACKAGE_JSON, 'utf8')).version;
101
+ }
102
+
103
+ function main() {
104
+ const version = process.argv[2] || readPackageVersion();
105
+ if (!/^\d+\.\d+\.\d+$/.test(version)) {
106
+ console.error('[check-changelog-extract] FAIL: bad version ' + JSON.stringify(version) + ' (expected MAJOR.MINOR.PATCH)');
107
+ process.exitCode = 1;
108
+ return;
109
+ }
110
+
111
+ let text;
112
+ try { text = fs.readFileSync(CHANGELOG, 'utf8'); }
113
+ catch (e) {
114
+ console.error('[check-changelog-extract] FAIL: cannot read CHANGELOG.md: ' + (e && e.message || e));
115
+ process.exitCode = 1;
116
+ return;
117
+ }
118
+
119
+ const heading = headingLine(text, version);
120
+ if (!heading) {
121
+ console.error('[check-changelog-extract] FAIL: no `## ' + version + ' …` heading in CHANGELOG.md — the release workflow extract would be empty and fall back to "Release of v' + version + '."');
122
+ process.exitCode = 1;
123
+ return;
124
+ }
125
+ // Heading must carry an ISO date: `## <version> — YYYY-MM-DD`.
126
+ if (!new RegExp('^## ' + version.replace(/\./g, '\\.') + ' [—-] \\d{4}-\\d{2}-\\d{2}\\s*$').test(heading)) {
127
+ console.error('[check-changelog-extract] FAIL: heading does not match `## ' + version + ' — YYYY-MM-DD`:');
128
+ console.error('[check-changelog-extract] got: ' + JSON.stringify(heading));
129
+ process.exitCode = 1;
130
+ return;
131
+ }
132
+
133
+ const section = extractSection(text, version);
134
+ if (section.length === 0) {
135
+ console.error('[check-changelog-extract] FAIL: v' + version + ' section is empty — the release body would fall back to the generic "Release of v' + version + '." line.');
136
+ process.exitCode = 1;
137
+ return;
138
+ }
139
+
140
+ const findings = lintOperatorClean(section);
141
+ if (findings.length > 0) {
142
+ console.error('[check-changelog-extract] FAIL: v' + version + ' release notes carry ' + findings.length + ' operator-facing violation(s):');
143
+ for (const f of findings) {
144
+ console.error(' • [' + f.rule + '] "' + f.match + '" — ' + f.why);
145
+ console.error(' ' + f.text);
146
+ }
147
+ console.error('[check-changelog-extract] The CHANGELOG section IS the public GitHub Release body. Describe the change, not how you arrived at it.');
148
+ process.exitCode = 1;
149
+ return;
150
+ }
151
+
152
+ console.log('[check-changelog-extract] OK — v' + version + ' release notes extract cleanly (' + section.length + ' line(s)) and pass the operator-facing lint.');
153
+ process.exitCode = 0;
154
+ }
155
+
156
+ module.exports = { extractSection, headingLine, lintOperatorClean, FORBIDDEN };
157
+
158
+ if (require.main === module) main();
@@ -44,7 +44,7 @@ exports). Missing references become findings.
44
44
  Changes in these locations are accepted without a covering test:
45
45
 
46
46
  - `*.md` outside `data/`, `.gitignore`, `.npmrc`, `.editorconfig`
47
- - `CHANGELOG.md` / `README.md` / `CONTRIBUTING.md` / `SECURITY.md` / `LICENSE` / `NOTICE` / `CODE_OF_CONDUCT.md` / `AGENTS.md` / `CLAUDE.md`
47
+ - `CHANGELOG.md` / `README.md` / `CONTRIBUTING.md` / `SECURITY.md` / `LICENSE` / `NOTICE` / `CODE_OF_CONDUCT.md` / `AGENTS.md`
48
48
  - Whitespace-only diffs (detected via `git diff --ignore-all-space --ignore-blank-lines`)
49
49
  - Any file under `tests/` (no test-of-tests recursion)
50
50
  - `skills/<name>/skill.md` (signature gate already covers content integrity)
@@ -198,7 +198,7 @@ function readMaybe(p) {
198
198
  // / .editorconfig are tooling). Edits here never need a regression test.
199
199
  const DOCS_ALWAYS_GREEN = new Set([
200
200
  "CONTRIBUTING.md", "LICENSE", "NOTICE", "CODE_OF_CONDUCT.md",
201
- "CLAUDE.md", "SUPPORT.md", ".gitignore", ".npmrc", ".editorconfig",
201
+ "SUPPORT.md", ".gitignore", ".npmrc", ".editorconfig",
202
202
  ]);
203
203
 
204
204
  // Operator-facing docs (release notes, install instructions, security
@@ -497,7 +497,7 @@ function coversCveIoc(corpus, cveId) {
497
497
 
498
498
  // --- Class-level lint: ban coincidence-passing notEqual(r.status, 0) --------
499
499
  //
500
- // CLAUDE.md anti-coincidence rule: every exit-code assertion must pin the
500
+ // Anti-coincidence rule: every exit-code assertion must pin the
501
501
  // EXACT code. `assert.notEqual(r.status, 0)` silently passes when an
502
502
  // unrelated failure produces ANY non-zero exit, hiding the regression the
503
503
  // test was meant to catch. This lint walks tests/*.test.js and rejects the
@@ -635,7 +635,7 @@ function analyze(opts) {
635
635
  file: f.file,
636
636
  kind: "coincidence-assert",
637
637
  surface: f.snippet,
638
- change: `line ${f.line}: pin to exact exit code; see CLAUDE.md anti-coincidence rule. Opt out only with \`// allow-notEqual: <reason>\` on the same line for genuine refusal-pins.`,
638
+ change: `line ${f.line}: pin to exact exit code (anti-coincidence rule). Opt out only with \`// allow-notEqual: <reason>\` on the same line for genuine refusal-pins.`,
639
639
  });
640
640
  }
641
641
 
@@ -34,6 +34,7 @@
34
34
 
35
35
  const fs = require("node:fs");
36
36
  const path = require("node:path");
37
+ const { execFileSync } = require("node:child_process");
37
38
 
38
39
  const ROOT = path.join(__dirname, "..");
39
40
  const BASELINE_PATH = path.join(ROOT, "tests", ".version-tag-baseline.json");
@@ -64,10 +65,33 @@ const COMMENT_EXEMPT = new Set([
64
65
  "CHANGELOG.md",
65
66
  "lib/version-pins.js",
66
67
  "scripts/check-version-tags.js",
67
- // Gitignored local-only contributor docs never shipped.
68
- "CLAUDE.md",
68
+ // The release-notes-extract gate test asserts version-based CHANGELOG
69
+ // extraction + the shorter-vs-longer prefix-collision guard, so its fixtures
70
+ // MUST embed real `## X.Y.Z` headings (e.g. 0.15.5 vs 0.15.50) — load-bearing
71
+ // test data, not sprinkled release tags.
72
+ "tests/check-changelog-extract.test.js",
69
73
  ]);
70
74
 
75
+ // Git-ignored files (a contributor's local-only working docs, scratch) are
76
+ // never scanned — the gate enforces on the would-be-shipped surface, with no
77
+ // need to name individual local-only files. Untracked-but-NOT-ignored files
78
+ // ARE still scanned: a new file a contributor is about to commit is exactly
79
+ // what the gate must catch. Computed via `git check-ignore` over the walked set.
80
+ function gitIgnoredSet(relPaths) {
81
+ if (!relPaths.length) return new Set();
82
+ try {
83
+ const out = execFileSync("git", ["check-ignore", "--stdin"], {
84
+ cwd: ROOT, input: relPaths.join("\n"), encoding: "utf8", maxBuffer: 64 * 1024 * 1024,
85
+ });
86
+ return new Set(out.split(/\r?\n/).filter(Boolean));
87
+ } catch (e) {
88
+ // `git check-ignore --stdin` exits 1 when NO path is ignored (not an
89
+ // error); any paths it did match are on stdout. Absent that, none ignored.
90
+ const out = e && e.stdout ? String(e.stdout) : "";
91
+ return new Set(out.split(/\r?\n/).filter(Boolean));
92
+ }
93
+ }
94
+
71
95
  // Pattern: project version like `v0.13.22` or bare `0.13.22`. Matches
72
96
  // our pre-1.0 release range. External package versions like ATLAS
73
97
  // `v5.6.0` or CycloneDX `1.6` don't match because the major is 0.
@@ -119,9 +143,14 @@ function countCommentViolations(rel) {
119
143
 
120
144
  function scanCurrent() {
121
145
  const files = walk(ROOT);
146
+ const ignored = gitIgnoredSet(files);
122
147
  const byFile = {};
123
148
  const filenameViolations = [];
124
149
  for (const rel of files) {
150
+ // Skip git-ignored, local-only files (a contributor's private working notes
151
+ // that `git clone` never ships). Untracked-but-not-ignored files are still
152
+ // scanned — a new file about to be committed is what the gate guards.
153
+ if (ignored.has(rel)) continue;
125
154
  if (FILENAME_VERSION_RE.test(rel)) filenameViolations.push(rel);
126
155
  const n = countCommentViolations(rel);
127
156
  if (n > 0) byFile[rel] = n;
@@ -249,6 +249,19 @@ const GATES = [
249
249
  args: [path.join(ROOT, "scripts", "check-codebase-patterns.js")],
250
250
  ciJobName: "Data integrity (catalog + manifest snapshot)",
251
251
  },
252
+ {
253
+ // Release-notes extract + quality gate. Runs the same `## <version>`
254
+ // CHANGELOG extraction the release workflow publishes as the GitHub
255
+ // Release body, and lints it for operator-facing quality (no internal
256
+ // phase/pass/slice narrative, no agent-dispatch / conversation residue,
257
+ // no tautological green claims). A malformed or internal-narrative section
258
+ // fails here rather than shipping as the public release body / falling
259
+ // back to the generic "Release of v<version>." line.
260
+ name: "Release-notes extract + operator-facing lint (CHANGELOG section)",
261
+ command: process.execPath,
262
+ args: [path.join(ROOT, "scripts", "check-changelog-extract.js")],
263
+ ciJobName: "Data integrity (catalog + manifest snapshot)",
264
+ },
252
265
  ];
253
266
 
254
267
  function runGate(gate) {
@@ -254,6 +254,11 @@ function cmdPrepare(opts) {
254
254
  process.exit(2);
255
255
  }
256
256
 
257
+ // The `## <next>` heading exists; confirm the section extracts cleanly and
258
+ // passes the operator-facing lint (the release workflow publishes it verbatim
259
+ // as the GitHub Release body). Fail fast here rather than at the gates phase.
260
+ _run("node", ["scripts/check-changelog-extract.js", next]);
261
+
257
262
  _writeJsonVersion("package.json", next);
258
263
  _writeJsonVersion("manifest.json", next);
259
264
  _ok("bumped package.json + manifest.json → " + next);
@@ -344,7 +344,7 @@ try {
344
344
  // itself report 0/38 on any tree where line-ending normalization
345
345
  // touched the source between sign and pack — a Windows contributor
346
346
  // with `core.autocrlf=true`, or a tool like Prettier between sign and
347
- // pack. CLAUDE.md flags this as the recurring CRLF-bypass class.
347
+ // pack. This is the recurring CRLF/line-ending-bypass class.
348
348
  const rawContent = fs.readFileSync(skillPath);
349
349
  const normalizedContent = normalizeSkillBytes(rawContent);
350
350
  const ok = crypto.verify(null, normalizedContent, pubKey, Buffer.from(s.signature, "base64"));
@@ -85,6 +85,8 @@ The defining incidents driving this expansion:
85
85
  - **Typosquat campaigns target the MCP, Hugging Face, npm `@modelcontextprotocol/*`, and PyPI ML namespaces.** The MITRE ATLAS technique AML.T0010 (ML Supply Chain Compromise) is the umbrella class; AML.T0018 (compromised model weight) is the specific artifact.
86
86
  - **The XZ Utils backdoor (2024, CVE-2024-3094)** remains the canonical example of a maintainer-position long-game supply-chain compromise that no SBOM-only program detects. The defense is in-toto attestation chain plus reproducible builds plus maintainer-key transparency — none of which are mandated by current compliance frameworks.
87
87
 
88
+ **Capability surface is a CVE-independent screening lens.** Beyond matching dependencies against catalogued CVEs, classify every package by the capabilities it actually exercises — network egress, filesystem write, shell/process spawn, environment and credential access, dynamic `eval`, install-lifecycle scripts, telemetry, and native-binary builds. A package with zero catalogued CVEs can still carry the supply-chain delivery primitive: an install-script that spawns a shell, opens the network, and reads environment variables is the credential-harvesting shape shared by the node-ipc protestware wiper (CVE-2022-23812), the TrapDoor cross-ecosystem stealer, the MOIKA dependency-confusion campaign, and the XZ backdoor — independent of whether any of them is yet in a vulnerability feed. Two deltas matter most: a capability surface disproportionate to a package's stated function (a date-formatting utility that opens the network and reads env), and a capability *gained across a version bump* the dependent never opted into when it first selected the package — the in-the-wild signature of a maintainer-account takeover or protestware injection before any CVE is assigned. Build-tooling and native-addon packages (node-gyp, esbuild, sharp, prebuild-install) legitimately carry install-script and native-binary capability, so treat the lens as a high-recall screen that routes to adjudication against publisher provenance and changelog evidence — not an auto-verdict.
89
+
88
90
  Mid-2026 baseline expectations have shifted to: SLSA Build L3 minimum on every release pipeline; in-toto attestations for every step in the build graph; Sigstore keyless signing (cosign + Fulcio + Rekor) for container images, packages, and model weights; CycloneDX 1.6 or SPDX 3.0 SBOM at build time and re-verification at deploy time; CSAF 2.0 VEX statements published by vendors and consumed by operators to filter false-positive vulnerability noise. Adoption is uneven — security-mature orgs and open-source foundations (Kubernetes, Sigstore, Linux Foundation projects) are at or near this baseline; most enterprise pipelines are between SLSA L1 and L2 with no attestation chain and no VEX consumption.
89
91
 
90
92
  This project itself uses Ed25519 (RFC 8032) signing for skill integrity (`lib/sign.js`, `lib/verify.js`, public key at `keys/public.pem`). That is the smallest-scale living example of the broader pattern this skill defines: artifacts are signed by an identified key, signatures are verified before load, tampered artifacts are rejected, and the signing key is operationally separated from the artifact registry. The same pattern scales to every layer of the build pipeline — exceptd is just the scoped-to-one-repo version.