@vibe-agent-toolkit/cli 0.1.41-rc.7 → 0.1.41
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/bin/vat +24 -1
- package/dist/bin/vat.js +24 -1
- package/dist/bin/vat.js.map +1 -1
- package/dist/bin.js +23 -2
- package/dist/bin.js.map +1 -1
- package/dist/commands/agent/list.d.ts +11 -0
- package/dist/commands/agent/list.d.ts.map +1 -1
- package/dist/commands/agent/list.js +30 -11
- package/dist/commands/agent/list.js.map +1 -1
- package/dist/commands/audit/distributed-tree.d.ts +172 -0
- package/dist/commands/audit/distributed-tree.d.ts.map +1 -0
- package/dist/commands/audit/distributed-tree.js +319 -0
- package/dist/commands/audit/distributed-tree.js.map +1 -0
- package/dist/commands/audit.d.ts +69 -1
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +525 -174
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/build.d.ts +14 -0
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +103 -26
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/claude/marketplace/git-publish.d.ts.map +1 -1
- package/dist/commands/claude/marketplace/git-publish.js +21 -3
- package/dist/commands/claude/marketplace/git-publish.js.map +1 -1
- package/dist/commands/claude/marketplace/validate.d.ts +125 -0
- package/dist/commands/claude/marketplace/validate.d.ts.map +1 -1
- package/dist/commands/claude/marketplace/validate.js +251 -44
- package/dist/commands/claude/marketplace/validate.js.map +1 -1
- package/dist/commands/claude/org/stubs.d.ts +9 -1
- package/dist/commands/claude/org/stubs.d.ts.map +1 -1
- package/dist/commands/claude/org/stubs.js +11 -3
- package/dist/commands/claude/org/stubs.js.map +1 -1
- package/dist/commands/claude/plugin/build.d.ts +34 -4
- package/dist/commands/claude/plugin/build.d.ts.map +1 -1
- package/dist/commands/claude/plugin/build.js +187 -16
- package/dist/commands/claude/plugin/build.js.map +1 -1
- package/dist/commands/claude/plugin/plugin-files.d.ts.map +1 -1
- package/dist/commands/claude/plugin/plugin-files.js +6 -2
- package/dist/commands/claude/plugin/plugin-files.js.map +1 -1
- package/dist/commands/claude/plugin/plugin-json-merge.d.ts.map +1 -1
- package/dist/commands/claude/plugin/plugin-json-merge.js +15 -0
- package/dist/commands/claude/plugin/plugin-json-merge.js.map +1 -1
- package/dist/commands/claude/plugin/tree-copy.d.ts +40 -0
- package/dist/commands/claude/plugin/tree-copy.d.ts.map +1 -1
- package/dist/commands/claude/plugin/tree-copy.js +100 -4
- package/dist/commands/claude/plugin/tree-copy.js.map +1 -1
- package/dist/commands/corpus/report.d.ts +22 -4
- package/dist/commands/corpus/report.d.ts.map +1 -1
- package/dist/commands/corpus/report.js +1 -0
- package/dist/commands/corpus/report.js.map +1 -1
- package/dist/commands/corpus/runner.d.ts +16 -1
- package/dist/commands/corpus/runner.d.ts.map +1 -1
- package/dist/commands/corpus/runner.js +40 -22
- package/dist/commands/corpus/runner.js.map +1 -1
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/inventory.d.ts +2 -0
- package/dist/commands/inventory.d.ts.map +1 -1
- package/dist/commands/inventory.js +45 -4
- package/dist/commands/inventory.js.map +1 -1
- package/dist/commands/phase-utils.d.ts +183 -14
- package/dist/commands/phase-utils.d.ts.map +1 -1
- package/dist/commands/phase-utils.js +294 -29
- package/dist/commands/phase-utils.js.map +1 -1
- package/dist/commands/positional-args.d.ts +36 -0
- package/dist/commands/positional-args.d.ts.map +1 -0
- package/dist/commands/positional-args.js +52 -0
- package/dist/commands/positional-args.js.map +1 -0
- package/dist/commands/rag/query-command.d.ts +40 -0
- package/dist/commands/rag/query-command.d.ts.map +1 -1
- package/dist/commands/rag/query-command.js +47 -20
- package/dist/commands/rag/query-command.js.map +1 -1
- package/dist/commands/resources/index.d.ts.map +1 -1
- package/dist/commands/resources/index.js +27 -9
- package/dist/commands/resources/index.js.map +1 -1
- package/dist/commands/resources/scan.d.ts +37 -0
- package/dist/commands/resources/scan.d.ts.map +1 -1
- package/dist/commands/resources/scan.js +47 -32
- package/dist/commands/resources/scan.js.map +1 -1
- package/dist/commands/resources/validate.d.ts +136 -1
- package/dist/commands/resources/validate.d.ts.map +1 -1
- package/dist/commands/resources/validate.js +105 -66
- 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 +5 -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 +7 -3
- package/dist/commands/skill/review.js.map +1 -1
- package/dist/commands/skill/test/run.d.ts +1 -1
- package/dist/commands/skill/test/run.d.ts.map +1 -1
- package/dist/commands/skill/test/run.js +8 -6
- package/dist/commands/skill/test/run.js.map +1 -1
- package/dist/commands/skills/build.d.ts +349 -11
- package/dist/commands/skills/build.d.ts.map +1 -1
- package/dist/commands/skills/build.js +901 -97
- package/dist/commands/skills/build.js.map +1 -1
- package/dist/commands/skills/command-helpers.d.ts +6 -10
- package/dist/commands/skills/command-helpers.d.ts.map +1 -1
- package/dist/commands/skills/command-helpers.js +6 -22
- package/dist/commands/skills/command-helpers.js.map +1 -1
- package/dist/commands/skills/list.d.ts +18 -0
- package/dist/commands/skills/list.d.ts.map +1 -1
- package/dist/commands/skills/list.js +41 -15
- package/dist/commands/skills/list.js.map +1 -1
- package/dist/commands/skills/package.d.ts +15 -0
- package/dist/commands/skills/package.d.ts.map +1 -1
- package/dist/commands/skills/package.js +30 -20
- package/dist/commands/skills/package.js.map +1 -1
- package/dist/commands/skills/scope-guard.d.ts +64 -0
- package/dist/commands/skills/scope-guard.d.ts.map +1 -0
- package/dist/commands/skills/scope-guard.js +86 -0
- package/dist/commands/skills/scope-guard.js.map +1 -0
- package/dist/commands/skills/validate-command.d.ts.map +1 -1
- package/dist/commands/skills/validate-command.js +41 -7
- package/dist/commands/skills/validate-command.js.map +1 -1
- package/dist/commands/skills/validate.d.ts +44 -3
- package/dist/commands/skills/validate.d.ts.map +1 -1
- package/dist/commands/skills/validate.js +217 -53
- package/dist/commands/skills/validate.js.map +1 -1
- package/dist/commands/validate.d.ts +18 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +89 -68
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/verify.d.ts +113 -6
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +440 -123
- package/dist/commands/verify.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/skill-resolution/index.d.ts +1 -1
- package/dist/skill-resolution/index.d.ts.map +1 -1
- package/dist/skill-resolution/index.js +1 -1
- package/dist/skill-resolution/index.js.map +1 -1
- package/dist/skill-resolution/packaging-config.d.ts +15 -2
- package/dist/skill-resolution/packaging-config.d.ts.map +1 -1
- package/dist/skill-resolution/packaging-config.js +39 -2
- package/dist/skill-resolution/packaging-config.js.map +1 -1
- package/dist/utils/command-error.d.ts +34 -1
- package/dist/utils/command-error.d.ts.map +1 -1
- package/dist/utils/command-error.js +32 -1
- package/dist/utils/command-error.js.map +1 -1
- package/dist/utils/help-loader.d.ts +6 -2
- package/dist/utils/help-loader.d.ts.map +1 -1
- package/dist/utils/help-loader.js +9 -16
- package/dist/utils/help-loader.js.map +1 -1
- package/dist/utils/issue-rendering.d.ts +130 -0
- package/dist/utils/issue-rendering.d.ts.map +1 -1
- package/dist/utils/issue-rendering.js +147 -0
- package/dist/utils/issue-rendering.js.map +1 -1
- package/dist/utils/issue-severity.d.ts +51 -0
- package/dist/utils/issue-severity.d.ts.map +1 -0
- package/dist/utils/issue-severity.js +63 -0
- package/dist/utils/issue-severity.js.map +1 -0
- package/dist/utils/output.d.ts +54 -5
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +111 -17
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/relativize-paths.d.ts +45 -0
- package/dist/utils/relativize-paths.d.ts.map +1 -0
- package/dist/utils/relativize-paths.js +67 -0
- package/dist/utils/relativize-paths.js.map +1 -0
- package/dist/utils/resource-loader.d.ts +21 -2
- package/dist/utils/resource-loader.d.ts.map +1 -1
- package/dist/utils/resource-loader.js +85 -11
- package/dist/utils/resource-loader.js.map +1 -1
- package/dist/utils/skill-packaging-config.d.ts +22 -1
- package/dist/utils/skill-packaging-config.d.ts.map +1 -1
- package/dist/utils/skill-packaging-config.js +26 -0
- package/dist/utils/skill-packaging-config.js.map +1 -1
- package/dist/utils/verdict-helpers.d.ts +4 -3
- package/dist/utils/verdict-helpers.d.ts.map +1 -1
- package/dist/utils/verdict-helpers.js +6 -13
- package/dist/utils/verdict-helpers.js.map +1 -1
- package/dist/version.d.ts +22 -2
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +31 -2
- package/dist/version.js.map +1 -1
- package/docs/index.md +12 -0
- package/docs/rag.md +9 -4
- package/docs/resources.md +30 -14
- package/docs/skills.md +25 -7
- package/package.json +12 -12
package/dist/commands/verify.js
CHANGED
|
@@ -5,27 +5,44 @@
|
|
|
5
5
|
* 1. vat resources validate (link integrity, collection schemas)
|
|
6
6
|
* 2. vat skills validate (SKILL.md frontmatter validation)
|
|
7
7
|
* 3. vat claude marketplace validate (strict marketplace validation, when configured)
|
|
8
|
-
* 4.
|
|
8
|
+
* 4. files-config-dests (in-process; every `files:` dest exists in the built output)
|
|
9
|
+
* 5. packaged-content (in-process; built bundles carry nothing that must not ship)
|
|
10
|
+
* 6. consistency check (in-process; skill distribution integrity — package.json, plugin assignment)
|
|
11
|
+
*
|
|
12
|
+
* 1–3 are subprocess phases chosen by {@link selectVerifyPhases}; 4–6 run here and
|
|
13
|
+
* are chosen by `selectInProcessVerifyPhases`. Both sets are config-gated, and both
|
|
14
|
+
* are announced on startup.
|
|
9
15
|
*/
|
|
10
16
|
import { existsSync } from 'node:fs';
|
|
11
17
|
import { basename } from 'node:path';
|
|
12
18
|
import { calculateValidationStatus, countBySeverity, } from '@vibe-agent-toolkit/agent-schema';
|
|
13
|
-
import { computeTreeCopiedSkillLocations,
|
|
19
|
+
import { computeTreeCopiedSkillLocations, detectPackagedAgentInstructionFiles, explicitFilesConfigDests, } from '@vibe-agent-toolkit/agent-skills';
|
|
14
20
|
import { safePath } from '@vibe-agent-toolkit/utils';
|
|
15
21
|
import { Command } from 'commander';
|
|
16
22
|
import { handleCommandError } from '../utils/command-error.js';
|
|
17
23
|
import { loadConfig } from '../utils/config-loader.js';
|
|
18
|
-
import {
|
|
24
|
+
import { formatIssueLines } from '../utils/issue-rendering.js';
|
|
25
|
+
import { resolveIssueSeverity } from '../utils/issue-severity.js';
|
|
19
26
|
import { writeYamlOutput } from '../utils/output.js';
|
|
20
27
|
import { requireProjectRoot } from '../utils/project-root-policy.js';
|
|
28
|
+
import { mergeSkillPackagingConfig } from '../utils/skill-packaging-config.js';
|
|
21
29
|
import { runConsistencyChecks, } from './consistency-check.js';
|
|
22
|
-
import { aggregatePhaseStatus, exitCodeForPhases,
|
|
30
|
+
import { addRetiredOnlyOption, aggregatePhaseStatus, applyPhaseSelection, createPhaseContext, decidePhaseSelection, exitCodeForPhases, rejectRetiredOnly, runPhase, } from './phase-utils.js';
|
|
31
|
+
import { rejectPositionalArguments } from './positional-args.js';
|
|
23
32
|
import { discoverSkillsFromConfig } from './skills/skill-discovery.js';
|
|
33
|
+
/**
|
|
34
|
+
* Measured full-run duration on the 90-skill / 1,041-document adopter, cited by
|
|
35
|
+
* the retired-`--only` message: resources 12.5s + skills 15.6s + marketplace
|
|
36
|
+
* 1.0s + consistency under a second.
|
|
37
|
+
*/
|
|
38
|
+
const VERIFY_FULL_RUN_SECONDS = 32;
|
|
39
|
+
/** How this command names itself in every user-facing diagnostic. */
|
|
40
|
+
const COMMAND_NAME = 'vat verify';
|
|
24
41
|
export function createVerifyTopLevelCommand() {
|
|
25
42
|
const command = new Command('verify');
|
|
26
|
-
command
|
|
43
|
+
addRetiredOnlyOption(command)
|
|
27
44
|
.description('Verify built artifacts (resources + skills + marketplace + consistency); marketplace/consistency read dist/ — run after vat build')
|
|
28
|
-
.option('--
|
|
45
|
+
.option('-v, --verbose', 'Show all inspected resources, including those without issues')
|
|
29
46
|
.option('--debug', 'Enable debug logging')
|
|
30
47
|
.action(verifyTopLevelCommand)
|
|
31
48
|
.addHelpText('after', `
|
|
@@ -36,26 +53,60 @@ Description:
|
|
|
36
53
|
For source-only validation that needs no build (resources + skills,
|
|
37
54
|
suitable for pre-commit and CI-before-build), use 'vat validate'.
|
|
38
55
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
Config-driven, exactly like 'vat validate': a phase runs only when its
|
|
57
|
+
config block is present. There is no phase filter — the whole run takes
|
|
58
|
+
about as long as its slowest two phases, so a CI gate cannot lose coverage
|
|
59
|
+
by naming a phase whose config key was renamed out from under it.
|
|
60
|
+
|
|
61
|
+
Phases (subprocess):
|
|
62
|
+
resources → link integrity, collection frontmatter schemas (when 'resources:' configured)
|
|
63
|
+
skills → SKILL.md frontmatter and packaging validation (when 'skills:' configured)
|
|
64
|
+
marketplace → strict marketplace validation (when 'claude.marketplaces:' configured)
|
|
65
|
+
|
|
66
|
+
Phases (in-process, run after the above, when 'skills:' is configured):
|
|
67
|
+
files-config-dests → every 'files:' dest exists in the built output. Appears
|
|
68
|
+
in the document only when a dest is missing.
|
|
69
|
+
packaged-content → built skill bundles carry no repo-internal agent-instruction
|
|
70
|
+
file (CLAUDE.md, AGENTS.md, GEMINI.md). A dest an explicit
|
|
71
|
+
'files:' entry names is honoured, not reported.
|
|
72
|
+
consistency → skill distribution integrity (package.json, plugin assignment)
|
|
73
|
+
|
|
74
|
+
A run with a 'skills:' block runs all four of skills, files-config-dests,
|
|
75
|
+
packaged-content and consistency: they read that same config block. Without it
|
|
76
|
+
no in-process phase has anything to read, so none run.
|
|
77
|
+
|
|
78
|
+
The startup line on stderr names, in order, the phases that will inspect
|
|
79
|
+
something. A phase that would consult nothing is not listed; a phase that does
|
|
80
|
+
inspect its inputs is listed even when it finds nothing to report.
|
|
44
81
|
|
|
45
82
|
Output:
|
|
46
|
-
YAML
|
|
47
|
-
per phase: status (success | warning | error | system-error).
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
83
|
+
ONE YAML document → stdout
|
|
84
|
+
per phase: status (success | warning | error | system-error). A subprocess
|
|
85
|
+
phase's own report is captured and nested under 'report', so the whole run
|
|
86
|
+
is a single parseable document (a phase's stdout is never streamed
|
|
87
|
+
through). A phase's status comes from the child's REPORTED status, not
|
|
88
|
+
from its exit code — an exit code cannot express 'warning'. In-process
|
|
89
|
+
phases also publish issueCounts {errors, warnings, info}; 'consistency' and
|
|
90
|
+
'packaged-content' carry their findings into the document too, while
|
|
91
|
+
'files-config-dests' publishes counts only and lists the missing dests on
|
|
92
|
+
stderr.
|
|
93
|
+
Progress and validation errors → stderr (streamed live)
|
|
94
|
+
|
|
95
|
+
By default each subprocess phase reports a per-asset summary plus the assets
|
|
96
|
+
that have findings. '--verbose' is forwarded to every subprocess phase, which
|
|
97
|
+
then also lists the assets it inspected and found nothing to report.
|
|
52
98
|
|
|
53
99
|
Exit Codes:
|
|
54
100
|
0 - All phases passed (a warning does not fail the run — read status/issueCounts)
|
|
55
|
-
1 - Validation errors found
|
|
56
|
-
skills: block (an explicit request for a phase that cannot run)
|
|
101
|
+
1 - Validation errors found
|
|
57
102
|
2 - System error (this command's own, or propagated from a phase that could
|
|
58
|
-
not run: exited 2, was killed by a signal,
|
|
103
|
+
not run: exited 2, was killed by a signal, was never spawned, or wrote
|
|
104
|
+
output that could not be parsed), or a usage error such as passing a path
|
|
105
|
+
|
|
106
|
+
Arguments:
|
|
107
|
+
None. Scope comes from vibe-agent-toolkit.config.yaml, never from the command
|
|
108
|
+
line — a path argument is rejected (exit 2) rather than discarded. To inspect
|
|
109
|
+
ONE skill or bundle by path, use 'vat audit <path>'.
|
|
59
110
|
|
|
60
111
|
Requirements:
|
|
61
112
|
projectRoot: required (errors if no vibe-agent-toolkit.config.yaml or .git/ ancestor)
|
|
@@ -64,9 +115,7 @@ Requirements:
|
|
|
64
115
|
See docs/concepts/roots-and-config.md for terminology.
|
|
65
116
|
|
|
66
117
|
Example:
|
|
67
|
-
$ vat verify # Verify
|
|
68
|
-
$ vat verify --only skills # Verify skills only
|
|
69
|
-
$ vat verify --only marketplace # Verify marketplace only
|
|
118
|
+
$ vat verify # Verify every configured phase
|
|
70
119
|
`);
|
|
71
120
|
return command;
|
|
72
121
|
}
|
|
@@ -77,38 +126,73 @@ Example:
|
|
|
77
126
|
function skillNameToFsPath(name) {
|
|
78
127
|
return name.replaceAll(':', '__');
|
|
79
128
|
}
|
|
129
|
+
/** The `files:` entries governing a candidate, `[]` when the skill declares none. */
|
|
130
|
+
function filesOf(entry) {
|
|
131
|
+
return entry.packaging.files ?? [];
|
|
132
|
+
}
|
|
80
133
|
/**
|
|
81
|
-
* Register a check for (skillName, outputDir,
|
|
134
|
+
* Register a check for (skillName, outputDir, packaging) in the dedup map.
|
|
82
135
|
*
|
|
83
136
|
* Skips silently if:
|
|
84
|
-
* - mergedFiles is empty (no entries to verify)
|
|
85
137
|
* - outputDir does not exist on disk (not a candidate)
|
|
86
138
|
* - the key was already added (dedup guard)
|
|
139
|
+
*
|
|
140
|
+
* An EMPTY `files:` block is registered, not skipped: {@link checkFilesConfigDests}
|
|
141
|
+
* has nothing to verify for such a skill and filters it out itself, but
|
|
142
|
+
* {@link checkPackagedAgentInstructionFiles} must still crawl that bundle — a skill
|
|
143
|
+
* with no `files:` block is exactly the one whose agent-instruction file arrived by
|
|
144
|
+
* some other route, and dropping it here would make the crawl blind to it.
|
|
87
145
|
*/
|
|
88
|
-
function tryAddCheckEntry(checks, skillName, outputDir,
|
|
89
|
-
if (mergedFiles.length === 0)
|
|
90
|
-
return;
|
|
146
|
+
function tryAddCheckEntry(checks, skillName, outputDir, packaging) {
|
|
91
147
|
// eslint-disable-next-line security/detect-non-literal-fs-filename -- outputDir is resolved from config, not user input
|
|
92
148
|
if (!existsSync(outputDir))
|
|
93
149
|
return;
|
|
94
150
|
const key = `${skillName}\0${outputDir}`;
|
|
95
151
|
if (!checks.has(key)) {
|
|
96
|
-
checks.set(key, { skillName, outputDir,
|
|
152
|
+
checks.set(key, { skillName, outputDir, packaging });
|
|
97
153
|
}
|
|
98
154
|
}
|
|
99
155
|
/**
|
|
100
|
-
*
|
|
156
|
+
* Every built skill bundle this project's config accounts for that EXISTS on disk,
|
|
157
|
+
* with the `files:` entries that govern it.
|
|
101
158
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* -
|
|
159
|
+
* ONE enumeration of "where did `vat build` write this skill", shared by every
|
|
160
|
+
* in-process verify phase, in the two locations the build actually uses:
|
|
161
|
+
* - Pool skills: `dist/skills/<fsName>/`
|
|
162
|
+
* - Tree-copy skills: `dist/.claude/plugins/.../skills/<name>/`
|
|
105
163
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
164
|
+
* Returns `[]` (never throws) for an unreadable config: `vat verify`'s subprocess
|
|
165
|
+
* phases report the real config error, and an in-process phase must not race them
|
|
166
|
+
* with a second, worse diagnosis.
|
|
108
167
|
*
|
|
109
|
-
*
|
|
168
|
+
* The pool arm enumerates the skills the run DISCOVERED, unioned with the keys of
|
|
169
|
+
* `skills.config`. It used to be the config keys alone, which made both in-process
|
|
170
|
+
* phases blind to the common case: a per-skill `config:` block is optional, so a
|
|
171
|
+
* skill discovered only by `skills.include` had no key and its bundle was never
|
|
172
|
+
* looked at. Measured on a two-skill fixture with an identical `CLAUDE.md` planted
|
|
173
|
+
* in each built bundle, `packaged-content` reported ONE finding and exit 0 — the
|
|
174
|
+
* phase was structurally blind to the population its own doc comment cites as the
|
|
175
|
+
* motivating incident. The same line made `files-config-dests` a total no-op for a
|
|
176
|
+
* project whose `files:` entries live only under `skills.defaults`, while the
|
|
177
|
+
* startup banner still announced that the phase ran.
|
|
178
|
+
*
|
|
179
|
+
* The config keys stay in the union rather than being replaced by discovery: a key
|
|
180
|
+
* naming a skill discovery does not reach (a renamed glob, a stale entry) still
|
|
181
|
+
* points at a bundle that may be sitting in `dist/`, and dropping it would trade
|
|
182
|
+
* one blindness for another.
|
|
183
|
+
*
|
|
184
|
+
* `discovered` is a REQUIRED parameter, not an optional convenience: discovery
|
|
185
|
+
* crawls the whole project, `vat verify` already needs the same list for its
|
|
186
|
+
* consistency phase, and an optional parameter here would let a call site quietly
|
|
187
|
+
* re-crawl (or, worse, skip discovery and reinstate the blindness above).
|
|
188
|
+
*
|
|
189
|
+
* The merge goes through {@link mergeSkillPackagingConfig} — the ONE helper every
|
|
190
|
+
* lane uses — so `vat verify` and `vat build` cannot disagree about a skill's
|
|
191
|
+
* effective config. Doing the `files:` half by hand here and leaving `validation:`
|
|
192
|
+
* unread is what made `severity.PACKAGED_AGENT_INSTRUCTION_FILE: ignore` a no-op in
|
|
193
|
+
* this command.
|
|
110
194
|
*/
|
|
111
|
-
|
|
195
|
+
function collectBuiltSkillOutputs(cwd, discovered) {
|
|
112
196
|
try {
|
|
113
197
|
const config = loadConfig(cwd);
|
|
114
198
|
if (!config)
|
|
@@ -117,12 +201,15 @@ export function checkFilesConfigDests(cwd) {
|
|
|
117
201
|
const defaults = skillsConfig?.defaults;
|
|
118
202
|
// Dedup map: key = `skillName\0outputDir` → check entry
|
|
119
203
|
const checks = new Map();
|
|
120
|
-
// --- Pool
|
|
121
|
-
|
|
204
|
+
// --- Pool skills: candidate dir is dist/skills/<fsName> ---
|
|
205
|
+
const poolNames = new Set([
|
|
206
|
+
...discovered.map((skill) => skill.name),
|
|
207
|
+
...Object.keys(skillsConfig?.config ?? {}),
|
|
208
|
+
]);
|
|
209
|
+
for (const skillName of poolNames) {
|
|
122
210
|
const perSkill = skillsConfig?.config?.[skillName];
|
|
123
|
-
const mergedFiles = mergeFilesConfig(defaults?.files, perSkill?.files);
|
|
124
211
|
const outputDir = safePath.resolve(cwd, 'dist', 'skills', skillNameToFsPath(skillName));
|
|
125
|
-
tryAddCheckEntry(checks, skillName, outputDir,
|
|
212
|
+
tryAddCheckEntry(checks, skillName, outputDir, mergeSkillPackagingConfig(defaults, perSkill));
|
|
126
213
|
}
|
|
127
214
|
// --- Tree-copy skills: candidate dirs are plugin output skill dirs ---
|
|
128
215
|
for (const loc of computeTreeCopiedSkillLocations(config, cwd)) {
|
|
@@ -130,52 +217,116 @@ export function checkFilesConfigDests(cwd) {
|
|
|
130
217
|
// path (`group/nested-skill` for a nested skill), so try its trailing segment
|
|
131
218
|
// too — the spelling that matches for every skill whose dir is named after it.
|
|
132
219
|
const dirLeaf = basename(loc.skillDirPath);
|
|
133
|
-
const perSkill = skillsConfig?.config?.[loc.skillDirPath] ?? skillsConfig?.config?.[dirLeaf];
|
|
134
|
-
|
|
135
|
-
tryAddCheckEntry(checks, loc.skillDirPath, loc.skillOutputDir, mergedFiles);
|
|
220
|
+
const perSkill = (skillsConfig?.config?.[loc.skillDirPath] ?? skillsConfig?.config?.[dirLeaf]);
|
|
221
|
+
tryAddCheckEntry(checks, loc.skillDirPath, loc.skillOutputDir, mergeSkillPackagingConfig(defaults, perSkill));
|
|
136
222
|
}
|
|
137
|
-
|
|
138
|
-
const results = [];
|
|
139
|
-
for (const { skillName, outputDir, mergedFiles } of checks.values()) {
|
|
140
|
-
const missing = [];
|
|
141
|
-
for (const entry of mergedFiles) {
|
|
142
|
-
const destPath = safePath.resolve(outputDir, entry.dest);
|
|
143
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- destPath resolved from config
|
|
144
|
-
if (!existsSync(destPath)) {
|
|
145
|
-
missing.push(entry.dest);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
if (missing.length > 0) {
|
|
149
|
-
results.push({ skillName, outputDir, missing });
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return results;
|
|
223
|
+
return [...checks.values()];
|
|
153
224
|
}
|
|
154
225
|
catch {
|
|
155
226
|
return [];
|
|
156
227
|
}
|
|
157
228
|
}
|
|
158
229
|
/**
|
|
159
|
-
* Check
|
|
160
|
-
*
|
|
230
|
+
* Check that all dest paths from the merged files config exist in the built output.
|
|
231
|
+
*
|
|
232
|
+
* A dest is "missing" ONLY when absent from a candidate dir that exists. If a skill
|
|
233
|
+
* has no existing candidate dir, it is not reported (build didn't run for that mode).
|
|
234
|
+
*
|
|
235
|
+
* @param discovered - The skills this run discovered from `skills.include`. See
|
|
236
|
+
* {@link collectBuiltSkillOutputs} for why it is required rather than optional.
|
|
237
|
+
* @returns One result per (skill, outputDir) pair where dests are absent.
|
|
161
238
|
*/
|
|
162
|
-
function
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
239
|
+
export function checkFilesConfigDests(cwd, discovered) {
|
|
240
|
+
const results = [];
|
|
241
|
+
for (const check of collectBuiltSkillOutputs(cwd, discovered)) {
|
|
242
|
+
const { skillName, outputDir } = check;
|
|
243
|
+
const mergedFiles = filesOf(check);
|
|
244
|
+
if (mergedFiles.length === 0)
|
|
245
|
+
continue;
|
|
246
|
+
const missing = [];
|
|
247
|
+
for (const entry of mergedFiles) {
|
|
248
|
+
const destPath = safePath.resolve(outputDir, entry.dest);
|
|
249
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- destPath resolved from config
|
|
250
|
+
if (!existsSync(destPath)) {
|
|
251
|
+
missing.push(entry.dest);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (missing.length > 0) {
|
|
255
|
+
results.push({ skillName, outputDir, missing });
|
|
167
256
|
}
|
|
168
|
-
return [];
|
|
169
257
|
}
|
|
170
|
-
|
|
171
|
-
|
|
258
|
+
return results;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Crawl every built skill bundle for repo-internal agent-instruction files.
|
|
262
|
+
*
|
|
263
|
+
* The built-skill-bundle arm of `PACKAGED_AGENT_INSTRUCTION_FILE`. Its description
|
|
264
|
+
* has always claimed three surfaces — a built skill bundle, an installed plugin, a
|
|
265
|
+
* plugin source directory — but only the two plugin arms had a producer: the skill
|
|
266
|
+
* lanes inspect SKILL.md plus what links reach from it, so a file that arrives in a
|
|
267
|
+
* bundle with no link at all was invisible to every command. Measured on an adopter
|
|
268
|
+
* bundle carrying two of them: `vat audit` reported `filesScanned: 1`, zero issues,
|
|
269
|
+
* and `vat verify` reported `warnings: 0`.
|
|
270
|
+
*
|
|
271
|
+
* UNCONDITIONAL here, with no provenance test: `vat verify` reads the built `dist/`
|
|
272
|
+
* tree by definition, so every tree this enumerates is distributed output. (`vat
|
|
273
|
+
* audit` takes an arbitrary path and therefore must answer the provenance question
|
|
274
|
+
* first — see `appendDistributedTreeFindings` in audit.ts.)
|
|
275
|
+
*
|
|
276
|
+
* Explicit `files:` dests are exempt (§8.2): here the config is knowable, and
|
|
277
|
+
* naming a dest is an instruction to ship that file. A glob match never earns the
|
|
278
|
+
* exemption — a glob is a net, not a declaration — which is why the exempt set
|
|
279
|
+
* comes from {@link explicitFilesConfigDests} rather than from every `files:` entry.
|
|
280
|
+
*
|
|
281
|
+
* Each bundle's findings are then resolved against that skill's effective
|
|
282
|
+
* `validation.severity` (see {@link resolveIssueSeverity}), so the opt-out the
|
|
283
|
+
* code's own `fix` text prescribes actually works here. It did not: this phase
|
|
284
|
+
* published `detectPackagedAgentInstructionFiles`' raw output straight into the
|
|
285
|
+
* document, so `severity.PACKAGED_AGENT_INSTRUCTION_FILE: ignore` changed nothing
|
|
286
|
+
* — measured `warnings: 1` with the override at `skills.defaults`, at
|
|
287
|
+
* `skills.config.<name>`, and with no override at all.
|
|
288
|
+
*
|
|
289
|
+
* `validation.allow` is deliberately NOT applied. Allow is a per-PATH suppression
|
|
290
|
+
* whose usage is only answerable across a whole run, and this phase would have to
|
|
291
|
+
* drain a ledger it is not the run of — reporting ALLOW_UNUSED for every entry the
|
|
292
|
+
* project declares for other lanes. Severity is answerable per unit of work; allow
|
|
293
|
+
* is not. See `AllowUsageLedger` in `@vibe-agent-toolkit/agent-schema`.
|
|
294
|
+
*
|
|
295
|
+
* Locations anchor at `cwd`, the run's stated root, so a reader can open them.
|
|
296
|
+
*
|
|
297
|
+
* @param discovered - The skills this run discovered from `skills.include`. See
|
|
298
|
+
* {@link collectBuiltSkillOutputs} for why it is required rather than optional.
|
|
299
|
+
*/
|
|
300
|
+
export function checkPackagedAgentInstructionFiles(cwd, discovered) {
|
|
301
|
+
const issues = [];
|
|
302
|
+
for (const check of collectBuiltSkillOutputs(cwd, discovered)) {
|
|
303
|
+
const raw = detectPackagedAgentInstructionFiles(check.outputDir, cwd, explicitFilesConfigDests(filesOf(check)));
|
|
304
|
+
issues.push(...resolveIssueSeverity(raw, check.packaging.validation));
|
|
172
305
|
}
|
|
306
|
+
return issues;
|
|
173
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* Load the project config without letting a broken one abort the command.
|
|
310
|
+
*
|
|
311
|
+
* A config that exists but does not parse is reported as `error` rather than
|
|
312
|
+
* swallowed as "no config": `vat verify` must not answer "that phase is not
|
|
313
|
+
* configured" when it could not read the configuration at all.
|
|
314
|
+
*/
|
|
315
|
+
function loadConfigTolerant(cwd) {
|
|
316
|
+
try {
|
|
317
|
+
return { config: loadConfig(cwd) };
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
return { config: undefined, error: error instanceof Error ? error.message : String(error) };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/** The in-process phase that checks `files:` dests against the built output. */
|
|
324
|
+
const FILES_CONFIG_DESTS = 'files-config-dests';
|
|
174
325
|
/**
|
|
175
326
|
* Log files-config-dests errors to stderr.
|
|
176
327
|
*/
|
|
177
328
|
function reportFilesDestErrors(results, logger) {
|
|
178
|
-
logger.error(
|
|
329
|
+
logger.error(`\n▶ Phase: ${FILES_CONFIG_DESTS}`);
|
|
179
330
|
for (const { skillName, outputDir, missing } of results) {
|
|
180
331
|
logger.error(` Skill '${skillName}': missing dest file(s) in ${outputDir}/:`);
|
|
181
332
|
for (const dest of missing) {
|
|
@@ -183,6 +334,25 @@ function reportFilesDestErrors(results, logger) {
|
|
|
183
334
|
}
|
|
184
335
|
}
|
|
185
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* Log packaged-content findings to stderr.
|
|
339
|
+
*
|
|
340
|
+
* A companion to the document entry, never a substitute for it: this phase's
|
|
341
|
+
* findings are published into the YAML too (see {@link FindingsPhaseResult}).
|
|
342
|
+
*
|
|
343
|
+
* Rendered through the SHARED {@link formatIssueLines}, which `vat skills build`
|
|
344
|
+
* and `vat skills validate` already use. The hand-rolled renderer this replaces
|
|
345
|
+
* printed severity, code, location and fix — and dropped `message`, the only part
|
|
346
|
+
* of a finding that says what is wrong. A second renderer for the same shape is
|
|
347
|
+
* how one command's findings end up spelled differently from every other's.
|
|
348
|
+
*/
|
|
349
|
+
function reportPackagedContentIssues(issues, logger) {
|
|
350
|
+
logger.error(`\n▶ Phase: ${PACKAGED_CONTENT}`);
|
|
351
|
+
for (const issue of issues) {
|
|
352
|
+
for (const line of formatIssueLines(issue, ' '))
|
|
353
|
+
logger.error(line);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
186
356
|
/**
|
|
187
357
|
* Log consistency check issues to stderr.
|
|
188
358
|
*/
|
|
@@ -203,27 +373,141 @@ function reportConsistencyIssues(issues, logger) {
|
|
|
203
373
|
logger.info(` INFO [${issue.code}]: ${issue.message}`);
|
|
204
374
|
}
|
|
205
375
|
}
|
|
206
|
-
|
|
207
|
-
|
|
376
|
+
/**
|
|
377
|
+
* Phases `vat verify` knows how to run, in stable execution order.
|
|
378
|
+
*
|
|
379
|
+
* `PhaseVocabulary.validNames` is what `--only` was checked against; with
|
|
380
|
+
* `--only` retired from this command it is documentation only — the arm that
|
|
381
|
+
* reads it in {@link decidePhaseSelection} is unreachable from here.
|
|
382
|
+
*/
|
|
383
|
+
const VALID_PHASES = ['resources', 'skills', 'marketplace', 'consistency'];
|
|
384
|
+
const VERIFY_VOCABULARY = {
|
|
385
|
+
noun: 'Phase',
|
|
386
|
+
verb: 'verify',
|
|
387
|
+
validNames: VALID_PHASES,
|
|
388
|
+
noop: {
|
|
389
|
+
warning: 'No resources:, skills: or claude.marketplaces: block found in vibe-agent-toolkit.config.yaml — nothing to verify. If this is unexpected, check your config.',
|
|
390
|
+
note: 'No configured phases (no resources, skills or claude.marketplaces block in vibe-agent-toolkit.config.yaml).',
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
/**
|
|
394
|
+
* Decide which verification phases to run.
|
|
395
|
+
*
|
|
396
|
+
* Config-gated, exactly as `vat validate` is. It used to push `resources` and
|
|
397
|
+
* `skills` unconditionally, so `vat verify --only skills` in a project with no
|
|
398
|
+
* `skills:` block exited 0 while `vat validate --only skills` on the same
|
|
399
|
+
* project exited 1 — same question, opposite verdicts, and a CI gate pinned to
|
|
400
|
+
* the verify form stayed green forever the moment the config key was renamed.
|
|
401
|
+
*
|
|
402
|
+
* `--only` is gone from this command entirely. Measured on a 90-skill project a
|
|
403
|
+
* full `vat verify` is ~32s, of which the two slowest phases are ~28s — the
|
|
404
|
+
* filter bought at most ~18s and repeatedly bought a wrong answer with it. Every
|
|
405
|
+
* run is now a whole run, and `only` is passed to {@link decidePhaseSelection}
|
|
406
|
+
* as `undefined` (that helper still routes `--only` for `vat validate` and
|
|
407
|
+
* `vat build`).
|
|
408
|
+
*
|
|
409
|
+
* @param configError - The config-load failure, when the config could not be
|
|
410
|
+
* read. Every subprocess phase still runs so the CHILD reports the real
|
|
411
|
+
* config error (exit 2) instead of this command guessing.
|
|
412
|
+
* @param verbose - Forwarded to each subprocess phase as `--verbose`. The
|
|
413
|
+
* children own their own summarization; this command only relays the request.
|
|
414
|
+
*/
|
|
415
|
+
export function selectVerifyPhases(config, configError, verbose) {
|
|
208
416
|
const phases = [];
|
|
209
|
-
|
|
210
|
-
|
|
417
|
+
const unreadable = configError !== undefined;
|
|
418
|
+
const detail = verbose === true ? ['--verbose'] : [];
|
|
419
|
+
if (unreadable || config?.resources) {
|
|
420
|
+
phases.push({ name: 'resources', args: ['resources', 'validate', ...detail] });
|
|
211
421
|
}
|
|
212
|
-
if (
|
|
213
|
-
phases.push({ name: 'skills', args: ['skills', 'validate'] });
|
|
422
|
+
if (unreadable || config?.skills) {
|
|
423
|
+
phases.push({ name: 'skills', args: ['skills', 'validate', ...detail] });
|
|
214
424
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
425
|
+
for (const name of Object.keys(config?.claude?.marketplaces ?? {})) {
|
|
426
|
+
phases.push({
|
|
427
|
+
name: `marketplace:${name}`,
|
|
428
|
+
args: [
|
|
429
|
+
'claude',
|
|
430
|
+
'marketplace',
|
|
431
|
+
'validate',
|
|
432
|
+
`dist/.claude/plugins/marketplaces/${name}`,
|
|
433
|
+
...detail,
|
|
434
|
+
],
|
|
435
|
+
});
|
|
225
436
|
}
|
|
226
|
-
return phases
|
|
437
|
+
return decidePhaseSelection(undefined, phases, VERIFY_VOCABULARY, {
|
|
438
|
+
unreadableConfig: configError,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
/** The in-process phase that crawls built skill bundles for what must not ship. */
|
|
442
|
+
const PACKAGED_CONTENT = 'packaged-content';
|
|
443
|
+
/**
|
|
444
|
+
* Which in-process phases this run performs, given the config.
|
|
445
|
+
*
|
|
446
|
+
* The SINGLE source for that question: {@link verifyTopLevelCommand} gates
|
|
447
|
+
* execution on this list and {@link formatVerifyAnnouncement} announces the same
|
|
448
|
+
* list, so the printed phase list cannot drift from the phases that run. It used
|
|
449
|
+
* to be announced from the subprocess phases alone while these two were gated by
|
|
450
|
+
* hand-written conditions further down, so a run printed '(phases: skills)' and
|
|
451
|
+
* then also ran `consistency`, which put a second entry in the emitted document.
|
|
452
|
+
* A status that under-reports what it did is the same defect class as
|
|
453
|
+
* {@link selectVerifyPhases}' silent exit-0 pass.
|
|
454
|
+
*
|
|
455
|
+
* The contract is **the phases that will inspect something** — not "code paths
|
|
456
|
+
* this run enters". An earlier fix traded the under-reporting for
|
|
457
|
+
* over-reporting: on a project with `resources:` and no `skills:`, a run
|
|
458
|
+
* announced 'resources → files-config-dests → consistency' and emitted a
|
|
459
|
+
* document holding `resources` and nothing else, so an operator read a claim
|
|
460
|
+
* that distribution consistency had been checked. Both in-process phases read
|
|
461
|
+
* the same input, the `skills:` block — without it {@link checkFilesConfigDests}
|
|
462
|
+
* has no `files:` entry to resolve (both `defaults.files` and
|
|
463
|
+
* `config.<skill>.files` live under `skills:`, so every merge is empty) and
|
|
464
|
+
* {@link runConsistencyPhase} returns before its first lookup. Neither can
|
|
465
|
+
* produce a finding, so neither is named.
|
|
466
|
+
*
|
|
467
|
+
* This is a truthfulness change, not a behaviour change: the condition here is
|
|
468
|
+
* the one the phases already applied internally, hoisted so the announcement can
|
|
469
|
+
* see it.
|
|
470
|
+
*
|
|
471
|
+
* Note "will inspect something" is not a prediction of the findings:
|
|
472
|
+
* `files-config-dests` is named whenever a `skills:` block engages it, even
|
|
473
|
+
* though a clean run reports nothing.
|
|
474
|
+
*/
|
|
475
|
+
function selectInProcessVerifyPhases(config) {
|
|
476
|
+
return config?.skills === undefined ? [] : [FILES_CONFIG_DESTS, PACKAGED_CONTENT, 'consistency'];
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* The startup announcement: every phase this run will inspect something with,
|
|
480
|
+
* in order. A phase that would consult nothing is not named.
|
|
481
|
+
*/
|
|
482
|
+
export function formatVerifyAnnouncement(subprocessPhaseNames, config) {
|
|
483
|
+
const all = [...subprocessPhaseNames, ...selectInProcessVerifyPhases(config)];
|
|
484
|
+
return `🔍 vat verify (phases: ${all.join(' → ')})`;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Project ONE finding into the archived YAML.
|
|
488
|
+
*
|
|
489
|
+
* The whole anchor rides along. This shape used to be `{severity, code, message,
|
|
490
|
+
* fix}` and the projection destructured exactly those four, so every
|
|
491
|
+
* `packaged-content` finding reached the document with no `location` — the same
|
|
492
|
+
* defect this PR fixed one command over in `vat skills build`, where a published
|
|
493
|
+
* count carried "no `code`, no location and no fix string at ANY verbosity". It
|
|
494
|
+
* looked survivable only because `materializeIssue` happens to interpolate the
|
|
495
|
+
* detail into `message` for this one code; that is a coincidence, not a contract,
|
|
496
|
+
* and stderr is not the document a CI consumer parses.
|
|
497
|
+
*/
|
|
498
|
+
export function toPublishedIssue(issue) {
|
|
499
|
+
return {
|
|
500
|
+
severity: issue.severity,
|
|
501
|
+
code: issue.code,
|
|
502
|
+
message: issue.message,
|
|
503
|
+
fix: issue.fix ?? '',
|
|
504
|
+
// Conditional rather than `location: issue.location`: `exactOptionalPropertyTypes`
|
|
505
|
+
// distinguishes an absent key from an explicit `undefined`, and a YAML document
|
|
506
|
+
// carrying `location: null` claims something the finding never said.
|
|
507
|
+
...(issue.location === undefined ? {} : { location: issue.location }),
|
|
508
|
+
...(issue.line === undefined ? {} : { line: issue.line }),
|
|
509
|
+
...(issue.reference === undefined ? {} : { reference: issue.reference }),
|
|
510
|
+
};
|
|
227
511
|
}
|
|
228
512
|
/**
|
|
229
513
|
* `ConsistencyIssue` speaks the same severity vocabulary as `ValidationIssue`
|
|
@@ -246,27 +530,26 @@ function asValidationIssues(issues) {
|
|
|
246
530
|
* to go to stderr only, so the archived YAML — the artifact of record — said
|
|
247
531
|
* nothing happened. And a warning-only run reported `passed`, which is the
|
|
248
532
|
* reassuring answer to a question it could not represent.
|
|
533
|
+
*
|
|
534
|
+
* Discovery is handed in rather than performed here: it crawls the whole project,
|
|
535
|
+
* and the packaged-content and files-config-dests phases need the same list. One
|
|
536
|
+
* crawl per run, one answer to "which skills does this project have".
|
|
249
537
|
*/
|
|
250
|
-
|
|
251
|
-
const config = loadConfig(process.cwd());
|
|
538
|
+
function runConsistencyPhase(logger, phaseResults, config, projectRoot, discoveredSkills) {
|
|
252
539
|
if (!config?.skills) {
|
|
253
|
-
// Nothing to cross-reference
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
error: "No skills: block in vibe-agent-toolkit.config.yaml — 'consistency' has nothing to check.",
|
|
264
|
-
});
|
|
265
|
-
}
|
|
540
|
+
// Nothing to cross-reference, so nothing to report: a run without a
|
|
541
|
+
// `skills:` block is a genuine no-op, and {@link selectInProcessVerifyPhases}
|
|
542
|
+
// has already decided not to name this phase. There used to be a second arm
|
|
543
|
+
// here that pushed an ERROR result instead — because `--only consistency`
|
|
544
|
+
// had asked for THIS phase specifically, and answering an explicit request
|
|
545
|
+
// with an empty phase list and `success` is the same silent pass
|
|
546
|
+
// `vat validate --only <unconfigured surface>` refuses to give. With
|
|
547
|
+
// `--only` retired from `vat verify` there is no way to ask for this phase
|
|
548
|
+
// specifically, so that arm went with it. This guard remains reachable only
|
|
549
|
+
// defensively (and narrows `config.skills` for the call below).
|
|
266
550
|
return;
|
|
267
551
|
}
|
|
268
|
-
const
|
|
269
|
-
const consistencyResult = runConsistencyChecks(discoveredSkills, config, process.cwd());
|
|
552
|
+
const consistencyResult = runConsistencyChecks([...discoveredSkills], config, projectRoot);
|
|
270
553
|
const issues = consistencyResult.issues;
|
|
271
554
|
if (issues.length > 0) {
|
|
272
555
|
reportConsistencyIssues(issues, logger);
|
|
@@ -276,44 +559,78 @@ async function runConsistencyPhase(logger, phaseResults, explicitlyRequested) {
|
|
|
276
559
|
name: 'consistency',
|
|
277
560
|
status: calculateValidationStatus(asValidation),
|
|
278
561
|
issueCounts: countBySeverity(asValidation),
|
|
279
|
-
issues:
|
|
562
|
+
issues: asValidation.map(toPublishedIssue),
|
|
280
563
|
};
|
|
281
564
|
phaseResults.push(result);
|
|
282
565
|
}
|
|
283
|
-
async function verifyTopLevelCommand(options) {
|
|
566
|
+
async function verifyTopLevelCommand(options, command) {
|
|
567
|
+
// First, and before requireProjectRoot: `vat verify dist/skills/demo` used to
|
|
568
|
+
// be accepted, have its path discarded, run wide over the whole project and
|
|
569
|
+
// report success. Nothing below can un-tell that lie, so the run ends here.
|
|
570
|
+
rejectPositionalArguments(command.args, COMMAND_NAME, 'verifies every phase vibe-agent-toolkit.config.yaml declares, against the built dist/ tree');
|
|
571
|
+
// Before requireProjectRoot: a retired flag is a usage error, and answering it
|
|
572
|
+
// with "no vibe-agent-toolkit.config.yaml found" would diagnose the wrong
|
|
573
|
+
// problem for anyone running the old invocation outside a project.
|
|
574
|
+
rejectRetiredOnly(options.only, COMMAND_NAME, VERIFY_FULL_RUN_SECONDS);
|
|
284
575
|
// Spec §7: `vat verify` requires a projectRoot.
|
|
285
|
-
requireProjectRoot(process.cwd(),
|
|
286
|
-
const
|
|
287
|
-
// Consistency is an in-process phase, not a subprocess. Allow --only consistency
|
|
288
|
-
// to produce an empty subprocess phase list without throwing.
|
|
289
|
-
if (phases.length === 0 && options.only !== 'consistency') {
|
|
290
|
-
throw new Error(`Unknown phase: ${options.only ?? ''}. Valid phases: resources, skills, marketplace, consistency`);
|
|
291
|
-
}
|
|
292
|
-
const logger = createLogger(options.debug ? { debug: true } : {});
|
|
293
|
-
const startTime = Date.now();
|
|
294
|
-
const binPath = resolveBinPath();
|
|
576
|
+
const projectRoot = requireProjectRoot(process.cwd(), COMMAND_NAME);
|
|
577
|
+
const { logger, startTime, binPath } = createPhaseContext(options.debug);
|
|
295
578
|
try {
|
|
296
|
-
|
|
579
|
+
// Inside the try, deliberately: phase selection used to throw from out here
|
|
580
|
+
// (on an unroutable `--only`), so the user got a raw Node stack trace and
|
|
581
|
+
// zero bytes of the structured document a scripted caller parses.
|
|
582
|
+
const { config, error: configError } = loadConfigTolerant(projectRoot);
|
|
583
|
+
const phases = applyPhaseSelection(selectVerifyPhases(config, configError, options.verbose), logger, startTime);
|
|
584
|
+
// Announced from the same list the in-process gates below read, so the
|
|
585
|
+
// printed phases and the executed phases cannot disagree.
|
|
586
|
+
const inProcess = selectInProcessVerifyPhases(config);
|
|
587
|
+
logger.info(formatVerifyAnnouncement(phases.map((p) => p.name), config));
|
|
297
588
|
const phaseResults = [];
|
|
298
589
|
for (const phase of phases) {
|
|
299
590
|
logger.info(`\n▶ Phase: ${phase.name}`);
|
|
300
591
|
phaseResults.push(runPhase(binPath, phase));
|
|
301
592
|
}
|
|
593
|
+
// ONE discovery for the whole in-process half of the run. Every phase below
|
|
594
|
+
// asks the same question — "which skills does this project have" — and each
|
|
595
|
+
// answer used to be a different one: `consistency` crawled, while
|
|
596
|
+
// `files-config-dests` and `packaged-content` read `skills.config` keys and
|
|
597
|
+
// were therefore blind to every skill discovered by a glob.
|
|
598
|
+
const discoveredSkills = inProcess.length > 0 && config?.skills
|
|
599
|
+
? await discoverSkillsFromConfig(config.skills, projectRoot)
|
|
600
|
+
: [];
|
|
302
601
|
// Post-build files config check: verify all dest paths exist in built output
|
|
303
|
-
if (
|
|
304
|
-
const filesDestResults = checkFilesConfigDests(
|
|
602
|
+
if (inProcess.includes(FILES_CONFIG_DESTS)) {
|
|
603
|
+
const filesDestResults = checkFilesConfigDests(projectRoot, discoveredSkills);
|
|
305
604
|
if (filesDestResults.length > 0) {
|
|
306
605
|
reportFilesDestErrors(filesDestResults, logger);
|
|
307
606
|
phaseResults.push({
|
|
308
|
-
name:
|
|
607
|
+
name: FILES_CONFIG_DESTS,
|
|
309
608
|
status: 'error',
|
|
310
609
|
issueCounts: { errors: filesDestResults.length, warnings: 0, info: 0 },
|
|
311
610
|
});
|
|
312
611
|
}
|
|
313
612
|
}
|
|
613
|
+
// Packaged-content check: crawl each built skill bundle for repo-internal
|
|
614
|
+
// agent-instruction files. Publishes its findings INTO the document rather
|
|
615
|
+
// than only logging them — a file that must not ship has to be visible in
|
|
616
|
+
// `issueCounts`, or a CI consumer reads a clean report for a bundle carrying
|
|
617
|
+
// one. Warnings do not fail the run; the exit code still comes from errors.
|
|
618
|
+
if (inProcess.includes(PACKAGED_CONTENT)) {
|
|
619
|
+
const packagedIssues = checkPackagedAgentInstructionFiles(projectRoot, discoveredSkills);
|
|
620
|
+
if (packagedIssues.length > 0) {
|
|
621
|
+
reportPackagedContentIssues(packagedIssues, logger);
|
|
622
|
+
}
|
|
623
|
+
const packagedResult = {
|
|
624
|
+
name: PACKAGED_CONTENT,
|
|
625
|
+
status: calculateValidationStatus(packagedIssues),
|
|
626
|
+
issueCounts: countBySeverity(packagedIssues),
|
|
627
|
+
issues: packagedIssues.map(toPublishedIssue),
|
|
628
|
+
};
|
|
629
|
+
phaseResults.push(packagedResult);
|
|
630
|
+
}
|
|
314
631
|
// Consistency check: cross-reference discovered skills vs package.json and plugin assignments
|
|
315
|
-
if (
|
|
316
|
-
|
|
632
|
+
if (inProcess.includes('consistency')) {
|
|
633
|
+
runConsistencyPhase(logger, phaseResults, config, projectRoot, discoveredSkills);
|
|
317
634
|
}
|
|
318
635
|
const duration = Date.now() - startTime;
|
|
319
636
|
// Worst-wins across phases, with `system-error` outranking `error`: a phase
|