@vibe-agent-toolkit/agent-skills 0.2.0-rc.2 → 0.2.0-rc.4

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 (213) hide show
  1. package/dist/files-config.d.ts +34 -1
  2. package/dist/files-config.d.ts.map +1 -1
  3. package/dist/files-config.js +280 -36
  4. package/dist/files-config.js.map +1 -1
  5. package/dist/fs-attribution.d.ts +45 -0
  6. package/dist/fs-attribution.d.ts.map +1 -0
  7. package/dist/fs-attribution.js +57 -0
  8. package/dist/fs-attribution.js.map +1 -0
  9. package/dist/index.d.ts +6 -5
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +9 -3
  12. package/dist/index.js.map +1 -1
  13. package/dist/inventory/detectors/declared-but-missing.d.ts +1 -1
  14. package/dist/inventory/detectors/declared-but-missing.d.ts.map +1 -1
  15. package/dist/inventory/detectors/marketplace-source-missing.d.ts +1 -1
  16. package/dist/inventory/detectors/marketplace-source-missing.d.ts.map +1 -1
  17. package/dist/inventory/detectors/present-but-undeclared.d.ts +1 -1
  18. package/dist/inventory/detectors/present-but-undeclared.d.ts.map +1 -1
  19. package/dist/inventory/detectors/reference-target-missing.d.ts +1 -1
  20. package/dist/inventory/detectors/reference-target-missing.d.ts.map +1 -1
  21. package/dist/inventory/index.d.ts +1 -1
  22. package/dist/inventory/index.d.ts.map +1 -1
  23. package/dist/inventory/index.js +1 -1
  24. package/dist/inventory/index.js.map +1 -1
  25. package/dist/inventory/serialize.d.ts +4 -4
  26. package/dist/inventory/serialize.d.ts.map +1 -1
  27. package/dist/inventory/serialize.js +6 -6
  28. package/dist/inventory/serialize.js.map +1 -1
  29. package/dist/plugin-distribution-layout.d.ts.map +1 -1
  30. package/dist/plugin-distribution-layout.js +3 -1
  31. package/dist/plugin-distribution-layout.js.map +1 -1
  32. package/dist/post-build-checks.d.ts +1 -1
  33. package/dist/post-build-checks.d.ts.map +1 -1
  34. package/dist/post-build-checks.js.map +1 -1
  35. package/dist/projection/skill-extent.d.ts +247 -0
  36. package/dist/projection/skill-extent.d.ts.map +1 -0
  37. package/dist/projection/skill-extent.js +558 -0
  38. package/dist/projection/skill-extent.js.map +1 -0
  39. package/dist/schemas/agent-skill-frontmatter.js +1 -1
  40. package/dist/schemas/agent-skill-frontmatter.js.map +1 -1
  41. package/dist/skill-packager.d.ts +45 -7
  42. package/dist/skill-packager.d.ts.map +1 -1
  43. package/dist/skill-packager.js +123 -25
  44. package/dist/skill-packager.js.map +1 -1
  45. package/dist/skill-source/git-clone.d.ts +1 -1
  46. package/dist/skill-source/git-clone.d.ts.map +1 -1
  47. package/dist/skill-source/git-clone.js +10 -8
  48. package/dist/skill-source/git-clone.js.map +1 -1
  49. package/dist/skill-source/sources/url-source.d.ts.map +1 -1
  50. package/dist/skill-source/sources/url-source.js +2 -1
  51. package/dist/skill-source/sources/url-source.js.map +1 -1
  52. package/dist/skill-test/baseline-delta.d.ts +1575 -0
  53. package/dist/skill-test/baseline-delta.d.ts.map +1 -0
  54. package/dist/skill-test/baseline-delta.js +272 -0
  55. package/dist/skill-test/baseline-delta.js.map +1 -0
  56. package/dist/skill-test/baseline-integrity.d.ts +944 -0
  57. package/dist/skill-test/baseline-integrity.d.ts.map +1 -0
  58. package/dist/skill-test/baseline-integrity.js +3083 -0
  59. package/dist/skill-test/baseline-integrity.js.map +1 -0
  60. package/dist/skill-test/configure-writer.js +1 -1
  61. package/dist/skill-test/configure-writer.js.map +1 -1
  62. package/dist/skill-test/declared-env.d.ts.map +1 -1
  63. package/dist/skill-test/declared-env.js +2 -1
  64. package/dist/skill-test/declared-env.js.map +1 -1
  65. package/dist/skill-test/eval-executor.d.ts +20 -4
  66. package/dist/skill-test/eval-executor.d.ts.map +1 -1
  67. package/dist/skill-test/eval-executor.js +4 -4
  68. package/dist/skill-test/eval-executor.js.map +1 -1
  69. package/dist/skill-test/eval-fragment.d.ts +80 -6
  70. package/dist/skill-test/eval-fragment.d.ts.map +1 -1
  71. package/dist/skill-test/eval-fragment.js +61 -7
  72. package/dist/skill-test/eval-fragment.js.map +1 -1
  73. package/dist/skill-test/eval-grader.d.ts +1 -1
  74. package/dist/skill-test/eval-grader.d.ts.map +1 -1
  75. package/dist/skill-test/eval-grader.js +145 -8
  76. package/dist/skill-test/eval-grader.js.map +1 -1
  77. package/dist/skill-test/eval-inputs.d.ts +107 -6
  78. package/dist/skill-test/eval-inputs.d.ts.map +1 -1
  79. package/dist/skill-test/eval-inputs.js +173 -16
  80. package/dist/skill-test/eval-inputs.js.map +1 -1
  81. package/dist/skill-test/eval-lint.d.ts +13 -0
  82. package/dist/skill-test/eval-lint.d.ts.map +1 -1
  83. package/dist/skill-test/eval-lint.js +17 -4
  84. package/dist/skill-test/eval-lint.js.map +1 -1
  85. package/dist/skill-test/executor-prompt.d.ts +13 -5
  86. package/dist/skill-test/executor-prompt.d.ts.map +1 -1
  87. package/dist/skill-test/executor-prompt.js +8 -5
  88. package/dist/skill-test/executor-prompt.js.map +1 -1
  89. package/dist/skill-test/exit-codes.d.ts +53 -16
  90. package/dist/skill-test/exit-codes.d.ts.map +1 -1
  91. package/dist/skill-test/exit-codes.js +123 -40
  92. package/dist/skill-test/exit-codes.js.map +1 -1
  93. package/dist/skill-test/fragment-merge.d.ts +1 -18
  94. package/dist/skill-test/fragment-merge.d.ts.map +1 -1
  95. package/dist/skill-test/fragment-merge.js +63 -4
  96. package/dist/skill-test/fragment-merge.js.map +1 -1
  97. package/dist/skill-test/grader-prompt.d.ts +27 -7
  98. package/dist/skill-test/grader-prompt.d.ts.map +1 -1
  99. package/dist/skill-test/grader-prompt.js +253 -61
  100. package/dist/skill-test/grader-prompt.js.map +1 -1
  101. package/dist/skill-test/grader-text.d.ts +145 -0
  102. package/dist/skill-test/grader-text.d.ts.map +1 -0
  103. package/dist/skill-test/grader-text.js +410 -0
  104. package/dist/skill-test/grader-text.js.map +1 -0
  105. package/dist/skill-test/grading-adapter.d.ts +32 -0
  106. package/dist/skill-test/grading-adapter.d.ts.map +1 -1
  107. package/dist/skill-test/grading-adapter.js +50 -3
  108. package/dist/skill-test/grading-adapter.js.map +1 -1
  109. package/dist/skill-test/grading-schema.d.ts +160 -5
  110. package/dist/skill-test/grading-schema.d.ts.map +1 -1
  111. package/dist/skill-test/grading-schema.js +66 -7
  112. package/dist/skill-test/grading-schema.js.map +1 -1
  113. package/dist/skill-test/index.d.ts +1 -1
  114. package/dist/skill-test/index.d.ts.map +1 -1
  115. package/dist/skill-test/index.js +1 -1
  116. package/dist/skill-test/index.js.map +1 -1
  117. package/dist/skill-test/lock.d.ts +56 -0
  118. package/dist/skill-test/lock.d.ts.map +1 -1
  119. package/dist/skill-test/lock.js +75 -2
  120. package/dist/skill-test/lock.js.map +1 -1
  121. package/dist/skill-test/pipeline.d.ts +16 -0
  122. package/dist/skill-test/pipeline.d.ts.map +1 -1
  123. package/dist/skill-test/pipeline.js +16 -9
  124. package/dist/skill-test/pipeline.js.map +1 -1
  125. package/dist/skill-test/preflight.d.ts +1 -1
  126. package/dist/skill-test/preflight.d.ts.map +1 -1
  127. package/dist/skill-test/preflight.js +59 -15
  128. package/dist/skill-test/preflight.js.map +1 -1
  129. package/dist/skill-test/run-harness.d.ts +578 -35
  130. package/dist/skill-test/run-harness.d.ts.map +1 -1
  131. package/dist/skill-test/run-harness.js +1281 -148
  132. package/dist/skill-test/run-harness.js.map +1 -1
  133. package/dist/skill-test/staging.d.ts +8 -2
  134. package/dist/skill-test/staging.d.ts.map +1 -1
  135. package/dist/skill-test/staging.js +8 -4
  136. package/dist/skill-test/staging.js.map +1 -1
  137. package/dist/skill-test/tool-eval-schema.d.ts +6 -6
  138. package/dist/test-input.d.ts +57 -6
  139. package/dist/test-input.d.ts.map +1 -1
  140. package/dist/test-input.js +35 -12
  141. package/dist/test-input.js.map +1 -1
  142. package/dist/validators/agent-instruction-presence.d.ts +1 -1
  143. package/dist/validators/agent-instruction-presence.d.ts.map +1 -1
  144. package/dist/validators/agent-instruction-presence.js +2 -1
  145. package/dist/validators/agent-instruction-presence.js.map +1 -1
  146. package/dist/validators/bundled-resource-link-detection.d.ts +1 -1
  147. package/dist/validators/bundled-resource-link-detection.d.ts.map +1 -1
  148. package/dist/validators/bundled-resource-link-detection.js +1 -1
  149. package/dist/validators/bundled-resource-link-detection.js.map +1 -1
  150. package/dist/validators/compat-detectors.d.ts +1 -1
  151. package/dist/validators/compat-detectors.d.ts.map +1 -1
  152. package/dist/validators/compat-detectors.js +1 -1
  153. package/dist/validators/compat-detectors.js.map +1 -1
  154. package/dist/validators/cross-skill-dependency-detection.d.ts +1 -1
  155. package/dist/validators/cross-skill-dependency-detection.d.ts.map +1 -1
  156. package/dist/validators/cross-skill-dependency-detection.js +1 -1
  157. package/dist/validators/cross-skill-dependency-detection.js.map +1 -1
  158. package/dist/validators/description-style-detection.d.ts +1 -1
  159. package/dist/validators/description-style-detection.d.ts.map +1 -1
  160. package/dist/validators/description-style-detection.js +1 -1
  161. package/dist/validators/description-style-detection.js.map +1 -1
  162. package/dist/validators/frontmatter-validation.d.ts +1 -1
  163. package/dist/validators/frontmatter-validation.d.ts.map +1 -1
  164. package/dist/validators/frontmatter-validation.js +1 -1
  165. package/dist/validators/frontmatter-validation.js.map +1 -1
  166. package/dist/validators/imperative-body-detection.d.ts +1 -1
  167. package/dist/validators/imperative-body-detection.d.ts.map +1 -1
  168. package/dist/validators/imperative-body-detection.js +1 -1
  169. package/dist/validators/imperative-body-detection.js.map +1 -1
  170. package/dist/validators/index.d.ts +1 -1
  171. package/dist/validators/index.d.ts.map +1 -1
  172. package/dist/validators/kebab-case-detection.d.ts +1 -1
  173. package/dist/validators/kebab-case-detection.d.ts.map +1 -1
  174. package/dist/validators/kebab-case-detection.js +1 -1
  175. package/dist/validators/kebab-case-detection.js.map +1 -1
  176. package/dist/validators/marketplace-validator.js +1 -1
  177. package/dist/validators/marketplace-validator.js.map +1 -1
  178. package/dist/validators/packaging-validator.d.ts +96 -7
  179. package/dist/validators/packaging-validator.d.ts.map +1 -1
  180. package/dist/validators/packaging-validator.js +148 -21
  181. package/dist/validators/packaging-validator.js.map +1 -1
  182. package/dist/validators/plugin-hosted-shape.d.ts +1 -1
  183. package/dist/validators/plugin-hosted-shape.d.ts.map +1 -1
  184. package/dist/validators/plugin-hosted-shape.js +1 -1
  185. package/dist/validators/plugin-hosted-shape.js.map +1 -1
  186. package/dist/validators/plugin-recommended-fields.d.ts +1 -1
  187. package/dist/validators/plugin-recommended-fields.d.ts.map +1 -1
  188. package/dist/validators/plugin-recommended-fields.js +1 -1
  189. package/dist/validators/plugin-recommended-fields.js.map +1 -1
  190. package/dist/validators/registry-validator.js +1 -1
  191. package/dist/validators/registry-validator.js.map +1 -1
  192. package/dist/validators/rule-engine/rule-engine.d.ts +1 -1
  193. package/dist/validators/rule-engine/rule-engine.d.ts.map +1 -1
  194. package/dist/validators/rule-engine/rule-engine.js +20 -9
  195. package/dist/validators/rule-engine/rule-engine.js.map +1 -1
  196. package/dist/validators/skill-validator.d.ts.map +1 -1
  197. package/dist/validators/skill-validator.js +21 -3
  198. package/dist/validators/skill-validator.js.map +1 -1
  199. package/dist/validators/types.d.ts +1 -1
  200. package/dist/validators/types.d.ts.map +1 -1
  201. package/dist/validators/unified-validator.js +1 -1
  202. package/dist/validators/unified-validator.js.map +1 -1
  203. package/dist/validators/validation-rules.d.ts +1 -1
  204. package/dist/validators/validation-rules.d.ts.map +1 -1
  205. package/dist/validators/walker-to-issues.d.ts +7 -3
  206. package/dist/validators/walker-to-issues.d.ts.map +1 -1
  207. package/dist/validators/walker-to-issues.js +37 -8
  208. package/dist/validators/walker-to-issues.js.map +1 -1
  209. package/dist/walk-link-graph.d.ts +2 -1
  210. package/dist/walk-link-graph.d.ts.map +1 -1
  211. package/dist/walk-link-graph.js +37 -2
  212. package/dist/walk-link-graph.js.map +1 -1
  213. package/package.json +7 -7
