vgxness 1.17.2 → 1.19.0
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/agents/agent-lookup-contexts.js +28 -0
- package/dist/agents/agent-registry-service.js +29 -2
- package/dist/agents/agent-resolver.js +24 -5
- package/dist/agents/agent-selector-resolver.js +12 -7
- package/dist/agents/canonical-agent-manifest.js +14 -11
- package/dist/agents/canonical-agent-projection.js +24 -2
- package/dist/cli/cli-help.js +1 -1
- package/dist/cli/home-tui-app.js +1 -1
- package/dist/mcp/client-install-opencode-contract.js +2 -2
- package/dist/mcp/client-install-opencode.js +2 -2
- package/dist/mcp/control-plane-snapshot-service.js +11 -0
- package/dist/mcp/control-plane.js +55 -0
- package/dist/mcp/schema.js +253 -0
- package/dist/mcp/stdio-server.js +6 -0
- package/dist/mcp/validation.js +662 -0
- package/dist/memory/active-work-preview.js +75 -0
- package/dist/memory/active-work-topics.js +50 -0
- package/dist/memory/sqlite/migrations/018_skill_system_v2.sql +112 -0
- package/dist/setup/providers/opencode-setup-adapter.js +4 -4
- package/dist/setup/setup-plan.js +1 -1
- package/dist/skills/repositories/skill-evaluation-requests.js +190 -0
- package/dist/skills/repositories/skill-improvement-proposals.js +55 -4
- package/dist/skills/repositories/skills.js +630 -3
- package/dist/skills/skill-payload.js +9 -2
- package/dist/skills/skill-registry-service.js +554 -4
- package/dist/skills/skill-resolver.js +53 -3
- package/dist/skills/skill-status-service.js +4 -1
- package/package.json +1 -1
|
@@ -152,8 +152,8 @@ export class SkillRepository {
|
|
|
152
152
|
const now = new Date().toISOString();
|
|
153
153
|
this.db.connection
|
|
154
154
|
.prepare(`
|
|
155
|
-
INSERT INTO skill_usage_records(id, skill_id, version_id, run_id, target_type, target_key, outcome, notes, created_at)
|
|
156
|
-
VALUES (@id, @skillId, @versionId, @runId, @targetType, @targetKey, @outcome, @notes, @createdAt)
|
|
155
|
+
INSERT INTO skill_usage_records(id, skill_id, version_id, run_id, target_type, target_key, outcome, notes, created_at, workflow, phase, provider_adapter, agent_id, intent_json, evidence_json)
|
|
156
|
+
VALUES (@id, @skillId, @versionId, @runId, @targetType, @targetKey, @outcome, @notes, @createdAt, @workflow, @phase, @providerAdapter, @agentId, @intentJson, @evidenceJson)
|
|
157
157
|
`)
|
|
158
158
|
.run({
|
|
159
159
|
id,
|
|
@@ -165,6 +165,12 @@ export class SkillRepository {
|
|
|
165
165
|
outcome: input.outcome,
|
|
166
166
|
notes: input.notes ?? null,
|
|
167
167
|
createdAt: now,
|
|
168
|
+
workflow: input.workflow ?? null,
|
|
169
|
+
phase: input.phase ?? null,
|
|
170
|
+
providerAdapter: input.providerAdapter ?? null,
|
|
171
|
+
agentId: input.agentId ?? null,
|
|
172
|
+
intentJson: input.intent === undefined ? null : JSON.stringify(input.intent),
|
|
173
|
+
evidenceJson: input.evidence === undefined ? null : JSON.stringify(input.evidence),
|
|
168
174
|
});
|
|
169
175
|
const read = this.getUsage(id);
|
|
170
176
|
if (!read.ok)
|
|
@@ -176,6 +182,218 @@ export class SkillRepository {
|
|
|
176
182
|
}
|
|
177
183
|
return result;
|
|
178
184
|
}
|
|
185
|
+
recordActivation(input) {
|
|
186
|
+
const validation = validateActivation(input);
|
|
187
|
+
if (!validation.ok)
|
|
188
|
+
return validation;
|
|
189
|
+
const result = this.db.transaction(() => {
|
|
190
|
+
const skill = this.getById(input.skillId);
|
|
191
|
+
if (!skill.ok)
|
|
192
|
+
throw new SkillRegistryValidationError(skill.error.message);
|
|
193
|
+
if (input.versionId !== undefined)
|
|
194
|
+
assertVersionBelongsToSkill(this.db, input.versionId, input.skillId);
|
|
195
|
+
if (skill.value.project !== input.project || skill.value.scope !== input.scope)
|
|
196
|
+
throw new SkillRegistryValidationError('Skill does not belong to the requested project/scope');
|
|
197
|
+
const id = randomUUID();
|
|
198
|
+
const now = new Date().toISOString();
|
|
199
|
+
this.db.connection
|
|
200
|
+
.prepare(`
|
|
201
|
+
INSERT INTO skill_activation_records(id, project, scope, skill_id, version_id, run_id, agent_id, workflow, phase, provider_adapter, intent_json, reason, payload_metadata_json, created_at)
|
|
202
|
+
VALUES (@id, @project, @scope, @skillId, @versionId, @runId, @agentId, @workflow, @phase, @providerAdapter, @intentJson, @reason, @payloadMetadataJson, @createdAt)
|
|
203
|
+
`)
|
|
204
|
+
.run({
|
|
205
|
+
id,
|
|
206
|
+
project: input.project,
|
|
207
|
+
scope: input.scope,
|
|
208
|
+
skillId: input.skillId,
|
|
209
|
+
versionId: input.versionId ?? null,
|
|
210
|
+
runId: input.runId ?? null,
|
|
211
|
+
agentId: input.agentId ?? null,
|
|
212
|
+
workflow: input.workflow ?? null,
|
|
213
|
+
phase: input.phase ?? null,
|
|
214
|
+
providerAdapter: input.providerAdapter ?? null,
|
|
215
|
+
intentJson: input.intent === undefined ? null : JSON.stringify(input.intent),
|
|
216
|
+
reason: input.reason ?? null,
|
|
217
|
+
payloadMetadataJson: JSON.stringify(input.payloadMetadata ?? {}),
|
|
218
|
+
createdAt: now,
|
|
219
|
+
});
|
|
220
|
+
const read = this.getActivation(id);
|
|
221
|
+
if (!read.ok)
|
|
222
|
+
throw new Error(read.error.message);
|
|
223
|
+
return read.value;
|
|
224
|
+
});
|
|
225
|
+
if (!result.ok && result.error.cause instanceof SkillRegistryValidationError) {
|
|
226
|
+
return validationFailure(result.error.cause.message);
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
createDraft(input) {
|
|
231
|
+
const validation = validateCreateDraft(input);
|
|
232
|
+
if (!validation.ok)
|
|
233
|
+
return validation;
|
|
234
|
+
const result = this.db.transaction(() => {
|
|
235
|
+
const existing = this.findByNaturalKey(input.project, input.scope ?? 'project', input.name);
|
|
236
|
+
const skill = existing === undefined ? this.register({ project: input.project, scope: input.scope ?? 'project', name: input.name, description: input.description }) : ok(mapSkill(existing));
|
|
237
|
+
if (!skill.ok)
|
|
238
|
+
throw new SkillRegistryValidationError(skill.error.message);
|
|
239
|
+
if (input.draftParentVersionId !== undefined)
|
|
240
|
+
assertVersionBelongsToSkill(this.db, input.draftParentVersionId, skill.value.id);
|
|
241
|
+
if (this.findVersion(skill.value.id, input.version) !== undefined)
|
|
242
|
+
throw new SkillRegistryValidationError(`Skill version already exists: ${input.version}`);
|
|
243
|
+
const now = new Date().toISOString();
|
|
244
|
+
const id = randomUUID();
|
|
245
|
+
this.db.connection
|
|
246
|
+
.prepare(`
|
|
247
|
+
INSERT INTO skill_versions(id, skill_id, version, source_kind, source_path, source_url, source_inline_metadata_json, compatibility_json, status, created_at, lifecycle_label, draft_parent_version_id, draft_revision, governance_json)
|
|
248
|
+
VALUES (@id, @skillId, @version, @sourceKind, @sourcePath, @sourceUrl, @sourceInlineMetadataJson, @compatibilityJson, 'draft', @createdAt, 'draft', @draftParentVersionId, 1, @governanceJson)
|
|
249
|
+
`)
|
|
250
|
+
.run({
|
|
251
|
+
id,
|
|
252
|
+
skillId: skill.value.id,
|
|
253
|
+
version: input.version,
|
|
254
|
+
sourceKind: input.source.kind,
|
|
255
|
+
sourcePath: input.source.path ?? null,
|
|
256
|
+
sourceUrl: input.source.url ?? null,
|
|
257
|
+
sourceInlineMetadataJson: JSON.stringify(input.source.inlineMetadata ?? {}),
|
|
258
|
+
compatibilityJson: JSON.stringify(input.compatibility ?? {}),
|
|
259
|
+
createdAt: now,
|
|
260
|
+
draftParentVersionId: input.draftParentVersionId ?? null,
|
|
261
|
+
governanceJson: JSON.stringify(input.governance ?? {}),
|
|
262
|
+
});
|
|
263
|
+
const version = this.getVersion(id);
|
|
264
|
+
if (!version.ok)
|
|
265
|
+
throw new Error(version.error.message);
|
|
266
|
+
const after = versionSnapshot(version.value);
|
|
267
|
+
const event = this.recordLifecycleEvent({
|
|
268
|
+
project: skill.value.project,
|
|
269
|
+
scope: skill.value.scope,
|
|
270
|
+
skillId: skill.value.id,
|
|
271
|
+
versionId: version.value.id,
|
|
272
|
+
eventType: 'draft-created',
|
|
273
|
+
actor: input.actor,
|
|
274
|
+
...(input.runId === undefined ? {} : { runId: input.runId }),
|
|
275
|
+
...(input.agentId === undefined ? {} : { agentId: input.agentId }),
|
|
276
|
+
...(input.reason === undefined ? {} : { reason: input.reason }),
|
|
277
|
+
before: existing === undefined ? {} : { skillExisted: true, currentVersionId: existing.current_version_id ?? null },
|
|
278
|
+
after,
|
|
279
|
+
...(input.evidence === undefined ? {} : { evidence: input.evidence }),
|
|
280
|
+
});
|
|
281
|
+
if (!event.ok)
|
|
282
|
+
throw new Error(event.error.message);
|
|
283
|
+
return { skill: skill.value, version: version.value, event: event.value };
|
|
284
|
+
});
|
|
285
|
+
if (!result.ok && result.error.cause instanceof SkillRegistryValidationError)
|
|
286
|
+
return validationFailure(result.error.cause.message);
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
updateDraft(input) {
|
|
290
|
+
const validation = validateUpdateDraft(input);
|
|
291
|
+
if (!validation.ok)
|
|
292
|
+
return validation;
|
|
293
|
+
const result = this.db.transaction(() => {
|
|
294
|
+
const skill = resolveSkillForMutation(this, input.project, input.scope ?? 'project', input.skillId, input.name);
|
|
295
|
+
const version = resolveVersionForMutation(this, skill.id, input.versionId, input.version);
|
|
296
|
+
if (version.status !== 'draft')
|
|
297
|
+
throw new SkillRegistryValidationError(`Cannot update ${version.status} skill versions; only draft versions can be updated`);
|
|
298
|
+
if (input.expectedDraftRevision !== undefined && (version.draftRevision ?? 0) !== input.expectedDraftRevision) {
|
|
299
|
+
throw new SkillRegistryValidationError(`Draft revision conflict: expected ${input.expectedDraftRevision}, found ${version.draftRevision ?? 0}`);
|
|
300
|
+
}
|
|
301
|
+
const before = versionSnapshot(version);
|
|
302
|
+
const nextRevision = (version.draftRevision ?? 0) + 1;
|
|
303
|
+
this.db.connection
|
|
304
|
+
.prepare(`
|
|
305
|
+
UPDATE skill_versions SET
|
|
306
|
+
source_kind=COALESCE(@sourceKind, source_kind),
|
|
307
|
+
source_path=@sourcePath,
|
|
308
|
+
source_url=@sourceUrl,
|
|
309
|
+
source_inline_metadata_json=COALESCE(@sourceInlineMetadataJson, source_inline_metadata_json),
|
|
310
|
+
compatibility_json=COALESCE(@compatibilityJson, compatibility_json),
|
|
311
|
+
draft_revision=@draftRevision,
|
|
312
|
+
governance_json=COALESCE(@governanceJson, governance_json)
|
|
313
|
+
WHERE id=@id
|
|
314
|
+
`)
|
|
315
|
+
.run({
|
|
316
|
+
id: version.id,
|
|
317
|
+
sourceKind: input.source?.kind ?? null,
|
|
318
|
+
sourcePath: input.source === undefined ? (version.source.path ?? null) : (input.source.path ?? null),
|
|
319
|
+
sourceUrl: input.source === undefined ? (version.source.url ?? null) : (input.source.url ?? null),
|
|
320
|
+
sourceInlineMetadataJson: input.source === undefined ? null : JSON.stringify(input.source.inlineMetadata ?? {}),
|
|
321
|
+
compatibilityJson: input.compatibility === undefined ? null : JSON.stringify(input.compatibility),
|
|
322
|
+
draftRevision: nextRevision,
|
|
323
|
+
governanceJson: input.governance === undefined ? null : JSON.stringify(input.governance),
|
|
324
|
+
});
|
|
325
|
+
const updated = this.getVersion(version.id);
|
|
326
|
+
if (!updated.ok)
|
|
327
|
+
throw new Error(updated.error.message);
|
|
328
|
+
const event = this.recordLifecycleEvent({
|
|
329
|
+
project: skill.project,
|
|
330
|
+
scope: skill.scope,
|
|
331
|
+
skillId: skill.id,
|
|
332
|
+
versionId: updated.value.id,
|
|
333
|
+
eventType: 'draft-updated',
|
|
334
|
+
actor: input.actor,
|
|
335
|
+
...(input.runId === undefined ? {} : { runId: input.runId }),
|
|
336
|
+
...(input.agentId === undefined ? {} : { agentId: input.agentId }),
|
|
337
|
+
...(input.reason === undefined ? {} : { reason: input.reason }),
|
|
338
|
+
before,
|
|
339
|
+
after: versionSnapshot(updated.value),
|
|
340
|
+
...(input.evidence === undefined ? {} : { evidence: input.evidence }),
|
|
341
|
+
});
|
|
342
|
+
if (!event.ok)
|
|
343
|
+
throw new Error(event.error.message);
|
|
344
|
+
return { skill, version: updated.value, event: event.value };
|
|
345
|
+
});
|
|
346
|
+
if (!result.ok && result.error.cause instanceof SkillRegistryValidationError)
|
|
347
|
+
return validationFailure(result.error.cause.message);
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
publishVersion(input) {
|
|
351
|
+
const validation = validatePublishVersion(input);
|
|
352
|
+
if (!validation.ok)
|
|
353
|
+
return validation;
|
|
354
|
+
const result = this.db.transaction(() => {
|
|
355
|
+
const skill = resolveSkillForMutation(this, input.project, input.scope ?? 'project', input.skillId, input.name);
|
|
356
|
+
const version = resolveVersionForMutation(this, skill.id, input.versionId, input.version);
|
|
357
|
+
if (version.status !== 'draft')
|
|
358
|
+
throw new SkillRegistryValidationError(`Cannot publish ${version.status} skill versions; only draft versions can be published`);
|
|
359
|
+
const before = { skill: { currentVersionId: skill.currentVersionId ?? null }, version: versionSnapshot(version) };
|
|
360
|
+
const previousCurrentVersionId = skill.currentVersionId ?? null;
|
|
361
|
+
const now = new Date().toISOString();
|
|
362
|
+
this.db.connection
|
|
363
|
+
.prepare(`
|
|
364
|
+
UPDATE skill_versions SET status='active', lifecycle_label='published', published_at=@publishedAt, published_by=@publishedBy, governance_json=COALESCE(@governanceJson, governance_json)
|
|
365
|
+
WHERE id=@id
|
|
366
|
+
`)
|
|
367
|
+
.run({ id: version.id, publishedAt: now, publishedBy: input.actor.id, governanceJson: input.governance === undefined ? null : JSON.stringify(input.governance) });
|
|
368
|
+
this.db.connection.prepare('UPDATE skills SET current_version_id=?, updated_at=? WHERE id=?').run(version.id, now, skill.id);
|
|
369
|
+
const updatedSkill = this.getById(skill.id);
|
|
370
|
+
if (!updatedSkill.ok)
|
|
371
|
+
throw new Error(updatedSkill.error.message);
|
|
372
|
+
const updated = this.getVersion(version.id);
|
|
373
|
+
if (!updated.ok)
|
|
374
|
+
throw new Error(updated.error.message);
|
|
375
|
+
const event = this.recordLifecycleEvent({
|
|
376
|
+
project: updatedSkill.value.project,
|
|
377
|
+
scope: updatedSkill.value.scope,
|
|
378
|
+
skillId: updatedSkill.value.id,
|
|
379
|
+
versionId: updated.value.id,
|
|
380
|
+
eventType: 'version-published',
|
|
381
|
+
actor: input.actor,
|
|
382
|
+
...(input.runId === undefined ? {} : { runId: input.runId }),
|
|
383
|
+
...(input.agentId === undefined ? {} : { agentId: input.agentId }),
|
|
384
|
+
reason: input.reason,
|
|
385
|
+
before,
|
|
386
|
+
after: { skill: { currentVersionId: updated.value.id }, version: versionSnapshot(updated.value) },
|
|
387
|
+
...(input.evidence === undefined ? {} : { evidence: input.evidence }),
|
|
388
|
+
});
|
|
389
|
+
if (!event.ok)
|
|
390
|
+
throw new Error(event.error.message);
|
|
391
|
+
return { skill: updatedSkill.value, version: updated.value, event: event.value, previousCurrentVersionId };
|
|
392
|
+
});
|
|
393
|
+
if (!result.ok && result.error.cause instanceof SkillRegistryValidationError)
|
|
394
|
+
return validationFailure(result.error.cause.message);
|
|
395
|
+
return result;
|
|
396
|
+
}
|
|
179
397
|
getById(id) {
|
|
180
398
|
try {
|
|
181
399
|
const row = this.db.connection.prepare('SELECT * FROM skills WHERE id=?').get(id);
|
|
@@ -246,6 +464,118 @@ export class SkillRepository {
|
|
|
246
464
|
return fail('Failed to list skills', cause);
|
|
247
465
|
}
|
|
248
466
|
}
|
|
467
|
+
search(input) {
|
|
468
|
+
try {
|
|
469
|
+
const limit = normalizeLimit(input.limit);
|
|
470
|
+
const where = ['s.project=@project', 's.scope=@scope'];
|
|
471
|
+
const parameters = { project: input.project, scope: input.scope ?? 'project', limit };
|
|
472
|
+
if (input.query !== undefined && input.query.trim().length > 0) {
|
|
473
|
+
where.push('(s.name LIKE @query OR s.description LIKE @query OR cv.version LIKE @query)');
|
|
474
|
+
parameters.query = `%${input.query.trim()}%`;
|
|
475
|
+
}
|
|
476
|
+
if (input.status !== undefined) {
|
|
477
|
+
where.push('cv.status=@status');
|
|
478
|
+
parameters.status = input.status;
|
|
479
|
+
}
|
|
480
|
+
if (input.sourceKind !== undefined) {
|
|
481
|
+
where.push('cv.source_kind=@sourceKind');
|
|
482
|
+
parameters.sourceKind = input.sourceKind;
|
|
483
|
+
}
|
|
484
|
+
const rows = this.db.connection
|
|
485
|
+
.prepare(`
|
|
486
|
+
SELECT
|
|
487
|
+
s.*,
|
|
488
|
+
cv.id AS current_version_row_id,
|
|
489
|
+
cv.version AS current_version,
|
|
490
|
+
cv.source_kind AS current_source_kind,
|
|
491
|
+
cv.source_path AS current_source_path,
|
|
492
|
+
cv.source_url AS current_source_url,
|
|
493
|
+
cv.source_inline_metadata_json AS current_source_inline_metadata_json,
|
|
494
|
+
cv.status AS current_status,
|
|
495
|
+
(SELECT COUNT(*) FROM skill_versions sv WHERE sv.skill_id=s.id) AS version_count,
|
|
496
|
+
(SELECT COUNT(*) FROM skill_attachments sa WHERE sa.skill_id=s.id) AS attachment_count
|
|
497
|
+
FROM skills s
|
|
498
|
+
LEFT JOIN skill_versions cv ON cv.id=s.current_version_id
|
|
499
|
+
WHERE ${where.join(' AND ')}
|
|
500
|
+
ORDER BY s.name ASC, s.id ASC
|
|
501
|
+
LIMIT @limit
|
|
502
|
+
`)
|
|
503
|
+
.all(parameters);
|
|
504
|
+
return ok(rows.map(mapSearchItem));
|
|
505
|
+
}
|
|
506
|
+
catch (cause) {
|
|
507
|
+
return fail('Failed to search skills', cause);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
countSearchMatches(input) {
|
|
511
|
+
try {
|
|
512
|
+
const where = ['s.project=@project', 's.scope=@scope'];
|
|
513
|
+
const parameters = { project: input.project, scope: input.scope ?? 'project' };
|
|
514
|
+
if (input.query !== undefined && input.query.trim().length > 0) {
|
|
515
|
+
where.push('(s.name LIKE @query OR s.description LIKE @query OR cv.version LIKE @query)');
|
|
516
|
+
parameters.query = `%${input.query.trim()}%`;
|
|
517
|
+
}
|
|
518
|
+
if (input.status !== undefined) {
|
|
519
|
+
where.push('cv.status=@status');
|
|
520
|
+
parameters.status = input.status;
|
|
521
|
+
}
|
|
522
|
+
if (input.sourceKind !== undefined) {
|
|
523
|
+
where.push('cv.source_kind=@sourceKind');
|
|
524
|
+
parameters.sourceKind = input.sourceKind;
|
|
525
|
+
}
|
|
526
|
+
const row = this.db.connection
|
|
527
|
+
.prepare(`SELECT COUNT(*) AS count FROM skills s LEFT JOIN skill_versions cv ON cv.id=s.current_version_id WHERE ${where.join(' AND ')}`)
|
|
528
|
+
.get(parameters);
|
|
529
|
+
return ok(row.count);
|
|
530
|
+
}
|
|
531
|
+
catch (cause) {
|
|
532
|
+
return fail('Failed to count skill search matches', cause);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
getV2Counts(project, scope) {
|
|
536
|
+
try {
|
|
537
|
+
const versions = zeroVersionCounts();
|
|
538
|
+
for (const row of this.db.connection
|
|
539
|
+
.prepare(`SELECT sv.status AS status, COUNT(*) AS count FROM skill_versions sv JOIN skills s ON s.id=sv.skill_id WHERE s.project=? AND s.scope=? GROUP BY sv.status`)
|
|
540
|
+
.all(project, scope)) {
|
|
541
|
+
versions[row.status] = row.count;
|
|
542
|
+
}
|
|
543
|
+
const proposals = zeroProposalCounts();
|
|
544
|
+
for (const row of this.db.connection
|
|
545
|
+
.prepare(`SELECT p.status AS status, COUNT(*) AS count FROM skill_improvement_proposals p JOIN skills s ON s.id=p.skill_id WHERE s.project=? AND s.scope=? GROUP BY p.status`)
|
|
546
|
+
.all(project, scope)) {
|
|
547
|
+
proposals[row.status] = row.count;
|
|
548
|
+
}
|
|
549
|
+
const evaluationRequests = zeroEvaluationRequestCounts();
|
|
550
|
+
for (const row of this.db.connection
|
|
551
|
+
.prepare('SELECT status, COUNT(*) AS count FROM skill_evaluation_requests WHERE project=? AND scope=? GROUP BY status')
|
|
552
|
+
.all(project, scope)) {
|
|
553
|
+
evaluationRequests[row.status] = row.count;
|
|
554
|
+
}
|
|
555
|
+
const evaluationResults = zeroEvaluationResultCounts();
|
|
556
|
+
for (const row of this.db.connection
|
|
557
|
+
.prepare(`SELECT r.status AS status, COUNT(*) AS count FROM skill_evaluation_results r JOIN skill_evaluation_scenarios e ON e.id=r.scenario_id JOIN skills s ON s.id=e.skill_id WHERE s.project=? AND s.scope=? GROUP BY r.status`)
|
|
558
|
+
.all(project, scope)) {
|
|
559
|
+
evaluationResults[row.status] = row.count;
|
|
560
|
+
}
|
|
561
|
+
return ok({
|
|
562
|
+
versions,
|
|
563
|
+
attachments: countScoped(this.db, 'skill_attachments', project, scope),
|
|
564
|
+
usageRecords: countScoped(this.db, 'skill_usage_records', project, scope),
|
|
565
|
+
activationRecords: countDirectScoped(this.db, 'skill_activation_records', project, scope),
|
|
566
|
+
improvementProposals: proposals,
|
|
567
|
+
evaluationScenarios: countScoped(this.db, 'skill_evaluation_scenarios', project, scope),
|
|
568
|
+
evaluationRequests,
|
|
569
|
+
evaluationResults,
|
|
570
|
+
drafts: versions.draft,
|
|
571
|
+
deprecated: versions.deprecated,
|
|
572
|
+
archived: versions.archived,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
catch (cause) {
|
|
576
|
+
return fail('Failed to count skill registry v2 records', cause);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
249
579
|
listVersions(skillId) {
|
|
250
580
|
try {
|
|
251
581
|
const rows = this.db.connection.prepare('SELECT * FROM skill_versions WHERE skill_id=? ORDER BY created_at DESC, version DESC').all(skillId);
|
|
@@ -296,6 +626,15 @@ export class SkillRepository {
|
|
|
296
626
|
return fail('Failed to read skill version', cause);
|
|
297
627
|
}
|
|
298
628
|
}
|
|
629
|
+
getVersionBySkillVersion(skillId, version) {
|
|
630
|
+
try {
|
|
631
|
+
const row = this.findVersion(skillId, version);
|
|
632
|
+
return row ? ok(mapVersion(row)) : missing(`Skill version not found: ${version}`);
|
|
633
|
+
}
|
|
634
|
+
catch (cause) {
|
|
635
|
+
return fail('Failed to read skill version', cause);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
299
638
|
getAttachment(id) {
|
|
300
639
|
try {
|
|
301
640
|
const row = this.db.connection.prepare('SELECT * FROM skill_attachments WHERE id=?').get(id);
|
|
@@ -314,6 +653,48 @@ export class SkillRepository {
|
|
|
314
653
|
return fail('Failed to read skill usage record', cause);
|
|
315
654
|
}
|
|
316
655
|
}
|
|
656
|
+
getActivation(id) {
|
|
657
|
+
try {
|
|
658
|
+
const row = this.db.connection.prepare('SELECT * FROM skill_activation_records WHERE id=?').get(id);
|
|
659
|
+
return row ? ok(mapActivation(row)) : missing(`Skill activation record not found: ${id}`);
|
|
660
|
+
}
|
|
661
|
+
catch (cause) {
|
|
662
|
+
return fail('Failed to read skill activation record', cause);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
recordLifecycleEvent(input) {
|
|
666
|
+
try {
|
|
667
|
+
const id = randomUUID();
|
|
668
|
+
const now = new Date().toISOString();
|
|
669
|
+
this.db.connection
|
|
670
|
+
.prepare(`
|
|
671
|
+
INSERT INTO skill_lifecycle_events(id, project, scope, skill_id, version_id, event_type, actor_type, actor_id, run_id, agent_id, reason, before_json, after_json, evidence_json, created_at)
|
|
672
|
+
VALUES (@id, @project, @scope, @skillId, @versionId, @eventType, @actorType, @actorId, @runId, @agentId, @reason, @beforeJson, @afterJson, @evidenceJson, @createdAt)
|
|
673
|
+
`)
|
|
674
|
+
.run({
|
|
675
|
+
id,
|
|
676
|
+
project: input.project,
|
|
677
|
+
scope: input.scope,
|
|
678
|
+
skillId: input.skillId,
|
|
679
|
+
versionId: input.versionId ?? null,
|
|
680
|
+
eventType: input.eventType,
|
|
681
|
+
actorType: input.actor?.type ?? null,
|
|
682
|
+
actorId: input.actor?.id ?? null,
|
|
683
|
+
runId: input.runId ?? null,
|
|
684
|
+
agentId: input.agentId ?? null,
|
|
685
|
+
reason: input.reason ?? null,
|
|
686
|
+
beforeJson: input.before === undefined ? null : JSON.stringify(input.before),
|
|
687
|
+
afterJson: input.after === undefined ? null : JSON.stringify(input.after),
|
|
688
|
+
evidenceJson: input.evidence === undefined ? null : JSON.stringify(input.evidence),
|
|
689
|
+
createdAt: now,
|
|
690
|
+
});
|
|
691
|
+
const row = this.db.connection.prepare('SELECT * FROM skill_lifecycle_events WHERE id=?').get(id);
|
|
692
|
+
return row ? ok(mapLifecycleEvent(row)) : missing(`Skill lifecycle event not found: ${id}`);
|
|
693
|
+
}
|
|
694
|
+
catch (cause) {
|
|
695
|
+
return fail('Failed to record skill lifecycle event', cause);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
317
698
|
findByNaturalKey(project, scope, name) {
|
|
318
699
|
return this.db.connection.prepare('SELECT * FROM skills WHERE project=? AND scope=? AND name=?').get(project, scope, name);
|
|
319
700
|
}
|
|
@@ -357,6 +738,81 @@ function validateUsage(input) {
|
|
|
357
738
|
return validationFailure('Skill usage target key is required when target type is set');
|
|
358
739
|
if (input.targetType === undefined && input.targetKey !== undefined)
|
|
359
740
|
return validationFailure('Skill usage target type is required when target key is set');
|
|
741
|
+
if (input.versionId !== undefined && !input.versionId.trim())
|
|
742
|
+
return validationFailure('Skill version id must not be empty');
|
|
743
|
+
return ok(undefined);
|
|
744
|
+
}
|
|
745
|
+
function validateActivation(input) {
|
|
746
|
+
if (!input.project.trim())
|
|
747
|
+
return validationFailure('Skill activation project is required');
|
|
748
|
+
if (!input.skillId.trim())
|
|
749
|
+
return validationFailure('Skill id is required');
|
|
750
|
+
if (input.versionId !== undefined && !input.versionId.trim())
|
|
751
|
+
return validationFailure('Skill version id must not be empty');
|
|
752
|
+
return ok(undefined);
|
|
753
|
+
}
|
|
754
|
+
function validateCreateDraft(input) {
|
|
755
|
+
if (!input.project.trim())
|
|
756
|
+
return validationFailure('Skill draft project is required');
|
|
757
|
+
if (!input.name.trim())
|
|
758
|
+
return validationFailure('Skill draft name is required');
|
|
759
|
+
if (!input.description.trim())
|
|
760
|
+
return validationFailure('Skill draft description is required');
|
|
761
|
+
if (!input.version.trim())
|
|
762
|
+
return validationFailure('Skill draft version is required');
|
|
763
|
+
const source = validateSource(input.source);
|
|
764
|
+
if (!source.ok)
|
|
765
|
+
return source;
|
|
766
|
+
return validateLifecycleActor(input.actor);
|
|
767
|
+
}
|
|
768
|
+
function validateUpdateDraft(input) {
|
|
769
|
+
if (!input.project.trim())
|
|
770
|
+
return validationFailure('Skill draft project is required');
|
|
771
|
+
if ((input.skillId === undefined || !input.skillId.trim()) && (input.name === undefined || !input.name.trim()))
|
|
772
|
+
return validationFailure('Either skillId or name is required');
|
|
773
|
+
if ((input.versionId === undefined || !input.versionId.trim()) && (input.version === undefined || !input.version.trim()))
|
|
774
|
+
return validationFailure('Either versionId or version is required');
|
|
775
|
+
if (input.versionId !== undefined && input.version !== undefined)
|
|
776
|
+
return validationFailure('Use either versionId or version, not both');
|
|
777
|
+
if (input.source === undefined && input.compatibility === undefined && input.governance === undefined)
|
|
778
|
+
return validationFailure('At least one draft update field is required');
|
|
779
|
+
if (input.expectedDraftRevision !== undefined && (!Number.isSafeInteger(input.expectedDraftRevision) || input.expectedDraftRevision < 1)) {
|
|
780
|
+
return validationFailure('expectedDraftRevision must be a positive safe integer');
|
|
781
|
+
}
|
|
782
|
+
if (input.source !== undefined) {
|
|
783
|
+
const source = validateSource(input.source);
|
|
784
|
+
if (!source.ok)
|
|
785
|
+
return source;
|
|
786
|
+
}
|
|
787
|
+
return validateLifecycleActor(input.actor);
|
|
788
|
+
}
|
|
789
|
+
function validatePublishVersion(input) {
|
|
790
|
+
if (!input.project.trim())
|
|
791
|
+
return validationFailure('Skill publish project is required');
|
|
792
|
+
if ((input.skillId === undefined || !input.skillId.trim()) && (input.name === undefined || !input.name.trim()))
|
|
793
|
+
return validationFailure('Either skillId or name is required');
|
|
794
|
+
if ((input.versionId === undefined || !input.versionId.trim()) && (input.version === undefined || !input.version.trim()))
|
|
795
|
+
return validationFailure('Either versionId or version is required');
|
|
796
|
+
if (input.versionId !== undefined && input.version !== undefined)
|
|
797
|
+
return validationFailure('Use either versionId or version, not both');
|
|
798
|
+
if (!input.reason.trim())
|
|
799
|
+
return validationFailure('Publish reason is required');
|
|
800
|
+
return validateLifecycleActor(input.actor);
|
|
801
|
+
}
|
|
802
|
+
function validateLifecycleActor(actor) {
|
|
803
|
+
if (!['human', 'agent', 'system'].includes(actor.type))
|
|
804
|
+
return validationFailure('Lifecycle actor type must be human, agent, or system');
|
|
805
|
+
if (!actor.id.trim())
|
|
806
|
+
return validationFailure('Lifecycle actor id is required');
|
|
807
|
+
return ok(undefined);
|
|
808
|
+
}
|
|
809
|
+
function validateSource(source) {
|
|
810
|
+
if (source.kind === 'path' && !source.path?.trim())
|
|
811
|
+
return validationFailure('Path skill sources require source.path');
|
|
812
|
+
if (source.kind === 'url' && !source.url?.trim())
|
|
813
|
+
return validationFailure('URL skill sources require source.url');
|
|
814
|
+
if (source.kind === 'inline' && (source.path !== undefined || source.url !== undefined))
|
|
815
|
+
return validationFailure('Inline skill sources cannot include source.path or source.url');
|
|
360
816
|
return ok(undefined);
|
|
361
817
|
}
|
|
362
818
|
function assertVersionBelongsToSkill(db, versionId, skillId) {
|
|
@@ -380,6 +836,36 @@ function mapSkill(row) {
|
|
|
380
836
|
skill.currentVersionId = row.current_version_id;
|
|
381
837
|
return skill;
|
|
382
838
|
}
|
|
839
|
+
function mapSearchItem(row) {
|
|
840
|
+
const source = mapNullableSource(row.current_source_kind, row.current_source_path, row.current_source_url, row.current_source_inline_metadata_json);
|
|
841
|
+
const currentVersionId = row.current_version_row_id ?? null;
|
|
842
|
+
return {
|
|
843
|
+
id: row.id,
|
|
844
|
+
project: row.project,
|
|
845
|
+
scope: row.scope,
|
|
846
|
+
name: row.name,
|
|
847
|
+
description: row.description,
|
|
848
|
+
currentVersionId,
|
|
849
|
+
currentVersion: row.current_version ?? null,
|
|
850
|
+
status: row.current_status ?? (row.current_version_id === null ? 'missing-current-version' : 'missing-current-version-record'),
|
|
851
|
+
source,
|
|
852
|
+
attachments: Number(row.attachment_count ?? 0),
|
|
853
|
+
versions: Number(row.version_count ?? 0),
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function mapNullableSource(kind, path, url, inlineMetadataJson) {
|
|
857
|
+
if (kind === null)
|
|
858
|
+
return null;
|
|
859
|
+
const source = { kind: kind };
|
|
860
|
+
if (path !== null)
|
|
861
|
+
source.path = path;
|
|
862
|
+
if (url !== null)
|
|
863
|
+
source.url = url;
|
|
864
|
+
const inlineMetadata = JSON.parse(inlineMetadataJson ?? '{}');
|
|
865
|
+
if (Object.keys(inlineMetadata).length > 0)
|
|
866
|
+
source.inlineMetadata = inlineMetadata;
|
|
867
|
+
return source;
|
|
868
|
+
}
|
|
383
869
|
function mapVersion(row) {
|
|
384
870
|
const source = { kind: row.source_kind };
|
|
385
871
|
if (row.source_path !== null)
|
|
@@ -389,7 +875,7 @@ function mapVersion(row) {
|
|
|
389
875
|
const inlineMetadata = JSON.parse(row.source_inline_metadata_json ?? '{}');
|
|
390
876
|
if (Object.keys(inlineMetadata).length > 0)
|
|
391
877
|
source.inlineMetadata = inlineMetadata;
|
|
392
|
-
|
|
878
|
+
const version = {
|
|
393
879
|
id: row.id,
|
|
394
880
|
skillId: row.skill_id,
|
|
395
881
|
version: row.version,
|
|
@@ -398,6 +884,78 @@ function mapVersion(row) {
|
|
|
398
884
|
status: row.status,
|
|
399
885
|
createdAt: row.created_at,
|
|
400
886
|
};
|
|
887
|
+
if (row.lifecycle_label !== null && row.lifecycle_label !== undefined)
|
|
888
|
+
version.lifecycleLabel = row.lifecycle_label;
|
|
889
|
+
if (row.draft_parent_version_id !== null && row.draft_parent_version_id !== undefined)
|
|
890
|
+
version.draftParentVersionId = row.draft_parent_version_id;
|
|
891
|
+
if (row.draft_revision !== null && row.draft_revision !== undefined)
|
|
892
|
+
version.draftRevision = row.draft_revision;
|
|
893
|
+
if (row.governance_json !== null && row.governance_json !== undefined)
|
|
894
|
+
version.governance = JSON.parse(row.governance_json);
|
|
895
|
+
if (row.published_at !== null && row.published_at !== undefined)
|
|
896
|
+
version.publishedAt = row.published_at;
|
|
897
|
+
if (row.published_by !== null && row.published_by !== undefined)
|
|
898
|
+
version.publishedBy = row.published_by;
|
|
899
|
+
if (row.deprecated_at !== null && row.deprecated_at !== undefined)
|
|
900
|
+
version.deprecatedAt = row.deprecated_at;
|
|
901
|
+
if (row.archived_at !== null && row.archived_at !== undefined)
|
|
902
|
+
version.archivedAt = row.archived_at;
|
|
903
|
+
return version;
|
|
904
|
+
}
|
|
905
|
+
function mapLifecycleEvent(row) {
|
|
906
|
+
const event = {
|
|
907
|
+
id: row.id,
|
|
908
|
+
project: row.project,
|
|
909
|
+
scope: row.scope,
|
|
910
|
+
skillId: row.skill_id,
|
|
911
|
+
eventType: row.event_type,
|
|
912
|
+
createdAt: row.created_at,
|
|
913
|
+
};
|
|
914
|
+
if (row.version_id !== null)
|
|
915
|
+
event.versionId = row.version_id;
|
|
916
|
+
if (row.actor_type !== null && row.actor_id !== null)
|
|
917
|
+
event.actor = { type: row.actor_type, id: row.actor_id };
|
|
918
|
+
if (row.run_id !== null)
|
|
919
|
+
event.runId = row.run_id;
|
|
920
|
+
if (row.agent_id !== null)
|
|
921
|
+
event.agentId = row.agent_id;
|
|
922
|
+
if (row.reason !== null)
|
|
923
|
+
event.reason = row.reason;
|
|
924
|
+
if (row.before_json !== null)
|
|
925
|
+
event.before = JSON.parse(row.before_json);
|
|
926
|
+
if (row.after_json !== null)
|
|
927
|
+
event.after = JSON.parse(row.after_json);
|
|
928
|
+
if (row.evidence_json !== null)
|
|
929
|
+
event.evidence = JSON.parse(row.evidence_json);
|
|
930
|
+
return event;
|
|
931
|
+
}
|
|
932
|
+
function resolveSkillForMutation(repository, project, scope, skillId, name) {
|
|
933
|
+
const result = skillId !== undefined ? repository.getById(skillId) : repository.getByName(project, scope, name ?? '');
|
|
934
|
+
if (!result.ok)
|
|
935
|
+
throw new SkillRegistryValidationError(result.error.message);
|
|
936
|
+
if (result.value.project !== project || result.value.scope !== scope)
|
|
937
|
+
throw new SkillRegistryValidationError('Skill does not belong to the requested project/scope');
|
|
938
|
+
return result.value;
|
|
939
|
+
}
|
|
940
|
+
function resolveVersionForMutation(repository, skillId, versionId, version) {
|
|
941
|
+
const result = versionId !== undefined ? repository.getVersion(versionId) : repository.getVersionBySkillVersion(skillId, version ?? '');
|
|
942
|
+
if (!result.ok)
|
|
943
|
+
throw new SkillRegistryValidationError(result.error.message);
|
|
944
|
+
if (result.value.skillId !== skillId)
|
|
945
|
+
throw new SkillRegistryValidationError('Skill version must belong to the requested skill');
|
|
946
|
+
return result.value;
|
|
947
|
+
}
|
|
948
|
+
function versionSnapshot(version) {
|
|
949
|
+
return {
|
|
950
|
+
id: version.id,
|
|
951
|
+
skillId: version.skillId,
|
|
952
|
+
version: version.version,
|
|
953
|
+
status: version.status,
|
|
954
|
+
draftRevision: version.draftRevision ?? null,
|
|
955
|
+
lifecycleLabel: version.lifecycleLabel ?? null,
|
|
956
|
+
publishedAt: version.publishedAt ?? null,
|
|
957
|
+
publishedBy: version.publishedBy ?? null,
|
|
958
|
+
};
|
|
401
959
|
}
|
|
402
960
|
function mapAttachment(row) {
|
|
403
961
|
const attachment = {
|
|
@@ -429,8 +987,48 @@ function mapUsage(row) {
|
|
|
429
987
|
usage.targetKey = row.target_key;
|
|
430
988
|
if (row.notes !== null)
|
|
431
989
|
usage.notes = row.notes;
|
|
990
|
+
if (row.workflow !== null)
|
|
991
|
+
usage.workflow = row.workflow;
|
|
992
|
+
if (row.phase !== null)
|
|
993
|
+
usage.phase = row.phase;
|
|
994
|
+
if (row.provider_adapter !== null)
|
|
995
|
+
usage.providerAdapter = row.provider_adapter;
|
|
996
|
+
if (row.agent_id !== null)
|
|
997
|
+
usage.agentId = row.agent_id;
|
|
998
|
+
if (row.intent_json !== null)
|
|
999
|
+
usage.intent = JSON.parse(row.intent_json);
|
|
1000
|
+
if (row.evidence_json !== null)
|
|
1001
|
+
usage.evidence = JSON.parse(row.evidence_json);
|
|
432
1002
|
return usage;
|
|
433
1003
|
}
|
|
1004
|
+
function mapActivation(row) {
|
|
1005
|
+
const activation = {
|
|
1006
|
+
id: row.id,
|
|
1007
|
+
project: row.project,
|
|
1008
|
+
scope: row.scope,
|
|
1009
|
+
skillId: row.skill_id,
|
|
1010
|
+
createdAt: row.created_at,
|
|
1011
|
+
};
|
|
1012
|
+
if (row.version_id !== null)
|
|
1013
|
+
activation.versionId = row.version_id;
|
|
1014
|
+
if (row.run_id !== null)
|
|
1015
|
+
activation.runId = row.run_id;
|
|
1016
|
+
if (row.agent_id !== null)
|
|
1017
|
+
activation.agentId = row.agent_id;
|
|
1018
|
+
if (row.workflow !== null)
|
|
1019
|
+
activation.workflow = row.workflow;
|
|
1020
|
+
if (row.phase !== null)
|
|
1021
|
+
activation.phase = row.phase;
|
|
1022
|
+
if (row.provider_adapter !== null)
|
|
1023
|
+
activation.providerAdapter = row.provider_adapter;
|
|
1024
|
+
if (row.intent_json !== null)
|
|
1025
|
+
activation.intent = JSON.parse(row.intent_json);
|
|
1026
|
+
if (row.reason !== null)
|
|
1027
|
+
activation.reason = row.reason;
|
|
1028
|
+
if (row.payload_metadata_json !== null)
|
|
1029
|
+
activation.payloadMetadata = JSON.parse(row.payload_metadata_json);
|
|
1030
|
+
return activation;
|
|
1031
|
+
}
|
|
434
1032
|
function ok(value) {
|
|
435
1033
|
return { ok: true, value };
|
|
436
1034
|
}
|
|
@@ -448,3 +1046,32 @@ function fail(message, cause) {
|
|
|
448
1046
|
}
|
|
449
1047
|
class SkillRegistryValidationError extends Error {
|
|
450
1048
|
}
|
|
1049
|
+
function normalizeLimit(limit) {
|
|
1050
|
+
if (limit === undefined)
|
|
1051
|
+
return 25;
|
|
1052
|
+
if (!Number.isSafeInteger(limit) || limit < 1)
|
|
1053
|
+
return 25;
|
|
1054
|
+
return Math.min(limit, 100);
|
|
1055
|
+
}
|
|
1056
|
+
function zeroVersionCounts() {
|
|
1057
|
+
return { draft: 0, active: 0, deprecated: 0, archived: 0 };
|
|
1058
|
+
}
|
|
1059
|
+
function zeroProposalCounts() {
|
|
1060
|
+
return { draft: 0, 'pending-approval': 0, approved: 0, rejected: 0, cancelled: 0, applied: 0 };
|
|
1061
|
+
}
|
|
1062
|
+
function zeroEvaluationResultCounts() {
|
|
1063
|
+
return { passed: 0, failed: 0, 'needs-review': 0, 'not-applicable': 0 };
|
|
1064
|
+
}
|
|
1065
|
+
function zeroEvaluationRequestCounts() {
|
|
1066
|
+
return { requested: 0, planned: 0, running: 0, completed: 0, failed: 0, cancelled: 0, skipped: 0 };
|
|
1067
|
+
}
|
|
1068
|
+
function countScoped(db, table, project, scope) {
|
|
1069
|
+
const row = db.connection
|
|
1070
|
+
.prepare(`SELECT COUNT(*) AS count FROM ${table} t JOIN skills s ON s.id=t.skill_id WHERE s.project=? AND s.scope=?`)
|
|
1071
|
+
.get(project, scope);
|
|
1072
|
+
return row.count;
|
|
1073
|
+
}
|
|
1074
|
+
function countDirectScoped(db, table, project, scope) {
|
|
1075
|
+
const row = db.connection.prepare(`SELECT COUNT(*) AS count FROM ${table} WHERE project=? AND scope=?`).get(project, scope);
|
|
1076
|
+
return row.count;
|
|
1077
|
+
}
|