@vibe-agent-toolkit/agent-skills 0.2.0-rc.5 → 0.2.0-rc.7

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 (79) hide show
  1. package/dist/content-type-routing.d.ts +32 -0
  2. package/dist/content-type-routing.d.ts.map +1 -1
  3. package/dist/content-type-routing.js +23 -0
  4. package/dist/content-type-routing.js.map +1 -1
  5. package/dist/index.d.ts +7 -4
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +14 -3
  8. package/dist/index.js.map +1 -1
  9. package/dist/parsers/frontmatter-parser.d.ts.map +1 -1
  10. package/dist/parsers/frontmatter-parser.js +30 -6
  11. package/dist/parsers/frontmatter-parser.js.map +1 -1
  12. package/dist/plugin-distribution-layout.d.ts.map +1 -1
  13. package/dist/plugin-distribution-layout.js +13 -0
  14. package/dist/plugin-distribution-layout.js.map +1 -1
  15. package/dist/post-build-checks.d.ts +37 -0
  16. package/dist/post-build-checks.d.ts.map +1 -1
  17. package/dist/post-build-checks.js +40 -0
  18. package/dist/post-build-checks.js.map +1 -1
  19. package/dist/schemas/allowed-tools.d.ts +31 -0
  20. package/dist/schemas/allowed-tools.d.ts.map +1 -0
  21. package/dist/schemas/allowed-tools.js +88 -0
  22. package/dist/schemas/allowed-tools.js.map +1 -0
  23. package/dist/schemas/index.d.ts +1 -0
  24. package/dist/schemas/index.d.ts.map +1 -1
  25. package/dist/schemas/index.js +1 -0
  26. package/dist/schemas/index.js.map +1 -1
  27. package/dist/schemas/marketplace-manifest.d.ts.map +1 -1
  28. package/dist/schemas/marketplace-manifest.js +17 -6
  29. package/dist/schemas/marketplace-manifest.js.map +1 -1
  30. package/dist/skill-identity.d.ts +10 -0
  31. package/dist/skill-identity.d.ts.map +1 -1
  32. package/dist/skill-identity.js +12 -0
  33. package/dist/skill-identity.js.map +1 -1
  34. package/dist/skill-packager.d.ts +85 -13
  35. package/dist/skill-packager.d.ts.map +1 -1
  36. package/dist/skill-packager.js +178 -32
  37. package/dist/skill-packager.js.map +1 -1
  38. package/dist/skill-test/eval-fragment.d.ts +31 -29
  39. package/dist/skill-test/eval-fragment.d.ts.map +1 -1
  40. package/dist/skill-test/eval-fragment.js +5 -3
  41. package/dist/skill-test/eval-fragment.js.map +1 -1
  42. package/dist/skill-test/eval-grader.d.ts +8 -2
  43. package/dist/skill-test/eval-grader.d.ts.map +1 -1
  44. package/dist/skill-test/eval-grader.js +3 -6
  45. package/dist/skill-test/eval-grader.js.map +1 -1
  46. package/dist/validators/agent-instruction-presence.d.ts.map +1 -1
  47. package/dist/validators/agent-instruction-presence.js +44 -1
  48. package/dist/validators/agent-instruction-presence.js.map +1 -1
  49. package/dist/validators/compat-detectors.d.ts +17 -0
  50. package/dist/validators/compat-detectors.d.ts.map +1 -1
  51. package/dist/validators/compat-detectors.js +46 -19
  52. package/dist/validators/compat-detectors.js.map +1 -1
  53. package/dist/validators/marketplace-validator.d.ts.map +1 -1
  54. package/dist/validators/marketplace-validator.js +6 -0
  55. package/dist/validators/marketplace-validator.js.map +1 -1
  56. package/dist/validators/mcp-tool-qualification.d.ts +158 -0
  57. package/dist/validators/mcp-tool-qualification.d.ts.map +1 -0
  58. package/dist/validators/mcp-tool-qualification.js +467 -0
  59. package/dist/validators/mcp-tool-qualification.js.map +1 -0
  60. package/dist/validators/packaged-size-limit.d.ts +296 -0
  61. package/dist/validators/packaged-size-limit.d.ts.map +1 -0
  62. package/dist/validators/packaged-size-limit.js +494 -0
  63. package/dist/validators/packaged-size-limit.js.map +1 -0
  64. package/dist/validators/packaging-validator.d.ts +41 -2
  65. package/dist/validators/packaging-validator.d.ts.map +1 -1
  66. package/dist/validators/packaging-validator.js +159 -15
  67. package/dist/validators/packaging-validator.js.map +1 -1
  68. package/dist/validators/referenced-path-missing.d.ts +211 -0
  69. package/dist/validators/referenced-path-missing.d.ts.map +1 -0
  70. package/dist/validators/referenced-path-missing.js +379 -0
  71. package/dist/validators/referenced-path-missing.js.map +1 -0
  72. package/dist/validators/skill-validator.d.ts.map +1 -1
  73. package/dist/validators/skill-validator.js +29 -0
  74. package/dist/validators/skill-validator.js.map +1 -1
  75. package/dist/validators/types.d.ts +14 -0
  76. package/dist/validators/types.d.ts.map +1 -1
  77. package/dist/walk-link-graph.js +26 -7
  78. package/dist/walk-link-graph.js.map +1 -1
  79. package/package.json +7 -7
