@chllming/wave-orchestration 0.5.4 → 0.6.1
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/CHANGELOG.md +52 -3
- package/README.md +33 -5
- package/docs/README.md +18 -4
- package/docs/agents/wave-cont-eval-role.md +36 -0
- package/docs/agents/{wave-evaluator-role.md → wave-cont-qa-role.md} +14 -11
- package/docs/agents/wave-documentation-role.md +1 -1
- package/docs/agents/wave-infra-role.md +1 -1
- package/docs/agents/wave-integration-role.md +3 -3
- package/docs/agents/wave-launcher-role.md +4 -3
- package/docs/agents/wave-security-role.md +40 -0
- package/docs/concepts/context7-vs-skills.md +1 -1
- package/docs/concepts/what-is-a-wave.md +56 -6
- package/docs/evals/README.md +166 -0
- package/docs/evals/benchmark-catalog.json +663 -0
- package/docs/guides/author-and-run-waves.md +135 -0
- package/docs/guides/planner.md +5 -0
- package/docs/guides/terminal-surfaces.md +2 -0
- package/docs/plans/component-cutover-matrix.json +1 -1
- package/docs/plans/component-cutover-matrix.md +1 -1
- package/docs/plans/current-state.md +19 -1
- package/docs/plans/examples/wave-example-live-proof.md +435 -0
- package/docs/plans/migration.md +42 -0
- package/docs/plans/wave-orchestrator.md +46 -7
- package/docs/plans/waves/wave-0.md +4 -4
- package/docs/reference/live-proof-waves.md +177 -0
- package/docs/reference/migration-0.2-to-0.5.md +26 -19
- package/docs/reference/npmjs-trusted-publishing.md +6 -5
- package/docs/reference/runtime-config/README.md +14 -4
- package/docs/reference/sample-waves.md +87 -0
- package/docs/reference/skills.md +110 -42
- package/docs/research/agent-context-sources.md +130 -11
- package/docs/research/coordination-failure-review.md +266 -0
- package/docs/roadmap.md +6 -2
- package/package.json +2 -2
- package/releases/manifest.json +35 -2
- package/scripts/research/agent-context-archive.mjs +83 -1
- package/scripts/research/manifests/agent-context-expanded-2026-03-22.mjs +811 -0
- package/scripts/wave-orchestrator/adhoc.mjs +1331 -0
- package/scripts/wave-orchestrator/agent-state.mjs +358 -6
- package/scripts/wave-orchestrator/artifact-schemas.mjs +173 -0
- package/scripts/wave-orchestrator/clarification-triage.mjs +10 -3
- package/scripts/wave-orchestrator/config.mjs +48 -12
- package/scripts/wave-orchestrator/context7.mjs +2 -0
- package/scripts/wave-orchestrator/coord-cli.mjs +51 -19
- package/scripts/wave-orchestrator/coordination-store.mjs +26 -4
- package/scripts/wave-orchestrator/coordination.mjs +83 -9
- package/scripts/wave-orchestrator/dashboard-state.mjs +20 -8
- package/scripts/wave-orchestrator/dep-cli.mjs +5 -2
- package/scripts/wave-orchestrator/docs-queue.mjs +8 -2
- package/scripts/wave-orchestrator/evals.mjs +451 -0
- package/scripts/wave-orchestrator/feedback.mjs +15 -1
- package/scripts/wave-orchestrator/install.mjs +32 -9
- package/scripts/wave-orchestrator/launcher-closure.mjs +281 -0
- package/scripts/wave-orchestrator/launcher-runtime.mjs +334 -0
- package/scripts/wave-orchestrator/launcher.mjs +709 -601
- package/scripts/wave-orchestrator/ledger.mjs +123 -20
- package/scripts/wave-orchestrator/local-executor.mjs +99 -12
- package/scripts/wave-orchestrator/planner.mjs +177 -42
- package/scripts/wave-orchestrator/replay.mjs +6 -3
- package/scripts/wave-orchestrator/role-helpers.mjs +84 -0
- package/scripts/wave-orchestrator/shared.mjs +75 -11
- package/scripts/wave-orchestrator/skills.mjs +637 -106
- package/scripts/wave-orchestrator/traces.mjs +71 -48
- package/scripts/wave-orchestrator/wave-files.mjs +947 -101
- package/scripts/wave.mjs +9 -0
- package/skills/README.md +202 -0
- package/skills/provider-aws/SKILL.md +111 -0
- package/skills/provider-aws/adapters/claude.md +1 -0
- package/skills/provider-aws/adapters/codex.md +1 -0
- package/skills/provider-aws/references/service-verification.md +39 -0
- package/skills/provider-aws/skill.json +50 -1
- package/skills/provider-custom-deploy/SKILL.md +59 -0
- package/skills/provider-custom-deploy/skill.json +46 -1
- package/skills/provider-docker-compose/SKILL.md +90 -0
- package/skills/provider-docker-compose/adapters/local.md +1 -0
- package/skills/provider-docker-compose/skill.json +49 -1
- package/skills/provider-github-release/SKILL.md +116 -1
- package/skills/provider-github-release/adapters/claude.md +1 -0
- package/skills/provider-github-release/adapters/codex.md +1 -0
- package/skills/provider-github-release/skill.json +51 -1
- package/skills/provider-kubernetes/SKILL.md +137 -0
- package/skills/provider-kubernetes/adapters/claude.md +1 -0
- package/skills/provider-kubernetes/adapters/codex.md +1 -0
- package/skills/provider-kubernetes/references/kubectl-patterns.md +58 -0
- package/skills/provider-kubernetes/skill.json +48 -1
- package/skills/provider-railway/SKILL.md +118 -1
- package/skills/provider-railway/references/verification-commands.md +39 -0
- package/skills/provider-railway/skill.json +67 -1
- package/skills/provider-ssh-manual/SKILL.md +91 -0
- package/skills/provider-ssh-manual/skill.json +50 -1
- package/skills/repo-coding-rules/SKILL.md +84 -0
- package/skills/repo-coding-rules/skill.json +30 -1
- package/skills/role-cont-eval/SKILL.md +90 -0
- package/skills/role-cont-eval/adapters/codex.md +1 -0
- package/skills/role-cont-eval/skill.json +36 -0
- package/skills/role-cont-qa/SKILL.md +93 -0
- package/skills/role-cont-qa/adapters/claude.md +1 -0
- package/skills/role-cont-qa/skill.json +36 -0
- package/skills/role-deploy/SKILL.md +90 -0
- package/skills/role-deploy/skill.json +32 -1
- package/skills/role-documentation/SKILL.md +66 -0
- package/skills/role-documentation/skill.json +32 -1
- package/skills/role-implementation/SKILL.md +62 -0
- package/skills/role-implementation/skill.json +32 -1
- package/skills/role-infra/SKILL.md +74 -0
- package/skills/role-infra/skill.json +32 -1
- package/skills/role-integration/SKILL.md +79 -1
- package/skills/role-integration/skill.json +32 -1
- package/skills/role-research/SKILL.md +58 -0
- package/skills/role-research/skill.json +32 -1
- package/skills/role-security/SKILL.md +60 -0
- package/skills/role-security/skill.json +36 -0
- package/skills/runtime-claude/SKILL.md +60 -1
- package/skills/runtime-claude/skill.json +32 -1
- package/skills/runtime-codex/SKILL.md +52 -1
- package/skills/runtime-codex/skill.json +32 -1
- package/skills/runtime-local/SKILL.md +39 -0
- package/skills/runtime-local/skill.json +32 -1
- package/skills/runtime-opencode/SKILL.md +51 -0
- package/skills/runtime-opencode/skill.json +32 -1
- package/skills/wave-core/SKILL.md +107 -0
- package/skills/wave-core/references/marker-syntax.md +62 -0
- package/skills/wave-core/skill.json +31 -1
- package/wave.config.json +35 -6
- package/skills/role-evaluator/SKILL.md +0 -6
- package/skills/role-evaluator/skill.json +0 -5
|
@@ -8,6 +8,27 @@ const REPO_ROOT = WORKSPACE_ROOT;
|
|
|
8
8
|
export const DEFAULT_SKILLS_DIR = "skills";
|
|
9
9
|
export const SKILL_ID_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
|
|
10
10
|
export const SUPPORTED_SKILL_RUNTIMES = ["codex", "claude", "opencode", "local"];
|
|
11
|
+
export const SUPPORTED_SKILL_ROLES = [
|
|
12
|
+
"implementation",
|
|
13
|
+
"integration",
|
|
14
|
+
"documentation",
|
|
15
|
+
"cont-qa",
|
|
16
|
+
"cont-eval",
|
|
17
|
+
"security",
|
|
18
|
+
"infra",
|
|
19
|
+
"deploy",
|
|
20
|
+
"research",
|
|
21
|
+
];
|
|
22
|
+
export const SUPPORTED_SKILL_DEPLOY_KINDS = [
|
|
23
|
+
"railway-cli",
|
|
24
|
+
"railway-mcp",
|
|
25
|
+
"docker-compose",
|
|
26
|
+
"kubernetes",
|
|
27
|
+
"ssh-manual",
|
|
28
|
+
"custom",
|
|
29
|
+
"aws",
|
|
30
|
+
"github-release",
|
|
31
|
+
];
|
|
11
32
|
|
|
12
33
|
function cleanText(value) {
|
|
13
34
|
return String(value ?? "").trim();
|
|
@@ -51,16 +72,130 @@ function normalizeRepoRelativePath(value, label) {
|
|
|
51
72
|
return raw;
|
|
52
73
|
}
|
|
53
74
|
|
|
54
|
-
function uniqueStrings(values) {
|
|
75
|
+
function uniqueStrings(values, options = {}) {
|
|
76
|
+
const lowerCase = options.lowerCase !== false;
|
|
55
77
|
return Array.from(
|
|
56
78
|
new Set(
|
|
57
79
|
(Array.isArray(values) ? values : [])
|
|
58
|
-
.map((value) =>
|
|
80
|
+
.map((value) => {
|
|
81
|
+
const normalized = cleanText(value);
|
|
82
|
+
return lowerCase ? normalized.toLowerCase() : normalized;
|
|
83
|
+
})
|
|
59
84
|
.filter(Boolean),
|
|
60
85
|
),
|
|
61
86
|
);
|
|
62
87
|
}
|
|
63
88
|
|
|
89
|
+
function listFilesRecursively(rootDir) {
|
|
90
|
+
if (!fs.existsSync(rootDir)) {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
const files = [];
|
|
94
|
+
const visit = (targetDir) => {
|
|
95
|
+
for (const entry of fs.readdirSync(targetDir, { withFileTypes: true })) {
|
|
96
|
+
const fullPath = path.join(targetDir, entry.name);
|
|
97
|
+
if (entry.isDirectory()) {
|
|
98
|
+
visit(fullPath);
|
|
99
|
+
} else {
|
|
100
|
+
files.push(fullPath);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
visit(rootDir);
|
|
105
|
+
return files.toSorted();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function repoRelativePath(filePath) {
|
|
109
|
+
return path.relative(REPO_ROOT, filePath).replaceAll(path.sep, "/");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function readJsonObject(filePath, label) {
|
|
113
|
+
if (!fs.existsSync(filePath)) {
|
|
114
|
+
throw new Error(`${label} not found: ${repoRelativePath(filePath)}`);
|
|
115
|
+
}
|
|
116
|
+
let parsed;
|
|
117
|
+
try {
|
|
118
|
+
parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
119
|
+
} catch (error) {
|
|
120
|
+
throw new Error(`Invalid ${label} JSON at ${repoRelativePath(filePath)}: ${error.message}`);
|
|
121
|
+
}
|
|
122
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
123
|
+
throw new Error(`${label} must be a JSON object: ${repoRelativePath(filePath)}`);
|
|
124
|
+
}
|
|
125
|
+
return parsed;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function normalizeRequiredText(value, label) {
|
|
129
|
+
const normalized = cleanText(value);
|
|
130
|
+
if (!normalized) {
|
|
131
|
+
throw new Error(`${label} is required`);
|
|
132
|
+
}
|
|
133
|
+
return normalized;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function normalizeOptionalText(value) {
|
|
137
|
+
const normalized = cleanText(value);
|
|
138
|
+
return normalized || null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function normalizeChoiceArray(values, label, allowedValues) {
|
|
142
|
+
if (values === undefined || values === null || values === "") {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
if (!Array.isArray(values)) {
|
|
146
|
+
throw new Error(`${label} must be an array`);
|
|
147
|
+
}
|
|
148
|
+
const normalized = uniqueStrings(values);
|
|
149
|
+
const allowed = new Set(allowedValues);
|
|
150
|
+
for (const value of normalized) {
|
|
151
|
+
if (!allowed.has(value)) {
|
|
152
|
+
throw new Error(`${label} contains unsupported value "${value}"`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return normalized;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function normalizeStringArray(values, label) {
|
|
159
|
+
if (values === undefined || values === null || values === "") {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
if (!Array.isArray(values)) {
|
|
163
|
+
throw new Error(`${label} must be an array`);
|
|
164
|
+
}
|
|
165
|
+
return uniqueStrings(values, { lowerCase: false });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function normalizeDeployKind(value, label = "deploy kind") {
|
|
169
|
+
const normalized = cleanText(value).toLowerCase();
|
|
170
|
+
if (!SKILL_ID_REGEX.test(normalized)) {
|
|
171
|
+
throw new Error(`${label} must match ${SKILL_ID_REGEX}`);
|
|
172
|
+
}
|
|
173
|
+
return normalized;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function normalizeDeployKindArray(values, label, options = {}) {
|
|
177
|
+
if (values === undefined || values === null || values === "") {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
if (!Array.isArray(values)) {
|
|
181
|
+
throw new Error(`${label} must be an array`);
|
|
182
|
+
}
|
|
183
|
+
const normalized = uniqueStrings(
|
|
184
|
+
values.map((value, index) => normalizeDeployKind(value, `${label}[${index}]`)),
|
|
185
|
+
);
|
|
186
|
+
const allowedValues = Array.isArray(options.allowedValues) ? options.allowedValues : [];
|
|
187
|
+
if (allowedValues.length === 0) {
|
|
188
|
+
return normalized;
|
|
189
|
+
}
|
|
190
|
+
const allowed = new Set(allowedValues.map((value) => normalizeDeployKind(value, label)));
|
|
191
|
+
for (const value of normalized) {
|
|
192
|
+
if (!allowed.has(value)) {
|
|
193
|
+
throw new Error(`${label} contains unsupported value "${value}"`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return normalized;
|
|
197
|
+
}
|
|
198
|
+
|
|
64
199
|
export function normalizeSkillId(value, label = "skill id") {
|
|
65
200
|
const normalized = cleanText(value).toLowerCase();
|
|
66
201
|
if (!SKILL_ID_REGEX.test(normalized)) {
|
|
@@ -81,18 +216,197 @@ export function normalizeSkillIdArray(values, label = "skills") {
|
|
|
81
216
|
);
|
|
82
217
|
}
|
|
83
218
|
|
|
84
|
-
function normalizeSkillMap(rawMap = {}, label) {
|
|
219
|
+
function normalizeSkillMap(rawMap = {}, label, options = {}) {
|
|
85
220
|
if (!rawMap || typeof rawMap !== "object" || Array.isArray(rawMap)) {
|
|
86
221
|
return {};
|
|
87
222
|
}
|
|
223
|
+
const allowedKeys = options.allowedKeys ? new Set(options.allowedKeys) : null;
|
|
88
224
|
return Object.fromEntries(
|
|
89
|
-
Object.entries(rawMap).map(([key, values]) =>
|
|
90
|
-
cleanText(key).toLowerCase()
|
|
91
|
-
|
|
92
|
-
|
|
225
|
+
Object.entries(rawMap).map(([key, values]) => {
|
|
226
|
+
const normalizedKey = cleanText(key).toLowerCase();
|
|
227
|
+
if (!normalizedKey) {
|
|
228
|
+
throw new Error(`${label} keys must be non-empty`);
|
|
229
|
+
}
|
|
230
|
+
if (allowedKeys && !allowedKeys.has(normalizedKey)) {
|
|
231
|
+
throw new Error(`${label}.${key} is not a supported selector key`);
|
|
232
|
+
}
|
|
233
|
+
return [normalizedKey, normalizeSkillIdArray(values, `${label}.${key}`)];
|
|
234
|
+
}),
|
|
93
235
|
);
|
|
94
236
|
}
|
|
95
237
|
|
|
238
|
+
function normalizeSkillActivation(rawActivation, label) {
|
|
239
|
+
if (!rawActivation || typeof rawActivation !== "object" || Array.isArray(rawActivation)) {
|
|
240
|
+
throw new Error(`${label} is required and must be an object`);
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
when: normalizeRequiredText(rawActivation.when, `${label}.when`),
|
|
244
|
+
roles: normalizeChoiceArray(rawActivation.roles, `${label}.roles`, SUPPORTED_SKILL_ROLES),
|
|
245
|
+
runtimes: normalizeChoiceArray(
|
|
246
|
+
rawActivation.runtimes,
|
|
247
|
+
`${label}.runtimes`,
|
|
248
|
+
SUPPORTED_SKILL_RUNTIMES,
|
|
249
|
+
),
|
|
250
|
+
deployKinds: normalizeDeployKindArray(rawActivation.deployKinds, `${label}.deployKinds`),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function normalizeSkillTermination(rawTermination, label) {
|
|
255
|
+
if (rawTermination === undefined || rawTermination === null || rawTermination === "") {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
if (typeof rawTermination === "string") {
|
|
259
|
+
return {
|
|
260
|
+
when: normalizeRequiredText(rawTermination, label),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (!rawTermination || typeof rawTermination !== "object" || Array.isArray(rawTermination)) {
|
|
264
|
+
throw new Error(`${label} must be a string or an object`);
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
when: normalizeRequiredText(rawTermination.when, `${label}.when`),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function normalizeSkillPermissions(rawPermissions, label) {
|
|
272
|
+
if (rawPermissions === undefined || rawPermissions === null || rawPermissions === "") {
|
|
273
|
+
return {
|
|
274
|
+
network: [],
|
|
275
|
+
shell: [],
|
|
276
|
+
mcpServers: [],
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
if (!rawPermissions || typeof rawPermissions !== "object" || Array.isArray(rawPermissions)) {
|
|
280
|
+
throw new Error(`${label} must be an object`);
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
network: normalizeStringArray(rawPermissions.network, `${label}.network`),
|
|
284
|
+
shell: normalizeStringArray(rawPermissions.shell, `${label}.shell`),
|
|
285
|
+
mcpServers: normalizeStringArray(rawPermissions.mcpServers, `${label}.mcpServers`),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function normalizeTrustTier(value, label) {
|
|
290
|
+
const normalized = cleanText(value).toLowerCase();
|
|
291
|
+
if (!normalized) {
|
|
292
|
+
throw new Error(`${label} is required`);
|
|
293
|
+
}
|
|
294
|
+
if (!SKILL_ID_REGEX.test(normalized)) {
|
|
295
|
+
throw new Error(`${label} must match ${SKILL_ID_REGEX}`);
|
|
296
|
+
}
|
|
297
|
+
return normalized;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function normalizeSkillTrust(rawTrust, label) {
|
|
301
|
+
if (rawTrust === undefined || rawTrust === null || rawTrust === "") {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
if (typeof rawTrust === "string") {
|
|
305
|
+
return {
|
|
306
|
+
tier: normalizeTrustTier(rawTrust, label),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
if (!rawTrust || typeof rawTrust !== "object" || Array.isArray(rawTrust)) {
|
|
310
|
+
throw new Error(`${label} must be a string or an object`);
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
tier: normalizeTrustTier(rawTrust.tier, `${label}.tier`),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function normalizeSkillEvalCase(rawEvalCase, label) {
|
|
318
|
+
if (!rawEvalCase || typeof rawEvalCase !== "object" || Array.isArray(rawEvalCase)) {
|
|
319
|
+
throw new Error(`${label} must be an object`);
|
|
320
|
+
}
|
|
321
|
+
const expectActive = rawEvalCase.expectActive;
|
|
322
|
+
if (typeof expectActive !== "boolean") {
|
|
323
|
+
throw new Error(`${label}.expectActive must be a boolean`);
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
id: normalizeSkillId(rawEvalCase.id, `${label}.id`),
|
|
327
|
+
role: normalizeChoiceArray(
|
|
328
|
+
[normalizeRequiredText(rawEvalCase.role, `${label}.role`)],
|
|
329
|
+
`${label}.role`,
|
|
330
|
+
SUPPORTED_SKILL_ROLES,
|
|
331
|
+
)[0],
|
|
332
|
+
runtime: normalizeChoiceArray(
|
|
333
|
+
[normalizeRequiredText(rawEvalCase.runtime, `${label}.runtime`)],
|
|
334
|
+
`${label}.runtime`,
|
|
335
|
+
SUPPORTED_SKILL_RUNTIMES,
|
|
336
|
+
)[0],
|
|
337
|
+
deployKind:
|
|
338
|
+
rawEvalCase.deployKind === undefined || rawEvalCase.deployKind === null || rawEvalCase.deployKind === ""
|
|
339
|
+
? null
|
|
340
|
+
: normalizeDeployKind(rawEvalCase.deployKind, `${label}.deployKind`),
|
|
341
|
+
expectActive,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function normalizeSkillEvalCaseArray(rawEvalCases, label) {
|
|
346
|
+
if (rawEvalCases === undefined || rawEvalCases === null || rawEvalCases === "") {
|
|
347
|
+
return [];
|
|
348
|
+
}
|
|
349
|
+
if (!Array.isArray(rawEvalCases)) {
|
|
350
|
+
throw new Error(`${label} must be an array`);
|
|
351
|
+
}
|
|
352
|
+
return rawEvalCases.map((entry, index) =>
|
|
353
|
+
normalizeSkillEvalCase(entry, `${label}[${index}]`),
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function normalizeSkillManifest(rawManifest, normalizedSkillId) {
|
|
358
|
+
const label = `skills.${normalizedSkillId}`;
|
|
359
|
+
return {
|
|
360
|
+
id: normalizeSkillId(rawManifest.id || normalizedSkillId, `${label}.id`),
|
|
361
|
+
title: normalizeRequiredText(rawManifest.title, `${label}.title`),
|
|
362
|
+
description: normalizeRequiredText(
|
|
363
|
+
rawManifest.description || rawManifest.summary,
|
|
364
|
+
`${label}.description`,
|
|
365
|
+
),
|
|
366
|
+
version: normalizeOptionalText(rawManifest.version),
|
|
367
|
+
tags: normalizeStringArray(rawManifest.tags, `${label}.tags`),
|
|
368
|
+
activation: normalizeSkillActivation(rawManifest.activation, `${label}.activation`),
|
|
369
|
+
termination: normalizeSkillTermination(rawManifest.termination, `${label}.termination`),
|
|
370
|
+
permissions: normalizeSkillPermissions(rawManifest.permissions, `${label}.permissions`),
|
|
371
|
+
trust: normalizeSkillTrust(rawManifest.trust, `${label}.trust`),
|
|
372
|
+
evalCases: normalizeSkillEvalCaseArray(rawManifest.evalCases, `${label}.evalCases`),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function formatScopeList(values, fallback = "any") {
|
|
377
|
+
return Array.isArray(values) && values.length > 0 ? values.join(", ") : fallback;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function formatPermissionSummary(permissions) {
|
|
381
|
+
const segments = [];
|
|
382
|
+
if (permissions.network.length > 0) {
|
|
383
|
+
segments.push(`network=${permissions.network.join(", ")}`);
|
|
384
|
+
}
|
|
385
|
+
if (permissions.shell.length > 0) {
|
|
386
|
+
segments.push(`shell=${permissions.shell.join(", ")}`);
|
|
387
|
+
}
|
|
388
|
+
if (permissions.mcpServers.length > 0) {
|
|
389
|
+
segments.push(`mcp=${permissions.mcpServers.join(", ")}`);
|
|
390
|
+
}
|
|
391
|
+
return segments.length > 0 ? segments.join("; ") : "none declared";
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function resolveSkillBundleDir(skillsDir, skillId) {
|
|
395
|
+
return path.join(REPO_ROOT, skillsDir, skillId);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function listSkillBundleIds(skillsDir) {
|
|
399
|
+
const skillsRoot = path.join(REPO_ROOT, skillsDir);
|
|
400
|
+
if (!fs.existsSync(skillsRoot) || !fs.statSync(skillsRoot).isDirectory()) {
|
|
401
|
+
return [];
|
|
402
|
+
}
|
|
403
|
+
return fs
|
|
404
|
+
.readdirSync(skillsRoot, { withFileTypes: true })
|
|
405
|
+
.filter((entry) => entry.isDirectory() && SKILL_ID_REGEX.test(entry.name))
|
|
406
|
+
.map((entry) => entry.name)
|
|
407
|
+
.toSorted();
|
|
408
|
+
}
|
|
409
|
+
|
|
96
410
|
export function emptySkillsConfig() {
|
|
97
411
|
return {
|
|
98
412
|
dir: DEFAULT_SKILLS_DIR,
|
|
@@ -106,17 +420,30 @@ export function emptySkillsConfig() {
|
|
|
106
420
|
export function normalizeSkillsConfig(rawSkills = {}, label = "skills", options = {}) {
|
|
107
421
|
const skills =
|
|
108
422
|
rawSkills && typeof rawSkills === "object" && !Array.isArray(rawSkills) ? rawSkills : {};
|
|
423
|
+
if (
|
|
424
|
+
skills.byRole &&
|
|
425
|
+
typeof skills.byRole === "object" &&
|
|
426
|
+
!Array.isArray(skills.byRole) &&
|
|
427
|
+
Object.keys(skills.byRole).some((key) => cleanText(key).toLowerCase() === "evaluator")
|
|
428
|
+
) {
|
|
429
|
+
throw new Error(`${label}.byRole.evaluator was renamed to ${label}.byRole.cont-qa`);
|
|
430
|
+
}
|
|
109
431
|
const dir =
|
|
110
432
|
Object.prototype.hasOwnProperty.call(skills, "dir")
|
|
111
433
|
? normalizeRepoRelativePath(skills.dir || DEFAULT_SKILLS_DIR, `${label}.dir`)
|
|
112
434
|
: options.preserveOmittedDir
|
|
113
435
|
? null
|
|
114
436
|
: DEFAULT_SKILLS_DIR;
|
|
437
|
+
const byRole = normalizeSkillMap(skills.byRole, `${label}.byRole`, {
|
|
438
|
+
allowedKeys: SUPPORTED_SKILL_ROLES,
|
|
439
|
+
});
|
|
115
440
|
return {
|
|
116
441
|
dir,
|
|
117
442
|
base: normalizeSkillIdArray(skills.base, `${label}.base`),
|
|
118
|
-
byRole
|
|
119
|
-
byRuntime: normalizeSkillMap(skills.byRuntime, `${label}.byRuntime
|
|
443
|
+
byRole,
|
|
444
|
+
byRuntime: normalizeSkillMap(skills.byRuntime, `${label}.byRuntime`, {
|
|
445
|
+
allowedKeys: SUPPORTED_SKILL_RUNTIMES,
|
|
446
|
+
}),
|
|
120
447
|
byDeployKind: normalizeSkillMap(skills.byDeployKind, `${label}.byDeployKind`),
|
|
121
448
|
};
|
|
122
449
|
}
|
|
@@ -129,11 +456,14 @@ function mergeSkillMaps(baseMap = {}, overrideMap = {}) {
|
|
|
129
456
|
);
|
|
130
457
|
}
|
|
131
458
|
|
|
132
|
-
export function mergeSkillsConfig(
|
|
459
|
+
export function mergeSkillsConfig(
|
|
460
|
+
baseSkills = emptySkillsConfig(),
|
|
461
|
+
overrideSkills = emptySkillsConfig(),
|
|
462
|
+
) {
|
|
133
463
|
return {
|
|
134
464
|
dir: overrideSkills.dir || baseSkills.dir || DEFAULT_SKILLS_DIR,
|
|
135
|
-
base: uniqueStrings([...(baseSkills.base || []), ...(overrideSkills.base || [])]).map(
|
|
136
|
-
normalizeSkillId(skillId),
|
|
465
|
+
base: uniqueStrings([...(baseSkills.base || []), ...(overrideSkills.base || [])]).map(
|
|
466
|
+
(skillId) => normalizeSkillId(skillId),
|
|
137
467
|
),
|
|
138
468
|
byRole: mergeSkillMaps(baseSkills.byRole, overrideSkills.byRole),
|
|
139
469
|
byRuntime: mergeSkillMaps(baseSkills.byRuntime, overrideSkills.byRuntime),
|
|
@@ -141,55 +471,54 @@ export function mergeSkillsConfig(baseSkills = emptySkillsConfig(), overrideSkil
|
|
|
141
471
|
};
|
|
142
472
|
}
|
|
143
473
|
|
|
144
|
-
function
|
|
145
|
-
if (!
|
|
146
|
-
return
|
|
474
|
+
export function skillMatchesActivation(bundle, context = {}) {
|
|
475
|
+
if (!bundle?.activation) {
|
|
476
|
+
return true;
|
|
147
477
|
}
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
visit(fullPath);
|
|
154
|
-
} else {
|
|
155
|
-
files.push(fullPath);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
visit(rootDir);
|
|
160
|
-
return files.toSorted();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function repoRelativePath(filePath) {
|
|
164
|
-
return path.relative(REPO_ROOT, filePath).replaceAll(path.sep, "/");
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function readJsonObject(filePath, label) {
|
|
168
|
-
if (!fs.existsSync(filePath)) {
|
|
169
|
-
throw new Error(`${label} not found: ${repoRelativePath(filePath)}`);
|
|
478
|
+
const role = cleanText(context.role).toLowerCase() || null;
|
|
479
|
+
const runtimeId = cleanText(context.runtimeId).toLowerCase() || null;
|
|
480
|
+
const deployKind = cleanText(context.deployKind).toLowerCase() || null;
|
|
481
|
+
if (bundle.activation.roles.length > 0 && (!role || !bundle.activation.roles.includes(role))) {
|
|
482
|
+
return false;
|
|
170
483
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
484
|
+
if (
|
|
485
|
+
bundle.activation.runtimes.length > 0 &&
|
|
486
|
+
(!runtimeId || !bundle.activation.runtimes.includes(runtimeId))
|
|
487
|
+
) {
|
|
488
|
+
return false;
|
|
176
489
|
}
|
|
177
|
-
if (
|
|
178
|
-
|
|
490
|
+
if (
|
|
491
|
+
bundle.activation.deployKinds.length > 0 &&
|
|
492
|
+
(!deployKind || !bundle.activation.deployKinds.includes(deployKind))
|
|
493
|
+
) {
|
|
494
|
+
return false;
|
|
179
495
|
}
|
|
180
|
-
return
|
|
496
|
+
return true;
|
|
181
497
|
}
|
|
182
498
|
|
|
183
|
-
function
|
|
184
|
-
|
|
499
|
+
export function evaluateSkillBundleCases(bundle) {
|
|
500
|
+
const errors = [];
|
|
501
|
+
for (const evalCase of bundle.evalCases || []) {
|
|
502
|
+
const actual = skillMatchesActivation(bundle, {
|
|
503
|
+
role: evalCase.role,
|
|
504
|
+
runtimeId: evalCase.runtime,
|
|
505
|
+
deployKind: evalCase.deployKind,
|
|
506
|
+
});
|
|
507
|
+
if (actual !== evalCase.expectActive) {
|
|
508
|
+
errors.push(
|
|
509
|
+
`Skill "${bundle.id}" eval case "${evalCase.id}" expected active=${evalCase.expectActive} but resolved ${actual}`,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return {
|
|
514
|
+
errors,
|
|
515
|
+
evaluatedCases: Array.isArray(bundle.evalCases) ? bundle.evalCases.length : 0,
|
|
516
|
+
};
|
|
185
517
|
}
|
|
186
518
|
|
|
187
519
|
export function loadSkillBundle(skillId, options = {}) {
|
|
188
520
|
const normalizedSkillId = normalizeSkillId(skillId);
|
|
189
|
-
const skillsDir = normalizeRepoRelativePath(
|
|
190
|
-
options.skillsDir || DEFAULT_SKILLS_DIR,
|
|
191
|
-
"skills.dir",
|
|
192
|
-
);
|
|
521
|
+
const skillsDir = normalizeRepoRelativePath(options.skillsDir || DEFAULT_SKILLS_DIR, "skills.dir");
|
|
193
522
|
const bundleDir = resolveSkillBundleDir(skillsDir, normalizedSkillId);
|
|
194
523
|
if (!fs.existsSync(bundleDir) || !fs.statSync(bundleDir).isDirectory()) {
|
|
195
524
|
throw new Error(
|
|
@@ -198,11 +527,10 @@ export function loadSkillBundle(skillId, options = {}) {
|
|
|
198
527
|
}
|
|
199
528
|
const manifestPath = path.join(bundleDir, "skill.json");
|
|
200
529
|
const skillPath = path.join(bundleDir, "SKILL.md");
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
);
|
|
530
|
+
const rawManifest = readJsonObject(manifestPath, "skill manifest");
|
|
531
|
+
const manifest = normalizeSkillManifest(rawManifest, normalizedSkillId);
|
|
532
|
+
if (manifest.id !== normalizedSkillId) {
|
|
533
|
+
throw new Error(`Skill manifest id mismatch for ${normalizedSkillId}: expected "${normalizedSkillId}"`);
|
|
206
534
|
}
|
|
207
535
|
if (!fs.existsSync(skillPath)) {
|
|
208
536
|
throw new Error(`Missing SKILL.md for skill "${normalizedSkillId}"`);
|
|
@@ -218,34 +546,63 @@ export function loadSkillBundle(skillId, options = {}) {
|
|
|
218
546
|
adapterPathByRuntime[runtimeId] = repoRelativePath(adapterPath);
|
|
219
547
|
adapterTextByRuntime[runtimeId] = fs.readFileSync(adapterPath, "utf8").trim();
|
|
220
548
|
}
|
|
549
|
+
const referencesDir = path.join(bundleDir, "references");
|
|
550
|
+
const referencePaths = listFilesRecursively(referencesDir).map((filePath) =>
|
|
551
|
+
repoRelativePath(filePath),
|
|
552
|
+
);
|
|
221
553
|
const sourceFiles = listFilesRecursively(bundleDir).map((filePath) => ({
|
|
222
554
|
path: repoRelativePath(filePath),
|
|
223
555
|
hash: hashBuffer(fs.readFileSync(filePath)),
|
|
224
556
|
}));
|
|
225
|
-
const bundleHash = hashText(
|
|
226
|
-
sourceFiles.map((entry) => `${entry.path}:${entry.hash}`).join("\n"),
|
|
227
|
-
);
|
|
557
|
+
const bundleHash = hashText(sourceFiles.map((entry) => `${entry.path}:${entry.hash}`).join("\n"));
|
|
228
558
|
return {
|
|
229
|
-
|
|
230
|
-
title: cleanText(manifest.title) || normalizedSkillId,
|
|
231
|
-
description: cleanText(manifest.description) || cleanText(manifest.summary) || null,
|
|
559
|
+
...manifest,
|
|
232
560
|
bundlePath: repoRelativePath(bundleDir),
|
|
233
561
|
manifestPath: repoRelativePath(manifestPath),
|
|
234
562
|
skillPath: repoRelativePath(skillPath),
|
|
235
563
|
skillText,
|
|
236
564
|
adapterPathByRuntime,
|
|
237
565
|
adapterTextByRuntime,
|
|
566
|
+
referencePaths,
|
|
238
567
|
sourceFiles,
|
|
239
568
|
bundleHash,
|
|
240
569
|
};
|
|
241
570
|
}
|
|
242
571
|
|
|
243
|
-
function
|
|
572
|
+
function renderBundleCatalog(bundle, runtimeId) {
|
|
244
573
|
const lines = [`## Skill ${bundle.id}`];
|
|
245
|
-
|
|
246
|
-
lines.push(`- Summary: ${bundle.description}`);
|
|
247
|
-
}
|
|
574
|
+
lines.push(`- Summary: ${bundle.description}`);
|
|
248
575
|
lines.push(`- Bundle: ${bundle.bundlePath}`);
|
|
576
|
+
lines.push(`- Manifest: ${bundle.manifestPath}`);
|
|
577
|
+
lines.push(`- Canonical instructions: ${bundle.skillPath}`);
|
|
578
|
+
lines.push(`- Activation: ${bundle.activation.when}`);
|
|
579
|
+
lines.push(`- Activation roles: ${formatScopeList(bundle.activation.roles)}`);
|
|
580
|
+
lines.push(`- Activation runtimes: ${formatScopeList(bundle.activation.runtimes)}`);
|
|
581
|
+
lines.push(`- Activation deploy kinds: ${formatScopeList(bundle.activation.deployKinds)}`);
|
|
582
|
+
if (bundle.termination?.when) {
|
|
583
|
+
lines.push(`- Termination: ${bundle.termination.when}`);
|
|
584
|
+
}
|
|
585
|
+
if (bundle.trust?.tier) {
|
|
586
|
+
lines.push(`- Trust tier: ${bundle.trust.tier}`);
|
|
587
|
+
}
|
|
588
|
+
lines.push(`- Permissions: ${formatPermissionSummary(bundle.permissions)}`);
|
|
589
|
+
lines.push(
|
|
590
|
+
`- Runtime adapter (${runtimeId}): ${bundle.adapterPathByRuntime[runtimeId] || "none"}`,
|
|
591
|
+
);
|
|
592
|
+
if (Array.isArray(bundle.referencePaths) && bundle.referencePaths.length > 0) {
|
|
593
|
+
for (const refPath of bundle.referencePaths) {
|
|
594
|
+
lines.push(`- Reference: ${refPath}`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
lines.push("");
|
|
598
|
+
return lines;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function renderBundleExpandedPrompt(bundle, runtimeId) {
|
|
602
|
+
const lines = [`## Skill ${bundle.id}`];
|
|
603
|
+
lines.push(`- Summary: ${bundle.description}`);
|
|
604
|
+
lines.push(`- Manifest: ${bundle.manifestPath}`);
|
|
605
|
+
lines.push(`- Canonical instructions: ${bundle.skillPath}`);
|
|
249
606
|
lines.push("### Canonical instructions");
|
|
250
607
|
lines.push("```text");
|
|
251
608
|
lines.push(bundle.skillText);
|
|
@@ -253,11 +610,18 @@ function renderBundlePrompt(bundle, runtimeId) {
|
|
|
253
610
|
const adapterText = bundle.adapterTextByRuntime[runtimeId];
|
|
254
611
|
if (adapterText) {
|
|
255
612
|
lines.push("");
|
|
256
|
-
lines.push(`### ${runtimeId} adapter`);
|
|
613
|
+
lines.push(`### ${runtimeId} adapter (${bundle.adapterPathByRuntime[runtimeId]})`);
|
|
257
614
|
lines.push("```text");
|
|
258
615
|
lines.push(adapterText);
|
|
259
616
|
lines.push("```");
|
|
260
617
|
}
|
|
618
|
+
if (Array.isArray(bundle.referencePaths) && bundle.referencePaths.length > 0) {
|
|
619
|
+
lines.push("");
|
|
620
|
+
lines.push("### Available references");
|
|
621
|
+
for (const refPath of bundle.referencePaths) {
|
|
622
|
+
lines.push(`- ${refPath}`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
261
625
|
lines.push("");
|
|
262
626
|
return lines;
|
|
263
627
|
}
|
|
@@ -268,32 +632,39 @@ function renderSkillPromptText(bundles, runtimeId) {
|
|
|
268
632
|
}
|
|
269
633
|
const lines = [
|
|
270
634
|
"Active skill packs for this run:",
|
|
271
|
-
...bundles.map(
|
|
272
|
-
(bundle) => `- ${bundle.id}${bundle.description ? `: ${bundle.description}` : ""}`,
|
|
273
|
-
),
|
|
635
|
+
...bundles.map((bundle) => `- ${bundle.id}: ${bundle.description}`),
|
|
274
636
|
"- Skills are additive guidance. Repository source, standing role prompts, shared summaries, and ownership boundaries remain authoritative.",
|
|
637
|
+
"- Use this catalog first. Open each bundle's manifest, SKILL.md, runtime adapter, and references only when needed for the current step.",
|
|
275
638
|
"",
|
|
276
639
|
];
|
|
277
640
|
for (const bundle of bundles) {
|
|
278
|
-
lines.push(...
|
|
641
|
+
lines.push(...renderBundleCatalog(bundle, runtimeId));
|
|
642
|
+
}
|
|
643
|
+
return lines.join("\n").trim();
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function renderExpandedSkillText(bundles, runtimeId) {
|
|
647
|
+
if (!Array.isArray(bundles) || bundles.length === 0) {
|
|
648
|
+
return "";
|
|
649
|
+
}
|
|
650
|
+
const lines = ["Full canonical skill payload for this run:", ""];
|
|
651
|
+
for (const bundle of bundles) {
|
|
652
|
+
lines.push(...renderBundleExpandedPrompt(bundle, runtimeId));
|
|
279
653
|
}
|
|
280
654
|
return lines.join("\n").trim();
|
|
281
655
|
}
|
|
282
656
|
|
|
283
657
|
function renderRuntimeOnlyText(bundles, runtimeId) {
|
|
284
|
-
|
|
658
|
+
if (!Array.isArray(bundles) || bundles.length === 0) {
|
|
659
|
+
return "";
|
|
660
|
+
}
|
|
661
|
+
const lines = [`Runtime skill catalog for ${runtimeId}:`];
|
|
285
662
|
for (const bundle of bundles) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
sections.push(`Skill ${bundle.id}`);
|
|
291
|
-
sections.push("```text");
|
|
292
|
-
sections.push(adapterText);
|
|
293
|
-
sections.push("```");
|
|
294
|
-
sections.push("");
|
|
663
|
+
lines.push(
|
|
664
|
+
`- ${bundle.id}: adapter=${bundle.adapterPathByRuntime[runtimeId] || "none"} manifest=${bundle.manifestPath}`,
|
|
665
|
+
);
|
|
295
666
|
}
|
|
296
|
-
return
|
|
667
|
+
return lines.join("\n").trim();
|
|
297
668
|
}
|
|
298
669
|
|
|
299
670
|
function defaultDeployEnvironmentKind(wave) {
|
|
@@ -301,7 +672,31 @@ function defaultDeployEnvironmentKind(wave) {
|
|
|
301
672
|
if (environments.length === 0) {
|
|
302
673
|
return null;
|
|
303
674
|
}
|
|
304
|
-
return
|
|
675
|
+
return (
|
|
676
|
+
environments.find((environment) => environment.isDefault)?.kind || environments[0]?.kind || null
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function buildPromptHash({ ids, role, runtimeId, deployKind, promptText, bundles }) {
|
|
681
|
+
return hashText(
|
|
682
|
+
JSON.stringify({
|
|
683
|
+
ids,
|
|
684
|
+
role,
|
|
685
|
+
runtimeId,
|
|
686
|
+
deployKind,
|
|
687
|
+
promptText,
|
|
688
|
+
bundles: bundles.map((bundle) => ({
|
|
689
|
+
id: bundle.id,
|
|
690
|
+
bundleHash: bundle.bundleHash,
|
|
691
|
+
adapterPath: bundle.adapterPathByRuntime[runtimeId || "local"] || null,
|
|
692
|
+
activation: bundle.activation,
|
|
693
|
+
termination: bundle.termination,
|
|
694
|
+
permissions: bundle.permissions,
|
|
695
|
+
trust: bundle.trust,
|
|
696
|
+
referencePaths: bundle.referencePaths,
|
|
697
|
+
})),
|
|
698
|
+
}),
|
|
699
|
+
);
|
|
305
700
|
}
|
|
306
701
|
|
|
307
702
|
export function resolveSkillIdsForAgent(agent, wave, laneProfile) {
|
|
@@ -309,53 +704,106 @@ export function resolveSkillIdsForAgent(agent, wave, laneProfile) {
|
|
|
309
704
|
const role = cleanText(agent?.executorResolved?.role).toLowerCase() || null;
|
|
310
705
|
const runtimeId = cleanText(agent?.executorResolved?.id).toLowerCase() || null;
|
|
311
706
|
const deployKind = cleanText(defaultDeployEnvironmentKind(wave)).toLowerCase() || null;
|
|
707
|
+
const configuredIds = uniqueStrings([
|
|
708
|
+
...(skillsConfig.base || []),
|
|
709
|
+
...(role ? skillsConfig.byRole?.[role] || [] : []),
|
|
710
|
+
...(runtimeId ? skillsConfig.byRuntime?.[runtimeId] || [] : []),
|
|
711
|
+
...(deployKind ? skillsConfig.byDeployKind?.[deployKind] || [] : []),
|
|
712
|
+
]).map((skillId) => normalizeSkillId(skillId));
|
|
713
|
+
const explicitIds = normalizeSkillIdArray(agent?.skills, "agent.skills");
|
|
312
714
|
return {
|
|
313
715
|
role,
|
|
314
716
|
runtimeId,
|
|
315
717
|
deployKind,
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
...(runtimeId ? skillsConfig.byRuntime?.[runtimeId] || [] : []),
|
|
320
|
-
...(deployKind ? skillsConfig.byDeployKind?.[deployKind] || [] : []),
|
|
321
|
-
...(Array.isArray(agent?.skills) ? agent.skills : []),
|
|
322
|
-
]).map((skillId) => normalizeSkillId(skillId)),
|
|
718
|
+
configuredIds,
|
|
719
|
+
explicitIds,
|
|
720
|
+
ids: uniqueStrings([...configuredIds, ...explicitIds]).map((skillId) => normalizeSkillId(skillId)),
|
|
323
721
|
};
|
|
324
722
|
}
|
|
325
723
|
|
|
326
724
|
export function resolveAgentSkills(agent, wave, options = {}) {
|
|
327
725
|
const laneProfile = options.laneProfile || {};
|
|
328
726
|
const skillsConfig = laneProfile.skills || emptySkillsConfig();
|
|
329
|
-
const {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
727
|
+
const { configuredIds, explicitIds, role, runtimeId, deployKind } = resolveSkillIdsForAgent(
|
|
728
|
+
agent,
|
|
729
|
+
wave,
|
|
730
|
+
laneProfile,
|
|
731
|
+
);
|
|
732
|
+
const resolvedRuntimeId = runtimeId || "local";
|
|
733
|
+
const bundleCache = new Map();
|
|
734
|
+
const loadBundleCached = (skillId) => {
|
|
735
|
+
if (!bundleCache.has(skillId)) {
|
|
736
|
+
bundleCache.set(skillId, loadSkillBundle(skillId, { skillsDir: skillsConfig.dir }));
|
|
737
|
+
}
|
|
738
|
+
return bundleCache.get(skillId);
|
|
739
|
+
};
|
|
740
|
+
const activeConfiguredBundles = configuredIds
|
|
741
|
+
.map((skillId) => loadBundleCached(skillId))
|
|
742
|
+
.filter((bundle) =>
|
|
743
|
+
skillMatchesActivation(bundle, {
|
|
744
|
+
role,
|
|
745
|
+
runtimeId: resolvedRuntimeId,
|
|
746
|
+
deployKind,
|
|
747
|
+
}),
|
|
748
|
+
);
|
|
749
|
+
const explicitBundles = explicitIds.map((skillId) => loadBundleCached(skillId));
|
|
750
|
+
const bundles = [];
|
|
751
|
+
const seen = new Set();
|
|
752
|
+
for (const bundle of [...activeConfiguredBundles, ...explicitBundles]) {
|
|
753
|
+
if (seen.has(bundle.id)) {
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
seen.add(bundle.id);
|
|
757
|
+
bundles.push(bundle);
|
|
758
|
+
}
|
|
759
|
+
const promptText = renderSkillPromptText(bundles, resolvedRuntimeId);
|
|
760
|
+
const expandedPromptText = renderExpandedSkillText(bundles, resolvedRuntimeId);
|
|
761
|
+
const runtimeText = renderRuntimeOnlyText(bundles, resolvedRuntimeId);
|
|
333
762
|
return {
|
|
334
763
|
dir: skillsConfig.dir,
|
|
335
|
-
ids,
|
|
764
|
+
ids: bundles.map((bundle) => bundle.id),
|
|
336
765
|
role,
|
|
337
766
|
runtime: runtimeId,
|
|
338
767
|
deployKind,
|
|
339
768
|
promptText,
|
|
340
|
-
|
|
769
|
+
expandedPromptText,
|
|
770
|
+
promptHash: buildPromptHash({
|
|
771
|
+
ids: bundles.map((bundle) => bundle.id),
|
|
772
|
+
role,
|
|
773
|
+
runtimeId: resolvedRuntimeId,
|
|
774
|
+
deployKind,
|
|
775
|
+
promptText,
|
|
776
|
+
bundles,
|
|
777
|
+
}),
|
|
341
778
|
runtimeText,
|
|
342
779
|
bundles: bundles.map((bundle) => ({
|
|
343
780
|
id: bundle.id,
|
|
344
781
|
title: bundle.title,
|
|
345
782
|
description: bundle.description,
|
|
783
|
+
version: bundle.version,
|
|
784
|
+
tags: bundle.tags,
|
|
785
|
+
activation: bundle.activation,
|
|
786
|
+
termination: bundle.termination,
|
|
787
|
+
permissions: bundle.permissions,
|
|
788
|
+
trust: bundle.trust,
|
|
789
|
+
evalCases: bundle.evalCases,
|
|
346
790
|
bundlePath: bundle.bundlePath,
|
|
347
791
|
manifestPath: bundle.manifestPath,
|
|
348
792
|
skillPath: bundle.skillPath,
|
|
349
|
-
adapterPath: bundle.adapterPathByRuntime[
|
|
793
|
+
adapterPath: bundle.adapterPathByRuntime[resolvedRuntimeId] || null,
|
|
794
|
+
referencePaths: bundle.referencePaths,
|
|
350
795
|
bundleHash: bundle.bundleHash,
|
|
351
796
|
sourceFiles: bundle.sourceFiles.map((entry) => entry.path),
|
|
352
797
|
})),
|
|
353
|
-
codexAddDirs: uniqueStrings(bundles.map((bundle) => bundle.bundlePath)),
|
|
798
|
+
codexAddDirs: uniqueStrings(bundles.map((bundle) => bundle.bundlePath), { lowerCase: false }),
|
|
354
799
|
opencodeFiles: uniqueStrings(
|
|
355
800
|
bundles.flatMap((bundle) => [
|
|
801
|
+
bundle.manifestPath,
|
|
356
802
|
bundle.skillPath,
|
|
357
|
-
bundle.adapterPathByRuntime[
|
|
803
|
+
bundle.adapterPathByRuntime[resolvedRuntimeId] || null,
|
|
804
|
+
...bundle.referencePaths,
|
|
358
805
|
]),
|
|
806
|
+
{ lowerCase: false },
|
|
359
807
|
),
|
|
360
808
|
opencodeInstructions: promptText ? [promptText] : [],
|
|
361
809
|
};
|
|
@@ -384,10 +832,18 @@ export function summarizeResolvedSkills(resolvedSkills) {
|
|
|
384
832
|
id: bundle.id,
|
|
385
833
|
title: bundle.title || null,
|
|
386
834
|
description: bundle.description || null,
|
|
835
|
+
version: bundle.version || null,
|
|
836
|
+
tags: Array.isArray(bundle.tags) ? bundle.tags.slice() : [],
|
|
837
|
+
activation: bundle.activation || null,
|
|
838
|
+
termination: bundle.termination || null,
|
|
839
|
+
permissions: bundle.permissions || null,
|
|
840
|
+
trust: bundle.trust || null,
|
|
841
|
+
evalCases: Array.isArray(bundle.evalCases) ? bundle.evalCases.slice() : [],
|
|
387
842
|
bundlePath: bundle.bundlePath,
|
|
388
843
|
manifestPath: bundle.manifestPath,
|
|
389
844
|
skillPath: bundle.skillPath,
|
|
390
845
|
adapterPath: bundle.adapterPath || null,
|
|
846
|
+
referencePaths: Array.isArray(bundle.referencePaths) ? bundle.referencePaths.slice() : [],
|
|
391
847
|
bundleHash: bundle.bundleHash || null,
|
|
392
848
|
sourceFiles: Array.isArray(bundle.sourceFiles) ? bundle.sourceFiles.slice() : [],
|
|
393
849
|
}))
|
|
@@ -401,17 +857,18 @@ export function writeResolvedSkillArtifacts(overlayDir, resolvedSkills) {
|
|
|
401
857
|
return null;
|
|
402
858
|
}
|
|
403
859
|
const promptPath = path.join(overlayDir, "skills.resolved.md");
|
|
860
|
+
const expandedPromptPath = path.join(overlayDir, "skills.expanded.md");
|
|
404
861
|
const metadataPath = path.join(overlayDir, "skills.metadata.json");
|
|
405
862
|
const runtimePromptPath =
|
|
406
|
-
resolvedSkills.runtime
|
|
407
|
-
? path.join(overlayDir, `${resolvedSkills.runtime}-skills.txt`)
|
|
408
|
-
: null;
|
|
863
|
+
resolvedSkills.runtime ? path.join(overlayDir, `${resolvedSkills.runtime}-skills.txt`) : null;
|
|
409
864
|
const artifacts = {
|
|
410
865
|
promptPath: repoRelativePath(promptPath),
|
|
866
|
+
expandedPromptPath: repoRelativePath(expandedPromptPath),
|
|
411
867
|
metadataPath: repoRelativePath(metadataPath),
|
|
412
868
|
runtimePromptPath: runtimePromptPath ? repoRelativePath(runtimePromptPath) : null,
|
|
413
869
|
};
|
|
414
870
|
writeTextAtomic(promptPath, `${resolvedSkills.promptText}\n`);
|
|
871
|
+
writeTextAtomic(expandedPromptPath, `${resolvedSkills.expandedPromptText || resolvedSkills.promptText}\n`);
|
|
415
872
|
if (runtimePromptPath) {
|
|
416
873
|
writeTextAtomic(runtimePromptPath, `${resolvedSkills.runtimeText || resolvedSkills.promptText}\n`);
|
|
417
874
|
}
|
|
@@ -425,24 +882,98 @@ export function writeResolvedSkillArtifacts(overlayDir, resolvedSkills) {
|
|
|
425
882
|
return artifacts;
|
|
426
883
|
}
|
|
427
884
|
|
|
428
|
-
export function validateLaneSkillConfiguration(laneProfile) {
|
|
885
|
+
export function validateLaneSkillConfiguration(laneProfile, options = {}) {
|
|
429
886
|
const skillsConfig = laneProfile?.skills || emptySkillsConfig();
|
|
430
|
-
const
|
|
887
|
+
const allowedDeployKinds = new Set([
|
|
888
|
+
...SUPPORTED_SKILL_DEPLOY_KINDS,
|
|
889
|
+
...(Array.isArray(options.allowedDeployKinds) ? options.allowedDeployKinds : []),
|
|
890
|
+
]);
|
|
891
|
+
const allSkillIds = uniqueStrings([
|
|
892
|
+
...listSkillBundleIds(skillsConfig.dir || DEFAULT_SKILLS_DIR),
|
|
431
893
|
...(skillsConfig.base || []),
|
|
432
894
|
...Object.values(skillsConfig.byRole || {}).flat(),
|
|
433
895
|
...Object.values(skillsConfig.byRuntime || {}).flat(),
|
|
434
896
|
...Object.values(skillsConfig.byDeployKind || {}).flat(),
|
|
435
897
|
]).map((skillId) => normalizeSkillId(skillId));
|
|
436
898
|
const errors = [];
|
|
437
|
-
|
|
899
|
+
const bundles = new Map();
|
|
900
|
+
const loadBundleSafe = (skillId) => {
|
|
901
|
+
if (bundles.has(skillId)) {
|
|
902
|
+
return bundles.get(skillId);
|
|
903
|
+
}
|
|
438
904
|
try {
|
|
439
|
-
loadSkillBundle(skillId, { skillsDir: skillsConfig.dir });
|
|
905
|
+
const bundle = loadSkillBundle(skillId, { skillsDir: skillsConfig.dir });
|
|
906
|
+
bundles.set(skillId, bundle);
|
|
907
|
+
return bundle;
|
|
440
908
|
} catch (error) {
|
|
441
909
|
errors.push(error instanceof Error ? error.message : String(error));
|
|
910
|
+
return null;
|
|
442
911
|
}
|
|
912
|
+
};
|
|
913
|
+
for (const skillId of allSkillIds) {
|
|
914
|
+
loadBundleSafe(skillId);
|
|
915
|
+
}
|
|
916
|
+
for (const [role, skillIds] of Object.entries(skillsConfig.byRole || {})) {
|
|
917
|
+
for (const skillId of skillIds) {
|
|
918
|
+
const bundle = loadBundleSafe(skillId);
|
|
919
|
+
if (!bundle) {
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
if (bundle.activation.roles.length > 0 && !bundle.activation.roles.includes(role)) {
|
|
923
|
+
errors.push(
|
|
924
|
+
`Skill "${skillId}" is configured under skills.byRole.${role} but manifest activation.roles excludes "${role}"`,
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
for (const [runtimeId, skillIds] of Object.entries(skillsConfig.byRuntime || {})) {
|
|
930
|
+
for (const skillId of skillIds) {
|
|
931
|
+
const bundle = loadBundleSafe(skillId);
|
|
932
|
+
if (!bundle) {
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
if (
|
|
936
|
+
bundle.activation.runtimes.length > 0 &&
|
|
937
|
+
!bundle.activation.runtimes.includes(runtimeId)
|
|
938
|
+
) {
|
|
939
|
+
errors.push(
|
|
940
|
+
`Skill "${skillId}" is configured under skills.byRuntime.${runtimeId} but manifest activation.runtimes excludes "${runtimeId}"`,
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
for (const [deployKind, skillIds] of Object.entries(skillsConfig.byDeployKind || {})) {
|
|
946
|
+
if (!allowedDeployKinds.has(deployKind)) {
|
|
947
|
+
errors.push(
|
|
948
|
+
`skills.byDeployKind.${deployKind} is not a supported selector key for this lane`,
|
|
949
|
+
);
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
for (const skillId of skillIds) {
|
|
953
|
+
const bundle = loadBundleSafe(skillId);
|
|
954
|
+
if (!bundle) {
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
if (
|
|
958
|
+
bundle.activation.deployKinds.length > 0 &&
|
|
959
|
+
!bundle.activation.deployKinds.includes(deployKind)
|
|
960
|
+
) {
|
|
961
|
+
errors.push(
|
|
962
|
+
`Skill "${skillId}" is configured under skills.byDeployKind.${deployKind} but manifest activation.deployKinds excludes "${deployKind}"`,
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
let evaluatedCases = 0;
|
|
968
|
+
for (const bundle of bundles.values()) {
|
|
969
|
+
const evalResult = evaluateSkillBundleCases(bundle);
|
|
970
|
+
errors.push(...evalResult.errors);
|
|
971
|
+
evaluatedCases += evalResult.evaluatedCases;
|
|
443
972
|
}
|
|
444
973
|
return {
|
|
445
974
|
ok: errors.length === 0,
|
|
446
975
|
errors,
|
|
976
|
+
evaluatedBundles: bundles.size,
|
|
977
|
+
evaluatedCases,
|
|
447
978
|
};
|
|
448
979
|
}
|