vgxness 0.1.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/LICENSE +9 -0
- package/README.md +110 -0
- package/dist/agents/agent-activation-service.js +144 -0
- package/dist/agents/agent-registry-service.js +46 -0
- package/dist/agents/agent-resolver.js +249 -0
- package/dist/agents/agent-seed-service.js +146 -0
- package/dist/agents/manager-profile-overlay-service.js +34 -0
- package/dist/agents/profile-model-routing.js +26 -0
- package/dist/agents/renderers/claude-renderer.js +98 -0
- package/dist/agents/renderers/index.js +16 -0
- package/dist/agents/renderers/json-renderer.js +87 -0
- package/dist/agents/renderers/opencode-renderer.js +100 -0
- package/dist/agents/renderers/provider-adapter.js +6 -0
- package/dist/agents/repositories/agents.js +185 -0
- package/dist/agents/repositories/manager-profile-overlays.js +81 -0
- package/dist/agents/schema.js +1 -0
- package/dist/cli/dashboard-operational-read-models.js +153 -0
- package/dist/cli/dashboard-renderer.js +109 -0
- package/dist/cli/dashboard-screen-renderers.js +332 -0
- package/dist/cli/dashboard-tui-read-model.js +71 -0
- package/dist/cli/dashboard-tui-state.js +218 -0
- package/dist/cli/dispatcher.js +2880 -0
- package/dist/cli/index.js +27 -0
- package/dist/cli/interactive-dashboard.js +29 -0
- package/dist/cli/mcp-start-path.js +21 -0
- package/dist/cli/setup-status-renderer.js +29 -0
- package/dist/cli/setup-wizard-read-model.js +56 -0
- package/dist/cli/setup-wizard-renderer.js +148 -0
- package/dist/cli/setup-wizard-state.js +82 -0
- package/dist/cli/tui-render-helpers.js +192 -0
- package/dist/export/redaction.js +71 -0
- package/dist/harness/tools/agents.js +245 -0
- package/dist/harness/tools/memory.js +29 -0
- package/dist/mcp/client-install-opencode-contract.js +227 -0
- package/dist/mcp/client-install-opencode.js +194 -0
- package/dist/mcp/client-setup-preview.js +38 -0
- package/dist/mcp/control-plane.js +175 -0
- package/dist/mcp/doctor.js +193 -0
- package/dist/mcp/index.js +10 -0
- package/dist/mcp/opencode-default-agent-config.js +156 -0
- package/dist/mcp/opencode-visibility.js +102 -0
- package/dist/mcp/schema.js +234 -0
- package/dist/mcp/stdio-server.js +56 -0
- package/dist/mcp/validation.js +761 -0
- package/dist/memory/import/dry-run-planner.js +58 -0
- package/dist/memory/import/index.js +3 -0
- package/dist/memory/import/observation-writer.js +220 -0
- package/dist/memory/import/package.js +178 -0
- package/dist/memory/memory-service.js +126 -0
- package/dist/memory/repositories/artifacts.js +41 -0
- package/dist/memory/repositories/observations.js +133 -0
- package/dist/memory/repositories/sessions.js +105 -0
- package/dist/memory/repositories/traces.js +58 -0
- package/dist/memory/schema.js +1 -0
- package/dist/memory/search.js +11 -0
- package/dist/memory/sqlite/database.js +97 -0
- package/dist/memory/sqlite/migrations/001_initial.sql +128 -0
- package/dist/memory/sqlite/migrations/002_observation_revisions.sql +14 -0
- package/dist/memory/sqlite/migrations/003_agent_registry.sql +26 -0
- package/dist/memory/sqlite/migrations/004_run_runtime.sql +62 -0
- package/dist/memory/sqlite/migrations/005_run_approvals.sql +20 -0
- package/dist/memory/sqlite/migrations/006_run_operation_attempts.sql +32 -0
- package/dist/memory/sqlite/migrations/007_abandoned_operation_attempts.sql +46 -0
- package/dist/memory/sqlite/migrations/008_run_execution_plan_events.sql +105 -0
- package/dist/memory/sqlite/migrations/009_multiple_operation_attempts.sql +73 -0
- package/dist/memory/sqlite/migrations/010_skill_registry.sql +66 -0
- package/dist/memory/sqlite/migrations/011_skill_usage_resolution_outcomes.sql +21 -0
- package/dist/memory/sqlite/migrations/012_skill_improvement_proposals.sql +37 -0
- package/dist/memory/sqlite/migrations/013_skill_evaluation_scenarios.sql +43 -0
- package/dist/memory/sqlite/migrations/014_manager_profile_overlays.sql +14 -0
- package/dist/memory/storage-paths.js +72 -0
- package/dist/orchestrator/natural-language-planner.js +191 -0
- package/dist/orchestrator/schema.js +1 -0
- package/dist/permissions/index.js +2 -0
- package/dist/permissions/policy-evaluator.js +109 -0
- package/dist/permissions/schema.js +1 -0
- package/dist/providers/opencode/injection-preview.js +134 -0
- package/dist/providers/opencode/manager-payload.js +129 -0
- package/dist/runs/execution-planning.js +117 -0
- package/dist/runs/operation-execution.js +1 -0
- package/dist/runs/operation-retry.js +124 -0
- package/dist/runs/repositories/runs.js +611 -0
- package/dist/runs/run-insights.js +145 -0
- package/dist/runs/run-service.js +713 -0
- package/dist/runs/run-snapshot-export-service.js +31 -0
- package/dist/runs/sandbox-process-execution.js +218 -0
- package/dist/runs/sandbox-worktree-planning.js +59 -0
- package/dist/runs/schema.js +1 -0
- package/dist/sdd/artifact-portability-service.js +118 -0
- package/dist/sdd/schema.js +17 -0
- package/dist/sdd/sdd-workflow-service.js +217 -0
- package/dist/setup/backup-rollback-service.js +76 -0
- package/dist/setup/index.js +3 -0
- package/dist/setup/providers/antigravity-setup-adapter.js +18 -0
- package/dist/setup/providers/claude-setup-adapter.js +30 -0
- package/dist/setup/providers/custom-setup-adapter.js +18 -0
- package/dist/setup/providers/index.js +6 -0
- package/dist/setup/providers/opencode-setup-adapter.js +104 -0
- package/dist/setup/providers/provider-setup-adapter.js +15 -0
- package/dist/setup/providers/provider-setup-registry.js +11 -0
- package/dist/setup/schema.js +1 -0
- package/dist/setup/setup-defaults.js +11 -0
- package/dist/setup/setup-lifecycle-service.js +175 -0
- package/dist/setup/setup-plan.js +105 -0
- package/dist/skills/repositories/skill-evaluation-scenarios.js +289 -0
- package/dist/skills/repositories/skill-improvement-proposals.js +288 -0
- package/dist/skills/repositories/skills.js +430 -0
- package/dist/skills/schema.js +1 -0
- package/dist/skills/skill-payload.js +94 -0
- package/dist/skills/skill-registry-service.js +92 -0
- package/dist/skills/skill-resolver.js +191 -0
- package/dist/workflows/command-allowlist-adapter.js +70 -0
- package/dist/workflows/schema.js +4 -0
- package/dist/workflows/workflow-executor.js +345 -0
- package/dist/workflows/workflow-registry.js +66 -0
- package/docs/architecture.md +698 -0
- package/docs/cli.md +741 -0
- package/docs/funcionamiento-del-sistema.md +868 -0
- package/docs/harness-gap-analysis.md +229 -0
- package/docs/prd.md +372 -0
- package/package.json +57 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
export class SkillRepository {
|
|
3
|
+
db;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
}
|
|
7
|
+
register(input) {
|
|
8
|
+
const validation = validateSkill(input);
|
|
9
|
+
if (!validation.ok)
|
|
10
|
+
return validation;
|
|
11
|
+
const result = this.db.transaction(() => {
|
|
12
|
+
const existing = this.findByNaturalKey(input.project, input.scope, input.name);
|
|
13
|
+
const id = existing?.id ?? randomUUID();
|
|
14
|
+
const now = new Date().toISOString();
|
|
15
|
+
this.db.connection.prepare(`
|
|
16
|
+
INSERT INTO skills(id, project, scope, name, description, created_at, updated_at)
|
|
17
|
+
VALUES (@id, @project, @scope, @name, @description, @createdAt, @updatedAt)
|
|
18
|
+
ON CONFLICT(project, scope, name) DO UPDATE SET
|
|
19
|
+
description=excluded.description,
|
|
20
|
+
updated_at=excluded.updated_at
|
|
21
|
+
`).run({
|
|
22
|
+
id,
|
|
23
|
+
project: input.project,
|
|
24
|
+
scope: input.scope,
|
|
25
|
+
name: input.name,
|
|
26
|
+
description: input.description,
|
|
27
|
+
createdAt: existing?.created_at ?? now,
|
|
28
|
+
updatedAt: now,
|
|
29
|
+
});
|
|
30
|
+
const read = this.getById(id);
|
|
31
|
+
if (!read.ok)
|
|
32
|
+
throw new Error(read.error.message);
|
|
33
|
+
return read.value;
|
|
34
|
+
});
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
addVersion(input) {
|
|
38
|
+
const validation = validateVersion(input);
|
|
39
|
+
if (!validation.ok)
|
|
40
|
+
return validation;
|
|
41
|
+
const result = this.db.transaction(() => {
|
|
42
|
+
const skill = this.getById(input.skillId);
|
|
43
|
+
if (!skill.ok)
|
|
44
|
+
throw new SkillRegistryValidationError(skill.error.message);
|
|
45
|
+
const existing = this.findVersion(input.skillId, input.version);
|
|
46
|
+
const id = existing?.id ?? randomUUID();
|
|
47
|
+
const now = new Date().toISOString();
|
|
48
|
+
const status = input.activate === true ? 'active' : input.status ?? existing?.status ?? 'draft';
|
|
49
|
+
this.db.connection.prepare(`
|
|
50
|
+
INSERT INTO skill_versions(id, skill_id, version, source_kind, source_path, source_url, source_inline_metadata_json, compatibility_json, status, created_at)
|
|
51
|
+
VALUES (@id, @skillId, @version, @sourceKind, @sourcePath, @sourceUrl, @sourceInlineMetadataJson, @compatibilityJson, @status, @createdAt)
|
|
52
|
+
ON CONFLICT(skill_id, version) DO UPDATE SET
|
|
53
|
+
source_kind=excluded.source_kind,
|
|
54
|
+
source_path=excluded.source_path,
|
|
55
|
+
source_url=excluded.source_url,
|
|
56
|
+
source_inline_metadata_json=excluded.source_inline_metadata_json,
|
|
57
|
+
compatibility_json=excluded.compatibility_json,
|
|
58
|
+
status=excluded.status
|
|
59
|
+
`).run({
|
|
60
|
+
id,
|
|
61
|
+
skillId: input.skillId,
|
|
62
|
+
version: input.version,
|
|
63
|
+
sourceKind: input.source.kind,
|
|
64
|
+
sourcePath: input.source.path ?? null,
|
|
65
|
+
sourceUrl: input.source.url ?? null,
|
|
66
|
+
sourceInlineMetadataJson: JSON.stringify(input.source.inlineMetadata ?? {}),
|
|
67
|
+
compatibilityJson: JSON.stringify(input.compatibility ?? {}),
|
|
68
|
+
status,
|
|
69
|
+
createdAt: existing?.created_at ?? now,
|
|
70
|
+
});
|
|
71
|
+
if (input.activate === true) {
|
|
72
|
+
this.db.connection.prepare('UPDATE skills SET current_version_id=?, updated_at=? WHERE id=?').run(id, now, input.skillId);
|
|
73
|
+
}
|
|
74
|
+
const read = this.getVersion(id);
|
|
75
|
+
if (!read.ok)
|
|
76
|
+
throw new Error(read.error.message);
|
|
77
|
+
return read.value;
|
|
78
|
+
});
|
|
79
|
+
if (!result.ok && result.error.cause instanceof SkillRegistryValidationError) {
|
|
80
|
+
return validationFailure(result.error.cause.message);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
attach(input) {
|
|
85
|
+
const validation = validateAttachment(input);
|
|
86
|
+
if (!validation.ok)
|
|
87
|
+
return validation;
|
|
88
|
+
const result = this.db.transaction(() => {
|
|
89
|
+
const skill = this.getById(input.skillId);
|
|
90
|
+
if (!skill.ok)
|
|
91
|
+
throw new SkillRegistryValidationError(skill.error.message);
|
|
92
|
+
if (input.versionId !== undefined)
|
|
93
|
+
assertVersionBelongsToSkill(this.db, input.versionId, input.skillId);
|
|
94
|
+
const existing = this.db.connection.prepare('SELECT * FROM skill_attachments WHERE skill_id=? AND target_type=? AND target_key=?').get(input.skillId, input.targetType, input.targetKey);
|
|
95
|
+
const id = existing?.id ?? randomUUID();
|
|
96
|
+
const now = new Date().toISOString();
|
|
97
|
+
this.db.connection.prepare(`
|
|
98
|
+
INSERT INTO skill_attachments(id, skill_id, version_id, target_type, target_key, metadata_json, created_at)
|
|
99
|
+
VALUES (@id, @skillId, @versionId, @targetType, @targetKey, @metadataJson, @createdAt)
|
|
100
|
+
ON CONFLICT(skill_id, target_type, target_key) DO UPDATE SET
|
|
101
|
+
version_id=excluded.version_id,
|
|
102
|
+
metadata_json=excluded.metadata_json
|
|
103
|
+
`).run({
|
|
104
|
+
id,
|
|
105
|
+
skillId: input.skillId,
|
|
106
|
+
versionId: input.versionId ?? null,
|
|
107
|
+
targetType: input.targetType,
|
|
108
|
+
targetKey: input.targetKey,
|
|
109
|
+
metadataJson: JSON.stringify(input.metadata ?? {}),
|
|
110
|
+
createdAt: existing?.created_at ?? now,
|
|
111
|
+
});
|
|
112
|
+
const read = this.getAttachment(id);
|
|
113
|
+
if (!read.ok)
|
|
114
|
+
throw new Error(read.error.message);
|
|
115
|
+
return read.value;
|
|
116
|
+
});
|
|
117
|
+
if (!result.ok && result.error.cause instanceof SkillRegistryValidationError) {
|
|
118
|
+
return validationFailure(result.error.cause.message);
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
detach(skillId, targetType, targetKey) {
|
|
123
|
+
try {
|
|
124
|
+
const result = this.db.connection.prepare('DELETE FROM skill_attachments WHERE skill_id=? AND target_type=? AND target_key=?').run(skillId, targetType, targetKey);
|
|
125
|
+
return ok({ detached: result.changes > 0 });
|
|
126
|
+
}
|
|
127
|
+
catch (cause) {
|
|
128
|
+
return fail('Failed to detach skill', cause);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
recordUsage(input) {
|
|
132
|
+
const validation = validateUsage(input);
|
|
133
|
+
if (!validation.ok)
|
|
134
|
+
return validation;
|
|
135
|
+
const result = this.db.transaction(() => {
|
|
136
|
+
const skill = this.getById(input.skillId);
|
|
137
|
+
if (!skill.ok)
|
|
138
|
+
throw new SkillRegistryValidationError(skill.error.message);
|
|
139
|
+
if (input.versionId !== undefined)
|
|
140
|
+
assertVersionBelongsToSkill(this.db, input.versionId, input.skillId);
|
|
141
|
+
const id = randomUUID();
|
|
142
|
+
const now = new Date().toISOString();
|
|
143
|
+
this.db.connection.prepare(`
|
|
144
|
+
INSERT INTO skill_usage_records(id, skill_id, version_id, run_id, target_type, target_key, outcome, notes, created_at)
|
|
145
|
+
VALUES (@id, @skillId, @versionId, @runId, @targetType, @targetKey, @outcome, @notes, @createdAt)
|
|
146
|
+
`).run({
|
|
147
|
+
id,
|
|
148
|
+
skillId: input.skillId,
|
|
149
|
+
versionId: input.versionId ?? null,
|
|
150
|
+
runId: input.runId ?? null,
|
|
151
|
+
targetType: input.targetType ?? null,
|
|
152
|
+
targetKey: input.targetKey ?? null,
|
|
153
|
+
outcome: input.outcome,
|
|
154
|
+
notes: input.notes ?? null,
|
|
155
|
+
createdAt: now,
|
|
156
|
+
});
|
|
157
|
+
const read = this.getUsage(id);
|
|
158
|
+
if (!read.ok)
|
|
159
|
+
throw new Error(read.error.message);
|
|
160
|
+
return read.value;
|
|
161
|
+
});
|
|
162
|
+
if (!result.ok && result.error.cause instanceof SkillRegistryValidationError) {
|
|
163
|
+
return validationFailure(result.error.cause.message);
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
getById(id) {
|
|
168
|
+
try {
|
|
169
|
+
const row = this.db.connection.prepare('SELECT * FROM skills WHERE id=?').get(id);
|
|
170
|
+
return row ? ok(mapSkill(row)) : missing(`Skill not found: ${id}`);
|
|
171
|
+
}
|
|
172
|
+
catch (cause) {
|
|
173
|
+
return fail('Failed to read skill', cause);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
getByName(project, scope, name) {
|
|
177
|
+
try {
|
|
178
|
+
const row = this.findByNaturalKey(project, scope, name);
|
|
179
|
+
return row ? ok(mapSkill(row)) : missing(`Skill not found: ${project}/${scope}/${name}`);
|
|
180
|
+
}
|
|
181
|
+
catch (cause) {
|
|
182
|
+
return fail('Failed to read skill by name', cause);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
getDetails(id) {
|
|
186
|
+
const skill = this.getById(id);
|
|
187
|
+
if (!skill.ok)
|
|
188
|
+
return skill;
|
|
189
|
+
const versions = this.listVersions(id);
|
|
190
|
+
if (!versions.ok)
|
|
191
|
+
return versions;
|
|
192
|
+
const attachments = this.listAttachments(id);
|
|
193
|
+
if (!attachments.ok)
|
|
194
|
+
return attachments;
|
|
195
|
+
const usage = this.listUsage(id);
|
|
196
|
+
if (!usage.ok)
|
|
197
|
+
return usage;
|
|
198
|
+
const details = { ...skill.value, versions: versions.value, attachments: attachments.value, usage: usage.value };
|
|
199
|
+
const currentVersion = versions.value.find((version) => version.id === skill.value.currentVersionId);
|
|
200
|
+
if (currentVersion !== undefined)
|
|
201
|
+
details.currentVersion = currentVersion;
|
|
202
|
+
return ok(details);
|
|
203
|
+
}
|
|
204
|
+
list(filters = {}) {
|
|
205
|
+
try {
|
|
206
|
+
const where = [];
|
|
207
|
+
const parameters = {};
|
|
208
|
+
if (filters.project !== undefined) {
|
|
209
|
+
where.push('project=@project');
|
|
210
|
+
parameters.project = filters.project;
|
|
211
|
+
}
|
|
212
|
+
if (filters.scope !== undefined) {
|
|
213
|
+
where.push('scope=@scope');
|
|
214
|
+
parameters.scope = filters.scope;
|
|
215
|
+
}
|
|
216
|
+
const rows = this.db.connection.prepare(`
|
|
217
|
+
SELECT * FROM skills
|
|
218
|
+
${where.length ? `WHERE ${where.join(' AND ')}` : ''}
|
|
219
|
+
ORDER BY name ASC
|
|
220
|
+
`).all(parameters);
|
|
221
|
+
return ok(rows.map((row) => {
|
|
222
|
+
const summary = mapSkill(row);
|
|
223
|
+
if (summary.currentVersionId !== undefined) {
|
|
224
|
+
const version = this.getVersion(summary.currentVersionId);
|
|
225
|
+
if (version.ok)
|
|
226
|
+
summary.currentVersion = version.value;
|
|
227
|
+
}
|
|
228
|
+
return summary;
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
catch (cause) {
|
|
232
|
+
return fail('Failed to list skills', cause);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
listVersions(skillId) {
|
|
236
|
+
try {
|
|
237
|
+
const rows = this.db.connection.prepare('SELECT * FROM skill_versions WHERE skill_id=? ORDER BY created_at DESC, version DESC').all(skillId);
|
|
238
|
+
return ok(rows.map(mapVersion));
|
|
239
|
+
}
|
|
240
|
+
catch (cause) {
|
|
241
|
+
return fail('Failed to list skill versions', cause);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
listAttachments(skillId) {
|
|
245
|
+
try {
|
|
246
|
+
const rows = this.db.connection.prepare('SELECT * FROM skill_attachments WHERE skill_id=? ORDER BY target_type ASC, target_key ASC').all(skillId);
|
|
247
|
+
return ok(rows.map(mapAttachment));
|
|
248
|
+
}
|
|
249
|
+
catch (cause) {
|
|
250
|
+
return fail('Failed to list skill attachments', cause);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
listAttachmentsForTargets(targets) {
|
|
254
|
+
try {
|
|
255
|
+
if (targets.length === 0)
|
|
256
|
+
return ok([]);
|
|
257
|
+
const rows = [];
|
|
258
|
+
const read = this.db.connection.prepare('SELECT * FROM skill_attachments WHERE target_type=? AND target_key=? ORDER BY created_at ASC, id ASC');
|
|
259
|
+
for (const target of targets)
|
|
260
|
+
rows.push(...read.all(target.targetType, target.targetKey));
|
|
261
|
+
return ok(rows.map(mapAttachment));
|
|
262
|
+
}
|
|
263
|
+
catch (cause) {
|
|
264
|
+
return fail('Failed to list skill attachments for targets', cause);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
listUsage(skillId) {
|
|
268
|
+
try {
|
|
269
|
+
const rows = this.db.connection.prepare('SELECT * FROM skill_usage_records WHERE skill_id=? ORDER BY created_at DESC').all(skillId);
|
|
270
|
+
return ok(rows.map(mapUsage));
|
|
271
|
+
}
|
|
272
|
+
catch (cause) {
|
|
273
|
+
return fail('Failed to list skill usage', cause);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
getVersion(id) {
|
|
277
|
+
try {
|
|
278
|
+
const row = this.db.connection.prepare('SELECT * FROM skill_versions WHERE id=?').get(id);
|
|
279
|
+
return row ? ok(mapVersion(row)) : missing(`Skill version not found: ${id}`);
|
|
280
|
+
}
|
|
281
|
+
catch (cause) {
|
|
282
|
+
return fail('Failed to read skill version', cause);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
getAttachment(id) {
|
|
286
|
+
try {
|
|
287
|
+
const row = this.db.connection.prepare('SELECT * FROM skill_attachments WHERE id=?').get(id);
|
|
288
|
+
return row ? ok(mapAttachment(row)) : missing(`Skill attachment not found: ${id}`);
|
|
289
|
+
}
|
|
290
|
+
catch (cause) {
|
|
291
|
+
return fail('Failed to read skill attachment', cause);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
getUsage(id) {
|
|
295
|
+
try {
|
|
296
|
+
const row = this.db.connection.prepare('SELECT * FROM skill_usage_records WHERE id=?').get(id);
|
|
297
|
+
return row ? ok(mapUsage(row)) : missing(`Skill usage record not found: ${id}`);
|
|
298
|
+
}
|
|
299
|
+
catch (cause) {
|
|
300
|
+
return fail('Failed to read skill usage record', cause);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
findByNaturalKey(project, scope, name) {
|
|
304
|
+
return this.db.connection.prepare('SELECT * FROM skills WHERE project=? AND scope=? AND name=?').get(project, scope, name);
|
|
305
|
+
}
|
|
306
|
+
findVersion(skillId, version) {
|
|
307
|
+
return this.db.connection.prepare('SELECT * FROM skill_versions WHERE skill_id=? AND version=?').get(skillId, version);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function validateSkill(input) {
|
|
311
|
+
if (!input.project.trim())
|
|
312
|
+
return validationFailure('Skill project is required');
|
|
313
|
+
if (!input.name.trim())
|
|
314
|
+
return validationFailure('Skill name is required');
|
|
315
|
+
if (!input.description.trim())
|
|
316
|
+
return validationFailure('Skill description is required');
|
|
317
|
+
return ok(undefined);
|
|
318
|
+
}
|
|
319
|
+
function validateVersion(input) {
|
|
320
|
+
if (!input.skillId.trim())
|
|
321
|
+
return validationFailure('Skill id is required');
|
|
322
|
+
if (!input.version.trim())
|
|
323
|
+
return validationFailure('Skill version is required');
|
|
324
|
+
if (input.source.kind === 'path' && !input.source.path?.trim())
|
|
325
|
+
return validationFailure('Path skill sources require source.path');
|
|
326
|
+
if (input.source.kind === 'url' && !input.source.url?.trim())
|
|
327
|
+
return validationFailure('URL skill sources require source.url');
|
|
328
|
+
if (input.source.kind === 'inline' && (input.source.path !== undefined || input.source.url !== undefined))
|
|
329
|
+
return validationFailure('Inline skill sources cannot include source.path or source.url');
|
|
330
|
+
return ok(undefined);
|
|
331
|
+
}
|
|
332
|
+
function validateAttachment(input) {
|
|
333
|
+
if (!input.skillId.trim())
|
|
334
|
+
return validationFailure('Skill id is required');
|
|
335
|
+
if (!input.targetKey.trim())
|
|
336
|
+
return validationFailure('Skill attachment target key is required');
|
|
337
|
+
return ok(undefined);
|
|
338
|
+
}
|
|
339
|
+
function validateUsage(input) {
|
|
340
|
+
if (!input.skillId.trim())
|
|
341
|
+
return validationFailure('Skill id is required');
|
|
342
|
+
if (input.targetType !== undefined && !input.targetKey?.trim())
|
|
343
|
+
return validationFailure('Skill usage target key is required when target type is set');
|
|
344
|
+
if (input.targetType === undefined && input.targetKey !== undefined)
|
|
345
|
+
return validationFailure('Skill usage target type is required when target key is set');
|
|
346
|
+
return ok(undefined);
|
|
347
|
+
}
|
|
348
|
+
function assertVersionBelongsToSkill(db, versionId, skillId) {
|
|
349
|
+
const row = db.connection.prepare('SELECT skill_id FROM skill_versions WHERE id=?').get(versionId);
|
|
350
|
+
if (row === undefined)
|
|
351
|
+
throw new SkillRegistryValidationError(`Skill version not found: ${versionId}`);
|
|
352
|
+
if (row.skill_id !== skillId)
|
|
353
|
+
throw new SkillRegistryValidationError('Skill version must belong to the attached skill');
|
|
354
|
+
}
|
|
355
|
+
function mapSkill(row) {
|
|
356
|
+
const skill = {
|
|
357
|
+
id: row.id,
|
|
358
|
+
project: row.project,
|
|
359
|
+
scope: row.scope,
|
|
360
|
+
name: row.name,
|
|
361
|
+
description: row.description,
|
|
362
|
+
createdAt: row.created_at,
|
|
363
|
+
updatedAt: row.updated_at,
|
|
364
|
+
};
|
|
365
|
+
if (row.current_version_id !== null)
|
|
366
|
+
skill.currentVersionId = row.current_version_id;
|
|
367
|
+
return skill;
|
|
368
|
+
}
|
|
369
|
+
function mapVersion(row) {
|
|
370
|
+
const source = { kind: row.source_kind };
|
|
371
|
+
if (row.source_path !== null)
|
|
372
|
+
source.path = row.source_path;
|
|
373
|
+
if (row.source_url !== null)
|
|
374
|
+
source.url = row.source_url;
|
|
375
|
+
const inlineMetadata = JSON.parse(row.source_inline_metadata_json ?? '{}');
|
|
376
|
+
if (Object.keys(inlineMetadata).length > 0)
|
|
377
|
+
source.inlineMetadata = inlineMetadata;
|
|
378
|
+
return {
|
|
379
|
+
id: row.id,
|
|
380
|
+
skillId: row.skill_id,
|
|
381
|
+
version: row.version,
|
|
382
|
+
source,
|
|
383
|
+
compatibility: JSON.parse(row.compatibility_json),
|
|
384
|
+
status: row.status,
|
|
385
|
+
createdAt: row.created_at,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function mapAttachment(row) {
|
|
389
|
+
const attachment = {
|
|
390
|
+
id: row.id,
|
|
391
|
+
skillId: row.skill_id,
|
|
392
|
+
targetType: row.target_type,
|
|
393
|
+
targetKey: row.target_key,
|
|
394
|
+
metadata: JSON.parse(row.metadata_json),
|
|
395
|
+
createdAt: row.created_at,
|
|
396
|
+
};
|
|
397
|
+
if (row.version_id !== null)
|
|
398
|
+
attachment.versionId = row.version_id;
|
|
399
|
+
return attachment;
|
|
400
|
+
}
|
|
401
|
+
function mapUsage(row) {
|
|
402
|
+
const usage = {
|
|
403
|
+
id: row.id,
|
|
404
|
+
skillId: row.skill_id,
|
|
405
|
+
outcome: row.outcome,
|
|
406
|
+
createdAt: row.created_at,
|
|
407
|
+
};
|
|
408
|
+
if (row.version_id !== null)
|
|
409
|
+
usage.versionId = row.version_id;
|
|
410
|
+
if (row.run_id !== null)
|
|
411
|
+
usage.runId = row.run_id;
|
|
412
|
+
if (row.target_type !== null)
|
|
413
|
+
usage.targetType = row.target_type;
|
|
414
|
+
if (row.target_key !== null)
|
|
415
|
+
usage.targetKey = row.target_key;
|
|
416
|
+
if (row.notes !== null)
|
|
417
|
+
usage.notes = row.notes;
|
|
418
|
+
return usage;
|
|
419
|
+
}
|
|
420
|
+
function ok(value) { return { ok: true, value }; }
|
|
421
|
+
function missing(message) { return { ok: false, error: { code: 'not_found', message } }; }
|
|
422
|
+
function validationFailure(message) { return { ok: false, error: { code: 'validation_failed', message } }; }
|
|
423
|
+
function fail(message, cause) {
|
|
424
|
+
const error = { code: 'validation_failed', message };
|
|
425
|
+
if (cause !== undefined)
|
|
426
|
+
error.cause = cause;
|
|
427
|
+
return { ok: false, error };
|
|
428
|
+
}
|
|
429
|
+
class SkillRegistryValidationError extends Error {
|
|
430
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute, relative, resolve } from 'node:path';
|
|
3
|
+
const defaultMaxSourceBytes = 64 * 1024;
|
|
4
|
+
export function buildSkillInjectionPayload(resolution, options = {}) {
|
|
5
|
+
const workspaceRoot = options.workspaceRoot !== undefined ? safeRealpath(options.workspaceRoot) : undefined;
|
|
6
|
+
if (options.workspaceRoot !== undefined && workspaceRoot === undefined)
|
|
7
|
+
return validationFailure(`Workspace root is unavailable: ${options.workspaceRoot}`);
|
|
8
|
+
const maxSourceBytes = options.maxSourceBytes ?? defaultMaxSourceBytes;
|
|
9
|
+
const items = resolution.skills.map((skill, index) => toPayloadItem(skill, index, workspaceRoot, maxSourceBytes));
|
|
10
|
+
return {
|
|
11
|
+
ok: true,
|
|
12
|
+
value: {
|
|
13
|
+
version: 1,
|
|
14
|
+
providerAgnostic: true,
|
|
15
|
+
context: resolution.context,
|
|
16
|
+
items,
|
|
17
|
+
skipped: resolution.skipped,
|
|
18
|
+
warnings: ['Skill payload v1 is injection-ready data only; it does not mutate provider configuration or fetch URL sources.'],
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function toPayloadItem(skill, index, workspaceRoot, maxSourceBytes) {
|
|
23
|
+
const loaded = loadContent(skill, workspaceRoot, maxSourceBytes);
|
|
24
|
+
return {
|
|
25
|
+
identity: {
|
|
26
|
+
skillId: skill.skillId,
|
|
27
|
+
name: skill.name,
|
|
28
|
+
description: skill.description,
|
|
29
|
+
versionId: skill.versionId,
|
|
30
|
+
version: skill.version,
|
|
31
|
+
},
|
|
32
|
+
source: {
|
|
33
|
+
kind: skill.source.kind,
|
|
34
|
+
...(skill.source.path !== undefined ? { path: skill.source.path } : {}),
|
|
35
|
+
...(skill.source.url !== undefined ? { url: skill.source.url } : {}),
|
|
36
|
+
},
|
|
37
|
+
content: loaded,
|
|
38
|
+
attachmentReasons: skill.attachmentSources,
|
|
39
|
+
ordering: {
|
|
40
|
+
index,
|
|
41
|
+
dedupeKey: skill.skillId,
|
|
42
|
+
attachmentSourceCount: skill.attachmentSources.length,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function loadContent(skill, workspaceRoot, maxSourceBytes) {
|
|
47
|
+
if (skill.source.kind === 'inline') {
|
|
48
|
+
if (skill.content !== undefined)
|
|
49
|
+
return { status: 'available', value: skill.content };
|
|
50
|
+
if (skill.summary !== undefined)
|
|
51
|
+
return { status: 'available', value: skill.summary };
|
|
52
|
+
return { status: 'unavailable', unavailableReason: 'inline_content_missing' };
|
|
53
|
+
}
|
|
54
|
+
if (skill.source.kind === 'url')
|
|
55
|
+
return { status: 'unavailable', unavailableReason: 'url_fetch_disabled' };
|
|
56
|
+
if (workspaceRoot === undefined)
|
|
57
|
+
return { status: 'unavailable', unavailableReason: 'workspace_root_required' };
|
|
58
|
+
if (skill.source.path === undefined || !skill.source.path.trim())
|
|
59
|
+
return { status: 'unavailable', unavailableReason: 'path_missing' };
|
|
60
|
+
const requested = isAbsolute(skill.source.path) ? skill.source.path : resolve(workspaceRoot, skill.source.path);
|
|
61
|
+
if (!isWithinRoot(requested, workspaceRoot))
|
|
62
|
+
return { status: 'unavailable', unavailableReason: 'path_outside_workspace' };
|
|
63
|
+
const real = safeRealpath(requested);
|
|
64
|
+
if (real === undefined)
|
|
65
|
+
return { status: 'unavailable', unavailableReason: 'path_not_found' };
|
|
66
|
+
if (!isWithinRoot(real, workspaceRoot))
|
|
67
|
+
return { status: 'unavailable', unavailableReason: 'path_outside_workspace' };
|
|
68
|
+
try {
|
|
69
|
+
const stats = statSync(real);
|
|
70
|
+
if (!stats.isFile())
|
|
71
|
+
return { status: 'unavailable', unavailableReason: 'path_not_file' };
|
|
72
|
+
if (stats.size > maxSourceBytes)
|
|
73
|
+
return { status: 'unavailable', unavailableReason: 'path_too_large' };
|
|
74
|
+
return { status: 'available', value: readFileSync(real, 'utf8') };
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return { status: 'unavailable', unavailableReason: 'path_read_failed' };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function safeRealpath(path) {
|
|
81
|
+
try {
|
|
82
|
+
return realpathSync(path);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function isWithinRoot(path, root) {
|
|
89
|
+
const relation = relative(root, path);
|
|
90
|
+
return relation === '' || (!relation.startsWith('..') && !isAbsolute(relation));
|
|
91
|
+
}
|
|
92
|
+
function validationFailure(message) {
|
|
93
|
+
return { ok: false, error: { code: 'validation_failed', message } };
|
|
94
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { AgentRepository } from '../agents/repositories/agents.js';
|
|
2
|
+
import { SkillEvaluationScenarioRepository } from './repositories/skill-evaluation-scenarios.js';
|
|
3
|
+
import { SkillImprovementProposalRepository } from './repositories/skill-improvement-proposals.js';
|
|
4
|
+
import { SkillRepository } from './repositories/skills.js';
|
|
5
|
+
import { buildSkillInjectionPayload } from './skill-payload.js';
|
|
6
|
+
import { SkillResolver } from './skill-resolver.js';
|
|
7
|
+
export class SkillRegistryService {
|
|
8
|
+
database;
|
|
9
|
+
skills;
|
|
10
|
+
proposals;
|
|
11
|
+
evaluationScenarios;
|
|
12
|
+
constructor(database) {
|
|
13
|
+
this.database = database;
|
|
14
|
+
this.skills = new SkillRepository(database);
|
|
15
|
+
this.proposals = new SkillImprovementProposalRepository(database);
|
|
16
|
+
this.evaluationScenarios = new SkillEvaluationScenarioRepository(database);
|
|
17
|
+
}
|
|
18
|
+
registerSkill(input) {
|
|
19
|
+
return this.skills.register(input);
|
|
20
|
+
}
|
|
21
|
+
addSkillVersion(input) {
|
|
22
|
+
return this.skills.addVersion(input);
|
|
23
|
+
}
|
|
24
|
+
attachSkill(input) {
|
|
25
|
+
return this.skills.attach(input);
|
|
26
|
+
}
|
|
27
|
+
detachSkill(skillId, targetType, targetKey) {
|
|
28
|
+
return this.skills.detach(skillId, targetType, targetKey);
|
|
29
|
+
}
|
|
30
|
+
recordSkillUsage(input) {
|
|
31
|
+
return this.skills.recordUsage(input);
|
|
32
|
+
}
|
|
33
|
+
getSkill(id) {
|
|
34
|
+
return this.skills.getById(id);
|
|
35
|
+
}
|
|
36
|
+
getSkillByName(project, scope, name) {
|
|
37
|
+
return this.skills.getByName(project, scope, name);
|
|
38
|
+
}
|
|
39
|
+
getSkillDetails(id) {
|
|
40
|
+
return this.skills.getDetails(id);
|
|
41
|
+
}
|
|
42
|
+
listSkills(filters = {}) {
|
|
43
|
+
return this.skills.list(filters);
|
|
44
|
+
}
|
|
45
|
+
listSkillVersions(skillId) {
|
|
46
|
+
return this.skills.listVersions(skillId);
|
|
47
|
+
}
|
|
48
|
+
resolveSkills(input) {
|
|
49
|
+
return new SkillResolver(this.skills, new AgentRepository(this.database)).resolve(input);
|
|
50
|
+
}
|
|
51
|
+
createSkillImprovementProposal(input) {
|
|
52
|
+
return this.proposals.create(input);
|
|
53
|
+
}
|
|
54
|
+
listSkillImprovementProposals(filters = {}) {
|
|
55
|
+
return this.proposals.list(filters);
|
|
56
|
+
}
|
|
57
|
+
getSkillImprovementProposal(id) {
|
|
58
|
+
return this.proposals.getById(id);
|
|
59
|
+
}
|
|
60
|
+
submitSkillImprovementProposal(input) {
|
|
61
|
+
return this.proposals.submitForApproval(input);
|
|
62
|
+
}
|
|
63
|
+
approveSkillImprovementProposal(input) {
|
|
64
|
+
return this.proposals.approve(input);
|
|
65
|
+
}
|
|
66
|
+
rejectSkillImprovementProposal(input) {
|
|
67
|
+
return this.proposals.reject(input);
|
|
68
|
+
}
|
|
69
|
+
cancelSkillImprovementProposal(input) {
|
|
70
|
+
return this.proposals.cancel(input);
|
|
71
|
+
}
|
|
72
|
+
applySkillImprovementProposal(input) {
|
|
73
|
+
return this.proposals.apply(input);
|
|
74
|
+
}
|
|
75
|
+
createSkillEvaluationScenario(input) {
|
|
76
|
+
return this.evaluationScenarios.createScenario(input);
|
|
77
|
+
}
|
|
78
|
+
listSkillEvaluationScenarios(filters) {
|
|
79
|
+
return this.evaluationScenarios.listScenarios(filters);
|
|
80
|
+
}
|
|
81
|
+
recordSkillEvaluationResult(input) {
|
|
82
|
+
return this.evaluationScenarios.recordResult(input);
|
|
83
|
+
}
|
|
84
|
+
listSkillEvaluationResults(filters = {}) {
|
|
85
|
+
return this.evaluationScenarios.listResults(filters);
|
|
86
|
+
}
|
|
87
|
+
buildSkillPayload(input, options = {}) {
|
|
88
|
+
const resolution = this.resolveSkills(input);
|
|
89
|
+
return resolution.ok ? buildSkillInjectionPayload(resolution.value, options) : resolution;
|
|
90
|
+
}
|
|
91
|
+
close() { this.database.close(); }
|
|
92
|
+
}
|