@lesliechan721/aw 0.1.0-beta.8 → 0.1.0-beta.9

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 (275) hide show
  1. package/dist/authoring/scripts/add-skill.mjs +4 -4
  2. package/dist/authoring/scripts/add-skill.mjs.map +1 -1
  3. package/dist/authoring/scripts/init-plugin.mjs +4 -4
  4. package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
  5. package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
  6. package/dist/authoring/scripts/lib/skill-scaffold.d.mts +77 -90
  7. package/dist/authoring/scripts/lib/skill-scaffold.mjs +99 -120
  8. package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
  9. package/dist/authoring/scripts/remove-plugin.mjs +1 -1
  10. package/dist/authoring/scripts/remove-plugin.mjs.map +1 -1
  11. package/dist/authoring/scripts/validate-plugin.mjs +34 -778
  12. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  13. package/dist/scripts/aw-skill-eval-materializer.d.ts +24 -0
  14. package/dist/scripts/{plugin-eval-materializer.js → aw-skill-eval-materializer.js} +34 -145
  15. package/dist/scripts/aw-skill-eval-materializer.js.map +1 -0
  16. package/dist/scripts/bundle-plugins.d.ts +1 -0
  17. package/dist/scripts/bundle-plugins.js +2 -1
  18. package/dist/scripts/bundle-plugins.js.map +1 -1
  19. package/dist/scripts/plugin-staging.d.ts +5 -6
  20. package/dist/scripts/plugin-staging.js +12 -13
  21. package/dist/scripts/plugin-staging.js.map +1 -1
  22. package/dist/src/authoring/workspace-commands.d.ts +2 -1
  23. package/dist/src/authoring/workspace-commands.js.map +1 -1
  24. package/dist/src/commands/aw-capabilities.d.ts +2 -2
  25. package/dist/src/commands/aw-capabilities.js +6 -11
  26. package/dist/src/commands/aw-capabilities.js.map +1 -1
  27. package/dist/src/commands/catalog-dev-source.d.ts +18 -12
  28. package/dist/src/commands/catalog-dev-source.js +241 -305
  29. package/dist/src/commands/catalog-dev-source.js.map +1 -1
  30. package/dist/src/commands/catalog-generation.d.ts +6 -19
  31. package/dist/src/commands/catalog-generation.js +10 -11
  32. package/dist/src/commands/catalog-generation.js.map +1 -1
  33. package/dist/src/commands/catalog-publication-inspection.d.ts +28 -27
  34. package/dist/src/commands/catalog-publication-inspection.js +139 -50
  35. package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
  36. package/dist/src/commands/catalog-publication-journal.d.ts +49 -0
  37. package/dist/src/commands/catalog-publication-journal.js +147 -0
  38. package/dist/src/commands/catalog-publication-journal.js.map +1 -0
  39. package/dist/src/commands/catalog-publication-target.d.ts +2 -1
  40. package/dist/src/commands/catalog-publication-target.js +37 -2
  41. package/dist/src/commands/catalog-publication-target.js.map +1 -1
  42. package/dist/src/commands/catalog-publish.d.ts +51 -15
  43. package/dist/src/commands/catalog-publish.js +576 -219
  44. package/dist/src/commands/catalog-publish.js.map +1 -1
  45. package/dist/src/commands/catalog-source.d.ts +3 -28
  46. package/dist/src/commands/catalog-source.js +29 -140
  47. package/dist/src/commands/catalog-source.js.map +1 -1
  48. package/dist/src/commands/catalog-status.d.ts +6 -26
  49. package/dist/src/commands/catalog-status.js +8 -146
  50. package/dist/src/commands/catalog-status.js.map +1 -1
  51. package/dist/src/commands/catalog-workspace-resolution.d.ts +13 -0
  52. package/dist/src/commands/catalog-workspace-resolution.js +42 -0
  53. package/dist/src/commands/catalog-workspace-resolution.js.map +1 -0
  54. package/dist/src/commands/dependency-operation-plan.d.ts +88 -9
  55. package/dist/src/commands/dependency-operation-plan.js +331 -6
  56. package/dist/src/commands/dependency-operation-plan.js.map +1 -1
  57. package/dist/src/commands/init.d.ts +5 -11
  58. package/dist/src/commands/init.js +30 -51
  59. package/dist/src/commands/init.js.map +1 -1
  60. package/dist/src/commands/offline-cleanup-evidence.d.ts +20 -0
  61. package/dist/src/commands/offline-cleanup-evidence.js +87 -9
  62. package/dist/src/commands/offline-cleanup-evidence.js.map +1 -1
  63. package/dist/src/commands/options.d.ts +6 -1
  64. package/dist/src/commands/options.js +64 -6
  65. package/dist/src/commands/options.js.map +1 -1
  66. package/dist/src/commands/plugin-capabilities.js +4 -0
  67. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  68. package/dist/src/commands/rebuild-command.d.ts +10 -8
  69. package/dist/src/commands/rebuild-command.js +1217 -426
  70. package/dist/src/commands/rebuild-command.js.map +1 -1
  71. package/dist/src/commands/rebuild-integration.d.ts +54 -6
  72. package/dist/src/commands/rebuild-integration.js +691 -364
  73. package/dist/src/commands/rebuild-integration.js.map +1 -1
  74. package/dist/src/commands/rebuild-operations.d.ts +2 -0
  75. package/dist/src/commands/rebuild-operations.js +31 -17
  76. package/dist/src/commands/rebuild-operations.js.map +1 -1
  77. package/dist/src/commands/skill-eval.d.ts +33 -0
  78. package/dist/src/commands/skill-eval.js +363 -0
  79. package/dist/src/commands/skill-eval.js.map +1 -0
  80. package/dist/src/commands/{status-v5.d.ts → status-v6.d.ts} +59 -26
  81. package/dist/src/commands/{status-v5.js → status-v6.js} +882 -79
  82. package/dist/src/commands/status-v6.js.map +1 -0
  83. package/dist/src/commands/sync-command.d.ts +21 -0
  84. package/dist/src/commands/sync-command.js +206 -0
  85. package/dist/src/commands/sync-command.js.map +1 -0
  86. package/dist/src/commands/wizard.d.ts +2 -2
  87. package/dist/src/commands/write-commands.js +1 -43
  88. package/dist/src/commands/write-commands.js.map +1 -1
  89. package/dist/src/constants.d.ts +5 -4
  90. package/dist/src/constants.js +5 -4
  91. package/dist/src/constants.js.map +1 -1
  92. package/dist/src/eval/manifest-v1.d.ts +66 -0
  93. package/dist/src/eval/manifest-v1.js +326 -0
  94. package/dist/src/eval/manifest-v1.js.map +1 -0
  95. package/dist/src/generators/local-plugin.d.ts +3 -0
  96. package/dist/src/generators/local-plugin.js +123 -55
  97. package/dist/src/generators/local-plugin.js.map +1 -1
  98. package/dist/src/index.d.ts +2 -0
  99. package/dist/src/index.js +412 -152
  100. package/dist/src/index.js.map +1 -1
  101. package/dist/src/output/command-receipt.d.ts +58 -8
  102. package/dist/src/output/command-receipt.js +141 -11
  103. package/dist/src/output/command-receipt.js.map +1 -1
  104. package/dist/src/output/model.d.ts +3 -11
  105. package/dist/src/portable/commands.d.ts +2 -2
  106. package/dist/src/portable/commands.js +2 -8
  107. package/dist/src/portable/commands.js.map +1 -1
  108. package/dist/src/portable/materializer.d.ts +1 -4
  109. package/dist/src/portable/materializer.js +0 -2
  110. package/dist/src/portable/materializer.js.map +1 -1
  111. package/dist/src/tui/catalog-generation.d.ts +20 -41
  112. package/dist/src/tui/catalog-generation.js +65 -224
  113. package/dist/src/tui/catalog-generation.js.map +1 -1
  114. package/dist/src/tui/components.js +1 -2
  115. package/dist/src/tui/components.js.map +1 -1
  116. package/dist/src/tui/console-app-v4.js +461 -330
  117. package/dist/src/tui/console-app-v4.js.map +1 -1
  118. package/dist/src/tui/controller-runtime.d.ts +3 -5
  119. package/dist/src/tui/controller-runtime.js +182 -187
  120. package/dist/src/tui/controller-runtime.js.map +1 -1
  121. package/dist/src/tui/controller.d.ts +42 -45
  122. package/dist/src/tui/controller.js +454 -407
  123. package/dist/src/tui/controller.js.map +1 -1
  124. package/dist/src/tui/diagnostics.d.ts +1 -2
  125. package/dist/src/tui/diagnostics.js.map +1 -1
  126. package/dist/src/tui/index.d.ts +1 -1
  127. package/dist/src/tui/index.js +1 -1
  128. package/dist/src/tui/index.js.map +1 -1
  129. package/dist/src/tui/model.d.ts +81 -13
  130. package/dist/src/tui/model.js +378 -95
  131. package/dist/src/tui/model.js.map +1 -1
  132. package/dist/src/tui/router.d.ts +9 -0
  133. package/dist/src/tui/router.js.map +1 -1
  134. package/dist/src/types.d.ts +54 -47
  135. package/dist/src/types.js.map +1 -1
  136. package/dist/src/utils/agent-version.d.ts +4 -1
  137. package/dist/src/utils/agent-version.js +28 -3
  138. package/dist/src/utils/agent-version.js.map +1 -1
  139. package/dist/src/utils/catalog-digests.d.ts +7 -0
  140. package/dist/src/utils/catalog-digests.js +27 -0
  141. package/dist/src/utils/catalog-digests.js.map +1 -1
  142. package/dist/src/utils/catalog-generation-control-snapshot.d.ts +11 -0
  143. package/dist/src/utils/catalog-generation-control-snapshot.js +45 -0
  144. package/dist/src/utils/catalog-generation-control-snapshot.js.map +1 -0
  145. package/dist/src/utils/catalog-generation-error.d.ts +2 -1
  146. package/dist/src/utils/catalog-generation-error.js +12 -0
  147. package/dist/src/utils/catalog-generation-error.js.map +1 -1
  148. package/dist/src/utils/catalog-generation-resolver.d.ts +10 -3
  149. package/dist/src/utils/catalog-generation-resolver.js +90 -76
  150. package/dist/src/utils/catalog-generation-resolver.js.map +1 -1
  151. package/dist/src/utils/catalog-generation-service.d.ts +24 -33
  152. package/dist/src/utils/catalog-generation-service.js +253 -567
  153. package/dist/src/utils/catalog-generation-service.js.map +1 -1
  154. package/dist/src/utils/catalog-generation-state.d.ts +4 -0
  155. package/dist/src/utils/catalog-generation-state.js +73 -106
  156. package/dist/src/utils/catalog-generation-state.js.map +1 -1
  157. package/dist/src/utils/catalog-generation-store.d.ts +16 -26
  158. package/dist/src/utils/catalog-generation-store.js +154 -290
  159. package/dist/src/utils/catalog-generation-store.js.map +1 -1
  160. package/dist/src/utils/catalog-manifest-v6.d.ts +5 -0
  161. package/dist/src/utils/{catalog-manifest-v5.js → catalog-manifest-v6.js} +23 -35
  162. package/dist/src/utils/catalog-manifest-v6.js.map +1 -0
  163. package/dist/src/utils/catalog-operation-context.d.ts +6 -14
  164. package/dist/src/utils/catalog-operation-context.js +68 -169
  165. package/dist/src/utils/catalog-operation-context.js.map +1 -1
  166. package/dist/src/utils/catalog-public-error.js +1 -4
  167. package/dist/src/utils/catalog-public-error.js.map +1 -1
  168. package/dist/src/utils/catalog-runtime-projection.d.ts +3 -0
  169. package/dist/src/utils/catalog-runtime-projection.js +5 -40
  170. package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
  171. package/dist/src/utils/catalog-snapshot.d.ts +2 -2
  172. package/dist/src/utils/catalog-source-references.d.ts +7 -0
  173. package/dist/src/utils/catalog-source-references.js +44 -55
  174. package/dist/src/utils/catalog-source-references.js.map +1 -1
  175. package/dist/src/utils/catalog-sources.js +25 -9
  176. package/dist/src/utils/catalog-sources.js.map +1 -1
  177. package/dist/src/utils/codex-config.d.ts +2 -0
  178. package/dist/src/utils/codex-config.js +22 -1
  179. package/dist/src/utils/codex-config.js.map +1 -1
  180. package/dist/src/utils/codex-manager.d.ts +50 -2
  181. package/dist/src/utils/codex-manager.js +160 -6
  182. package/dist/src/utils/codex-manager.js.map +1 -1
  183. package/dist/src/utils/codex-reconciler.d.ts +131 -0
  184. package/dist/src/utils/codex-reconciler.js +327 -0
  185. package/dist/src/utils/codex-reconciler.js.map +1 -0
  186. package/dist/src/utils/codex-shared-native.d.ts +56 -0
  187. package/dist/src/utils/codex-shared-native.js +87 -0
  188. package/dist/src/utils/codex-shared-native.js.map +1 -0
  189. package/dist/src/utils/codex-surface-analysis.d.ts +44 -0
  190. package/dist/src/utils/codex-surface-analysis.js +247 -0
  191. package/dist/src/utils/codex-surface-analysis.js.map +1 -0
  192. package/dist/src/utils/git-catalog-provider.js +56 -115
  193. package/dist/src/utils/git-catalog-provider.js.map +1 -1
  194. package/dist/src/utils/manifest.d.ts +1 -3
  195. package/dist/src/utils/manifest.js +1 -3
  196. package/dist/src/utils/manifest.js.map +1 -1
  197. package/dist/src/utils/multi-source-catalog.d.ts +1 -10
  198. package/dist/src/utils/multi-source-catalog.js +8 -175
  199. package/dist/src/utils/multi-source-catalog.js.map +1 -1
  200. package/dist/src/utils/native-cli.d.ts +34 -0
  201. package/dist/src/utils/native-cli.js +91 -0
  202. package/dist/src/utils/native-cli.js.map +1 -1
  203. package/dist/src/utils/orphan-marketplace-cleanup.js +1 -0
  204. package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -1
  205. package/dist/src/utils/ownership.d.ts +0 -10
  206. package/dist/src/utils/ownership.js +3 -88
  207. package/dist/src/utils/ownership.js.map +1 -1
  208. package/dist/src/utils/path-catalog-provider.d.ts +1 -1
  209. package/dist/src/utils/path-catalog-provider.js +45 -62
  210. package/dist/src/utils/path-catalog-provider.js.map +1 -1
  211. package/dist/src/utils/paths.d.ts +0 -1
  212. package/dist/src/utils/paths.js +0 -1
  213. package/dist/src/utils/paths.js.map +1 -1
  214. package/dist/src/utils/plugin-config-write.js +0 -5
  215. package/dist/src/utils/plugin-config-write.js.map +1 -1
  216. package/dist/src/utils/plugin-intent-state.d.ts +39 -0
  217. package/dist/src/utils/plugin-intent-state.js +325 -0
  218. package/dist/src/utils/plugin-intent-state.js.map +1 -0
  219. package/dist/src/utils/target-eligibility.d.ts +3 -2
  220. package/dist/src/utils/target-eligibility.js +4 -3
  221. package/dist/src/utils/target-eligibility.js.map +1 -1
  222. package/package.json +5 -4
  223. package/dist/scripts/aw-skill-eval-engine.d.ts +0 -13
  224. package/dist/scripts/aw-skill-eval-engine.js +0 -31
  225. package/dist/scripts/aw-skill-eval-engine.js.map +0 -1
  226. package/dist/scripts/codex-eval-app-server.d.ts +0 -91
  227. package/dist/scripts/codex-eval-app-server.js +0 -1334
  228. package/dist/scripts/codex-eval-app-server.js.map +0 -1
  229. package/dist/scripts/codex-eval-isolation.d.ts +0 -61
  230. package/dist/scripts/codex-eval-isolation.js +0 -318
  231. package/dist/scripts/codex-eval-isolation.js.map +0 -1
  232. package/dist/scripts/codex-eval-process-management.d.ts +0 -8
  233. package/dist/scripts/codex-eval-process-management.js +0 -69
  234. package/dist/scripts/codex-eval-process-management.js.map +0 -1
  235. package/dist/scripts/codex-eval-process-tree.d.ts +0 -38
  236. package/dist/scripts/codex-eval-process-tree.js +0 -803
  237. package/dist/scripts/codex-eval-process-tree.js.map +0 -1
  238. package/dist/scripts/codex-eval-runtime-config.d.ts +0 -62
  239. package/dist/scripts/codex-eval-runtime-config.js +0 -843
  240. package/dist/scripts/codex-eval-runtime-config.js.map +0 -1
  241. package/dist/scripts/codex-trigger-eval.d.ts +0 -84
  242. package/dist/scripts/codex-trigger-eval.js +0 -968
  243. package/dist/scripts/codex-trigger-eval.js.map +0 -1
  244. package/dist/scripts/eval-plugin-skills-audited.d.ts +0 -6
  245. package/dist/scripts/eval-plugin-skills-audited.js +0 -261
  246. package/dist/scripts/eval-plugin-skills-audited.js.map +0 -1
  247. package/dist/scripts/eval-plugin-skills.d.ts +0 -45
  248. package/dist/scripts/eval-plugin-skills.js +0 -1589
  249. package/dist/scripts/eval-plugin-skills.js.map +0 -1
  250. package/dist/scripts/eval-run-layout.d.ts +0 -17
  251. package/dist/scripts/eval-run-layout.js +0 -26
  252. package/dist/scripts/eval-run-layout.js.map +0 -1
  253. package/dist/scripts/plugin-eval-contract.d.ts +0 -189
  254. package/dist/scripts/plugin-eval-contract.js +0 -745
  255. package/dist/scripts/plugin-eval-contract.js.map +0 -1
  256. package/dist/scripts/plugin-eval-materializer.d.ts +0 -13
  257. package/dist/scripts/plugin-eval-materializer.js.map +0 -1
  258. package/dist/scripts/skill-reporting.d.ts +0 -212
  259. package/dist/scripts/skill-reporting.js +0 -922
  260. package/dist/scripts/skill-reporting.js.map +0 -1
  261. package/dist/src/commands/catalog-context.d.ts +0 -52
  262. package/dist/src/commands/catalog-context.js +0 -156
  263. package/dist/src/commands/catalog-context.js.map +0 -1
  264. package/dist/src/commands/status-v5.js.map +0 -1
  265. package/dist/src/utils/catalog-manifest-v5.d.ts +0 -5
  266. package/dist/src/utils/catalog-manifest-v5.js.map +0 -1
  267. package/dist/src/utils/catalog-source-cache.d.ts +0 -62
  268. package/dist/src/utils/catalog-source-cache.js +0 -321
  269. package/dist/src/utils/catalog-source-cache.js.map +0 -1
  270. package/dist/src/utils/codex-project-surface.d.ts +0 -3
  271. package/dist/src/utils/codex-project-surface.js +0 -49
  272. package/dist/src/utils/codex-project-surface.js.map +0 -1
  273. package/dist/src/utils/plugin-release.d.ts +0 -2
  274. package/dist/src/utils/plugin-release.js +0 -2
  275. package/dist/src/utils/plugin-release.js.map +0 -1
