@vibe-agent-toolkit/cli 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.
- package/README.md +4 -4
- package/dist/bin.js +41 -158
- package/dist/bin.js.map +1 -1
- package/dist/command-loaders.d.ts +2 -2
- package/dist/command-loaders.d.ts.map +1 -1
- package/dist/command-loaders.js +4 -2
- package/dist/command-loaders.js.map +1 -1
- package/dist/commands/ard/emit.d.ts +104 -0
- package/dist/commands/ard/emit.d.ts.map +1 -0
- package/dist/commands/ard/emit.js +223 -0
- package/dist/commands/ard/emit.js.map +1 -0
- package/dist/commands/ard/index.d.ts +12 -0
- package/dist/commands/ard/index.d.ts.map +1 -0
- package/dist/commands/ard/index.js +79 -0
- package/dist/commands/ard/index.js.map +1 -0
- package/dist/commands/ard/surfaces.d.ts +56 -0
- package/dist/commands/ard/surfaces.d.ts.map +1 -0
- package/dist/commands/ard/surfaces.js +150 -0
- package/dist/commands/ard/surfaces.js.map +1 -0
- package/dist/commands/audit.d.ts +75 -8
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +726 -127
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/claude/budget.d.ts +13 -0
- package/dist/commands/claude/budget.d.ts.map +1 -1
- package/dist/commands/claude/budget.js +80 -28
- package/dist/commands/claude/budget.js.map +1 -1
- package/dist/commands/claude/claude-config.d.ts.map +1 -1
- package/dist/commands/claude/claude-config.js +6 -0
- package/dist/commands/claude/claude-config.js.map +1 -1
- package/dist/commands/claude/context.js +1 -1
- package/dist/commands/claude/context.js.map +1 -1
- package/dist/commands/claude/marketplace/validate.d.ts +55 -22
- package/dist/commands/claude/marketplace/validate.d.ts.map +1 -1
- package/dist/commands/claude/marketplace/validate.js +380 -75
- package/dist/commands/claude/marketplace/validate.js.map +1 -1
- package/dist/commands/claude/org/helpers.d.ts +59 -1
- package/dist/commands/claude/org/helpers.d.ts.map +1 -1
- package/dist/commands/claude/org/helpers.js +58 -7
- package/dist/commands/claude/org/helpers.js.map +1 -1
- package/dist/commands/claude/org/index.d.ts.map +1 -1
- package/dist/commands/claude/org/index.js +24 -16
- package/dist/commands/claude/org/index.js.map +1 -1
- package/dist/commands/claude/org/skills.d.ts +509 -0
- package/dist/commands/claude/org/skills.d.ts.map +1 -1
- package/dist/commands/claude/org/skills.js +1364 -94
- package/dist/commands/claude/org/skills.js.map +1 -1
- package/dist/commands/claude/org/users.d.ts.map +1 -1
- package/dist/commands/claude/org/users.js +3 -1
- package/dist/commands/claude/org/users.js.map +1 -1
- package/dist/commands/claude/org/workspaces.d.ts.map +1 -1
- package/dist/commands/claude/org/workspaces.js +5 -2
- package/dist/commands/claude/org/workspaces.js.map +1 -1
- package/dist/commands/claude/plugin/build.d.ts +8 -0
- package/dist/commands/claude/plugin/build.d.ts.map +1 -1
- package/dist/commands/claude/plugin/build.js +16 -2
- package/dist/commands/claude/plugin/build.js.map +1 -1
- package/dist/commands/claude/plugin/tree-copy.d.ts +72 -0
- package/dist/commands/claude/plugin/tree-copy.d.ts.map +1 -1
- package/dist/commands/claude/plugin/tree-copy.js +215 -10
- package/dist/commands/claude/plugin/tree-copy.js.map +1 -1
- package/dist/commands/corpus/index.d.ts.map +1 -1
- package/dist/commands/corpus/index.js +3 -0
- package/dist/commands/corpus/index.js.map +1 -1
- package/dist/commands/corpus/report.d.ts +0 -1
- package/dist/commands/corpus/report.d.ts.map +1 -1
- package/dist/commands/corpus/report.js +0 -1
- package/dist/commands/corpus/report.js.map +1 -1
- package/dist/commands/corpus/runner.d.ts +58 -1
- package/dist/commands/corpus/runner.d.ts.map +1 -1
- package/dist/commands/corpus/runner.js +90 -15
- package/dist/commands/corpus/runner.js.map +1 -1
- package/dist/commands/corpus/scan.d.ts.map +1 -1
- package/dist/commands/corpus/scan.js +0 -1
- package/dist/commands/corpus/scan.js.map +1 -1
- package/dist/commands/doctor.d.ts +14 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +96 -18
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/okf/index.d.ts +11 -0
- package/dist/commands/okf/index.d.ts.map +1 -0
- package/dist/commands/okf/index.js +102 -0
- package/dist/commands/okf/index.js.map +1 -0
- package/dist/commands/okf/validate.d.ts +82 -0
- package/dist/commands/okf/validate.d.ts.map +1 -0
- package/dist/commands/okf/validate.js +157 -0
- package/dist/commands/okf/validate.js.map +1 -0
- package/dist/commands/phase-utils.d.ts +19 -3
- package/dist/commands/phase-utils.d.ts.map +1 -1
- package/dist/commands/phase-utils.js +26 -5
- package/dist/commands/phase-utils.js.map +1 -1
- package/dist/commands/rag/index-command.d.ts +27 -2
- package/dist/commands/rag/index-command.d.ts.map +1 -1
- package/dist/commands/rag/index-command.js +44 -7
- package/dist/commands/rag/index-command.js.map +1 -1
- package/dist/commands/rag/index.d.ts.map +1 -1
- package/dist/commands/rag/index.js +2 -0
- package/dist/commands/rag/index.js.map +1 -1
- package/dist/commands/resources/check-progress.d.ts +3 -0
- package/dist/commands/resources/check-progress.d.ts.map +1 -1
- package/dist/commands/resources/check-progress.js +6 -0
- package/dist/commands/resources/check-progress.js.map +1 -1
- package/dist/commands/resources/check.d.ts +33 -4
- package/dist/commands/resources/check.d.ts.map +1 -1
- package/dist/commands/resources/check.js +131 -44
- package/dist/commands/resources/check.js.map +1 -1
- package/dist/commands/resources/index.d.ts.map +1 -1
- package/dist/commands/resources/index.js +41 -9
- package/dist/commands/resources/index.js.map +1 -1
- package/dist/commands/resources/query.d.ts +18 -1
- package/dist/commands/resources/query.d.ts.map +1 -1
- package/dist/commands/resources/query.js +29 -4
- package/dist/commands/resources/query.js.map +1 -1
- package/dist/commands/resources/validate.d.ts +29 -0
- package/dist/commands/resources/validate.d.ts.map +1 -1
- package/dist/commands/resources/validate.js +116 -13
- package/dist/commands/resources/validate.js.map +1 -1
- package/dist/commands/skill/review-checklist.d.ts.map +1 -1
- package/dist/commands/skill/review-checklist.js +35 -0
- package/dist/commands/skill/review-checklist.js.map +1 -1
- package/dist/commands/skill/review.d.ts.map +1 -1
- package/dist/commands/skill/review.js +9 -2
- package/dist/commands/skill/review.js.map +1 -1
- package/dist/commands/skill/test/configure.d.ts +37 -0
- package/dist/commands/skill/test/configure.d.ts.map +1 -1
- package/dist/commands/skill/test/configure.js +57 -12
- package/dist/commands/skill/test/configure.js.map +1 -1
- package/dist/commands/skill/test/run.d.ts.map +1 -1
- package/dist/commands/skill/test/run.js +21 -6
- package/dist/commands/skill/test/run.js.map +1 -1
- package/dist/commands/skills/build.d.ts.map +1 -1
- package/dist/commands/skills/build.js +16 -3
- package/dist/commands/skills/build.js.map +1 -1
- package/dist/commands/skills/list.d.ts +2 -1
- package/dist/commands/skills/list.d.ts.map +1 -1
- package/dist/commands/skills/list.js +31 -9
- package/dist/commands/skills/list.js.map +1 -1
- package/dist/commands/skills/skill-discovery.d.ts +40 -1
- package/dist/commands/skills/skill-discovery.d.ts.map +1 -1
- package/dist/commands/skills/skill-discovery.js +32 -3
- package/dist/commands/skills/skill-discovery.js.map +1 -1
- package/dist/commands/skills/validate-command.d.ts.map +1 -1
- package/dist/commands/skills/validate-command.js +11 -3
- package/dist/commands/skills/validate-command.js.map +1 -1
- package/dist/commands/skills/validate.d.ts +66 -7
- package/dist/commands/skills/validate.d.ts.map +1 -1
- package/dist/commands/skills/validate.js +133 -29
- package/dist/commands/skills/validate.js.map +1 -1
- package/dist/commands/verify.d.ts +94 -3
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +192 -45
- package/dist/commands/verify.js.map +1 -1
- package/dist/skill-resolution/packaging-config.d.ts +49 -5
- package/dist/skill-resolution/packaging-config.d.ts.map +1 -1
- package/dist/skill-resolution/packaging-config.js +59 -19
- package/dist/skill-resolution/packaging-config.js.map +1 -1
- package/dist/skill-resolution/resolve-skill-reference.d.ts.map +1 -1
- package/dist/skill-resolution/resolve-skill-reference.js +6 -3
- package/dist/skill-resolution/resolve-skill-reference.js.map +1 -1
- package/dist/utils/command-error.d.ts +31 -0
- package/dist/utils/command-error.d.ts.map +1 -1
- package/dist/utils/command-error.js +45 -0
- package/dist/utils/command-error.js.map +1 -1
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +22 -6
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/edge-lens-evaluation.d.ts +83 -0
- package/dist/utils/edge-lens-evaluation.d.ts.map +1 -0
- package/dist/utils/edge-lens-evaluation.js +110 -0
- package/dist/utils/edge-lens-evaluation.js.map +1 -0
- package/dist/utils/optional-backend.d.ts +5 -4
- package/dist/utils/optional-backend.d.ts.map +1 -1
- package/dist/utils/optional-backend.js +5 -4
- package/dist/utils/optional-backend.js.map +1 -1
- package/dist/utils/project-root-policy.d.ts +20 -0
- package/dist/utils/project-root-policy.d.ts.map +1 -1
- package/dist/utils/project-root-policy.js +32 -0
- package/dist/utils/project-root-policy.js.map +1 -1
- package/dist/utils/projection-query.d.ts +41 -4
- package/dist/utils/projection-query.d.ts.map +1 -1
- package/dist/utils/projection-query.js +44 -10
- package/dist/utils/projection-query.js.map +1 -1
- package/dist/utils/projection-store.d.ts +41 -15
- package/dist/utils/projection-store.d.ts.map +1 -1
- package/dist/utils/projection-store.js +65 -18
- package/dist/utils/projection-store.js.map +1 -1
- package/dist/utils/repeatable-option.d.ts +37 -0
- package/dist/utils/repeatable-option.d.ts.map +1 -0
- package/dist/utils/repeatable-option.js +39 -0
- package/dist/utils/repeatable-option.js.map +1 -0
- package/dist/utils/resource-loader.d.ts +4 -3
- package/dist/utils/resource-loader.d.ts.map +1 -1
- package/dist/utils/resource-loader.js +61 -30
- package/dist/utils/resource-loader.js.map +1 -1
- package/dist/utils/root-argv.d.ts +53 -0
- package/dist/utils/root-argv.d.ts.map +1 -0
- package/dist/utils/root-argv.js +215 -0
- package/dist/utils/root-argv.js.map +1 -0
- package/dist/utils/run-integrity.d.ts +95 -0
- package/dist/utils/run-integrity.d.ts.map +1 -0
- package/dist/utils/run-integrity.js +102 -0
- package/dist/utils/run-integrity.js.map +1 -0
- package/dist/utils/sqlite-experimental-warning.d.ts +69 -0
- package/dist/utils/sqlite-experimental-warning.d.ts.map +1 -0
- package/dist/utils/sqlite-experimental-warning.js +144 -0
- package/dist/utils/sqlite-experimental-warning.js.map +1 -0
- package/dist/utils/user-context-scanner.d.ts +8 -0
- package/dist/utils/user-context-scanner.d.ts.map +1 -1
- package/dist/utils/user-context-scanner.js +4 -0
- package/dist/utils/user-context-scanner.js.map +1 -1
- package/dist/utils/validate-help-files.d.ts.map +1 -1
- package/dist/utils/validate-help-files.js +9 -2
- package/dist/utils/validate-help-files.js.map +1 -1
- package/docs/audit.md +120 -7
- package/docs/doctor.md +6 -6
- package/docs/index.md +1 -1
- package/docs/rag.md +3 -1
- package/docs/resources.md +5 -1
- package/docs/skills.md +47 -62
- package/package.json +18 -18
|
@@ -9,16 +9,64 @@
|
|
|
9
9
|
* - anything the caller names in `exclude` (glob, or a bare/trailing-slash
|
|
10
10
|
* directory name; a pattern matching nothing is returned to the caller)
|
|
11
11
|
*
|
|
12
|
+
* Symlinks are judged before anything is copied, identically on both crawl
|
|
13
|
+
* routes: a FILE symlink whose target this copy ships is copied by content and
|
|
14
|
+
* reported; one that leaves the source, does not resolve, names a directory,
|
|
15
|
+
* or names a file the copy leaves out stops the copy by name
|
|
16
|
+
* ({@link PluginSymlinkRefusedError}) before the first byte lands.
|
|
17
|
+
*
|
|
12
18
|
* Respects .gitignore via crawlDirectory (respectGitignore: true, the default).
|
|
13
19
|
* Returns counts keyed to the spec's YAML summary extension.
|
|
14
20
|
*/
|
|
15
21
|
import { existsSync } from 'node:fs';
|
|
16
|
-
import { copyFile, mkdir } from 'node:fs/promises';
|
|
22
|
+
import { copyFile, lstat, mkdir, readdir, realpath, stat } from 'node:fs/promises';
|
|
17
23
|
import { dirname } from 'node:path';
|
|
18
24
|
import { AGENT_INSTRUCTION_FILE_PATTERNS, toAnyDepthGlobs } from '@vibe-agent-toolkit/agent-skills';
|
|
19
25
|
import { isGlob, safePath, toForwardSlash } from '@vibe-agent-toolkit/utils';
|
|
20
|
-
import { crawlDirectory } from '@vibe-agent-toolkit/utils/crawl';
|
|
26
|
+
import { crawlDirectory, crawlPathFilter } from '@vibe-agent-toolkit/utils/crawl';
|
|
27
|
+
import { gitFindRoot } from '@vibe-agent-toolkit/utils/git';
|
|
21
28
|
import picomatch from 'picomatch';
|
|
29
|
+
function describeRefusal(entry) {
|
|
30
|
+
switch (entry.reason) {
|
|
31
|
+
case 'escapes-source':
|
|
32
|
+
return 'points outside the plugin source, so the bundle would carry a file from elsewhere on the build host';
|
|
33
|
+
case 'unresolvable':
|
|
34
|
+
return 'does not resolve to anything (dangling, or a loop)';
|
|
35
|
+
case 'directory':
|
|
36
|
+
return 'is a directory symlink; a bundle is a plain tree and cannot carry a directory alias';
|
|
37
|
+
case 'target-excluded':
|
|
38
|
+
return `resolves to '${entry.target}', which this copy leaves out (excluded, gitignored, untracked, or`
|
|
39
|
+
+ ' packaged by another phase), so the bundle would carry the excluded bytes under the link\'s name';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The copy stopped because the plugin source holds symlinks that cannot ship.
|
|
44
|
+
*
|
|
45
|
+
* The sibling of the listing refusal (`DirectoryListingRefusedError` from the
|
|
46
|
+
* crawl): thrown BEFORE the first byte is copied, with every offender named,
|
|
47
|
+
* so a failing build leaves no half-written bundle and the operator fixes the
|
|
48
|
+
* whole list in one pass. Measured before this existed: on the git route a
|
|
49
|
+
* directory or dangling symlink threw a raw `ENOTSUP` / `ENOENT` out of
|
|
50
|
+
* `copyFile` after earlier files had already landed, and a file symlink out of
|
|
51
|
+
* the source was copied by content with no diagnostic at all; on the walk route
|
|
52
|
+
* every symlink was silently dropped.
|
|
53
|
+
*
|
|
54
|
+
* The message speaks the bundle's coordinates (root-relative paths), never the
|
|
55
|
+
* build host's absolute paths, and names the remedy — the `exclude:` knob — in
|
|
56
|
+
* the same terms the listing refusal does.
|
|
57
|
+
*/
|
|
58
|
+
export class PluginSymlinkRefusedError extends Error {
|
|
59
|
+
refused;
|
|
60
|
+
constructor(refused) {
|
|
61
|
+
const lines = refused.map((entry) => ` - '${entry.path}' ${describeRefusal(entry)}`);
|
|
62
|
+
super(`Refusing to copy the plugin source: ${refused.length} symbolic link(s) cannot be shipped in a bundle,`
|
|
63
|
+
+ ` so nothing was copied.\n${lines.join('\n')}\n`
|
|
64
|
+
+ 'Replace each link with the file it points at, or name it in the plugin\'s `exclude:` list to leave'
|
|
65
|
+
+ ' it out of the bundle deliberately.');
|
|
66
|
+
this.name = 'PluginSymlinkRefusedError';
|
|
67
|
+
this.refused = refused;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
22
70
|
/**
|
|
23
71
|
* Built-in exclusions for the verbatim plugin copy.
|
|
24
72
|
*
|
|
@@ -104,6 +152,126 @@ function classifyRelative(rel) {
|
|
|
104
152
|
return 'mcpCopied';
|
|
105
153
|
return undefined;
|
|
106
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Split the crawl's paths into regular files and symlinks by `lstat`, on BOTH
|
|
157
|
+
* routes — this is the one predicate the two crawl lanes share. The walker
|
|
158
|
+
* never yields a symlink (see {@link sweepSymlinks}), so there it finds none;
|
|
159
|
+
* `git ls-files` yields every tracked symlink as an ordinary path, so there it
|
|
160
|
+
* is what stops `copyFile` from being the first thing to notice.
|
|
161
|
+
*/
|
|
162
|
+
async function partitionByLstat(sourceDir, files) {
|
|
163
|
+
const regular = [];
|
|
164
|
+
const links = [];
|
|
165
|
+
for (const abs of files) {
|
|
166
|
+
const entry = { abs, rel: toForwardSlash(safePath.relative(sourceDir, abs)) };
|
|
167
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- path returned by the crawl of sourceDir
|
|
168
|
+
const info = await lstat(abs);
|
|
169
|
+
(info.isSymbolicLink() ? links : regular).push(entry);
|
|
170
|
+
}
|
|
171
|
+
return { regular, links };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Every symlink under `sourceDir` that the exclude list admits — the half of the
|
|
175
|
+
* population the walk route cannot see.
|
|
176
|
+
*
|
|
177
|
+
* `crawlDirectory`'s walker skips every symlink entry unless `followSymlinks`
|
|
178
|
+
* is set, and following would not do either: a directory link would be
|
|
179
|
+
* traversed (its contents yielded under the link's name) and a dangling one
|
|
180
|
+
* silently skipped as absence. Neither is "here is a symlink, decide". So on
|
|
181
|
+
* the walk route this pass lists them itself, with the SAME membership filter
|
|
182
|
+
* the crawl applied, and hands each one to the same judgement the git route's
|
|
183
|
+
* tracked symlinks get. A directory that will not list is not swallowed here:
|
|
184
|
+
* the crawl has already refused it by name before this runs.
|
|
185
|
+
*
|
|
186
|
+
* Only the walk route needs it. Inside a repository `git ls-files` already
|
|
187
|
+
* yields the tracked symlinks, and a sweep there would also find IGNORED ones
|
|
188
|
+
* (`node_modules/.bin/*` is nothing but symlinks) and refuse a bundle for links
|
|
189
|
+
* the crawl had correctly left out. The route is read the way the crawl reads
|
|
190
|
+
* it — is there a repository above `sourceDir` — with one gap: a repository
|
|
191
|
+
* whose `git ls-files` fails makes the crawl fall back to the walker, and that
|
|
192
|
+
* run gets no sweep. A repository with no working `git` already hard-fails the
|
|
193
|
+
* default scan elsewhere, so the gap is not widened here.
|
|
194
|
+
*/
|
|
195
|
+
async function sweepSymlinks(sourceDir, isMember) {
|
|
196
|
+
const found = [];
|
|
197
|
+
const walk = async (dir) => {
|
|
198
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- walking beneath the validated sourceDir
|
|
199
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
200
|
+
for (const entry of entries) {
|
|
201
|
+
const abs = safePath.join(dir, entry.name);
|
|
202
|
+
const rel = toForwardSlash(safePath.relative(sourceDir, abs));
|
|
203
|
+
if (!isMember(rel))
|
|
204
|
+
continue;
|
|
205
|
+
if (entry.isSymbolicLink()) {
|
|
206
|
+
found.push({ abs, rel });
|
|
207
|
+
}
|
|
208
|
+
else if (entry.isDirectory()) {
|
|
209
|
+
await walk(abs);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
await walk(sourceDir);
|
|
214
|
+
return found;
|
|
215
|
+
}
|
|
216
|
+
/** Is `real` the source root itself, or beneath it? Both sides canonical. */
|
|
217
|
+
function isUnderSource(real, realSource) {
|
|
218
|
+
return real === realSource || real.startsWith(`${realSource}/`);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Judge one symlink: the one shape that ships (`'file'`, in-tree, copied by
|
|
222
|
+
* content) or the refusal that stops the copy.
|
|
223
|
+
*
|
|
224
|
+
* `shipped` is the set of root-relative REGULAR files this copy will land —
|
|
225
|
+
* after the crawl's membership (built-in exclusions, gitignore, tracking) and
|
|
226
|
+
* the caller's `exclude:`. A link ships only when its target is in that set:
|
|
227
|
+
* "in-tree" alone is not enough, because the tree is not the bundle. Every
|
|
228
|
+
* way a file can be left out — a caller pattern, `.claude-plugin/**`, a skill
|
|
229
|
+
* dir another phase produces, a gitignored or untracked path on the git route
|
|
230
|
+
* — is a way a one-line alias would put its bytes back, under a different
|
|
231
|
+
* name, with nothing to say so. Asking "would the target ship on its own?"
|
|
232
|
+
* closes all of them with the copy's own answer rather than re-deriving each
|
|
233
|
+
* exclusion here.
|
|
234
|
+
*/
|
|
235
|
+
async function classifySymlink(link, realSource, shipped) {
|
|
236
|
+
let real;
|
|
237
|
+
try {
|
|
238
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- a symlink the crawl of sourceDir returned
|
|
239
|
+
real = toForwardSlash(await realpath(link.abs));
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return { path: link.rel, reason: 'unresolvable' };
|
|
243
|
+
}
|
|
244
|
+
if (!isUnderSource(real, realSource))
|
|
245
|
+
return { path: link.rel, reason: 'escapes-source' };
|
|
246
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- canonical path proven to sit under sourceDir
|
|
247
|
+
if ((await stat(real)).isDirectory())
|
|
248
|
+
return { path: link.rel, reason: 'directory' };
|
|
249
|
+
const target = toForwardSlash(safePath.relative(realSource, real));
|
|
250
|
+
return shipped.has(target) ? 'file' : { path: link.rel, reason: 'target-excluded', target };
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Judge every symlink and split them: the ones that ship, and the ones that
|
|
254
|
+
* stop the copy. Sorted by path so the refusal reads the same on every
|
|
255
|
+
* filesystem — `readdir` order is not stable across platforms.
|
|
256
|
+
*/
|
|
257
|
+
async function judgeSymlinks(sourceDir, links, shipped) {
|
|
258
|
+
const copyable = [];
|
|
259
|
+
const refused = [];
|
|
260
|
+
if (links.length === 0)
|
|
261
|
+
return { copyable, refused };
|
|
262
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- sourceDir resolved from config
|
|
263
|
+
const realSource = toForwardSlash(await realpath(sourceDir));
|
|
264
|
+
for (const link of [...links].toSorted((a, b) => a.rel.localeCompare(b.rel))) {
|
|
265
|
+
const verdict = await classifySymlink(link, realSource, shipped);
|
|
266
|
+
if (verdict === 'file') {
|
|
267
|
+
copyable.push(link);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
refused.push(verdict);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return { copyable, refused };
|
|
274
|
+
}
|
|
107
275
|
export async function treeCopyPlugin(options) {
|
|
108
276
|
const { sourceDir, destDir, excludeSkillDirs = [], exclude: callerExclude = [], warn } = options;
|
|
109
277
|
const result = {
|
|
@@ -113,6 +281,7 @@ export async function treeCopyPlugin(options) {
|
|
|
113
281
|
mcpCopied: 0,
|
|
114
282
|
filesCopied: 0,
|
|
115
283
|
unusedExcludePatterns: [],
|
|
284
|
+
symlinksCopied: [],
|
|
116
285
|
};
|
|
117
286
|
// eslint-disable-next-line security/detect-non-literal-fs-filename -- sourceDir resolved from config
|
|
118
287
|
if (!existsSync(sourceDir)) {
|
|
@@ -128,6 +297,14 @@ export async function treeCopyPlugin(options) {
|
|
|
128
297
|
...EXCLUDE_PATTERNS,
|
|
129
298
|
...excludeSkillDirs.flatMap((name) => expandExcludePattern(`skills/${name}`)),
|
|
130
299
|
];
|
|
300
|
+
// A directory the crawl cannot LIST stops the copy, by name. The alternative —
|
|
301
|
+
// enumerate around it and copy what was seen — ships a plugin missing every
|
|
302
|
+
// file beneath that directory while the build reports success, which is the
|
|
303
|
+
// silent-drop shape this whole function is built to refuse (see the
|
|
304
|
+
// zero-match report below). Refusing here also runs BEFORE the first copy,
|
|
305
|
+
// so a build that fails leaves no half-written bundle to be mistaken for one.
|
|
306
|
+
// Only the walk route lists directories; inside a repository `git ls-files`
|
|
307
|
+
// answers and a refusal surfaces on the file copy instead.
|
|
131
308
|
const files = await crawlDirectory({
|
|
132
309
|
baseDir: sourceDir,
|
|
133
310
|
include: ['**/*'],
|
|
@@ -135,27 +312,55 @@ export async function treeCopyPlugin(options) {
|
|
|
135
312
|
absolute: true,
|
|
136
313
|
filesOnly: true,
|
|
137
314
|
respectGitignore: true,
|
|
315
|
+
unreadable: {
|
|
316
|
+
refuse: {
|
|
317
|
+
root: sourceDir,
|
|
318
|
+
remedy: 'Fix the permissions on that directory, or name it in the plugin\'s `exclude:` list to leave it out of the bundle deliberately.',
|
|
319
|
+
},
|
|
320
|
+
},
|
|
138
321
|
});
|
|
139
322
|
// Caller `exclude:` patterns are applied HERE rather than handed to the crawl,
|
|
140
323
|
// for two reasons: the two crawl lanes disagree about directory-shaped patterns
|
|
141
324
|
// (see expandExcludePattern), and a pattern that matches nothing must be
|
|
142
325
|
// reportable — a file the crawl never returns is a drop nothing can observe.
|
|
143
326
|
const excludeMatchers = buildExcludeMatchers(callerExclude);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
327
|
+
const notExcludedByCaller = (entry) => !isExcludedByCaller(entry.rel, excludeMatchers);
|
|
328
|
+
// Symlinks are decided BEFORE the first copy, on both routes, and by one
|
|
329
|
+
// judgement (see classifySymlink). The two lanes used to disagree twice over:
|
|
330
|
+
// the walker dropped every symlink silently, while `git ls-files` handed them
|
|
331
|
+
// to `copyFile`, which followed a link out of the source (shipping a file from
|
|
332
|
+
// elsewhere on the build host) and threw a raw errno on a directory or
|
|
333
|
+
// dangling one after earlier files had already landed. Collect first, judge,
|
|
334
|
+
// then copy — the same shape as the listing refusal above, so a build that
|
|
335
|
+
// fails on a symlink leaves no half-written bundle either.
|
|
336
|
+
const { regular, links: crawledLinks } = await partitionByLstat(sourceDir, files);
|
|
337
|
+
const links = gitFindRoot(sourceDir) === null
|
|
338
|
+
? [...crawledLinks, ...(await sweepSymlinks(sourceDir, crawlPathFilter(['**/*'], exclude)))]
|
|
339
|
+
: crawledLinks;
|
|
340
|
+
// A link the caller excluded is left out like any other excluded path — and
|
|
341
|
+
// that is the remedy the refusal names, so it must count as a hit here.
|
|
342
|
+
// The regular files are filtered FIRST so the judgement can ask whether a
|
|
343
|
+
// link's target is one of them (see classifySymlink) — and so a caller
|
|
344
|
+
// pattern that matches only a link's target still records its hit.
|
|
345
|
+
const shippedRegular = regular.filter(notExcludedByCaller);
|
|
346
|
+
const { copyable, refused } = await judgeSymlinks(sourceDir, links.filter(notExcludedByCaller), new Set(shippedRegular.map((entry) => entry.rel)));
|
|
347
|
+
if (refused.length > 0) {
|
|
348
|
+
throw new PluginSymlinkRefusedError(refused);
|
|
349
|
+
}
|
|
350
|
+
for (const entry of [...shippedRegular, ...copyable]) {
|
|
351
|
+
const target = safePath.join(destDir, entry.rel);
|
|
150
352
|
// eslint-disable-next-line security/detect-non-literal-fs-filename -- dest resolved from sourceDir+relative
|
|
151
353
|
await mkdir(dirname(target), { recursive: true });
|
|
152
|
-
|
|
354
|
+
// `copyFile` follows a symlink, which is the point for the in-tree file
|
|
355
|
+
// links that reach here: the bundle carries the target's bytes.
|
|
356
|
+
await copyFile(entry.abs, target);
|
|
153
357
|
result.filesCopied += 1;
|
|
154
|
-
const bucket = classifyRelative(rel);
|
|
358
|
+
const bucket = classifyRelative(entry.rel);
|
|
155
359
|
if (bucket) {
|
|
156
360
|
result[bucket] += 1;
|
|
157
361
|
}
|
|
158
362
|
}
|
|
363
|
+
result.symlinksCopied = copyable.map((entry) => entry.rel);
|
|
159
364
|
// A typo'd or wrong-shaped exclude pattern used to be perfectly silent: the
|
|
160
365
|
// knob no-oped and the junk shipped anyway. Zero matches is the only evidence
|
|
161
366
|
// the author can get, so it always leaves this function — as DATA, on the one
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree-copy.js","sourceRoot":"","sources":["../../../../src/commands/claude/plugin/tree-copy.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"tree-copy.js","sourceRoot":"","sources":["../../../../src/commands/claude/plugin/tree-copy.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/corpus/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,mBAAmB,IAAI,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/corpus/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,mBAAmB,IAAI,OAAO,CA6D7C"}
|
|
@@ -37,6 +37,9 @@ Output:
|
|
|
37
37
|
<--out>/<UTC-date>-<vat-short-sha>/
|
|
38
38
|
summary.yaml # index: per-plugin status + totals
|
|
39
39
|
<name>-audit.yaml # full audit output per plugin
|
|
40
|
+
# results: one entry per audited file; issues: run-level
|
|
41
|
+
# findings. An audit over 0 files is audit.status: error
|
|
42
|
+
# with one RESOURCE_CHECK_BROKEN, never a clean row.
|
|
40
43
|
<name>-review.md # full skill-review output (only with --with-review)
|
|
41
44
|
|
|
42
45
|
Exit codes:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/corpus/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,iBAAiB,EAA0B,MAAM,WAAW,CAAC;AAEtE,MAAM,UAAU,mBAAmB;IACjC,MAAM,MAAM,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC;IAErC,MAAM;SACH,WAAW,CAAC,uFAAuF,CAAC;SACpG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEtB,MAAM;SACH,OAAO,CAAC,kBAAkB,CAAC;SAC3B,WAAW,CAAC,qEAAqE,CAAC;SAClF,QAAQ,CAAC,aAAa,EAAE,+CAA+C,CAAC;SACxE,cAAc,CAAC,aAAa,EAAE,wEAAwE,CAAC;SACvG,MAAM,CAAC,eAAe,EAAE,uEAAuE,CAAC;SAChG,MAAM,CAAC,SAAS,EAAE,mDAAmD,CAAC;SACtE,MAAM,CAAC,KAAK,WAA0B,QAA4B;QACjE,MAAM,iBAAiB,CAAC,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAuB,CAAC,CAAC;IACjF,CAAC,CAAC;SACD,WAAW,CACV,OAAO,EACP
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/corpus/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,iBAAiB,EAA0B,MAAM,WAAW,CAAC;AAEtE,MAAM,UAAU,mBAAmB;IACjC,MAAM,MAAM,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC;IAErC,MAAM;SACH,WAAW,CAAC,uFAAuF,CAAC;SACpG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEtB,MAAM;SACH,OAAO,CAAC,kBAAkB,CAAC;SAC3B,WAAW,CAAC,qEAAqE,CAAC;SAClF,QAAQ,CAAC,aAAa,EAAE,+CAA+C,CAAC;SACxE,cAAc,CAAC,aAAa,EAAE,wEAAwE,CAAC;SACvG,MAAM,CAAC,eAAe,EAAE,uEAAuE,CAAC;SAChG,MAAM,CAAC,SAAS,EAAE,mDAAmD,CAAC;SACtE,MAAM,CAAC,KAAK,WAA0B,QAA4B;QACjE,MAAM,iBAAiB,CAAC,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAuB,CAAC,CAAC;IACjF,CAAC,CAAC;SACD,WAAW,CACV,OAAO,EACP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCL,CACI,CAAC;IAEJ,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../../src/commands/corpus/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAIjE,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,YAAY,CAAC;AACzE,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,OAAO,GAAG,SAAS,CAAC;AAEtD;;;;;;GAMG;AACH,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,EAAE,OAAO,CAAC;IAC5B,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../../src/commands/corpus/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAIjE,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,YAAY,CAAC;AACzE,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,OAAO,GAAG,SAAS,CAAC;AAEtD;;;;;;GAMG;AACH,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,EAAE,OAAO,CAAC;IAC5B,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE;QAAE,WAAW,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAChD,OAAO,EAAE,SAAS,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CAmC1D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAG1D;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAqBvF"}
|
|
@@ -67,7 +67,6 @@ export async function writeRunReport(report, outDir) {
|
|
|
67
67
|
mkdirSync(runDir, { recursive: true });
|
|
68
68
|
const totals = computeTotals(report);
|
|
69
69
|
const dump = {
|
|
70
|
-
schema_version: report.schema_version,
|
|
71
70
|
generated_at: report.generated_at,
|
|
72
71
|
vat_version: report.vat_version,
|
|
73
72
|
vat_commit: report.vat_commit,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/commands/corpus/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGnD,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/commands/corpus/report.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,13 +10,55 @@
|
|
|
10
10
|
* URL handling clones via Layer 1's `withClonedRepo` helper. Validation
|
|
11
11
|
* overlay (Task 5) is added on top of this base.
|
|
12
12
|
*/
|
|
13
|
-
import type {
|
|
13
|
+
import type { ValidationResult } from '@vibe-agent-toolkit/agent-skills';
|
|
14
|
+
import { type ValidationIssue } from '@vibe-agent-toolkit/schema';
|
|
15
|
+
import type { DirectoryRefusal } from '@vibe-agent-toolkit/utils/crawl';
|
|
16
|
+
import type { AuditOutcome, PluginRow, ReviewOutcome } from './report.js';
|
|
14
17
|
import type { PluginEntry } from './seed.js';
|
|
15
18
|
export interface RunnerOptions {
|
|
16
19
|
runDir: string;
|
|
17
20
|
withReview: boolean;
|
|
18
21
|
debug: boolean;
|
|
19
22
|
}
|
|
23
|
+
/** The per-plugin audit document: one result per file, plus the run-level findings that belong to none. */
|
|
24
|
+
export interface AuditDocument {
|
|
25
|
+
results: readonly ValidationResult[];
|
|
26
|
+
/** Present only when non-empty, so a clean run's document keeps its shape. */
|
|
27
|
+
issues?: readonly ValidationIssue[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Derive one plugin's audit row and its document from the per-file results.
|
|
31
|
+
*
|
|
32
|
+
* 🚨 **An audit over zero files is an ERROR, not "an empty tree audits
|
|
33
|
+
* cleanly".** `summarizeRun` over zero results gives `files_scanned: 0` and
|
|
34
|
+
* zero findings, and zero findings is `success` — so a source that resolved to
|
|
35
|
+
* a tree with nothing to audit (a wrong subdirectory, a clone whose default
|
|
36
|
+
* branch holds no skill, an excluded tree) wrote the same `summary.yaml` row as
|
|
37
|
+
* a clean plugin. The review lane on the identical fixture already answered
|
|
38
|
+
* `error` ("No SKILL.md files found"); this is the same question one lane
|
|
39
|
+
* over, and it now gets the same answer, through the shared mechanism in
|
|
40
|
+
* `run-integrity.ts`: one non-overridable `RESOURCE_CHECK_BROKEN` at `error`,
|
|
41
|
+
* counted in the row's `errors` and `findings_emitted`, carried in the document
|
|
42
|
+
* under `issues` beside the (empty) per-file results.
|
|
43
|
+
*
|
|
44
|
+
* `unloadable` is deliberately NOT the status here. Unloadable means the audit
|
|
45
|
+
* could not run — a missing path, a failed clone. This audit ran, and found no
|
|
46
|
+
* file to run over; the difference is the difference between "no verdict
|
|
47
|
+
* possible" and "this verdict is vacuous", and `computeTotals` counts them
|
|
48
|
+
* apart.
|
|
49
|
+
*
|
|
50
|
+
* Pure, and exported for that reason: the runner's loop clones, writes and
|
|
51
|
+
* catches, and the refusal has to be pinned without any of that.
|
|
52
|
+
*
|
|
53
|
+
* @param results - One validation result per audited file
|
|
54
|
+
* @param durationMs - The row's measured duration
|
|
55
|
+
* @param outputPath - Where the document is written, relative to the run dir
|
|
56
|
+
* @returns The row's audit outcome and the document to write for it
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildAuditOutcome(results: readonly ValidationResult[], durationMs: number, outputPath: string): {
|
|
59
|
+
audit: AuditOutcome;
|
|
60
|
+
document: AuditDocument;
|
|
61
|
+
};
|
|
20
62
|
/**
|
|
21
63
|
* Run audit + optional review against one plugin entry.
|
|
22
64
|
*/
|
|
@@ -26,6 +68,21 @@ export interface SkillReviewSection {
|
|
|
26
68
|
ok: boolean;
|
|
27
69
|
body: string;
|
|
28
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* One failed section per directory the review scan could not list.
|
|
73
|
+
*
|
|
74
|
+
* The review lane's population is what `scan` enumerates; a directory it could
|
|
75
|
+
* not enter is a set of skills that were never reviewed, and a review.md that
|
|
76
|
+
* omits them reads as one that covered the plugin. Filing the gap on the
|
|
77
|
+
* existing per-skill channel — `ok: false`, keyed by the directory — is what
|
|
78
|
+
* makes `buildReviewOutcome` grade the run `error` and the aggregate name the
|
|
79
|
+
* subtree, without inventing a second channel the report would have to learn.
|
|
80
|
+
*
|
|
81
|
+
* @param unreadable - What `ScanSummary.unreadable` carried
|
|
82
|
+
* @param scanPath - The plugin root every section is keyed relative to
|
|
83
|
+
* @returns Sections in the order the refusals were met
|
|
84
|
+
*/
|
|
85
|
+
export declare function unlistedDirectorySections(unreadable: readonly DirectoryRefusal[], scanPath: string): SkillReviewSection[];
|
|
29
86
|
/**
|
|
30
87
|
* Derive one plugin's `ReviewOutcome` from its per-skill sections.
|
|
31
88
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/commands/corpus/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/commands/corpus/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAEzE,OAAO,EAA8C,KAAK,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE9G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAUxE,OAAO,KAAK,EAAE,YAAY,EAAgB,SAAS,EAAE,aAAa,EAAiB,MAAM,aAAa,CAAC;AACvG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;CAChB;AAcD,2GAA2G;AAC3G,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACrC,8EAA8E;IAC9E,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB;IAAE,KAAK,EAAE,YAAY,CAAC;IAAC,QAAQ,EAAE,aAAa,CAAA;CAAE,CAqBlD;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,SAAS,CAAC,CAKpB;AAiED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAsDD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,SAAS,gBAAgB,EAAE,EACvC,QAAQ,EAAE,MAAM,GACf,kBAAkB,EAAE,CAYtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,EACvC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,aAAa,CAmBf"}
|
|
@@ -15,10 +15,11 @@ import { existsSync, writeFileSync } from 'node:fs';
|
|
|
15
15
|
import { dirname } from 'node:path';
|
|
16
16
|
import { scan } from '@vibe-agent-toolkit/discovery';
|
|
17
17
|
import { calculateValidationStatus, countBySeverity } from '@vibe-agent-toolkit/schema';
|
|
18
|
-
import { safePath } from '@vibe-agent-toolkit/utils';
|
|
18
|
+
import { safePath, toForwardSlash, transientRefusalClause } from '@vibe-agent-toolkit/utils';
|
|
19
19
|
import { isGitUrl, parseGitUrl } from '@vibe-agent-toolkit/utils/git';
|
|
20
20
|
import * as yaml from 'yaml';
|
|
21
21
|
import { createLogger } from '../../utils/logger.js';
|
|
22
|
+
import { nothingCheckedFinding } from '../../utils/run-integrity.js';
|
|
22
23
|
import { resolveVatBinPath } from '../../utils/vat-bin-path.js';
|
|
23
24
|
import { withClonedRepo } from '../audit/git-url-clone.js';
|
|
24
25
|
import { deriveScanRoot, getValidationResults } from '../audit.js';
|
|
@@ -32,6 +33,55 @@ const SKIPPED_REVIEW = { status: 'skipped', duration_ms: 0 };
|
|
|
32
33
|
function summarizeRun(allIssues, filesScanned) {
|
|
33
34
|
return { ...countBySeverity(allIssues), files_scanned: filesScanned };
|
|
34
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Derive one plugin's audit row and its document from the per-file results.
|
|
38
|
+
*
|
|
39
|
+
* 🚨 **An audit over zero files is an ERROR, not "an empty tree audits
|
|
40
|
+
* cleanly".** `summarizeRun` over zero results gives `files_scanned: 0` and
|
|
41
|
+
* zero findings, and zero findings is `success` — so a source that resolved to
|
|
42
|
+
* a tree with nothing to audit (a wrong subdirectory, a clone whose default
|
|
43
|
+
* branch holds no skill, an excluded tree) wrote the same `summary.yaml` row as
|
|
44
|
+
* a clean plugin. The review lane on the identical fixture already answered
|
|
45
|
+
* `error` ("No SKILL.md files found"); this is the same question one lane
|
|
46
|
+
* over, and it now gets the same answer, through the shared mechanism in
|
|
47
|
+
* `run-integrity.ts`: one non-overridable `RESOURCE_CHECK_BROKEN` at `error`,
|
|
48
|
+
* counted in the row's `errors` and `findings_emitted`, carried in the document
|
|
49
|
+
* under `issues` beside the (empty) per-file results.
|
|
50
|
+
*
|
|
51
|
+
* `unloadable` is deliberately NOT the status here. Unloadable means the audit
|
|
52
|
+
* could not run — a missing path, a failed clone. This audit ran, and found no
|
|
53
|
+
* file to run over; the difference is the difference between "no verdict
|
|
54
|
+
* possible" and "this verdict is vacuous", and `computeTotals` counts them
|
|
55
|
+
* apart.
|
|
56
|
+
*
|
|
57
|
+
* Pure, and exported for that reason: the runner's loop clones, writes and
|
|
58
|
+
* catches, and the refusal has to be pinned without any of that.
|
|
59
|
+
*
|
|
60
|
+
* @param results - One validation result per audited file
|
|
61
|
+
* @param durationMs - The row's measured duration
|
|
62
|
+
* @param outputPath - Where the document is written, relative to the run dir
|
|
63
|
+
* @returns The row's audit outcome and the document to write for it
|
|
64
|
+
*/
|
|
65
|
+
export function buildAuditOutcome(results, durationMs, outputPath) {
|
|
66
|
+
const fileIssues = results.flatMap((r) => r.issues);
|
|
67
|
+
const runIssues = nothingCheckedFinding(results.length, fileIssues, () => 'The audit ran over 0 files, so this row is not a verdict: a plugin with nothing'
|
|
68
|
+
+ ' to audit produces the same counts as a clean one. The source resolved to a tree'
|
|
69
|
+
+ ' holding no auditable file — usually a wrong subdirectory, a clone whose default'
|
|
70
|
+
+ ' branch carries no skill or plugin, or an excluded tree.'
|
|
71
|
+
+ ' `vat audit <source>` over the same path shows what the scan enumerates.');
|
|
72
|
+
const allIssues = [...runIssues, ...fileIssues];
|
|
73
|
+
const summary = summarizeRun(allIssues, results.length);
|
|
74
|
+
return {
|
|
75
|
+
audit: {
|
|
76
|
+
status: calculateValidationStatus(allIssues),
|
|
77
|
+
duration_ms: durationMs,
|
|
78
|
+
summary,
|
|
79
|
+
findings_emitted: summary.errors + summary.warnings + summary.info,
|
|
80
|
+
output_path: outputPath,
|
|
81
|
+
},
|
|
82
|
+
document: { results, ...(runIssues.length === 0 ? {} : { issues: runIssues }) },
|
|
83
|
+
};
|
|
84
|
+
}
|
|
35
85
|
/**
|
|
36
86
|
* Run audit + optional review against one plugin entry.
|
|
37
87
|
*/
|
|
@@ -64,19 +114,12 @@ async function auditAndRecord(entry, scanPath, opts) {
|
|
|
64
114
|
try {
|
|
65
115
|
// The corpus run root is the scanned plugin itself — one root per row.
|
|
66
116
|
const results = await getValidationResults(scanPath, true, {}, logger, deriveScanRoot(scanPath));
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const auditYamlPath = safePath.join(opts.runDir, `${entry.name}-audit.yaml`);
|
|
117
|
+
const outputPath = `${entry.name}-audit.yaml`;
|
|
118
|
+
const outcome = buildAuditOutcome(results, Date.now() - start, outputPath);
|
|
119
|
+
const auditYamlPath = safePath.join(opts.runDir, outputPath);
|
|
71
120
|
// eslint-disable-next-line security/detect-non-literal-fs-filename -- composed under run dir
|
|
72
|
-
writeFileSync(auditYamlPath, yaml.stringify(
|
|
73
|
-
audit =
|
|
74
|
-
status,
|
|
75
|
-
duration_ms: Date.now() - start,
|
|
76
|
-
summary,
|
|
77
|
-
findings_emitted: summary.errors + summary.warnings + summary.info,
|
|
78
|
-
output_path: `${entry.name}-audit.yaml`,
|
|
79
|
-
};
|
|
121
|
+
writeFileSync(auditYamlPath, yaml.stringify(outcome.document, { lineWidth: 0, aliasDuplicateObjects: false }), 'utf-8');
|
|
122
|
+
audit = outcome.audit;
|
|
80
123
|
}
|
|
81
124
|
catch (err) {
|
|
82
125
|
audit = {
|
|
@@ -139,6 +182,33 @@ function summarizeReview(sections) {
|
|
|
139
182
|
const reviewed = sections.filter((s) => s.ok).length;
|
|
140
183
|
return { reviewed, failed: sections.length - reviewed, skills_scanned: sections.length };
|
|
141
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* One failed section per directory the review scan could not list.
|
|
187
|
+
*
|
|
188
|
+
* The review lane's population is what `scan` enumerates; a directory it could
|
|
189
|
+
* not enter is a set of skills that were never reviewed, and a review.md that
|
|
190
|
+
* omits them reads as one that covered the plugin. Filing the gap on the
|
|
191
|
+
* existing per-skill channel — `ok: false`, keyed by the directory — is what
|
|
192
|
+
* makes `buildReviewOutcome` grade the run `error` and the aggregate name the
|
|
193
|
+
* subtree, without inventing a second channel the report would have to learn.
|
|
194
|
+
*
|
|
195
|
+
* @param unreadable - What `ScanSummary.unreadable` carried
|
|
196
|
+
* @param scanPath - The plugin root every section is keyed relative to
|
|
197
|
+
* @returns Sections in the order the refusals were met
|
|
198
|
+
*/
|
|
199
|
+
export function unlistedDirectorySections(unreadable, scanPath) {
|
|
200
|
+
return unreadable.map((refusal) => {
|
|
201
|
+
const relativePath = toForwardSlash(safePath.relative(scanPath, refusal.directory)) || '.';
|
|
202
|
+
const cause = refusal.transient
|
|
203
|
+
? `${transientRefusalClause(refusal.code)} — re-run before investigating anything`
|
|
204
|
+
: `listing was refused with ${refusal.code}`;
|
|
205
|
+
return {
|
|
206
|
+
relativePath,
|
|
207
|
+
ok: false,
|
|
208
|
+
body: `Directory could not be listed (${cause}); every skill beneath it was not reviewed.`,
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
}
|
|
142
212
|
/**
|
|
143
213
|
* Derive one plugin's `ReviewOutcome` from its per-skill sections.
|
|
144
214
|
*
|
|
@@ -182,7 +252,12 @@ async function runSkillReview(entry, scanPath, runDir) {
|
|
|
182
252
|
const reviewPath = safePath.join(runDir, `${entry.name}-review.md`);
|
|
183
253
|
const summary = await scan({ path: scanPath, recursive: true });
|
|
184
254
|
const skills = summary.results.filter((r) => r.format === 'agent-skill' && !r.isGitIgnored);
|
|
185
|
-
|
|
255
|
+
// Directories the scan could not list hold skills this review never saw. They
|
|
256
|
+
// enter the aggregate as failed sections — see `unlistedDirectorySections` —
|
|
257
|
+
// so the outcome is `error` and the review names the gap, while every skill
|
|
258
|
+
// that WAS found is still reviewed below.
|
|
259
|
+
const unlisted = unlistedDirectorySections(summary.unreadable, scanPath);
|
|
260
|
+
if (skills.length === 0 && unlisted.length === 0) {
|
|
186
261
|
return {
|
|
187
262
|
status: 'error',
|
|
188
263
|
duration_ms: Date.now() - start,
|
|
@@ -190,7 +265,7 @@ async function runSkillReview(entry, scanPath, runDir) {
|
|
|
190
265
|
error: `No SKILL.md files found under ${scanPath}`,
|
|
191
266
|
};
|
|
192
267
|
}
|
|
193
|
-
const sections = [];
|
|
268
|
+
const sections = [...unlisted];
|
|
194
269
|
for (const skill of skills) {
|
|
195
270
|
const skillDir = dirname(skill.path);
|
|
196
271
|
sections.push(reviewOneSkill(bin, skillDir, skill.relativePath));
|