@blamejs/exceptd-skills 0.19.33 → 0.19.35

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 (119) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/bin/exceptd.js +895 -2828
  3. package/data/_indexes/_meta.json +2 -2
  4. package/lib/auto-discovery.js +56 -286
  5. package/lib/canonical-eq.js +7 -40
  6. package/lib/citation-resolve.js +22 -70
  7. package/lib/collectors/ai-api.js +170 -76
  8. package/lib/collectors/cicd-pipeline-compromise.js +113 -136
  9. package/lib/collectors/citation-hygiene.js +72 -210
  10. package/lib/collectors/containers.js +41 -130
  11. package/lib/collectors/cred-stores.js +31 -115
  12. package/lib/collectors/crypto-codebase.js +55 -138
  13. package/lib/collectors/crypto.js +24 -54
  14. package/lib/collectors/hardening.js +20 -78
  15. package/lib/collectors/kernel.js +16 -46
  16. package/lib/collectors/library-author.js +198 -211
  17. package/lib/collectors/mcp.js +24 -70
  18. package/lib/collectors/runtime.js +24 -86
  19. package/lib/collectors/sbom.js +130 -118
  20. package/lib/collectors/scan-excludes.js +33 -139
  21. package/lib/collectors/secrets.js +62 -178
  22. package/lib/cross-ref-api.js +39 -123
  23. package/lib/currency-severity.js +8 -27
  24. package/lib/cve-batch.js +13 -21
  25. package/lib/cve-cli.js +13 -20
  26. package/lib/cve-curation.js +72 -239
  27. package/lib/cve-regression-watcher.js +29 -155
  28. package/lib/cvss.js +13 -54
  29. package/lib/doctor-bucketing.js +3 -19
  30. package/lib/exit-codes.js +10 -42
  31. package/lib/flag-suggest.js +7 -25
  32. package/lib/framework-gap.js +39 -113
  33. package/lib/gap-detectors.js +37 -159
  34. package/lib/id-validation.js +9 -30
  35. package/lib/job-queue.js +13 -36
  36. package/lib/lint-skills.js +88 -236
  37. package/lib/playbook-runner.js +759 -2107
  38. package/lib/prefetch.js +101 -376
  39. package/lib/refresh-external.js +199 -633
  40. package/lib/refresh-network.js +78 -311
  41. package/lib/rfc-cli.js +23 -68
  42. package/lib/scoring.js +85 -146
  43. package/lib/sign.js +43 -229
  44. package/lib/source-advisories.js +43 -194
  45. package/lib/source-ghsa.js +37 -120
  46. package/lib/source-osv.js +94 -266
  47. package/lib/ttp-mapper.js +28 -27
  48. package/lib/upstream-check-cli.js +36 -29
  49. package/lib/upstream-check.js +19 -44
  50. package/lib/validate-catalog-meta.js +17 -61
  51. package/lib/validate-cve-catalog.js +52 -121
  52. package/lib/validate-indexes.js +25 -76
  53. package/lib/validate-package.js +16 -62
  54. package/lib/validate-playbooks.js +78 -286
  55. package/lib/validate-vendor.js +16 -49
  56. package/lib/verify.js +56 -286
  57. package/lib/version-pins.js +5 -34
  58. package/lib/worker-pool.js +11 -30
  59. package/lib/xml-tokenizer.js +47 -152
  60. package/manifest.json +53 -53
  61. package/orchestrator/dispatcher.js +17 -68
  62. package/orchestrator/event-bus.js +11 -74
  63. package/orchestrator/index.js +138 -413
  64. package/orchestrator/pipeline.js +28 -85
  65. package/orchestrator/scanner.js +34 -138
  66. package/orchestrator/scheduler.js +20 -84
  67. package/package.json +1 -1
  68. package/sbom.cdx.json +242 -242
  69. package/scripts/audit-catalog-gaps.js +9 -62
  70. package/scripts/audit-cross-skill.js +5 -31
  71. package/scripts/audit-perf.js +29 -28
  72. package/scripts/backfill-theater-test.js +7 -64
  73. package/scripts/bootstrap.js +12 -44
  74. package/scripts/build-indexes.js +40 -154
  75. package/scripts/builders/activity-feed.js +4 -14
  76. package/scripts/builders/catalog-summaries.js +3 -10
  77. package/scripts/builders/currency.js +7 -20
  78. package/scripts/builders/cwe-chains.js +7 -30
  79. package/scripts/builders/did-ladders.js +6 -13
  80. package/scripts/builders/frequency.js +5 -19
  81. package/scripts/builders/jurisdiction-clocks.js +6 -25
  82. package/scripts/builders/recipes.js +6 -14
  83. package/scripts/builders/section-offsets.js +13 -51
  84. package/scripts/builders/stale-content.js +7 -28
  85. package/scripts/builders/summary-cards.js +8 -29
  86. package/scripts/builders/theater-fingerprints.js +21 -31
  87. package/scripts/builders/token-budget.js +4 -31
  88. package/scripts/check-agents-md-collectors.js +26 -57
  89. package/scripts/check-catalog-gap-budget.js +15 -32
  90. package/scripts/check-changelog-extract.js +18 -48
  91. package/scripts/check-codebase-patterns-currency.js +6 -22
  92. package/scripts/check-codebase-patterns.js +63 -143
  93. package/scripts/check-epss-consistency.js +9 -64
  94. package/scripts/check-framework-gap-coverage.js +13 -31
  95. package/scripts/check-manifest-snapshot.js +62 -81
  96. package/scripts/check-sbom-currency.js +44 -142
  97. package/scripts/check-test-count.js +15 -52
  98. package/scripts/check-test-coverage.js +83 -198
  99. package/scripts/check-test-subjects.js +21 -62
  100. package/scripts/check-ttp-references.js +14 -38
  101. package/scripts/check-ttp-upstream.js +8 -40
  102. package/scripts/check-version-bump.js +9 -61
  103. package/scripts/check-version-tags.js +20 -121
  104. package/scripts/predeploy.js +38 -184
  105. package/scripts/refresh-manifest-snapshot.js +16 -38
  106. package/scripts/refresh-mitre-atlas.js +7 -8
  107. package/scripts/refresh-mitre-attack.js +1 -8
  108. package/scripts/refresh-mitre-d3fend.js +3 -9
  109. package/scripts/refresh-mitre-ics-attack.js +7 -8
  110. package/scripts/refresh-reverse-refs.js +27 -94
  111. package/scripts/refresh-rfc-index.js +7 -10
  112. package/scripts/refresh-sbom.js +31 -161
  113. package/scripts/refresh-upstream-catalogs.js +63 -148
  114. package/scripts/release.js +69 -234
  115. package/scripts/run-e2e-scenarios.js +26 -73
  116. package/scripts/sync-manifest-metadata.js +10 -34
  117. package/scripts/sync-package-description.js +8 -17
  118. package/scripts/validate-vendor-online.js +13 -44
  119. package/scripts/verify-shipped-tarball.js +35 -141
