@vibe-agent-toolkit/cli 0.1.41-rc.7 → 0.1.41-rc.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/bin/vat +24 -1
  2. package/dist/bin/vat.js +24 -1
  3. package/dist/bin/vat.js.map +1 -1
  4. package/dist/bin.js +12 -1
  5. package/dist/bin.js.map +1 -1
  6. package/dist/commands/agent/list.d.ts +11 -0
  7. package/dist/commands/agent/list.d.ts.map +1 -1
  8. package/dist/commands/agent/list.js +30 -11
  9. package/dist/commands/agent/list.js.map +1 -1
  10. package/dist/commands/audit.d.ts +34 -1
  11. package/dist/commands/audit.d.ts.map +1 -1
  12. package/dist/commands/audit.js +220 -88
  13. package/dist/commands/audit.js.map +1 -1
  14. package/dist/commands/build.d.ts +14 -0
  15. package/dist/commands/build.d.ts.map +1 -1
  16. package/dist/commands/build.js +89 -24
  17. package/dist/commands/build.js.map +1 -1
  18. package/dist/commands/claude/marketplace/validate.d.ts +97 -0
  19. package/dist/commands/claude/marketplace/validate.d.ts.map +1 -1
  20. package/dist/commands/claude/marketplace/validate.js +154 -40
  21. package/dist/commands/claude/marketplace/validate.js.map +1 -1
  22. package/dist/commands/claude/org/stubs.d.ts +9 -1
  23. package/dist/commands/claude/org/stubs.d.ts.map +1 -1
  24. package/dist/commands/claude/org/stubs.js +11 -3
  25. package/dist/commands/claude/org/stubs.js.map +1 -1
  26. package/dist/commands/claude/plugin/build.d.ts +25 -2
  27. package/dist/commands/claude/plugin/build.d.ts.map +1 -1
  28. package/dist/commands/claude/plugin/build.js +99 -10
  29. package/dist/commands/claude/plugin/build.js.map +1 -1
  30. package/dist/commands/claude/plugin/plugin-json-merge.d.ts.map +1 -1
  31. package/dist/commands/claude/plugin/plugin-json-merge.js +15 -0
  32. package/dist/commands/claude/plugin/plugin-json-merge.js.map +1 -1
  33. package/dist/commands/corpus/report.d.ts +22 -4
  34. package/dist/commands/corpus/report.d.ts.map +1 -1
  35. package/dist/commands/corpus/report.js +1 -0
  36. package/dist/commands/corpus/report.js.map +1 -1
  37. package/dist/commands/corpus/runner.d.ts +16 -1
  38. package/dist/commands/corpus/runner.d.ts.map +1 -1
  39. package/dist/commands/corpus/runner.js +40 -22
  40. package/dist/commands/corpus/runner.js.map +1 -1
  41. package/dist/commands/inventory.d.ts +2 -0
  42. package/dist/commands/inventory.d.ts.map +1 -1
  43. package/dist/commands/inventory.js +45 -4
  44. package/dist/commands/inventory.js.map +1 -1
  45. package/dist/commands/phase-utils.d.ts +183 -14
  46. package/dist/commands/phase-utils.d.ts.map +1 -1
  47. package/dist/commands/phase-utils.js +294 -29
  48. package/dist/commands/phase-utils.js.map +1 -1
  49. package/dist/commands/rag/query-command.d.ts +40 -0
  50. package/dist/commands/rag/query-command.d.ts.map +1 -1
  51. package/dist/commands/rag/query-command.js +47 -20
  52. package/dist/commands/rag/query-command.js.map +1 -1
  53. package/dist/commands/resources/index.d.ts.map +1 -1
  54. package/dist/commands/resources/index.js +27 -9
  55. package/dist/commands/resources/index.js.map +1 -1
  56. package/dist/commands/resources/scan.d.ts +37 -0
  57. package/dist/commands/resources/scan.d.ts.map +1 -1
  58. package/dist/commands/resources/scan.js +47 -32
  59. package/dist/commands/resources/scan.js.map +1 -1
  60. package/dist/commands/resources/validate.d.ts +136 -1
  61. package/dist/commands/resources/validate.d.ts.map +1 -1
  62. package/dist/commands/resources/validate.js +105 -66
  63. package/dist/commands/resources/validate.js.map +1 -1
  64. package/dist/commands/skill/review.d.ts.map +1 -1
  65. package/dist/commands/skill/review.js +7 -3
  66. package/dist/commands/skill/review.js.map +1 -1
  67. package/dist/commands/skill/test/run.d.ts +1 -1
  68. package/dist/commands/skill/test/run.d.ts.map +1 -1
  69. package/dist/commands/skill/test/run.js +8 -6
  70. package/dist/commands/skill/test/run.js.map +1 -1
  71. package/dist/commands/skills/build.d.ts +96 -7
  72. package/dist/commands/skills/build.d.ts.map +1 -1
  73. package/dist/commands/skills/build.js +277 -60
  74. package/dist/commands/skills/build.js.map +1 -1
  75. package/dist/commands/skills/command-helpers.d.ts +6 -10
  76. package/dist/commands/skills/command-helpers.d.ts.map +1 -1
  77. package/dist/commands/skills/command-helpers.js +6 -22
  78. package/dist/commands/skills/command-helpers.js.map +1 -1
  79. package/dist/commands/skills/list.d.ts +18 -0
  80. package/dist/commands/skills/list.d.ts.map +1 -1
  81. package/dist/commands/skills/list.js +41 -15
  82. package/dist/commands/skills/list.js.map +1 -1
  83. package/dist/commands/skills/package.d.ts +15 -0
  84. package/dist/commands/skills/package.d.ts.map +1 -1
  85. package/dist/commands/skills/package.js +30 -20
  86. package/dist/commands/skills/package.js.map +1 -1
  87. package/dist/commands/skills/validate-command.d.ts.map +1 -1
  88. package/dist/commands/skills/validate-command.js +22 -6
  89. package/dist/commands/skills/validate-command.js.map +1 -1
  90. package/dist/commands/skills/validate.d.ts +44 -3
  91. package/dist/commands/skills/validate.d.ts.map +1 -1
  92. package/dist/commands/skills/validate.js +180 -46
  93. package/dist/commands/skills/validate.js.map +1 -1
  94. package/dist/commands/validate.d.ts +18 -0
  95. package/dist/commands/validate.d.ts.map +1 -1
  96. package/dist/commands/validate.js +73 -66
  97. package/dist/commands/validate.js.map +1 -1
  98. package/dist/commands/verify.d.ts +38 -1
  99. package/dist/commands/verify.d.ts.map +1 -1
  100. package/dist/commands/verify.js +203 -83
  101. package/dist/commands/verify.js.map +1 -1
  102. package/dist/index.d.ts +1 -1
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.js +7 -1
  105. package/dist/index.js.map +1 -1
  106. package/dist/skill-resolution/index.d.ts +1 -1
  107. package/dist/skill-resolution/index.d.ts.map +1 -1
  108. package/dist/skill-resolution/index.js +1 -1
  109. package/dist/skill-resolution/index.js.map +1 -1
  110. package/dist/skill-resolution/packaging-config.d.ts +15 -2
  111. package/dist/skill-resolution/packaging-config.d.ts.map +1 -1
  112. package/dist/skill-resolution/packaging-config.js +39 -2
  113. package/dist/skill-resolution/packaging-config.js.map +1 -1
  114. package/dist/utils/command-error.d.ts +34 -1
  115. package/dist/utils/command-error.d.ts.map +1 -1
  116. package/dist/utils/command-error.js +32 -1
  117. package/dist/utils/command-error.js.map +1 -1
  118. package/dist/utils/help-loader.d.ts +6 -2
  119. package/dist/utils/help-loader.d.ts.map +1 -1
  120. package/dist/utils/help-loader.js +9 -16
  121. package/dist/utils/help-loader.js.map +1 -1
  122. package/dist/utils/issue-rendering.d.ts +88 -0
  123. package/dist/utils/issue-rendering.d.ts.map +1 -1
  124. package/dist/utils/issue-rendering.js +94 -0
  125. package/dist/utils/issue-rendering.js.map +1 -1
  126. package/dist/utils/output.d.ts +54 -5
  127. package/dist/utils/output.d.ts.map +1 -1
  128. package/dist/utils/output.js +111 -17
  129. package/dist/utils/output.js.map +1 -1
  130. package/dist/utils/relativize-paths.d.ts +45 -0
  131. package/dist/utils/relativize-paths.d.ts.map +1 -0
  132. package/dist/utils/relativize-paths.js +67 -0
  133. package/dist/utils/relativize-paths.js.map +1 -0
  134. package/dist/utils/resource-loader.d.ts +21 -2
  135. package/dist/utils/resource-loader.d.ts.map +1 -1
  136. package/dist/utils/resource-loader.js +85 -11
  137. package/dist/utils/resource-loader.js.map +1 -1
  138. package/dist/utils/skill-packaging-config.d.ts +22 -1
  139. package/dist/utils/skill-packaging-config.d.ts.map +1 -1
  140. package/dist/utils/skill-packaging-config.js +26 -0
  141. package/dist/utils/skill-packaging-config.js.map +1 -1
  142. package/dist/utils/verdict-helpers.d.ts +4 -3
  143. package/dist/utils/verdict-helpers.d.ts.map +1 -1
  144. package/dist/utils/verdict-helpers.js +6 -13
  145. package/dist/utils/verdict-helpers.js.map +1 -1
  146. package/docs/rag.md +9 -4
  147. package/docs/resources.md +30 -14
  148. package/docs/skills.md +9 -5
  149. package/package.json +12 -12
