@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.
- package/dist/authoring/scripts/add-skill.mjs +4 -4
- package/dist/authoring/scripts/add-skill.mjs.map +1 -1
- package/dist/authoring/scripts/init-plugin.mjs +4 -4
- package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
- package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
- package/dist/authoring/scripts/lib/skill-scaffold.d.mts +77 -90
- package/dist/authoring/scripts/lib/skill-scaffold.mjs +99 -120
- package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
- package/dist/authoring/scripts/remove-plugin.mjs +1 -1
- package/dist/authoring/scripts/remove-plugin.mjs.map +1 -1
- package/dist/authoring/scripts/validate-plugin.mjs +34 -778
- package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
- package/dist/scripts/aw-skill-eval-materializer.d.ts +24 -0
- package/dist/scripts/{plugin-eval-materializer.js → aw-skill-eval-materializer.js} +34 -145
- package/dist/scripts/aw-skill-eval-materializer.js.map +1 -0
- package/dist/scripts/bundle-plugins.d.ts +1 -0
- package/dist/scripts/bundle-plugins.js +2 -1
- package/dist/scripts/bundle-plugins.js.map +1 -1
- package/dist/scripts/plugin-staging.d.ts +5 -6
- package/dist/scripts/plugin-staging.js +12 -13
- package/dist/scripts/plugin-staging.js.map +1 -1
- package/dist/src/authoring/workspace-commands.d.ts +2 -1
- package/dist/src/authoring/workspace-commands.js.map +1 -1
- package/dist/src/commands/aw-capabilities.d.ts +2 -2
- package/dist/src/commands/aw-capabilities.js +6 -11
- package/dist/src/commands/aw-capabilities.js.map +1 -1
- package/dist/src/commands/catalog-dev-source.d.ts +18 -12
- package/dist/src/commands/catalog-dev-source.js +241 -305
- package/dist/src/commands/catalog-dev-source.js.map +1 -1
- package/dist/src/commands/catalog-generation.d.ts +6 -19
- package/dist/src/commands/catalog-generation.js +10 -11
- package/dist/src/commands/catalog-generation.js.map +1 -1
- package/dist/src/commands/catalog-publication-inspection.d.ts +28 -27
- package/dist/src/commands/catalog-publication-inspection.js +139 -50
- package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
- package/dist/src/commands/catalog-publication-journal.d.ts +49 -0
- package/dist/src/commands/catalog-publication-journal.js +147 -0
- package/dist/src/commands/catalog-publication-journal.js.map +1 -0
- package/dist/src/commands/catalog-publication-target.d.ts +2 -1
- package/dist/src/commands/catalog-publication-target.js +37 -2
- package/dist/src/commands/catalog-publication-target.js.map +1 -1
- package/dist/src/commands/catalog-publish.d.ts +51 -15
- package/dist/src/commands/catalog-publish.js +576 -219
- package/dist/src/commands/catalog-publish.js.map +1 -1
- package/dist/src/commands/catalog-source.d.ts +3 -28
- package/dist/src/commands/catalog-source.js +29 -140
- package/dist/src/commands/catalog-source.js.map +1 -1
- package/dist/src/commands/catalog-status.d.ts +6 -26
- package/dist/src/commands/catalog-status.js +8 -146
- package/dist/src/commands/catalog-status.js.map +1 -1
- package/dist/src/commands/catalog-workspace-resolution.d.ts +13 -0
- package/dist/src/commands/catalog-workspace-resolution.js +42 -0
- package/dist/src/commands/catalog-workspace-resolution.js.map +1 -0
- package/dist/src/commands/dependency-operation-plan.d.ts +88 -9
- package/dist/src/commands/dependency-operation-plan.js +331 -6
- package/dist/src/commands/dependency-operation-plan.js.map +1 -1
- package/dist/src/commands/init.d.ts +5 -11
- package/dist/src/commands/init.js +30 -51
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/offline-cleanup-evidence.d.ts +20 -0
- package/dist/src/commands/offline-cleanup-evidence.js +87 -9
- package/dist/src/commands/offline-cleanup-evidence.js.map +1 -1
- package/dist/src/commands/options.d.ts +6 -1
- package/dist/src/commands/options.js +64 -6
- package/dist/src/commands/options.js.map +1 -1
- package/dist/src/commands/plugin-capabilities.js +4 -0
- package/dist/src/commands/plugin-capabilities.js.map +1 -1
- package/dist/src/commands/rebuild-command.d.ts +10 -8
- package/dist/src/commands/rebuild-command.js +1217 -426
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/rebuild-integration.d.ts +54 -6
- package/dist/src/commands/rebuild-integration.js +691 -364
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/rebuild-operations.d.ts +2 -0
- package/dist/src/commands/rebuild-operations.js +31 -17
- package/dist/src/commands/rebuild-operations.js.map +1 -1
- package/dist/src/commands/skill-eval.d.ts +33 -0
- package/dist/src/commands/skill-eval.js +363 -0
- package/dist/src/commands/skill-eval.js.map +1 -0
- package/dist/src/commands/{status-v5.d.ts → status-v6.d.ts} +59 -26
- package/dist/src/commands/{status-v5.js → status-v6.js} +882 -79
- package/dist/src/commands/status-v6.js.map +1 -0
- package/dist/src/commands/sync-command.d.ts +21 -0
- package/dist/src/commands/sync-command.js +206 -0
- package/dist/src/commands/sync-command.js.map +1 -0
- package/dist/src/commands/wizard.d.ts +2 -2
- package/dist/src/commands/write-commands.js +1 -43
- package/dist/src/commands/write-commands.js.map +1 -1
- package/dist/src/constants.d.ts +5 -4
- package/dist/src/constants.js +5 -4
- package/dist/src/constants.js.map +1 -1
- package/dist/src/eval/manifest-v1.d.ts +66 -0
- package/dist/src/eval/manifest-v1.js +326 -0
- package/dist/src/eval/manifest-v1.js.map +1 -0
- package/dist/src/generators/local-plugin.d.ts +3 -0
- package/dist/src/generators/local-plugin.js +123 -55
- package/dist/src/generators/local-plugin.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +412 -152
- package/dist/src/index.js.map +1 -1
- package/dist/src/output/command-receipt.d.ts +58 -8
- package/dist/src/output/command-receipt.js +141 -11
- package/dist/src/output/command-receipt.js.map +1 -1
- package/dist/src/output/model.d.ts +3 -11
- package/dist/src/portable/commands.d.ts +2 -2
- package/dist/src/portable/commands.js +2 -8
- package/dist/src/portable/commands.js.map +1 -1
- package/dist/src/portable/materializer.d.ts +1 -4
- package/dist/src/portable/materializer.js +0 -2
- package/dist/src/portable/materializer.js.map +1 -1
- package/dist/src/tui/catalog-generation.d.ts +20 -41
- package/dist/src/tui/catalog-generation.js +65 -224
- package/dist/src/tui/catalog-generation.js.map +1 -1
- package/dist/src/tui/components.js +1 -2
- package/dist/src/tui/components.js.map +1 -1
- package/dist/src/tui/console-app-v4.js +461 -330
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.d.ts +3 -5
- package/dist/src/tui/controller-runtime.js +182 -187
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +42 -45
- package/dist/src/tui/controller.js +454 -407
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/diagnostics.d.ts +1 -2
- package/dist/src/tui/diagnostics.js.map +1 -1
- package/dist/src/tui/index.d.ts +1 -1
- package/dist/src/tui/index.js +1 -1
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/model.d.ts +81 -13
- package/dist/src/tui/model.js +378 -95
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/router.d.ts +9 -0
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/types.d.ts +54 -47
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils/agent-version.d.ts +4 -1
- package/dist/src/utils/agent-version.js +28 -3
- package/dist/src/utils/agent-version.js.map +1 -1
- package/dist/src/utils/catalog-digests.d.ts +7 -0
- package/dist/src/utils/catalog-digests.js +27 -0
- package/dist/src/utils/catalog-digests.js.map +1 -1
- package/dist/src/utils/catalog-generation-control-snapshot.d.ts +11 -0
- package/dist/src/utils/catalog-generation-control-snapshot.js +45 -0
- package/dist/src/utils/catalog-generation-control-snapshot.js.map +1 -0
- package/dist/src/utils/catalog-generation-error.d.ts +2 -1
- package/dist/src/utils/catalog-generation-error.js +12 -0
- package/dist/src/utils/catalog-generation-error.js.map +1 -1
- package/dist/src/utils/catalog-generation-resolver.d.ts +10 -3
- package/dist/src/utils/catalog-generation-resolver.js +90 -76
- package/dist/src/utils/catalog-generation-resolver.js.map +1 -1
- package/dist/src/utils/catalog-generation-service.d.ts +24 -33
- package/dist/src/utils/catalog-generation-service.js +253 -567
- package/dist/src/utils/catalog-generation-service.js.map +1 -1
- package/dist/src/utils/catalog-generation-state.d.ts +4 -0
- package/dist/src/utils/catalog-generation-state.js +73 -106
- package/dist/src/utils/catalog-generation-state.js.map +1 -1
- package/dist/src/utils/catalog-generation-store.d.ts +16 -26
- package/dist/src/utils/catalog-generation-store.js +154 -290
- package/dist/src/utils/catalog-generation-store.js.map +1 -1
- package/dist/src/utils/catalog-manifest-v6.d.ts +5 -0
- package/dist/src/utils/{catalog-manifest-v5.js → catalog-manifest-v6.js} +23 -35
- package/dist/src/utils/catalog-manifest-v6.js.map +1 -0
- package/dist/src/utils/catalog-operation-context.d.ts +6 -14
- package/dist/src/utils/catalog-operation-context.js +68 -169
- package/dist/src/utils/catalog-operation-context.js.map +1 -1
- package/dist/src/utils/catalog-public-error.js +1 -4
- package/dist/src/utils/catalog-public-error.js.map +1 -1
- package/dist/src/utils/catalog-runtime-projection.d.ts +3 -0
- package/dist/src/utils/catalog-runtime-projection.js +5 -40
- package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
- package/dist/src/utils/catalog-snapshot.d.ts +2 -2
- package/dist/src/utils/catalog-source-references.d.ts +7 -0
- package/dist/src/utils/catalog-source-references.js +44 -55
- package/dist/src/utils/catalog-source-references.js.map +1 -1
- package/dist/src/utils/catalog-sources.js +25 -9
- package/dist/src/utils/catalog-sources.js.map +1 -1
- package/dist/src/utils/codex-config.d.ts +2 -0
- package/dist/src/utils/codex-config.js +22 -1
- package/dist/src/utils/codex-config.js.map +1 -1
- package/dist/src/utils/codex-manager.d.ts +50 -2
- package/dist/src/utils/codex-manager.js +160 -6
- package/dist/src/utils/codex-manager.js.map +1 -1
- package/dist/src/utils/codex-reconciler.d.ts +131 -0
- package/dist/src/utils/codex-reconciler.js +327 -0
- package/dist/src/utils/codex-reconciler.js.map +1 -0
- package/dist/src/utils/codex-shared-native.d.ts +56 -0
- package/dist/src/utils/codex-shared-native.js +87 -0
- package/dist/src/utils/codex-shared-native.js.map +1 -0
- package/dist/src/utils/codex-surface-analysis.d.ts +44 -0
- package/dist/src/utils/codex-surface-analysis.js +247 -0
- package/dist/src/utils/codex-surface-analysis.js.map +1 -0
- package/dist/src/utils/git-catalog-provider.js +56 -115
- package/dist/src/utils/git-catalog-provider.js.map +1 -1
- package/dist/src/utils/manifest.d.ts +1 -3
- package/dist/src/utils/manifest.js +1 -3
- package/dist/src/utils/manifest.js.map +1 -1
- package/dist/src/utils/multi-source-catalog.d.ts +1 -10
- package/dist/src/utils/multi-source-catalog.js +8 -175
- package/dist/src/utils/multi-source-catalog.js.map +1 -1
- package/dist/src/utils/native-cli.d.ts +34 -0
- package/dist/src/utils/native-cli.js +91 -0
- package/dist/src/utils/native-cli.js.map +1 -1
- package/dist/src/utils/orphan-marketplace-cleanup.js +1 -0
- package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -1
- package/dist/src/utils/ownership.d.ts +0 -10
- package/dist/src/utils/ownership.js +3 -88
- package/dist/src/utils/ownership.js.map +1 -1
- package/dist/src/utils/path-catalog-provider.d.ts +1 -1
- package/dist/src/utils/path-catalog-provider.js +45 -62
- package/dist/src/utils/path-catalog-provider.js.map +1 -1
- package/dist/src/utils/paths.d.ts +0 -1
- package/dist/src/utils/paths.js +0 -1
- package/dist/src/utils/paths.js.map +1 -1
- package/dist/src/utils/plugin-config-write.js +0 -5
- package/dist/src/utils/plugin-config-write.js.map +1 -1
- package/dist/src/utils/plugin-intent-state.d.ts +39 -0
- package/dist/src/utils/plugin-intent-state.js +325 -0
- package/dist/src/utils/plugin-intent-state.js.map +1 -0
- package/dist/src/utils/target-eligibility.d.ts +3 -2
- package/dist/src/utils/target-eligibility.js +4 -3
- package/dist/src/utils/target-eligibility.js.map +1 -1
- package/package.json +5 -4
- package/dist/scripts/aw-skill-eval-engine.d.ts +0 -13
- package/dist/scripts/aw-skill-eval-engine.js +0 -31
- package/dist/scripts/aw-skill-eval-engine.js.map +0 -1
- package/dist/scripts/codex-eval-app-server.d.ts +0 -91
- package/dist/scripts/codex-eval-app-server.js +0 -1334
- package/dist/scripts/codex-eval-app-server.js.map +0 -1
- package/dist/scripts/codex-eval-isolation.d.ts +0 -61
- package/dist/scripts/codex-eval-isolation.js +0 -318
- package/dist/scripts/codex-eval-isolation.js.map +0 -1
- package/dist/scripts/codex-eval-process-management.d.ts +0 -8
- package/dist/scripts/codex-eval-process-management.js +0 -69
- package/dist/scripts/codex-eval-process-management.js.map +0 -1
- package/dist/scripts/codex-eval-process-tree.d.ts +0 -38
- package/dist/scripts/codex-eval-process-tree.js +0 -803
- package/dist/scripts/codex-eval-process-tree.js.map +0 -1
- package/dist/scripts/codex-eval-runtime-config.d.ts +0 -62
- package/dist/scripts/codex-eval-runtime-config.js +0 -843
- package/dist/scripts/codex-eval-runtime-config.js.map +0 -1
- package/dist/scripts/codex-trigger-eval.d.ts +0 -84
- package/dist/scripts/codex-trigger-eval.js +0 -968
- package/dist/scripts/codex-trigger-eval.js.map +0 -1
- package/dist/scripts/eval-plugin-skills-audited.d.ts +0 -6
- package/dist/scripts/eval-plugin-skills-audited.js +0 -261
- package/dist/scripts/eval-plugin-skills-audited.js.map +0 -1
- package/dist/scripts/eval-plugin-skills.d.ts +0 -45
- package/dist/scripts/eval-plugin-skills.js +0 -1589
- package/dist/scripts/eval-plugin-skills.js.map +0 -1
- package/dist/scripts/eval-run-layout.d.ts +0 -17
- package/dist/scripts/eval-run-layout.js +0 -26
- package/dist/scripts/eval-run-layout.js.map +0 -1
- package/dist/scripts/plugin-eval-contract.d.ts +0 -189
- package/dist/scripts/plugin-eval-contract.js +0 -745
- package/dist/scripts/plugin-eval-contract.js.map +0 -1
- package/dist/scripts/plugin-eval-materializer.d.ts +0 -13
- package/dist/scripts/plugin-eval-materializer.js.map +0 -1
- package/dist/scripts/skill-reporting.d.ts +0 -212
- package/dist/scripts/skill-reporting.js +0 -922
- package/dist/scripts/skill-reporting.js.map +0 -1
- package/dist/src/commands/catalog-context.d.ts +0 -52
- package/dist/src/commands/catalog-context.js +0 -156
- package/dist/src/commands/catalog-context.js.map +0 -1
- package/dist/src/commands/status-v5.js.map +0 -1
- package/dist/src/utils/catalog-manifest-v5.d.ts +0 -5
- package/dist/src/utils/catalog-manifest-v5.js.map +0 -1
- package/dist/src/utils/catalog-source-cache.d.ts +0 -62
- package/dist/src/utils/catalog-source-cache.js +0 -321
- package/dist/src/utils/catalog-source-cache.js.map +0 -1
- package/dist/src/utils/codex-project-surface.d.ts +0 -3
- package/dist/src/utils/codex-project-surface.js +0 -49
- package/dist/src/utils/codex-project-surface.js.map +0 -1
- package/dist/src/utils/plugin-release.d.ts +0 -2
- package/dist/src/utils/plugin-release.js +0 -2
- 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 =
|
|
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
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
4265
|
-
|
|
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,
|