@@ -18,7 +18,7 @@ const CLI_PACKAGE_ROOT = path.basename(authoringRuntimeRoot) === 'dist'
18
18
  : authoringRuntimeRoot;
19
19
  const errors = [];
20
20
  const warnings = [];
21
- const SOURCE_PLUGIN_MANIFEST_CONTRACT_VERSION = 8;
21
+ const SOURCE_PLUGIN_MANIFEST_CONTRACT_VERSION = 9;
22
22
  let capabilityDiagnostic = null;
23
23
  const MAX_SKILL_NAME_LEN = 64;
24
24
  const MAX_SKILL_DESCRIPTION_LEN = 1024;
@@ -150,22 +150,9 @@ const ARTIFACT_PROJECT_MANAGED_KIND_BY_TARGET = {
150
150
  'codex.skill': 'skill',
151
151
  };
152
152
  const PLUGIN_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
153
- const PLUGIN_EVAL_SCHEMA_VERSION = 2;
154
- const PLUGIN_EVAL_CASE_SCHEMA_VERSION = 1;
155
- const PLUGIN_EVAL_SCOPES = new Set(['user', 'project']);
156
- const PLUGIN_EVAL_SPLITS = new Set(['train', 'validation', 'sanity', 'all']);
157
- const PLUGIN_EVAL_TRIGGER_CASE_SPLITS = new Set(['train', 'validation', 'sanity']);
158
- const PLUGIN_EVAL_COMMAND_SUPPORTS = new Set([
159
- 'split',
160
- 'runs',
161
- 'case',
162
- 'model',
163
- 'codexBin',
164
- 'timeout',
165
- 'allowEnvironmentSkips',
166
- ]);
167
153
  let yamlParser = null;