@@ -6,27 +6,37 @@
6
6
  * packaging config (schema defaults -> config defaults -> per-skill overrides),
7
7
  * validates, and packages into dist/skills/<name>/.
8
8
  */
9
- import { type SeverityCounts } from '@vibe-agent-toolkit/agent-schema';
10
- import { type PackageSkillResult, type PackagingValidationResult } from '@vibe-agent-toolkit/agent-skills';
9
+ import { type SeverityCounts, type ValidationIssue } from '@vibe-agent-toolkit/agent-schema';
10
+ import { type DeclaredEvalSuite, type PackageSkillResult, type PackagingValidationResult, type SkillPackagingConfig } from '@vibe-agent-toolkit/agent-skills';
11
11
  import { Command } from 'commander';
12
+ import { type createLogger } from '../../utils/logger.js';
13
+ import { type DiscoveredSkill } from './command-helpers.js';
12
14
  export interface SkillsBuildCommandOptions {
13
15
  skill?: string;
14
16
  dryRun?: boolean;
15
17
  debug?: boolean;
18
+ verbose?: boolean;
16
19
  }
17
20
  export declare function createBuildCommand(): Command;
18
21
  /**
19
22
  * Render every emitted pre-build finding, each labelled with its own severity.
20
23
  *
21
24
  * `allErrors` is the full emitted set INCLUDING info (its name lies — see the
22
- * doc comment on `PackagingValidationResult`). Walking `activeErrors` plus only
23
- * the `ALLOW_EXPIRED` subset of `activeWarnings`, which is what this used to do,
25
+ * doc comment on `PackagingValidationResult`). Walking only the errors plus the
26
+ * `ALLOW_EXPIRED` subset of the warnings, which is what this used to do,
24
27
  * dropped every other warning and every info finding from a report that had
25
28
  * already decided to abort the build.
26
29
  *
30
+ * `verbose` picks how much of that set is spelled out, never how much of it is
31
+ * COUNTED: the heading is built from `allErrors` either way, so the severity
32
+ * breakdown stays whole even when the bodies below it do not (see
33
+ * `issuesToRenderAtVerbosity`). Every error is printed at both verbosities —
34
+ * these are the findings that just aborted the build, so putting one behind a
35
+ * flag would mean re-running to learn what broke.
36
+ *
27
37
  * Pure so the whole set is assertable, not a chosen subset of it.
28
38
  */
