@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.
Files changed (136) hide show
  1. package/AGENTS.md +232 -0
  2. package/ARCHITECTURE.md +267 -0
  3. package/CHANGELOG.md +616 -0
  4. package/CONTEXT.md +203 -0
  5. package/LICENSE +200 -0
  6. package/NOTICE +82 -0
  7. package/README.md +307 -0
  8. package/SECURITY.md +73 -0
  9. package/agents/README.md +81 -0
  10. package/agents/report-generator.md +156 -0
  11. package/agents/skill-updater.md +102 -0
  12. package/agents/source-validator.md +119 -0
  13. package/agents/threat-researcher.md +149 -0
  14. package/bin/exceptd.js +183 -0
  15. package/data/_indexes/_meta.json +88 -0
  16. package/data/_indexes/activity-feed.json +362 -0
  17. package/data/_indexes/catalog-summaries.json +229 -0
  18. package/data/_indexes/chains.json +7135 -0
  19. package/data/_indexes/currency.json +359 -0
  20. package/data/_indexes/did-ladders.json +451 -0
  21. package/data/_indexes/frequency.json +2072 -0
  22. package/data/_indexes/handoff-dag.json +476 -0
  23. package/data/_indexes/jurisdiction-clocks.json +967 -0
  24. package/data/_indexes/jurisdiction-map.json +536 -0
  25. package/data/_indexes/recipes.json +319 -0
  26. package/data/_indexes/section-offsets.json +3656 -0
  27. package/data/_indexes/stale-content.json +14 -0
  28. package/data/_indexes/summary-cards.json +1736 -0
  29. package/data/_indexes/theater-fingerprints.json +381 -0
  30. package/data/_indexes/token-budget.json +2137 -0
  31. package/data/_indexes/trigger-table.json +1374 -0
  32. package/data/_indexes/xref.json +818 -0
  33. package/data/atlas-ttps.json +282 -0
  34. package/data/cve-catalog.json +496 -0
  35. package/data/cwe-catalog.json +1017 -0
  36. package/data/d3fend-catalog.json +738 -0
  37. package/data/dlp-controls.json +1039 -0
  38. package/data/exploit-availability.json +67 -0
  39. package/data/framework-control-gaps.json +1255 -0
  40. package/data/global-frameworks.json +2913 -0
  41. package/data/rfc-references.json +324 -0
  42. package/data/zeroday-lessons.json +377 -0
  43. package/keys/public.pem +3 -0
  44. package/lib/framework-gap.js +328 -0
  45. package/lib/job-queue.js +195 -0
  46. package/lib/lint-skills.js +536 -0
  47. package/lib/prefetch.js +372 -0
  48. package/lib/refresh-external.js +713 -0
  49. package/lib/schemas/cve-catalog.schema.json +151 -0
  50. package/lib/schemas/manifest.schema.json +106 -0
  51. package/lib/schemas/skill-frontmatter.schema.json +113 -0
  52. package/lib/scoring.js +149 -0
  53. package/lib/sign.js +197 -0
  54. package/lib/ttp-mapper.js +80 -0
  55. package/lib/validate-catalog-meta.js +198 -0
  56. package/lib/validate-cve-catalog.js +213 -0
  57. package/lib/validate-indexes.js +83 -0
  58. package/lib/validate-package.js +162 -0
  59. package/lib/validate-vendor.js +85 -0
  60. package/lib/verify.js +216 -0
  61. package/lib/worker-pool.js +84 -0
  62. package/manifest-snapshot.json +1833 -0
  63. package/manifest.json +2108 -0
  64. package/orchestrator/README.md +124 -0
  65. package/orchestrator/dispatcher.js +140 -0
  66. package/orchestrator/event-bus.js +146 -0
  67. package/orchestrator/index.js +874 -0
  68. package/orchestrator/pipeline.js +201 -0
  69. package/orchestrator/scanner.js +327 -0
  70. package/orchestrator/scheduler.js +137 -0
  71. package/package.json +113 -0
  72. package/sbom.cdx.json +158 -0
  73. package/scripts/audit-cross-skill.js +261 -0
  74. package/scripts/audit-perf.js +160 -0
  75. package/scripts/bootstrap.js +205 -0
  76. package/scripts/build-indexes.js +721 -0
  77. package/scripts/builders/activity-feed.js +79 -0
  78. package/scripts/builders/catalog-summaries.js +67 -0
  79. package/scripts/builders/currency.js +109 -0
  80. package/scripts/builders/cwe-chains.js +105 -0
  81. package/scripts/builders/did-ladders.js +149 -0
  82. package/scripts/builders/frequency.js +89 -0
  83. package/scripts/builders/jurisdiction-clocks.js +126 -0
  84. package/scripts/builders/recipes.js +159 -0
  85. package/scripts/builders/section-offsets.js +162 -0
  86. package/scripts/builders/stale-content.js +171 -0
  87. package/scripts/builders/summary-cards.js +166 -0
  88. package/scripts/builders/theater-fingerprints.js +198 -0
  89. package/scripts/builders/token-budget.js +96 -0
  90. package/scripts/check-manifest-snapshot.js +217 -0
  91. package/scripts/predeploy.js +267 -0
  92. package/scripts/refresh-manifest-snapshot.js +57 -0
  93. package/scripts/refresh-sbom.js +222 -0
  94. package/skills/age-gates-child-safety/skill.md +456 -0
  95. package/skills/ai-attack-surface/skill.md +282 -0
  96. package/skills/ai-c2-detection/skill.md +440 -0
  97. package/skills/ai-risk-management/skill.md +311 -0
  98. package/skills/api-security/skill.md +287 -0
  99. package/skills/attack-surface-pentest/skill.md +381 -0
  100. package/skills/cloud-security/skill.md +384 -0
  101. package/skills/compliance-theater/skill.md +365 -0
  102. package/skills/container-runtime-security/skill.md +379 -0
  103. package/skills/coordinated-vuln-disclosure/skill.md +473 -0
  104. package/skills/defensive-countermeasure-mapping/skill.md +300 -0
  105. package/skills/dlp-gap-analysis/skill.md +337 -0
  106. package/skills/email-security-anti-phishing/skill.md +206 -0
  107. package/skills/exploit-scoring/skill.md +331 -0
  108. package/skills/framework-gap-analysis/skill.md +374 -0
  109. package/skills/fuzz-testing-strategy/skill.md +313 -0
  110. package/skills/global-grc/skill.md +564 -0
  111. package/skills/identity-assurance/skill.md +272 -0
  112. package/skills/incident-response-playbook/skill.md +546 -0
  113. package/skills/kernel-lpe-triage/skill.md +303 -0
  114. package/skills/mcp-agent-trust/skill.md +326 -0
  115. package/skills/mlops-security/skill.md +325 -0
  116. package/skills/ot-ics-security/skill.md +340 -0
  117. package/skills/policy-exception-gen/skill.md +437 -0
  118. package/skills/pqc-first/skill.md +546 -0
  119. package/skills/rag-pipeline-security/skill.md +294 -0
  120. package/skills/researcher/skill.md +310 -0
  121. package/skills/sector-energy/skill.md +409 -0
  122. package/skills/sector-federal-government/skill.md +302 -0
  123. package/skills/sector-financial/skill.md +398 -0
  124. package/skills/sector-healthcare/skill.md +373 -0
  125. package/skills/security-maturity-tiers/skill.md +464 -0
  126. package/skills/skill-update-loop/skill.md +463 -0
  127. package/skills/supply-chain-integrity/skill.md +318 -0
  128. package/skills/threat-model-currency/skill.md +404 -0
  129. package/skills/threat-modeling-methodology/skill.md +312 -0
  130. package/skills/webapp-security/skill.md +281 -0
  131. package/skills/zeroday-gap-learn/skill.md +350 -0
  132. package/vendor/blamejs/LICENSE +201 -0
  133. package/vendor/blamejs/README.md +54 -0
  134. package/vendor/blamejs/_PROVENANCE.json +54 -0
  135. package/vendor/blamejs/retry.js +335 -0
  136. package/vendor/blamejs/worker-pool.js +418 -0
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ /**
3
+ * scripts/audit-perf.js
4
+ *
5
+ * Micro-benchmarks the hot paths a skill / orchestrator / audit
6
+ * actually exercises. Times each operation so we can decide what's
7
+ * worth pre-computing into a seeded index.
8
+ *
9
+ * Usage: node scripts/audit-perf.js
10
+ */
11
+
12
+ const fs = require("fs");
13
+ const path = require("path");
14
+
15
+ const ROOT = path.join(__dirname, "..");
16
+ const ABS = (p) => path.join(ROOT, p);
17
+
18
+ function bench(label, fn, iters = 1) {
19
+ const start = process.hrtime.bigint();
20
+ let result;
21
+ for (let i = 0; i < iters; i++) result = fn();
22
+ const ns = Number(process.hrtime.bigint() - start);
23
+ const ms = (ns / 1e6 / iters).toFixed(3);
24
+ console.log(` ${ms.padStart(10)} ms ${label}`);
25
+ return result;
26
+ }
27
+
28
+ console.log("\n=== exceptd hot-path performance ===\n");
29
+ console.log("Operation Time");
30
+ console.log("-".repeat(70));
31
+
32
+ // 1. Load manifest
33
+ const manifest = bench("load manifest.json (parse)", () =>
34
+ JSON.parse(fs.readFileSync(ABS("manifest.json"), "utf8"))
35
+ );
36
+ const skills = manifest.skills;
37
+
38
+ // 2. Load every data catalog
39
+ const catalogs = [
40
+ "cve-catalog.json",
41
+ "atlas-ttps.json",
42
+ "framework-control-gaps.json",
43
+ "global-frameworks.json",
44
+ "cwe-catalog.json",
45
+ "d3fend-catalog.json",
46
+ "rfc-references.json",
47
+ "dlp-controls.json",
48
+ "zeroday-lessons.json",
49
+ "exploit-availability.json",
50
+ ];
51
+ const catalogObjs = bench(`load all ${catalogs.length} data catalogs`, () => {
52
+ const out = {};
53
+ for (const c of catalogs) out[c] = JSON.parse(fs.readFileSync(ABS("data/" + c), "utf8"));
54
+ return out;
55
+ });
56
+
57
+ // 3. Read every skill body
58
+ bench(`read all ${skills.length} skill.md bodies`, () => {
59
+ for (const s of skills) fs.readFileSync(ABS(s.path), "utf8");
60
+ });
61
+
62
+ // 4. Parse every skill frontmatter (the linter's expensive op)
63
+ function parseFm(text) {
64
+ if (!text.startsWith("---")) return null;
65
+ const end = text.indexOf("\n---", 3);
66
+ if (end < 0) return null;
67
+ const fm = text.slice(3, end).replace(/^\r?\n/, "");
68
+ const r = {};
69
+ const lines = fm.split(/\r?\n/);
70
+ let i = 0;
71
+ while (i < lines.length) {
72
+ const L = lines[i];
73
+ if (!L.trim() || L.trimStart().startsWith("#")) { i++; continue; }
74
+ const m = L.match(/^([A-Za-z_]+):\s*(.*)$/);
75
+ if (!m) { i++; continue; }
76
+ const k = m[1], rest = m[2].trim();
77
+ if (rest === "" || rest === undefined) {
78
+ const items = []; i++;
79
+ while (i < lines.length && /^\s+-\s+/.test(lines[i])) {
80
+ items.push(lines[i].match(/^\s+-\s+(.*)$/)[1].trim()); i++;
81
+ }
82
+ r[k] = items; continue;
83
+ }
84
+ if (rest === "[]") { r[k] = []; i++; continue; }
85
+ r[k] = rest; i++;
86
+ }
87
+ return r;
88
+ }
89
+ bench(`parse all ${skills.length} skill frontmatters`, () => {
90
+ for (const s of skills) parseFm(fs.readFileSync(ABS(s.path), "utf8"));
91
+ });
92
+
93
+ // 5. Trigger lookup (what the dispatcher does)
94
+ const flatTriggers = [];
95
+ for (const s of skills) for (const t of s.triggers || []) flatTriggers.push([t.toLowerCase(), s.name]);
96
+ bench("trigger string-match against all skills (single query)", () => {
97
+ const q = "ai red team";
98
+ return flatTriggers.filter(([t]) => t.includes(q) || q.includes(t));
99
+ });
100
+
101
+ // 6. Cross-reference lookup: which skills cite a given CWE?
102
+ bench("xref: which skills cite CWE-79? (linear scan)", () => {
103
+ const refSet = "CWE-79";
104
+ return skills.filter((s) => (s.cwe_refs || []).includes(refSet)).map((s) => s.name);
105
+ });
106
+
107
+ // 7. Multi-hop chain: CVE → CWE → ATLAS → framework_gaps for one CVE
108
+ const cve = catalogObjs["cve-catalog.json"]["CVE-2026-31431"];
109
+ bench("multi-hop chain: CVE-2026-31431 → CWE → ATLAS → frameworks", () => {
110
+ // skills that mention CVE → their CWE refs → their ATLAS refs → their framework gaps
111
+ const skillsCiting = skills.filter((s) =>
112
+ (catalogObjs["cve-catalog.json"]["CVE-2026-31431"].evidence_cves || []).length > 0 // dummy filter
113
+ );
114
+ const cwes = new Set();
115
+ const atlases = new Set();
116
+ const fws = new Set();
117
+ for (const s of skills) {
118
+ if (!(s.atlas_refs || []).length) continue;
119
+ for (const c of s.cwe_refs || []) cwes.add(c);
120
+ for (const a of s.atlas_refs || []) atlases.add(a);
121
+ for (const f of s.framework_gaps || []) fws.add(f);
122
+ }
123
+ return { cwes: [...cwes], atlases: [...atlases], fws: [...fws] };
124
+ });
125
+
126
+ // 8. Forward_watch aggregator (read 38 skill files, parse frontmatter, union all forward_watch)
127
+ bench(`watchlist aggregator (full scan, ${skills.length} skills)`, () => {
128
+ const watch = new Set();
129
+ for (const s of skills) {
130
+ const fm = parseFm(fs.readFileSync(ABS(s.path), "utf8"));
131
+ if (fm && Array.isArray(fm.forward_watch)) for (const w of fm.forward_watch) watch.add(w);
132
+ }
133
+ return watch.size;
134
+ });
135
+
136
+ // 9. Full cross-skill audit
137
+ bench("full cross-skill audit script (subprocess overhead included)", () => {
138
+ // Simulate: load manifest + all catalogs + all skill files + compute every refset
139
+ for (const s of skills) {
140
+ fs.readFileSync(ABS(s.path), "utf8");
141
+ for (const f of s.cwe_refs || []) { /* lookup */ }
142
+ for (const f of s.d3fend_refs || []) { /* lookup */ }
143
+ for (const f of s.framework_gaps || []) { /* lookup */ }
144
+ for (const f of s.atlas_refs || []) { /* lookup */ }
145
+ for (const f of s.rfc_refs || []) { /* lookup */ }
146
+ }
147
+ });
148
+
149
+ console.log("");
150
+ console.log("=== Sizes ===");
151
+ const totalBytes = (paths) => paths.reduce((t, p) => t + fs.statSync(ABS(p)).size, 0);
152
+ console.log(` manifest.json: ${fs.statSync(ABS("manifest.json")).size.toLocaleString()} bytes`);
153
+ console.log(` manifest-snapshot.json: ${fs.statSync(ABS("manifest-snapshot.json")).size.toLocaleString()} bytes`);
154
+ console.log(` data/*.json (${catalogs.length} files): ${totalBytes(catalogs.map(c => "data/" + c)).toLocaleString()} bytes`);
155
+ console.log(` skills/*/skill.md (${skills.length} files): ${totalBytes(skills.map(s => s.path)).toLocaleString()} bytes`);
156
+
157
+ console.log("\n=== Recommendation surfaces (manual review) ===");
158
+ console.log(" - Anything slower than 50 ms in the hot path = candidate for pre-computed index");
159
+ console.log(" - Anything called >1×/operation = candidate for cached + invalidated index");
160
+ console.log(" - JSON files >100 KB = candidate for streaming or partial load if hot-path indexed");
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * exceptd Security — bootstrap ceremony.
6
+ *
7
+ * Three audiences, three behaviors. The script auto-detects which mode is
8
+ * appropriate so a downstream consumer never accidentally invalidates the
9
+ * maintainer's signing key.
10
+ *
11
+ * 1. Downstream consumer (keys/public.pem exists, .keys/private.pem missing,
12
+ * no --init flag) — VERIFY ONLY. The maintainer already shipped the public
13
+ * key + signed manifest. Running `npm run bootstrap` here just confirms
14
+ * the working tree is intact. No keypair is generated; no signatures are
15
+ * rewritten. This is the safe default.
16
+ *
17
+ * 2. Maintainer re-sign (.keys/private.pem exists) — SIGN + VERIFY. Used
18
+ * after editing skill content. Re-signs every skill with the existing
19
+ * private key, then verifies.
20
+ *
21
+ * 3. First-maintainer init (no keys/public.pem, OR --init explicitly passed)
22
+ * — GENERATE + SIGN + VERIFY. Used once when a maintainer sets up signing
23
+ * for a brand-new clone. Generates an Ed25519 keypair, signs every skill,
24
+ * and verifies. The new public key is committed; the private key stays in
25
+ * .keys/ (gitignored).
26
+ *
27
+ * The private key never leaves the maintainer's machine. The public key in
28
+ * keys/public.pem is the one tracked artifact and is committed by the
29
+ * maintainer after first init.
30
+ *
31
+ * Subprocesses use execFileSync (no shell) with argument arrays — there is no
32
+ * user input on the path, and avoiding the shell removes the injection surface
33
+ * regardless.
34
+ *
35
+ * Usage:
36
+ * node scripts/bootstrap.js Auto-detect mode and run.
37
+ * node scripts/bootstrap.js --init Force first-maintainer init (generate
38
+ * keypair + sign + verify).
39
+ * node scripts/bootstrap.js --force Re-run even if marker exists.
40
+ * node scripts/bootstrap.js --help Print this help text.
41
+ *
42
+ * Dependencies: Node 24 stdlib only. package.json has no runtime deps and
43
+ * this script keeps it that way.
44
+ */
45
+
46
+ const fs = require('node:fs');
47
+ const path = require('node:path');
48
+ const childProcess = require('node:child_process');
49
+
50
+ const ROOT = path.join(__dirname, '..');
51
+ const PRIVATE_KEY_PATH = path.join(ROOT, '.keys', 'private.pem');
52
+ const PUBLIC_KEY_PATH = path.join(ROOT, 'keys', 'public.pem');
53
+ const MARKER_PATH = path.join(ROOT, '.bootstrap-complete');
54
+ const SIGN_SCRIPT = path.join(ROOT, 'lib', 'sign.js');
55
+ const VERIFY_SCRIPT = path.join(ROOT, 'lib', 'verify.js');
56
+
57
+ const HELP = `
58
+ exceptd Security — bootstrap ceremony
59
+
60
+ Mode is auto-detected from current state:
61
+
62
+ Downstream consumer (default):
63
+ Public key present, private key absent → VERIFY ONLY.
64
+ No keypair generated; no signatures rewritten.
65
+
66
+ Maintainer re-sign:
67
+ Private key present → SIGN + VERIFY.
68
+ Re-signs every skill in manifest.json with the existing private key.
69
+
70
+ First-maintainer init (--init or no keys at all):
71
+ GENERATE + SIGN + VERIFY.
72
+ Generates an Ed25519 keypair, signs every skill, runs the verifier.
73
+
74
+ Usage:
75
+ node scripts/bootstrap.js Auto-detect mode and run.
76
+ node scripts/bootstrap.js --init Force first-maintainer init.
77
+ node scripts/bootstrap.js --force Re-run even if marker exists.
78
+ node scripts/bootstrap.js --help Show this message.
79
+
80
+ Outputs (only in maintainer modes):
81
+ .keys/private.pem Ed25519 private key (gitignored — never commit).
82
+ keys/public.pem Ed25519 public key (commit this).
83
+ manifest.json Updated with per-skill signatures.
84
+ .bootstrap-complete Local timestamp marker (gitignored).
85
+ `;
86
+
87
+ function parseArgs(argv) {
88
+ const args = { force: false, init: false, help: false };
89
+ for (const a of argv.slice(2)) {
90
+ if (a === '--force') args.force = true;
91
+ else if (a === '--init') args.init = true;
92
+ else if (a === '--help' || a === '-h') args.help = true;
93
+ else {
94
+ console.error(`[bootstrap] Unknown argument: ${a}`);
95
+ console.error('[bootstrap] Run with --help for usage.');
96
+ process.exit(2);
97
+ }
98
+ }
99
+ return args;
100
+ }
101
+
102
+ function detectMode(args) {
103
+ const hasPublic = fs.existsSync(PUBLIC_KEY_PATH);
104
+ const hasPrivate = fs.existsSync(PRIVATE_KEY_PATH);
105
+
106
+ if (args.init) return 'init';
107
+ if (hasPrivate) return 'resign';
108
+ if (hasPublic) return 'verify-only';
109
+ return 'init';
110
+ }
111
+
112
+ function run(label, scriptPath, scriptArgs) {
113
+ const pretty = `node ${path.relative(ROOT, scriptPath)} ${scriptArgs.join(' ')}`.trim();
114
+ console.log(`[bootstrap] ${label}: ${pretty}`);
115
+ try {
116
+ // execFileSync: no shell, args passed as a vetted array. The script path
117
+ // and all args here are constants under this repo's control.
118
+ childProcess.execFileSync(process.execPath, [scriptPath, ...scriptArgs], {
119
+ cwd: ROOT,
120
+ stdio: 'inherit'
121
+ });
122
+ } catch (err) {
123
+ console.error(`[bootstrap] FAILED at step "${label}".`);
124
+ process.exit(err.status && Number.isInteger(err.status) ? err.status : 1);
125
+ }
126
+ }
127
+
128
+ function writeMarker() {
129
+ const payload = {
130
+ completed_at: new Date().toISOString(),
131
+ node_version: process.version,
132
+ platform: process.platform
133
+ };
134
+ fs.writeFileSync(MARKER_PATH, JSON.stringify(payload, null, 2) + '\n', 'utf8');
135
+ console.log(`[bootstrap] Wrote ${path.relative(ROOT, MARKER_PATH)}`);
136
+ }
137
+
138
+ function main() {
139
+ const args = parseArgs(process.argv);
140
+
141
+ if (args.help) {
142
+ process.stdout.write(HELP);
143
+ return;
144
+ }
145
+
146
+ if (fs.existsSync(MARKER_PATH) && !args.force) {
147
+ console.log('[bootstrap] Already complete (.bootstrap-complete present).');
148
+ console.log('[bootstrap] Pass --force to re-run the ceremony.');
149
+ return;
150
+ }
151
+
152
+ const mode = detectMode(args);
153
+
154
+ if (mode === 'verify-only') {
155
+ // Downstream consumer path. The maintainer already shipped the public
156
+ // key and signed manifest. Running bootstrap here just confirms tree
157
+ // integrity — never generates or signs, which would invalidate the
158
+ // upstream maintainer's signing chain.
159
+ console.log('[bootstrap] Detected downstream-consumer state:');
160
+ console.log(' - keys/public.pem present (shipped by maintainer)');
161
+ console.log(' - .keys/private.pem absent');
162
+ console.log('[bootstrap] Running VERIFY ONLY. No keypair will be generated.');
163
+ console.log('[bootstrap] If you ARE the maintainer setting up a new clone, pass --init.');
164
+ console.log();
165
+ run('verify signatures', VERIFY_SCRIPT, []);
166
+ writeMarker();
167
+ console.log('\n[bootstrap] Verify-only ceremony complete. Tree integrity confirmed.');
168
+ return;
169
+ }
170
+
171
+ if (mode === 'resign') {
172
+ // Maintainer re-sign path. Private key already exists; re-sign every
173
+ // skill against the current content and verify.
174
+ console.log('[bootstrap] Detected maintainer re-sign state (private key present).');
175
+ console.log('[bootstrap] Re-signing every skill with the existing private key.');
176
+ console.log();
177
+ run('sign all skills', SIGN_SCRIPT, ['sign-all']);
178
+ run('verify signatures', VERIFY_SCRIPT, []);
179
+ writeMarker();
180
+ console.log('\n[bootstrap] Re-sign ceremony complete.');
181
+ console.log('[bootstrap] If skill content or manifest.json changed, commit the updates:');
182
+ console.log(' git add manifest.json');
183
+ console.log(' git commit -m "resign: skill content updated"');
184
+ return;
185
+ }
186
+
187
+ // mode === 'init' — first-maintainer setup.
188
+ console.log('[bootstrap] First-maintainer init mode:');
189
+ console.log(' - generating Ed25519 keypair (one-time)');
190
+ console.log(' - signing every skill listed in manifest.json');
191
+ console.log(' - verifying the signature chain end-to-end');
192
+ console.log();
193
+ run('generate Ed25519 keypair', SIGN_SCRIPT, ['generate-keypair']);
194
+ run('sign all skills', SIGN_SCRIPT, ['sign-all']);
195
+ run('verify signatures', VERIFY_SCRIPT, []);
196
+ writeMarker();
197
+
198
+ console.log('\n[bootstrap] First-maintainer init complete. Next steps:');
199
+ console.log(' git add keys/public.pem manifest.json');
200
+ console.log(' git commit -m "bootstrap: add signing public key and signed manifest"');
201
+ }
202
+
203
+ if (require.main === module) {
204
+ main();
205
+ }