@blamejs/exceptd-skills 0.9.1
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/AGENTS.md +232 -0
- package/ARCHITECTURE.md +267 -0
- package/CHANGELOG.md +616 -0
- package/CONTEXT.md +203 -0
- package/LICENSE +200 -0
- package/NOTICE +82 -0
- package/README.md +307 -0
- package/SECURITY.md +73 -0
- package/agents/README.md +81 -0
- package/agents/report-generator.md +156 -0
- package/agents/skill-updater.md +102 -0
- package/agents/source-validator.md +119 -0
- package/agents/threat-researcher.md +149 -0
- package/bin/exceptd.js +183 -0
- package/data/_indexes/_meta.json +88 -0
- package/data/_indexes/activity-feed.json +362 -0
- package/data/_indexes/catalog-summaries.json +229 -0
- package/data/_indexes/chains.json +7135 -0
- package/data/_indexes/currency.json +359 -0
- package/data/_indexes/did-ladders.json +451 -0
- package/data/_indexes/frequency.json +2072 -0
- package/data/_indexes/handoff-dag.json +476 -0
- package/data/_indexes/jurisdiction-clocks.json +967 -0
- package/data/_indexes/jurisdiction-map.json +536 -0
- package/data/_indexes/recipes.json +319 -0
- package/data/_indexes/section-offsets.json +3656 -0
- package/data/_indexes/stale-content.json +14 -0
- package/data/_indexes/summary-cards.json +1736 -0
- package/data/_indexes/theater-fingerprints.json +381 -0
- package/data/_indexes/token-budget.json +2137 -0
- package/data/_indexes/trigger-table.json +1374 -0
- package/data/_indexes/xref.json +818 -0
- package/data/atlas-ttps.json +282 -0
- package/data/cve-catalog.json +496 -0
- package/data/cwe-catalog.json +1017 -0
- package/data/d3fend-catalog.json +738 -0
- package/data/dlp-controls.json +1039 -0
- package/data/exploit-availability.json +67 -0
- package/data/framework-control-gaps.json +1255 -0
- package/data/global-frameworks.json +2913 -0
- package/data/rfc-references.json +324 -0
- package/data/zeroday-lessons.json +377 -0
- package/keys/public.pem +3 -0
- package/lib/framework-gap.js +328 -0
- package/lib/job-queue.js +195 -0
- package/lib/lint-skills.js +536 -0
- package/lib/prefetch.js +372 -0
- package/lib/refresh-external.js +713 -0
- package/lib/schemas/cve-catalog.schema.json +151 -0
- package/lib/schemas/manifest.schema.json +106 -0
- package/lib/schemas/skill-frontmatter.schema.json +113 -0
- package/lib/scoring.js +149 -0
- package/lib/sign.js +197 -0
- package/lib/ttp-mapper.js +80 -0
- package/lib/validate-catalog-meta.js +198 -0
- package/lib/validate-cve-catalog.js +213 -0
- package/lib/validate-indexes.js +83 -0
- package/lib/validate-package.js +162 -0
- package/lib/validate-vendor.js +85 -0
- package/lib/verify.js +216 -0
- package/lib/worker-pool.js +84 -0
- package/manifest-snapshot.json +1833 -0
- package/manifest.json +2108 -0
- package/orchestrator/README.md +124 -0
- package/orchestrator/dispatcher.js +140 -0
- package/orchestrator/event-bus.js +146 -0
- package/orchestrator/index.js +874 -0
- package/orchestrator/pipeline.js +201 -0
- package/orchestrator/scanner.js +327 -0
- package/orchestrator/scheduler.js +137 -0
- package/package.json +113 -0
- package/sbom.cdx.json +158 -0
- package/scripts/audit-cross-skill.js +261 -0
- package/scripts/audit-perf.js +160 -0
- package/scripts/bootstrap.js +205 -0
- package/scripts/build-indexes.js +721 -0
- package/scripts/builders/activity-feed.js +79 -0
- package/scripts/builders/catalog-summaries.js +67 -0
- package/scripts/builders/currency.js +109 -0
- package/scripts/builders/cwe-chains.js +105 -0
- package/scripts/builders/did-ladders.js +149 -0
- package/scripts/builders/frequency.js +89 -0
- package/scripts/builders/jurisdiction-clocks.js +126 -0
- package/scripts/builders/recipes.js +159 -0
- package/scripts/builders/section-offsets.js +162 -0
- package/scripts/builders/stale-content.js +171 -0
- package/scripts/builders/summary-cards.js +166 -0
- package/scripts/builders/theater-fingerprints.js +198 -0
- package/scripts/builders/token-budget.js +96 -0
- package/scripts/check-manifest-snapshot.js +217 -0
- package/scripts/predeploy.js +267 -0
- package/scripts/refresh-manifest-snapshot.js +57 -0
- package/scripts/refresh-sbom.js +222 -0
- package/skills/age-gates-child-safety/skill.md +456 -0
- package/skills/ai-attack-surface/skill.md +282 -0
- package/skills/ai-c2-detection/skill.md +440 -0
- package/skills/ai-risk-management/skill.md +311 -0
- package/skills/api-security/skill.md +287 -0
- package/skills/attack-surface-pentest/skill.md +381 -0
- package/skills/cloud-security/skill.md +384 -0
- package/skills/compliance-theater/skill.md +365 -0
- package/skills/container-runtime-security/skill.md +379 -0
- package/skills/coordinated-vuln-disclosure/skill.md +473 -0
- package/skills/defensive-countermeasure-mapping/skill.md +300 -0
- package/skills/dlp-gap-analysis/skill.md +337 -0
- package/skills/email-security-anti-phishing/skill.md +206 -0
- package/skills/exploit-scoring/skill.md +331 -0
- package/skills/framework-gap-analysis/skill.md +374 -0
- package/skills/fuzz-testing-strategy/skill.md +313 -0
- package/skills/global-grc/skill.md +564 -0
- package/skills/identity-assurance/skill.md +272 -0
- package/skills/incident-response-playbook/skill.md +546 -0
- package/skills/kernel-lpe-triage/skill.md +303 -0
- package/skills/mcp-agent-trust/skill.md +326 -0
- package/skills/mlops-security/skill.md +325 -0
- package/skills/ot-ics-security/skill.md +340 -0
- package/skills/policy-exception-gen/skill.md +437 -0
- package/skills/pqc-first/skill.md +546 -0
- package/skills/rag-pipeline-security/skill.md +294 -0
- package/skills/researcher/skill.md +310 -0
- package/skills/sector-energy/skill.md +409 -0
- package/skills/sector-federal-government/skill.md +302 -0
- package/skills/sector-financial/skill.md +398 -0
- package/skills/sector-healthcare/skill.md +373 -0
- package/skills/security-maturity-tiers/skill.md +464 -0
- package/skills/skill-update-loop/skill.md +463 -0
- package/skills/supply-chain-integrity/skill.md +318 -0
- package/skills/threat-model-currency/skill.md +404 -0
- package/skills/threat-modeling-methodology/skill.md +312 -0
- package/skills/webapp-security/skill.md +281 -0
- package/skills/zeroday-gap-learn/skill.md +350 -0
- package/vendor/blamejs/LICENSE +201 -0
- package/vendor/blamejs/README.md +54 -0
- package/vendor/blamejs/_PROVENANCE.json +54 -0
- package/vendor/blamejs/retry.js +335 -0
- package/vendor/blamejs/worker-pool.js +418 -0
package/package.json
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@blamejs/exceptd-skills",
|
|
3
|
+
"version": "0.9.1",
|
|
4
|
+
"description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 38 skills, 10 catalogs, 34 jurisdictions, pre-computed indexes, Ed25519-signed.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai-security",
|
|
7
|
+
"ai-skills",
|
|
8
|
+
"atlas",
|
|
9
|
+
"att-ck",
|
|
10
|
+
"compliance",
|
|
11
|
+
"cve",
|
|
12
|
+
"cwe",
|
|
13
|
+
"d3fend",
|
|
14
|
+
"framework-gap",
|
|
15
|
+
"grc",
|
|
16
|
+
"kev",
|
|
17
|
+
"llm-security",
|
|
18
|
+
"mcp",
|
|
19
|
+
"nist",
|
|
20
|
+
"nvd",
|
|
21
|
+
"prompt-injection",
|
|
22
|
+
"rwep",
|
|
23
|
+
"security-research",
|
|
24
|
+
"skills",
|
|
25
|
+
"supply-chain",
|
|
26
|
+
"threat-intelligence",
|
|
27
|
+
"threat-modeling"
|
|
28
|
+
],
|
|
29
|
+
"homepage": "https://exceptd.com",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/blamejs/exceptd-skills.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/blamejs/exceptd-skills/issues"
|
|
36
|
+
},
|
|
37
|
+
"license": "Apache-2.0",
|
|
38
|
+
"author": "blamejs contributors",
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=24.0.0 <25.0.0"
|
|
41
|
+
},
|
|
42
|
+
"bin": {
|
|
43
|
+
"exceptd": "bin/exceptd.js"
|
|
44
|
+
},
|
|
45
|
+
"main": "orchestrator/index.js",
|
|
46
|
+
"files": [
|
|
47
|
+
"bin/",
|
|
48
|
+
"lib/",
|
|
49
|
+
"orchestrator/",
|
|
50
|
+
"scripts/",
|
|
51
|
+
"vendor/",
|
|
52
|
+
"agents/",
|
|
53
|
+
"data/",
|
|
54
|
+
"skills/",
|
|
55
|
+
"keys/public.pem",
|
|
56
|
+
"manifest.json",
|
|
57
|
+
"manifest-snapshot.json",
|
|
58
|
+
"sbom.cdx.json",
|
|
59
|
+
"AGENTS.md",
|
|
60
|
+
"ARCHITECTURE.md",
|
|
61
|
+
"CONTEXT.md",
|
|
62
|
+
"CHANGELOG.md",
|
|
63
|
+
"LICENSE",
|
|
64
|
+
"NOTICE",
|
|
65
|
+
"README.md",
|
|
66
|
+
"SECURITY.md"
|
|
67
|
+
],
|
|
68
|
+
"publishConfig": {
|
|
69
|
+
"access": "public",
|
|
70
|
+
"provenance": true,
|
|
71
|
+
"registry": "https://registry.npmjs.org/"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"bootstrap": "node scripts/bootstrap.js",
|
|
75
|
+
"verify": "node lib/verify.js",
|
|
76
|
+
"verify:update": "node lib/verify.js update",
|
|
77
|
+
"test": "node --test --test-concurrency=1 \"tests/*.test.js\"",
|
|
78
|
+
"lint": "node lib/lint-skills.js",
|
|
79
|
+
"check-snapshot": "node scripts/check-manifest-snapshot.js",
|
|
80
|
+
"refresh-snapshot": "node scripts/refresh-manifest-snapshot.js",
|
|
81
|
+
"validate-catalog": "node lib/validate-cve-catalog.js",
|
|
82
|
+
"validate-catalog-meta": "node lib/validate-catalog-meta.js",
|
|
83
|
+
"validate-package": "node lib/validate-package.js",
|
|
84
|
+
"refresh-sbom": "node scripts/refresh-sbom.js",
|
|
85
|
+
"predeploy": "node scripts/predeploy.js",
|
|
86
|
+
"prepublishOnly": "node scripts/predeploy.js && node lib/validate-package.js",
|
|
87
|
+
"test:docker": "docker build --target predeploy -t exceptd-test:predeploy -f docker/test.Dockerfile . && docker run --rm exceptd-test:predeploy",
|
|
88
|
+
"test:docker:fresh": "docker build --target fresh-bootstrap -t exceptd-test:fresh-bootstrap -f docker/test.Dockerfile . && docker run --rm exceptd-test:fresh-bootstrap",
|
|
89
|
+
"scan": "node orchestrator/index.js scan",
|
|
90
|
+
"dispatch": "node orchestrator/index.js dispatch",
|
|
91
|
+
"currency": "node orchestrator/index.js currency",
|
|
92
|
+
"report": "node orchestrator/index.js report",
|
|
93
|
+
"watch": "node orchestrator/index.js watch",
|
|
94
|
+
"validate-cves": "node orchestrator/index.js validate-cves",
|
|
95
|
+
"validate-rfcs": "node orchestrator/index.js validate-rfcs",
|
|
96
|
+
"watchlist": "node orchestrator/index.js watchlist",
|
|
97
|
+
"build-indexes": "node scripts/build-indexes.js",
|
|
98
|
+
"validate-indexes": "node lib/validate-indexes.js",
|
|
99
|
+
"validate-vendor": "node lib/validate-vendor.js",
|
|
100
|
+
"audit-perf": "node scripts/audit-perf.js",
|
|
101
|
+
"audit-cross-skill": "node scripts/audit-cross-skill.js",
|
|
102
|
+
"refresh": "node lib/refresh-external.js",
|
|
103
|
+
"refresh:dry": "node lib/refresh-external.js",
|
|
104
|
+
"refresh:apply": "node lib/refresh-external.js --apply",
|
|
105
|
+
"refresh:offline": "node lib/refresh-external.js --from-fixture tests/fixtures/refresh",
|
|
106
|
+
"refresh:from-cache": "node lib/refresh-external.js --from-cache .cache/upstream",
|
|
107
|
+
"refresh:swarm": "node lib/refresh-external.js --swarm",
|
|
108
|
+
"prefetch": "node lib/prefetch.js",
|
|
109
|
+
"prefetch:dry": "node lib/prefetch.js --no-network"
|
|
110
|
+
},
|
|
111
|
+
"dependencies": {},
|
|
112
|
+
"devDependencies": {}
|
|
113
|
+
}
|
package/sbom.cdx.json
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
{
|
|
2
|
+
"bomFormat": "CycloneDX",
|
|
3
|
+
"specVersion": "1.6",
|
|
4
|
+
"serialNumber": "urn:uuid:986fb119-b9a5-4395-9a40-62fdd3ab0a7b",
|
|
5
|
+
"version": 1,
|
|
6
|
+
"metadata": {
|
|
7
|
+
"timestamp": "2026-05-11T23:30:00.623Z",
|
|
8
|
+
"tools": [
|
|
9
|
+
{
|
|
10
|
+
"name": "hand-written",
|
|
11
|
+
"version": "0.1.0",
|
|
12
|
+
"description": "SBOM generated from package.json + manual review (scripts/refresh-sbom.js)."
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"component": {
|
|
16
|
+
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.9.1",
|
|
17
|
+
"type": "application",
|
|
18
|
+
"name": "@blamejs/exceptd-skills",
|
|
19
|
+
"version": "0.9.1",
|
|
20
|
+
"description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 38 skills, 10 catalogs, 34 jurisdictions, pre-computed indexes, Ed25519-signed.",
|
|
21
|
+
"licenses": [
|
|
22
|
+
{
|
|
23
|
+
"license": {
|
|
24
|
+
"id": "Apache-2.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"purl": "pkg:npm/%40blamejs/exceptd-skills@0.9.1",
|
|
29
|
+
"externalReferences": [
|
|
30
|
+
{
|
|
31
|
+
"type": "distribution",
|
|
32
|
+
"url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.9.1"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"type": "vcs",
|
|
36
|
+
"url": "git+https://github.com/blamejs/exceptd-skills.git"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"properties": [
|
|
41
|
+
{
|
|
42
|
+
"name": "cyclonedx:dataflow:input",
|
|
43
|
+
"value": "data/atlas-ttps.json,data/cve-catalog.json,data/cwe-catalog.json,data/d3fend-catalog.json,data/dlp-controls.json,data/exploit-availability.json,data/framework-control-gaps.json,data/global-frameworks.json,data/rfc-references.json,data/zeroday-lessons.json"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "exceptd:catalog:count",
|
|
47
|
+
"value": "10"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "exceptd:skill:count",
|
|
51
|
+
"value": "38"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "exceptd:integrity:method",
|
|
55
|
+
"value": "Ed25519 per-skill (lib/sign.js)"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "exceptd:runtime:dependency:count",
|
|
59
|
+
"value": "0"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "exceptd:devDependency:count",
|
|
63
|
+
"value": "0"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "exceptd:vendor:count",
|
|
67
|
+
"value": "2"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "exceptd:vendor:pin",
|
|
71
|
+
"value": "https://github.com/blamejs/blamejs@1442f17758a4bd511c63877561c0ffa759f66a87"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"components": [
|
|
76
|
+
{
|
|
77
|
+
"bom-ref": "vendor:blamejs:retry.js",
|
|
78
|
+
"type": "library",
|
|
79
|
+
"name": "blamejs/retry.js",
|
|
80
|
+
"version": "1442f17758a4",
|
|
81
|
+
"description": "Vendored from blamejs/lib/retry.js (flattened + stripped). See vendor/blamejs/README.md.",
|
|
82
|
+
"licenses": [
|
|
83
|
+
{
|
|
84
|
+
"license": {
|
|
85
|
+
"id": "Apache-2.0"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
"hashes": [
|
|
90
|
+
{
|
|
91
|
+
"alg": "SHA-256",
|
|
92
|
+
"content": "2b9a7d99a8477740dade4e4490e436817c250a3d63e9d91898a9cdaaeff82ac4"
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"externalReferences": [
|
|
96
|
+
{
|
|
97
|
+
"type": "vcs",
|
|
98
|
+
"url": "https://github.com/blamejs/blamejs"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"type": "distribution",
|
|
102
|
+
"url": "https://github.com/blamejs/blamejs/blob/1442f17758a4bd511c63877561c0ffa759f66a87/lib/retry.js"
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
"properties": [
|
|
106
|
+
{
|
|
107
|
+
"name": "exceptd:vendor:upstream_sha256_at_pin",
|
|
108
|
+
"value": "e307f19a9012265d71f25fcf8e43e595479d38d1358f4b607b052d0ce053abb0"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "exceptd:vendor:strip_summary",
|
|
112
|
+
"value": "observability event sink (_emitEvent → no-op); audit chain hooks; lazy-require for safeAsync (replaced with stdlib AbortSignal-aware sleep); numeric-checks dep (replaced with two inline predicates); constants C.TIME.seconds(...) (replaced with literal ms values)"
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"bom-ref": "vendor:blamejs:worker-pool.js",
|
|
118
|
+
"type": "library",
|
|
119
|
+
"name": "blamejs/worker-pool.js",
|
|
120
|
+
"version": "1442f17758a4",
|
|
121
|
+
"description": "Vendored from blamejs/lib/worker-pool.js (flattened + stripped). See vendor/blamejs/README.md.",
|
|
122
|
+
"licenses": [
|
|
123
|
+
{
|
|
124
|
+
"license": {
|
|
125
|
+
"id": "Apache-2.0"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"hashes": [
|
|
130
|
+
{
|
|
131
|
+
"alg": "SHA-256",
|
|
132
|
+
"content": "57d8f5c3bca23cd4ef2a16adf4107bc60c4da3fbc9bb861580edc76ed9a2d132"
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"externalReferences": [
|
|
136
|
+
{
|
|
137
|
+
"type": "vcs",
|
|
138
|
+
"url": "https://github.com/blamejs/blamejs"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"type": "distribution",
|
|
142
|
+
"url": "https://github.com/blamejs/blamejs/blob/1442f17758a4bd511c63877561c0ffa759f66a87/lib/worker-pool.js"
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
"properties": [
|
|
146
|
+
{
|
|
147
|
+
"name": "exceptd:vendor:upstream_sha256_at_pin",
|
|
148
|
+
"value": "262f99e9cc3d4a8f4eba9ad3e28401e8a1f47f78040afa63c9b17eb998437171"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"name": "exceptd:vendor:strip_summary",
|
|
152
|
+
"value": "WorkerPoolError class (replaced with vanilla Error with `code` field); validate-opts dep (replaced with inline whitelist + type checks); numeric-bounds dep (replaced with inline predicates); audit event sink (_emitAudit → no-op stub); constants C.BYTES.kib / C.TIME.minutes (replaced with literal int values)"
|
|
153
|
+
}
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
"dependencies": []
|
|
158
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* scripts/audit-cross-skill.js
|
|
4
|
+
*
|
|
5
|
+
* Comprehensive cross-skill accuracy / bug audit. Run after any
|
|
6
|
+
* skill add / rename / dispatch-rewire. Surfaces:
|
|
7
|
+
*
|
|
8
|
+
* - manifest paths that don't exist on disk
|
|
9
|
+
* - skill directories on disk with no manifest entry
|
|
10
|
+
* - frontmatter `name` drift from manifest `name`
|
|
11
|
+
* - skills missing from the researcher dispatch table
|
|
12
|
+
* - skills missing from AGENTS.md Quick Skill Reference
|
|
13
|
+
* - version drift between package.json / manifest.json / CHANGELOG.md
|
|
14
|
+
* - manifest-snapshot.json drift from manifest.json
|
|
15
|
+
* - sbom.cdx.json drift from live skill / catalog counts
|
|
16
|
+
* - broken ref: any cwe_refs / d3fend_refs / framework_gaps / atlas_refs /
|
|
17
|
+
* rfc_refs / dlp_refs that doesn't resolve in its catalog
|
|
18
|
+
* - RFC catalog reverse-references that drift from manifest forward-refs
|
|
19
|
+
* - skill-update-loop "Affected skills" blocks referencing nonexistent skills
|
|
20
|
+
* - stale references to renamed skills in any tracked file
|
|
21
|
+
* - trigger collisions between skills (informational)
|
|
22
|
+
* - README badge count drift
|
|
23
|
+
*
|
|
24
|
+
* Exit non-zero on any finding (excluding trigger collisions which are informational).
|
|
25
|
+
*
|
|
26
|
+
* Usage: node scripts/audit-cross-skill.js
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const fs = require("fs");
|
|
30
|
+
const path = require("path");
|
|
31
|
+
|
|
32
|
+
const ROOT = path.join(__dirname, "..");
|
|
33
|
+
const ABS = (p) => path.join(ROOT, p);
|
|
34
|
+
|
|
35
|
+
const issues = [];
|
|
36
|
+
const note = (s) => issues.push(s);
|
|
37
|
+
|
|
38
|
+
const manifest = JSON.parse(fs.readFileSync(ABS("manifest.json"), "utf8"));
|
|
39
|
+
const skills = manifest.skills;
|
|
40
|
+
const skillNames = new Set(skills.map((s) => s.name));
|
|
41
|
+
|
|
42
|
+
// 1. Manifest paths exist
|
|
43
|
+
for (const s of skills) {
|
|
44
|
+
if (!fs.existsSync(ABS(s.path))) {
|
|
45
|
+
note(`MANIFEST PATH MISSING: ${s.name} → ${s.path}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 2. Disk skill dirs have manifest entries
|
|
50
|
+
const skillDirs = fs
|
|
51
|
+
.readdirSync(ABS("skills"))
|
|
52
|
+
.filter((d) => fs.existsSync(ABS(`skills/${d}/skill.md`)));
|
|
53
|
+
for (const d of skillDirs) {
|
|
54
|
+
if (!skillNames.has(d)) note(`ORPHAN SKILL FILE: skills/${d}/skill.md has no manifest entry`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 3. Frontmatter name == manifest name
|
|
58
|
+
for (const s of skills) {
|
|
59
|
+
const c = fs.readFileSync(ABS(s.path), "utf8");
|
|
60
|
+
const m = c.match(/^name:\s*(\S+)/m);
|
|
61
|
+
if (m && m[1] !== s.name) {
|
|
62
|
+
note(`NAME DRIFT: manifest "${s.name}" vs frontmatter "${m[1]}" in ${s.path}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 4. Researcher dispatch covers every non-researcher skill
|
|
67
|
+
const researcher = fs.readFileSync(ABS("skills/researcher/skill.md"), "utf8");
|
|
68
|
+
const missingDispatch = [];
|
|
69
|
+
for (const n of skillNames) {
|
|
70
|
+
if (n === "researcher") continue;
|
|
71
|
+
if (!researcher.includes("`" + n + "`")) missingDispatch.push(n);
|
|
72
|
+
}
|
|
73
|
+
if (missingDispatch.length) {
|
|
74
|
+
note(`RESEARCHER DISPATCH GAPS: ${missingDispatch.join(", ")}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 5. AGENTS.md Quick Skill Reference lists every skill
|
|
78
|
+
const agents = fs.readFileSync(ABS("AGENTS.md"), "utf8");
|
|
79
|
+
const missingAgents = [];
|
|
80
|
+
for (const n of skillNames) {
|
|
81
|
+
if (!agents.includes("| " + n + " |") && !agents.includes("|" + n + "|")) {
|
|
82
|
+
missingAgents.push(n);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (missingAgents.length) {
|
|
86
|
+
note(`AGENTS.md QUICK REF GAPS: ${missingAgents.join(", ")}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 6. Version triple agreement
|
|
90
|
+
const pkg = JSON.parse(fs.readFileSync(ABS("package.json"), "utf8"));
|
|
91
|
+
const cl = fs.readFileSync(ABS("CHANGELOG.md"), "utf8");
|
|
92
|
+
const clTop = cl.match(/^## (\d+\.\d+\.\d+)/m);
|
|
93
|
+
if (clTop && clTop[1] !== pkg.version) {
|
|
94
|
+
note(`VERSION DRIFT: package.json ${pkg.version} vs CHANGELOG top ${clTop[1]}`);
|
|
95
|
+
}
|
|
96
|
+
if (manifest.version !== pkg.version) {
|
|
97
|
+
note(`VERSION DRIFT: manifest.json ${manifest.version} vs package.json ${pkg.version}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 7. Manifest snapshot matches manifest
|
|
101
|
+
const snap = JSON.parse(fs.readFileSync(ABS("manifest-snapshot.json"), "utf8"));
|
|
102
|
+
if (snap.skill_count !== skills.length) {
|
|
103
|
+
note(`SNAPSHOT DRIFT: snapshot ${snap.skill_count} vs manifest ${skills.length}`);
|
|
104
|
+
}
|
|
105
|
+
const snapNames = new Set(snap.skills.map((s) => s.name));
|
|
106
|
+
for (const n of skillNames) if (!snapNames.has(n)) note(`SNAPSHOT MISSING: ${n}`);
|
|
107
|
+
for (const n of snapNames) if (!skillNames.has(n)) note(`SNAPSHOT STALE: snapshot has "${n}" not in manifest`);
|
|
108
|
+
|
|
109
|
+
// 8. SBOM counts
|
|
110
|
+
const sbom = JSON.parse(fs.readFileSync(ABS("sbom.cdx.json"), "utf8"));
|
|
111
|
+
const sbomProps = Object.fromEntries(
|
|
112
|
+
(sbom.metadata.properties || []).map((p) => [p.name, p.value])
|
|
113
|
+
);
|
|
114
|
+
const sbomSkills = Number(sbomProps["exceptd:skill:count"]);
|
|
115
|
+
if (sbomSkills !== skills.length) {
|
|
116
|
+
note(`SBOM SKILL COUNT DRIFT: sbom ${sbomSkills} vs live ${skills.length}`);
|
|
117
|
+
}
|
|
118
|
+
const liveCatalogs = fs.readdirSync(ABS("data")).filter((f) => f.endsWith(".json")).length;
|
|
119
|
+
const sbomCatalogs = Number(sbomProps["exceptd:catalog:count"]);
|
|
120
|
+
if (sbomCatalogs !== liveCatalogs) {
|
|
121
|
+
note(`SBOM CATALOG COUNT DRIFT: sbom ${sbomCatalogs} vs live ${liveCatalogs}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 9. Catalog ref resolution
|
|
125
|
+
function catKeys(p) {
|
|
126
|
+
return new Set(
|
|
127
|
+
Object.keys(JSON.parse(fs.readFileSync(ABS(p), "utf8"))).filter(
|
|
128
|
+
(k) => !k.startsWith("_")
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const cweK = catKeys("data/cwe-catalog.json");
|
|
133
|
+
const d3K = catKeys("data/d3fend-catalog.json");
|
|
134
|
+
const fwK = catKeys("data/framework-control-gaps.json");
|
|
135
|
+
const atlasK = catKeys("data/atlas-ttps.json");
|
|
136
|
+
const rfcK = catKeys("data/rfc-references.json");
|
|
137
|
+
const dlpK = catKeys("data/dlp-controls.json");
|
|
138
|
+
for (const s of skills) {
|
|
139
|
+
for (const r of s.cwe_refs || []) if (!cweK.has(r)) note(`BAD CWE_REF: ${s.name} cites "${r}" not in cwe-catalog`);
|
|
140
|
+
for (const r of s.d3fend_refs || []) if (!d3K.has(r)) note(`BAD D3FEND_REF: ${s.name} cites "${r}" not in d3fend-catalog`);
|
|
141
|
+
for (const r of s.framework_gaps || []) if (!fwK.has(r)) note(`BAD FRAMEWORK_GAP: ${s.name} cites "${r}" not in framework-control-gaps`);
|
|
142
|
+
for (const r of s.atlas_refs || []) if (!atlasK.has(r)) note(`BAD ATLAS_REF: ${s.name} cites "${r}" not in atlas-ttps`);
|
|
143
|
+
for (const r of s.rfc_refs || []) if (!rfcK.has(r)) note(`BAD RFC_REF: ${s.name} cites "${r}" not in rfc-references`);
|
|
144
|
+
for (const r of s.dlp_refs || []) if (!dlpK.has(r)) note(`BAD DLP_REF: ${s.name} cites "${r}" not in dlp-controls`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 10. RFC catalog reverse-refs symmetric with manifest forward-refs
|
|
148
|
+
const rfcs = JSON.parse(fs.readFileSync(ABS("data/rfc-references.json"), "utf8"));
|
|
149
|
+
const forwardRfc = {};
|
|
150
|
+
for (const s of skills) {
|
|
151
|
+
for (const r of s.rfc_refs || []) (forwardRfc[r] = forwardRfc[r] || []).push(s.name);
|
|
152
|
+
}
|
|
153
|
+
for (const [rfcId, names] of Object.entries(forwardRfc)) {
|
|
154
|
+
const rev = new Set(rfcs[rfcId]?.skills_referencing || []);
|
|
155
|
+
for (const n of names) {
|
|
156
|
+
if (!rev.has(n)) note(`RFC REVERSE-REF MISSING: ${rfcId}.skills_referencing should include "${n}"`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const rfcId of Object.keys(rfcs).filter((k) => !k.startsWith("_"))) {
|
|
160
|
+
for (const n of rfcs[rfcId].skills_referencing || []) {
|
|
161
|
+
if (!skillNames.has(n)) {
|
|
162
|
+
note(`RFC STALE REVERSE-REF: ${rfcId} lists "${n}" not in manifest`);
|
|
163
|
+
} else {
|
|
164
|
+
const s = skills.find((x) => x.name === n);
|
|
165
|
+
if (!(s.rfc_refs || []).includes(rfcId)) {
|
|
166
|
+
note(`RFC ASYMMETRIC: ${rfcId}.skills_referencing has "${n}" but skill rfc_refs doesn't include ${rfcId}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 11. skill-update-loop Affected-skills blocks reference real skill names
|
|
173
|
+
const sul = fs.readFileSync(ABS("skills/skill-update-loop/skill.md"), "utf8");
|
|
174
|
+
const sulBlocks = [...sul.matchAll(/\*\*Affected skills.*?\*\*\s*([^\n]+)/g)];
|
|
175
|
+
for (const m of sulBlocks) {
|
|
176
|
+
const tokens = m[1].split(/[,;]/).map((s) => s.trim().replace(/[.()`*]/g, ""));
|
|
177
|
+
for (const tok of tokens) {
|
|
178
|
+
const first = tok.split(/\s/)[0];
|
|
179
|
+
if (!first) continue;
|
|
180
|
+
if (!/^[a-z][a-z0-9-]+$/.test(first)) continue;
|
|
181
|
+
if (skillNames.has(first)) continue;
|
|
182
|
+
if (["other", "any", "none", "skill", "existing", "and", "or", "the", "a", "an"].includes(first)) continue;
|
|
183
|
+
note(`SKILL-UPDATE-LOOP unknown affected-skill ref: "${first}"`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 12. Stale renamed-skill references
|
|
188
|
+
const staleTokens = ["age-gates-minor-safeguarding", "minor-safeguarding"];
|
|
189
|
+
const trackedDocs = [
|
|
190
|
+
"AGENTS.md", "CHANGELOG.md", "README.md",
|
|
191
|
+
"CONTEXT.md", "ARCHITECTURE.md", "MAINTAINERS.md",
|
|
192
|
+
"manifest.json", "manifest-snapshot.json",
|
|
193
|
+
];
|
|
194
|
+
for (const f of trackedDocs) {
|
|
195
|
+
if (!fs.existsSync(ABS(f))) continue;
|
|
196
|
+
const body = fs.readFileSync(ABS(f), "utf8");
|
|
197
|
+
for (const tok of staleTokens) {
|
|
198
|
+
if (body.includes(tok)) {
|
|
199
|
+
// CHANGELOG legitimately records the rename in the 0.5.4 entry.
|
|
200
|
+
if (f === "CHANGELOG.md") continue;
|
|
201
|
+
note(`STALE RENAME REF in ${f}: contains "${tok}"`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
for (const s of skills) {
|
|
206
|
+
const body = fs.readFileSync(ABS(s.path), "utf8");
|
|
207
|
+
for (const tok of staleTokens) {
|
|
208
|
+
if (body.includes(tok)) note(`STALE RENAME REF in ${s.path}: contains "${tok}"`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 13. Trigger collisions (informational; not necessarily bugs)
|
|
213
|
+
const triggerOwners = {};
|
|
214
|
+
for (const s of skills) {
|
|
215
|
+
for (const t of s.triggers || []) {
|
|
216
|
+
const k = String(t).toLowerCase().trim();
|
|
217
|
+
(triggerOwners[k] = triggerOwners[k] || []).push(s.name);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const collisions = Object.entries(triggerOwners).filter(([, v]) => v.length > 1);
|
|
221
|
+
|
|
222
|
+
// 14. README badges
|
|
223
|
+
const readme = fs.readFileSync(ABS("README.md"), "utf8");
|
|
224
|
+
const badgeMatch = readme.match(/skills-(\d+)-/);
|
|
225
|
+
if (badgeMatch && Number(badgeMatch[1]) !== skills.length) {
|
|
226
|
+
note(`README BADGE DRIFT: shows skills-${badgeMatch[1]}- but manifest has ${skills.length}`);
|
|
227
|
+
}
|
|
228
|
+
const jurBadge = readme.match(/jurisdictions-(\d+)-/);
|
|
229
|
+
const liveJurs = (() => {
|
|
230
|
+
const g = JSON.parse(fs.readFileSync(ABS("data/global-frameworks.json"), "utf8"));
|
|
231
|
+
return Object.keys(g).filter((k) => !k.startsWith("_") && k !== "GLOBAL").length;
|
|
232
|
+
})();
|
|
233
|
+
if (jurBadge && Number(jurBadge[1]) !== liveJurs) {
|
|
234
|
+
note(`README BADGE DRIFT: shows jurisdictions-${jurBadge[1]}- but live count is ${liveJurs}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 15. Researcher count claim
|
|
238
|
+
const cntClaim = researcher.match(/(\d+)\s+specialized skills downstream/);
|
|
239
|
+
if (cntClaim) {
|
|
240
|
+
const claimed = Number(cntClaim[1]);
|
|
241
|
+
const actual = skills.length - 1; // minus researcher itself
|
|
242
|
+
if (claimed !== actual) {
|
|
243
|
+
note(`RESEARCHER COUNT CLAIM: says ${claimed} downstream but actual is ${actual}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Output
|
|
248
|
+
console.log("\n=== CROSS-SKILL AUDIT ===");
|
|
249
|
+
console.log(`Skills: ${skills.length}`);
|
|
250
|
+
console.log(`Catalogs: ${liveCatalogs}`);
|
|
251
|
+
console.log(`Trigger collisions: ${collisions.length} (informational)`);
|
|
252
|
+
for (const [t, owners] of collisions) {
|
|
253
|
+
console.log(` "${t}" → ${owners.join(", ")}`);
|
|
254
|
+
}
|
|
255
|
+
console.log(`\n=== ISSUES (${issues.length}) ===`);
|
|
256
|
+
if (issues.length === 0) {
|
|
257
|
+
console.log("zero issues");
|
|
258
|
+
} else {
|
|
259
|
+
for (const i of issues) console.log(" • " + i);
|
|
260
|
+
}
|
|
261
|
+
process.exit(issues.length === 0 ? 0 : 1);
|