@lesliechan721/aw 0.1.0-beta.50 → 0.1.0-beta.51
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/lib/skill-scaffold.d.mts +0 -2
- package/dist/authoring/scripts/lib/skill-scaffold.mjs +0 -4
- package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
- package/dist/src/commands/aw-capabilities.d.ts +1 -1
- package/dist/src/commands/aw-capabilities.js +0 -1
- package/dist/src/commands/aw-capabilities.js.map +1 -1
- package/dist/src/commands/review-inspection.d.ts +5 -173
- package/dist/src/commands/review-inspection.js +8 -927
- package/dist/src/commands/review-inspection.js.map +1 -1
- package/dist/src/eval/manifest-v1.d.ts +0 -3
- package/dist/src/eval/manifest-v1.js +4 -21
- package/dist/src/eval/manifest-v1.js.map +1 -1
- package/dist/src/index.js +7 -170
- package/dist/src/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/src/commands/review-publication.d.ts +0 -99
- package/dist/src/commands/review-publication.js +0 -904
- package/dist/src/commands/review-publication.js.map +0 -1
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { canonicalJson,
|
|
3
|
-
import {
|
|
2
|
+
import { canonicalJson, computeSkillDigest } from '@lesliechan721/agent-skill-eval';
|
|
3
|
+
import { DEFAULT_CATALOG_OUTPUT_PATH, GENERATED_OUTPUT_MARKER_FILENAME, validateCatalogSnapshotComplete, } from '@lesliechan721/aw-plugin-core';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
|
-
import { readAwSkillEvalManifest
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { computeReviewComponentDigest } from './review-publication.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';
|
|
5
|
+
import { readAwSkillEvalManifest } from '../eval/manifest-v1.js';
|
|
6
|
+
import { readAwSourceBinding, resolvePortablePluginProfileConfig, validateAwSourceBindingForWorkspace, } from '../review/source-binding.js';
|
|
7
|
+
import { resolveReviewSelection, reviewBindingMatchesSelection, } from '../review/review-selection.js';
|
|
11
8
|
import { resolveEffectivePluginConfigFromStores, } from '../utils/plugin-config.js';
|
|
12
9
|
export class ReviewInspectionInputError extends Error {
|
|
13
10
|
code;
|
|
@@ -31,84 +28,6 @@ class ReviewInspectionResolvedTargetError extends Error {
|
|
|
31
28
|
}
|
|
32
29
|
}
|
|
33
30
|
const compareText = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
34
|
-
function stripDotSlash(value) {
|
|
35
|
-
return value.startsWith('./') ? value.slice(2) : value;
|
|
36
|
-
}
|
|
37
|
-
function relativeWorkspacePath(workspaceRoot, filePath) {
|
|
38
|
-
return path.relative(workspaceRoot, filePath).split(path.sep).join('/') || '.';
|
|
39
|
-
}
|
|
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}`;
|
|
47
|
-
}
|
|
48
|
-
function requiredFinalKey(seed) {
|
|
49
|
-
return [
|
|
50
|
-
'final',
|
|
51
|
-
seed.plugin.manifest.name,
|
|
52
|
-
seed.skill,
|
|
53
|
-
seed.profile,
|
|
54
|
-
seed.targetId ?? 'static',
|
|
55
|
-
].join(':');
|
|
56
|
-
}
|
|
57
|
-
function materializationIdentityKey(options) {
|
|
58
|
-
return [
|
|
59
|
-
options.plugin,
|
|
60
|
-
options.skill,
|
|
61
|
-
options.profile,
|
|
62
|
-
options.materializationProfile.kind,
|
|
63
|
-
options.materializationProfile.id,
|
|
64
|
-
options.materializationProfile.inputDigest,
|
|
65
|
-
].join(':');
|
|
66
|
-
}
|
|
67
|
-
function scopeFromProfile(profile) {
|
|
68
|
-
return profile.endsWith('-project') ? 'project' : 'user';
|
|
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
|
-
}
|
|
77
|
-
async function directSkillDirectories(root) {
|
|
78
|
-
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
79
|
-
const result = [];
|
|
80
|
-
for (const entry of entries.sort((left, right) => compareText(left.name, right.name))) {
|
|
81
|
-
const entryPath = path.join(root, entry.name);
|
|
82
|
-
const stat = await fs.lstat(entryPath);
|
|
83
|
-
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
84
|
-
throw new Error(`Skill collection direct child must be a regular directory: ${entryPath}`);
|
|
85
|
-
}
|
|
86
|
-
result.push({ name: entry.name, root: await fs.realpath(entryPath) });
|
|
87
|
-
}
|
|
88
|
-
return result;
|
|
89
|
-
}
|
|
90
|
-
function pluginImports(plugin) {
|
|
91
|
-
return [
|
|
92
|
-
...(plugin.manifest.buildConfig?.buildImports ?? []).map((entry) => ({
|
|
93
|
-
purpose: 'build',
|
|
94
|
-
provider: entry.plugin,
|
|
95
|
-
export: entry.export,
|
|
96
|
-
consumer: entry.env,
|
|
97
|
-
})),
|
|
98
|
-
...(plugin.manifest.buildConfig?.skillRuntimeFiles ?? []).flatMap((entry) => entry.import ? [{
|
|
99
|
-
purpose: 'skill-runtime-file',
|
|
100
|
-
provider: entry.import.plugin,
|
|
101
|
-
export: entry.import.export,
|
|
102
|
-
consumer: `${entry.skill}/${entry.target}`,
|
|
103
|
-
}] : []),
|
|
104
|
-
...(plugin.manifest.artifacts?.targets ?? []).flatMap((target) => (target.runtimeImports ?? []).map((entry) => ({
|
|
105
|
-
purpose: 'artifact-runtime',
|
|
106
|
-
provider: entry.plugin,
|
|
107
|
-
export: entry.export,
|
|
108
|
-
consumer: `${target.id}/${entry.name}`,
|
|
109
|
-
}))),
|
|
110
|
-
].sort((left, right) => compareText(`${left.purpose}\0${left.consumer}\0${left.provider}\0${left.export}`, `${right.purpose}\0${right.consumer}\0${right.provider}\0${right.export}`));
|
|
111
|
-
}
|
|
112
31
|
async function inspectSnapshot(workspace, workspaceSourceIdentity) {
|
|
113
32
|
const snapshotPath = path.join(workspace.workspaceRoot, DEFAULT_CATALOG_OUTPUT_PATH);
|
|
114
33
|
if (!await fs.pathExists(snapshotPath)) {
|
|
@@ -116,12 +35,7 @@ async function inspectSnapshot(workspace, workspaceSourceIdentity) {
|
|
|
116
35
|
receipt: {
|
|
117
36
|
path: snapshotPath,
|
|
118
37
|
state: 'absent',
|
|
119
|
-
catalogVersion: null,
|
|
120
|
-
snapshotDigest: null,
|
|
121
|
-
workspaceIdentity: null,
|
|
122
|
-
sourceIdentity: null,
|
|
123
38
|
},
|
|
124
|
-
entries: new Map(),
|
|
125
39
|
root: null,
|
|
126
40
|
};
|
|
127
41
|
}
|
|
@@ -136,12 +50,7 @@ async function inspectSnapshot(workspace, workspaceSourceIdentity) {
|
|
|
136
50
|
receipt: {
|
|
137
51
|
path: snapshotPath,
|
|
138
52
|
state: current ? 'current' : 'stale',
|
|
139
|
-
catalogVersion: validation.manifest.catalogVersion,
|
|
140
|
-
snapshotDigest: validation.snapshotDigest,
|
|
141
|
-
workspaceIdentity: marker.workspaceIdentity,
|
|
142
|
-
sourceIdentity: marker.sourceIdentity,
|
|
143
53
|
},
|
|
144
|
-
entries: new Map(validation.manifest.entries.map((entry) => [entry.name, entry])),
|
|
145
54
|
root: current ? validation.rootDir : null,
|
|
146
55
|
};
|
|
147
56
|
}
|
|
@@ -150,497 +59,11 @@ async function inspectSnapshot(workspace, workspaceSourceIdentity) {
|
|
|
150
59
|
receipt: {
|
|
151
60
|
path: snapshotPath,
|
|
152
61
|
state: 'invalid',
|
|
153
|
-
catalogVersion: null,
|
|
154
|
-
snapshotDigest: null,
|
|
155
|
-
workspaceIdentity: null,
|
|
156
|
-
sourceIdentity: null,
|
|
157
62
|
},
|
|
158
|
-
entries: new Map(),
|
|
159
63
|
root: null,
|
|
160
64
|
};
|
|
161
65
|
}
|
|
162
66
|
}
|
|
163
|
-
async function addSourceSubject(options) {
|
|
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
|
-
});
|
|
171
|
-
if (options.subjects.some((subject) => subject.key === key))
|
|
172
|
-
return key;
|
|
173
|
-
const digest = options.kind === 'artifact-source'
|
|
174
|
-
? (await computeReviewComponentDigest({
|
|
175
|
-
repoRoot: options.workspace.workspaceRoot,
|
|
176
|
-
path: options.root,
|
|
177
|
-
})).digest
|
|
178
|
-
: await computeSkillDigest(options.root);
|
|
179
|
-
options.subjects.push({
|
|
180
|
-
key,
|
|
181
|
-
kind: options.kind,
|
|
182
|
-
plugin: options.plugin.manifest.name,
|
|
183
|
-
skill: options.skill,
|
|
184
|
-
path: options.root,
|
|
185
|
-
profile: null,
|
|
186
|
-
digest,
|
|
187
|
-
origin: 'workspace',
|
|
188
|
-
});
|
|
189
|
-
return key;
|
|
190
|
-
}
|
|
191
|
-
async function inventoryPluginSource(options) {
|
|
192
|
-
const { workspace, plugin, subjects } = options;
|
|
193
|
-
const nativeSkills = plugin.manifest.skills
|
|
194
|
-
? await directSkillDirectories(path.join(plugin.rootDir, stripDotSlash(plugin.manifest.skills)))
|
|
195
|
-
: [];
|
|
196
|
-
const portableSkills = plugin.manifest.distributions?.agentSkills?.skills
|
|
197
|
-
? await directSkillDirectories(path.join(plugin.rootDir, stripDotSlash(plugin.manifest.distributions.agentSkills.skills)))
|
|
198
|
-
: [];
|
|
199
|
-
const nativeDynamicByPlatform = new Map([
|
|
200
|
-
['claude', new Set()],
|
|
201
|
-
['codex', new Set()],
|
|
202
|
-
]);
|
|
203
|
-
const portableDynamic = new Set();
|
|
204
|
-
const sourceSubjectKeys = [];
|
|
205
|
-
const seeds = [];
|
|
206
|
-
for (const target of plugin.manifest.artifacts?.targets ?? []) {
|
|
207
|
-
if (!['codex.skill', 'claude.skill', 'agent-skills.skill'].includes(target.target))
|
|
208
|
-
continue;
|
|
209
|
-
const skill = target.skillKey;
|
|
210
|
-
if (target.target === 'agent-skills.skill')
|
|
211
|
-
portableDynamic.add(skill);
|
|
212
|
-
else
|
|
213
|
-
nativeDynamicByPlatform.get(target.target === 'codex.skill' ? 'codex' : 'claude').add(skill);
|
|
214
|
-
}
|
|
215
|
-
for (const skill of nativeSkills) {
|
|
216
|
-
sourceSubjectKeys.push(await addSourceSubject({
|
|
217
|
-
workspace,
|
|
218
|
-
subjects,
|
|
219
|
-
plugin,
|
|
220
|
-
skill: skill.name,
|
|
221
|
-
root: skill.root,
|
|
222
|
-
kind: 'source-skill',
|
|
223
|
-
}));
|
|
224
|
-
}
|
|
225
|
-
for (const skill of portableSkills) {
|
|
226
|
-
sourceSubjectKeys.push(await addSourceSubject({
|
|
227
|
-
workspace,
|
|
228
|
-
subjects,
|
|
229
|
-
plugin,
|
|
230
|
-
skill: skill.name,
|
|
231
|
-
root: skill.root,
|
|
232
|
-
kind: 'portable-source-skill',
|
|
233
|
-
}));
|
|
234
|
-
}
|
|
235
|
-
for (const platform of [...resolveSupportedPlatforms(plugin.manifest)].sort(compareText)) {
|
|
236
|
-
for (const skill of nativeSkills) {
|
|
237
|
-
if (nativeDynamicByPlatform.get(platform).has(skill.name))
|
|
238
|
-
continue;
|
|
239
|
-
for (const scope of ['project', 'user']) {
|
|
240
|
-
seeds.push({
|
|
241
|
-
plugin,
|
|
242
|
-
skill: skill.name,
|
|
243
|
-
profile: `${platform}-${scope}`,
|
|
244
|
-
targetId: null,
|
|
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
|
-
}),
|
|
252
|
-
dynamic: false,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
if (plugin.manifest.distributions?.agentSkills) {
|
|
258
|
-
for (const skill of portableSkills) {
|
|
259
|
-
if (portableDynamic.has(skill.name))
|
|
260
|
-
continue;
|
|
261
|
-
for (const scope of ['project', 'user']) {
|
|
262
|
-
seeds.push({
|
|
263
|
-
plugin,
|
|
264
|
-
skill: skill.name,
|
|
265
|
-
profile: `agent-skills-${scope}`,
|
|
266
|
-
targetId: null,
|
|
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
|
-
}),
|
|
274
|
-
dynamic: false,
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
for (const target of plugin.manifest.artifacts?.targets ?? []) {
|
|
280
|
-
if (!['codex.skill', 'claude.skill', 'agent-skills.skill'].includes(target.target))
|
|
281
|
-
continue;
|
|
282
|
-
const skill = target.skillKey;
|
|
283
|
-
const platform = target.target === 'agent-skills.skill'
|
|
284
|
-
? 'agent-skills'
|
|
285
|
-
: target.target === 'codex.skill' ? 'codex' : 'claude';
|
|
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);
|
|
302
|
-
for (const scope of target.scopes) {
|
|
303
|
-
seeds.push({
|
|
304
|
-
plugin,
|
|
305
|
-
skill,
|
|
306
|
-
profile: `${platform}-${scope}`,
|
|
307
|
-
targetId: target.id,
|
|
308
|
-
sourceSubjectKey,
|
|
309
|
-
dynamic: true,
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
return {
|
|
314
|
-
seeds: seeds.sort((left, right) => compareText(requiredFinalKey(left), requiredFinalKey(right))),
|
|
315
|
-
sourceSubjectKeys: [...new Set(sourceSubjectKeys)].sort(compareText),
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
function releaseCases(suite, document, runs) {
|
|
319
|
-
const splits = new Set(suite.releaseSplits ?? []);
|
|
320
|
-
return document.cases
|
|
321
|
-
.filter((entry) => splits.size === 0 || splits.has('all') || splits.has(entry.split))
|
|
322
|
-
.map((entry) => ({ id: entry.id, split: entry.split, minimumRuns: runs }))
|
|
323
|
-
.sort((left, right) => compareText(left.id, right.id));
|
|
324
|
-
}
|
|
325
|
-
async function inventoryRequiredEvals(options) {
|
|
326
|
-
const required = [];
|
|
327
|
-
const caseDigests = new Map();
|
|
328
|
-
const bySkill = new Map();
|
|
329
|
-
for (const seed of options.seeds) {
|
|
330
|
-
const key = `${seed.plugin.manifest.name}\0${seed.skill}`;
|
|
331
|
-
bySkill.set(key, [...(bySkill.get(key) ?? []), seed]);
|
|
332
|
-
}
|
|
333
|
-
for (const seeds of bySkill.values()) {
|
|
334
|
-
const first = seeds[0];
|
|
335
|
-
const manifestPath = path.join(first.plugin.rootDir, 'skills', first.skill, 'evals', 'manifest.json');
|
|
336
|
-
if (!await fs.pathExists(manifestPath))
|
|
337
|
-
continue;
|
|
338
|
-
let loaded;
|
|
339
|
-
try {
|
|
340
|
-
loaded = await readAwSkillEvalManifest({ plugin: first.plugin, skill: first.skill });
|
|
341
|
-
}
|
|
342
|
-
catch (error) {
|
|
343
|
-
options.issues.push({
|
|
344
|
-
category: 'invalid',
|
|
345
|
-
code: 'evalManifestInvalid',
|
|
346
|
-
message: error instanceof Error ? error.message : String(error),
|
|
347
|
-
blocking: true,
|
|
348
|
-
path: relativeWorkspacePath(options.workspace.workspaceRoot, manifestPath),
|
|
349
|
-
});
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
352
|
-
const requiredSuites = loaded.manifest.suites.filter((suite) => suite.requiredForRelease === true);
|
|
353
|
-
const profileBindings = new Map();
|
|
354
|
-
const invalidProfileIds = new Set();
|
|
355
|
-
const codexSeeds = seeds.filter((seed) => seed.profile.startsWith('codex-'));
|
|
356
|
-
for (const suite of requiredSuites) {
|
|
357
|
-
const accepted = suite.profiles ? new Set(suite.profiles) : null;
|
|
358
|
-
const suiteProfiles = loaded.manifest.profiles.filter((profile) => !accepted || accepted.has(profile.id));
|
|
359
|
-
for (const profile of suiteProfiles) {
|
|
360
|
-
if (!profileBindings.has(profile.id) && !invalidProfileIds.has(profile.id)) {
|
|
361
|
-
try {
|
|
362
|
-
profileBindings.set(profile.id, resolveEvalProfileBindingInput(profile, options.workspace.plugins));
|
|
363
|
-
}
|
|
364
|
-
catch (error) {
|
|
365
|
-
invalidProfileIds.add(profile.id);
|
|
366
|
-
options.issues.push({
|
|
367
|
-
category: 'invalid',
|
|
368
|
-
code: 'evalProfileInvalid',
|
|
369
|
-
message: error instanceof Error ? error.message : String(error),
|
|
370
|
-
blocking: true,
|
|
371
|
-
path: relativeWorkspacePath(options.workspace.workspaceRoot, manifestPath),
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
const profileBinding = profileBindings.get(profile.id);
|
|
376
|
-
if (!profileBinding)
|
|
377
|
-
continue;
|
|
378
|
-
const matchingSeeds = codexSeeds.filter((seed) => scopeFromProfile(seed.profile) === profile.scope);
|
|
379
|
-
if (matchingSeeds.length === 0) {
|
|
380
|
-
options.issues.push({
|
|
381
|
-
category: 'unsupported',
|
|
382
|
-
code: 'requiredEvalTargetUnsupported',
|
|
383
|
-
message: `Required Eval ${first.plugin.manifest.name}/${first.skill}/${suite.id}/${profile.id} has no declared Codex ${profile.scope} final.`,
|
|
384
|
-
blocking: true,
|
|
385
|
-
path: relativeWorkspacePath(options.workspace.workspaceRoot, manifestPath),
|
|
386
|
-
});
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
389
|
-
const document = loaded.casesBySuite.get(suite.id);
|
|
390
|
-
const casesPath = path.join(loaded.evalRoot, suite.cases);
|
|
391
|
-
const runs = suite.runs ?? loaded.manifest.defaults.runs ?? 1;
|
|
392
|
-
const timeoutMs = suite.timeoutMs ?? loaded.manifest.defaults.timeoutMs ?? null;
|
|
393
|
-
let casesDigest = caseDigests.get(casesPath);
|
|
394
|
-
if (!casesDigest) {
|
|
395
|
-
casesDigest = await computeCaseCorpusDigest([casesPath]);
|
|
396
|
-
caseDigests.set(casesPath, casesDigest);
|
|
397
|
-
}
|
|
398
|
-
const minimumPassRate = typeof document.suite.runtime?.minimumPassRate === 'number'
|
|
399
|
-
? document.suite.runtime.minimumPassRate
|
|
400
|
-
: 1;
|
|
401
|
-
const selected = releaseCases(suite, document, runs);
|
|
402
|
-
const selectedDocument = selected.length === 0
|
|
403
|
-
? { ...document, cases: [] }
|
|
404
|
-
: selectAwSourceBindingEvalCases(document, {
|
|
405
|
-
caseIds: selected.map((entry) => entry.id),
|
|
406
|
-
});
|
|
407
|
-
for (const seed of matchingSeeds) {
|
|
408
|
-
const finalKey = requiredFinalKey(seed);
|
|
409
|
-
required.push({
|
|
410
|
-
key: `eval:${finalKey}:${profile.id}:${suite.id}`,
|
|
411
|
-
plugin: first.plugin.manifest.name,
|
|
412
|
-
skill: first.skill,
|
|
413
|
-
finalProfile: seed.profile,
|
|
414
|
-
requiredFinalKey: finalKey,
|
|
415
|
-
evalProfile: {
|
|
416
|
-
id: profile.id,
|
|
417
|
-
scope: profile.scope,
|
|
418
|
-
inputDigest: profileBinding.profile.inputDigest,
|
|
419
|
-
plugins: profileBinding.materializationProfile.plugins
|
|
420
|
-
.map((entry) => entry.name)
|
|
421
|
-
.sort(compareText),
|
|
422
|
-
},
|
|
423
|
-
suite: {
|
|
424
|
-
id: suite.id,
|
|
425
|
-
kind: suite.kind,
|
|
426
|
-
provider: suite.provider ?? null,
|
|
427
|
-
suiteDefaultsDigest: computeAwSourceBindingSuiteDefaultsDigest(suite, loaded.manifest.defaults),
|
|
428
|
-
},
|
|
429
|
-
cases: {
|
|
430
|
-
sourcePath: relativeWorkspacePath(options.workspace.workspaceRoot, casesPath),
|
|
431
|
-
sourceCorpusDigest: casesDigest,
|
|
432
|
-
selectedCasesDigest: computeAwSourceBindingSelectedCasesDigest(selectedDocument),
|
|
433
|
-
selected,
|
|
434
|
-
},
|
|
435
|
-
runs,
|
|
436
|
-
timeoutMs,
|
|
437
|
-
minimumPassRate,
|
|
438
|
-
processManagement: suite.releaseProcessManagement ?? 'managed-only',
|
|
439
|
-
materializedFinalKeys: [],
|
|
440
|
-
verifiedBindingPaths: [],
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
return required.sort((left, right) => compareText(left.key, right.key));
|
|
447
|
-
}
|
|
448
|
-
async function resolveTarget(options) {
|
|
449
|
-
if (options.target === 'catalog') {
|
|
450
|
-
return {
|
|
451
|
-
kind: 'catalog',
|
|
452
|
-
id: options.workspace.workspaceIdentity,
|
|
453
|
-
path: options.workspace.workspaceRoot,
|
|
454
|
-
plugins: options.workspace.plugins,
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
const named = options.workspace.plugins.find((plugin) => plugin.manifest.name === options.target);
|
|
458
|
-
if (named) {
|
|
459
|
-
return { kind: 'plugin', id: named.manifest.name, path: named.rootDir, plugins: [named] };
|
|
460
|
-
}
|
|
461
|
-
const candidate = path.resolve(options.cwd, options.target);
|
|
462
|
-
const stat = await fs.lstat(candidate).catch(() => null);
|
|
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.');
|
|
468
|
-
}
|
|
469
|
-
const real = await fs.realpath(candidate);
|
|
470
|
-
if (real === options.workspace.workspaceRoot) {
|
|
471
|
-
return {
|
|
472
|
-
kind: 'catalog',
|
|
473
|
-
id: options.workspace.workspaceIdentity,
|
|
474
|
-
path: real,
|
|
475
|
-
plugins: options.workspace.plugins,
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
const plugin = options.workspace.plugins.find((entry) => entry.rootDir === real);
|
|
479
|
-
if (!plugin) {
|
|
480
|
-
throw new ReviewInspectionInputError('invalidArguments', 'The release view requires the complete Plugin or Catalog target.');
|
|
481
|
-
}
|
|
482
|
-
return { kind: 'plugin', id: plugin.manifest.name, path: real, plugins: [plugin] };
|
|
483
|
-
}
|
|
484
|
-
async function inspectMaterializations(options) {
|
|
485
|
-
const materializations = [];
|
|
486
|
-
const finals = new Map();
|
|
487
|
-
const targetPluginNames = new Set(options.targetPlugins.map((plugin) => plugin.manifest.name));
|
|
488
|
-
const context = {
|
|
489
|
-
workspace: options.workspace,
|
|
490
|
-
source: {
|
|
491
|
-
kind: 'workspace',
|
|
492
|
-
workspaceIdentity: options.workspace.workspaceIdentity,
|
|
493
|
-
sourceIdentity: options.workspaceSourceIdentity,
|
|
494
|
-
},
|
|
495
|
-
};
|
|
496
|
-
const bindingPaths = [...new Set(options.paths.map((entry) => path.resolve(entry)))].sort(compareText);
|
|
497
|
-
for (const bindingPath of bindingPaths) {
|
|
498
|
-
let parsed = null;
|
|
499
|
-
try {
|
|
500
|
-
parsed = await readAwSourceBinding(bindingPath);
|
|
501
|
-
}
|
|
502
|
-
catch {
|
|
503
|
-
// The validator returns the public typed state and safe issue below.
|
|
504
|
-
}
|
|
505
|
-
const expectedSubject = parsed?.subject ?? {
|
|
506
|
-
plugin: options.targetPlugins[0]?.manifest.name ?? 'unknown',
|
|
507
|
-
};
|
|
508
|
-
const validation = await validateAwSourceBindingForWorkspace({
|
|
509
|
-
bindingPath,
|
|
510
|
-
context,
|
|
511
|
-
expectedSubject,
|
|
512
|
-
cwd: options.workspace.workspaceRoot,
|
|
513
|
-
env: options.env,
|
|
514
|
-
});
|
|
515
|
-
if (validation.state !== 'verified') {
|
|
516
|
-
materializations.push({
|
|
517
|
-
path: bindingPath,
|
|
518
|
-
state: validation.state,
|
|
519
|
-
plugin: validation.binding?.subject.plugin ?? null,
|
|
520
|
-
skill: validation.binding?.subject.skill ?? null,
|
|
521
|
-
profile: validation.binding ? finalProfileFromBinding(validation.binding.profile) : null,
|
|
522
|
-
finalKeys: [],
|
|
523
|
-
verifiedBinding: null,
|
|
524
|
-
issues: validation.issues.map((issue) => issue.message),
|
|
525
|
-
});
|
|
526
|
-
continue;
|
|
527
|
-
}
|
|
528
|
-
const profile = finalProfileFromBinding(validation.binding.profile);
|
|
529
|
-
const finalKeys = [];
|
|
530
|
-
for (const output of validation.outputs) {
|
|
531
|
-
if (output.kind !== 'eval-target'
|
|
532
|
-
&& output.kind !== 'portable-skill'
|
|
533
|
-
&& output.kind !== 'review-skill')
|
|
534
|
-
continue;
|
|
535
|
-
const outputPlugin = output.kind === 'portable-skill' || output.kind === 'review-skill'
|
|
536
|
-
? output.plugin
|
|
537
|
-
: validation.binding.subject.plugin;
|
|
538
|
-
if (!targetPluginNames.has(outputPlugin))
|
|
539
|
-
continue;
|
|
540
|
-
const compatibleRequired = options.requiredFinals.filter((required) => required.plugin === outputPlugin
|
|
541
|
-
&& required.skill === output.skill
|
|
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) {
|
|
551
|
-
options.issues.push({
|
|
552
|
-
category: 'unknown',
|
|
553
|
-
code: 'materializedOutputOwnerUnknown',
|
|
554
|
-
message: `The verified source binding does not contain a selected leaf for ${outputPlugin}/${output.skill}/${profile}.`,
|
|
555
|
-
blocking: false,
|
|
556
|
-
path: output.path,
|
|
557
|
-
});
|
|
558
|
-
continue;
|
|
559
|
-
}
|
|
560
|
-
const identityKey = materializationIdentityKey({
|
|
561
|
-
plugin: outputPlugin,
|
|
562
|
-
skill: output.skill,
|
|
563
|
-
profile,
|
|
564
|
-
materializationProfile: validation.binding.profile,
|
|
565
|
-
});
|
|
566
|
-
const key = [
|
|
567
|
-
'materialized',
|
|
568
|
-
identityKey,
|
|
569
|
-
output.digest,
|
|
570
|
-
].join(':');
|
|
571
|
-
const existingSubject = options.subjects.find((subject) => subject.key === key);
|
|
572
|
-
if (existingSubject) {
|
|
573
|
-
if (compareText(output.path, existingSubject.path) < 0)
|
|
574
|
-
existingSubject.path = output.path;
|
|
575
|
-
}
|
|
576
|
-
else {
|
|
577
|
-
options.subjects.push({
|
|
578
|
-
key,
|
|
579
|
-
kind: 'materialized-skill',
|
|
580
|
-
plugin: outputPlugin,
|
|
581
|
-
skill: output.skill,
|
|
582
|
-
path: output.path,
|
|
583
|
-
profile,
|
|
584
|
-
digest: output.digest,
|
|
585
|
-
origin: 'source-binding',
|
|
586
|
-
materializationProfile: validation.binding.profile,
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
finalKeys.push(key);
|
|
590
|
-
const existingFinal = finals.get(key);
|
|
591
|
-
if (existingFinal) {
|
|
592
|
-
if (compareText(output.path, existingFinal.path) < 0)
|
|
593
|
-
existingFinal.path = output.path;
|
|
594
|
-
}
|
|
595
|
-
else {
|
|
596
|
-
finals.set(key, {
|
|
597
|
-
key,
|
|
598
|
-
plugin: outputPlugin,
|
|
599
|
-
skill: output.skill,
|
|
600
|
-
profile,
|
|
601
|
-
materializationProfile: validation.binding.profile,
|
|
602
|
-
identityKey,
|
|
603
|
-
path: output.path,
|
|
604
|
-
digest: output.digest,
|
|
605
|
-
requiredKeys: compatibleRequired.map((required) => required.key).sort(compareText),
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
const materializationIssues = [];
|
|
610
|
-
if (finalKeys.length === 0) {
|
|
611
|
-
const message = 'The verified source binding does not contribute a final to the selected inspection target.';
|
|
612
|
-
materializationIssues.push(message);
|
|
613
|
-
options.issues.push({
|
|
614
|
-
category: 'unknown',
|
|
615
|
-
code: 'sourceBindingUnused',
|
|
616
|
-
message,
|
|
617
|
-
blocking: false,
|
|
618
|
-
path: bindingPath,
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
materializations.push({
|
|
622
|
-
path: bindingPath,
|
|
623
|
-
state: 'verified',
|
|
624
|
-
plugin: validation.binding.subject.plugin,
|
|
625
|
-
skill: validation.binding.subject.skill ?? null,
|
|
626
|
-
profile,
|
|
627
|
-
finalKeys: [...new Set(finalKeys)].sort(compareText),
|
|
628
|
-
verifiedBinding: {
|
|
629
|
-
source: validation.binding.source,
|
|
630
|
-
subject: validation.binding.subject,
|
|
631
|
-
profile: validation.binding.profile,
|
|
632
|
-
evalInput: validation.binding.evalInput ?? null,
|
|
633
|
-
finalDigest: validation.binding.finalDigest,
|
|
634
|
-
artifacts: [...validation.outputs].sort((left, right) => compareText(`${left.kind}\0${'plugin' in left ? left.plugin : ''}\0${'skill' in left ? left.skill : ''}\0${left.path}`, `${right.kind}\0${'plugin' in right ? right.plugin : ''}\0${'skill' in right ? right.skill : ''}\0${right.path}`)),
|
|
635
|
-
},
|
|
636
|
-
issues: materializationIssues,
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
|
-
return {
|
|
640
|
-
materializations,
|
|
641
|
-
finals: [...finals.values()].sort((left, right) => compareText(left.key, right.key)),
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
67
|
function sameResolvedConfig(left, right) {
|
|
645
68
|
return canonicalJson({ values: left.values, sources: left.sources })
|
|
646
69
|
=== canonicalJson({ values: right.values, sources: right.sources });
|
|
@@ -834,7 +257,7 @@ async function inspectNavigationBindings(options) {
|
|
|
834
257
|
function navigationSkillKey(skill, profile) {
|
|
835
258
|
return `${skill.pluginName}\0${skill.name}\0${profile.platform}\0${profile.scope}`;
|
|
836
259
|
}
|
|
837
|
-
async function
|
|
260
|
+
export async function inspectReviewTarget(options) {
|
|
838
261
|
const selection = await resolveReviewSelection({
|
|
839
262
|
cwd: options.cwd,
|
|
840
263
|
target: options.target,
|
|
@@ -952,338 +375,6 @@ async function inspectNavigationTarget(options) {
|
|
|
952
375
|
});
|
|
953
376
|
}
|
|
954
377
|
}
|
|
955
|
-
async function inspectReleaseTarget(options) {
|
|
956
|
-
const targetCandidate = options.target === 'catalog' ? null : path.resolve(options.cwd, options.target);
|
|
957
|
-
const targetCandidateStat = targetCandidate
|
|
958
|
-
? await fs.lstat(targetCandidate).catch(() => null)
|
|
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
|
-
}
|
|
963
|
-
const resolution = await resolveCatalogWorkspaceRoot({
|
|
964
|
-
cwd: options.workspaceRoot === undefined && targetCandidateStat?.isDirectory()
|
|
965
|
-
? targetCandidate
|
|
966
|
-
: options.cwd,
|
|
967
|
-
...(options.workspaceRoot === undefined ? {} : { explicitRoot: options.workspaceRoot }),
|
|
968
|
-
mode: 'existing',
|
|
969
|
-
});
|
|
970
|
-
const workspace = await readCatalogWorkspace(resolution.workspaceRoot, {
|
|
971
|
-
reservedImports: CLI_RESERVED_IMPORTS,
|
|
972
|
-
});
|
|
973
|
-
const workspaceSourceIdentity = await calculateCatalogWorkspaceSourceIdentity(workspace);
|
|
974
|
-
const [target, snapshot] = await Promise.all([
|
|
975
|
-
resolveTarget({ cwd: options.cwd, target: options.target, workspace }),
|
|
976
|
-
inspectSnapshot(workspace, workspaceSourceIdentity),
|
|
977
|
-
]);
|
|
978
|
-
const issues = [];
|
|
979
|
-
const subjects = [{
|
|
980
|
-
key: 'catalog:source',
|
|
981
|
-
kind: 'catalog-source',
|
|
982
|
-
plugin: null,
|
|
983
|
-
skill: null,
|
|
984
|
-
path: workspace.workspaceRoot,
|
|
985
|
-
profile: null,
|
|
986
|
-
digest: workspaceSourceIdentity,
|
|
987
|
-
origin: 'workspace',
|
|
988
|
-
}];
|
|
989
|
-
if (snapshot.receipt.state === 'current') {
|
|
990
|
-
subjects.push({
|
|
991
|
-
key: 'catalog:snapshot',
|
|
992
|
-
kind: 'catalog-snapshot',
|
|
993
|
-
plugin: null,
|
|
994
|
-
skill: null,
|
|
995
|
-
path: snapshot.receipt.path,
|
|
996
|
-
profile: null,
|
|
997
|
-
digest: snapshot.receipt.snapshotDigest,
|
|
998
|
-
origin: 'catalog-snapshot',
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
else if (snapshot.receipt.state !== 'absent') {
|
|
1002
|
-
issues.push({
|
|
1003
|
-
category: snapshot.receipt.state === 'invalid' ? 'invalid' : 'unknown',
|
|
1004
|
-
code: snapshot.receipt.state === 'invalid' ? 'snapshotInvalid' : 'snapshotStale',
|
|
1005
|
-
message: snapshot.receipt.state === 'invalid'
|
|
1006
|
-
? 'The existing Catalog snapshot is invalid.'
|
|
1007
|
-
: 'The existing Catalog snapshot is not bound to the current Workspace source.',
|
|
1008
|
-
blocking: false,
|
|
1009
|
-
path: relativeWorkspacePath(workspace.workspaceRoot, snapshot.receipt.path),
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
const seeds = [];
|
|
1013
|
-
const pluginInputs = [];
|
|
1014
|
-
for (const plugin of target.plugins) {
|
|
1015
|
-
const sourceContainerKey = `plugin:${plugin.manifest.name}:source`;
|
|
1016
|
-
const component = await computeReviewComponentDigest({
|
|
1017
|
-
repoRoot: workspace.workspaceRoot,
|
|
1018
|
-
path: plugin.rootDir,
|
|
1019
|
-
});
|
|
1020
|
-
subjects.push({
|
|
1021
|
-
key: sourceContainerKey,
|
|
1022
|
-
kind: 'aw-plugin-source',
|
|
1023
|
-
plugin: plugin.manifest.name,
|
|
1024
|
-
skill: null,
|
|
1025
|
-
path: plugin.rootDir,
|
|
1026
|
-
profile: null,
|
|
1027
|
-
digest: component.digest,
|
|
1028
|
-
origin: 'workspace',
|
|
1029
|
-
});
|
|
1030
|
-
const source = await inventoryPluginSource({ workspace, plugin, subjects });
|
|
1031
|
-
seeds.push(...source.seeds);
|
|
1032
|
-
pluginInputs.push({ plugin, sourceContainerKey, sourceSubjectKeys: source.sourceSubjectKeys });
|
|
1033
|
-
}
|
|
1034
|
-
const requiredFinals = seeds.map((seed) => ({
|
|
1035
|
-
key: requiredFinalKey(seed),
|
|
1036
|
-
kind: 'materialized-skill',
|
|
1037
|
-
plugin: seed.plugin.manifest.name,
|
|
1038
|
-
skill: seed.skill,
|
|
1039
|
-
path: null,
|
|
1040
|
-
profile: seed.profile,
|
|
1041
|
-
digest: null,
|
|
1042
|
-
targetId: seed.targetId,
|
|
1043
|
-
sourceSubjectKey: seed.sourceSubjectKey,
|
|
1044
|
-
status: 'missing',
|
|
1045
|
-
matchedFinalKeys: [],
|
|
1046
|
-
}));
|
|
1047
|
-
const requiredByKey = new Map(requiredFinals.map((entry) => [entry.key, entry]));
|
|
1048
|
-
if (snapshot.root) {
|
|
1049
|
-
for (const seed of seeds.filter((entry) => !entry.dynamic && !entry.profile.startsWith('agent-skills-'))) {
|
|
1050
|
-
const platform = seed.profile.startsWith('codex-') ? 'codex' : 'claude';
|
|
1051
|
-
const finalPath = path.join(snapshot.root, 'plugins', seed.plugin.manifest.name, platform, 'skills', seed.skill);
|
|
1052
|
-
try {
|
|
1053
|
-
const digest = await computeSkillDigest(finalPath);
|
|
1054
|
-
const key = `${requiredFinalKey(seed)}:snapshot`;
|
|
1055
|
-
subjects.push({
|
|
1056
|
-
key,
|
|
1057
|
-
kind: 'materialized-skill',
|
|
1058
|
-
plugin: seed.plugin.manifest.name,
|
|
1059
|
-
skill: seed.skill,
|
|
1060
|
-
path: finalPath,
|
|
1061
|
-
profile: seed.profile,
|
|
1062
|
-
digest,
|
|
1063
|
-
origin: 'catalog-snapshot',
|
|
1064
|
-
});
|
|
1065
|
-
const required = requiredByKey.get(requiredFinalKey(seed));
|
|
1066
|
-
required.path = finalPath;
|
|
1067
|
-
required.digest = digest;
|
|
1068
|
-
required.status = 'resolved';
|
|
1069
|
-
required.matchedFinalKeys.push(key);
|
|
1070
|
-
}
|
|
1071
|
-
catch (error) {
|
|
1072
|
-
const required = requiredByKey.get(requiredFinalKey(seed));
|
|
1073
|
-
required.status = 'invalid';
|
|
1074
|
-
issues.push({
|
|
1075
|
-
category: 'invalid',
|
|
1076
|
-
code: 'snapshotFinalInvalid',
|
|
1077
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1078
|
-
blocking: true,
|
|
1079
|
-
subjectKey: required.key,
|
|
1080
|
-
path: relativeWorkspacePath(workspace.workspaceRoot, finalPath),
|
|
1081
|
-
});
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
const binding = await inspectMaterializations({
|
|
1086
|
-
paths: options.materializations ?? [],
|
|
1087
|
-
workspace,
|
|
1088
|
-
workspaceSourceIdentity,
|
|
1089
|
-
targetPlugins: target.plugins,
|
|
1090
|
-
requiredFinals,
|
|
1091
|
-
subjects,
|
|
1092
|
-
issues,
|
|
1093
|
-
env: options.env,
|
|
1094
|
-
});
|
|
1095
|
-
const bindingFinalsByRequiredKey = new Map();
|
|
1096
|
-
for (const final of binding.finals) {
|
|
1097
|
-
const candidates = requiredFinals.filter((required) => required.plugin === final.plugin
|
|
1098
|
-
&& required.skill === final.skill
|
|
1099
|
-
&& required.profile === final.profile
|
|
1100
|
-
&& final.requiredKeys.includes(required.key));
|
|
1101
|
-
if (candidates.length === 1) {
|
|
1102
|
-
const required = candidates[0];
|
|
1103
|
-
bindingFinalsByRequiredKey.set(required.key, [...(bindingFinalsByRequiredKey.get(required.key) ?? []), final]);
|
|
1104
|
-
}
|
|
1105
|
-
else if (candidates.length > 1) {
|
|
1106
|
-
issues.push({
|
|
1107
|
-
category: 'unknown',
|
|
1108
|
-
code: 'materializedFinalAmbiguous',
|
|
1109
|
-
message: `Materialized final ${final.plugin}/${final.skill}/${final.profile} matches multiple declarations.`,
|
|
1110
|
-
blocking: true,
|
|
1111
|
-
subjectKey: final.key,
|
|
1112
|
-
});
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
for (const required of requiredFinals) {
|
|
1116
|
-
const finals = bindingFinalsByRequiredKey.get(required.key) ?? [];
|
|
1117
|
-
const byIdentity = new Map();
|
|
1118
|
-
for (const final of finals) {
|
|
1119
|
-
byIdentity.set(final.identityKey, [...(byIdentity.get(final.identityKey) ?? []), final]);
|
|
1120
|
-
}
|
|
1121
|
-
const ambiguous = [...byIdentity.entries()]
|
|
1122
|
-
.filter(([, entries]) => new Set(entries.map((entry) => entry.digest)).size > 1)
|
|
1123
|
-
.sort(([left], [right]) => compareText(left, right));
|
|
1124
|
-
required.matchedFinalKeys = [...new Set([
|
|
1125
|
-
...required.matchedFinalKeys,
|
|
1126
|
-
...finals.map((final) => final.key),
|
|
1127
|
-
])].sort(compareText);
|
|
1128
|
-
if (ambiguous.length > 0) {
|
|
1129
|
-
required.status = 'unknown';
|
|
1130
|
-
required.path = null;
|
|
1131
|
-
required.digest = null;
|
|
1132
|
-
for (const [identity] of ambiguous) {
|
|
1133
|
-
issues.push({
|
|
1134
|
-
category: 'unknown',
|
|
1135
|
-
code: 'materializedFinalDigestAmbiguous',
|
|
1136
|
-
message: `One materialization identity produced different target digests for ${required.plugin}/${required.skill}/${required.profile}: ${identity}.`,
|
|
1137
|
-
blocking: true,
|
|
1138
|
-
subjectKey: required.key,
|
|
1139
|
-
});
|
|
1140
|
-
}
|
|
1141
|
-
continue;
|
|
1142
|
-
}
|
|
1143
|
-
if (finals.length > 0)
|
|
1144
|
-
required.status = 'resolved';
|
|
1145
|
-
if (required.matchedFinalKeys.length === 1) {
|
|
1146
|
-
const subject = subjects.find((entry) => entry.key === required.matchedFinalKeys[0]);
|
|
1147
|
-
required.path = subject.path;
|
|
1148
|
-
required.digest = subject.digest;
|
|
1149
|
-
}
|
|
1150
|
-
else if (required.matchedFinalKeys.length > 1) {
|
|
1151
|
-
required.path = null;
|
|
1152
|
-
required.digest = null;
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
for (const required of requiredFinals.filter((entry) => entry.status === 'missing')) {
|
|
1156
|
-
issues.push({
|
|
1157
|
-
category: 'missing',
|
|
1158
|
-
code: 'requiredFinalMissing',
|
|
1159
|
-
message: `No current final is available for ${required.plugin}/${required.skill}/${required.profile}.`,
|
|
1160
|
-
blocking: false,
|
|
1161
|
-
subjectKey: required.key,
|
|
1162
|
-
});
|
|
1163
|
-
}
|
|
1164
|
-
const requiredEvals = await inventoryRequiredEvals({ workspace, seeds, issues });
|
|
1165
|
-
for (const required of requiredEvals) {
|
|
1166
|
-
required.materializedFinalKeys = binding.finals
|
|
1167
|
-
.filter((final) => final.plugin === required.plugin
|
|
1168
|
-
&& final.skill === required.skill
|
|
1169
|
-
&& final.profile === required.finalProfile
|
|
1170
|
-
&& final.materializationProfile.kind === 'eval'
|
|
1171
|
-
&& final.materializationProfile.id === required.evalProfile.id
|
|
1172
|
-
&& final.materializationProfile.inputDigest === required.evalProfile.inputDigest)
|
|
1173
|
-
.map((final) => final.key)
|
|
1174
|
-
.sort(compareText);
|
|
1175
|
-
required.verifiedBindingPaths = [...new Set(binding.materializations
|
|
1176
|
-
.filter((materialization) => materialization.state === 'verified'
|
|
1177
|
-
&& materialization.verifiedBinding?.subject.plugin === required.plugin
|
|
1178
|
-
&& materialization.verifiedBinding.subject.skill === required.skill
|
|
1179
|
-
&& materialization.verifiedBinding.subject.suite === required.suite.id
|
|
1180
|
-
&& materialization.verifiedBinding.profile.kind === 'eval'
|
|
1181
|
-
&& materialization.verifiedBinding.profile.id === required.evalProfile.id
|
|
1182
|
-
&& materialization.verifiedBinding.profile.inputDigest === required.evalProfile.inputDigest)
|
|
1183
|
-
.map((materialization) => materialization.path))]
|
|
1184
|
-
.sort(compareText);
|
|
1185
|
-
if (required.materializedFinalKeys.length === 0) {
|
|
1186
|
-
const snapshotFinal = (requiredByKey.get(required.requiredFinalKey)?.matchedFinalKeys ?? [])
|
|
1187
|
-
.filter((key) => subjects.some((subject) => subject.key === key && subject.origin === 'catalog-snapshot'));
|
|
1188
|
-
required.materializedFinalKeys = [...snapshotFinal];
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
const plugins = pluginInputs.map(({ plugin, sourceContainerKey, sourceSubjectKeys }) => {
|
|
1192
|
-
const entry = snapshot.receipt.state === 'current' ? snapshot.entries.get(plugin.manifest.name) : undefined;
|
|
1193
|
-
let finalContainerKey = null;
|
|
1194
|
-
if (entry && snapshot.root) {
|
|
1195
|
-
finalContainerKey = `plugin:${plugin.manifest.name}:snapshot`;
|
|
1196
|
-
subjects.push({
|
|
1197
|
-
key: finalContainerKey,
|
|
1198
|
-
kind: 'aw-plugin',
|
|
1199
|
-
plugin: plugin.manifest.name,
|
|
1200
|
-
skill: null,
|
|
1201
|
-
path: path.join(snapshot.root, 'plugins', plugin.manifest.name),
|
|
1202
|
-
profile: null,
|
|
1203
|
-
digest: entry.artifactDigest,
|
|
1204
|
-
origin: 'catalog-snapshot',
|
|
1205
|
-
});
|
|
1206
|
-
}
|
|
1207
|
-
const pluginFinals = requiredFinals.filter((required) => required.plugin === plugin.manifest.name);
|
|
1208
|
-
const pluginEvals = requiredEvals.filter((required) => required.plugin === plugin.manifest.name);
|
|
1209
|
-
return {
|
|
1210
|
-
id: plugin.manifest.name,
|
|
1211
|
-
name: plugin.manifest.name,
|
|
1212
|
-
version: plugin.manifest.version,
|
|
1213
|
-
path: plugin.rootDir,
|
|
1214
|
-
sourceContainerKey,
|
|
1215
|
-
finalContainerKey,
|
|
1216
|
-
dependencies: [...(plugin.manifest.dependencies ?? [])].sort(compareText),
|
|
1217
|
-
supportedPlatforms: [...resolveSupportedPlatforms(plugin.manifest)].sort(compareText),
|
|
1218
|
-
finalProfiles: [...new Set(pluginFinals.map((entry) => entry.profile))].sort(compareText),
|
|
1219
|
-
imports: pluginImports(plugin),
|
|
1220
|
-
snapshotEntry: entry ? {
|
|
1221
|
-
artifactDigest: entry.artifactDigest,
|
|
1222
|
-
manifestDigest: entry.manifestDigest,
|
|
1223
|
-
contentDigest: entry.contentDigest,
|
|
1224
|
-
} : null,
|
|
1225
|
-
sourceSubjectKeys,
|
|
1226
|
-
requiredFinalKeys: pluginFinals.map((entry) => entry.key).sort(compareText),
|
|
1227
|
-
requiredEvalKeys: pluginEvals.map((entry) => entry.key).sort(compareText),
|
|
1228
|
-
};
|
|
1229
|
-
});
|
|
1230
|
-
issues.push(...binding.materializations
|
|
1231
|
-
.filter((entry) => entry.state !== 'verified')
|
|
1232
|
-
.map((entry) => ({
|
|
1233
|
-
category: entry.state === 'incomplete'
|
|
1234
|
-
? 'missing'
|
|
1235
|
-
: entry.state === 'unsupported' ? 'unsupported' : entry.state === 'drifted' ? 'unknown' : 'invalid',
|
|
1236
|
-
code: `sourceBinding${entry.state[0].toUpperCase()}${entry.state.slice(1)}`,
|
|
1237
|
-
message: entry.issues.join('; ') || `Source binding is ${entry.state}.`,
|
|
1238
|
-
blocking: true,
|
|
1239
|
-
path: entry.path,
|
|
1240
|
-
})));
|
|
1241
|
-
issues.sort((left, right) => compareText(`${left.code}\0${left.subjectKey ?? ''}\0${left.path ?? ''}`, `${right.code}\0${right.subjectKey ?? ''}\0${right.path ?? ''}`));
|
|
1242
|
-
subjects.sort((left, right) => compareText(left.key, right.key));
|
|
1243
|
-
const ok = !issues.some((issue) => issue.blocking);
|
|
1244
|
-
return {
|
|
1245
|
-
schemaVersion: 2,
|
|
1246
|
-
command: 'review:inspect',
|
|
1247
|
-
view: 'release',
|
|
1248
|
-
ok,
|
|
1249
|
-
completeness: ok && requiredFinals.every((entry) => entry.status === 'resolved')
|
|
1250
|
-
? 'complete' : 'incomplete',
|
|
1251
|
-
target: { kind: target.kind, id: target.id, path: target.path },
|
|
1252
|
-
workspace: {
|
|
1253
|
-
root: workspace.workspaceRoot,
|
|
1254
|
-
resolvedFrom: resolution.workspaceRootResolvedFrom,
|
|
1255
|
-
identity: workspace.workspaceIdentity,
|
|
1256
|
-
sourceIdentity: workspaceSourceIdentity,
|
|
1257
|
-
catalogVersion: workspace.metadata.catalog.version,
|
|
1258
|
-
},
|
|
1259
|
-
snapshot: snapshot.receipt,
|
|
1260
|
-
plugins,
|
|
1261
|
-
subjects,
|
|
1262
|
-
requiredFinals,
|
|
1263
|
-
finalMappings: requiredFinals.map((required) => ({
|
|
1264
|
-
requiredFinalKey: required.key,
|
|
1265
|
-
sourceSubjectKey: required.sourceSubjectKey,
|
|
1266
|
-
finalSubjectKeys: [...required.matchedFinalKeys].sort(compareText),
|
|
1267
|
-
status: required.status,
|
|
1268
|
-
})),
|
|
1269
|
-
requiredEvals,
|
|
1270
|
-
materializations: binding.materializations,
|
|
1271
|
-
issues,
|
|
1272
|
-
};
|
|
1273
|
-
}
|
|
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
378
|
export function getReviewInspectionErrorCode(error, fallback = 'reviewInspectionFailed') {
|
|
1288
379
|
if (error && typeof error === 'object' && 'code' in error) {
|
|
1289
380
|
const code = error.code;
|
|
@@ -1314,23 +405,13 @@ function getReviewInspectionResolvedTarget(error) {
|
|
|
1314
405
|
return null;
|
|
1315
406
|
return { kind, path: targetPath };
|
|
1316
407
|
}
|
|
1317
|
-
export function createReviewInspectionFailure(error,
|
|
408
|
+
export function createReviewInspectionFailure(error, fallbackCode = 'reviewInspectionFailed') {
|
|
1318
409
|
const code = getReviewInspectionErrorCode(error, fallbackCode);
|
|
1319
410
|
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
|
-
}
|
|
1330
411
|
return {
|
|
1331
412
|
schemaVersion: 2,
|
|
1332
413
|
command: 'review:inspect',
|
|
1333
|
-
view,
|
|
414
|
+
view: 'review',
|
|
1334
415
|
ok: false,
|
|
1335
416
|
target: getReviewInspectionResolvedTarget(error),
|
|
1336
417
|
sources: [],
|