@@ -7,8 +7,8 @@
7
7
  * - Computing deferred paths for validation
8
8
  * - Copying declared build artifacts into a skill output dir (every build path)
9
9
  */
10
- import { type ValidationIssue } from '@vibe-agent-toolkit/agent-schema';
11
10
  import type { SkillFileEntry } from '@vibe-agent-toolkit/resources';
11
+ import { type ValidationIssue } from '@vibe-agent-toolkit/schema';
12
12
  export type { SkillFileEntry } from '@vibe-agent-toolkit/resources';
13
13
  /** One file a GLOB `files:` entry matched and the never-package list refused. */
14
14
  export interface DroppedGlobMatch {
@@ -57,6 +57,16 @@ export interface AppliedFilesConfig {
57
57
  * that silently shipped less than the config asked for.
58
58
  */
59
59
  dropped: DroppedGlobMatch[];
60
+ /**
61
+ * Matches a GLOB caught that are not copyable files, and so were skipped.
62
+ *
63
+ * Reported for the same reason as `dropped` — the bundle holds less than the
64
+ * config asked for and a CI consumer must be able to see it — but kept as its
65
+ * own population because the cause and the remedy differ (see
66
+ * {@link GlobExpansion.nonRegular}). Before this existed, the copy died on a raw
67
+ * `ENOTSUP` naming neither the entry nor the path (issue #183).
68
+ */
69
+ skipped: DroppedGlobMatch[];
60
70
  }
61
71
  /**
62
72
  * Turn never-package drops into reportable findings.
@@ -78,6 +88,17 @@ export interface AppliedFilesConfig {
78
88
  * silently emits paths relative to the wrong tree.
79
89
  */
80
90
  export declare function droppedGlobMatchesToIssues(dropped: readonly DroppedGlobMatch[], locationRoot: string): ValidationIssue[];
91
+ /**
92
+ * Turn non-copyable matches into reportable findings.
93
+ *
94
+ * Same anchoring contract as {@link droppedGlobMatchesToIssues} — and it shares
95
+ * that function's body rather than restating it, so the two populations cannot
96
+ * drift into describing themselves in different coordinate systems.
97
+ *
98
+ * @param locationRoot The ONE base this run anchors locations at (see
99
+ * {@link droppedGlobMatchesToIssues}).
100
+ */
101
+ export declare function skippedGlobMatchesToIssues(skipped: readonly DroppedGlobMatch[], locationRoot: string): ValidationIssue[];
81
102
  export declare function buildArtifactHint(source: string): string;
82
103
  /**
83
104
  * Normalize a relative path for comparison: forward slashes, no leading `./`.
@@ -249,6 +270,18 @@ export interface PreBuildGlobFindings {
249
270
  allRefused: AllRefusedGlobEntry[];
250
271
  /** Globs that currently match nothing — the other input the build dies on. */
251
272
  unmatched: UnmatchedGlobEntry[];
273
+ /**
274
+ * Matches that are not copyable files (symlink-to-directory, FIFO, socket,
275
+ * device node, dangling link) and so cannot be packaged at all.
276
+ *
277
+ * A FIFTH population, orthogonal to the other four: those partition an entry by
278
+ * what the never-package POLICY decided about its matches, this one by what the
279
+ * matched object physically IS. An entry can appear here AND ship normally, so
280
+ * this is not a failure verdict — see {@link DroppedGlobMatch}, whose shape it
281
+ * reuses because the reported facts (which entry, which file, which would-be
282
+ * dest) are the same three.
283
+ */
284
+ skipped: DroppedGlobMatch[];
252
285
  }
