@lesliechan721/aw 0.1.0-beta.23 → 0.1.0-beta.24
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/scripts/aw-skill-eval-materializer.js +12 -63
- package/dist/scripts/aw-skill-eval-materializer.js.map +1 -1
- package/dist/src/commands/aw-capabilities.d.ts +1 -1
- package/dist/src/commands/aw-capabilities.js +2 -1
- package/dist/src/commands/aw-capabilities.js.map +1 -1
- package/dist/src/commands/rebuild-integration.d.ts +25 -0
- package/dist/src/commands/rebuild-integration.js +154 -6
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/review-inspection.d.ts +71 -12
- package/dist/src/commands/review-inspection.js +485 -61
- package/dist/src/commands/review-inspection.js.map +1 -1
- package/dist/src/commands/review-preparation.d.ts +49 -0
- package/dist/src/commands/review-preparation.js +427 -0
- package/dist/src/commands/review-preparation.js.map +1 -0
- package/dist/src/generators/local-plugin.d.ts +2 -1
- package/dist/src/generators/local-plugin.js +40 -34
- package/dist/src/generators/local-plugin.js.map +1 -1
- package/dist/src/index.js +146 -20
- package/dist/src/index.js.map +1 -1
- package/dist/src/portable/materializer.d.ts +1 -0
- package/dist/src/portable/materializer.js +51 -11
- package/dist/src/portable/materializer.js.map +1 -1
- package/dist/src/review/review-profile.d.ts +17 -0
- package/dist/src/review/review-profile.js +177 -0
- package/dist/src/review/review-profile.js.map +1 -0
- package/dist/src/review/review-selection.d.ts +30 -0
- package/dist/src/review/review-selection.js +527 -0
- package/dist/src/review/review-selection.js.map +1 -0
- package/dist/src/review/review-types.d.ts +68 -0
- package/dist/src/review/review-types.js +16 -0
- package/dist/src/review/review-types.js.map +1 -0
- package/dist/src/review/skill-materialization.d.ts +13 -0
- package/dist/src/review/skill-materialization.js +72 -0
- package/dist/src/review/skill-materialization.js.map +1 -0
- package/dist/src/review/source-binding.d.ts +21 -9
- package/dist/src/review/source-binding.js +58 -18
- package/dist/src/review/source-binding.js.map +1 -1
- package/dist/src/tui/console-app-v4.js +2 -1
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.js +5 -2
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/model.d.ts +1 -0
- package/dist/src/tui/model.js +32 -3
- package/dist/src/tui/model.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { computeCaseCorpusDigest, computeSkillDigest, } from '@lesliechan721/agent-skill-eval';
|
|
2
|
+
import { canonicalJson, computeCaseCorpusDigest, computeSkillDigest, } from '@lesliechan721/agent-skill-eval';
|
|
3
3
|
import { calculateCatalogWorkspaceSourceIdentity, DEFAULT_CATALOG_OUTPUT_PATH, GENERATED_OUTPUT_MARKER_FILENAME, readCatalogWorkspace, resolveSupportedPlatforms, validateCatalogSnapshotComplete, } from '@lesliechan721/aw-plugin-core';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
import { readAwSkillEvalManifest, } from '../eval/manifest-v1.js';
|
|
6
6
|
import { CLI_RESERVED_IMPORTS } from '../utils/manifest.js';
|
|
7
7
|
import { resolveCatalogWorkspaceRoot, } from './catalog-workspace-resolution.js';
|
|
8
8
|
import { computeReviewComponentDigest } from './review-publication.js';
|
|
9
|
-
import { computeAwSourceBindingSelectedCasesDigest, computeAwSourceBindingSuiteDefaultsDigest, readAwSourceBinding, resolveEvalProfileBindingInput, selectAwSourceBindingEvalCases, validateAwSourceBindingForWorkspace, } from '../review/source-binding.js';
|
|
9
|
+
import { computeAwSourceBindingSelectedCasesDigest, computeAwSourceBindingSuiteDefaultsDigest, readAwSourceBinding, resolvePortablePluginProfileConfig, resolveEvalProfileBindingInput, selectAwSourceBindingEvalCases, validateAwSourceBindingForWorkspace, } from '../review/source-binding.js';
|
|
10
|
+
import { resolveReviewDynamicSkillSource, resolveReviewSelection, reviewBindingMatchesSelection, } from '../review/review-selection.js';
|
|
11
|
+
import { resolveEffectivePluginConfigFromStores, } from '../utils/plugin-config.js';
|
|
12
|
+
export class ReviewInspectionInputError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
exitCode = 2;
|
|
15
|
+
constructor(code, message) {
|
|
16
|
+
super(`${code}: ${message}`);
|
|
17
|
+
this.name = 'ReviewInspectionInputError';
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
class ReviewInspectionResolvedTargetError extends Error {
|
|
22
|
+
code;
|
|
23
|
+
exitCode;
|
|
24
|
+
reviewTarget;
|
|
25
|
+
constructor(error, target) {
|
|
26
|
+
super(error instanceof Error ? error.message : String(error), { cause: error });
|
|
27
|
+
this.name = 'ReviewInspectionResolvedTargetError';
|
|
28
|
+
this.code = getReviewInspectionErrorCode(error);
|
|
29
|
+
this.exitCode = getReviewInspectionExitCode(error);
|
|
30
|
+
this.reviewTarget = target;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
10
33
|
const compareText = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
11
34
|
function stripDotSlash(value) {
|
|
12
35
|
return value.startsWith('./') ? value.slice(2) : value;
|
|
@@ -14,8 +37,13 @@ function stripDotSlash(value) {
|
|
|
14
37
|
function relativeWorkspacePath(workspaceRoot, filePath) {
|
|
15
38
|
return path.relative(workspaceRoot, filePath).split(path.sep).join('/') || '.';
|
|
16
39
|
}
|
|
17
|
-
function sourceKey(
|
|
18
|
-
|
|
40
|
+
function sourceKey(options) {
|
|
41
|
+
const sourcePaths = options.roots.map((root) => relativeWorkspacePath(options.workspaceRoot, root));
|
|
42
|
+
const sourceIdentity = encodeURIComponent(JSON.stringify({
|
|
43
|
+
primary: sourcePaths[0],
|
|
44
|
+
sources: [...sourcePaths].sort(compareText),
|
|
45
|
+
}));
|
|
46
|
+
return `source:${options.plugin}:${options.skill}:${options.kind}:${sourceIdentity}`;
|
|
19
47
|
}
|
|
20
48
|
function requiredFinalKey(seed) {
|
|
21
49
|
return [
|
|
@@ -39,6 +67,13 @@ function materializationIdentityKey(options) {
|
|
|
39
67
|
function scopeFromProfile(profile) {
|
|
40
68
|
return profile.endsWith('-project') ? 'project' : 'user';
|
|
41
69
|
}
|
|
70
|
+
function finalProfileFromBinding(profile) {
|
|
71
|
+
if (profile.kind === 'eval')
|
|
72
|
+
return `codex-${profile.scope}`;
|
|
73
|
+
if (profile.kind === 'portable')
|
|
74
|
+
return `agent-skills-${profile.scope}`;
|
|
75
|
+
return `${profile.platform}-${profile.scope}`;
|
|
76
|
+
}
|
|
42
77
|
async function directSkillDirectories(root) {
|
|
43
78
|
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
44
79
|
const result = [];
|
|
@@ -48,7 +83,7 @@ async function directSkillDirectories(root) {
|
|
|
48
83
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
49
84
|
throw new Error(`Skill collection direct child must be a regular directory: ${entryPath}`);
|
|
50
85
|
}
|
|
51
|
-
result.push({ name: entry.name, root: entryPath });
|
|
86
|
+
result.push({ name: entry.name, root: await fs.realpath(entryPath) });
|
|
52
87
|
}
|
|
53
88
|
return result;
|
|
54
89
|
}
|
|
@@ -126,7 +161,13 @@ async function inspectSnapshot(workspace, workspaceSourceIdentity) {
|
|
|
126
161
|
}
|
|
127
162
|
}
|
|
128
163
|
async function addSourceSubject(options) {
|
|
129
|
-
const key = sourceKey(
|
|
164
|
+
const key = sourceKey({
|
|
165
|
+
workspaceRoot: options.workspace.workspaceRoot,
|
|
166
|
+
plugin: options.plugin.manifest.name,
|
|
167
|
+
skill: options.skill,
|
|
168
|
+
kind: options.kind,
|
|
169
|
+
roots: options.sourceRoots ?? [options.root],
|
|
170
|
+
});
|
|
130
171
|
if (options.subjects.some((subject) => subject.key === key))
|
|
131
172
|
return key;
|
|
132
173
|
const digest = options.kind === 'artifact-source'
|
|
@@ -155,8 +196,6 @@ async function inventoryPluginSource(options) {
|
|
|
155
196
|
const portableSkills = plugin.manifest.distributions?.agentSkills?.skills
|
|
156
197
|
? await directSkillDirectories(path.join(plugin.rootDir, stripDotSlash(plugin.manifest.distributions.agentSkills.skills)))
|
|
157
198
|
: [];
|
|
158
|
-
const nativeByName = new Map(nativeSkills.map((entry) => [entry.name, entry]));
|
|
159
|
-
const portableByName = new Map(portableSkills.map((entry) => [entry.name, entry]));
|
|
160
199
|
const nativeDynamicByPlatform = new Map([
|
|
161
200
|
['claude', new Set()],
|
|
162
201
|
['codex', new Set()],
|
|
@@ -203,7 +242,13 @@ async function inventoryPluginSource(options) {
|
|
|
203
242
|
skill: skill.name,
|
|
204
243
|
profile: `${platform}-${scope}`,
|
|
205
244
|
targetId: null,
|
|
206
|
-
sourceSubjectKey: sourceKey(
|
|
245
|
+
sourceSubjectKey: sourceKey({
|
|
246
|
+
workspaceRoot: workspace.workspaceRoot,
|
|
247
|
+
plugin: plugin.manifest.name,
|
|
248
|
+
skill: skill.name,
|
|
249
|
+
kind: 'source-skill',
|
|
250
|
+
roots: [skill.root],
|
|
251
|
+
}),
|
|
207
252
|
dynamic: false,
|
|
208
253
|
});
|
|
209
254
|
}
|
|
@@ -219,7 +264,13 @@ async function inventoryPluginSource(options) {
|
|
|
219
264
|
skill: skill.name,
|
|
220
265
|
profile: `agent-skills-${scope}`,
|
|
221
266
|
targetId: null,
|
|
222
|
-
sourceSubjectKey: sourceKey(
|
|
267
|
+
sourceSubjectKey: sourceKey({
|
|
268
|
+
workspaceRoot: workspace.workspaceRoot,
|
|
269
|
+
plugin: plugin.manifest.name,
|
|
270
|
+
skill: skill.name,
|
|
271
|
+
kind: 'portable-source-skill',
|
|
272
|
+
roots: [skill.root],
|
|
273
|
+
}),
|
|
223
274
|
dynamic: false,
|
|
224
275
|
});
|
|
225
276
|
}
|
|
@@ -232,27 +283,22 @@ async function inventoryPluginSource(options) {
|
|
|
232
283
|
const platform = target.target === 'agent-skills.skill'
|
|
233
284
|
? 'agent-skills'
|
|
234
285
|
: target.target === 'codex.skill' ? 'codex' : 'claude';
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
root: sourceRoot,
|
|
252
|
-
kind: 'artifact-source',
|
|
253
|
-
});
|
|
254
|
-
sourceSubjectKeys.push(sourceSubjectKey);
|
|
255
|
-
}
|
|
286
|
+
const source = await resolveReviewDynamicSkillSource({
|
|
287
|
+
plugin,
|
|
288
|
+
target,
|
|
289
|
+
nativeSkillPaths: nativeSkills.map((entry) => entry.root),
|
|
290
|
+
portableSkillPaths: portableSkills.map((entry) => entry.root),
|
|
291
|
+
});
|
|
292
|
+
const sourceSubjectKey = await addSourceSubject({
|
|
293
|
+
workspace,
|
|
294
|
+
subjects,
|
|
295
|
+
plugin,
|
|
296
|
+
skill,
|
|
297
|
+
root: source.sourcePath,
|
|
298
|
+
sourceRoots: source.sourcePaths,
|
|
299
|
+
kind: source.sourceKind,
|
|
300
|
+
});
|
|
301
|
+
sourceSubjectKeys.push(sourceSubjectKey);
|
|
256
302
|
for (const scope of target.scopes) {
|
|
257
303
|
seeds.push({
|
|
258
304
|
plugin,
|
|
@@ -414,8 +460,11 @@ async function resolveTarget(options) {
|
|
|
414
460
|
}
|
|
415
461
|
const candidate = path.resolve(options.cwd, options.target);
|
|
416
462
|
const stat = await fs.lstat(candidate).catch(() => null);
|
|
417
|
-
if (!stat
|
|
418
|
-
throw new
|
|
463
|
+
if (!stat) {
|
|
464
|
+
throw new ReviewInspectionInputError('targetNotFound', `Review target was not found: ${candidate}`);
|
|
465
|
+
}
|
|
466
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
467
|
+
throw new ReviewInspectionInputError('invalidArguments', 'The release view requires a canonical Plugin or Catalog Workspace directory.');
|
|
419
468
|
}
|
|
420
469
|
const real = await fs.realpath(candidate);
|
|
421
470
|
if (real === options.workspace.workspaceRoot) {
|
|
@@ -428,7 +477,7 @@ async function resolveTarget(options) {
|
|
|
428
477
|
}
|
|
429
478
|
const plugin = options.workspace.plugins.find((entry) => entry.rootDir === real);
|
|
430
479
|
if (!plugin) {
|
|
431
|
-
throw new
|
|
480
|
+
throw new ReviewInspectionInputError('invalidArguments', 'The release view requires the complete Plugin or Catalog target.');
|
|
432
481
|
}
|
|
433
482
|
return { kind: 'plugin', id: plugin.manifest.name, path: real, plugins: [plugin] };
|
|
434
483
|
}
|
|
@@ -444,7 +493,8 @@ async function inspectMaterializations(options) {
|
|
|
444
493
|
sourceIdentity: options.workspaceSourceIdentity,
|
|
445
494
|
},
|
|
446
495
|
};
|
|
447
|
-
|
|
496
|
+
const bindingPaths = [...new Set(options.paths.map((entry) => path.resolve(entry)))].sort(compareText);
|
|
497
|
+
for (const bindingPath of bindingPaths) {
|
|
448
498
|
let parsed = null;
|
|
449
499
|
try {
|
|
450
500
|
parsed = await readAwSourceBinding(bindingPath);
|
|
@@ -468,33 +518,36 @@ async function inspectMaterializations(options) {
|
|
|
468
518
|
state: validation.state,
|
|
469
519
|
plugin: validation.binding?.subject.plugin ?? null,
|
|
470
520
|
skill: validation.binding?.subject.skill ?? null,
|
|
471
|
-
profile: validation.binding
|
|
472
|
-
? validation.binding.profile.kind === 'eval'
|
|
473
|
-
? `codex-${validation.binding.profile.scope}`
|
|
474
|
-
: `agent-skills-${validation.binding.profile.scope}`
|
|
475
|
-
: null,
|
|
521
|
+
profile: validation.binding ? finalProfileFromBinding(validation.binding.profile) : null,
|
|
476
522
|
finalKeys: [],
|
|
477
523
|
verifiedBinding: null,
|
|
478
524
|
issues: validation.issues.map((issue) => issue.message),
|
|
479
525
|
});
|
|
480
526
|
continue;
|
|
481
527
|
}
|
|
482
|
-
const profile = validation.binding.profile
|
|
483
|
-
? `codex-${validation.binding.profile.scope}`
|
|
484
|
-
: `agent-skills-${validation.binding.profile.scope}`;
|
|
528
|
+
const profile = finalProfileFromBinding(validation.binding.profile);
|
|
485
529
|
const finalKeys = [];
|
|
486
530
|
for (const output of validation.outputs) {
|
|
487
|
-
if (output.kind !== 'eval-target'
|
|
531
|
+
if (output.kind !== 'eval-target'
|
|
532
|
+
&& output.kind !== 'portable-skill'
|
|
533
|
+
&& output.kind !== 'review-skill')
|
|
488
534
|
continue;
|
|
489
|
-
const outputPlugin = output.kind === 'portable-skill'
|
|
535
|
+
const outputPlugin = output.kind === 'portable-skill' || output.kind === 'review-skill'
|
|
490
536
|
? output.plugin
|
|
491
537
|
: validation.binding.subject.plugin;
|
|
492
538
|
if (!targetPluginNames.has(outputPlugin))
|
|
493
539
|
continue;
|
|
494
|
-
const
|
|
540
|
+
const compatibleRequired = options.requiredFinals.filter((required) => required.plugin === outputPlugin
|
|
495
541
|
&& required.skill === output.skill
|
|
496
|
-
&& required.profile === profile
|
|
497
|
-
|
|
542
|
+
&& required.profile === profile
|
|
543
|
+
&& (validation.binding.profile.kind !== 'eval'
|
|
544
|
+
|| required.targetId === null
|
|
545
|
+
|| options.targetPlugins
|
|
546
|
+
.find((plugin) => plugin.manifest.name === required.plugin)
|
|
547
|
+
?.manifest.artifacts?.targets
|
|
548
|
+
?.find((target) => target.id === required.targetId)
|
|
549
|
+
?.delivery?.mode !== 'codex-native'));
|
|
550
|
+
if (compatibleRequired.length === 0) {
|
|
498
551
|
options.issues.push({
|
|
499
552
|
category: 'unknown',
|
|
500
553
|
code: 'materializedOutputOwnerUnknown',
|
|
@@ -549,6 +602,7 @@ async function inspectMaterializations(options) {
|
|
|
549
602
|
identityKey,
|
|
550
603
|
path: output.path,
|
|
551
604
|
digest: output.digest,
|
|
605
|
+
requiredKeys: compatibleRequired.map((required) => required.key).sort(compareText),
|
|
552
606
|
});
|
|
553
607
|
}
|
|
554
608
|
}
|
|
@@ -587,11 +641,325 @@ async function inspectMaterializations(options) {
|
|
|
587
641
|
finals: [...finals.values()].sort((left, right) => compareText(left.key, right.key)),
|
|
588
642
|
};
|
|
589
643
|
}
|
|
590
|
-
|
|
644
|
+
function sameResolvedConfig(left, right) {
|
|
645
|
+
return canonicalJson({ values: left.values, sources: left.sources })
|
|
646
|
+
=== canonicalJson({ values: right.values, sources: right.sources });
|
|
647
|
+
}
|
|
648
|
+
function selectResolvedConfig(config, keys) {
|
|
649
|
+
return {
|
|
650
|
+
values: Object.fromEntries(keys.flatMap((key) => Object.hasOwn(config.values, key) ? [[key, config.values[key]]] : [])),
|
|
651
|
+
sources: Object.fromEntries(keys.flatMap((key) => Object.hasOwn(config.sources, key) ? [[key, config.sources[key]]] : [])),
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
function selectedSkillConfig(profile, skill) {
|
|
655
|
+
const entries = profile.pluginConfigs.filter((entry) => entry.pluginName === skill.pluginName);
|
|
656
|
+
if (entries.length === 0)
|
|
657
|
+
return skill.refreshKeys.length === 0 ? { values: {}, sources: {} } : null;
|
|
658
|
+
const values = Object.assign({}, ...entries.map((entry) => entry.values));
|
|
659
|
+
const sources = Object.assign({}, ...entries.map((entry) => entry.sources));
|
|
660
|
+
return selectResolvedConfig({ values, sources }, skill.refreshKeys);
|
|
661
|
+
}
|
|
662
|
+
function legacyProfileStore(scope, values) {
|
|
663
|
+
return {
|
|
664
|
+
path: `<legacy-${scope}-profile>`,
|
|
665
|
+
exists: Object.keys(values).length > 0,
|
|
666
|
+
state: 'ready',
|
|
667
|
+
values,
|
|
668
|
+
issues: [],
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
async function legacyBindingMatchesSelection(options) {
|
|
672
|
+
const { binding, requestedProfile, skill } = options;
|
|
673
|
+
if (binding.profile.scope !== requestedProfile.scope)
|
|
674
|
+
return false;
|
|
675
|
+
if (binding.profile.kind === 'eval') {
|
|
676
|
+
if (requestedProfile.platform !== 'codex'
|
|
677
|
+
|| !binding.subject.skill
|
|
678
|
+
|| skill.delivery === 'codex-native')
|
|
679
|
+
return false;
|
|
680
|
+
const plugin = options.selection.workspace.plugins.find((entry) => entry.manifest.name === binding.subject.plugin);
|
|
681
|
+
if (!plugin)
|
|
682
|
+
return false;
|
|
683
|
+
const loaded = await readAwSkillEvalManifest({ plugin, skill: binding.subject.skill });
|
|
684
|
+
const evalProfile = loaded.manifest.profiles.find((entry) => entry.id === binding.profile.id);
|
|
685
|
+
if (!evalProfile)
|
|
686
|
+
return false;
|
|
687
|
+
const config = Object.fromEntries(evalProfile.plugins.map((entry) => [entry.name, entry.config]));
|
|
688
|
+
try {
|
|
689
|
+
const requested = selectedSkillConfig(requestedProfile, skill);
|
|
690
|
+
if (!requested)
|
|
691
|
+
return false;
|
|
692
|
+
const overrides = config[skill.pluginName] ?? {};
|
|
693
|
+
const userValues = binding.profile.scope === 'user' ? overrides : {};
|
|
694
|
+
const effective = await resolveEffectivePluginConfigFromStores({
|
|
695
|
+
pluginName: skill.pluginName,
|
|
696
|
+
manifest: skill.plugin.manifest,
|
|
697
|
+
targetScope: binding.profile.scope,
|
|
698
|
+
projectRoot: options.selection.workspace.workspaceRoot,
|
|
699
|
+
stores: {
|
|
700
|
+
user: legacyProfileStore('user', userValues),
|
|
701
|
+
...(binding.profile.scope === 'project'
|
|
702
|
+
? { project: legacyProfileStore('project', overrides) }
|
|
703
|
+
: {}),
|
|
704
|
+
},
|
|
705
|
+
});
|
|
706
|
+
return sameResolvedConfig(requested, selectResolvedConfig(effective, skill.refreshKeys));
|
|
707
|
+
}
|
|
708
|
+
catch {
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (binding.profile.kind !== 'portable' || requestedProfile.platform !== 'agent-skills') {
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
try {
|
|
716
|
+
const requested = selectedSkillConfig(requestedProfile, skill);
|
|
717
|
+
if (!requested)
|
|
718
|
+
return false;
|
|
719
|
+
const current = await resolvePortablePluginProfileConfig({
|
|
720
|
+
plugin: skill.plugin,
|
|
721
|
+
scope: skill.configScope,
|
|
722
|
+
cwd: options.selection.workspace.workspaceRoot,
|
|
723
|
+
env: options.env,
|
|
724
|
+
});
|
|
725
|
+
return sameResolvedConfig(requested, selectResolvedConfig(current.config, skill.refreshKeys));
|
|
726
|
+
}
|
|
727
|
+
catch {
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function bindingOutputIdentity(binding, artifact) {
|
|
732
|
+
if (artifact.kind === 'eval-target') {
|
|
733
|
+
return { plugin: binding.subject.plugin, skill: artifact.skill };
|
|
734
|
+
}
|
|
735
|
+
if (artifact.kind === 'portable-skill' || artifact.kind === 'review-skill') {
|
|
736
|
+
return { plugin: artifact.plugin, skill: artifact.skill };
|
|
737
|
+
}
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
async function inspectNavigationBindings(options) {
|
|
741
|
+
const candidates = new Map();
|
|
742
|
+
const issues = [];
|
|
743
|
+
const context = {
|
|
744
|
+
workspace: options.selection.workspace,
|
|
745
|
+
source: {
|
|
746
|
+
kind: 'workspace',
|
|
747
|
+
workspaceIdentity: options.selection.workspace.workspaceIdentity,
|
|
748
|
+
sourceIdentity: options.selection.sourceIdentity,
|
|
749
|
+
},
|
|
750
|
+
};
|
|
751
|
+
const bindingPaths = [...new Set(options.materializations.map((entry) => path.resolve(entry)))]
|
|
752
|
+
.sort(compareText);
|
|
753
|
+
for (const bindingPath of bindingPaths) {
|
|
754
|
+
let binding;
|
|
755
|
+
try {
|
|
756
|
+
binding = await readAwSourceBinding(bindingPath);
|
|
757
|
+
}
|
|
758
|
+
catch (error) {
|
|
759
|
+
throw new ReviewInspectionInputError('sourceBindingInvalid', error instanceof Error ? error.message : String(error));
|
|
760
|
+
}
|
|
761
|
+
const validation = await validateAwSourceBindingForWorkspace({
|
|
762
|
+
bindingPath,
|
|
763
|
+
context,
|
|
764
|
+
expectedSubject: binding.subject,
|
|
765
|
+
cwd: options.selection.workspace.workspaceRoot,
|
|
766
|
+
env: options.env,
|
|
767
|
+
});
|
|
768
|
+
if (validation.state === 'invalid' || validation.state === 'incomplete') {
|
|
769
|
+
throw new ReviewInspectionInputError('sourceBindingInvalid', validation.issues.map((entry) => entry.message).join('; '));
|
|
770
|
+
}
|
|
771
|
+
let selected = false;
|
|
772
|
+
for (const requestedProfile of options.selection.profiles) {
|
|
773
|
+
const profileMatches = binding.profile.kind === 'review'
|
|
774
|
+
? binding.profile.platform === requestedProfile.platform
|
|
775
|
+
&& binding.profile.scope === requestedProfile.scope
|
|
776
|
+
: binding.profile.kind === 'eval'
|
|
777
|
+
? requestedProfile.platform === 'codex'
|
|
778
|
+
: requestedProfile.platform === 'agent-skills';
|
|
779
|
+
if (!profileMatches)
|
|
780
|
+
continue;
|
|
781
|
+
for (const artifact of binding.artifacts) {
|
|
782
|
+
const identity = bindingOutputIdentity(binding, artifact);
|
|
783
|
+
if (!identity)
|
|
784
|
+
continue;
|
|
785
|
+
const skill = requestedProfile.skills.find((entry) => entry.pluginName === identity.plugin && entry.name === identity.skill);
|
|
786
|
+
if (!skill)
|
|
787
|
+
continue;
|
|
788
|
+
if (binding.profile.kind === 'review'
|
|
789
|
+
&& !await reviewBindingMatchesSelection({
|
|
790
|
+
workspace: options.selection.workspace,
|
|
791
|
+
bindingProfile: binding.profile,
|
|
792
|
+
requestedProfile,
|
|
793
|
+
pluginName: identity.plugin,
|
|
794
|
+
skill: identity.skill,
|
|
795
|
+
}))
|
|
796
|
+
continue;
|
|
797
|
+
if (binding.profile.kind !== 'review'
|
|
798
|
+
&& !await legacyBindingMatchesSelection({
|
|
799
|
+
selection: options.selection,
|
|
800
|
+
requestedProfile,
|
|
801
|
+
skill,
|
|
802
|
+
binding,
|
|
803
|
+
env: options.env,
|
|
804
|
+
}))
|
|
805
|
+
continue;
|
|
806
|
+
selected = true;
|
|
807
|
+
const key = `${identity.plugin}\0${identity.skill}\0${requestedProfile.platform}\0${requestedProfile.scope}`;
|
|
808
|
+
const candidate = {
|
|
809
|
+
path: path.resolve(path.dirname(bindingPath), ...artifact.path.split('/')),
|
|
810
|
+
digest: artifact.digest,
|
|
811
|
+
evidencePath: bindingPath,
|
|
812
|
+
state: validation.state === 'verified' ? 'ready' : 'stale',
|
|
813
|
+
};
|
|
814
|
+
candidates.set(key, [...(candidates.get(key) ?? []), candidate]);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
if (!selected) {
|
|
818
|
+
issues.push({
|
|
819
|
+
code: 'materializationNotSelected',
|
|
820
|
+
path: bindingPath,
|
|
821
|
+
message: 'The source binding does not match the selected Skill, profile, and config input.',
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
else if (validation.state === 'drifted') {
|
|
825
|
+
issues.push({
|
|
826
|
+
code: 'sourceBindingDrifted',
|
|
827
|
+
path: bindingPath,
|
|
828
|
+
message: validation.issues.map((entry) => entry.message).join('; '),
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return { candidates, issues };
|
|
833
|
+
}
|
|
834
|
+
function navigationSkillKey(skill, profile) {
|
|
835
|
+
return `${skill.pluginName}\0${skill.name}\0${profile.platform}\0${profile.scope}`;
|
|
836
|
+
}
|
|
837
|
+
async function inspectNavigationTarget(options) {
|
|
838
|
+
const selection = await resolveReviewSelection({
|
|
839
|
+
cwd: options.cwd,
|
|
840
|
+
target: options.target,
|
|
841
|
+
...(options.workspaceRoot === undefined ? {} : { workspaceRoot: options.workspaceRoot }),
|
|
842
|
+
...(options.skills === undefined ? {} : { skills: options.skills }),
|
|
843
|
+
...(options.platform === undefined ? {} : { platform: options.platform }),
|
|
844
|
+
...(options.scope === undefined ? {} : { scope: options.scope }),
|
|
845
|
+
...(options.configFile === undefined ? {} : { configFile: options.configFile }),
|
|
846
|
+
...(options.env === undefined ? {} : { env: options.env }),
|
|
847
|
+
});
|
|
848
|
+
try {
|
|
849
|
+
const snapshot = await inspectSnapshot(selection.workspace, selection.sourceIdentity);
|
|
850
|
+
const binding = await inspectNavigationBindings({
|
|
851
|
+
selection,
|
|
852
|
+
materializations: options.materializations ?? [],
|
|
853
|
+
env: options.env,
|
|
854
|
+
});
|
|
855
|
+
const skills = [];
|
|
856
|
+
const issues = [...binding.issues];
|
|
857
|
+
if (snapshot.receipt.state === 'invalid' || snapshot.receipt.state === 'stale') {
|
|
858
|
+
issues.push({
|
|
859
|
+
code: snapshot.receipt.state === 'invalid' ? 'snapshotInvalid' : 'snapshotStale',
|
|
860
|
+
path: snapshot.receipt.path,
|
|
861
|
+
message: snapshot.receipt.state === 'invalid'
|
|
862
|
+
? 'The existing Catalog snapshot is invalid.'
|
|
863
|
+
: 'The existing Catalog snapshot is not bound to the current Workspace source.',
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
for (const profile of selection.profiles) {
|
|
867
|
+
for (const skill of profile.skills) {
|
|
868
|
+
const key = navigationSkillKey(skill, profile);
|
|
869
|
+
const candidates = [...(binding.candidates.get(key) ?? [])];
|
|
870
|
+
if (snapshot.root
|
|
871
|
+
&& !skill.dynamic
|
|
872
|
+
&& profile.platform !== 'agent-skills') {
|
|
873
|
+
const finalPath = path.join(snapshot.root, 'plugins', skill.pluginName, profile.platform, 'skills', skill.name);
|
|
874
|
+
try {
|
|
875
|
+
candidates.push({
|
|
876
|
+
path: finalPath,
|
|
877
|
+
digest: await computeSkillDigest(finalPath),
|
|
878
|
+
evidencePath: snapshot.receipt.path,
|
|
879
|
+
state: 'ready',
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
catch {
|
|
883
|
+
// Complete snapshot validation reports invalid final content through its own state.
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
const ready = candidates.filter((entry) => entry.state === 'ready');
|
|
887
|
+
const readyDigests = new Set(ready.map((entry) => entry.digest));
|
|
888
|
+
let state = 'missing';
|
|
889
|
+
let chosen = null;
|
|
890
|
+
if (readyDigests.size > 1) {
|
|
891
|
+
state = 'invalid';
|
|
892
|
+
issues.push({
|
|
893
|
+
code: 'materializationAmbiguous',
|
|
894
|
+
path: null,
|
|
895
|
+
message: `Current materializations have different digests for ${skill.pluginName}/${skill.name}/${profile.profile.id}.`,
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
else if (ready.length > 0) {
|
|
899
|
+
state = 'ready';
|
|
900
|
+
chosen = ready.sort((left, right) => compareText(`${left.evidencePath}\0${left.path}`, `${right.evidencePath}\0${right.path}`))[0];
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
const stale = candidates.filter((entry) => entry.state === 'stale')
|
|
904
|
+
.sort((left, right) => compareText(left.evidencePath, right.evidencePath));
|
|
905
|
+
if (stale.length > 0) {
|
|
906
|
+
state = 'stale';
|
|
907
|
+
chosen = stale[0];
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
if (state === 'missing') {
|
|
911
|
+
issues.push({
|
|
912
|
+
code: 'requiredFinalMissing',
|
|
913
|
+
path: skill.sourcePath,
|
|
914
|
+
message: `No current final is available for ${skill.pluginName}/${skill.name}/${profile.profile.id}.`,
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
skills.push({
|
|
918
|
+
skill: skill.name,
|
|
919
|
+
sourcePath: skill.sourcePath,
|
|
920
|
+
profile: {
|
|
921
|
+
platform: profile.platform,
|
|
922
|
+
scope: profile.scope,
|
|
923
|
+
inputDigest: profile.profile.inputDigest,
|
|
924
|
+
},
|
|
925
|
+
finalPath: chosen?.path ?? null,
|
|
926
|
+
digest: chosen?.digest ?? null,
|
|
927
|
+
state,
|
|
928
|
+
evidencePath: chosen?.evidencePath ?? null,
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
skills.sort((left, right) => compareText(`${left.skill}\0${left.profile.platform}\0${left.profile.scope}\0${left.sourcePath}`, `${right.skill}\0${right.profile.platform}\0${right.profile.scope}\0${right.sourcePath}`));
|
|
933
|
+
issues.sort((left, right) => compareText(`${left.code}\0${left.path ?? ''}\0${left.message}`, `${right.code}\0${right.path ?? ''}\0${right.message}`));
|
|
934
|
+
return {
|
|
935
|
+
schemaVersion: 2,
|
|
936
|
+
command: 'review:inspect',
|
|
937
|
+
view: 'review',
|
|
938
|
+
ok: true,
|
|
939
|
+
target: { kind: selection.target.kind, path: selection.target.path },
|
|
940
|
+
sources: [...new Set(selection.sources.map((entry) => path.resolve(entry)))].sort(compareText),
|
|
941
|
+
...(selection.target.kind === 'catalog'
|
|
942
|
+
? { snapshot: { path: snapshot.receipt.path, state: snapshot.receipt.state } }
|
|
943
|
+
: {}),
|
|
944
|
+
skills,
|
|
945
|
+
issues,
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
catch (error) {
|
|
949
|
+
throw new ReviewInspectionResolvedTargetError(error, {
|
|
950
|
+
kind: selection.target.kind,
|
|
951
|
+
path: selection.target.path,
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
async function inspectReleaseTarget(options) {
|
|
591
956
|
const targetCandidate = options.target === 'catalog' ? null : path.resolve(options.cwd, options.target);
|
|
592
957
|
const targetCandidateStat = targetCandidate
|
|
593
958
|
? await fs.lstat(targetCandidate).catch(() => null)
|
|
594
959
|
: null;
|
|
960
|
+
if (targetCandidateStat && (!targetCandidateStat.isDirectory() || targetCandidateStat.isSymbolicLink())) {
|
|
961
|
+
throw new ReviewInspectionInputError('invalidArguments', 'The release view requires a canonical Plugin or Catalog Workspace directory.');
|
|
962
|
+
}
|
|
595
963
|
const resolution = await resolveCatalogWorkspaceRoot({
|
|
596
964
|
cwd: options.workspaceRoot === undefined && targetCandidateStat?.isDirectory()
|
|
597
965
|
? targetCandidate
|
|
@@ -728,7 +1096,8 @@ export async function inspectReviewTarget(options) {
|
|
|
728
1096
|
for (const final of binding.finals) {
|
|
729
1097
|
const candidates = requiredFinals.filter((required) => required.plugin === final.plugin
|
|
730
1098
|
&& required.skill === final.skill
|
|
731
|
-
&& required.profile === final.profile
|
|
1099
|
+
&& required.profile === final.profile
|
|
1100
|
+
&& final.requiredKeys.includes(required.key));
|
|
732
1101
|
if (candidates.length === 1) {
|
|
733
1102
|
const required = candidates[0];
|
|
734
1103
|
bindingFinalsByRequiredKey.set(required.key, [...(bindingFinalsByRequiredKey.get(required.key) ?? []), final]);
|
|
@@ -814,7 +1183,8 @@ export async function inspectReviewTarget(options) {
|
|
|
814
1183
|
.map((materialization) => materialization.path))]
|
|
815
1184
|
.sort(compareText);
|
|
816
1185
|
if (required.materializedFinalKeys.length === 0) {
|
|
817
|
-
const snapshotFinal = requiredByKey.get(required.requiredFinalKey)?.matchedFinalKeys ?? []
|
|
1186
|
+
const snapshotFinal = (requiredByKey.get(required.requiredFinalKey)?.matchedFinalKeys ?? [])
|
|
1187
|
+
.filter((key) => subjects.some((subject) => subject.key === key && subject.origin === 'catalog-snapshot'));
|
|
818
1188
|
required.materializedFinalKeys = [...snapshotFinal];
|
|
819
1189
|
}
|
|
820
1190
|
}
|
|
@@ -872,8 +1242,9 @@ export async function inspectReviewTarget(options) {
|
|
|
872
1242
|
subjects.sort((left, right) => compareText(left.key, right.key));
|
|
873
1243
|
const ok = !issues.some((issue) => issue.blocking);
|
|
874
1244
|
return {
|
|
875
|
-
schemaVersion:
|
|
1245
|
+
schemaVersion: 2,
|
|
876
1246
|
command: 'review:inspect',
|
|
1247
|
+
view: 'release',
|
|
877
1248
|
ok,
|
|
878
1249
|
completeness: ok && requiredFinals.every((entry) => entry.status === 'resolved')
|
|
879
1250
|
? 'complete' : 'incomplete',
|
|
@@ -900,18 +1271,71 @@ export async function inspectReviewTarget(options) {
|
|
|
900
1271
|
issues,
|
|
901
1272
|
};
|
|
902
1273
|
}
|
|
903
|
-
export function
|
|
1274
|
+
export async function inspectReviewTarget(options) {
|
|
1275
|
+
const view = options.view ?? 'review';
|
|
1276
|
+
if (view === 'release') {
|
|
1277
|
+
if ((options.skills !== undefined && options.skills.length > 0)
|
|
1278
|
+
|| options.platform !== undefined
|
|
1279
|
+
|| options.scope !== undefined
|
|
1280
|
+
|| options.configFile !== undefined) {
|
|
1281
|
+
throw new ReviewInspectionInputError('invalidArguments', 'The release view requires the complete Plugin or Catalog target and does not accept selection options.');
|
|
1282
|
+
}
|
|
1283
|
+
return await inspectReleaseTarget(options);
|
|
1284
|
+
}
|
|
1285
|
+
return await inspectNavigationTarget(options);
|
|
1286
|
+
}
|
|
1287
|
+
export function getReviewInspectionErrorCode(error, fallback = 'reviewInspectionFailed') {
|
|
1288
|
+
if (error && typeof error === 'object' && 'code' in error) {
|
|
1289
|
+
const code = error.code;
|
|
1290
|
+
if (typeof code === 'string' && code !== '')
|
|
1291
|
+
return code;
|
|
1292
|
+
}
|
|
1293
|
+
return fallback;
|
|
1294
|
+
}
|
|
1295
|
+
export function getReviewInspectionExitCode(error) {
|
|
1296
|
+
if (error && typeof error === 'object' && 'exitCode' in error) {
|
|
1297
|
+
const exitCode = error.exitCode;
|
|
1298
|
+
if (exitCode === 2 || exitCode === 3)
|
|
1299
|
+
return exitCode;
|
|
1300
|
+
}
|
|
1301
|
+
return 1;
|
|
1302
|
+
}
|
|
1303
|
+
function getReviewInspectionResolvedTarget(error) {
|
|
1304
|
+
if (!error || typeof error !== 'object' || !('reviewTarget' in error))
|
|
1305
|
+
return null;
|
|
1306
|
+
const target = error.reviewTarget;
|
|
1307
|
+
if (!target || typeof target !== 'object')
|
|
1308
|
+
return null;
|
|
1309
|
+
const kind = target.kind;
|
|
1310
|
+
const targetPath = target.path;
|
|
1311
|
+
if ((kind !== 'skill' && kind !== 'plugin' && kind !== 'catalog')
|
|
1312
|
+
|| typeof targetPath !== 'string'
|
|
1313
|
+
|| targetPath === '')
|
|
1314
|
+
return null;
|
|
1315
|
+
return { kind, path: targetPath };
|
|
1316
|
+
}
|
|
1317
|
+
export function createReviewInspectionFailure(error, view = 'review', fallbackCode = 'reviewInspectionFailed') {
|
|
1318
|
+
const code = getReviewInspectionErrorCode(error, fallbackCode);
|
|
1319
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1320
|
+
if (view === 'release') {
|
|
1321
|
+
return {
|
|
1322
|
+
schemaVersion: 2,
|
|
1323
|
+
command: 'review:inspect',
|
|
1324
|
+
view,
|
|
1325
|
+
ok: false,
|
|
1326
|
+
completeness: 'incomplete',
|
|
1327
|
+
issues: [{ category: 'invalid', code, message, blocking: true }],
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
904
1330
|
return {
|
|
905
|
-
schemaVersion:
|
|
1331
|
+
schemaVersion: 2,
|
|
906
1332
|
command: 'review:inspect',
|
|
1333
|
+
view,
|
|
907
1334
|
ok: false,
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
message: error instanceof Error ? error.message : String(error),
|
|
913
|
-
blocking: true,
|
|
914
|
-
}],
|
|
1335
|
+
target: getReviewInspectionResolvedTarget(error),
|
|
1336
|
+
sources: [],
|
|
1337
|
+
skills: [],
|
|
1338
|
+
issues: [{ code, path: null, message }],
|
|
915
1339
|
};
|
|
916
1340
|
}
|
|
917
1341
|
//# sourceMappingURL=review-inspection.js.map
|