168
154
  let pluginVersionCheckResult = null;
155
+ let skillEvalManifestTools = null;
169
156
  function error(msg) {
170
157
  errors.push(msg);
171
158
  }
@@ -958,769 +945,19 @@ function validateNonEmptyString(value, fieldName, maxLength) {
958
945
  }
959
946
  return trimmed;
960
947
  }
961
- function isPathInsideOrEqual(root, candidate) {
962
- const relative = path.relative(root, candidate);
963
- return relative === '' || (relative !== '..' &&
964
- !relative.startsWith(`..${path.sep}`) &&
965
- !path.isAbsolute(relative));
966
- }
967
- function validateRealPathBoundary(boundary, candidate, fieldName, boundaryName) {
968
- const canonicalBoundary = fs.realpathSync(boundary);
969
- const canonicalCandidate = fs.realpathSync(candidate);
970
- if (!isPathInsideOrEqual(canonicalBoundary, canonicalCandidate)) {
971
- error(`${fieldName} must stay inside the ${boundaryName}`);
972
- return false;
973
- }
974
- return true;
975
- }
976
- function validateEvalRelativeFile(evalDir, value, fieldName) {
977
- const relativePath = validateNonEmptyString(value, fieldName);
978
- if (!relativePath)
979
- return null;
980
- const normalized = stripDotSlash(relativePath).split('\\').join('/');
981
- if (path.isAbsolute(normalized) || normalized.split('/').includes('..')) {
982
- error(`${fieldName} must stay inside the Skill eval directory`);
983
- return null;
984
- }
985
- const absolutePath = path.resolve(evalDir, normalized);
986
- const relativeToRoot = path.relative(evalDir, absolutePath);
987
- if (relativeToRoot.startsWith('..') || path.isAbsolute(relativeToRoot)) {
988
- error(`${fieldName} must stay inside the Skill eval directory`);
989
- return null;
990
- }
991
- if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isFile()) {
992
- error(`${fieldName} does not exist: ${normalized}`);
993
- return null;
994
- }
995
- if (!validateRealPathBoundary(evalDir, absolutePath, fieldName, 'Skill eval directory')) {
996
- return null;
997
- }
998
- return absolutePath;
999
- }
1000
- function validateEvalWorkspacePath(value, fieldName, options = {}) {
1001
- const raw = validateNonEmptyString(value, fieldName);
1002
- if (!raw)
1003
- return null;
1004
- const treePattern = options.allowTreePattern === true && raw.endsWith('/**');
1005
- const candidate = treePattern ? raw.slice(0, -3) : raw;
1006
- if (candidate === '' ||
1007
- candidate.includes('\0') ||
1008
- candidate.includes('\\') ||
1009
- candidate.includes('*') ||
1010
- candidate.includes('?') ||
1011
- candidate.includes('[') ||
1012
- candidate.includes(']') ||
1013
- path.posix.isAbsolute(candidate) ||
1014
- path.win32.isAbsolute(candidate) ||
1015
- path.posix.normalize(candidate) !== candidate ||
1016
- candidate.split('/').includes('..')) {
1017
- error(`${fieldName} must be a normalized POSIX path inside the eval workspace`);
1018
- return null;
1019
- }
1020
- const firstSegment = candidate.split('/')[0];
1021
- if (firstSegment === '.git' || firstSegment === '.agents' || firstSegment === '.codex') {
1022
- error(`${fieldName} must not target an eval control directory`);
1023
- return null;
1024
- }
1025
- return raw;
1026
- }
1027
- function validateEvalFixtureTree(evalRoot, target, fieldName) {
1028
- const relativePath = path.relative(evalRoot, target);
1029
- let currentPath = evalRoot;
1030
- for (const segment of relativePath.split(path.sep).filter(Boolean)) {
1031
- currentPath = path.join(currentPath, segment);
1032
- const stat = fs.lstatSync(currentPath);
1033
- if (stat.isSymbolicLink()) {
1034
- error(`${fieldName} must not contain symbolic links`);
1035
- return false;
1036
- }
1037
- }
1038
- if (!validateRealPathBoundary(evalRoot, target, fieldName, 'Eval fixture root')) {
1039
- return false;
1040
- }
1041
- const stat = fs.lstatSync(target);
1042
- if (stat.isFile())
1043
- return true;
1044
- if (!stat.isDirectory()) {
1045
- error(`${fieldName} must reference a regular file or directory fixture`);
1046
- return false;
1047
- }
1048
- return fs.readdirSync(target).every((entry) => validateEvalFixtureTree(evalRoot, path.join(target, entry), fieldName));
1049
- }
1050
- function evalWorkspaceTargetsOverlap(left, right) {
1051
- return left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
1052
- }
1053
- function validateConversationEnvironment(value, fieldName, fixtureRoot, occupiedTargets = new Set()) {
1054
- if (value === undefined)
1055
- return [];
1056
- const environment = expectRecord(value, fieldName);
1057
- if (!environment)
1058
- return [];
1059
- validateAllowedKeys(environment, fieldName, ['pathPrepend']);
1060
- if (!Array.isArray(environment.pathPrepend) || environment.pathPrepend.length === 0) {
1061
- error(`${fieldName}.pathPrepend must be a non-empty array`);
1062
- return [];
1063
- }
1064
- const targets = environment.pathPrepend
1065
- .map((entry, index) => ({
1066
- index,
1067
- relativePath: validateEvalWorkspacePath(entry, `${fieldName}.pathPrepend[${index}]`, {
1068
- allowTreePattern: false,
1069
- }),
1070
- }))
1071
- .filter((entry) => entry.relativePath !== null);
1072
- if (new Set(targets.map((entry) => entry.relativePath)).size !== targets.length) {
1073
- error(`${fieldName}.pathPrepend must not contain duplicates`);
1074
- }
1075
- targets.forEach(({ index, relativePath }) => {
1076
- const target = path.resolve(fixtureRoot, relativePath);
1077
- if (!fs.existsSync(target)) {
1078
- error(`${fieldName}.pathPrepend[${index}] must reference an existing fixture`);
1079
- return;
1080
- }
1081
- validateEvalFixtureTree(fixtureRoot, target, `${fieldName}.pathPrepend[${index}]`);
1082
- if ([...occupiedTargets].some((occupiedTarget) => evalWorkspaceTargetsOverlap(relativePath, occupiedTarget))) {
1083
- error(`${fieldName}.pathPrepend[${index}] conflicts with another PATH fixture target`);
1084
- return;
1085
- }
1086
- occupiedTargets.add(relativePath);
1087
- });
1088
- return targets.map((entry) => entry.relativePath);
1089
- }
1090
- function evalWorkspacePathMatchesPattern(relativePath, pattern) {
1091
- if (pattern.endsWith('/**')) {
1092
- const prefix = pattern.slice(0, -3);
1093
- return relativePath === prefix || relativePath.startsWith(`${prefix}/`);
1094
- }
1095
- return relativePath === pattern;
1096
- }
1097
- function validateConversationCaseCorpus(evalDir, suite, fieldName, suitePathPrepend = []) {
1098
- const casesPath = validateEvalRelativeFile(evalDir, suite.cases, `${fieldName}.cases`);
1099
- if (!casesPath)
1100
- return;
1101
- let document;
1102
- try {
1103
- document = JSON.parse(fs.readFileSync(casesPath, 'utf8'));
1104
- }
1105
- catch (e) {
1106
- error(`${fieldName}.cases is not valid JSON: ${e.message}`);
1107
- return;
1108
- }
1109
- const corpus = expectRecord(document, `${fieldName}.cases document`);
1110
- if (!corpus)
1111
- return;
1112
- const skillName = validateNonEmptyString(corpus.skill_name, `${fieldName}.cases.skill_name`);
1113
- if (skillName && skillName !== suite.skill) {
1114
- error(`${fieldName}.cases.skill_name must match ${JSON.stringify(suite.skill)}`);
1115
- }
1116
- if (!Array.isArray(corpus.evals) || corpus.evals.length === 0) {
1117
- error(`${fieldName}.cases.evals must be a non-empty array`);
1118
- return;
1119
- }
1120
- const ids = new Set();
1121
- corpus.evals.forEach((value, caseIndex) => {
1122
- const caseField = `${fieldName}.cases.evals[${caseIndex}]`;
1123
- const entry = expectRecord(value, caseField);
1124
- if (!entry)
1125
- return;
1126
- const id = validateNonEmptyString(entry.id, `${caseField}.id`);
1127
- if (id && ids.has(id)) {
1128
- error(`${fieldName}.cases has duplicate case id ${JSON.stringify(id)}`);
1129
- }
1130
- else if (id) {
1131
- ids.add(id);
1132
- }
1133
- validateNonEmptyString(entry.prompt, `${caseField}.prompt`);
1134
- const sandbox = entry.sandbox === undefined
1135
- ? 'read-only'
1136
- : validateNonEmptyString(entry.sandbox, `${caseField}.sandbox`);
1137
- if (sandbox && sandbox !== 'read-only' && sandbox !== 'workspace-write') {
1138
- error(`${caseField}.sandbox must be "read-only" or "workspace-write"`);
1139
- }
1140
- if (!Array.isArray(entry.scripted_replies)) {
1141
- error(`${caseField}.scripted_replies must be an array`);
1142
- }
1143
- else {
1144
- entry.scripted_replies.forEach((reply, replyIndex) => {
1145
- const replyField = `${caseField}.scripted_replies[${replyIndex}]`;
1146
- if (typeof reply === 'string')
1147
- return;
1148
- const replyEntry = expectRecord(reply, replyField);
1149
- if (!replyEntry)
1150
- return;
1151
- if (!Array.isArray(replyEntry.answers_by_index) || replyEntry.answers_by_index.some((answer) => typeof answer !== 'string')) {
1152
- error(`${replyField}.answers_by_index must be an array of strings`);
1153
- }
1154
- if (replyEntry.expected_question_count !== undefined &&
1155
- (!Number.isInteger(replyEntry.expected_question_count) ||
1156
- replyEntry.expected_question_count < 0)) {
1157
- error(`${replyField}.expected_question_count must be a non-negative integer`);
1158
- }
1159
- validateNonEmptyString(replyEntry.unexpected_question_answer, `${replyField}.unexpected_question_answer`);
1160
- });
1161
- }
1162
- validateNonEmptyString(entry.expected_output, `${caseField}.expected_output`);
1163
- validateConversationEnvironment(entry.environment, `${caseField}.environment`, path.dirname(casesPath), new Set(suitePathPrepend));
1164
- if (entry.workspace_files !== undefined) {
1165
- const workspaceFiles = expectRecord(entry.workspace_files, `${caseField}.workspace_files`);
1166
- if (workspaceFiles) {
1167
- const workspaceFileEntries = Object.entries(workspaceFiles);
1168
- if (workspaceFileEntries.length === 0) {
1169
- error(`${caseField}.workspace_files must contain at least one file`);
1170
- }
1171
- workspaceFileEntries.forEach(([relativePath, content]) => {
1172
- validateEvalWorkspacePath(relativePath, `${caseField}.workspace_files.${relativePath}`, { allowTreePattern: false });
1173
- validateNonEmptyString(content, `${caseField}.workspace_files.${relativePath}`);
1174
- });
1175
- }
1176
- }
1177
- const review = expectRecord(entry.review, `${caseField}.review`);
1178
- if (review) {
1179
- if (!Array.isArray(review.checks) || review.checks.length === 0 || review.checks.some((check) => typeof check !== 'string' || check.trim() === '')) {
1180
- error(`${caseField}.review.checks must be a non-empty array of strings`);
1181
- }
1182
- if (review.workspace_unchanged !== undefined &&
1183
- typeof review.workspace_unchanged !== 'boolean') {
1184
- error(`${caseField}.review.workspace_unchanged must be boolean`);
1185
- }
1186
- const allowedWorkspaceChanges = [];
1187
- if (review.allowed_workspace_changes !== undefined) {
1188
- if (!Array.isArray(review.allowed_workspace_changes) ||
1189
- review.allowed_workspace_changes.length === 0) {
1190
- error(`${caseField}.review.allowed_workspace_changes must be a non-empty array`);
1191
- }
1192
- else {
1193
- review.allowed_workspace_changes.forEach((pathValue, pathIndex) => {
1194
- const normalized = validateEvalWorkspacePath(pathValue, `${caseField}.review.allowed_workspace_changes[${pathIndex}]`, { allowTreePattern: true });
1195
- if (normalized)
1196
- allowedWorkspaceChanges.push(normalized);
1197
- });
1198
- }
1199
- }
1200
- const artifactPaths = [];
1201
- if (review.artifacts !== undefined) {
1202
- if (!Array.isArray(review.artifacts) || review.artifacts.length === 0) {
1203
- error(`${caseField}.review.artifacts must be a non-empty array`);
1204
- }
1205
- else {
1206
- review.artifacts.forEach((artifactValue, artifactIndex) => {
1207
- const artifactField = `${caseField}.review.artifacts[${artifactIndex}]`;
1208
- const artifact = expectRecord(artifactValue, artifactField);
1209
- if (!artifact)
1210
- return;
1211
- validateAllowedKeys(artifact, artifactField, ['path', 'exists', 'contains', 'not_contains', 'json']);
1212
- const artifactPath = validateEvalWorkspacePath(artifact.path, `${artifactField}.path`);
1213
- if (artifactPath)
1214
- artifactPaths.push(artifactPath);
1215
- if (artifact.exists !== undefined && typeof artifact.exists !== 'boolean') {
1216
- error(`${artifactField}.exists must be boolean`);
1217
- }
1218
- for (const key of ['contains', 'not_contains']) {
1219
- if (artifact[key] !== undefined &&
1220
- (!Array.isArray(artifact[key]) || artifact[key].length === 0 || artifact[key].some((fragment) => typeof fragment !== 'string' || fragment.length === 0))) {
1221
- error(`${artifactField}.${key} must be a non-empty array of strings`);
1222
- }
1223
- }
1224
- if (artifact.json !== undefined && !isRecord(artifact.json)) {
1225
- error(`${artifactField}.json must be an object`);
1226
- }
1227
- });
1228
- }
1229
- }
1230
- if (new Set(artifactPaths).size !== artifactPaths.length) {
1231
- error(`${caseField}.review.artifacts contains duplicate paths`);
1232
- }
1233
- if (sandbox === 'workspace-write') {
1234
- if (review.workspace_unchanged !== false) {
1235
- error(`${caseField} workspace-write requires review.workspace_unchanged=false`);
1236
- }
1237
- if (allowedWorkspaceChanges.length === 0 || artifactPaths.length === 0) {
1238
- error(`${caseField} workspace-write requires allowed changes and artifacts`);
1239
- }
1240
- const unmatchedArtifact = artifactPaths.find((artifactPath) => !allowedWorkspaceChanges.some((pattern) => evalWorkspacePathMatchesPattern(artifactPath, pattern)));
1241
- if (unmatchedArtifact) {
1242
- error(`${caseField} artifact ${JSON.stringify(unmatchedArtifact)} is outside allowed changes`);
1243
- }
1244
- }
1245
- }
1246
- });
1247
- }
1248
- function validateTriggerCaseCorpus(evalDir, suite, fieldName) {
1249
- const casesPath = validateEvalRelativeFile(evalDir, suite.cases, `${fieldName}.cases`);
1250
- if (!casesPath)
1251
- return;
1252
- let document;
1253
- try {
1254
- document = JSON.parse(fs.readFileSync(casesPath, 'utf8'));
1255
- }
1256
- catch (e) {
1257
- error(`${fieldName}.cases is not valid JSON: ${e.message}`);
1258
- return;
1259
- }
1260
- const corpus = expectRecord(document, `${fieldName}.cases document`);
1261
- if (!corpus)
1262
- return;
1263
- if (corpus.schemaVersion !== PLUGIN_EVAL_CASE_SCHEMA_VERSION) {
1264
- error(`${fieldName}.cases.schemaVersion must be ${PLUGIN_EVAL_CASE_SCHEMA_VERSION}`);
1265
- }
1266
- if (corpus.skill !== suite.skill) {
1267
- error(`${fieldName}.cases.skill must match ${JSON.stringify(suite.skill)}`);
1268
- }
1269
- if (!Array.isArray(corpus.cases) || corpus.cases.length === 0) {
1270
- error(`${fieldName}.cases.cases must be a non-empty array`);
1271
- return;
1272
- }
1273
- const ids = new Set();
1274
- const seenSplits = new Set();
1275
- let hasWorkspaceFixture = false;
1276
- corpus.cases.forEach((value, caseIndex) => {
1277
- const caseField = `${fieldName}.cases.cases[${caseIndex}]`;
1278
- const entry = expectRecord(value, caseField);
1279
- if (!entry)
1280
- return;
1281
- const id = validateNonEmptyString(entry.id, `${caseField}.id`);
1282
- if (id && !/^[a-z0-9][a-z0-9_]*$/.test(id)) {
1283
- error(`${caseField}.id must use lowercase snake_case`);
1284
- }
1285
- else if (id && ids.has(id)) {
1286
- error(`${fieldName}.cases has duplicate case id ${JSON.stringify(id)}`);
1287
- }
1288
- else if (id) {
1289
- ids.add(id);
1290
- }
1291
- if (!PLUGIN_EVAL_TRIGGER_CASE_SPLITS.has(entry.split)) {
1292
- error(`${caseField}.split must be train, validation, or sanity`);
1293
- }
1294
- else {
1295
- seenSplits.add(entry.split);
1296
- }
1297
- validateNonEmptyString(entry.query, `${caseField}.query`);
1298
- const usesLegacyExpectation = entry.should_load_skill !== undefined;
1299
- const usesRouteExpectation = entry.expected_loaded_skills !== undefined;
1300
- if (usesLegacyExpectation === usesRouteExpectation) {
1301
- error(`${caseField} must declare exactly one of should_load_skill or expected_loaded_skills`);
1302
- }
1303
- else if (usesLegacyExpectation && typeof entry.should_load_skill !== 'boolean') {
1304
- error(`${caseField}.should_load_skill must be boolean`);
1305
- }
1306
- else if (usesRouteExpectation) {
1307
- if (!Array.isArray(entry.expected_loaded_skills)) {
1308
- error(`${caseField}.expected_loaded_skills must be an array`);
1309
- }
1310
- else {
1311
- const expectedSkills = entry.expected_loaded_skills;
1312
- expectedSkills.forEach((skillName, skillIndex) => {
1313
- if (typeof skillName !== 'string' || skillName.trim() === '') {
1314
- error(`${caseField}.expected_loaded_skills[${skillIndex}] must be a non-empty string`);
1315
- }
1316
- else if (!suite.skills.includes(skillName)) {
1317
- error(`${caseField}.expected_loaded_skills[${skillIndex}] must name a participant Skill`);
1318
- }
1319
- });
1320
- if (new Set(expectedSkills).size !== expectedSkills.length) {
1321
- error(`${caseField}.expected_loaded_skills must not contain duplicates`);
1322
- }
1323
- if (suite.forbidMultipleSkillLoads === true && expectedSkills.length > 1) {
1324
- error(`${caseField} expects multiple Skills but the suite forbids multiple Skill loads`);
1325
- }
1326
- }
1327
- }
1328
- if (suite.observeSpawnAgent === true) {
1329
- if (typeof entry.should_spawn_agent !== 'boolean') {
1330
- error(`${caseField}.should_spawn_agent is required when observeSpawnAgent=true`);
1331
- }
1332
- }
1333
- else if (entry.should_spawn_agent !== undefined) {
1334
- error(`${caseField}.should_spawn_agent requires observeSpawnAgent=true`);
1335
- }
1336
- if (entry.workspace_fixture !== undefined) {
1337
- hasWorkspaceFixture = true;
1338
- validateNonEmptyString(entry.workspace_fixture, `${caseField}.workspace_fixture`);
1339
- }
1340
- if (entry.use_project_rules !== undefined &&
1341
- typeof entry.use_project_rules !== 'boolean') {
1342
- error(`${caseField}.use_project_rules must be boolean`);
1343
- }
1344
- if (entry.tags !== undefined) {
1345
- if (!Array.isArray(entry.tags) || entry.tags.length === 0) {
1346
- error(`${caseField}.tags must be a non-empty array`);
1347
- }
1348
- else {
1349
- entry.tags.forEach((tag, tagIndex) => {
1350
- validateNonEmptyString(tag, `${caseField}.tags[${tagIndex}]`);
1351
- });
1352
- }
1353
- }
1354
- });
1355
- if (hasWorkspaceFixture && suite.fixtureModule === undefined) {
1356
- error(`${fieldName}.fixtureModule is required when cases declare workspace_fixture`);
1357
- }
1358
- if (Array.isArray(suite.releaseSplits)) {
1359
- suite.releaseSplits.forEach((split) => {
1360
- if (split !== 'all' && !seenSplits.has(split)) {
1361
- error(`${fieldName}.cases has no cases for release split ${JSON.stringify(split)}`);
1362
- }
1363
- });
1364
- }
1365
- }
1366
- function resolveEvalProfilePlugin(pluginDir, ownerManifest, pluginName, fieldName) {
1367
- if (pluginName === ownerManifest.name) {
1368
- return { pluginDir, manifest: ownerManifest };
1369
- }
1370
- const companionDir = path.join(path.dirname(pluginDir), pluginName);
1371
- const manifestPath = path.join(companionDir, '.agents-plugin', 'plugin.json');
1372
- if (!fs.existsSync(manifestPath)) {
1373
- error(`${fieldName} must reference repo-local plugin "${pluginName}"`);
1374
- return null;
1375
- }
1376
- let manifest;
948
+ async function loadSkillEvalManifestTools() {
949
+ if (skillEvalManifestTools)
950
+ return skillEvalManifestTools;
951
+ const modulePath = path.join(CLI_PACKAGE_ROOT, 'dist', 'src', 'eval', 'manifest-v1.js');
1377
952
  try {
1378
- manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
1379
- }
1380
- catch (e) {
1381
- error(`${fieldName} companion manifest is not valid JSON: ${e.message}`);
1382
- return null;
953
+ skillEvalManifestTools = await import(pathToFileURL(modulePath).href);
954
+ return skillEvalManifestTools;
1383
955
  }
1384
- if (!isRecord(manifest) || manifest.name !== pluginName) {
1385
- error(`${fieldName} companion source must declare plugin name "${pluginName}"`);
956
+ catch {
1386
957
  return null;
1387
958
  }
1388
- return { pluginDir: companionDir, manifest };
1389
959
  }
1390
- function validateOnePluginEvalManifest(pluginDir, pluginManifest, evalDir, expectedSkill) {
1391
- const evalManifestPath = path.join(evalDir, 'manifest.json');
1392
- const manifestField = path.relative(pluginDir, evalManifestPath).split(path.sep).join('/');
1393
- let document;
1394
- try {
1395
- document = JSON.parse(fs.readFileSync(evalManifestPath, 'utf8'));
1396
- }
1397
- catch (e) {
1398
- error(`${manifestField} is not valid JSON: ${e.message}`);
1399
- return;
1400
- }
1401
- const manifest = expectRecord(document, manifestField);
1402
- if (!manifest)
1403
- return;
1404
- validateAllowedKeys(manifest, manifestField, ['schemaVersion', 'plugin', 'skill', 'profiles', 'suites']);
1405
- if (manifest.schemaVersion !== PLUGIN_EVAL_SCHEMA_VERSION) {
1406
- error(`${manifestField}.schemaVersion must be ${PLUGIN_EVAL_SCHEMA_VERSION}`);
1407
- }
1408
- if (manifest.plugin !== pluginManifest.name) {
1409
- error(`${manifestField}.plugin must match plugin name ${JSON.stringify(pluginManifest.name)}`);
1410
- }
1411
- const skill = validateNonEmptyString(manifest.skill, `${manifestField}.skill`);
1412
- if (skill && skill !== expectedSkill) {
1413
- error(`${manifestField}.skill must match owning Skill ${JSON.stringify(expectedSkill)}`);
1414
- }
1415
- if (skill && !collectDeclaredSkillNames(pluginDir, pluginManifest).all.has(skill)) {
1416
- error(`${manifestField}.skill must reference a declared static or dynamic skill`);
1417
- }
1418
- if (!Array.isArray(manifest.profiles) || manifest.profiles.length === 0) {
1419
- error(`${manifestField}.profiles must be a non-empty array`);
1420
- return;
1421
- }
1422
- const profilesById = new Map();
1423
- manifest.profiles.forEach((value, index) => {
1424
- const fieldName = `${manifestField}.profiles[${index}]`;
1425
- const profile = expectRecord(value, fieldName);
1426
- if (!profile)
1427
- return;
1428
- validateAllowedKeys(profile, fieldName, ['id', 'scope', 'plugins', 'runModel', 'requiredForRelease']);
1429
- const id = validateNonEmptyString(profile.id, `${fieldName}.id`);
1430
- if (id && !PLUGIN_NAME_PATTERN.test(id)) {
1431
- error(`${fieldName}.id must use lowercase kebab-case`);
1432
- }
1433
- else if (id && profilesById.has(id)) {
1434
- error(`${manifestField} has duplicate profile id ${JSON.stringify(id)}`);
1435
- }
1436
- if (!PLUGIN_EVAL_SCOPES.has(profile.scope)) {
1437
- error(`${fieldName}.scope must be user or project`);
1438
- }
1439
- const skillOwners = new Map();
1440
- const installedPlugins = new Set();
1441
- if (!Array.isArray(profile.plugins) || profile.plugins.length === 0) {
1442
- error(`${fieldName}.plugins must be a non-empty array`);
1443
- }
1444
- else {
1445
- profile.plugins.forEach((value, pluginIndex) => {
1446
- const pluginField = `${fieldName}.plugins[${pluginIndex}]`;
1447
- const installation = expectRecord(value, pluginField);
1448
- if (!installation)
1449
- return;
1450
- validateAllowedKeys(installation, pluginField, ['name', 'config']);
1451
- const pluginName = validateNonEmptyString(installation.name, `${pluginField}.name`);
1452
- if (!pluginName)
1453
- return;
1454
- if (!PLUGIN_NAME_PATTERN.test(pluginName)) {
1455
- error(`${pluginField}.name must use lowercase kebab-case`);
1456
- }
1457
- if (installedPlugins.has(pluginName)) {
1458
- error(`${fieldName}.plugins has duplicate plugin ${JSON.stringify(pluginName)}`);
1459
- return;
1460
- }
1461
- installedPlugins.add(pluginName);
1462
- if (installation.config !== undefined && !isRecord(installation.config)) {
1463
- error(`${pluginField}.config must be an object`);
1464
- }
1465
- const source = resolveEvalProfilePlugin(pluginDir, pluginManifest, pluginName, `${pluginField}.name`);
1466
- if (!source)
1467
- return;
1468
- for (const skillName of collectDeclaredSkillNames(source.pluginDir, source.manifest).all) {
1469
- const existing = skillOwners.get(skillName);
1470
- if (existing && existing !== pluginName) {
1471
- error(`${fieldName} installs duplicate Skill key ${JSON.stringify(skillName)} ` +
1472
- `from plugins ${JSON.stringify(existing)} and ${JSON.stringify(pluginName)}`);
1473
- }
1474
- else {
1475
- skillOwners.set(skillName, pluginName);
1476
- }
1477
- }
1478
- });
1479
- }
1480
- if (!installedPlugins.has(pluginManifest.name)) {
1481
- error(`${fieldName}.plugins must include owner plugin ${JSON.stringify(pluginManifest.name)}`);
1482
- }
1483
- if (typeof profile.runModel !== 'boolean') {
1484
- error(`${fieldName}.runModel must be boolean`);
1485
- }
1486
- if (typeof profile.requiredForRelease !== 'boolean') {
1487
- error(`${fieldName}.requiredForRelease must be boolean`);
1488
- }
1489
- if (profile.scope === 'user' && profile.runModel === true) {
1490
- error(`${fieldName}: eval schema v2 does not allow real model execution for user scope`);
1491
- }
1492
- if (id && PLUGIN_NAME_PATTERN.test(id) && !profilesById.has(id)) {
1493
- profilesById.set(id, { ...profile, skillOwners });
1494
- }
1495
- });
1496
- if (!manifest.profiles.some((profile) => isRecord(profile) && profile.scope === 'project' && profile.runModel === true)) {
1497
- error(`${manifestField} must define at least one project profile with runModel=true`);
1498
- }
1499
- if (!Array.isArray(manifest.suites) || manifest.suites.length === 0) {
1500
- error(`${manifestField}.suites must be a non-empty array`);
1501
- return;
1502
- }
1503
- const suiteIds = new Set();
1504
- manifest.suites.forEach((value, index) => {
1505
- const fieldName = `${manifestField}.suites[${index}]`;
1506
- const suite = expectRecord(value, fieldName);
1507
- if (!suite)
1508
- return;
1509
- const id = validateNonEmptyString(suite.id, `${fieldName}.id`);
1510
- if (id && !PLUGIN_NAME_PATTERN.test(id)) {
1511
- error(`${fieldName}.id must use lowercase kebab-case`);
1512
- }
1513
- else if (id && suiteIds.has(id)) {
1514
- error(`${manifestField} has duplicate suite id ${JSON.stringify(id)}`);
1515
- }
1516
- else if (id) {
1517
- suiteIds.add(id);
1518
- }
1519
- if (suite.kind !== 'command' && suite.kind !== 'conversation' && suite.kind !== 'trigger') {
1520
- error(`${fieldName}.kind must be command, conversation, or trigger`);
1521
- }
1522
- const suiteSkills = new Set();
1523
- if (!Array.isArray(suite.skills) || suite.skills.length === 0) {
1524
- error(`${fieldName}.skills must be a non-empty array`);
1525
- }
1526
- else {
1527
- suite.skills.forEach((skillName, skillIndex) => {
1528
- const skillField = `${fieldName}.skills[${skillIndex}]`;
1529
- const normalized = validateNonEmptyString(skillName, skillField);
1530
- if (!normalized)
1531
- return;
1532
- if (!PLUGIN_NAME_PATTERN.test(normalized)) {
1533
- error(`${skillField} must use lowercase kebab-case`);
1534
- }
1535
- else if (suiteSkills.has(normalized)) {
1536
- error(`${fieldName}.skills has duplicate Skill ${JSON.stringify(normalized)}`);
1537
- }
1538
- else {
1539
- suiteSkills.add(normalized);
1540
- }
1541
- });
1542
- }
1543
- if (skill && !suiteSkills.has(skill)) {
1544
- error(`${fieldName}.skills must include owner Skill ${JSON.stringify(skill)}`);
1545
- }
1546
- if (!Array.isArray(suite.profiles) || suite.profiles.length === 0) {
1547
- error(`${fieldName}.profiles must be a non-empty array`);
1548
- }
1549
- else {
1550
- suite.profiles.forEach((profileId, profileIndex) => {
1551
- const profileField = `${fieldName}.profiles[${profileIndex}]`;
1552
- if (typeof profileId !== 'string' || profileId.trim() === '') {
1553
- error(`${profileField} must be a non-empty string`);
1554
- return;
1555
- }
1556
- const profile = profilesById.get(profileId);
1557
- if (!profile) {
1558
- error(`${fieldName} references unknown profile ${JSON.stringify(profileId)}`);
1559
- }
1560
- else if (profile.runModel !== true) {
1561
- error(`${fieldName} cannot execute materialize-only profile ${JSON.stringify(profileId)}`);
1562
- }
1563
- if (profile) {
1564
- for (const skillName of suiteSkills) {
1565
- if (!profile.skillOwners.has(skillName)) {
1566
- error(`${fieldName} requires Skill ${JSON.stringify(skillName)}, but profile ` +
1567
- `${JSON.stringify(profileId)} does not install a plugin that declares it`);
1568
- }
1569
- }
1570
- }
1571
- });
1572
- }
1573
- if (!Number.isInteger(suite.defaultRuns) || suite.defaultRuns <= 0) {
1574
- error(`${fieldName}.defaultRuns must be a positive integer`);
1575
- }
1576
- if (!Number.isInteger(suite.perRunTimeoutMs) || suite.perRunTimeoutMs <= 0) {
1577
- error(`${fieldName}.perRunTimeoutMs must be a positive integer`);
1578
- }
1579
- if (typeof suite.requiredForRelease !== 'boolean') {
1580
- error(`${fieldName}.requiredForRelease must be boolean`);
1581
- }
1582
- if (!Array.isArray(suite.releaseSplits)) {
1583
- error(`${fieldName}.releaseSplits must be an array`);
1584
- }
1585
- else {
1586
- suite.releaseSplits.forEach((split, splitIndex) => {
1587
- if (!PLUGIN_EVAL_SPLITS.has(split)) {
1588
- error(`${fieldName}.releaseSplits[${splitIndex}] is unsupported`);
1589
- }
1590
- });
1591
- }
1592
- if (suite.kind === 'command') {
1593
- validateAllowedKeys(suite, fieldName, [
1594
- 'id', 'kind', 'skills', 'entry', 'cases', 'profiles', 'defaultRuns', 'releaseSplits',
1595
- 'requiredForRelease', 'perRunTimeoutMs', 'suiteTimeoutMs', 'supports',
1596
- ]);
1597
- validateEvalRelativeFile(evalDir, suite.entry, `${fieldName}.entry`);
1598
- if (!Number.isInteger(suite.suiteTimeoutMs) || suite.suiteTimeoutMs <= 0) {
1599
- error(`${fieldName}.suiteTimeoutMs must be a positive integer`);
1600
- }
1601
- else if (suite.suiteTimeoutMs < suite.perRunTimeoutMs) {
1602
- error(`${fieldName}.suiteTimeoutMs must be greater than or equal to perRunTimeoutMs`);
1603
- }
1604
- if (!Array.isArray(suite.supports)) {
1605
- error(`${fieldName}.supports must be an array`);
1606
- }
1607
- else {
1608
- suite.supports.forEach((support, supportIndex) => {
1609
- if (!PLUGIN_EVAL_COMMAND_SUPPORTS.has(support)) {
1610
- error(`${fieldName}.supports[${supportIndex}] is unsupported`);
1611
- }
1612
- });
1613
- if (Array.isArray(suite.releaseSplits) &&
1614
- suite.releaseSplits.length > 0 &&
1615
- !suite.supports.includes('split')) {
1616
- error(`${fieldName} declares releaseSplits but does not support split`);
1617
- }
1618
- const supportsCase = suite.supports.includes('case');
1619
- if (supportsCase && suite.cases === undefined) {
1620
- error(`${fieldName}.cases is required when supports includes case`);
1621
- }
1622
- else if (!supportsCase && suite.cases !== undefined) {
1623
- error(`${fieldName}.cases requires supports to include case`);
1624
- }
1625
- if (supportsCase && suite.cases !== undefined) {
1626
- const casesPath = validateEvalRelativeFile(evalDir, suite.cases, `${fieldName}.cases`);
1627
- if (casesPath) {
1628
- try {
1629
- const casesDocument = JSON.parse(fs.readFileSync(casesPath, 'utf8'));
1630
- if (!isRecord(casesDocument) || casesDocument.skill_name !== skill) {
1631
- error(`${fieldName}.cases.skill_name must match ${JSON.stringify(skill)}`);
1632
- }
1633
- else if (!Array.isArray(casesDocument.evals) || casesDocument.evals.length === 0) {
1634
- error(`${fieldName}.cases.evals must be a non-empty array`);
1635
- }
1636
- else {
1637
- const caseIds = new Set();
1638
- casesDocument.evals.forEach((entry, caseIndex) => {
1639
- const caseField = `${fieldName}.cases.evals[${caseIndex}]`;
1640
- if (!isRecord(entry) || typeof entry.id !== 'string' || !/^[a-z0-9_]+$/.test(entry.id)) {
1641
- error(`${caseField}.id must use lowercase snake_case`);
1642
- }
1643
- else if (caseIds.has(entry.id)) {
1644
- error(`${fieldName}.cases has duplicate case id ${JSON.stringify(entry.id)}`);
1645
- }
1646
- else {
1647
- caseIds.add(entry.id);
1648
- }
1649
- });
1650
- }
1651
- }
1652
- catch (cause) {
1653
- error(`${fieldName}.cases is not valid JSON: ${cause.message}`);
1654
- }
1655
- }
1656
- }
1657
- }
1658
- }
1659
- else if (suite.kind === 'conversation' || suite.kind === 'trigger') {
1660
- const normalizedSuite = { ...suite, skill };
1661
- const isolation = suite.isolation ?? 'standard';
1662
- if (isolation !== 'standard' && isolation !== 'strict') {
1663
- error(`${fieldName}.isolation must be standard or strict`);
1664
- }
1665
- if (suite.kind === 'conversation') {
1666
- validateAllowedKeys(suite, fieldName, [
1667
- 'id', 'kind', 'skills', 'cases', 'profiles', 'defaultRuns', 'releaseSplits',
1668
- 'requiredForRelease', 'perRunTimeoutMs', 'isolation', 'environment',
1669
- ]);
1670
- if (Array.isArray(suite.releaseSplits) && suite.releaseSplits.length > 0) {
1671
- error(`${fieldName} cannot declare releaseSplits because conversation cases do not support split filtering`);
1672
- }
1673
- const suitePathPrepend = validateConversationEnvironment(suite.environment, `${fieldName}.environment`, evalDir);
1674
- validateConversationCaseCorpus(evalDir, normalizedSuite, fieldName, suitePathPrepend);
1675
- }
1676
- else {
1677
- validateAllowedKeys(suite, fieldName, [
1678
- 'id', 'kind', 'skills', 'cases', 'fixtureModule', 'observeSpawnAgent', 'isolation',
1679
- 'strictNegativeSplits', 'minimumPassRate', 'forbidMultipleSkillLoads',
1680
- 'profiles', 'defaultRuns', 'releaseSplits',
1681
- 'requiredForRelease', 'perRunTimeoutMs',
1682
- ]);
1683
- if (typeof suite.observeSpawnAgent !== 'boolean') {
1684
- error(`${fieldName}.observeSpawnAgent must be boolean`);
1685
- }
1686
- if (!Array.isArray(suite.strictNegativeSplits)) {
1687
- error(`${fieldName}.strictNegativeSplits must be an array`);
1688
- }
1689
- else {
1690
- suite.strictNegativeSplits.forEach((split, splitIndex) => {
1691
- if (!PLUGIN_EVAL_TRIGGER_CASE_SPLITS.has(split)) {
1692
- error(`${fieldName}.strictNegativeSplits[${splitIndex}] is unsupported`);
1693
- }
1694
- });
1695
- }
1696
- if (suite.minimumPassRate !== undefined &&
1697
- (typeof suite.minimumPassRate !== 'number' ||
1698
- !Number.isFinite(suite.minimumPassRate) ||
1699
- suite.minimumPassRate <= 0 ||
1700
- suite.minimumPassRate > 1)) {
1701
- error(`${fieldName}.minimumPassRate must be greater than 0 and at most 1`);
1702
- }
1703
- if (suite.forbidMultipleSkillLoads !== undefined &&
1704
- typeof suite.forbidMultipleSkillLoads !== 'boolean') {
1705
- error(`${fieldName}.forbidMultipleSkillLoads must be boolean`);
1706
- }
1707
- if (suite.fixtureModule !== undefined) {
1708
- const fixturePath = validateEvalRelativeFile(evalDir, suite.fixtureModule, `${fieldName}.fixtureModule`);
1709
- if (fixturePath && path.extname(fixturePath) !== '.mjs') {
1710
- error(`${fieldName}.fixtureModule must reference an .mjs file`);
1711
- }
1712
- }
1713
- validateTriggerCaseCorpus(evalDir, normalizedSuite, fieldName);
1714
- }
1715
- }
1716
- });
1717
- if (skill
1718
- && collectDeclaredSkillNames(pluginDir, pluginManifest).dynamic.has(skill)
1719
- && !manifest.suites.some((suite) => isRecord(suite) && suite.requiredForRelease === true)) {
1720
- error(`${manifestField}.suites must include at least one release-required suite for dynamic Skill ${JSON.stringify(skill)}`);
1721
- }
1722
- }
1723
- function validatePluginEvalManifest(pluginDir, pluginManifest) {
960
+ async function validatePluginEvalManifest(pluginDir, pluginManifest) {
1724
961
  const legacyRoot = path.join(pluginDir, 'evals');
1725
962
  if (fs.existsSync(legacyRoot)) {
1726
963
  error('Plugin-root evals/ is legacy; put evals/ inside its owning static or dynamic Skill directory.');
@@ -1729,17 +966,33 @@ function validatePluginEvalManifest(pluginDir, pluginManifest) {
1729
966
  .sort()
1730
967
  .map((skill) => ({ skill, root: path.join(pluginDir, 'skills', skill) }));
1731
968
  const seenSkills = new Set();
969
+ let manifestTools = null;
1732
970
  for (const owner of skillRoots) {
1733
971
  const evalDir = path.join(owner.root, 'evals');
1734
972
  const manifestPath = path.join(evalDir, 'manifest.json');
1735
973
  if (!fs.existsSync(manifestPath))
1736
974
  continue;
975
+ const manifestField = path.relative(pluginDir, manifestPath).split(path.sep).join('/');
1737
976
  if (seenSkills.has(owner.skill)) {
1738
977
  error(`Skill ${JSON.stringify(owner.skill)} has more than one evals/manifest.json owner`);
1739
978
  continue;
1740
979
  }
1741
980
  seenSkills.add(owner.skill);
1742
- validateOnePluginEvalManifest(pluginDir, pluginManifest, evalDir, owner.skill);
981
+ manifestTools ??= await loadSkillEvalManifestTools();
982
+ if (typeof manifestTools?.validateAwSkillEvalManifest !== 'function') {
983
+ error('Canonical AwSkillEvalManifest v1 validator is unavailable; run the current built aw CLI.');
984
+ continue;
985
+ }
986
+ const validation = await manifestTools.validateAwSkillEvalManifest({
987
+ plugin: {
988
+ rootDir: pluginDir,
989
+ manifestPath: path.join(pluginDir, '.agents-plugin', 'plugin.json'),
990
+ manifest: pluginManifest,
991
+ },
992
+ skill: owner.skill,
993
+ });
994
+ for (const message of validation.errors)
995
+ error(`${manifestField}: ${message}`);
1743
996
  }
1744
997
  }
1745
998
  function countCharacters(value) {
@@ -4252,7 +3505,6 @@ async function validatePluginDir(pluginDir, options) {
4252
3505
  manifest: normalizedManifest,
4253
3506
  implementedCapabilities: cliManifestTools.implementedCapabilities,
4254
3507
  });
4255
- const blockingObservations = requirements.observations.filter((observation) => observation.blocking);
4256
3508
  if (requirements.unsupportedByCurrentAw.length > 0) {
4257
3509
  capabilityDiagnostic = {
4258
3510
  errorCode: 'awCapabilityUnsupported',
@@ -4261,8 +3513,12 @@ async function validatePluginDir(pluginDir, options) {
4261
3513
  };
4262
3514
  error(`[awCapabilityUnsupported] missingCapabilities=${JSON.stringify(requirements.unsupportedByCurrentAw)}`);
4263
3515
  }
4264
- for (const observation of blockingObservations) {
4265
- error(`[awCapabilityScanBlocked] ${observation.path}: ${observation.message}`);
3516
+ const satisfiedCapabilities = new Set([...requirements.declared, ...requirements.derived]);
3517
+ const advisoryObservations = requirements.observations.filter((observation) => (observation.code !== 'aw-command-detected'
3518
+ || observation.capability === undefined
3519
+ || !satisfiedCapabilities.has(observation.capability)));
3520
+ for (const observation of advisoryObservations) {
3521
+ warn(`[awCapabilityObservation] ${observation.path}: ${observation.message}`);
4266
3522
  }
4267
3523
  }
4268
3524
  catch (e) {
@@ -4274,7 +3530,7 @@ async function validatePluginDir(pluginDir, options) {
4274
3530
  validateNoBuildOutputDirs(pluginDir);
4275
3531
  validateDeclaredNativeAssets(pluginDir, manifest);
4276
3532
  validateCanonicalSkillLayout(pluginDir, manifest);
4277
- validatePluginEvalManifest(pluginDir, manifest);
3533
+ await validatePluginEvalManifest(pluginDir, manifest);
4278
3534
  const artifactRenderEntry = validateScriptsLightweight(pluginDir, manifest);
4279
3535
  const runtimeImports = await resolveAuthorRuntimeImports({
4280
3536
  pluginDir,