253
286
  /**
254
287
  * What the project's GLOB `files:` entries would do, without copying anything.
@@ -1 +1 @@
1
- {"version":3,"file":"files-config.d.ts","sourceRoot":"","sources":["../src/files-config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"files-config.d.ts","sourceRoot":"","sources":["../src/files-config.ts"],"names":[],"mappings":"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"}
@@ -7,12 +7,13 @@
7
7
  * - Computing deferred paths for validation
8
8
  * - Copying declared build artifacts into a skill output dir (every build path)
9
9
  */
10
- import { existsSync, statSync } from 'node:fs';
11
- import { copyFile, mkdir } from 'node:fs/promises';
10
+ import { statSync } from 'node:fs';
11
+ import { copyFile, lstat, mkdir, stat } from 'node:fs/promises';
12
12
  import { dirname } from 'node:path';
13
- import { fileContentHash, globMagicRemainder, hasParentTraversalSegment, isGlob, issueLocation, safePath, staticGlobBase, toForwardSlash, } from '@vibe-agent-toolkit/utils';
13
+ import { fileContentHash, globMagicRemainder, hasParentTraversalSegment, isFilesystemAccessError, isGlob, issueLocation, safePath, staticGlobBase, toForwardSlash, } from '@vibe-agent-toolkit/utils';
14
14
  import { glob } from 'glob';