@@ -0,0 +1,494 @@
1
+ /**
2
+ * PACKAGED_SIZE_EXCEEDS_API_LIMIT — the packaged skill is too large to upload to
3
+ * the Anthropic Skills API.
4
+ *
5
+ * ## Why this needs its own detector rather than a threshold on an existing one
6
+ *
7
+ * VAT ships two checks whose names suggest they already answer this, and neither
8
+ * can:
9
+ *
10
+ * - `SKILL_TOTAL_SIZE_LARGE` counts **lines** across bundled markdown (≤2000).
11
+ * - `SKILL_TOO_MANY_FILES` counts **files** (≤6).
12
+ *
13
+ * The shape that actually blocks a publish is one large binary, which has no
14
+ * lines and is one file. The measured instance is a 35.7 MB `.wasm` runtime
15
+ * bundled by three skills in a single adopter marketplace: over the ceiling on
16
+ * its own, before a byte of markdown counts, and invisible to both checks above.
17
+ * Before this module there was no byte measurement anywhere in the validators.
18
+ *
19
+ * ## Why the message names the largest files
20
+ *
21
+ * A total alone ("34.9 MB, limit 30 MB") tells an author they have a problem and
22
+ * not where it is; a bundle at that size has hundreds of files and the true cause
23
+ * is nearly always one or two of them. The adopter who reported this built the
24
+ * same thing locally and said naming the largest files "is the thing that made
25
+ * the cause obvious in one line". So the finding leads with the offenders.
26
+ *
27
+ * ## Built phase only
28
+ *
29
+ * The bytes that matter are the bytes that ship, and a `files:` config entry
30
+ * materializes files at build time — a source tree can lack the very artifact
31
+ * that blows the ceiling. Measuring the source directory would answer a
32
+ * different question and get it wrong in both directions.
33
+ *
34
+ * ## This module is the ONLY thing that stats the packaged bytes
35
+ *
36
+ * That is why an entry it cannot weigh gets a receipt rather than a zero — see
37
+ * {@link walkBundle}. No other validator revisits these files, so a silent zero
38
+ * here is a silent zero everywhere.
39
+ */
40
+ import { readdirSync, statSync } from 'node:fs';
41
+ import { CODE_REGISTRY } from '@vibe-agent-toolkit/schema';
42
+ import { safePath } from '@vibe-agent-toolkit/utils';
43
+ /**
44
+ * The Skills API's refusal ceiling, in bytes. The vendor documents it as "Total
45
+ * upload size must be under 30 MB (uncompressed)" and the 413 says "requests up
46
+ * to 30MBs", neither of which says whether MB means 10^6 or 2^20 — a 1.4 MB gap.
47
+ *
48
+ * **This is measured, not read.** VAT first assumed decimal (30,000,000) as the
49
+ * conservative reading. Uploading against the live API disproved it:
50
+ *
51
+ * | Bundle content bytes | Result |
52
+ * |---|---|
53
+ * | 30,700,000 | **accepted** — above decimal 30 MB, so decimal is wrong |
54
+ * | 31,500,000 | 413 refused |
55
+ * | 35,900,338 | 413 refused |
56
+ *
57
+ * That brackets the ceiling to (30,700,000, 31,500,000], and 2^20 × 30 =
58
+ * 31,457,280 is the only round number in the window. So the limit is mebibytes,
59
+ * and the old decimal constant warned ~1.46 MB early — a false-positive band on
60
+ * every bundle between 30.0 and 31.4 MB, which is exactly where a big bundle
61
+ * lands.
62
+ *
63
+ * ## What this counts versus what the API counts
64
+ *
65
+ * The API measures the REQUEST. This check measures the bundle's file bytes,
66
+ * because at build time there is no request to weigh — see
67
+ * {@link checkPackagedSizeLimit} for why that is left as it is rather than
68
+ * modelled.
69
+ *
70
+ * The gap between the two is not small. `buildMultipartFormData` frames every
71
+ * file part with a 51-byte boundary line, a `Content-Disposition` header of
72
+ * 61 bytes plus the filename, a 42-byte content-type-and-blank-line, and a
73
+ * 2-byte trailing CRLF — **156 bytes plus the filename's length, per file** —
74
+ * and closes the body with a 53-byte terminator. Measured: one part with a
75
+ * 14-character filename costs 170 bytes of framing, and a 1,000-file bundle
76
+ * with `resources/`-shaped names costs ~185,000. So a bundle whose FILES sit
77
+ * just under the ceiling can be a REQUEST comfortably over it. An earlier
78
+ * version of this comment put the residue at "a few hundred bytes… roughly a
79
+ * kilobyte", which understates it by two orders of magnitude on a many-file
80
+ * bundle.
81
+ *
82
+ * The uploader therefore weighs the body it built rather than the sum of its
83
+ * file bytes, and says so in its own message ({@link OversizeMeasure}). Neither
84
+ * lane pads its threshold with a fudge factor: an invented margin is a number
85
+ * nobody can re-derive.
86
+ *
87
+ * ## The two lanes compare DIFFERENTLY, and that is the correct answer
88
+ *
89
+ * The ceiling is INCLUSIVE, measured: a request body of exactly 31,457,280 bytes
90
+ * (31,456,735 file bytes plus 545 of framing) returned `status: success`, and one
91
+ * byte more returned `413`. So:
92
+ *
93
+ * - **Request bytes: `>` refuses.** The uploader knows the exact body and must
94
+ * not refuse one the API would take.
95
+ * - **File bytes: `>=` refuses** — this lane, {@link checkPackagedSizeLimit}.
96
+ * Framing is never zero, so files totalling exactly the limit ALWAYS frame up
97
+ * into a request larger than the limit and earn a real 413.
98
+ *
99
+ * An earlier version of this comment claimed "both fire AT the ceiling rather
100
+ * than only above it" and offered that as the conservative reading. It was
101
+ * reasoned, never measured, and the measurement above refutes it for the request
102
+ * lane. Do not harmonise the two operators: they are applied to different
103
+ * quantities and each is right about the one it can see.
104
+ *
105
+ * ## If the bracket ever moves, these surfaces do NOT follow automatically
106
+ *
107
+ * Nothing derives the prose from this constant, so a re-measurement is a
108
+ * multi-file edit and the list belongs here, next to the number, rather than in
109
+ * whichever file the next maintainer happens to open:
110
+ *
111
+ * 1. This constant.
112
+ * 2. `docs/validation-codes.md`, `PACKAGED_SIZE_EXCEEDS_API_LIMIT` — several
113
+ * bullets restate both the MiB figure and the exact byte count.
114
+ * 3. The `PACKAGED_SIZE_EXCEEDS_API_LIMIT` entry in
115
+ * `packages/schema/src/validation-codes.ts` (its `description`).
116
+ * 4. The `vat-skill-review` skill's checklist item for the same code.
117
+ *
118
+ * The two `vat claude org skills` help texts interpolate this constant and need
119
+ * no edit. `CHANGELOG.md` is history and is never restated.
120
+ *
121
+ * @vendor-claim reviewed=2026-09-06 verify=Re-measure rather than re-read — the docs do not state the unit. Upload two skill bundles via `vat claude org skills install`, one just under and one just over this constant, and confirm the first is accepted and the second returns 413. Adjust only if the bracket moves; if it does, update the four surfaces listed above.
122
+ */
123
+ export const API_SKILL_MAX_UPLOAD_BYTES = 31_457_280;
124
+ /**
125
+ * Directory names that never reach the Skills API, so their bytes never count
126
+ * toward its ceiling.
127
+ *
128
+ * 🔑 **This must stay identical to the uploader's exclusion set**, because the
129
+ * two halves apply the SAME constant and the SAME sentence and must therefore
130
+ * apply them to the same file set. They did not: the walk summed everything
131
+ * under the output directory while `vat claude org skills install` dropped these
132
+ * three directories from the multipart body, so a bundle of 31.0 MB of shipped
133
+ * content plus a 1.0 MB `evals/` fired at build and uploaded fine — a false
134
+ * positive on the only lane that shows the finding to the AUTHOR, which is the
135
+ * lane whose credibility the check depends on.
136
+ *
137
+ * The case is reachable and documented into existence: a root `evals/` holding
138
+ * no `evals.json` is ordinary content, so `vat build` packages it and the
139
+ * uploader drops it unconditionally.
140
+ *
141
+ * The exclusion is by DIRECTORY, so a regular FILE named `evals` is weighed here
142
+ * because it is sent there. The uploader spells its own test
143
+ * `directoryLike && NEVER_UPLOADED_DIR_NAMES.has(entry.name)`, where
144
+ * `directoryLike` is a real directory OR a symbolic link resolving to one — this
145
+ * walk cannot descend a linked directory at all (`Dirent.isDirectory()` is
146
+ * lstat-based), so it weighs one as zero and files a `SCAN_PATH_UNREADABLE`
147
+ * receipt for it. Both lanes therefore leave a linked `evals` out of the total;
148
+ * they differ only in whether they say so.
149
+ *
150
+ * ⚠️ ONE asymmetry remains, and it can only under-count: the uploader also
151
+ * withholds the skill's DECLARED eval-suite unit, which it resolves from the
152
+ * governing VAT config asynchronously. This walk is synchronous and knows only
153
+ * an output directory. In a built bundle that unit is normally absent anyway —
154
+ * the packager drops declared test input on the way in and reports what it
155
+ * dropped — so the residue is a bundle weighed slightly heavy, never light.
156
+ */
157
+ export const NEVER_UPLOADED_DIR_NAMES = new Set(['evals', 'node_modules', '.git']);
158
+ /** Naive English pluralisation; every noun this file counts is regular. */
159
+ function plural(noun, count) {
160
+ return count === 1 ? noun : `${noun}s`;
161
+ }
162
+ /** How many offenders the message names before it stops listing. */
163
+ const LARGEST_FILES_NAMED = 3;
164
+ /**
165
+ * Binary units, because the only threshold any of these numbers is read against
166
+ * is binary: {@link API_SKILL_MAX_UPLOAD_BYTES} is exactly 30 × 2^20, measured.
167
+ * Rendered in decimal, a total and the ceiling it is compared to could not be
168
+ * compared as written — "51.7 MB … over the 30 MiB ceiling" makes the reader do
169
+ * the conversion before they know how far over they are. The base and the labels
170
+ * move together: a function dividing by 1024 must not print "MB".
171
+ */
172
+ const BYTE_UNITS = ['KiB', 'MiB', 'GiB', 'TiB'];
173
+ /** Human-readable bytes, in the binary units the API's ceiling is expressed in. */
174
+ export function formatBytes(bytes) {
175
+ if (bytes < 1024)
176
+ return `${bytes} B`;
177
+ let value = bytes / 1024;
178
+ let unit = 0;
179
+ while (value >= 1024 && unit < BYTE_UNITS.length - 1) {
180
+ value /= 1024;
181
+ unit += 1;
182
+ }
183
+ // `?? ''` is unreachable — `unit` is bounded by the loop condition — but
184
+ // noUncheckedIndexedAccess types the read as possibly-undefined and a
185
+ // non-null assertion would hide a real bug if that bound ever moved.
186
+ return `${value.toFixed(1)} ${BYTE_UNITS[unit] ?? ''}`.trimEnd();
187
+ }
188
+ /**
189
+ * A size AND its exact byte count, for the two numbers a refusal compares.
190
+ *
191
+ * {@link formatBytes} rounds to one decimal, so a bundle one byte over the
192
+ * ceiling read `30.0 MiB … over the 30.0 MiB ceiling` — a sentence that refutes
193
+ * itself and leaves an author with no way to know how much to cut. Rounded for
194
+ * scanning, exact for acting on; the ceiling is quoted the same way so the two
195
+ * can actually be compared as written.
196
+ */
197
+ function sizeWithExactBytes(bytes) {
198
+ return `${formatBytes(bytes)} (${bytes.toLocaleString('en-US')} bytes)`;
199
+ }
200
+ /** Whatever the platform gave us, as text a reader can act on. */
201
+ function causeOf(error) {
202
+ return error instanceof Error ? error.message : String(error);
203
+ }
204
+ /** A path as the finding spells it: bundle-relative POSIX, and `.` for the root. */
205
+ function bundleRelative(root, target) {
206
+ return safePath.relative(root, target) || '.';
207
+ }
208
+ /**
209
+ * The half of `SCAN_PATH_UNREADABLE`'s registry `fix` that is FALSE for every
210
+ * entry this walk skips by policy, stated once so the two remedies below cannot
211
+ * drift from each other. See {@link UnweighedEntry.fix}.
212
+ */
213
+ const NOT_A_PERMISSION_PROBLEM = "Nothing about this entry's permissions is wrong, and `--exclude` is an `audit` flag "
214
+ + 'that the `vat build` / `vat skills package` lane this check runs in does not accept.';
215
+ /** Remedy for a symlink: skipped because the UPLOADER refuses it, not because it is unreadable. */
216
+ const SYMLINK_SKIPPED_FIX = 'Replace the link with a real copy of the file if the skill needs it, or remove it from the '
217
+ + 'packaged output — the uploader refuses symbolic links outright, so this entry can never '
218
+ + `contribute bytes to a published skill. ${NOT_A_PERMISSION_PROBLEM}`;
219
+ /** Remedy for a device, socket or FIFO: nothing to weigh and nothing that can be uploaded. */
220
+ const NOT_A_REGULAR_FILE_FIX = 'Remove it from the packaged output — a device, socket or FIFO has no bytes to weigh and '
221
+ + 'cannot be uploaded, and one reaching a build output almost always came from a source tree '
222
+ + `that should not have contained it. ${NOT_A_PERMISSION_PROBLEM}`;
223
+ /**
224
+ * Every regular file under `root` with its size, plus every entry whose size
225
+ * could NOT be established.
226
+ *
227
+ * ## Why "could not weigh it" is a return value and not a zero
228
+ *
229
+ * Every unreadable entry used to be swallowed into `[]`/`undefined` and summed
230
+ * as ZERO. That under-counts in exactly the direction that produces a clean bill
231
+ * of health: `statSync` failing on the 35.7 MB `.wasm` (an ELOOP symlink cycle, a
232
+ * Windows file lock, a file removed between readdir and stat) took the total from
233
+ * ~36 MB to ~2 MB, the build reported no warnings, and the upload ate a 413
234
+ * eleven seconds later — precisely the outcome this module exists to prevent.
235
+ *
236
+ * Nothing else was going to say so. `RESOURCE_UNREADABLE` — which an earlier
237
+ * version of this comment claimed "already owns 'VAT could not read this'" — is
238
+ * emitted by `walk-link-graph.ts` over the SOURCE link graph and never stats the
239
+ * packaged output. This walk is the only thing that touches these bytes, so the
240
+ * receipt is its own to emit (`SCAN_PATH_UNREADABLE`, warning: a size check must
241
+ * not be the thing that fails a build, but it must never be silent about what it
242
+ * could not put on the scale).
243
+ *
244
+ * ## Symlinks, exactly
245
+ *
246
+ * **No symlink is weighed, whatever it resolves to** — each becomes an unweighed
247
+ * entry naming the link, and the receipt says the uploader refuses it outright.
248
+ *
249
+ * ⚠️ An earlier version of this comment defended measuring a linked FILE
250
+ * *through* the link — "a link resolving to a 30 MB file contributes 30 MB,
251
+ * which matches the uploader, whose `readFileSync` reads the target's bytes."
252
+ * The two lanes did agree, and both were wrong: reading through the link is how
253
+ * `notes.md -> /etc/passwd` got published into a shared workspace. The uploader
254
+ * now refuses any symlink, so there is no such upload to weigh, and pricing one
255
+ * here would put a number on bytes that can never be sent.
256
+ *
257
+ * A symlinked DIRECTORY was never walked and contributed nothing, because
258
+ * `Dirent.isDirectory()` is lstat-based and answers `false` for it. Now it is
259
+ * reported for the same reason as the linked file, alongside any other
260
+ * non-regular entry (device, socket, FIFO) and any dangling link.
261
+ */
262
+ function walkBundle(root) {
263
+ const acc = { files: [], unweighed: [] };
264
+ const stack = [root];
265
+ while (stack.length > 0) {
266
+ const dir = stack.pop();
267
+ if (dir === undefined)
268
+ break;
269
+ for (const dirent of readDirOrRecord(dir, root, acc)) {
270
+ const full = safePath.join(dir, dirent.name);
271
+ if (dirent.isDirectory()) {
272
+ if (!NEVER_UPLOADED_DIR_NAMES.has(dirent.name))
273
+ stack.push(full);
274
+ continue;
275
+ }
276
+ if (dirent.isSymbolicLink()) {
277
+ acc.unweighed.push({
278
+ path: bundleRelative(root, full),
279
+ reason: 'it is a symbolic link, which the uploader refuses rather than following — '
280
+ + 'no byte of its target can be sent, so none is weighed here',
281
+ fix: SYMLINK_SKIPPED_FIX,
282
+ });
283
+ continue;
284
+ }
285
+ weighEntry(full, bundleRelative(root, full), acc);
286
+ }
287
+ }
288
+ return acc;
289
+ }
290
+ /** Directory entries, recording a receipt when the directory cannot be read. */
291
+ function readDirOrRecord(dir, root, acc) {
292
+ try {
293
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- caller-supplied packaged output dir
294
+ return readdirSync(dir, { withFileTypes: true });
295
+ }
296
+ catch (error) {
297
+ acc.unweighed.push({
298
+ path: bundleRelative(root, dir),
299
+ reason: `VAT could not read the directory (${causeOf(error)})`,
300
+ });
301
+ return [];
302
+ }
303
+ }
304
+ /** Record one non-directory entry: its size, or why it has none VAT can use. */
305
+ function weighEntry(full, rel, acc) {
306
+ let stats;
307
+ try {
308
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- as above
309
+ stats = statSync(full);
310
+ }
311
+ catch (error) {
312
+ acc.unweighed.push({ path: rel, reason: `VAT could not stat it (${causeOf(error)})` });
313
+ return;
314
+ }
315
+ if (stats.isFile()) {
316
+ acc.files.push({ path: rel, bytes: stats.size });
317
+ return;
318
+ }
319
+ acc.unweighed.push({
320
+ path: rel,
321
+ // Symbolic links never reach here — they are caught by the walk, which does
322
+ // not follow one. What is left is a device, a socket or a FIFO.
323
+ reason: 'it is not a regular file (a device, a socket, or a FIFO)',
324
+ fix: NOT_A_REGULAR_FILE_FIX,
325
+ });
326
+ }
327
+ /**
328
+ * The bundle's files, heaviest first — the order both the message and the anchor
329
+ * want — with EQUAL sizes broken by path, in code-point order.
330
+ *
331
+ * 🔑 The tiebreak is not cosmetic: the heaviest file becomes the finding's `link`,
332
+ * and `link` is the key an adopter's `validation.allow` entry matches. Sorting on
333
+ * bytes alone left ties in the order `readdir` happened to return them, so two
334
+ * equal-sized files — a vendored runtime shipped twice, which is exactly the shape
335
+ * that motivated this check — could swap between a developer's macOS box and a
336
+ * Linux CI runner. The waiver then matched locally, missed in CI, and was reported
337
+ * `ALLOW_UNUSED` in the run that needed it.
338
+ *
339
+ * The comparator is explicit code points rather than `localeCompare`, for the same
340
+ * reason `walkableChildren` in the sibling module gives: the order only has to be
341
+ * the SAME everywhere, and `localeCompare` is locale- and ICU-dependent.
342
+ */
343
+ function largestFirst(files) {
344
+ return [...files].sort((a, b) => b.bytes - a.bytes || (a.path < b.path ? -1 : Number(a.path > b.path)));
345
+ }
346
+ /**
347
+ * The one sentence describing an over-ceiling upload, shared by the build-time
348
+ * validator and by `vat claude org skills install`'s pre-flight refusal.
349
+ *
350
+ * Both refuse for the same external reason, so neither owns the wording: the
351
+ * uploader's copy would otherwise drift from the validator's, and an author who
352
+ * met the warning at build would meet a differently-worded version of it at
353
+ * upload. It takes already-measured files rather than a directory because the
354
+ * uploader knows something the walker cannot — which files it EXCLUDED (the
355
+ * declared eval suite) — and must describe the bundle it is actually sending.
356
+ * The exclusions it can state up front are shared as
357
+ * {@link NEVER_UPLOADED_DIR_NAMES}, so both sides weigh the same file set.
358
+ *
359
+ * The two lanes differ in exactly one place, {@link OversizeMeasure}: the request
360
+ * variant reports the body it weighed AND the file bytes inside it, so the
361
+ * framing is visible as the difference rather than as an unexplained discrepancy.
362
+ */
363
+ export function describeOversizeBundle(files, measure, limitBytes = API_SKILL_MAX_UPLOAD_BYTES) {
364
+ const largest = largestFirst(files).slice(0, LARGEST_FILES_NAMED);
365
+ const named = largest.map(file => `${file.path} (${formatBytes(file.bytes)})`).join(', ');
366
+ const remainder = files.length - largest.length;
367
+ const tail = remainder > 0 ? `, and ${remainder} more ${plural('file', remainder)}` : '';
368
+ const across = `${files.length} ${plural('file', files.length)}`;
369
+ const fileBytes = files.reduce((sum, file) => sum + file.bytes, 0);
370
+ const subject = measure.of === 'upload-request'
371
+ ? `Upload request body is ${sizeWithExactBytes(measure.bytes)} — ${formatBytes(fileBytes)} `
372
+ + `of file content across ${across}, plus per-part multipart framing —`
373
+ : `Packaged skill is ${sizeWithExactBytes(measure.bytes)} across ${across},`;
374
+ return (`${subject} over the ${sizeWithExactBytes(limitBytes)} Anthropic Skills API ` +
375
+ `upload ceiling. Largest: ${named}${tail}`);
376
+ }
377
+ /**
378
+ * One receipt per entry the walk could not put on the scale.
379
+ *
380
+ * The `fix` is PER INSTANCE, falling back to the registry's only for the entries
381
+ * the registry's text actually describes — a readdir or stat FAILURE. Everything
382
+ * else here is a deliberate skip, and the registry advice ("make the path
383
+ * readable… or pass `--exclude`") sends its author to fix permissions on a
384
+ * healthy file with a flag this command does not have. See
385
+ * {@link UnweighedEntry.fix}.
386
+ */
387
+ function unweighedIssues(entries) {
388
+ const registryEntry = CODE_REGISTRY.SCAN_PATH_UNREADABLE;
389
+ return entries.map(entry => ({
390
+ severity: registryEntry.defaultSeverity,
391
+ code: 'SCAN_PATH_UNREADABLE',
392
+ message: `Not weighed against the ${formatBytes(API_SKILL_MAX_UPLOAD_BYTES)} Anthropic Skills API upload ceiling: `
393
+ + `${entry.reason}. The packaged size VAT measured is a LOWER BOUND.`,
394
+ location: entry.path,
395
+ fix: entry.fix ?? registryEntry.fix,
396
+ reference: registryEntry.reference,
397
+ }));
398
+ }
399
+ /**
400
+ * Emit `PACKAGED_SIZE_EXCEEDS_API_LIMIT` when the packaged bundle at `outputDir`
401
+ * is at or over the API upload ceiling, plus one `SCAN_PATH_UNREADABLE` for every
402
+ * entry whose bytes VAT could not establish.
403
+ *
404
+ * ## This weighs FILES, and deliberately not the request
405
+ *
406
+ * The API refuses on the size of the multipart REQUEST, and the uploader weighs
407
+ * exactly that — the body it is about to send. This lane cannot: there is no
408
+ * request at build time, and the framing is not a property of the bundle. Its
409
+ * per-part cost is `156 + len(filename)`, where the filename is not the packaged
410
+ * path but `<declared-name>/<packaged-path>` as the uploader keys it, and the
411
+ * body also carries whatever fields that endpoint takes — a `display_title` on
412
+ * create, none on a version add. Every one of those is decided at upload time.
413
+ *
414
+ * Adding an ESTIMATE of it here would mean re-deriving the multipart builder's
415
+ * arithmetic in a second place, where it would silently stop matching the first —
416
+ * which is the same defect as a fudge factor, only harder to spot. So this check
417
+ * measures what it can see, the message says "Packaged skill is …", and the
418
+ * uploader's says "Upload request body is …". They are different numbers on
419
+ * purpose, and a bundle that clears this one can still be refused there.
420
+ *
421
+ * One size issue for the bundle, not one per file: unlike a per-reference
422
+ * finding, the defect here IS the total — no single file is "the" problem unless
423
+ * it alone exceeds the ceiling, and emitting per file would hand an adopter a
424
+ * waiver granularity that cannot express the actual fix.
425
+ *
426
+ * ## The anchor, and why the largest file is `link`
427
+ *
428
+ * `location` is the bundle root (`.`) because the finding is about the bundle —
429
+ * but `.` is the SAME STRING for every skill in a multi-skill build, so an allow
430
+ * glob written against it waives all of them or none. That left one escape hatch,
431
+ * `severity.PACKAGED_SIZE_EXCEEDS_API_LIMIT: ignore`, which is project-wide: an
432
+ * adopter with 40 skills, one legitimately bundling a 32 MB runtime it never
433
+ * publishes to the API, had to blind themselves on the other 39.
434
+ *
435
+ * So the largest file rides as `link` — the same convention the rest of this lane
436
+ * uses (`referenced-path-missing`, `mcp-tool-qualification`), where `link` is the
437
+ * specific thing an instance is about and `location` stays the file you open.
438
+ * `applyAllowFilter` matches an allow glob against `location` OR `link`, so:
439
+ *
440
+ * ```yaml
441
+ * validation:
442
+ * allow:
443
+ * PACKAGED_SIZE_EXCEEDS_API_LIMIT:
444
+ * - paths: ["scripts/duckdb-eh.wasm"]
445
+ * reason: "Runtime this skill needs; never published to the Skills API."
446
+ * ```
447
+ *
448
+ * …waives exactly the bundles whose weight that file explains, and every other
449
+ * skill keeps the check.
450
+ *
451
+ * ⚠️ Two skills bundling the SAME oversized artifact share one anchor, so one
452
+ * entry waives both. That is the right grain for the case that produced this
453
+ * check (one `.wasm`, three skills, one decision), but it is NOT per-skill
454
+ * precision. Per-skill would need an anchor naming the skill, which this call
455
+ * site cannot mint: `location` must be a project-relative path and the packager
456
+ * passes only an absolute output directory. A caller that wants it should pass
457
+ * the bundle root as `issueLocation(outputPath, projectRoot)` and have this
458
+ * function use it in place of `.`.
459
+ *
460
+ * @param outputDir Absolute path to the packaged skill output.
461
+ * @param limitBytes Ceiling to compare against. Defaults to the documented API
462
+ * limit; parameterized so a test can drive it with a few kilobytes instead of
463
+ * writing 30 MB of fixture to disk.
464
+ */
465
+ export function checkPackagedSizeLimit(outputDir, limitBytes = API_SKILL_MAX_UPLOAD_BYTES) {
466
+ const { files, unweighed } = walkBundle(outputDir);
467
+ const issues = unweighedIssues(unweighed);
468
+ const total = files.reduce((sum, file) => sum + file.bytes, 0);
469
+ // `>=`, deliberately unlike the uploader's `>`. This weighs FILE bytes, and
470
+ // per-part framing is never zero, so a bundle whose files total exactly the
471
+ // limit is always a request LARGER than the limit — a real 413. See
472
+ // {@link API_SKILL_MAX_UPLOAD_BYTES} for the measured boundary and why the two
473
+ // operators must not be made to match.
474
+ if (total < limitBytes)
475
+ return issues;
476
+ const registryEntry = CODE_REGISTRY.PACKAGED_SIZE_EXCEEDS_API_LIMIT;
477
+ const heaviest = largestFirst(files)[0];
478
+ issues.push({
479
+ severity: registryEntry.defaultSeverity,
480
+ code: 'PACKAGED_SIZE_EXCEEDS_API_LIMIT',
481
+ message: describeOversizeBundle(files, { of: 'packaged-files', bytes: total }, limitBytes),
482
+ // The bundle root, because the finding is about the bundle. Naming the
483
+ // largest file here would claim that one file is the defect, which is only
484
+ // true when it alone exceeds the ceiling — it rides as `link` instead.
485
+ location: '.',
486
+ // Absent only for a bundle with no weighable file at all, which needs a
487
+ // zero or negative limit to reach this line.
488
+ ...(heaviest === undefined ? {} : { link: heaviest.path }),
489
+ fix: registryEntry.fix,
490
+ reference: registryEntry.reference,
491
+ });
492
+ return issues;
493
+ }
494
+ //# sourceMappingURL=packaged-size-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packaged-size-limit.js","sourceRoot":"","sources":["../../src/validators/packaged-size-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAe,MAAM,SAAS,CAAC;AAE7D,OAAO,EAAE,aAAa,EAAwB,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+EG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,UAAU,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAwB,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;AAExG,2EAA2E;AAC3E,SAAS,MAAM,CAAC,IAAY,EAAE,KAAa;IACzC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC;AACzC,CAAC;AAED,oEAAoE;AACpE,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAyC9B;;;;;;;GAOG;AACH,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAU,CAAC;AAEzD,mFAAmF;AACnF,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,IAAI,CAAC;IACtC,IAAI,KAAK,GAAG,KAAK,GAAG,IAAI,CAAC;IACzB,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,OAAO,KAAK,IAAI,IAAI,IAAI,IAAI,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrD,KAAK,IAAI,IAAI,CAAC;QACd,IAAI,IAAI,CAAC,CAAC;IACZ,CAAC;IACD,yEAAyE;IACzE,sEAAsE;IACtE,qEAAqE;IACrE,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC;AACnE,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,kBAAkB,CAAC,KAAa;IACvC,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,SAAS,CAAC;AAC1E,CAAC;AAQD,kEAAkE;AAClE,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC;AAED,oFAAoF;AACpF,SAAS,cAAc,CAAC,IAAY,EAAE,MAAc;IAClD,OAAO,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,GAAG,CAAC;AAChD,CAAC;AAED;;;;GAIG;AACH,MAAM,wBAAwB,GAC5B,sFAAsF;MACpF,sFAAsF,CAAC;AAE3F,mGAAmG;AACnG,MAAM,mBAAmB,GACvB,6FAA6F;MAC3F,0FAA0F;MAC1F,0CAA0C,wBAAwB,EAAE,CAAC;AAEzE,8FAA8F;AAC9F,MAAM,sBAAsB,GAC1B,0FAA0F;MACxF,4FAA4F;MAC5F,sCAAsC,wBAAwB,EAAE,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,SAAS,UAAU,CAAC,IAAY;IAC9B,MAAM,GAAG,GAAoB,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IAC1D,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,CAAC;IACrB,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QACxB,IAAI,GAAG,KAAK,SAAS;YAAE,MAAM;QAC7B,KAAK,MAAM,MAAM,IAAI,eAAe,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;YACrD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAC7C,IAAI,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC;gBACzB,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;oBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACjE,SAAS;YACX,CAAC;YACD,IAAI,MAAM,CAAC,cAAc,EAAE,EAAE,CAAC;gBAC5B,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC;oBACjB,IAAI,EAAE,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC;oBAChC,MAAM,EACJ,4EAA4E;0BAC1E,4DAA4D;oBAChE,GAAG,EAAE,mBAAmB;iBACzB,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YACD,UAAU,CAAC,IAAI,EAAE,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,gFAAgF;AAChF,SAAS,eAAe,CAAC,GAAW,EAAE,IAAY,EAAE,GAAoB;IACtE,IAAI,CAAC;QACH,0GAA0G;QAC1G,OAAO,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC;YACjB,IAAI,EAAE,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC;YAC/B,MAAM,EAAE,qCAAqC,OAAO,CAAC,KAAK,CAAC,GAAG;SAC/D,CAAC,CAAC;QACH,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,gFAAgF;AAChF,SAAS,UAAU,CAAC,IAAY,EAAE,GAAW,EAAE,GAAoB;IACjE,IAAI,KAAK,CAAC;IACV,IAAI,CAAC;QACH,+EAA+E;QAC/E,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,0BAA0B,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QACvF,OAAO;IACT,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QACnB,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QACjD,OAAO;IACT,CAAC;IACD,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC;QACjB,IAAI,EAAE,GAAG;QACT,4EAA4E;QAC5E,gEAAgE;QAChE,MAAM,EAAE,0DAA0D;QAClE,GAAG,EAAE,sBAAsB;KAC5B,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,SAAS,YAAY,CAAC,KAA2B;IAC/C,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAC9B,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC3E,CAAC;AAoBD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,sBAAsB,CACpC,KAA2B,EAC3B,OAAwB,EACxB,aAAqB,0BAA0B;IAE/C,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC;IAClE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,KAAK,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1F,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAChD,MAAM,IAAI,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACzF,MAAM,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;IACjE,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACnE,MAAM,OAAO,GAAG,OAAO,CAAC,EAAE,KAAK,gBAAgB;QAC7C,CAAC,CAAC,0BAA0B,kBAAkB,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,WAAW,CAAC,SAAS,CAAC,GAAG;cACxF,0BAA0B,MAAM,qCAAqC;QACzE,CAAC,CAAC,qBAAqB,kBAAkB,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,MAAM,GAAG,CAAC;IAC/E,OAAO,CACL,GAAG,OAAO,aAAa,kBAAkB,CAAC,UAAU,CAAC,wBAAwB;QAC7E,4BAA4B,KAAK,GAAG,IAAI,EAAE,CAC3C,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,eAAe,CAAC,OAAkC;IACzD,MAAM,aAAa,GAAG,aAAa,CAAC,oBAAoB,CAAC;IACzD,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC3B,QAAQ,EAAE,aAAa,CAAC,eAAe;QACvC,IAAI,EAAE,sBAA+B;QACrC,OAAO,EACL,2BAA2B,WAAW,CAAC,0BAA0B,CAAC,wCAAwC;cACxG,GAAG,KAAK,CAAC,MAAM,oDAAoD;QACvE,QAAQ,EAAE,KAAK,CAAC,IAAI;QACpB,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,aAAa,CAAC,GAAG;QACnC,SAAS,EAAE,aAAa,CAAC,SAAS;KACnC,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,MAAM,UAAU,sBAAsB,CACpC,SAAiB,EACjB,aAAqB,0BAA0B;IAE/C,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC/D,4EAA4E;IAC5E,4EAA4E;IAC5E,oEAAoE;IACpE,+EAA+E;IAC/E,uCAAuC;IACvC,IAAI,KAAK,GAAG,UAAU;QAAE,OAAO,MAAM,CAAC;IAEtC,MAAM,aAAa,GAAG,aAAa,CAAC,+BAA+B,CAAC;IACpE,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,MAAM,CAAC,IAAI,CAAC;QACV,QAAQ,EAAE,aAAa,CAAC,eAAe;QACvC,IAAI,EAAE,iCAAiC;QACvC,OAAO,EAAE,sBAAsB,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,gBAAgB,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,UAAU,CAAC;QAC1F,uEAAuE;QACvE,2EAA2E;QAC3E,uEAAuE;QACvE,QAAQ,EAAE,GAAG;QACb,wEAAwE;QACxE,6CAA6C;QAC7C,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC1D,GAAG,EAAE,aAAa,CAAC,GAAG;QACtB,SAAS,EAAE,aAAa,CAAC,SAAS;KACnC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -12,7 +12,7 @@
12
12
  * - vat skills build (block build on validation errors)