29
- export declare function formatPreBuildIssueReport(validationResult: PackagingValidationResult): string[];
39
+ export declare function formatPreBuildIssueReport(validationResult: PackagingValidationResult, verbose: boolean): string[];
30
40
  /**
31
41
  * Render post-build integrity issues, each prefixed by its OWN resolved severity.
32
42
  *
@@ -35,10 +45,18 @@ export declare function formatPreBuildIssueReport(validationResult: PackagingVal
35
45
  * failed it, and the heading names the set's actual severity mix rather than
36
46
  * calling every set by its worst member.
37
47
  *
48
+ * The heading is unconditional and counts the WHOLE set; `verbose` decides only
49
+ * which findings get a block beneath it (see `issuesToRenderAtVerbosity`). This
50
+ * loop printing every occurrence is what made a 90-skill build emit 6,552 stderr
51
+ * lines against the 804 `vat skills validate` emitted for the same corpus — 1,620
52
+ * of them one high-cardinality warning code. A set whose findings all collapse
53
+ * still renders its heading: collapsing that too would turn a warning-carrying
54
+ * build into silence, which is the reassuring direction this module warns about.
55
+ *
38
56
  * Pure: returns the lines instead of writing them, so the whole rendered set is
39
57
  * assertable without capturing a stream.
40
58
  */
41
- export declare function formatPostBuildIssueReport(result: PackageSkillResult): string[];
59
+ export declare function formatPostBuildIssueReport(result: PackageSkillResult, verbose: boolean): string[];
42
60
  /**
43
61
  * The archived summary of a build, as YAML fields.
44
62
  *
@@ -48,20 +66,91 @@ export declare function formatPostBuildIssueReport(result: PackageSkillResult):
48
66
  * from the same issues the human stream renders, and the per-severity counts
49
67
  * ride beside it because a status cannot express a three-valued distribution:
50
68
  * `success` here means "nothing you must act on", not "there was nothing to see".
69
+ *
70
+ * The header total reconciles against the document, by construction:
71
+ *
72
+ * issueCounts === Σ skills[].issueCounts
73
+ * + Σ failedSkills[].issueCounts
74
+ * + runIssueCounts
75
+ *
76
+ * Every addend therefore has a ROW a reader can point at. Counting the failures
77
+ * only in the header — which is how this first shipped — reproduced, one command
78
+ * over, the exact defect `vat skills validate` was fixed for: a header reporting
79
+ * more than its rows summed to (there, 1814 warnings against 1800), leaving a
80
+ * consumer to hand-count a list to find out what the difference was. `--help`
81
+ * states this identity; changing it means changing that text too.
51
82
  */
52
83
  export declare function buildYamlSummary(results: Array<{
53
84
  name: string;
54
85
  result: PackageSkillResult;
55
- }>, duration: number): {
86
+ }>, failures: readonly SkillBuildFailure[], duration: number, runIssues: readonly ValidationIssue[], skillsWithErrors: readonly string[]): {
56
87
  status: 'success' | 'warning' | 'error';
57
88
  issueCounts: SeverityCounts;
89
+ runIssueCounts: SeverityCounts;
58
90
  skillsBuilt: number;
91
+ skillsFailed: number;
92
+ skillsWithErrors: string[];
59
93
  skills: Array<{
60
94
  name: string;
61
95
  outputPath: string;
62
96
  filesPackaged: number;
63
97
  issueCounts: SeverityCounts;
64
98
  }>;
99
+ failedSkills: Array<{
100
+ name: string;
101
+ error: string;
102
+ issueCounts: SeverityCounts;
103
+ }>;
104
+ runIssues: ValidationIssue[];
65
105
  duration: string;
66
106
  };
107
+ /** One discovered skill paired with the packaging config merged for it. */
108
+ export interface BuildSkillSpec {
109
+ skill: DiscoveredSkill;
110
+ packagingConfig: SkillPackagingConfig;
111
+ }
112
+ /**
113
+ * A skill whose packaging THREW — it produced no artifact at all.
114
+ *
115
+ * Deliberately distinct from `skillsWithErrors`, which names skills that built
116
+ * successfully and then failed post-build validation. Collapsing the two would
117
+ * lose the only distinction that matters to the reader: whether `dist/skills/`
118
+ * contains anything for that name.
119
+ */
120
+ export interface SkillBuildFailure {
121
+ name: string;
122
+ message: string;
123
+ }
124
+ /** Everything one `vat build` invocation produced, ready to report on. */
125
+ export interface SkillBuildRun {
126
+ results: Array<{
127
+ name: string;
128
+ result: PackageSkillResult;
129
+ }>;
130
+ /** Findings that belong to the run, not to any one skill (ALLOW_UNUSED). */
131
+ runIssues: ValidationIssue[];
132
+ /** Names of skills that BUILT and whose own post-build validation errored. */
133
+ skillsWithErrors: string[];
134
+ /** Skills that never built because packaging threw. */
135
+ failures: SkillBuildFailure[];
136
+ }
137
+ /**
138
+ * Validate, package, and drain — the whole span of ONE `vat build` invocation.
139
+ *
140
+ * The allow-usage ledger created here spans EVERY skill and BOTH validation
141
+ * lanes (the source-tree pre-build check and the two lanes inside
142
+ * `packageSkill`), because `validation.allow` is declared once for the package
143
+ * while being evaluated once per skill per lane. Anything narrower reports
144
+ * entries that legitimately matched somewhere else as unused: measured on this
145
+ * repo's own 13-skill package, 3 live entries produced 32 false ALLOW_UNUSED
146
+ * warnings — 6 from the cross-skill seam, 26 because the two lanes inside
147
+ * `packageSkill` see a FILTERED issue population against a file packaging has
148
+ * renamed to `SKILL.md`, so entries scoped to a source filename are structurally
149
+ * incapable of matching there. A lane that sees a subset cannot answer "matched
150
+ * nothing"; only the union can. Hence one ledger, drained once, here.
151
+ *
152
+ * Extracted from the command body so the span is testable without driving
153
+ * `process.exit` — the drain seam is the thing worth asserting.
154
+ */
155
+ export declare function runSkillBuild(specs: readonly BuildSkillSpec[], cwd: string, logger: ReturnType<typeof createLogger>, projectSkills: readonly DeclaredEvalSuite[], verbose?: boolean): Promise<SkillBuildRun>;
67
156
  //# sourceMappingURL=build.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/skills/build.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAKL,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAG/B,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwBpC,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAkE5C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,gBAAgB,EAAE,yBAAyB,GAC1C,MAAM,EAAE,CASV;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,EAAE,CAQ/E;AAwHD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAA;CAAE,CAAC,EAC5D,QAAQ,EAAE,MAAM,GACf;IACD,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;IACxC,WAAW,EAAE,cAAc,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,cAAc,CAAC;KAC7B,CAAC,CAAC;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB,CAyBA"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/skills/build.ts"],"names":[],"mappings":"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"}
