@vibe-agent-toolkit/cli 0.1.41-rc.8 → 0.1.42-rc.1
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.js +11 -1
- package/dist/bin.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 +35 -0
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +312 -93
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +16 -4
- 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 +31 -3
- package/dist/commands/claude/marketplace/validate.d.ts.map +1 -1
- package/dist/commands/claude/marketplace/validate.js +105 -12
- package/dist/commands/claude/marketplace/validate.js.map +1 -1
- package/dist/commands/claude/plugin/build.d.ts +9 -2
- package/dist/commands/claude/plugin/build.d.ts.map +1 -1
- package/dist/commands/claude/plugin/build.js +90 -8
- 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/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/doctor.js +1 -1
- package/dist/commands/doctor.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/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/skills/build.d.ts +258 -9
- package/dist/commands/skills/build.d.ts.map +1 -1
- package/dist/commands/skills/build.js +698 -111
- package/dist/commands/skills/build.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 +19 -1
- package/dist/commands/skills/validate-command.js.map +1 -1
- package/dist/commands/skills/validate.d.ts.map +1 -1
- package/dist/commands/skills/validate.js +37 -7
- package/dist/commands/skills/validate.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +18 -4
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/verify.d.ts +77 -7
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +254 -57
- package/dist/commands/verify.js.map +1 -1
- package/dist/utils/issue-rendering.d.ts +42 -0
- package/dist/utils/issue-rendering.d.ts.map +1 -1
- package/dist/utils/issue-rendering.js +53 -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/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/skills.md +16 -2
- package/package.json +15 -12
|
@@ -6,15 +6,19 @@
|
|
|
6
6
|
* 2. vat skills validate (SKILL.md frontmatter validation)
|
|
7
7
|
* 3. vat claude marketplace validate (strict marketplace validation, when configured)
|
|
8
8
|
* 4. files-config-dests (in-process; every `files:` dest exists in the built output)
|
|
9
|
-
* 5.
|
|
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)
|
|
10
11
|
*
|
|
11
|
-
* 1–3 are subprocess phases chosen by {@link selectVerifyPhases}; 4–
|
|
12
|
+
* 1–3 are subprocess phases chosen by {@link selectVerifyPhases}; 4–6 run here and
|
|
12
13
|
* are chosen by `selectInProcessVerifyPhases`. Both sets are config-gated, and both
|
|
13
14
|
* are announced on startup.
|
|
14
15
|
*/
|
|
16
|
+
import { type ValidationIssue } from '@vibe-agent-toolkit/agent-schema';
|
|
15
17
|
import type { ProjectConfig } from '@vibe-agent-toolkit/resources';
|
|
16
18
|
import { Command } from 'commander';
|
|
19
|
+
import { type ConsistencyIssueSeverity } from './consistency-check.js';
|
|
17
20
|
import { type PhaseSelection } from './phase-utils.js';
|
|
21
|
+
import type { DiscoveredSkill } from './skills/command-helpers.js';
|
|
18
22
|
export interface VerifyCommandOptions {
|
|
19
23
|
/** Retired; declared only so {@link rejectRetiredOnly} can explain the removal. */
|
|
20
24
|
only?: string;
|
|
@@ -32,16 +36,55 @@ export interface FilesDestCheckResult {
|
|
|
32
36
|
/**
|
|
33
37
|
* Check that all dest paths from the merged files config exist in the built output.
|
|
34
38
|
*
|
|
35
|
-
* Checks each skill's dests in the location(s) where `vat build` actually wrote them:
|
|
36
|
-
* - Pool skills: `dist/skills/<fsName>/` (only when that dir exists)
|
|
37
|
-
* - Tree-copy skills: `dist/.claude/plugins/.../skills/<name>/` (only when that dir exists)
|
|
38
|
-
*
|
|
39
39
|
* A dest is "missing" ONLY when absent from a candidate dir that exists. If a skill
|
|
40
40
|
* has no existing candidate dir, it is not reported (build didn't run for that mode).
|
|
41
41
|
*
|
|
42
|
+
* @param discovered - The skills this run discovered from `skills.include`. See
|
|
43
|
+
* {@link collectBuiltSkillOutputs} for why it is required rather than optional.
|
|
42
44
|
* @returns One result per (skill, outputDir) pair where dests are absent.
|
|
43
45
|
*/
|
|
44
|
-
export declare function checkFilesConfigDests(cwd: string): FilesDestCheckResult[];
|
|
46
|
+
export declare function checkFilesConfigDests(cwd: string, discovered: readonly DiscoveredSkill[]): FilesDestCheckResult[];
|
|
47
|
+
/**
|
|
48
|
+
* Crawl every built skill bundle for repo-internal agent-instruction files.
|
|
49
|
+
*
|
|
50
|
+
* The built-skill-bundle arm of `PACKAGED_AGENT_INSTRUCTION_FILE`. Its description
|
|
51
|
+
* has always claimed three surfaces — a built skill bundle, an installed plugin, a
|
|
52
|
+
* plugin source directory — but only the two plugin arms had a producer: the skill
|
|
53
|
+
* lanes inspect SKILL.md plus what links reach from it, so a file that arrives in a
|
|
54
|
+
* bundle with no link at all was invisible to every command. Measured on an adopter
|
|
55
|
+
* bundle carrying two of them: `vat audit` reported `filesScanned: 1`, zero issues,
|
|
56
|
+
* and `vat verify` reported `warnings: 0`.
|
|
57
|
+
*
|
|
58
|
+
* UNCONDITIONAL here, with no provenance test: `vat verify` reads the built `dist/`
|
|
59
|
+
* tree by definition, so every tree this enumerates is distributed output. (`vat
|
|
60
|
+
* audit` takes an arbitrary path and therefore must answer the provenance question
|
|
61
|
+
* first — see `appendDistributedTreeFindings` in audit.ts.)
|
|
62
|
+
*
|
|
63
|
+
* Explicit `files:` dests are exempt (§8.2): here the config is knowable, and
|
|
64
|
+
* naming a dest is an instruction to ship that file. A glob match never earns the
|
|
65
|
+
* exemption — a glob is a net, not a declaration — which is why the exempt set
|
|
66
|
+
* comes from {@link explicitFilesConfigDests} rather than from every `files:` entry.
|
|
67
|
+
*
|
|
68
|
+
* Each bundle's findings are then resolved against that skill's effective
|
|
69
|
+
* `validation.severity` (see {@link resolveIssueSeverity}), so the opt-out the
|
|
70
|
+
* code's own `fix` text prescribes actually works here. It did not: this phase
|
|
71
|
+
* published `detectPackagedAgentInstructionFiles`' raw output straight into the
|
|
72
|
+
* document, so `severity.PACKAGED_AGENT_INSTRUCTION_FILE: ignore` changed nothing
|
|
73
|
+
* — measured `warnings: 1` with the override at `skills.defaults`, at
|
|
74
|
+
* `skills.config.<name>`, and with no override at all.
|
|
75
|
+
*
|
|
76
|
+
* `validation.allow` is deliberately NOT applied. Allow is a per-PATH suppression
|
|
77
|
+
* whose usage is only answerable across a whole run, and this phase would have to
|
|
78
|
+
* drain a ledger it is not the run of — reporting ALLOW_UNUSED for every entry the
|
|
79
|
+
* project declares for other lanes. Severity is answerable per unit of work; allow
|
|
80
|
+
* is not. See `AllowUsageLedger` in `@vibe-agent-toolkit/agent-schema`.
|
|
81
|
+
*
|
|
82
|
+
* Locations anchor at `cwd`, the run's stated root, so a reader can open them.
|
|
83
|
+
*
|
|
84
|
+
* @param discovered - The skills this run discovered from `skills.include`. See
|
|
85
|
+
* {@link collectBuiltSkillOutputs} for why it is required rather than optional.
|
|
86
|
+
*/
|
|
87
|
+
export declare function checkPackagedAgentInstructionFiles(cwd: string, discovered: readonly DiscoveredSkill[]): ValidationIssue[];
|
|
45
88
|
/**
|
|
46
89
|
* Decide which verification phases to run.
|
|
47
90
|
*
|
|
@@ -70,4 +113,31 @@ export declare function selectVerifyPhases(config: ProjectConfig | undefined, co
|
|
|
70
113
|
* in order. A phase that would consult nothing is not named.
|
|
71
114
|
*/
|
|
72
115
|
export declare function formatVerifyAnnouncement(subprocessPhaseNames: readonly string[], config: ProjectConfig | undefined): string;
|
|
116
|
+
/** An in-process phase's finding as it appears in the archived YAML. */
|
|
117
|
+
interface PublishedIssue {
|
|
118
|
+
severity: ValidationIssue['severity'] | ConsistencyIssueSeverity;
|
|
119
|
+
code: string;
|
|
120
|
+
message: string;
|
|
121
|
+
fix: string;
|
|
122
|
+
/** Project-relative path of the file to open. Absent when the finding has none. */
|
|
123
|
+
location?: string;
|
|
124
|
+
/** 1-based line within {@link PublishedIssue.location}. */
|
|
125
|
+
line?: number;
|
|
126
|
+
/** Doc link the code's registry entry carries. */
|
|
127
|
+
reference?: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Project ONE finding into the archived YAML.
|
|
131
|
+
*
|
|
132
|
+
* The whole anchor rides along. This shape used to be `{severity, code, message,
|
|
133
|
+
* fix}` and the projection destructured exactly those four, so every
|
|
134
|
+
* `packaged-content` finding reached the document with no `location` — the same
|
|
135
|
+
* defect this PR fixed one command over in `vat skills build`, where a published
|
|
136
|
+
* count carried "no `code`, no location and no fix string at ANY verbosity". It
|
|
137
|
+
* looked survivable only because `materializeIssue` happens to interpolate the
|
|
138
|
+
* detail into `message` for this one code; that is a coincidence, not a contract,
|
|
139
|
+
* and stderr is not the document a CI consumer parses.
|
|
140
|
+
*/
|
|
141
|
+
export declare function toPublishedIssue(issue: ValidationIssue): PublishedIssue;
|
|
142
|
+
export {};
|
|
73
143
|
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,kCAAkC,CAAC;AAO1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAEnE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAWL,KAAK,cAAc,EAEpB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAGnE,MAAM,WAAW,oBAAoB;IACnC,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAYD,wBAAgB,2BAA2B,IAAI,OAAO,CAqFrD;AAED,iFAAiF;AACjF,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAyID;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,SAAS,eAAe,EAAE,GACrC,oBAAoB,EAAE,CAmBxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,kCAAkC,CAChD,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,SAAS,eAAe,EAAE,GACrC,eAAe,EAAE,CAWnB;AAwGD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,OAAO,GAChB,cAAc,CA6BhB;AA4CD;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,oBAAoB,EAAE,SAAS,MAAM,EAAE,EACvC,MAAM,EAAE,aAAa,GAAG,SAAS,GAChC,MAAM,CAGR;AAED,wEAAwE;AACxE,UAAU,cAAc;IAGtB,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,GAAG,wBAAwB,CAAC;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,cAAc,CAavE"}
|
package/dist/commands/verify.js
CHANGED
|
@@ -6,24 +6,29 @@
|
|
|
6
6
|
* 2. vat skills validate (SKILL.md frontmatter validation)
|
|
7
7
|
* 3. vat claude marketplace validate (strict marketplace validation, when configured)
|
|
8
8
|
* 4. files-config-dests (in-process; every `files:` dest exists in the built output)
|
|
9
|
-
* 5.
|
|
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)
|
|
10
11
|
*
|
|
11
|
-
* 1–3 are subprocess phases chosen by {@link selectVerifyPhases}; 4–
|
|
12
|
+
* 1–3 are subprocess phases chosen by {@link selectVerifyPhases}; 4–6 run here and
|
|
12
13
|
* are chosen by `selectInProcessVerifyPhases`. Both sets are config-gated, and both
|
|
13
14
|
* are announced on startup.
|
|
14
15
|
*/
|
|
15
16
|
import { existsSync } from 'node:fs';
|
|
16
17
|
import { basename } from 'node:path';
|
|
17
18
|
import { calculateValidationStatus, countBySeverity, } from '@vibe-agent-toolkit/agent-schema';
|
|
18
|
-
import { computeTreeCopiedSkillLocations,
|
|
19
|
+
import { computeTreeCopiedSkillLocations, detectPackagedAgentInstructionFiles, explicitFilesConfigDests, } from '@vibe-agent-toolkit/agent-skills';
|
|
19
20
|
import { safePath } from '@vibe-agent-toolkit/utils';
|
|
20
21
|
import { Command } from 'commander';
|
|
21
22
|
import { handleCommandError } from '../utils/command-error.js';
|
|
22
23
|
import { loadConfig } from '../utils/config-loader.js';
|
|
24
|
+
import { formatIssueLines } from '../utils/issue-rendering.js';
|
|
25
|
+
import { resolveIssueSeverity } from '../utils/issue-severity.js';
|
|
23
26
|
import { writeYamlOutput } from '../utils/output.js';
|
|
24
27
|
import { requireProjectRoot } from '../utils/project-root-policy.js';
|
|
28
|
+
import { mergeSkillPackagingConfig } from '../utils/skill-packaging-config.js';
|
|
25
29
|
import { runConsistencyChecks, } from './consistency-check.js';
|
|
26
30
|
import { addRetiredOnlyOption, aggregatePhaseStatus, applyPhaseSelection, createPhaseContext, decidePhaseSelection, exitCodeForPhases, rejectRetiredOnly, runPhase, } from './phase-utils.js';
|
|
31
|
+
import { rejectPositionalArguments } from './positional-args.js';
|
|
27
32
|
import { discoverSkillsFromConfig } from './skills/skill-discovery.js';
|
|
28
33
|
/**
|
|
29
34
|
* Measured full-run duration on the 90-skill / 1,041-document adopter, cited by
|
|
@@ -31,6 +36,8 @@ import { discoverSkillsFromConfig } from './skills/skill-discovery.js';
|
|
|
31
36
|
* 1.0s + consistency under a second.
|
|
32
37
|
*/
|
|
33
38
|
const VERIFY_FULL_RUN_SECONDS = 32;
|
|
39
|
+
/** How this command names itself in every user-facing diagnostic. */
|
|
40
|
+
const COMMAND_NAME = 'vat verify';
|
|
34
41
|
export function createVerifyTopLevelCommand() {
|
|
35
42
|
const command = new Command('verify');
|
|
36
43
|
addRetiredOnlyOption(command)
|
|
@@ -59,11 +66,14 @@ Description:
|
|
|
59
66
|
Phases (in-process, run after the above, when 'skills:' is configured):
|
|
60
67
|
files-config-dests → every 'files:' dest exists in the built output. Appears
|
|
61
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.
|
|
62
72
|
consistency → skill distribution integrity (package.json, plugin assignment)
|
|
63
73
|
|
|
64
|
-
A run with a 'skills:' block runs all
|
|
65
|
-
consistency: they read that same config block. Without it
|
|
66
|
-
phase has anything to read, so
|
|
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.
|
|
67
77
|
|
|
68
78
|
The startup line on stderr names, in order, the phases that will inspect
|
|
69
79
|
something. A phase that would consult nothing is not listed; a phase that does
|
|
@@ -76,9 +86,10 @@ Output:
|
|
|
76
86
|
is a single parseable document (a phase's stdout is never streamed
|
|
77
87
|
through). A phase's status comes from the child's REPORTED status, not
|
|
78
88
|
from its exit code — an exit code cannot express 'warning'. In-process
|
|
79
|
-
phases also publish issueCounts {errors, warnings, info}; 'consistency'
|
|
80
|
-
|
|
81
|
-
publishes counts only and lists the missing dests on
|
|
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.
|
|
82
93
|
Progress and validation errors → stderr (streamed live)
|
|
83
94
|
|
|
84
95
|
By default each subprocess phase reports a per-asset summary plus the assets
|
|
@@ -90,7 +101,12 @@ Exit Codes:
|
|
|
90
101
|
1 - Validation errors found
|
|
91
102
|
2 - System error (this command's own, or propagated from a phase that could
|
|
92
103
|
not run: exited 2, was killed by a signal, was never spawned, or wrote
|
|
93
|
-
output that could not be parsed)
|
|
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>'.
|
|
94
110
|
|
|
95
111
|
Requirements:
|
|
96
112
|
projectRoot: required (errors if no vibe-agent-toolkit.config.yaml or .git/ ancestor)
|
|
@@ -110,38 +126,73 @@ Example:
|
|
|
110
126
|
function skillNameToFsPath(name) {
|
|
111
127
|
return name.replaceAll(':', '__');
|
|
112
128
|
}
|
|
129
|
+
/** The `files:` entries governing a candidate, `[]` when the skill declares none. */
|
|
130
|
+
function filesOf(entry) {
|
|
131
|
+
return entry.packaging.files ?? [];
|
|
132
|
+
}
|
|
113
133
|
/**
|
|
114
|
-
* Register a check for (skillName, outputDir,
|
|
134
|
+
* Register a check for (skillName, outputDir, packaging) in the dedup map.
|
|
115
135
|
*
|
|
116
136
|
* Skips silently if:
|
|
117
|
-
* - mergedFiles is empty (no entries to verify)
|
|
118
137
|
* - outputDir does not exist on disk (not a candidate)
|
|
119
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.
|
|
120
145
|
*/
|
|
121
|
-
function tryAddCheckEntry(checks, skillName, outputDir,
|
|
122
|
-
if (mergedFiles.length === 0)
|
|
123
|
-
return;
|
|
146
|
+
function tryAddCheckEntry(checks, skillName, outputDir, packaging) {
|
|
124
147
|
// eslint-disable-next-line security/detect-non-literal-fs-filename -- outputDir is resolved from config, not user input
|
|
125
148
|
if (!existsSync(outputDir))
|
|
126
149
|
return;
|
|
127
150
|
const key = `${skillName}\0${outputDir}`;
|
|
128
151
|
if (!checks.has(key)) {
|
|
129
|
-
checks.set(key, { skillName, outputDir,
|
|
152
|
+
checks.set(key, { skillName, outputDir, packaging });
|
|
130
153
|
}
|
|
131
154
|
}
|
|
132
155
|
/**
|
|
133
|
-
*
|
|
156
|
+
* Every built skill bundle this project's config accounts for that EXISTS on disk,
|
|
157
|
+
* with the `files:` entries that govern it.
|
|
134
158
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* -
|
|
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>/`
|
|
138
163
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
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.
|
|
141
167
|
*
|
|
142
|
-
*
|
|
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.
|
|
143
194
|
*/
|
|
144
|
-
|
|
195
|
+
function collectBuiltSkillOutputs(cwd, discovered) {
|
|
145
196
|
try {
|
|
146
197
|
const config = loadConfig(cwd);
|
|
147
198
|
if (!config)
|
|
@@ -150,12 +201,15 @@ export function checkFilesConfigDests(cwd) {
|
|
|
150
201
|
const defaults = skillsConfig?.defaults;
|
|
151
202
|
// Dedup map: key = `skillName\0outputDir` → check entry
|
|
152
203
|
const checks = new Map();
|
|
153
|
-
// --- Pool
|
|
154
|
-
|
|
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) {
|
|
155
210
|
const perSkill = skillsConfig?.config?.[skillName];
|
|
156
|
-
const mergedFiles = mergeFilesConfig(defaults?.files, perSkill?.files);
|
|
157
211
|
const outputDir = safePath.resolve(cwd, 'dist', 'skills', skillNameToFsPath(skillName));
|
|
158
|
-
tryAddCheckEntry(checks, skillName, outputDir,
|
|
212
|
+
tryAddCheckEntry(checks, skillName, outputDir, mergeSkillPackagingConfig(defaults, perSkill));
|
|
159
213
|
}
|
|
160
214
|
// --- Tree-copy skills: candidate dirs are plugin output skill dirs ---
|
|
161
215
|
for (const loc of computeTreeCopiedSkillLocations(config, cwd)) {
|
|
@@ -163,31 +217,94 @@ export function checkFilesConfigDests(cwd) {
|
|
|
163
217
|
// path (`group/nested-skill` for a nested skill), so try its trailing segment
|
|
164
218
|
// too — the spelling that matches for every skill whose dir is named after it.
|
|
165
219
|
const dirLeaf = basename(loc.skillDirPath);
|
|
166
|
-
const perSkill = skillsConfig?.config?.[loc.skillDirPath] ?? skillsConfig?.config?.[dirLeaf];
|
|
167
|
-
|
|
168
|
-
tryAddCheckEntry(checks, loc.skillDirPath, loc.skillOutputDir, mergedFiles);
|
|
169
|
-
}
|
|
170
|
-
// --- Run each pending check and collect results ---
|
|
171
|
-
const results = [];
|
|
172
|
-
for (const { skillName, outputDir, mergedFiles } of checks.values()) {
|
|
173
|
-
const missing = [];
|
|
174
|
-
for (const entry of mergedFiles) {
|
|
175
|
-
const destPath = safePath.resolve(outputDir, entry.dest);
|
|
176
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- destPath resolved from config
|
|
177
|
-
if (!existsSync(destPath)) {
|
|
178
|
-
missing.push(entry.dest);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (missing.length > 0) {
|
|
182
|
-
results.push({ skillName, outputDir, missing });
|
|
183
|
-
}
|
|
220
|
+
const perSkill = (skillsConfig?.config?.[loc.skillDirPath] ?? skillsConfig?.config?.[dirLeaf]);
|
|
221
|
+
tryAddCheckEntry(checks, loc.skillDirPath, loc.skillOutputDir, mergeSkillPackagingConfig(defaults, perSkill));
|
|
184
222
|
}
|
|
185
|
-
return
|
|
223
|
+
return [...checks.values()];
|
|
186
224
|
}
|
|
187
225
|
catch {
|
|
188
226
|
return [];
|
|
189
227
|
}
|
|
190
228
|
}
|
|
229
|
+
/**
|
|
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.
|
|
238
|
+
*/
|
|
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 });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
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));
|
|
305
|
+
}
|
|
306
|
+
return issues;
|
|
307
|
+
}
|
|
191
308
|
/**
|
|
192
309
|
* Load the project config without letting a broken one abort the command.
|
|
193
310
|
*
|
|
@@ -217,6 +334,25 @@ function reportFilesDestErrors(results, logger) {
|
|
|
217
334
|
}
|
|
218
335
|
}
|
|
219
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
|
+
}
|
|
220
356
|
/**
|
|
221
357
|
* Log consistency check issues to stderr.
|
|
222
358
|
*/
|
|
@@ -302,6 +438,8 @@ export function selectVerifyPhases(config, configError, verbose) {
|
|
|
302
438
|
unreadableConfig: configError,
|
|
303
439
|
});
|
|
304
440
|
}
|
|
441
|
+
/** The in-process phase that crawls built skill bundles for what must not ship. */
|
|
442
|
+
const PACKAGED_CONTENT = 'packaged-content';
|
|
305
443
|
/**
|
|
306
444
|
* Which in-process phases this run performs, given the config.
|
|
307
445
|
*
|
|
@@ -335,7 +473,7 @@ export function selectVerifyPhases(config, configError, verbose) {
|
|
|
335
473
|
* though a clean run reports nothing.
|
|
336
474
|
*/
|
|
337
475
|
function selectInProcessVerifyPhases(config) {
|
|
338
|
-
return config?.skills === undefined ? [] : [FILES_CONFIG_DESTS, 'consistency'];
|
|
476
|
+
return config?.skills === undefined ? [] : [FILES_CONFIG_DESTS, PACKAGED_CONTENT, 'consistency'];
|
|
339
477
|
}
|
|
340
478
|
/**
|
|
341
479
|
* The startup announcement: every phase this run will inspect something with,
|
|
@@ -345,6 +483,32 @@ export function formatVerifyAnnouncement(subprocessPhaseNames, config) {
|
|
|
345
483
|
const all = [...subprocessPhaseNames, ...selectInProcessVerifyPhases(config)];
|
|
346
484
|
return `🔍 vat verify (phases: ${all.join(' → ')})`;
|
|
347
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
|
+
};
|
|
511
|
+
}
|
|
348
512
|
/**
|
|
349
513
|
* `ConsistencyIssue` speaks the same severity vocabulary as `ValidationIssue`
|
|
350
514
|
* but carries a free-form `code`, so it is counted through this projection —
|
|
@@ -366,8 +530,12 @@ function asValidationIssues(issues) {
|
|
|
366
530
|
* to go to stderr only, so the archived YAML — the artifact of record — said
|
|
367
531
|
* nothing happened. And a warning-only run reported `passed`, which is the
|
|
368
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".
|
|
369
537
|
*/
|
|
370
|
-
|
|
538
|
+
function runConsistencyPhase(logger, phaseResults, config, projectRoot, discoveredSkills) {
|
|
371
539
|
if (!config?.skills) {
|
|
372
540
|
// Nothing to cross-reference, so nothing to report: a run without a
|
|
373
541
|
// `skills:` block is a genuine no-op, and {@link selectInProcessVerifyPhases}
|
|
@@ -381,8 +549,7 @@ async function runConsistencyPhase(logger, phaseResults, config, projectRoot) {
|
|
|
381
549
|
// defensively (and narrows `config.skills` for the call below).
|
|
382
550
|
return;
|
|
383
551
|
}
|
|
384
|
-
const
|
|
385
|
-
const consistencyResult = runConsistencyChecks(discoveredSkills, config, projectRoot);
|
|
552
|
+
const consistencyResult = runConsistencyChecks([...discoveredSkills], config, projectRoot);
|
|
386
553
|
const issues = consistencyResult.issues;
|
|
387
554
|
if (issues.length > 0) {
|
|
388
555
|
reportConsistencyIssues(issues, logger);
|
|
@@ -392,17 +559,21 @@ async function runConsistencyPhase(logger, phaseResults, config, projectRoot) {
|
|
|
392
559
|
name: 'consistency',
|
|
393
560
|
status: calculateValidationStatus(asValidation),
|
|
394
561
|
issueCounts: countBySeverity(asValidation),
|
|
395
|
-
issues:
|
|
562
|
+
issues: asValidation.map(toPublishedIssue),
|
|
396
563
|
};
|
|
397
564
|
phaseResults.push(result);
|
|
398
565
|
}
|
|
399
|
-
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');
|
|
400
571
|
// Before requireProjectRoot: a retired flag is a usage error, and answering it
|
|
401
572
|
// with "no vibe-agent-toolkit.config.yaml found" would diagnose the wrong
|
|
402
573
|
// problem for anyone running the old invocation outside a project.
|
|
403
|
-
rejectRetiredOnly(options.only,
|
|
574
|
+
rejectRetiredOnly(options.only, COMMAND_NAME, VERIFY_FULL_RUN_SECONDS);
|
|
404
575
|
// Spec §7: `vat verify` requires a projectRoot.
|
|
405
|
-
const projectRoot = requireProjectRoot(process.cwd(),
|
|
576
|
+
const projectRoot = requireProjectRoot(process.cwd(), COMMAND_NAME);
|
|
406
577
|
const { logger, startTime, binPath } = createPhaseContext(options.debug);
|
|
407
578
|
try {
|
|
408
579
|
// Inside the try, deliberately: phase selection used to throw from out here
|
|
@@ -419,9 +590,17 @@ async function verifyTopLevelCommand(options) {
|
|
|
419
590
|
logger.info(`\n▶ Phase: ${phase.name}`);
|
|
420
591
|
phaseResults.push(runPhase(binPath, phase));
|
|
421
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
|
+
: [];
|
|
422
601
|
// Post-build files config check: verify all dest paths exist in built output
|
|
423
602
|
if (inProcess.includes(FILES_CONFIG_DESTS)) {
|
|
424
|
-
const filesDestResults = checkFilesConfigDests(projectRoot);
|
|
603
|
+
const filesDestResults = checkFilesConfigDests(projectRoot, discoveredSkills);
|
|
425
604
|
if (filesDestResults.length > 0) {
|
|
426
605
|
reportFilesDestErrors(filesDestResults, logger);
|
|
427
606
|
phaseResults.push({
|
|
@@ -431,9 +610,27 @@ async function verifyTopLevelCommand(options) {
|
|
|
431
610
|
});
|
|
432
611
|
}
|
|
433
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
|
+
}
|
|
434
631
|
// Consistency check: cross-reference discovered skills vs package.json and plugin assignments
|
|
435
632
|
if (inProcess.includes('consistency')) {
|
|
436
|
-
|
|
633
|
+
runConsistencyPhase(logger, phaseResults, config, projectRoot, discoveredSkills);
|
|
437
634
|
}
|
|
438
635
|
const duration = Date.now() - startTime;
|
|
439
636
|
// Worst-wins across phases, with `system-error` outranking `error`: a phase
|