13
13
  * - vat skills audit --user (report issues, exit 0 always)
14
14
  */
15
- import { ResourceRegistry, type ResourcePopulationSource, type SkillExecutableEntry } from '@vibe-agent-toolkit/resources';
15
+ import { type RegistryUnreadablePolicy, ResourceRegistry, type ResourcePopulationSource, type SkillExecutableEntry } from '@vibe-agent-toolkit/resources';
16
16
  import { type AllowUsageLedger, type AllowRecord, type ValidationConfig, type ValidationIssue } from '@vibe-agent-toolkit/schema';
17
17
  import { type GitTracker } from '@vibe-agent-toolkit/utils/git';
18
18
  import type { EvidenceRecord, Observation } from '../evidence/index.js';
@@ -143,6 +143,18 @@ export declare function activeWarningsOf(result: WithAllErrors): ValidationIssue
143
143
  * Options for {@link crawlAndResolveRegistry}.
144
144
  */
145
145
  export interface CrawlRegistryOptions {
146
+ /**
147
+ * What the crawl does with a directory it cannot list — the caller's ruling,
148
+ * required, no default: `'refuse'` for every verb that acts on the registry as
149
+ * a complete population, `{ degrade }` for `vat audit`. See
150
+ * {@link RegistryUnreadablePolicy} on the registry for the two rulings.
151
+ *
152
+ * NOT part of the memo key: the memo replays the refusals the ONE crawl met
153
+ * into every later caller's policy, so a `'refuse'` caller served a registry
154
+ * that was built under `{ degrade }` still gets its throw — see
155
+ * {@link MemoizedRegistry}.
156
+ */
157
+ unreadable: RegistryUnreadablePolicy;
146
158
  /**
147
159
  * Where the file list comes from — omit for the incumbent walk, supply one to
148
160
  * source it from a projection instead.
@@ -207,7 +219,7 @@ export interface CrawlRegistryOptions {
207
219
  * the projection lane no declarations, so the two lanes still AGREE — which is
208
220
  * the property this function exists to hold.
209
221
  */
210
- export declare function crawlAndResolveRegistry(projectRoot: string, options?: CrawlRegistryOptions): Promise<ResourceRegistry>;
222
+ export declare function crawlAndResolveRegistry(projectRoot: string, options: CrawlRegistryOptions): Promise<ResourceRegistry>;
211
223
  /**
212
224
  * Drop every memoized registry.
213
225
  *
@@ -234,6 +246,23 @@ export declare function resetPackagingRegistryCache(): void;
234
246
  * legacy per-skill behavior so one-off callers keep working.
235
247
  */
236
248
  export interface SkillValidationSharedContext {
249
+ /**
250
+ * What the validator's OWN registry crawl does with a directory it cannot
251
+ * list, when it has to build one (no `registry` below, or one that does not
252
+ * cover this skill) — the caller's ruling, and the ONE required field here:
253
+ * `'refuse'` for `vat skills validate`/`build`, `vat skill review` and the
254
+ * packager's post-build check, `{ degrade }` for `vat audit`, whose directory
255
+ * lane supplies no registry and reaches {@link crawlAndResolveRegistry} through
256
+ * exactly this seam. See {@link RegistryUnreadablePolicy}.
257
+ *
258
+ * Ignored when `registry` below already covers the skill, for the same reason
259
+ * `populationSource` is: the caller that built it has already ruled.
260
+ *
261
+ * A caller that passes NO shared context at all is a single-skill lane whose
262
+ * result is acted on whole, and is ruled `'refuse'` by
263
+ * {@link validateSkillForPackaging} — said there, once, not defaulted here.
264
+ */
265
+ unreadable: RegistryUnreadablePolicy;
237
266
  /** Pre-built registry that covers the skill's project root. */
238
267
  registry?: ResourceRegistry;
239
268
  /**
@@ -356,6 +385,16 @@ export declare function validateSkillForPackaging(skillPath: string, packagingCo
356
385
  * with values derived from the skill path.
357
386
  */
358
387
  export declare function detectNameMismatchIssue(frontmatterName: unknown, parentDir: string, skillLocation: string): ValidationIssue | null;
388
+ /**
389
+ * Collect NON_PORTABLE_ASSET_REFERENCE issues — scan one skill document for any
390
+ * member of the non-portable asset-reference family.
391
+ */
392
+ export declare function collectNonPortableAssetReferenceIssues(content: string, docLocation: string, issues: ValidationIssue[]): void;
393
+ /**
394
+ * Collect NON_PORTABLE_COMMAND issues — scan one skill document for any member of
395
+ * the non-portable shell-command family.
396
+ */
397
+ export declare function collectNonPortableCommandIssues(content: string, docLocation: string, issues: ValidationIssue[]): void;
359
398
  /**
360
399
  * Process links from parsed markdown and return resolved .md file paths.
361
400
  *
@@ -1 +1 @@
1
- {"version":3,"file":"packaging-validator.d.ts","sourceRoot":"","sources":["../../src/validators/packaging-validator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"packaging-validator.d.ts","sourceRoot":"","sources":["../../src/validators/packaging-validator.ts"],"names":[],"mappings":"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"}