@@ -8,16 +8,16 @@
8
8
  */
9
9
  import { existsSync } from 'node:fs';
10
10
  import { rm } from 'node:fs/promises';
11
- import { calculateValidationStatus, countBySeverity, } from '@vibe-agent-toolkit/agent-schema';
11
+ import { allowUnusedIssues, calculateValidationStatus, countBySeverity, createAllowUsageLedger, } from '@vibe-agent-toolkit/agent-schema';
12
12
  import { packageSkills, packagingConfigToPackageOptions, skillNameToFsPath, validateSkillForPackaging, } from '@vibe-agent-toolkit/agent-skills';
13
13
  import { safePath } from '@vibe-agent-toolkit/utils';
14
14
  import { Command } from 'commander';
15
15
  import * as yaml from 'yaml';
16
- import { handleCommandError } from '../../utils/command-error.js';
16
+ import { handleCommandError, handleValidationGateFailure } from '../../utils/command-error.js';
17
17
  import { loadConfig } from '../../utils/config-loader.js';
18
- import { collectPostBuildIssues, formatIssueLines, formatIssueSetHeading, sumSeverityCounts, } from '../../utils/issue-rendering.js';
18
+ import { collectPostBuildIssues, formatIssueLines, formatIssueSetHeading, formatRunIssueLines, issuesToRenderAtVerbosity, sumSeverityCounts, } from '../../utils/issue-rendering.js';
19
19
  import { requireProjectRoot } from '../../utils/project-root-policy.js';
20
- import { mergeSkillPackagingConfig } from '../../utils/skill-packaging-config.js';
20
+ import { collectDeclaredEvalSuites, mergeSkillPackagingConfig } from '../../utils/skill-packaging-config.js';
21
21
  import { applyConfigVerdicts } from '../../utils/verdict-helpers.js';
22
22
  import { filterSkillsByName, setupCommandContext, writeYamlHeader, } from './command-helpers.js';
23
23
  import { discoverSkillsFromConfig } from './skill-discovery.js';
