@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.
- package/dist/files-config.d.ts +34 -1
- package/dist/files-config.d.ts.map +1 -1
- package/dist/files-config.js +280 -36
- package/dist/files-config.js.map +1 -1
- package/dist/fs-attribution.d.ts +45 -0
- package/dist/fs-attribution.d.ts.map +1 -0
- package/dist/fs-attribution.js +57 -0
- package/dist/fs-attribution.js.map +1 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/inventory/detectors/declared-but-missing.d.ts +1 -1
- package/dist/inventory/detectors/declared-but-missing.d.ts.map +1 -1
- package/dist/inventory/detectors/marketplace-source-missing.d.ts +1 -1
- package/dist/inventory/detectors/marketplace-source-missing.d.ts.map +1 -1
- package/dist/inventory/detectors/present-but-undeclared.d.ts +1 -1
- package/dist/inventory/detectors/present-but-undeclared.d.ts.map +1 -1
- package/dist/inventory/detectors/reference-target-missing.d.ts +1 -1
- package/dist/inventory/detectors/reference-target-missing.d.ts.map +1 -1
- package/dist/inventory/index.d.ts +1 -1
- package/dist/inventory/index.d.ts.map +1 -1
- package/dist/inventory/index.js +1 -1
- package/dist/inventory/index.js.map +1 -1
- package/dist/inventory/serialize.d.ts +4 -4
- package/dist/inventory/serialize.d.ts.map +1 -1
- package/dist/inventory/serialize.js +6 -6
- package/dist/inventory/serialize.js.map +1 -1
- package/dist/plugin-distribution-layout.d.ts.map +1 -1
- package/dist/plugin-distribution-layout.js +3 -1
- package/dist/plugin-distribution-layout.js.map +1 -1
- package/dist/post-build-checks.d.ts +1 -1
- package/dist/post-build-checks.d.ts.map +1 -1
- package/dist/post-build-checks.js.map +1 -1
- package/dist/projection/skill-extent.d.ts +247 -0
- package/dist/projection/skill-extent.d.ts.map +1 -0
- package/dist/projection/skill-extent.js +558 -0
- package/dist/projection/skill-extent.js.map +1 -0
- package/dist/schemas/agent-skill-frontmatter.js +1 -1
- package/dist/schemas/agent-skill-frontmatter.js.map +1 -1
- package/dist/skill-packager.d.ts +45 -7
- package/dist/skill-packager.d.ts.map +1 -1
- package/dist/skill-packager.js +123 -25
- package/dist/skill-packager.js.map +1 -1
- package/dist/skill-source/git-clone.d.ts +1 -1
- package/dist/skill-source/git-clone.d.ts.map +1 -1
- package/dist/skill-source/git-clone.js +10 -8
- package/dist/skill-source/git-clone.js.map +1 -1
- package/dist/skill-source/sources/url-source.d.ts.map +1 -1
- package/dist/skill-source/sources/url-source.js +2 -1
- package/dist/skill-source/sources/url-source.js.map +1 -1
- package/dist/skill-test/baseline-delta.d.ts +1575 -0
- package/dist/skill-test/baseline-delta.d.ts.map +1 -0
- package/dist/skill-test/baseline-delta.js +272 -0
- package/dist/skill-test/baseline-delta.js.map +1 -0
- package/dist/skill-test/baseline-integrity.d.ts +944 -0
- package/dist/skill-test/baseline-integrity.d.ts.map +1 -0
- package/dist/skill-test/baseline-integrity.js +3083 -0
- package/dist/skill-test/baseline-integrity.js.map +1 -0
- package/dist/skill-test/configure-writer.js +1 -1
- package/dist/skill-test/configure-writer.js.map +1 -1
- package/dist/skill-test/declared-env.d.ts.map +1 -1
- package/dist/skill-test/declared-env.js +2 -1
- package/dist/skill-test/declared-env.js.map +1 -1
- package/dist/skill-test/eval-executor.d.ts +20 -4
- package/dist/skill-test/eval-executor.d.ts.map +1 -1
- package/dist/skill-test/eval-executor.js +4 -4
- package/dist/skill-test/eval-executor.js.map +1 -1
- package/dist/skill-test/eval-fragment.d.ts +80 -6
- package/dist/skill-test/eval-fragment.d.ts.map +1 -1
- package/dist/skill-test/eval-fragment.js +61 -7
- package/dist/skill-test/eval-fragment.js.map +1 -1
- package/dist/skill-test/eval-grader.d.ts +1 -1
- package/dist/skill-test/eval-grader.d.ts.map +1 -1
- package/dist/skill-test/eval-grader.js +145 -8
- package/dist/skill-test/eval-grader.js.map +1 -1
- package/dist/skill-test/eval-inputs.d.ts +107 -6
- package/dist/skill-test/eval-inputs.d.ts.map +1 -1
- package/dist/skill-test/eval-inputs.js +173 -16
- package/dist/skill-test/eval-inputs.js.map +1 -1
- package/dist/skill-test/eval-lint.d.ts +13 -0
- package/dist/skill-test/eval-lint.d.ts.map +1 -1
- package/dist/skill-test/eval-lint.js +17 -4
- package/dist/skill-test/eval-lint.js.map +1 -1
- package/dist/skill-test/executor-prompt.d.ts +13 -5
- package/dist/skill-test/executor-prompt.d.ts.map +1 -1
- package/dist/skill-test/executor-prompt.js +8 -5
- package/dist/skill-test/executor-prompt.js.map +1 -1
- package/dist/skill-test/exit-codes.d.ts +53 -16
- package/dist/skill-test/exit-codes.d.ts.map +1 -1
- package/dist/skill-test/exit-codes.js +123 -40
- package/dist/skill-test/exit-codes.js.map +1 -1
- package/dist/skill-test/fragment-merge.d.ts +1 -18
- package/dist/skill-test/fragment-merge.d.ts.map +1 -1
- package/dist/skill-test/fragment-merge.js +63 -4
- package/dist/skill-test/fragment-merge.js.map +1 -1
- package/dist/skill-test/grader-prompt.d.ts +27 -7
- package/dist/skill-test/grader-prompt.d.ts.map +1 -1
- package/dist/skill-test/grader-prompt.js +253 -61
- package/dist/skill-test/grader-prompt.js.map +1 -1
- package/dist/skill-test/grader-text.d.ts +145 -0
- package/dist/skill-test/grader-text.d.ts.map +1 -0
- package/dist/skill-test/grader-text.js +410 -0
- package/dist/skill-test/grader-text.js.map +1 -0
- package/dist/skill-test/grading-adapter.d.ts +32 -0
- package/dist/skill-test/grading-adapter.d.ts.map +1 -1
- package/dist/skill-test/grading-adapter.js +50 -3
- package/dist/skill-test/grading-adapter.js.map +1 -1
- package/dist/skill-test/grading-schema.d.ts +160 -5
- package/dist/skill-test/grading-schema.d.ts.map +1 -1
- package/dist/skill-test/grading-schema.js +66 -7
- package/dist/skill-test/grading-schema.js.map +1 -1
- package/dist/skill-test/index.d.ts +1 -1
- package/dist/skill-test/index.d.ts.map +1 -1
- package/dist/skill-test/index.js +1 -1
- package/dist/skill-test/index.js.map +1 -1
- package/dist/skill-test/lock.d.ts +56 -0
- package/dist/skill-test/lock.d.ts.map +1 -1
- package/dist/skill-test/lock.js +75 -2
- package/dist/skill-test/lock.js.map +1 -1
- package/dist/skill-test/pipeline.d.ts +16 -0
- package/dist/skill-test/pipeline.d.ts.map +1 -1
- package/dist/skill-test/pipeline.js +16 -9
- package/dist/skill-test/pipeline.js.map +1 -1
- package/dist/skill-test/preflight.d.ts +1 -1
- package/dist/skill-test/preflight.d.ts.map +1 -1
- package/dist/skill-test/preflight.js +59 -15
- package/dist/skill-test/preflight.js.map +1 -1
- package/dist/skill-test/run-harness.d.ts +578 -35
- package/dist/skill-test/run-harness.d.ts.map +1 -1
- package/dist/skill-test/run-harness.js +1281 -148
- package/dist/skill-test/run-harness.js.map +1 -1
- package/dist/skill-test/staging.d.ts +8 -2
- package/dist/skill-test/staging.d.ts.map +1 -1
- package/dist/skill-test/staging.js +8 -4
- package/dist/skill-test/staging.js.map +1 -1
- package/dist/skill-test/tool-eval-schema.d.ts +6 -6
- package/dist/test-input.d.ts +57 -6
- package/dist/test-input.d.ts.map +1 -1
- package/dist/test-input.js +35 -12
- package/dist/test-input.js.map +1 -1
- package/dist/validators/agent-instruction-presence.d.ts +1 -1
- package/dist/validators/agent-instruction-presence.d.ts.map +1 -1
- package/dist/validators/agent-instruction-presence.js +2 -1
- package/dist/validators/agent-instruction-presence.js.map +1 -1
- package/dist/validators/bundled-resource-link-detection.d.ts +1 -1
- package/dist/validators/bundled-resource-link-detection.d.ts.map +1 -1
- package/dist/validators/bundled-resource-link-detection.js +1 -1
- package/dist/validators/bundled-resource-link-detection.js.map +1 -1
- package/dist/validators/compat-detectors.d.ts +1 -1
- package/dist/validators/compat-detectors.d.ts.map +1 -1
- package/dist/validators/compat-detectors.js +1 -1
- package/dist/validators/compat-detectors.js.map +1 -1
- package/dist/validators/cross-skill-dependency-detection.d.ts +1 -1
- package/dist/validators/cross-skill-dependency-detection.d.ts.map +1 -1
- package/dist/validators/cross-skill-dependency-detection.js +1 -1
- package/dist/validators/cross-skill-dependency-detection.js.map +1 -1
- package/dist/validators/description-style-detection.d.ts +1 -1
- package/dist/validators/description-style-detection.d.ts.map +1 -1
- package/dist/validators/description-style-detection.js +1 -1
- package/dist/validators/description-style-detection.js.map +1 -1
- package/dist/validators/frontmatter-validation.d.ts +1 -1
- package/dist/validators/frontmatter-validation.d.ts.map +1 -1
- package/dist/validators/frontmatter-validation.js +1 -1
- package/dist/validators/frontmatter-validation.js.map +1 -1
- package/dist/validators/imperative-body-detection.d.ts +1 -1
- package/dist/validators/imperative-body-detection.d.ts.map +1 -1
- package/dist/validators/imperative-body-detection.js +1 -1
- package/dist/validators/imperative-body-detection.js.map +1 -1
- package/dist/validators/index.d.ts +1 -1
- package/dist/validators/index.d.ts.map +1 -1
- package/dist/validators/kebab-case-detection.d.ts +1 -1
- package/dist/validators/kebab-case-detection.d.ts.map +1 -1
- package/dist/validators/kebab-case-detection.js +1 -1
- package/dist/validators/kebab-case-detection.js.map +1 -1
- package/dist/validators/marketplace-validator.js +1 -1
- package/dist/validators/marketplace-validator.js.map +1 -1
- package/dist/validators/packaging-validator.d.ts +96 -7
- package/dist/validators/packaging-validator.d.ts.map +1 -1
- package/dist/validators/packaging-validator.js +148 -21
- package/dist/validators/packaging-validator.js.map +1 -1
- package/dist/validators/plugin-hosted-shape.d.ts +1 -1
- package/dist/validators/plugin-hosted-shape.d.ts.map +1 -1
- package/dist/validators/plugin-hosted-shape.js +1 -1
- package/dist/validators/plugin-hosted-shape.js.map +1 -1
- package/dist/validators/plugin-recommended-fields.d.ts +1 -1
- package/dist/validators/plugin-recommended-fields.d.ts.map +1 -1
- package/dist/validators/plugin-recommended-fields.js +1 -1
- package/dist/validators/plugin-recommended-fields.js.map +1 -1
- package/dist/validators/registry-validator.js +1 -1
- package/dist/validators/registry-validator.js.map +1 -1
- package/dist/validators/rule-engine/rule-engine.d.ts +1 -1
- package/dist/validators/rule-engine/rule-engine.d.ts.map +1 -1
- package/dist/validators/rule-engine/rule-engine.js +20 -9
- package/dist/validators/rule-engine/rule-engine.js.map +1 -1
- package/dist/validators/skill-validator.d.ts.map +1 -1
- package/dist/validators/skill-validator.js +21 -3
- package/dist/validators/skill-validator.js.map +1 -1
- package/dist/validators/types.d.ts +1 -1
- package/dist/validators/types.d.ts.map +1 -1
- package/dist/validators/unified-validator.js +1 -1
- package/dist/validators/unified-validator.js.map +1 -1
- package/dist/validators/validation-rules.d.ts +1 -1
- package/dist/validators/validation-rules.d.ts.map +1 -1
- package/dist/validators/walker-to-issues.d.ts +7 -3
- package/dist/validators/walker-to-issues.d.ts.map +1 -1
- package/dist/validators/walker-to-issues.js +37 -8
- package/dist/validators/walker-to-issues.js.map +1 -1
- package/dist/walk-link-graph.d.ts +2 -1
- package/dist/walk-link-graph.d.ts.map +1 -1
- package/dist/walk-link-graph.js +37 -2
- package/dist/walk-link-graph.js.map +1 -1
- package/package.json +7 -7
package/dist/files-config.d.ts
CHANGED
|
@@ -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":"AAAA;;;;;;;;GAQG;AAMH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"files-config.d.ts","sourceRoot":"","sources":["../src/files-config.ts"],"names":[],"mappings":"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"}
|
package/dist/files-config.js
CHANGED
|
@@ -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 {
|
|
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
|
|
63
|
-
|
|
64
|
-
|
|
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 '${
|
|
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
|
-
//
|
|
236
|
-
|
|
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
|
-
//
|
|
303
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
//
|
|
416
|
-
|
|
417
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|