15
15
  import picomatch from 'picomatch';
16
+ import { withFsAttribution } from './fs-attribution.js';
16
17
  import { materializeIssue } from './validators/rule-engine/index.js';
17
18
  import { isNeverPackagedBasename } from './validators/validation-rules.js';
18
19
  /**
@@ -39,6 +40,126 @@ function partitionNeverPackaged(matches) {
39
40
  }
40
41
  return { kept, dropped };
41
42
  }
43
+ /**
44
+ * Partition a glob's matches into the ones the OS can copy and the ones it cannot.
45
+ *
46
+ * `glob`'s `nodir: true` is NOT this check, and cannot be made into it: it filters
47
+ * on what `glob` itself stat'd, and `glob` does not follow symlinks — so a symlink
48
+ * pointing at a DIRECTORY is never a directory to it and arrives here as an
49
+ * ordinary match. `copyFile` on that link then throws a raw `ENOTSUP` which, on
50
+ * macOS, renders as "operation not supported on socket" and misdescribes the
51
+ * object outright (issue #183).
52
+ *
53
+ * Two simpler predicates were rejected:
54
+ *
55
+ * - Refusing on `isSymbolicLink()` alone would drop the legitimate
56
+ * symlink-to-FILE case — copying a symlinked file by content is reasonable and
57
+ * widely relied on — and would still let FIFOs, sockets and device nodes through
58
+ * to the same raw errno.
59
+ * - Testing only `stat().isFile()` would follow the link and answer correctly, but
60
+ * throws on a DANGLING link, putting the same unattributed errno back on the
61
+ * build's critical path.
62
+ *
63
+ * So the predicate is "does this resolve to a regular file, without throwing" —
64
+ * see {@link isCopyableFile}, which is the one place following a link is correct,
65
+ * because the target's bytes are what `copyFile` would move.
66
+ */
67
+ async function partitionRegularFiles(matches, absoluteBase) {
68
+ // Bounded concurrency, not a serial loop and not an unbounded `Promise.all`.
69
+ // This adds one or two stats per match where the build previously did none, on
70
+ // the critical path of a command that already stats every match once inside
71
+ // `glob` — serially that is a latency floor proportional to the match count, and
72
+ // a monorepo glob can match thousands. Unbounded would trade it for EMFILE.
73
+ //
74
+ // Results are written BY INDEX rather than pushed, so the partition stays in the
75
+ // caller's sorted order regardless of completion order — `droppedRel` and
76
+ // `nonRegularRel` are rendered into error messages an adopter reads, and a set
77
+ // that reshuffles between runs is a diff for no reason.
78
+ const copyable = await mapWithConcurrency(matches, STAT_CONCURRENCY, (rel) =>
79
+ // joinUnderRoot asserts the match stays under absoluteBase, the same guard the
80
+ // copy loop applies before reading it.
81
+ isCopyableFile(safePath.joinUnderRoot(absoluteBase, rel)));
82
+ const regular = [];
83
+ const nonRegular = [];
84
+ for (const [i, rel] of matches.entries()) {
85
+ (copyable[i] === true ? regular : nonRegular).push(rel);
86
+ }
87
+ return { regular, nonRegular };
88
+ }
89
+ /**
90
+ * Enough parallelism to hide per-call latency, low enough to stay far from the
91
+ * default file-descriptor ceiling even with several skills building at once.
92
+ */
93
+ const STAT_CONCURRENCY = 16;
94
+ /** `Promise.all`-shaped, but with at most `limit` calls in flight. Order preserved. */
95
+ async function mapWithConcurrency(items, limit, fn) {
96
+ const results = Array.from({ length: items.length });
97
+ let next = 0;
98
+ const worker = async () => {
99
+ while (next < items.length) {
100
+ const i = next++;
101
+ // Non-null: `i` is always a valid index, guarded by the loop condition.
102
+ results[i] = await fn(items[i]);
103
+ }
104
+ };
105
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
106
+ return results;
107
+ }
108
+ /**
109
+ * Whether `copyFile` can read this path as a stream of bytes.
110
+ *
111
+ * A regular file is copyable directly. A SYMLINK is copyable exactly when its
112
+ * target resolves to a regular file — that is the `stat` call, and it is the one
113
+ * place following the link is correct, because it is the target's bytes `copyFile`
114
+ * would move. A dangling link, or one resolving to a directory, throws from `stat`
115
+ * and is reported as not copyable rather than escaping as the build's whole
116
+ * explanation.
117
+ */
118
+ async function isCopyableFile(absPath) {
119
+ try {
120
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- match of a validated config glob
121
+ const linkStat = await lstat(absPath);
122
+ if (linkStat.isFile())
123
+ return true;
124
+ if (!linkStat.isSymbolicLink())
125
+ return false;
126
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- match of a validated config glob
127
+ return (await stat(absPath)).isFile();
128
+ }
129
+ catch (error) {
130
+ // Unreadable or dangling. Not copyable is the honest answer, and the caller
131
+ // reports it as a named finding — which is strictly more than the raw errno
132
+ // this replaces ever gave.
133
+ //
134
+ // Narrowed to filesystem errors on purpose: a bare `catch {}` here would
135
+ // answer "not a regular file" to a bug in our own code, silently converting a
136
+ // defect into a routine skip. Same predicate the audit walk uses, from one
137
+ // shared source so the two cannot drift.
138
+ if (!isFilesystemAccessError(error))
139
+ throw error;
140
+ return false;
141
+ }
142
+ }
143
+ /**
144
+ * Run the filesystem work for ONE `files:` entry, and make any failure name the
145
+ * entry that caused it.
146
+ *
147
+ * THE attribution point for the whole module, used by the glob and non-glob copy
148
+ * paths alike. It exists because the same defect was fixed three times in a row,
149
+ * one call site at a time: a raw errno from `copyFile` on a glob match, then the
150
+ * `mkdir` a line above it, then the identical pair on the explicit-entry path.
151
+ * Each fix was correct and none of them was the class. Routing every per-entry
152
+ * filesystem call through here is.
153
+ *
154
+ * The message is deliberately about the ENTRY, not the syscall: an author reading
155
+ * a build failure needs to know which line of their config produced it, which
156
+ * path it resolved to, and what to do — none of which an errno carries. The OS
157
+ * text is kept verbatim inside it, because it is what distinguishes a permission
158
+ * problem from a full disk from a vanished mount.
159
+ */
160
+ async function attributed(entry, absPath, projectRoot, work, action) {
161
+ return withFsAttribution(`files: source '${entry.source}' resolved to ${anchoredPath(absPath, projectRoot)}`, work, action);
162
+ }
42
163
  /**
43
164
  * Turn never-package drops into reportable findings.
44
165
  *
@@ -59,11 +180,32 @@ function partitionNeverPackaged(matches) {
59
180
  * silently emits paths relative to the wrong tree.
60
181
  */