@@ -1,72 +1,33 @@
1
1
  "use strict";
2
2
 
3
3
  /**
4
- * lib/collectors/scan-excludes.js
5
- *
6
- * Shared directory-walk exclusion policy for every code-scope collector.
7
- *
8
- * Before this existed, each collector hard-coded its own exclude Set. None
9
- * skipped agent/editor worktree copies (`.claude/worktrees/`), so a tree
10
- * holding N detached worktrees scanned each one as a full repo — inflating
11
- * hit counts to N+1 duplicates of the same source and forcing manual dedup.
12
- *
13
- * Two layers:
14
- * 1. NAME exclusions — directory basenames never worth descending into
15
- * (dependency caches, build output, VCS metadata, agent scratch).
16
- * 2. A LINKED-WORKTREE predicate — a directory that is itself a git
17
- * worktree distinct from the scan root. A `git worktree add` target
18
- * carries a `.git` *file* (a gitdir pointer) rather than a `.git`
19
- * directory; agent tools frequently stamp full repo copies under
20
- * `.claude/worktrees/`. Skipping these keeps a scan to the one tree
21
- * the operator actually pointed at.
22
- *
23
- * Collectors apply both: spread DEFAULT_CODE_EXCLUDES into their exclude
24
- * Set, and call isLinkedWorktreeDir(fullPath) before descending into a
25
- * subdirectory.
4
+ * Shared directory-walk exclusion policy for every code-scope collector: a set of
5
+ * directory basenames never worth descending into, and a predicate for linked git
6
+ * worktrees, which rescan the host tree's source and multiply every hit.
7
+ * Collectors spread DEFAULT_CODE_EXCLUDES into their exclude Set and call
8
+ * isLinkedWorktreeDir(fullPath) before descending.
26
9
  */
