@blamejs/exceptd-skills 0.15.51 → 0.15.53
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 +10 -0
- package/NOTICE +1 -1
- package/bin/exceptd.js +1 -1
- package/data/_indexes/_meta.json +3 -3
- package/data/cve-catalog.json +91 -8
- package/data/playbooks/sbom.json +91 -1
- package/lib/citation-resolve.js +23 -1
- package/lib/prefetch.js +2 -2
- package/lib/schemas/cve-catalog.schema.json +23 -0
- package/lib/scoring.js +33 -0
- package/manifest.json +44 -44
- package/package.json +1 -1
- package/sbom.cdx.json +55 -40
- package/scripts/check-changelog-extract.js +158 -0
- package/scripts/check-test-coverage.README.md +1 -1
- package/scripts/check-test-coverage.js +3 -3
- package/scripts/check-version-tags.js +31 -2
- package/scripts/predeploy.js +13 -0
- package/scripts/release.js +5 -0
- package/scripts/verify-shipped-tarball.js +1 -1
package/sbom.cdx.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.6",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:347093c0-9155-471a-9a22-58cb926555f2",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "
|
|
7
|
+
"timestamp": "2053-11-17T18:58:08.000Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "blamejs",
|
|
11
11
|
"name": "scripts/refresh-sbom.js",
|
|
12
|
-
"version": "0.15.
|
|
12
|
+
"version": "0.15.53"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"component": {
|
|
16
|
-
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.15.
|
|
16
|
+
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.15.53",
|
|
17
17
|
"type": "application",
|
|
18
18
|
"name": "@blamejs/exceptd-skills",
|
|
19
|
-
"version": "0.15.
|
|
19
|
+
"version": "0.15.53",
|
|
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.
|
|
28
|
+
"purl": "pkg:npm/%40blamejs/exceptd-skills@0.15.53",
|
|
29
29
|
"hashes": [
|
|
30
30
|
{
|
|
31
31
|
"alg": "SHA-256",
|
|
32
|
-
"content": "
|
|
32
|
+
"content": "5fde4e0c2d0ef764c883ff21dbe1f4c5dbc9e74c675e59c15a6941a7e6d68c70"
|
|
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.
|
|
38
|
+
"url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.15.53"
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"type": "vcs",
|
|
@@ -116,11 +116,11 @@
|
|
|
116
116
|
"hashes": [
|
|
117
117
|
{
|
|
118
118
|
"alg": "SHA-256",
|
|
119
|
-
"content": "
|
|
119
|
+
"content": "3f31b297747c16270e24932a2d762bc06655b2d4d7bc2a5c9943e7eccf2a6839"
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
"alg": "SHA3-512",
|
|
123
|
-
"content": "
|
|
123
|
+
"content": "6489d29205b4185795b070e341e3290de0368f0dafb096907c1a912ccb5576dfb096fa020cff503d24bf830a9f1c3170ca8897daebcef3542ee6af6b6fae86b4"
|
|
124
124
|
}
|
|
125
125
|
]
|
|
126
126
|
},
|
|
@@ -161,11 +161,11 @@
|
|
|
161
161
|
"hashes": [
|
|
162
162
|
{
|
|
163
163
|
"alg": "SHA-256",
|
|
164
|
-
"content": "
|
|
164
|
+
"content": "34dd0f6eab14ad5b2c974b81e7016c841c7849f862b58fcc94fd499a5ed2b8a9"
|
|
165
165
|
},
|
|
166
166
|
{
|
|
167
167
|
"alg": "SHA3-512",
|
|
168
|
-
"content": "
|
|
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": "
|
|
284
|
+
"content": "d0ca92931278d72229edb9f2526157f2832df1d97cd7e8440a0a841f8b1c664d"
|
|
285
285
|
},
|
|
286
286
|
{
|
|
287
287
|
"alg": "SHA3-512",
|
|
288
|
-
"content": "
|
|
288
|
+
"content": "93377a5256af65b898ffda3586e081e64f6bb3d820305b6995dd37f022857a66e929bec0c167fed5aed5c5b18a8dec7e7e4214b3d5cfacea32e93dc8065f01c0"
|
|
289
289
|
}
|
|
290
290
|
]
|
|
291
291
|
},
|
|
@@ -326,11 +326,11 @@
|
|
|
326
326
|
"hashes": [
|
|
327
327
|
{
|
|
328
328
|
"alg": "SHA-256",
|
|
329
|
-
"content": "
|
|
329
|
+
"content": "1aac7e75eae24ece2ef09d1c63977bdd7a1f81a9f11609ebb966109be316426c"
|
|
330
330
|
},
|
|
331
331
|
{
|
|
332
332
|
"alg": "SHA3-512",
|
|
333
|
-
"content": "
|
|
333
|
+
"content": "24df35aa1e38b1d24d29957e06da86e71cce646d6b21a1e5b326e752803cca5441a0d618a763f5648a10cbda50728d8fc28e94dcee3e54eab4a081ee3ebdb156"
|
|
334
334
|
}
|
|
335
335
|
]
|
|
336
336
|
},
|
|
@@ -731,11 +731,11 @@
|
|
|
731
731
|
"hashes": [
|
|
732
732
|
{
|
|
733
733
|
"alg": "SHA-256",
|
|
734
|
-
"content": "
|
|
734
|
+
"content": "1e2f307a7207a260f8c9ab23f95e8eebb4950349d79eceb2af5b8c4df2b37dc4"
|
|
735
735
|
},
|
|
736
736
|
{
|
|
737
737
|
"alg": "SHA3-512",
|
|
738
|
-
"content": "
|
|
738
|
+
"content": "9298e037ccac473c5ac994e5ef4804d93a7abfbb3fa14c836a97c4503de2e9d10b1ad8a2f371115bf17c1f63851fd252214cb183c8655f1f4150b0e3a2dc9248"
|
|
739
739
|
}
|
|
740
740
|
]
|
|
741
741
|
},
|
|
@@ -881,11 +881,11 @@
|
|
|
881
881
|
"hashes": [
|
|
882
882
|
{
|
|
883
883
|
"alg": "SHA-256",
|
|
884
|
-
"content": "
|
|
884
|
+
"content": "27e0665cd005f7c5b3bee0de927c8b43d7193933b44d8ca0fe832a754f0fd6f7"
|
|
885
885
|
},
|
|
886
886
|
{
|
|
887
887
|
"alg": "SHA3-512",
|
|
888
|
-
"content": "
|
|
888
|
+
"content": "c2a42a955e05a9bde003eaddba6c49fea138c215ca0ad9b0876949743fcbfe3f4753fe063a19339729a289a751bc8572f2e59d007f770ca8d1ab5e66841971e3"
|
|
889
889
|
}
|
|
890
890
|
]
|
|
891
891
|
},
|
|
@@ -1331,11 +1331,11 @@
|
|
|
1331
1331
|
"hashes": [
|
|
1332
1332
|
{
|
|
1333
1333
|
"alg": "SHA-256",
|
|
1334
|
-
"content": "
|
|
1334
|
+
"content": "bd757082645bbe88e0d03e5d533ee7c5c8e2ea922e31e13e28a336f69cd3a756"
|
|
1335
1335
|
},
|
|
1336
1336
|
{
|
|
1337
1337
|
"alg": "SHA3-512",
|
|
1338
|
-
"content": "
|
|
1338
|
+
"content": "85c3844a6562a1df8b6b71e54c6fc4788ad62ad918856ceb441c443625032f547c30e39618631f397c49c02708af4e1420bf2ee78885c23248f9c7f8885cef2f"
|
|
1339
1339
|
}
|
|
1340
1340
|
]
|
|
1341
1341
|
},
|
|
@@ -1391,11 +1391,11 @@
|
|
|
1391
1391
|
"hashes": [
|
|
1392
1392
|
{
|
|
1393
1393
|
"alg": "SHA-256",
|
|
1394
|
-
"content": "
|
|
1394
|
+
"content": "b7ace475bc015e13890ed08de28a94195f7672ea7c0dbc08ca2dc62bda78d291"
|
|
1395
1395
|
},
|
|
1396
1396
|
{
|
|
1397
1397
|
"alg": "SHA3-512",
|
|
1398
|
-
"content": "
|
|
1398
|
+
"content": "216e86bd9b695771287f29797d07cbef1f7cff4bdffa30749c351ec654e62888c7857a29c48b2fe09809c610f3522219712a322437d92c00bf8b6e8954431c74"
|
|
1399
1399
|
}
|
|
1400
1400
|
]
|
|
1401
1401
|
},
|
|
@@ -1451,11 +1451,11 @@
|
|
|
1451
1451
|
"hashes": [
|
|
1452
1452
|
{
|
|
1453
1453
|
"alg": "SHA-256",
|
|
1454
|
-
"content": "
|
|
1454
|
+
"content": "ed49d69da4ef5acb4fd827deaaaf280bd6317a5defa96f6fa5696dc96c2929b4"
|
|
1455
1455
|
},
|
|
1456
1456
|
{
|
|
1457
1457
|
"alg": "SHA3-512",
|
|
1458
|
-
"content": "
|
|
1458
|
+
"content": "cee40245e9bb979c399942241faf038e95adb38ad66b6f3e70ba6034b78f774241310369773bd9ab36ceb0af1fe92e8f8b7e9b9965b3f629ab2847152f892530"
|
|
1459
1459
|
}
|
|
1460
1460
|
]
|
|
1461
1461
|
},
|
|
@@ -1751,11 +1751,11 @@
|
|
|
1751
1751
|
"hashes": [
|
|
1752
1752
|
{
|
|
1753
1753
|
"alg": "SHA-256",
|
|
1754
|
-
"content": "
|
|
1754
|
+
"content": "4359446f2ab6bfb0484b716ca15b81755ca4075d941b0c8caa5d2d4ad1c4f0a7"
|
|
1755
1755
|
},
|
|
1756
1756
|
{
|
|
1757
1757
|
"alg": "SHA3-512",
|
|
1758
|
-
"content": "
|
|
1758
|
+
"content": "26d55c25bbbc0e76e1592fd5633b87858c6cf3ba281e0d6b19ce17761af9468992564c0f040110e541a63492f384e3c982c3afd5f4a05596a08f03e9c93ca2fa"
|
|
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": "
|
|
2279
|
+
"content": "d43e59b96a252c1228ca709e3753582fc02d4c32d2766b247a69ee78c0a18e28"
|
|
2265
2280
|
},
|
|
2266
2281
|
{
|
|
2267
2282
|
"alg": "SHA3-512",
|
|
2268
|
-
"content": "
|
|
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": "
|
|
2294
|
+
"content": "906735e02f452962cc221e129a9d7d08cb645f0b79b48168fea62038c35f6b71"
|
|
2280
2295
|
},
|
|
2281
2296
|
{
|
|
2282
2297
|
"alg": "SHA3-512",
|
|
2283
|
-
"content": "
|
|
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": "
|
|
2309
|
+
"content": "2306d6560552040baf57e9a552c14f2344967338babdc1a4fff8e3b4fc0d4d9b"
|
|
2295
2310
|
},
|
|
2296
2311
|
{
|
|
2297
2312
|
"alg": "SHA3-512",
|
|
2298
|
-
"content": "
|
|
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": "
|
|
2339
|
+
"content": "3e37c0d93dfc1ff3c6fd5e90bb65417f3ebcabd8435925c4292b4ca86bdd11c2"
|
|
2325
2340
|
},
|
|
2326
2341
|
{
|
|
2327
2342
|
"alg": "SHA3-512",
|
|
2328
|
-
"content": "
|
|
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": "
|
|
2489
|
+
"content": "f199cc754f202fa994c1769af5e02a4ef995ee02e87221422b00f945b03e99db"
|
|
2475
2490
|
},
|
|
2476
2491
|
{
|
|
2477
2492
|
"alg": "SHA3-512",
|
|
2478
|
-
"content": "
|
|
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": "
|
|
2549
|
+
"content": "fd04f3e4122b4f3ca7e9a266c763dd4e954710d496da8072f5c2e500a4dbe32c"
|
|
2535
2550
|
},
|
|
2536
2551
|
{
|
|
2537
2552
|
"alg": "SHA3-512",
|
|
2538
|
-
"content": "
|
|
2553
|
+
"content": "9b606b4d5afcc79a7a39650c3f425af0a8abb21f484e70e5469431a0be26b27969108bd51f167cab23b41d6e0ec7d14b8e6d82d597ca6166942605e15fc016c0"
|
|
2539
2554
|
}
|
|
2540
2555
|
]
|
|
2541
2556
|
},
|
|
@@ -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`
|
|
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
|
-
"
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
68
|
-
|
|
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;
|
package/scripts/predeploy.js
CHANGED
|
@@ -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) {
|
package/scripts/release.js
CHANGED
|
@@ -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.
|
|
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"));
|