61
182
  export function droppedGlobMatchesToIssues(dropped, locationRoot) {
62
- return dropped.map((drop) => {
63
- const file = issueLocation(drop.absFile, locationRoot);
64
- return materializeIssue('FILES_GLOB_DROPPED_NEVER_PACKAGED', {
183
+ return globMatchesToIssues(dropped, locationRoot, 'FILES_GLOB_DROPPED_NEVER_PACKAGED');
184
+ }
185
+ /**
186
+ * Turn non-copyable matches into reportable findings.
187
+ *
188
+ * Same anchoring contract as {@link droppedGlobMatchesToIssues} — and it shares
189
+ * that function's body rather than restating it, so the two populations cannot
190
+ * drift into describing themselves in different coordinate systems.
191
+ *
192
+ * @param locationRoot The ONE base this run anchors locations at (see
193
+ * {@link droppedGlobMatchesToIssues}).
194
+ */
195
+ export function skippedGlobMatchesToIssues(skipped, locationRoot) {
196
+ return globMatchesToIssues(skipped, locationRoot, 'FILES_GLOB_SKIPPED_NON_REGULAR_FILE');
197
+ }
198
+ /**
199
+ * The one emitter behind both per-file glob findings: anchored at the SOURCE file,
200
+ * naming the entry whose net caught it. Parameterized by code rather than copied
201
+ * per population — the anchoring rule above is the part that must never diverge.
202
+ */
203
+ function globMatchesToIssues(matches, locationRoot, code) {
204
+ return matches.map((match) => {
205
+ const file = issueLocation(match.absFile, locationRoot);
206
+ return materializeIssue(code, {
65
207
  location: file,
66
- detail: `${file} matched by files: source '${drop.source}'`,
208
+ detail: `${file} matched by files: source '${match.source}'`,
67
209
  });
68
210
  });
69
211
  }
@@ -232,8 +374,19 @@ export function explicitFilesConfigDests(files) {
232
374
  */
233
375
  export function verifyFilesIntegrity(pairs) {
234
376
  for (const { absSource, absDest } of pairs) {
235
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- paths from validated config
236
- if (!existsSync(absDest)) {
377
+ // `statSync` in a guard rather than `existsSync`, because `existsSync`
378
+ // answers FALSE for a file that is present but unreadable — so a permissions
379
+ // problem on the dest was reported as "dest file missing", sending the author
380
+ // to look for a file that is sitting right there. Only a real ENOENT is
381
+ // missing; anything else is the filesystem refusing the path, and is rethrown
382
+ // for the caller to attribute to the `files:` entry.
383
+ try {
384
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- paths from validated config
385
+ statSync(absDest);
386
+ }
387
+ catch (error) {
388
+ if (error.code !== 'ENOENT')
389
+ throw error;
237
390
  throw new Error(`files: integrity check failed — dest file missing: ${toForwardSlash(absDest)}`);
238
391
  }
239
392
  const srcHash = fileContentHash(absSource);
@@ -299,12 +452,25 @@ export async function verifyDestSet(destDir, expectedRel, source) {
299
452
  * Throws on missing source or if source is a directory.
300
453
  */
301
454
  async function copyNonGlobEntry(entry, absoluteSource, projectRoot, skillOutputDir) {
302
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- source path from validated config
303
- if (!existsSync(absoluteSource)) {
455
+ // `statSync` in a guard, not `existsSync`: `existsSync` swallows EACCES and
456
+ // answers FALSE, so a source the process merely cannot REACH — one under a
457
+ // directory it lacks permission to traverse — was reported as "does not exist".
458
+ // For a `dist/` source that also appended `buildArtifactHint`, telling the
459
+ // author to run a build that would not have helped: a confidently wrong
460
+ // diagnosis plus a confidently wrong remedy. Only ENOENT is absence; anything
461
+ // else is the filesystem refusing the path, and `attributed` at the call site
462
+ // names the entry.
463
+ let sourceStat;
464
+ try {
465
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- source path from validated config
466
+ sourceStat = statSync(absoluteSource);
467
+ }
468
+ catch (error) {
469
+ if (error.code !== 'ENOENT')
470
+ throw error;
304
471
  throw new Error(`files: source '${entry.source}' does not exist (resolved to ${anchoredPath(absoluteSource, projectRoot)}).${buildArtifactHint(entry.source)}`);
305
472
  }
306
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- source path from validated config
307
- if (statSync(absoluteSource).isDirectory()) {
473
+ if (sourceStat.isDirectory()) {
308
474
  throw new Error(`files: source '${entry.source}' is a directory; use a glob like '${entry.source}/**/*' to copy its contents.`);
309
475
  }
310
476
  // joinUnderRoot rejects a dest that escapes the skill output dir (absolute /
@@ -344,21 +510,28 @@ async function expandGlobEntry(entry, projectRoot) {
344
510
  // package symmetric with verifyDestSet (M10: silent dot-file drop).
345
511
  const rawMatches = await glob(remainder, { cwd: absoluteBase, nodir: true, dot: true });
346
512
  const allMatches = rawMatches.map((m) => toForwardSlash(m)).sort((a, b) => a.localeCompare(b));
347
- const { kept, dropped } = partitionNeverPackaged(allMatches);
513
+ // BEFORE the never-package split, because the two ask different questions and
514
+ // only one of them can be answered about a non-file: `partitionNeverPackaged`
515
+ // decides what POLICY refuses to ship, this decides what the OS can copy at all.
516
+ const { regular, nonRegular } = await partitionRegularFiles(allMatches, absoluteBase);
517
+ const { kept, dropped } = partitionNeverPackaged(regular);
518
+ const asMatchRecord = (rel) => ({
519
+ source: entry.source,
520
+ // The refused file itself. Computed HERE, where the base a match is
521
+ // relative to is known for certain, rather than re-derived by each
522
+ // consumer from `dest` — a dest cannot be inverted back to a source once
523
+ // the entry has rebased it.
524
+ absFile: toForwardSlash(safePath.joinUnderRoot(absoluteBase, rel)),
525
+ dest: normalizeRelPath(safePath.join(entry.dest, rel)),
526
+ });
348
527
  return {
349
528
  absoluteBase,
350
529
  allMatches,
351
530
  kept,
352
531
  droppedRel: dropped,
353
- dropped: dropped.map((rel) => ({
354
- source: entry.source,
355
- // The refused file itself. Computed HERE, where the base a match is
356
- // relative to is known for certain, rather than re-derived by each
357
- // consumer from `dest` — a dest cannot be inverted back to a source once
358
- // the entry has rebased it.
359
- absFile: toForwardSlash(safePath.joinUnderRoot(absoluteBase, rel)),
360
- dest: normalizeRelPath(safePath.join(entry.dest, rel)),
361
- })),
532
+ dropped: dropped.map(asMatchRecord),
533
+ nonRegularRel: nonRegular,
534
+ nonRegular: nonRegular.map(asMatchRecord),
362
535
  };
363
536
  }
364
537
  /**
@@ -380,24 +553,44 @@ async function expandGlobEntry(entry, projectRoot) {
380
553
  * complete and makes integrity mean what it says.
381
554
  */
382
555
  async function copyGlobEntry(entry, projectRoot, skillOutputDir) {
383
- const { absoluteBase, allMatches, kept: matches, droppedRel, dropped } = await expandGlobEntry(entry, projectRoot);
556
+ const { absoluteBase, allMatches, kept: matches, droppedRel, dropped, nonRegularRel, nonRegular, } = await expandGlobEntry(entry, projectRoot);
384
557
  // Project-relative in both throws below: these messages are published verbatim
385
558
  // as `failedSkills[].message` (see {@link anchoredPath}).
386
559
  const reportedBase = anchoredPath(absoluteBase, projectRoot);
387
560
  if (allMatches.length === 0) {
388
561
  throw new Error(`files: source '${entry.source}' (glob) matched no files under ${reportedBase} — has your build run?`);
389
562
  }
563
+ // Ordered BEFORE the never-package throw because it is the more specific
564
+ // diagnosis: if the entry nets nothing shippable and nothing it netted was even
565
+ // a file, "all of them are never packaged" names the wrong cause and its
566
+ // remediation ("declare an explicit source: entry") would not work if followed.
567
+ if (matches.length === 0 && droppedRel.length === 0) {
568
+ throw new Error(`files: source '${entry.source}' (glob) matched ${allMatches.length} path(s) under ${reportedBase}, ` +
569
+ `but none of them is a regular file (a symlink to a directory, a FIFO, a socket or a device node ` +
570
+ `cannot be packaged): ${nonRegularRel.join(', ')}. ` +
571
+ `Point the glob at files, or replace the link with the directory it targets.`);
572
+ }
390
573
  // Distinct from the zero-match case above: the build DID run and the glob DID
391
574
  // match — every match is simply a file that never ships. Saying "has your build
392
575
  // run?" here would send the author hunting a build failure that isn't there.
393
576
  if (matches.length === 0) {
577
+ // The MIXED case reaches here — some matches refused by policy, some not files
578
+ // at all — so the non-regular ones are named too. Listing only `droppedRel`
579
+ // would say "all of them are never packaged" while silently omitting paths that
580
+ // failed for an entirely different reason, and this throw is the last word on
581
+ // the entry: it aborts the build, so the `skipped` findings never get emitted.
582
+ const alsoNonRegular = nonRegularRel.length === 0
583
+ ? ''
584
+ : ` A further ${nonRegularRel.length.toString()} match(es) are not regular files and could not ` +
585
+ `be packaged either: ${nonRegularRel.join(', ')}.`;
394
586
  throw new Error(`files: source '${entry.source}' (glob) matched ${allMatches.length} file(s) under ${reportedBase}, ` +
395
587
  `but all of them are never packaged into a skill bundle: ${droppedRel.join(', ')}. ` +
396
588
  // NOT "widen the glob": the filter is on basename and applies at any width, so a
397
589
  // wider glob clears this error while still shipping none of these files — advice
398
590
  // that looks like it worked and silently does not.
399
591
  `Declare an explicit source: entry for a file you intend to ship, ` +
400
- `or point the glob at a directory that holds files which can be packaged.`);
592
+ `or point the glob at a directory that holds files which can be packaged.` +
593
+ alsoNonRegular);
401
594
  }
402
595
  const copied = [];
403
596
  const pairs = [];
@@ -412,14 +605,27 @@ async function copyGlobEntry(entry, projectRoot, skillOutputDir) {
412
605
  const absSource = safePath.joinUnderRoot(absoluteBase, rel);
413
606
  const relDest = normalizeRelPath(safePath.join(entry.dest, rel));
414
607
  const absDest = safePath.joinUnderRoot(skillOutputDir, entry.dest, rel);
415
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- dest path from validated config
416
- await mkdir(dirname(absDest), { recursive: true });
417
- await copyFile(absSource, absDest);
608
+ // `isCopyableFile` classified the TYPE of this match; it deliberately did not
609
+ // assert readability, because a permission can change between the check and
610
+ // the copy and a guard that pretended otherwise would just move the race. So
611
+ // the work itself is where an IO failure is caught — BOTH calls, since the
612
+ // `mkdir` fails on an unwritable output dir, a full disk, or a dest whose
613
+ // parent is an existing file.
614
+ //
615
+ // Still a hard failure, unlike a non-regular match: an unreadable file is one
616
+ // the author DECLARED and expects in the bundle, so shipping silently without
617
+ // it would change the artifact behind their back. A non-regular match can
618
+ // never be packaged at all, which is why that one degrades instead.
619
+ await attributed(entry, absSource, projectRoot, async () => {
620
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- dest path from validated config
621
+ await mkdir(dirname(absDest), { recursive: true });
622
+ await copyFile(absSource, absDest);
623
+ });
418
624
  copied.push(relDest);
419
625
  pairs.push({ absSource, absDest });
420
626
  rels.push(toForwardSlash(rel));
421
627
  }
422
- return { copied, pairs, rels, dropped };
628
+ return { copied, pairs, rels, dropped, skipped: nonRegular };
423
629
  }
424
630
  /**
425
631
  * What the project's GLOB `files:` entries would do, without copying anything.
@@ -465,6 +671,7 @@ export async function collectPreBuildGlobFindings(filesConfig, projectRoot) {
465
671
  const dropped = [];
466
672
  const allRefused = [];
467
673
  const unmatched = [];
674
+ const skipped = [];
468
675
  for (const entry of filesConfig) {
469
676
  if (!isGlob(entry.source))
470
677
  continue;
@@ -495,6 +702,18 @@ export async function collectPreBuildGlobFindings(filesConfig, projectRoot) {
495
702
  unmatched.push({ source: entry.source, absBase });
496
703
  continue;
497
704
  }
705
+ // Reported for EVERY entry that has them, including ones that go on to be
706
+ // `allRefused` below — unlike the per-file drops, which that verdict
707
+ // supersedes. A non-file is not the cause `allRefused` names, so its finding
708
+ // is not a second granularity of the same defect; suppressing it here would
709
+ // put the author back where the raw errno left them.
710
+ skipped.push(...expansion.nonRegular);
711
+ if (expansion.kept.length === 0 && expansion.dropped.length === 0) {
712
+ // Matched only non-files. `allRefused` would name the never-package list as
713
+ // the cause, which is not what happened, and its remediation would not work.
714
+ // The `skipped` findings just pushed carry this entry on their own.
715
+ continue;
716
+ }
498
717
  if (expansion.kept.length === 0) {
499
718
  // Supersedes this entry's per-file drops rather than adding to them: the
500
719
  // build raises ONE error listing every refused file, and reporting the same
@@ -509,7 +728,7 @@ export async function collectPreBuildGlobFindings(filesConfig, projectRoot) {
509
728
  }
510
729
  dropped.push(...expansion.dropped.filter((d) => !shippedDests.has(normalizeRelPath(d.dest))));
511
730
  }
512
- return { dropped, allRefused, unmatched };
731
+ return { dropped, allRefused, unmatched, skipped };
513
732
  }
514
733
  /**
515
734
  * Turn {@link collectPreBuildGlobFindings} into reportable pre-build issues.
@@ -527,6 +746,7 @@ export function preBuildGlobFindingsToIssues(findings, locationRoot) {
527
746
  const anchorBase = (absBase) => anchoredPath(absBase, locationRoot);
528
747
  return [
529
748
  ...droppedGlobMatchesToIssues(findings.dropped, locationRoot),
749
+ ...skippedGlobMatchesToIssues(findings.skipped, locationRoot),
530
750
  ...findings.allRefused.map((entry) => {
531
751
  const base = anchorBase(entry.absBase);
532
752
  // Every refused file, in the SAME source coordinates the drop findings use —
@@ -549,10 +769,11 @@ export function preBuildGlobFindingsToIssues(findings, locationRoot) {
549
769
  }
550
770
  /** Copy one GLOB `files:` entry. */
551
771
  async function applyGlobFileEntry(fileEntry, opts) {
552
- const { copied, pairs, rels, dropped } = await copyGlobEntry(fileEntry, opts.projectRoot, opts.skillOutputDir);
772
+ const { copied, pairs, rels, dropped, skipped } = await copyGlobEntry(fileEntry, opts.projectRoot, opts.skillOutputDir);
553
773
  return {
554
774
  dests: copied,
555
775
  dropped,
776
+ skipped,
556
777
  pending: fileEntry.integrity === true ? { entry: fileEntry, pairs, rels } : undefined,
557
778
  };
558
779
  }
@@ -610,15 +831,25 @@ async function applyNonGlobFileEntry(fileEntry, opts, bundledFileSet) {
610
831
  return {
611
832
  dests: [normalizeRelPath(fileEntry.dest)],
612
833
  dropped: [],
834
+ skipped: [],
613
835
  pending: optedIn
614
836
  ? { entry: fileEntry, pairs: [{ absSource: absoluteSource, absDest }], rels: undefined }
615
837
  : undefined,
616
838
  };
617
839
  }
618
- const { relDest, absSource, absDest } = await copyNonGlobEntry(fileEntry, absoluteSource, opts.projectRoot, opts.skillOutputDir);
840
+ // The WHOLE entry's filesystem work is guarded, not just its copy. Guarding the
841
+ // copy alone left the source `statSync` outside — so a source under a directory
842
+ // the process cannot traverse still escaped as a bare errno, which is the same
843
+ // defect one call earlier. An EXPLICIT entry is the spelling that unambiguously
844
+ // says "ship this file", so a bare errno here is if anything worse than on a
845
+ // glob: the author named the path, and the build owes them an error that names
846
+ // it back. (`copyNonGlobEntry`'s own deliberate errors — "does not exist", "is a
847
+ // directory" — carry no errno, so `attributed` passes them through untouched.)
848
+ const { relDest, absSource, absDest } = await attributed(fileEntry, absoluteSource, opts.projectRoot, () => copyNonGlobEntry(fileEntry, absoluteSource, opts.projectRoot, opts.skillOutputDir));
619
849
  return {
620
850
  dests: [relDest],
621
851
  dropped: [],
852
+ skipped: [],
622
853
  pending: optedIn ? { entry: fileEntry, pairs: [{ absSource, absDest }], rels: undefined } : undefined,
623
854
  };
624
855
  }
@@ -643,9 +874,16 @@ function relUnderDest(destRoot, dest) {
643
874
  * is still unexpected — so this stays a real assertion about the shipped bundle
644
875
  * rather than being weakened into a permissive one.
645
876
  */
646
- async function runDeferredIntegrity(pending, allDests, skillOutputDir) {
877
+ async function runDeferredIntegrity(pending, allDests, skillOutputDir, projectRoot) {
647
878
  for (const { entry, pairs, rels } of pending) {
648
- verifyFilesIntegrity(pairs);
879
+ // Through the same attribution point as the copy. `verifyFilesIntegrity`
880
+ // hashes both sides, so an unreadable source or dest surfaces here as a raw
881
+ // errno carrying an absolute path and no entry name — the same defect the
882
+ // copy loop had, in the one lane that had not been routed through the guard.
883
+ // Anchored at the ENTRY, not at `pairs[0]`: any of the entry's files can be
884
+ // the one that failed, and naming the first while the errno names another is
885
+ // worse than naming none. The errno itself carries the offending path.
886
+ await attributed(entry, safePath.resolve(safePath.join(projectRoot, entry.source)), projectRoot, async () => { verifyFilesIntegrity(pairs); }, 'verified');
649
887
  if (rels === undefined)
650
888
  continue;
651
889
  const destRoot = normalizeRelPath(entry.dest);
@@ -662,6 +900,7 @@ export async function applyFilesConfig(opts) {
662
900
  const bundledFileSet = new Set((opts.bundledFiles ?? []).map((f) => toForwardSlash(f)));
663
901
  const dests = [];
664
902
  const dropped = [];
903
+ const skipped = [];
665
904
  const pending = [];
666
905
  for (const fileEntry of opts.filesConfig) {
667
906
  const outcome = isGlob(fileEntry.source)
@@ -669,10 +908,11 @@ export async function applyFilesConfig(opts) {
669
908
  : await applyNonGlobFileEntry(fileEntry, opts, bundledFileSet);
670
909
  dests.push(...outcome.dests);
671
910
  dropped.push(...outcome.dropped);
911
+ skipped.push(...outcome.skipped);
672
912
  if (outcome.pending)
673
913
  pending.push(outcome.pending);
674
914
  }
675
- await runDeferredIntegrity(pending, dests, opts.skillOutputDir);
915
+ await runDeferredIntegrity(pending, dests, opts.skillOutputDir, opts.projectRoot);
676
916
  // A drop is only worth reporting if the file genuinely did not ship. In the
677
917
  // escape-hatch config the guide prescribes — a glob over a subtree plus an
678
918
  // explicit entry naming one never-packaged file inside it — the glob drops
@@ -682,6 +922,10 @@ export async function applyFilesConfig(opts) {
682
922
  // set (not on entry order) keeps the finding true whichever order they wrote.
683
923
  const shippedDests = new Set(dests.map((d) => normalizeRelPath(d)));
684
924
  const reportableDropped = dropped.filter((d) => !shippedDests.has(normalizeRelPath(d.dest)));
685
- return { dests, dropped: reportableDropped };
925
+ // Same filter, same reason: an explicit entry naming the link's dest resolves it
926
+ // deliberately, and reporting "it did not ship" about a path now in the bundle
927
+ // would tell the author their remediation failed when it worked.
928
+ const reportableSkipped = skipped.filter((s) => !shippedDests.has(normalizeRelPath(s.dest)));
929
+ return { dests, dropped: reportableDropped, skipped: reportableSkipped };
686
930
  }
687
931
  //# sourceMappingURL=files-config.js.map