27
10
 
28
11
  const fs = require("node:fs");
29
12
  const path = require("node:path");
30
13
 
31
- // Directory basenames excluded from every code-scope walk. Superset of the
32
- // per-collector lists that predated this module, plus agent/editor scratch
33
- // (`.claude`) and additional dependency/build caches that only ever hold
34
- // generated or third-party content.
35
14
  const DEFAULT_CODE_EXCLUDES = Object.freeze([
36
- // VCS + agent/editor scratch
37
15
  ".git", ".hg", ".svn", ".claude", ".idea", ".vscode",
38
- // dependency trees / package caches
39
16
  "node_modules", ".pnpm-store", "bower_components",
40
17
  ".venv", "venv", "__pycache__", ".pytest_cache", ".mypy_cache",
41
18
  ".tox", ".gradle", ".m2",
42
- // build output
43
19
  "dist", "build", "out", "target", "coverage",
44
20
  ".next", ".nuxt", ".svelte-kit", ".turbo", ".cache",
45
21
  ]);
46
22
 
47
- /**
48
- * Build an exclude Set for a collector. Pass any collector-specific extra
49
- * basenames; they are merged with the shared defaults.
50
- *
51
- * @param {Iterable<string>} [extra] additional basenames to exclude
52
- * @returns {Set<string>}
53
- */
54
23
  function codeExcludeSet(extra = []) {
55
24
  return new Set([...DEFAULT_CODE_EXCLUDES, ...extra]);
56
25
  }
57
26
 
58
27
  /**
59
- * True when `dir` is a git worktree linked to a repository elsewhere — i.e.
60
- * its `.git` entry is a file (a `gitdir: …` pointer) rather than a directory.
61
- * These are detached copies created by `git worktree add` (commonly under
62
- * `.claude/worktrees/<id>/`); descending into them rescans unrelated repo
63
- * state. A normal repo root has a `.git` *directory* and is NOT skipped.
64
- *
65
- * Cheap and synchronous: one lstat. Returns false on any error so a walk
66
- * never aborts on a permission/race issue.
67
- *
68
- * @param {string} dir absolute path to a candidate directory
69
- * @returns {boolean}
28
+ * True when `dir` is a git worktree linked elsewhere — its `.git` entry is a file
29
+ * (a `gitdir: …` pointer), where a normal repo root has a `.git` *directory* and
30
+ * is NOT skipped. Returns false on any error, so a walk never aborts on a race.
70
31
  */