@@ -28,6 +28,7 @@ export function createBuildCommand() {
28
28
  .argument('[path]', 'Path to project directory (default: current directory)')
29
29
  .option('--skill <name>', 'Build specific skill only')
30
30
  .option('--dry-run', 'Preview build without creating files')
31
+ .option('-v, --verbose', 'Show every individual finding, not just the errors')
31
32
  .option('--debug', 'Enable debug logging')
32
33
  .action(buildCommand)
33
34
  .addHelpText('after', `
@@ -67,9 +68,38 @@ Output:
67
68
  YAML summary -> stdout (for programmatic parsing)
68
69
  Build progress -> stderr (for human reading)
69
70
 
71
+ On stderr, every findings heading names the whole set and its severity
72
+ breakdown, and errors are always printed in full beneath it. Warnings and
73
+ info findings stay collapsed into that heading unless --verbose, because
74
+ they are the high-cardinality ones (one adopter skill carries 348
75
+ LINK_DROPPED_BY_DEPTH warnings alone). The stdout YAML is NOT affected by
76
+ --verbose: it carries every finding at every verbosity.
77
+
78
+ skills: one row per skill that produced a bundle
79
+ failedSkills: one row per skill that could not be packaged AT ALL (no
80
+ bundle exists for it); each carries name, error and an
81
+ issueCounts of one error
82
+ skillsWithErrors / skillsWithErrorNames:
83
+ the OTHER failure mode — skills that packaged fine and then
84
+ emitted post-build validation errors. They are counted in
85
+ skillsBuilt, not skillsFailed, so read BOTH before concluding
86
+ a run was clean: the exit code follows this one too.
87
+ runIssueCounts: findings that belong to the run rather than to any one
88
+ skill (ALLOW_UNUSED)
89
+ issueCounts: the run total, which reconciles against the rows above:
90
+
91
+ issueCounts = sum(skills[].issueCounts)
92
+ + sum(failedSkills[].issueCounts)
93
+ + runIssueCounts
94
+
95
+ Every error, warning and info in the header total is
96
+ therefore attributable to a row you can point at.
97
+
70
98
  Exit Codes:
71
99
  0 - All skills built successfully (or dry-run preview)
72
- 1 - One or more skills emitted validation errors
100
+ 1 - One or more skills emitted validation errors, or could not be packaged
101
+ at all (a skill that fails to package does NOT abort the others; the
102
+ rest still build and the failures are listed under failedSkills)
73
103
  2 - System error (config invalid, directory not found)
74
104
 
75
105
  Requirements:
@@ -87,18 +117,25 @@ Example:
87
117
  * Render every emitted pre-build finding, each labelled with its own severity.
88
118
  *
89
119
  * `allErrors` is the full emitted set INCLUDING info (its name lies — see the
90
- * doc comment on `PackagingValidationResult`). Walking `activeErrors` plus only
91
- * the `ALLOW_EXPIRED` subset of `activeWarnings`, which is what this used to do,
120
+ * doc comment on `PackagingValidationResult`). Walking only the errors plus the
121
+ * `ALLOW_EXPIRED` subset of the warnings, which is what this used to do,
92
122
  * dropped every other warning and every info finding from a report that had
93
123
  * already decided to abort the build.
94
124
  *
125
+ * `verbose` picks how much of that set is spelled out, never how much of it is
126
+ * COUNTED: the heading is built from `allErrors` either way, so the severity
127
+ * breakdown stays whole even when the bodies below it do not (see
128
+ * `issuesToRenderAtVerbosity`). Every error is printed at both verbosities —
129
+ * these are the findings that just aborted the build, so putting one behind a
130
+ * flag would mean re-running to learn what broke.
131
+ *
95
132
  * Pure so the whole set is assertable, not a chosen subset of it.
96
133
  */
97
- export function formatPreBuildIssueReport(validationResult) {
134
+ export function formatPreBuildIssueReport(validationResult, verbose) {
98
135
  const lines = [];
99
136
  if (validationResult.allErrors.length > 0) {
100
137
  lines.push(`\n ${formatIssueSetHeading(validationResult.allErrors)}:`);
101
- for (const issue of validationResult.allErrors) {
138
+ for (const issue of issuesToRenderAtVerbosity(validationResult.allErrors, verbose)) {
102
139
  lines.push(...formatIssueLines(issue, ' '));
103
140
  }
104
141
  }
@@ -112,15 +149,23 @@ export function formatPreBuildIssueReport(validationResult) {
112
149
  * failed it, and the heading names the set's actual severity mix rather than
113
150
  * calling every set by its worst member.
114
151
  *
152
+ * The heading is unconditional and counts the WHOLE set; `verbose` decides only
153
+ * which findings get a block beneath it (see `issuesToRenderAtVerbosity`). This
154
+ * loop printing every occurrence is what made a 90-skill build emit 6,552 stderr
155
+ * lines against the 804 `vat skills validate` emitted for the same corpus — 1,620
156
+ * of them one high-cardinality warning code. A set whose findings all collapse
157
+ * still renders its heading: collapsing that too would turn a warning-carrying
158
+ * build into silence, which is the reassuring direction this module warns about.
159
+ *
115
160
  * Pure: returns the lines instead of writing them, so the whole rendered set is
116
161
  * assertable without capturing a stream.
117
162
  */
118
- export function formatPostBuildIssueReport(result) {
163
+ export function formatPostBuildIssueReport(result, verbose) {
119
164
  const issues = collectPostBuildIssues(result);
120
165
  if (issues.length === 0)
121
166
  return [];
122
167
  const lines = [` ${formatIssueSetHeading(issues, 'post-build')}:`];
123
- for (const issue of issues) {
168
+ for (const issue of issuesToRenderAtVerbosity(issues, verbose)) {
124
169
  lines.push(...formatIssueLines(issue, ' '));
125
170
  }
126
171
  return lines;
@@ -129,8 +174,8 @@ export function formatPostBuildIssueReport(result) {
129
174
  * Log post-build integrity issues to stderr (the human stream; stdout is
130
175
  * reserved for the YAML summary).
131
176
  */
132
- function logPostBuildIssues(result, logger) {
133
- for (const line of formatPostBuildIssueReport(result)) {
177
+ function logPostBuildIssues(result, logger, verbose) {
178
+ for (const line of formatPostBuildIssueReport(result, verbose)) {
134
179
  logger.info(line);
135
180
  }
136
181
  }
@@ -145,12 +190,17 @@ function displayIgnoredErrors(validationResult, logger) {
145
190
  }
146
191
  }
147
192
  }
148
- /**
149
- * Validate skill before building
150
- */
151
- async function validateSkillOrExit(skillName, sourcePath, packagingConfig, logger, locationRoot) {
193
+ async function validateSkillOrExit(input) {
194
+ const { skillName, sourcePath, packagingConfig, logger, locationRoot, allowLedger, projectSkills, verbose } = input;
152
195
  logger.debug(` Validating skill: ${skillName}`);
153
- const validationResult = await validateSkillForPackaging(sourcePath, packagingConfig);
196
+ // The run's ledger, not this call's: an allow entry scoped to a SOURCE
197
+ // filename can only ever match here — packaging renames the file to
198
+ // `SKILL.md` and the built lane drops the source-only codes — so a build that
199
+ // withholds this lane's matches from the run reports live entries as dead.
200
+ //
201
+ // The run's declared eval suites likewise: this lane must model the same bundle
202
+ // the packager below produces, and that bundle excludes EVERY skill's test input.
203
+ const validationResult = await validateSkillForPackaging(sourcePath, packagingConfig, 'source', { allowLedger, projectSkills });
154
204
  applyConfigVerdicts(validationResult, packagingConfig.targets, sourcePath, locationRoot);
155
205
  if (validationResult.status !== 'error') {
156
206
  if (validationResult.ignoredErrors.length > 0) {
@@ -161,12 +211,15 @@ async function validateSkillOrExit(skillName, sourcePath, packagingConfig, logge
161
211
  // Validation failed - display every emitted finding and exit
162
212
  logger.error(`\nSkill validation failed: ${skillName}`);
163
213
  logger.error(` Source: ${sourcePath}`);
164
- for (const line of formatPreBuildIssueReport(validationResult)) {
214
+ for (const line of formatPreBuildIssueReport(validationResult, verbose)) {
165
215
  logger.error(line);
166
216
  }
167
217
  displayIgnoredErrors(validationResult, logger);
168
218
  logger.error(`\n Build aborted due to validation errors`);
169
- process.exit(1);
219
+ // Every finding above went to STDERR. Exiting here without the stdout summary
220
+ // is what made `vat skills build | jq .status` return an empty document
221
+ // alongside exit 1 — the one case the help text documents exit 1 for.
222
+ handleValidationGateFailure(skillName, validationResult.allErrors);
170
223
  }
171
224
  /**
172
225
  * Output dry-run results
@@ -206,6 +259,14 @@ function performDryRun(skillsToBuild, duration, logger) {
206
259
  logger.info(`\nDry-run complete (no files created)`);
207
260
  logger.info(` Run without --dry-run to build the skills`);
208
261
  }
262
+ /**
263
+ * What ONE unpackageable skill contributes to the run's counts.
264
+ *
265
+ * A fresh object per row: these are published as separate YAML nodes, and a
266
+ * shared reference is how a document grows anchors (or a later mutation edits
267
+ * every row at once).
268
+ */
269
+ const failureIssueCounts = () => ({ errors: 1, warnings: 0, info: 0 });
209
270
  /**
210
271
  * The archived summary of a build, as YAML fields.
211
272
  *
@@ -215,12 +276,37 @@ function performDryRun(skillsToBuild, duration, logger) {
215
276
  * from the same issues the human stream renders, and the per-severity counts
216
277
  * ride beside it because a status cannot express a three-valued distribution:
217
278
  * `success` here means "nothing you must act on", not "there was nothing to see".
279
+ *
280
+ * The header total reconciles against the document, by construction:
281
+ *
282
+ * issueCounts === Σ skills[].issueCounts
283
+ * + Σ failedSkills[].issueCounts
284
+ * + runIssueCounts
285
+ *
286
+ * Every addend therefore has a ROW a reader can point at. Counting the failures
287
+ * only in the header — which is how this first shipped — reproduced, one command
288
+ * over, the exact defect `vat skills validate` was fixed for: a header reporting
289
+ * more than its rows summed to (there, 1814 warnings against 1800), leaving a
290
+ * consumer to hand-count a list to find out what the difference was. `--help`
291
+ * states this identity; changing it means changing that text too.
218
292
  */
219
- export function buildYamlSummary(results, duration) {
293
+ export function buildYamlSummary(results, failures, duration, runIssues, skillsWithErrors) {
220
294
  const perSkill = results.map(({ name, result }) => {
221
295
  const issues = collectPostBuildIssues(result);
222
296
  return {
223
297
  name,
298
+ // KNOWN, DELIBERATELY NOT FIXED — this is an ABSOLUTE path, so stdout carries
299
+ // `$HOME`. Confirmed on a real 90-skill adopter run:
300
+ // `outputPath: /Users/<user>/Workspaces/.../dist/skills/<name>`. It is absolute
301
+ // because `runSkillBuild` builds it with `safePath.resolve(cwd, 'dist', ...)`.
302
+ //
303
+ // Do NOT "helpfully" relativize it. It is blocked on an approved-but-unbuilt
304
+ // design decision that is the project owner's call: the document must state one
305
+ // `root:` and re-base every path onto it, the same coordinate-system rule
306
+ // `vat audit` already follows (see `deriveScanRoot` in ../audit.ts — "the ONE base
307
+ // every `path` and `location` in a report is expressed relative to"). Making this
308
+ // one field relative ahead of that decision picks the anchor by accident and
309
+ // leaves the document in two coordinate systems.
224
310
  outputPath: result.outputPath,
225
311
  filesPackaged: result.files.dependencies.length + 1,
226
312
  issueCounts: countBySeverity(issues),
@@ -228,26 +314,76 @@ export function buildYamlSummary(results, duration) {
228
314
  };
229
315
  });
230
316
  const allIssues = perSkill.flatMap((s) => s.issues);
317
+ const runIssueCounts = countBySeverity(runIssues);
318
+ // A skill that THREW emits no issues at all — it never reached the lanes that
319
+ // produce them. Deriving the header purely from issue channels therefore said
320
+ // `success` for a run the command then exited 1 on, which is exactly the
321
+ // reassuring contradiction this summary exists to prevent. Each failure is
322
+ // counted as one error so `status`, `issueCounts` and the exit code agree —
323
+ // and that error is published ON the failure's own row (see
324
+ // `failureIssueCounts`), never as a header-only addend, so the identity
325
+ // documented in `--help` holds.
326
+ const failedSkills = failures.map(({ name, message }) => ({
327
+ name,
328
+ error: message,
329
+ issueCounts: failureIssueCounts(),
330
+ }));
231
331
  return {
232
- status: calculateValidationStatus(allIssues),
233
- issueCounts: sumSeverityCounts(perSkill.map((s) => s.issueCounts)),
332
+ status: failures.length > 0
333
+ ? 'error'
334
+ : calculateValidationStatus([...allIssues, ...runIssues]),
335
+ issueCounts: sumSeverityCounts([
336
+ ...perSkill.map((s) => s.issueCounts),
337
+ ...failedSkills.map((s) => s.issueCounts),
338
+ runIssueCounts,
339
+ ]),
340
+ runIssueCounts,
234
341
  skillsBuilt: results.length,
342
+ skillsFailed: failures.length,
343
+ // The OTHER meaning of "failed", published because the exit code follows
344
+ // THIS one and nothing in the document did. `skillsFailed` counts skills
345
+ // that could not be packaged at all; a skill that packaged fine and then
346
+ // emitted post-build validation errors is a `skillsBuilt`, so a real run
347
+ // printed "Build failed: 3 skill(s) emitted post-build validation errors"
348
+ // over `skillsFailed: 0` and `failedSkills: []`, and exited 1. A CI job
349
+ // reading either field saw a clean build. Both categories are now named.
350
+ skillsWithErrors: [...skillsWithErrors],
351
+ // `skills` lists what exists on disk. A failed skill is published in its own
352
+ // list rather than here, because every field of this shape (outputPath,
353
+ // filesPackaged) would have to be invented for a bundle that was not written.
235
354
  skills: perSkill.map(({ name, outputPath, filesPackaged, issueCounts }) => ({
236
355
  name,
237
356
  outputPath,
238
357
  filesPackaged,
239
358
  issueCounts,
240
359
  })),
360
+ failedSkills,
361
+ runIssues: [...runIssues],
241
362
  duration: `${duration}ms`,
242
363
  };
243
364
  }
244
365
  /**
245
366
  * Output build results
246
367
  */
247
- function outputBuildYaml(results, duration) {
248
- const { status, skillsBuilt, issueCounts, skills, duration: durationText } = buildYamlSummary(results, duration);
249
- writeYamlHeader({ status, skillsBuilt });
250
- process.stdout.write(yaml.stringify({ issueCounts, skills }, { indent: 2, lineWidth: 0, aliasDuplicateObjects: false }));
368
+ function outputBuildYaml(results, failures, duration, runIssues, skillsWithErrors) {
369
+ const summary = buildYamlSummary(results, failures, duration, runIssues, skillsWithErrors);
370
+ const { status, skillsBuilt, skillsFailed, issueCounts, runIssueCounts, skills, failedSkills, duration: durationText, } = summary;
371
+ // In the header, beside the other two counts: this is the number the exit code
372
+ // actually follows, so a reader who stops at the header is not misled by it.
373
+ writeYamlHeader({
374
+ status,
375
+ skillsBuilt,
376
+ skillsFailed,
377
+ skillsWithErrors: summary.skillsWithErrors.length,
378
+ });
379
+ process.stdout.write(yaml.stringify({
380
+ issueCounts,
381
+ runIssueCounts,
382
+ skills,
383
+ failedSkills,
384
+ skillsWithErrorNames: summary.skillsWithErrors,
385
+ runIssues: summary.runIssues,
386
+ }, { indent: 2, lineWidth: 0, aliasDuplicateObjects: false }));
251
387
  process.stdout.write(`duration: ${durationText}\n`);
252
388
  }
253
389
  /**
@@ -270,6 +406,94 @@ async function cleanStaleSkillOutputs(cwd, skillName) {
270
406
  }
271
407
  }
272
408
  }
409
+ /**
410
+ * Validate, package, and drain — the whole span of ONE `vat build` invocation.
411
+ *
412
+ * The allow-usage ledger created here spans EVERY skill and BOTH validation
413
+ * lanes (the source-tree pre-build check and the two lanes inside
414
+ * `packageSkill`), because `validation.allow` is declared once for the package
415
+ * while being evaluated once per skill per lane. Anything narrower reports
416
+ * entries that legitimately matched somewhere else as unused: measured on this
417
+ * repo's own 13-skill package, 3 live entries produced 32 false ALLOW_UNUSED
418
+ * warnings — 6 from the cross-skill seam, 26 because the two lanes inside
419
+ * `packageSkill` see a FILTERED issue population against a file packaging has
420
+ * renamed to `SKILL.md`, so entries scoped to a source filename are structurally
421
+ * incapable of matching there. A lane that sees a subset cannot answer "matched
422
+ * nothing"; only the union can. Hence one ledger, drained once, here.
423
+ *
424
+ * Extracted from the command body so the span is testable without driving
425
+ * `process.exit` — the drain seam is the thing worth asserting.
426
+ */
427
+ export async function runSkillBuild(specs, cwd, logger, projectSkills, verbose = false) {
428
+ const allowLedger = createAllowUsageLedger();
429
+ // Validate all skills before building
430
+ for (const { skill, packagingConfig } of specs) {
431
+ const outputDir = safePath.resolve(cwd, 'dist', 'skills', skillNameToFsPath(skill.name));
432
+ logger.info(`\nBuilding skill: ${skill.name}`);
433
+ logger.info(` Source: ${skill.sourcePath}`);
434
+ logger.info(` Output: ${outputDir}`);
435
+ await validateSkillOrExit({
436
+ skillName: skill.name,
437
+ sourcePath: skill.sourcePath,
438
+ packagingConfig,
439
+ logger,
440
+ locationRoot: cwd,
441
+ allowLedger,
442
+ projectSkills,
443
+ verbose,
444
+ });
445
+ }
446
+ // Build all skills with a shared registry.
447
+ //
448
+ // `projectSkills` is the run's declared eval suites — the whole project's, not
449
+ // `specs`'. `--skill x` narrows what gets BUILT; it never narrows what counts as
450
+ // test input, because an excluded skill's suite is still an answer key that must
451
+ // not ship inside x's bundle.
452
+ const buildSpecs = specs.map(({ skill, packagingConfig }) => ({
453
+ skillPath: skill.sourcePath,
454
+ options: packagingConfigToPackageOptions(packagingConfig, {
455
+ skillPath: skill.sourcePath,
456
+ outputPath: safePath.resolve(cwd, 'dist', 'skills', skillNameToFsPath(skill.name)),
457
+ }, projectSkills),
458
+ }));
459
+ const outcomes = await packageSkills(buildSpecs, cwd, allowLedger);
460
+ const results = [];
461
+ const skillsWithErrors = [];
462
+ const failures = [];
463
+ for (const [i, spec] of specs.entries()) {
464
+ const outcome = outcomes[i];
465
+ if (!outcome)
466
+ continue;
467
+ if (outcome.status === 'failed') {
468
+ // No `Built N files` line here: nothing was built, and claiming a count
469
+ // for an absent bundle is the misreport this branch exists to avoid.
470
+ logger.error(`\nBuild failed for skill: ${spec.skill.name}`);
471
+ logger.error(` ${outcome.error.message}`);
472
+ failures.push({ name: spec.skill.name, message: outcome.error.message });
473
+ continue;
474
+ }
475
+ const { result } = outcome;
476
+ logger.info(` Built ${result.files.dependencies.length + 1} files`);
477
+ logPostBuildIssues(result, logger, verbose);
478
+ if (result.hasErrors) {
479
+ skillsWithErrors.push(spec.skill.name);
480
+ }
481
+ results.push({ name: spec.skill.name, result });
482
+ }
483
+ // Drain point: every skill and every lane has now contributed, so this is the
484
+ // first place the run can honestly say an entry matched nothing. A skill that
485
+ // threw still contributed the matches it made before throwing — which is why
486
+ // the drain must stay here, after a partially-failed batch, and not move into
487
+ // a success-only path. (Before per-skill containment the drain never ran at
488
+ // all on a throw, because the exception propagated straight past it.)
489
+ //
490
+ // Known residual: a skill that threw never reached its later lanes, so an
491
+ // allow entry only THAT skill could have matched may now be reported
492
+ // ALLOW_UNUSED. That is a warning, and `runHasErrors` gates on `error` only,
493
+ // so it cannot fail a build on its own — and the run is already exiting 1 on
494
+ // the failure the operator actually needs to fix.
495
+ return { results, runIssues: allowUnusedIssues(allowLedger), skillsWithErrors, failures };
496
+ }
273
497
  async function buildCommand(pathArg, options) {
274
498
  const { logger, cwd, startTime } = setupCommandContext(pathArg, options.debug);
275
499
  try {
@@ -303,47 +527,40 @@ async function buildCommand(pathArg, options) {
303
527
  performDryRun(skillsToBuild, duration, logger);
304
528
  process.exit(0);
305
529
  }
306
- // Validate all skills before building
307
- const validatedSpecs = [];
308
- for (const skill of skillsToBuild) {
309
- const packagingConfig = mergeSkillPackagingConfig(skillsConfig.defaults, skillsConfig.config?.[skill.name]);
310
- const outputDir = safePath.resolve(cwd, 'dist', 'skills', skillNameToFsPath(skill.name));
311
- logger.info(`\nBuilding skill: ${skill.name}`);
312
- logger.info(` Source: ${skill.sourcePath}`);
313
- logger.info(` Output: ${outputDir}`);
314
- await validateSkillOrExit(skill.name, skill.sourcePath, packagingConfig, logger, cwd);
315
- validatedSpecs.push({ skill, packagingConfig });
316
- }
317
- // Build all skills with a shared registry
318
- const specs = validatedSpecs.map(({ skill, packagingConfig }) => ({
319
- skillPath: skill.sourcePath,
320
- options: packagingConfigToPackageOptions(packagingConfig, {
321
- skillPath: skill.sourcePath,
322
- outputPath: safePath.resolve(cwd, 'dist', 'skills', skillNameToFsPath(skill.name)),
323
- }),
530
+ const buildSpecs = skillsToBuild.map((skill) => ({
531
+ skill,
532
+ packagingConfig: mergeSkillPackagingConfig(skillsConfig.defaults, skillsConfig.config?.[skill.name]),
324
533
  }));
325
- const packageResults = await packageSkills(specs, cwd);
326
- const results = [];
327
- const skillsWithErrors = [];
328
- for (const [i, spec] of validatedSpecs.entries()) {
329
- const result = packageResults[i];
330
- if (result) {
331
- logger.info(` Built ${result.files.dependencies.length + 1} files`);
332
- logPostBuildIssues(result, logger);
333
- if (result.hasErrors) {
334
- skillsWithErrors.push(spec.skill.name);
335
- }
336
- results.push({ name: spec.skill.name, result });
337
- }
338
- }
534
+ // Assembled ONCE, from the UNFILTERED discovery: `--skill x` narrows the build,
535
+ // not the set of files that count as some skill's declared test input.
536
+ const projectSkills = collectDeclaredEvalSuites(skillsConfig, discoveredSkills);
537
+ const { results, runIssues, skillsWithErrors, failures } = await runSkillBuild(buildSpecs, cwd, logger, projectSkills, options.verbose === true);
339
538
  const duration = Date.now() - startTime;
340
539
  // Output YAML results
341
- outputBuildYaml(results, duration);
540
+ outputBuildYaml(results, failures, duration, runIssues, skillsWithErrors);
541
+ for (const line of formatRunIssueLines(runIssues)) {
542
+ logger.info(line);
543
+ }
544
+ if (failures.length > 0) {
545
+ logger.error(`\nBuild failed: ${failures.length} skill(s) could not be packaged at all`);
546
+ for (const { name, message } of failures) {
547
+ logger.error(` - ${name}: ${message.split('\n')[0] ?? message}`);
548
+ }
549
+ }
342
550
  if (skillsWithErrors.length > 0) {
343
551
  logger.error(`\nBuild failed: ${skillsWithErrors.length} skill(s) emitted post-build validation errors`);
344
552
  for (const name of skillsWithErrors) {
345
553
  logger.error(` - ${name}`);
346
554
  }
555
+ }
556
+ // A run-level finding belongs to no skill, so the loop above cannot carry
557
+ // it. Only `error` severity gates: ALLOW_UNUSED ships as a `warning`, and a
558
+ // warning must never abort a build that produced its artifacts.
559
+ const runHasErrors = runIssues.some((i) => i.severity === 'error');
560
+ if (runHasErrors) {
561
+ logger.error(`\nBuild failed: run-level validation errors (project config, not any one skill)`);
562
+ }
563
+ if (skillsWithErrors.length > 0 || runHasErrors || failures.length > 0) {
347
564
  process.exit(1);
348
565
  }
349
566
  logger.info(`\nBuilt ${results.length} skill(s) successfully`);