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.
@@ -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
- return {
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
+ }