71
32
  function isLinkedWorktreeDir(dir) {
72
33
  try {
@@ -79,46 +40,14 @@ function isLinkedWorktreeDir(dir) {
79
40
  }
80
41
 
81
42
  /**
82
- * Shared cwd-tree walker for every code-scope collector. Returns one entry
83
- * per regular file as `{ full, rel, name }` — the exact shape the four
84
- * collectors (secrets / crypto-codebase / containers / citation-hygiene)
85
- * built with their own near-identical local walkers.
86
- *
87
- * Behavior preserved from those walkers:
88
- * - Directory basenames in `excludes` are never descended into.
89
- * - A linked git worktree (its `.git` is a gitdir-pointer FILE) is never
90
- * descended into (isLinkedWorktreeDir).
91
- * - Recursion follows ONLY real directories (`entry.isDirectory()`).
92
- * Symlinks are never traversed and symlink targets are never emitted
93
- * as files — matching the old behavior where a Dirent for a symlink is
94
- * neither isDirectory() nor isFile(), so it fell through both branches.
95
- * - Depth is capped at `maxDepth`.
96
- *
97
- * Performance: `fs.realpathSync` is called ONLY on directories and symlinks
98
- * (the only entries that can introduce a traversal cycle), never on regular
99
- * files. The old walkers realpath'd every file entry, which on a large repo
100
- * dominated walk time. A regular file cannot create a directory cycle, so
101
- * dropping its realpath preserves cycle protection exactly while removing
102
- * the per-file syscall. The symlink-cycle `seen` set still guards every
103
- * directory and symlink by canonical path.
104
- *
105
- * Depth-cap visibility: a subtree pruned because it would exceed `maxDepth`
106
- * is otherwise an invisible coverage gap — the files inside are never emitted
107
- * and the caller cannot tell "scanned, nothing there" from "never scanned".
108
- * Pass `opts.truncations` (an array) to have the walker push one
109
- * `{ rel, depth }` entry per directory whose contents were skipped for being
110
- * beyond the cap, so a content collector can surface a `depth_capped` notice
111
- * symmetric with its per-file size-cap reporting. The return value is
112
- * unchanged (the file array); callers that don't pass `truncations` are
113
- * unaffected.
43
+ * Shared cwd-tree walker for every code-scope collector. Returns one entry per
44
+ * regular file as `{ full, rel, name }`, `rel` forward-slashed on every platform.
45
+ * Directory basenames in `excludes` and linked git worktrees are never descended
46
+ * into, recursion follows only real directories — a symlink is neither traversed
47
+ * nor emitted — and depth is capped at `maxDepth`.
114
48
  *
115
- * @param {string} root absolute scan root
116
- * @param {object} [opts]
117
- * @param {number} [opts.maxDepth] max recursion depth (collector-specific)
118
- * @param {Set<string>} [opts.excludes] directory basenames to skip
119
- * @param {Array<{rel:string, depth:number}>} [opts.truncations] out-param the
120
- * walker appends to when a directory is pruned for exceeding maxDepth
121
- * @returns {Array<{full:string, rel:string, name:string}>}
49
+ * `opts.truncations`, when an array, is an out-param: one `{ rel, depth }` per
50
+ * directory pruned for exceeding the cap. The return value is the file array.
122
51
  */
123
52
  function walkTree(root, opts = {}) {
124
53
  const maxDepth = opts.maxDepth ?? 6;
@@ -137,21 +66,14 @@ function walkTree(root, opts = {}) {
137
66
  const full = path.join(dir, entry.name);
138
67
 
139
68
  if (entry.isDirectory()) {
140
- // Cycle guard: canonicalize directories (and symlinks below) so a
141
- // symlinked or bind-mounted loop is visited at most once. realpath
142
- // here is bounded by directory count, not file count.
69
+ // Cycle guard: canonicalize so a symlinked or bind-mounted loop is
70
+ // visited at most once.
143
71
  let real;
144
72
  try { real = fs.realpathSync(full); } catch { continue; }
145
73
  if (seen.has(real)) continue;
146
74
  seen.add(real);
147
- // Never descend into a linked git worktree (its `.git` is a gitdir
148
- // pointer file) — agent tooling stamps full repo copies under
149
- // `.claude/worktrees/<id>/`; walking them rescans the same source
150
- // as the host tree and multiplies every hit.
151
75
  if (isLinkedWorktreeDir(full)) continue;
152
- // Descending would land at depth+1 > maxDepth: the directory's
153
- // contents are about to be dropped. Record the prune so the caller
154
- // can report the unscanned subtree instead of silently missing it.
76
+ // Checked before descending, so the pruned directory can be recorded.
155
77
  if (depth + 1 > maxDepth) {
156
78
  if (truncations) {
157
79
  truncations.push({
@@ -163,19 +85,13 @@ function walkTree(root, opts = {}) {
163
85
  }
164
86
  walk(full, depth + 1);
165
87
  } else if (entry.isSymbolicLink()) {
166
- // A symlink Dirent is neither isDirectory() nor isFile(). The old
167
- // walkers realpath'd it (adding the target to `seen`) and then
168
- // emitted nothing, because neither branch matched. Preserve that:
169
- // canonicalize for the cycle guard, never emit, never traverse.
170
- try { fs.realpathSync(full); } catch { /* dangling link — ignore */ }
88
+ // A symlink Dirent is neither isDirectory() nor isFile(), so it is
89
+ // never traversed and never emitted — including a dangling one.
90
+ continue;
171
91
  } else if (entry.isFile()) {
172
- // Regular files cannot introduce a directory cycle, so no realpath /
173
- // `seen` check is needed. This is the hot path — it runs once per
174
- // file in the tree and is now syscall-free beyond the parent
175
- // readdir.
176
- // Emit forward-slash rel paths on every platform so artifact
177
- // summaries match the SARIF evidence_locations (which normalize the
178
- // same way) — on Windows path.relative returns backslash separators.
92
+ // A regular file cannot introduce a directory cycle, so no realpath /
93
+ // `seen` check. Windows path.relative returns backslashes, so rel is
94
+ // normalized to match SARIF locations.
179
95
  out.push({ full, rel: path.relative(root, full).split(path.sep).join("/"), name: entry.name });
180
96
  }
181
97
  }
@@ -184,27 +100,15 @@ function walkTree(root, opts = {}) {
184
100
  return out;
185
101
  }
186
102
 
187
- // Per-indicator cap so a flood of identical findings can't bloat a SARIF
188
- // upload. 50 locations per result is well within GitHub code-scanning's
189
- // rendering budget while still showing the operator where every distinct
190
- // finding lives.
103
+ // Per-indicator cap: 50 sits well inside GitHub code-scanning's rendering budget.
191
104
  const MAX_EVIDENCE_LOCATIONS_PER_INDICATOR = 50;
192
105
 
193
106
  /**
194
- * Turn a collector's per-indicator file hits into the `evidence_locations`
195
- * entry the runner threads onto a firing indicator for SARIF
196
- * `results[].locations`. Each hit contributes a `{ uri, startLine? }` where
197
- * `uri` is the repo-relative file path the collector already recorded and
198
- * `startLine` is included only when a real (non-placeholder) line number is
199
- * present — many collectors store `line: 0` to mean "file-level, no line",
200
- * which omits the region so SARIF points at the file rather than line 0.
201
- *
202
- * Identical `{ uri, startLine }` entries are de-duplicated and the list is
203
- * capped at MAX_EVIDENCE_LOCATIONS_PER_INDICATOR.
204
- *
205
- * @param {Array<object>} hits entries with a `rel` or `file` repo-relative
206
- * path and an optional `line` number
207
- * @returns {Array<{uri:string,startLine?:number}>}
107
+ * Turns a collector's per-indicator file hits into `evidence_locations` for SARIF
108
+ * `results[].locations`. Each hit yields `{ uri, startLine? }` from its `rel` (or
109
+ * `file`) repo-relative path; `startLine` is emitted only above zero, because
110
+ * collectors store `line: 0` to mean "file-level, no line". Identical entries
111
+ * de-duplicate; the list caps at MAX_EVIDENCE_LOCATIONS_PER_INDICATOR.
208
112
  */
209
113
  function buildEvidenceLocations(hits) {
210
114
  if (!Array.isArray(hits) || hits.length === 0) return [];
@@ -227,19 +131,9 @@ function buildEvidenceLocations(hits) {
227
131
  }
228
132
 
229
133
  /**
230
- * Convert a byte/char offset into a file's text to a 1-based line number.
231
- * Content-regex collectors record the offset of a match (`m.index`) but not
232
- * a line, which left `buildEvidenceLocations` emitting a file-level location
233
- * with no region. Pairing the offset with this helper lets SARIF point at the
234
- * exact line.
235
- *
236
- * Returns 1 for offset 0 / a non-finite offset (file-level fallback at the
237
- * first line). Counts `\n` occurrences before the offset; `\r\n` is handled
238
- * because the `\n` is what increments the line.
239
- *
240
- * @param {string} content the file text the offset indexes into
241
- * @param {number} offset 0-based offset of the match within `content`
242
- * @returns {number} 1-based line number
134
+ * Converts a 0-based offset into `content` to a 1-based line number, so a
135
+ * content-regex collector can pair a match's `m.index` with a SARIF region.
136
+ * Returns 1 for offset 0 or a non-finite offset. Counts `\n`, covering `\r\n`.
243
137
  */
244
138
  function lineFromOffset(content, offset) {
245
139
  if (typeof content !== "string") return 1;